From c7e120f7b4b2476ef8603877a69dbdfec74e4d50 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 14:12:42 +0000 Subject: [PATCH 1/2] fix(plugins): document project-root placeholder and fix DevboxDirRoot docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin docs described `{{ .DevboxDirRoot }}` as pointing to the project root (where devbox.json lives), but it actually resolves to `/devbox.d` — the shared parent of every plugin's DevboxDir. The nginx, caddy and apache plugins all rely on this `/devbox.d` meaning, so the implementation is correct and the documentation was wrong. This left users (issue #1987) with no documented way to reference the project root, even though a correctly-named `{{ .DevboxProjectDir }}` placeholder already existed (used by the python, poetry and apacheHttpd plugins) but was undocumented and only wired into one of the two template contexts. - Expose `DevboxProjectDir` in the create-files template context too, so the placeholder works consistently in generated file contents (it was already available for env vars, init hooks and create_files keys). - Correct the plugins README: document `{{ .DevboxProjectDir }}` as the project root and fix the `{{ .DevboxDirRoot }}` description. - Add a unit test pinning the meaning of each template placeholder. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01UXuHTgPqNNZWowkgsh7Ydw --- internal/plugin/plugin.go | 1 + internal/plugin/plugin_test.go | 74 ++++++++++++++++++++++++++++++++++ plugins/README.md | 3 +- 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 internal/plugin/plugin_test.go diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index cf2834559e8..4c2ac080989 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -147,6 +147,7 @@ func (m *Manager) createFile( var buf bytes.Buffer if err = tmpl.Execute(&buf, map[string]any{ + "DevboxProjectDir": m.ProjectDir(), "DevboxDir": filepath.Join(m.ProjectDir(), devboxDirName, name), "DevboxDirRoot": filepath.Join(m.ProjectDir(), devboxDirName), "DevboxProfileDefault": filepath.Join(m.ProjectDir(), nix.ProfilePath), diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go new file mode 100644 index 00000000000..71abc1de150 --- /dev/null +++ b/internal/plugin/plugin_test.go @@ -0,0 +1,74 @@ +// Copyright 2024 Jetify Inc. and contributors. All rights reserved. +// Use of this source code is governed by the license in the LICENSE file. + +package plugin + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// fakeIncludable is a minimal Includable used to exercise buildConfig's +// template-placeholder substitution without touching the filesystem or network. +type fakeIncludable struct { + name string +} + +func (f fakeIncludable) CanonicalName() string { return f.name } +func (f fakeIncludable) FileContent(string) ([]byte, error) { return nil, nil } +func (f fakeIncludable) Hash() string { return "" } +func (f fakeIncludable) LockfileKey() string { return f.name } + +// TestBuildConfigTemplatePlaceholders documents and locks in the meaning of the +// template placeholders available to plugins. In particular it guards against +// the confusion reported in #1987: DevboxDirRoot is the root of the devbox.d +// directory (not the project root), and DevboxProjectDir is the project root. +func TestBuildConfigTemplatePlaceholders(t *testing.T) { + projectDir := filepath.Join("/home", "user", "my-project") + const pluginName = "my-plugin" + + content := `{ + "name": "my-plugin", + "version": "0.0.1", + "create_files": { + "{{ .DevboxProjectDir }}/project-root": "a", + "{{ .DevboxDirRoot }}/dir-root": "b", + "{{ .DevboxDir }}/dir": "c", + "{{ .Virtenv }}/virtenv": "d" + } +}` + + cfg, err := buildConfig(fakeIncludable{name: pluginName}, projectDir, content) + require.NoError(t, err) + + // Invert the create_files map (contentPath -> renderedPath) so the + // assertions read naturally regardless of map ordering. + renderedByContent := map[string]string{} + for renderedPath, contentPath := range cfg.CreateFiles { + renderedByContent[contentPath] = renderedPath + } + + assert.Equal(t, + filepath.Join(projectDir, "project-root"), + renderedByContent["a"], + "DevboxProjectDir should be the project root (where devbox.json lives)", + ) + assert.Equal(t, + filepath.Join(projectDir, devboxDirName, "dir-root"), + renderedByContent["b"], + "DevboxDirRoot should be /devbox.d", + ) + assert.Equal(t, + filepath.Join(projectDir, devboxDirName, pluginName, "dir"), + renderedByContent["c"], + "DevboxDir should be /devbox.d/", + ) + assert.Equal(t, + filepath.Join(projectDir, VirtenvPath, pluginName, "virtenv"), + renderedByContent["d"], + "Virtenv should be /.devbox/virtenv/", + ) +} diff --git a/plugins/README.md b/plugins/README.md index 7d863a29abf..1e0f1583777 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -63,7 +63,8 @@ flowchart TD Devbox's Plugin System provides a few special placeholders that should be used when specifying paths for env variables and helper files: -* `{{ .DevboxDirRoot }}` – points to the root folder of their project, where the user's `devbox.json` is stored. +* `{{ .DevboxProjectDir }}` – points to the root folder of the user's project, where their `devbox.json` is stored. +* `{{ .DevboxDirRoot }}` – points to `/devbox.d`, the shared parent directory of every plugin's `DevboxDir`. (Despite its name, this is *not* the project root — use `{{ .DevboxProjectDir }}` for that.) * `{{ .DevboxDir }}` – points to `/devbox.d/`. This directory is public and added to source control by default. This directory is not modified or recreated by Devbox after the initial package installation. You should use this location for files that a user will want to modify and check-in to source control alongside their project (e.g., `.conf` files or other configs). * `{{ .Virtenv }}` – points to `/.devbox/virtenv/` whenever the plugin activates. This directory is hidden and added to `.gitignore` by default You should use this location for files or variables that a user should not check-in or edit directly. Files in this directory should be considered managed by Devbox, and may be recreated or modified after the initial installation. From 7e27b68b64552408bd1da2e0997d5959db6b779e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 14:16:38 +0000 Subject: [PATCH 2/2] ci: re-trigger CI (transient nix-installer 503 in artifacts.nixos.org) The previous run's test matrix was cancelled by fail-fast after the Nix experimental installer download returned HTTP 503 from artifacts.nixos.org, which is unrelated to the changes in this PR. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01UXuHTgPqNNZWowkgsh7Ydw