totopo 3.3.1 → 3.3.2-rc-1
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/dist/commands/dev.js +34 -11
- package/dist/lib/constants.js +5 -0
- package/package.json +1 -1
package/dist/commands/dev.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// =========================================================================================================================================
|
|
2
2
|
// src/commands/dev.ts - Start the dev container and connect via docker exec
|
|
3
|
-
// In-memory Dockerfile build, profile selection, pattern-based shadows, env_file handling.
|
|
3
|
+
// In-memory Dockerfile build, profile selection, pattern-based shadows, env_file handling, runtime env injection.
|
|
4
4
|
// =========================================================================================================================================
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
import { existsSync } from "node:fs";
|
|
7
8
|
import { join, relative } from "node:path";
|
|
8
9
|
import { cancel, confirm, isCancel, log, outro, select } from "@clack/prompts";
|
|
9
10
|
import { buildAgentContextDocs, buildAgentMountArgs, injectAgentContext } from "../lib/agent-context.js";
|
|
10
|
-
import { CONTAINER_STARTUP, CONTAINER_WORKSPACE, LABEL_MANAGED, LABEL_PROFILE, LABEL_SHADOWS, PROFILE } from "../lib/constants.js";
|
|
11
|
+
import { CONTAINER_STARTUP, CONTAINER_WORKSPACE, LABEL_MANAGED, LABEL_PROFILE, LABEL_RUNTIME_ENV, LABEL_SHADOWS, PROFILE, RUNTIME_ENV, } from "../lib/constants.js";
|
|
11
12
|
import { buildDockerfile, buildImageWithTempfile } from "../lib/dockerfile-builder.js";
|
|
12
13
|
import { isImageStale } from "../lib/migrate-to-latest.js";
|
|
13
14
|
import { buildShadowMountArgs, ensureShadowsInSync, expandShadowPatterns } from "../lib/shadows.js";
|
|
@@ -63,13 +64,13 @@ async function selectProfile(ctx, profiles) {
|
|
|
63
64
|
}
|
|
64
65
|
// Returns null when the container does not exist (docker inspect exits non-zero).
|
|
65
66
|
function inspectContainer(containerName) {
|
|
66
|
-
const fmt = `{{.State.Status}}|{{index .Config.Labels "${LABEL_SHADOWS}"}}|{{index .Config.Labels "${LABEL_PROFILE}"}}`;
|
|
67
|
+
const fmt = `{{.State.Status}}|{{index .Config.Labels "${LABEL_SHADOWS}"}}|{{index .Config.Labels "${LABEL_PROFILE}"}}|{{index .Config.Labels "${LABEL_RUNTIME_ENV}"}}`;
|
|
67
68
|
const result = spawnSync("docker", ["inspect", "--format", fmt, containerName], { encoding: "utf8", stdio: "pipe" });
|
|
68
69
|
if (result.status !== 0)
|
|
69
70
|
return null;
|
|
70
71
|
const clean = (s) => (s === "<no value>" ? "" : s);
|
|
71
|
-
const [status = "", shadows = "", profile = ""] = result.stdout.trim().split("|");
|
|
72
|
-
return { status, shadowLabel: clean(shadows), profileLabel: clean(profile) };
|
|
72
|
+
const [status = "", shadows = "", profile = "", runtimeEnv = ""] = result.stdout.trim().split("|");
|
|
73
|
+
return { status, shadowLabel: clean(shadows), profileLabel: clean(profile), runtimeEnvLabel: clean(runtimeEnv) };
|
|
73
74
|
}
|
|
74
75
|
// --- Shadow label ------------------------------------------------------------------------------------------------------------------------
|
|
75
76
|
function shadowLabel(paths) {
|
|
@@ -77,6 +78,17 @@ function shadowLabel(paths) {
|
|
|
77
78
|
return "";
|
|
78
79
|
return [...paths].sort().join(",");
|
|
79
80
|
}
|
|
81
|
+
// --- Runtime env fingerprint -------------------------------------------------------------------------------------------------------------
|
|
82
|
+
function runtimeEnvLabel() {
|
|
83
|
+
const entries = Object.entries(RUNTIME_ENV);
|
|
84
|
+
if (entries.length === 0)
|
|
85
|
+
return "";
|
|
86
|
+
const sorted = entries
|
|
87
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
88
|
+
.sort()
|
|
89
|
+
.join(",");
|
|
90
|
+
return createHash("sha256").update(sorted).digest("hex").slice(0, 12);
|
|
91
|
+
}
|
|
80
92
|
// --- Stop and remove container -----------------------------------------------------------------------------------------------------------
|
|
81
93
|
function stopAndRemoveContainer(containerName) {
|
|
82
94
|
spawnSync("docker", ["stop", containerName], { stdio: "pipe" });
|
|
@@ -114,18 +126,25 @@ export function startContainer(opts) {
|
|
|
114
126
|
`${LABEL_SHADOWS}=${shadowLabel(expandedShadows)}`,
|
|
115
127
|
"--label",
|
|
116
128
|
`${LABEL_PROFILE}=${activeProfile}`,
|
|
129
|
+
"--label",
|
|
130
|
+
`${LABEL_RUNTIME_ENV}=${runtimeEnvLabel()}`,
|
|
131
|
+
];
|
|
132
|
+
// --- Runtime env vars -----------------------------------------------------------------------------------------------------------------
|
|
133
|
+
const runtimeEnvArgs = [
|
|
134
|
+
...Object.entries(RUNTIME_ENV).flatMap(([k, v]) => ["-e", `${k}=${v}`]),
|
|
135
|
+
"-e",
|
|
136
|
+
`TOTOPO_WORKSPACE=${workspaceName}`,
|
|
117
137
|
];
|
|
118
|
-
// --- Workspace identity env var ------------------------------------------------------------------------------------------------------
|
|
119
|
-
const workspaceEnvArgs = ["-e", `TOTOPO_WORKSPACE=${workspaceName}`];
|
|
120
138
|
// --- Inspect container state ---------------------------------------------------------------------------------------------------------
|
|
121
139
|
const info = inspectContainer(containerName);
|
|
122
140
|
let containerStatus = info?.status ?? null;
|
|
123
|
-
// --- Check for shadow or
|
|
141
|
+
// --- Check for shadow, profile, or runtime env mismatch ------------------------------------------------------------------------------
|
|
124
142
|
if (info !== null) {
|
|
125
143
|
const expectedShadowLabel = shadowLabel(expandedShadows);
|
|
126
144
|
const shadowChanged = info.shadowLabel !== expectedShadowLabel;
|
|
127
145
|
const profileChanged = info.profileLabel !== activeProfile;
|
|
128
|
-
|
|
146
|
+
const runtimeEnvChanged = info.runtimeEnvLabel !== runtimeEnvLabel();
|
|
147
|
+
if (shadowChanged || profileChanged || runtimeEnvChanged) {
|
|
129
148
|
stopAndRemoveContainer(containerName);
|
|
130
149
|
containerStatus = null;
|
|
131
150
|
if (profileChanged) {
|
|
@@ -134,10 +153,14 @@ export function startContainer(opts) {
|
|
|
134
153
|
log.info(`Profile changed (${info.profileLabel} -> ${activeProfile}) — rebuilding...`);
|
|
135
154
|
spawnSync("docker", ["rmi", containerName], { stdio: "pipe" });
|
|
136
155
|
}
|
|
137
|
-
else {
|
|
156
|
+
else if (shadowChanged) {
|
|
138
157
|
if (!quiet)
|
|
139
158
|
log.info("Shadow paths changed — recreating container...");
|
|
140
159
|
}
|
|
160
|
+
else {
|
|
161
|
+
if (!quiet)
|
|
162
|
+
log.info("Runtime environment updated — recreating container...");
|
|
163
|
+
}
|
|
141
164
|
}
|
|
142
165
|
}
|
|
143
166
|
if (containerStatus === null) {
|
|
@@ -163,7 +186,7 @@ export function startContainer(opts) {
|
|
|
163
186
|
containerName,
|
|
164
187
|
...mountArgs,
|
|
165
188
|
...envFileArgs,
|
|
166
|
-
...
|
|
189
|
+
...runtimeEnvArgs,
|
|
167
190
|
"--security-opt",
|
|
168
191
|
"no-new-privileges:true",
|
|
169
192
|
...labelArgs,
|
package/dist/lib/constants.js
CHANGED
|
@@ -28,8 +28,13 @@ export const CONTAINER_NAME_PREFIX = "totopo-";
|
|
|
28
28
|
export const LABEL_MANAGED = "totopo.managed";
|
|
29
29
|
export const LABEL_SHADOWS = "totopo.shadows";
|
|
30
30
|
export const LABEL_PROFILE = "totopo.profile";
|
|
31
|
+
export const LABEL_RUNTIME_ENV = "totopo.runtime-env";
|
|
31
32
|
// Built-in profile names (must match keys in buildDefaultTotopoYaml in totopo-yaml.ts)
|
|
32
33
|
export const PROFILE = {
|
|
33
34
|
default: "default",
|
|
34
35
|
extended: "extended",
|
|
35
36
|
};
|
|
37
|
+
// Runtime env vars injected into every container via docker run -e
|
|
38
|
+
export const RUNTIME_ENV = {
|
|
39
|
+
CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY: "1",
|
|
40
|
+
};
|