@mdulghier/devtree 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/bin/devtree.mjs +21 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +425 -0
- package/dist/cli.js.map +1 -0
- package/dist/command-builder.d.ts +11 -0
- package/dist/command-builder.js +23 -0
- package/dist/command-builder.js.map +1 -0
- package/dist/config-command.d.ts +5 -0
- package/dist/config-command.js +181 -0
- package/dist/config-command.js.map +1 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -0
- package/dist/env-file.d.ts +12 -0
- package/dist/env-file.js +114 -0
- package/dist/env-file.js.map +1 -0
- package/dist/gc.d.ts +23 -0
- package/dist/gc.js +243 -0
- package/dist/gc.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/instance.d.ts +18 -0
- package/dist/instance.js +98 -0
- package/dist/instance.js.map +1 -0
- package/dist/process.d.ts +13 -0
- package/dist/process.js +43 -0
- package/dist/process.js.map +1 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.js +34 -0
- package/dist/registry.js.map +1 -0
- package/dist/tailscale.d.ts +10 -0
- package/dist/tailscale.js +65 -0
- package/dist/tailscale.js.map +1 -0
- package/dist/vite.d.ts +3 -0
- package/dist/vite.js +72 -0
- package/dist/vite.js.map +1 -0
- package/package.json +82 -0
- package/skills/devtree-run-and-operate-devtree/SKILL.md +179 -0
- package/skills/devtree-set-up-devtree/SKILL.md +355 -0
package/dist/vite.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
function create_core_devtree_plugin() {
|
|
10
|
+
return {
|
|
11
|
+
name: "devtree",
|
|
12
|
+
apply: "serve",
|
|
13
|
+
config(user_config) {
|
|
14
|
+
const next_config = {};
|
|
15
|
+
const tailscale_host = process.env.DEVTREE_TAILSCALE_HOST?.trim();
|
|
16
|
+
if (!user_config.server?.host) {
|
|
17
|
+
next_config.server = {
|
|
18
|
+
...user_config.server,
|
|
19
|
+
host: "127.0.0.1",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (tailscale_host && user_config.server?.allowedHosts !== true) {
|
|
23
|
+
const allowed_hosts = user_config.server?.allowedHosts ?? [];
|
|
24
|
+
if (!allowed_hosts.includes(tailscale_host)) {
|
|
25
|
+
next_config.server = {
|
|
26
|
+
...next_config.server,
|
|
27
|
+
...user_config.server,
|
|
28
|
+
allowedHosts: [...allowed_hosts, tailscale_host],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (user_config.clearScreen === undefined) {
|
|
33
|
+
next_config.clearScreen = false;
|
|
34
|
+
}
|
|
35
|
+
return next_config;
|
|
36
|
+
},
|
|
37
|
+
configResolved(resolved_config) {
|
|
38
|
+
if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (resolved_config.command !== "serve") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (process.env.DEVTREE_ACTIVE === "1") {
|
|
45
|
+
console.log(`[devtree] URL ${process.env.DEVTREE_PUBLIC_URL ?? "unknown"}`);
|
|
46
|
+
console.log(`[devtree] Instance ${process.env.DEVTREE_INSTANCE_ID ?? "unknown"}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.warn("[devtree] Run `vp run devtree dev` for isolated multi-instance development.");
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function devtree_vite_plugins(config) {
|
|
54
|
+
const plugins = [create_core_devtree_plugin()];
|
|
55
|
+
if (config.env.provider !== "varlock") {
|
|
56
|
+
return plugins;
|
|
57
|
+
}
|
|
58
|
+
const package_name = "@varlock/vite-integration";
|
|
59
|
+
try {
|
|
60
|
+
const imported_module = (await import(__rewriteRelativeImportExtension(package_name)));
|
|
61
|
+
if (!imported_module.varlockVitePlugin) {
|
|
62
|
+
throw new Error(`Expected varlockVitePlugin export from ${package_name}.`);
|
|
63
|
+
}
|
|
64
|
+
plugins.unshift(imported_module.varlockVitePlugin({ ssrInjectMode: "init-only" }));
|
|
65
|
+
return plugins;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
throw new Error(`Varlock mode is enabled, but ${package_name} could not be loaded. ${message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mdulghier/devtree",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Worktree-aware multi-instance dev orchestration for Vite+ apps.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"devtree",
|
|
7
|
+
"portless",
|
|
8
|
+
"varlock",
|
|
9
|
+
"vite",
|
|
10
|
+
"vite-plus",
|
|
11
|
+
"worktree",
|
|
12
|
+
"tanstack-intent"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/mdulghier/devtree#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/mdulghier/devtree.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/mdulghier/devtree/issues"
|
|
21
|
+
},
|
|
22
|
+
"license": "UNLICENSED",
|
|
23
|
+
"bin": {
|
|
24
|
+
"devtree": "./bin/devtree.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"skills",
|
|
31
|
+
"!skills/_artifacts"
|
|
32
|
+
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./vite": {
|
|
42
|
+
"types": "./dist/vite.d.ts",
|
|
43
|
+
"import": "./dist/vite.js"
|
|
44
|
+
},
|
|
45
|
+
"./package.json": "./package.json"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.build.json",
|
|
49
|
+
"prepare": "npm run build",
|
|
50
|
+
"prepack": "npm run build",
|
|
51
|
+
"ci": "npm run check && npm test && npm run build",
|
|
52
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
53
|
+
"test": "vp test run src/*.test.ts"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"dotenv": "^17.3.1",
|
|
57
|
+
"tsx": "^4.21.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^24.5.2",
|
|
61
|
+
"@tanstack/intent": "^0.0.23",
|
|
62
|
+
"typescript": "^5.7.2",
|
|
63
|
+
"vite-plus": "latest"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@varlock/vite-integration": "*",
|
|
67
|
+
"varlock": "*",
|
|
68
|
+
"vite-plus": "*"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"@varlock/vite-integration": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"varlock": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=24"
|
|
80
|
+
},
|
|
81
|
+
"packageManager": "pnpm@10.32.1"
|
|
82
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devtree-run-and-operate-devtree
|
|
3
|
+
description: >
|
|
4
|
+
Run and troubleshoot daily devtree workflows: `doctor`, `setup`, `dev`,
|
|
5
|
+
`info`, `env write`, `env show`, `deps start|stop|logs`, and `gc`. Load
|
|
6
|
+
this when an agent needs to verify that the app is running, reachable at
|
|
7
|
+
the worktree URL, and cleaned up correctly after worktrees are deleted.
|
|
8
|
+
type: core
|
|
9
|
+
library: devtree
|
|
10
|
+
library_version: "0.1.0"
|
|
11
|
+
sources:
|
|
12
|
+
- "mdulghier/devtree:README.md"
|
|
13
|
+
- "mdulghier/devtree:src/cli.ts"
|
|
14
|
+
- "mdulghier/devtree:src/command-builder.ts"
|
|
15
|
+
- "mdulghier/devtree:src/gc.ts"
|
|
16
|
+
- "mdulghier/devtree:src/registry.ts"
|
|
17
|
+
- "mdulghier/devtree:src/process.ts"
|
|
18
|
+
- "mdulghier/devtree:src/instance.ts"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Devtree - Run And Operate
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
Use the CLI in this order when bringing up a worktree instance.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm devtree doctor --fix
|
|
29
|
+
pnpm devtree setup
|
|
30
|
+
pnpm devtree dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then inspect the assigned URL and names.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm devtree info
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For cleanup and dependency inspection:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm devtree deps logs
|
|
43
|
+
pnpm devtree gc --dry-run
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Core Patterns
|
|
47
|
+
|
|
48
|
+
### Verify health with doctor first
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm devtree doctor --fix
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use `doctor` as the first check; it validates local prerequisites and bootstraps `portless` when needed.
|
|
55
|
+
|
|
56
|
+
### Bring up dependencies before the dev server
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pnpm devtree setup
|
|
60
|
+
pnpm devtree dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`setup` writes env overrides, starts configured dependencies, and runs setup hooks before `dev` starts the app.
|
|
64
|
+
|
|
65
|
+
### Inspect the current instance URL and names
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pnpm devtree info
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`info` prints the public URL, app name, instance ID, scoped name, worktree path, env provider, env file, and Compose project names.
|
|
72
|
+
|
|
73
|
+
### Clean up orphaned Docker resources safely
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pnpm devtree gc --dry-run
|
|
77
|
+
pnpm devtree gc
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Dry-run first, then remove orphaned dependency resources created by deleted worktrees.
|
|
81
|
+
|
|
82
|
+
## Common Mistakes
|
|
83
|
+
|
|
84
|
+
### CRITICAL Start Vite directly instead of devtree
|
|
85
|
+
|
|
86
|
+
Wrong:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"scripts": {
|
|
91
|
+
"dev": "vp dev"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pnpm dev
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Correct:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"scripts": {
|
|
105
|
+
"devtree": "devtree"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pnpm devtree dev
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Starting raw Vite skips devtree's portless wrapping and runtime env injection, so multi-worktree isolation disappears.
|
|
115
|
+
|
|
116
|
+
Source: `README.md`, `src/command-builder.ts:17`
|
|
117
|
+
|
|
118
|
+
### HIGH Disable portless and expect APP_URL to exist
|
|
119
|
+
|
|
120
|
+
Wrong:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
PORTLESS=0 pnpm devtree dev
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Correct:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
BETTER_AUTH_URL=http://localhost:3000 PORTLESS=0 pnpm devtree dev
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
When portless is disabled, devtree does not inject the public URL, so callback-based auth flows need an explicit fallback URL.
|
|
133
|
+
|
|
134
|
+
Source: `src/cli.ts:499`
|
|
135
|
+
|
|
136
|
+
### HIGH Skip setup when dependencies or hooks matter
|
|
137
|
+
|
|
138
|
+
Wrong:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pnpm devtree dev
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Correct:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pnpm devtree setup
|
|
148
|
+
pnpm devtree dev
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`dev` starts the app, but `setup` is what starts dependencies and runs `setup`, `migrate`, and `post_setup` hooks.
|
|
152
|
+
|
|
153
|
+
Source: `README.md`, `src/cli.ts:448`
|
|
154
|
+
|
|
155
|
+
### MEDIUM Assume gc removes unknown docker projects
|
|
156
|
+
|
|
157
|
+
Wrong:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pnpm devtree gc
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Correct:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
pnpm devtree gc --dry-run
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Unknown projects without worktree metadata are reported and skipped, so `gc` is not a universal Docker janitor.
|
|
170
|
+
|
|
171
|
+
Source: `src/gc.ts:356`
|
|
172
|
+
|
|
173
|
+
### HIGH Tension: portless compatibility versus public URL correctness
|
|
174
|
+
|
|
175
|
+
Disabling portless can simplify local bootstrapping in odd environments, but it removes the stable public URL contract that devtree normally provides. Agents that take the shortcut need to replace that URL explicitly.
|
|
176
|
+
|
|
177
|
+
See also: `devtree-set-up-devtree` - setup choices around portless and env injection decide whether runtime URLs work.
|
|
178
|
+
|
|
179
|
+
See also: `devtree-set-up-devtree` - configuration drives most runtime failures.
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devtree-set-up-devtree
|
|
3
|
+
description: >
|
|
4
|
+
Set up devtree in a Vite+ repository: install `devtree`, create
|
|
5
|
+
`devtree.config.ts`, define `env.entries`, choose `dotenv` or `varlock`,
|
|
6
|
+
register `devtree_vite_plugins`, and configure optional `dependencies`
|
|
7
|
+
and `hooks`. Load this when an agent needs to bootstrap worktree-aware
|
|
8
|
+
local development without URL, env, or Docker naming collisions.
|
|
9
|
+
type: core
|
|
10
|
+
library: devtree
|
|
11
|
+
library_version: "0.1.0"
|
|
12
|
+
sources:
|
|
13
|
+
- "mdulghier/devtree:README.md"
|
|
14
|
+
- "mdulghier/devtree:src/config.ts"
|
|
15
|
+
- "mdulghier/devtree:src/cli.ts"
|
|
16
|
+
- "mdulghier/devtree:src/vite.ts"
|
|
17
|
+
- "mdulghier/devtree:src/env-file.ts"
|
|
18
|
+
- "mdulghier/devtree:src/instance.ts"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Devtree - Set Up
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
Install `devtree` and register both the config file and the Vite plugin.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add -D devtree vite-plus
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { define_devtree_config } from 'devtree'
|
|
33
|
+
|
|
34
|
+
export default define_devtree_config({
|
|
35
|
+
app_name: 'my-app',
|
|
36
|
+
env: {
|
|
37
|
+
provider: 'dotenv',
|
|
38
|
+
entries: ({ instance }) => [
|
|
39
|
+
{
|
|
40
|
+
kind: 'value',
|
|
41
|
+
key: 'APP_URL',
|
|
42
|
+
value: instance.public_url,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
kind: 'value',
|
|
46
|
+
key: 'DATABASE_PORT',
|
|
47
|
+
value: String(instance.allocate_port('postgres', 5400)),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { defineConfig } from 'vite-plus'
|
|
56
|
+
import { devtree_vite_plugins } from 'devtree/vite'
|
|
57
|
+
|
|
58
|
+
import devtree_config from './devtree.config.ts'
|
|
59
|
+
|
|
60
|
+
export default defineConfig({
|
|
61
|
+
plugins: [...(await devtree_vite_plugins(devtree_config))],
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"scripts": {
|
|
68
|
+
"devtree": "devtree"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pnpm devtree doctor --fix
|
|
75
|
+
pnpm devtree setup
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Core Patterns
|
|
79
|
+
|
|
80
|
+
### Keep env values instance-derived
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { define_devtree_config } from 'devtree'
|
|
84
|
+
|
|
85
|
+
export default define_devtree_config({
|
|
86
|
+
app_name: 'my-app',
|
|
87
|
+
env: {
|
|
88
|
+
provider: 'dotenv',
|
|
89
|
+
entries: ({ instance }) => [
|
|
90
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
91
|
+
{
|
|
92
|
+
kind: 'value',
|
|
93
|
+
key: 'REDIS_PORT',
|
|
94
|
+
value: String(instance.allocate_port('redis', 6300)),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Use `instance.public_url`, `instance.get_scoped_name()` and `instance.allocate_port()` instead of fixed local values.
|
|
102
|
+
|
|
103
|
+
### Start Docker dependencies through config
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { define_devtree_config } from 'devtree'
|
|
107
|
+
|
|
108
|
+
export default define_devtree_config({
|
|
109
|
+
app_name: 'my-app',
|
|
110
|
+
env: {
|
|
111
|
+
provider: 'dotenv',
|
|
112
|
+
entries: ({ instance }) => [
|
|
113
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
dependencies: [
|
|
117
|
+
{
|
|
118
|
+
kind: 'compose',
|
|
119
|
+
name: 'postgres',
|
|
120
|
+
file_path: 'docker-compose.yml',
|
|
121
|
+
project_name: ({ instance }) => instance.get_scoped_name('db'),
|
|
122
|
+
services: ['db'],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Compose dependencies are the default way to give each worktree isolated container, network, and volume names.
|
|
129
|
+
|
|
130
|
+
### Use hooks for repo-specific setup
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { define_devtree_config } from 'devtree'
|
|
134
|
+
|
|
135
|
+
export default define_devtree_config({
|
|
136
|
+
app_name: 'my-app',
|
|
137
|
+
env: {
|
|
138
|
+
provider: 'dotenv',
|
|
139
|
+
entries: ({ instance }) => [
|
|
140
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
hooks: {
|
|
144
|
+
migrate: [
|
|
145
|
+
{
|
|
146
|
+
command: ['pnpm', 'db:migrate'],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
pre_dev: [
|
|
150
|
+
{
|
|
151
|
+
command: ['pnpm', 'codegen'],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`setup` runs `setup`, `migrate`, and `post_setup`; `dev` runs `pre_dev` before starting the server.
|
|
159
|
+
|
|
160
|
+
### Turn on varlock only with its prerequisites
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pnpm add -D varlock @varlock/vite-integration
|
|
164
|
+
touch .env.schema
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { define_devtree_config } from 'devtree'
|
|
169
|
+
|
|
170
|
+
export default define_devtree_config({
|
|
171
|
+
app_name: 'my-app',
|
|
172
|
+
env: {
|
|
173
|
+
provider: 'varlock',
|
|
174
|
+
schema_path: '.env.schema',
|
|
175
|
+
entries: ({ instance }) => [
|
|
176
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Keep the simple `dotenv` path as the default; use `varlock` when the repo already wants schema-based env handling.
|
|
183
|
+
|
|
184
|
+
## Common Mistakes
|
|
185
|
+
|
|
186
|
+
### CRITICAL Hard-code shared URLs and ports
|
|
187
|
+
|
|
188
|
+
Wrong:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { define_devtree_config } from 'devtree'
|
|
192
|
+
|
|
193
|
+
export default define_devtree_config({
|
|
194
|
+
app_name: 'my-app',
|
|
195
|
+
env: {
|
|
196
|
+
provider: 'dotenv',
|
|
197
|
+
entries: () => [
|
|
198
|
+
{ kind: 'value', key: 'APP_URL', value: 'http://localhost:3000' },
|
|
199
|
+
{ kind: 'value', key: 'DATABASE_PORT', value: '5432' },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Correct:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { define_devtree_config } from 'devtree'
|
|
209
|
+
|
|
210
|
+
export default define_devtree_config({
|
|
211
|
+
app_name: 'my-app',
|
|
212
|
+
env: {
|
|
213
|
+
provider: 'dotenv',
|
|
214
|
+
entries: ({ instance }) => [
|
|
215
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
216
|
+
{
|
|
217
|
+
kind: 'value',
|
|
218
|
+
key: 'DATABASE_PORT',
|
|
219
|
+
value: String(instance.allocate_port('postgres', 5400)),
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Static values remove the per-worktree isolation that devtree is supposed to enforce.
|
|
227
|
+
|
|
228
|
+
Source: `README.md`
|
|
229
|
+
|
|
230
|
+
### HIGH Store manual values inside managed block
|
|
231
|
+
|
|
232
|
+
Wrong:
|
|
233
|
+
|
|
234
|
+
```dotenv
|
|
235
|
+
# >>> devtree managed env >>>
|
|
236
|
+
APP_URL=http://feature-x.my-app.localhost:1355
|
|
237
|
+
STRIPE_SECRET_KEY=sk_live_manual_override
|
|
238
|
+
# <<< devtree managed env <<<
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Correct:
|
|
242
|
+
|
|
243
|
+
```dotenv
|
|
244
|
+
# >>> devtree managed env >>>
|
|
245
|
+
APP_URL=http://feature-x.my-app.localhost:1355
|
|
246
|
+
# <<< devtree managed env <<<
|
|
247
|
+
|
|
248
|
+
STRIPE_SECRET_KEY=sk_live_manual_override
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Devtree rewrites the managed block on each run and only preserves custom content outside that block.
|
|
252
|
+
|
|
253
|
+
Source: `src/env-file.ts:78`
|
|
254
|
+
|
|
255
|
+
### HIGH Force shared dependency project names
|
|
256
|
+
|
|
257
|
+
Wrong:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { define_devtree_config } from 'devtree'
|
|
261
|
+
|
|
262
|
+
export default define_devtree_config({
|
|
263
|
+
app_name: 'my-app',
|
|
264
|
+
env: {
|
|
265
|
+
provider: 'dotenv',
|
|
266
|
+
entries: ({ instance }) => [
|
|
267
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
dependencies: [
|
|
271
|
+
{
|
|
272
|
+
kind: 'compose',
|
|
273
|
+
name: 'db',
|
|
274
|
+
project_name: 'my-app',
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Correct:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { define_devtree_config } from 'devtree'
|
|
284
|
+
|
|
285
|
+
export default define_devtree_config({
|
|
286
|
+
app_name: 'my-app',
|
|
287
|
+
env: {
|
|
288
|
+
provider: 'dotenv',
|
|
289
|
+
entries: ({ instance }) => [
|
|
290
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
dependencies: [
|
|
294
|
+
{
|
|
295
|
+
kind: 'compose',
|
|
296
|
+
name: 'db',
|
|
297
|
+
project_name: ({ instance }) => instance.get_scoped_name('db'),
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Static Compose project names silently make multiple worktrees fight over the same Docker resources.
|
|
304
|
+
|
|
305
|
+
Source: `src/cli.ts:156`
|
|
306
|
+
|
|
307
|
+
### HIGH Enable varlock without integration prerequisites
|
|
308
|
+
|
|
309
|
+
Wrong:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import { define_devtree_config } from 'devtree'
|
|
313
|
+
|
|
314
|
+
export default define_devtree_config({
|
|
315
|
+
app_name: 'my-app',
|
|
316
|
+
env: {
|
|
317
|
+
provider: 'varlock',
|
|
318
|
+
entries: ({ instance }) => [
|
|
319
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
320
|
+
],
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Correct:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
pnpm add -D varlock @varlock/vite-integration
|
|
329
|
+
touch .env.schema
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import { define_devtree_config } from 'devtree'
|
|
334
|
+
|
|
335
|
+
export default define_devtree_config({
|
|
336
|
+
app_name: 'my-app',
|
|
337
|
+
env: {
|
|
338
|
+
provider: 'varlock',
|
|
339
|
+
schema_path: '.env.schema',
|
|
340
|
+
entries: ({ instance }) => [
|
|
341
|
+
{ kind: 'value', key: 'APP_URL', value: instance.public_url },
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
})
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`varlock` mode depends on the CLI, the schema file, and `@varlock/vite-integration`; missing any of them breaks setup or plugin loading.
|
|
348
|
+
|
|
349
|
+
Source: `src/cli.ts:338`, `src/vite.ts:45`
|
|
350
|
+
|
|
351
|
+
### HIGH Tension: simple setup versus explicit override freedom
|
|
352
|
+
|
|
353
|
+
Devtree works best when it owns URL, env, and dependency naming. Agents trying to be “helpful” by hard-coding familiar local values usually delete the whole point of the library.
|
|
354
|
+
|
|
355
|
+
See also: `devtree-run-and-operate-devtree` - runtime checks expose the fallout from setup shortcuts.
|