totopo 3.0.0-rc-8 → 3.0.0
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
CHANGED
|
@@ -7,7 +7,7 @@ import { existsSync } from "node:fs";
|
|
|
7
7
|
import { join, relative } from "node:path";
|
|
8
8
|
import { cancel, isCancel, log, outro, select } from "@clack/prompts";
|
|
9
9
|
import { buildAgentContextDocs, buildAgentMountArgs, injectAgentContext } from "../lib/agent-context.js";
|
|
10
|
-
import { CONTAINER_POST_START, CONTAINER_WORKSPACE, LABEL_MANAGED, LABEL_PROFILE, LABEL_SHADOWS } from "../lib/constants.js";
|
|
10
|
+
import { CONTAINER_POST_START, CONTAINER_WORKSPACE, LABEL_MANAGED, LABEL_PROFILE, LABEL_SHADOWS, PROFILE } from "../lib/constants.js";
|
|
11
11
|
import { buildDockerfile, buildImageWithTempfile } from "../lib/dockerfile-builder.js";
|
|
12
12
|
import { buildShadowMountArgs, ensureShadowsInSync, expandShadowPatterns } from "../lib/shadows.js";
|
|
13
13
|
import { readTotopoYaml } from "../lib/totopo-yaml.js";
|
|
@@ -35,9 +35,9 @@ async function promptWorkdir(workspaceDir, cwd) {
|
|
|
35
35
|
async function selectProfile(ctx, profiles) {
|
|
36
36
|
const profileNames = Object.keys(profiles);
|
|
37
37
|
if (profileNames.length <= 1) {
|
|
38
|
-
return profileNames[0] ??
|
|
38
|
+
return profileNames[0] ?? PROFILE.default;
|
|
39
39
|
}
|
|
40
|
-
const currentProfile = readActiveProfile(ctx.workspaceId) ??
|
|
40
|
+
const currentProfile = readActiveProfile(ctx.workspaceId) ?? PROFILE.default;
|
|
41
41
|
const choice = await select({
|
|
42
42
|
message: "Profile:",
|
|
43
43
|
options: profileNames.map((name) => {
|
package/dist/commands/menu.js
CHANGED
|
@@ -6,11 +6,12 @@ import { existsSync } from "node:fs";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { styleText } from "node:util";
|
|
8
8
|
import { box, cancel, isCancel, select } from "@clack/prompts";
|
|
9
|
+
import { PROFILE } from "../lib/constants.js";
|
|
9
10
|
import { readActiveProfile } from "../lib/workspace-identity.js";
|
|
10
11
|
export async function run(args) {
|
|
11
12
|
const { ctx, workspaceRunning } = args;
|
|
12
13
|
// --- Read workspace config -----------------------------------------------------------------------------------------------------------
|
|
13
|
-
const activeProfile = readActiveProfile(ctx.workspaceId) ??
|
|
14
|
+
const activeProfile = readActiveProfile(ctx.workspaceId) ?? PROFILE.default;
|
|
14
15
|
const hasGit = existsSync(join(ctx.workspaceRoot, ".git"));
|
|
15
16
|
// --- Status box ----------------------------------------------------------------------------------------------------------------------
|
|
16
17
|
const containerStatus = workspaceRunning ? "running" : "stopped";
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { relative } from "node:path";
|
|
6
6
|
import { cancel, confirm, isCancel, log, multiselect, note, outro, path, select, text } from "@clack/prompts";
|
|
7
|
+
import { PROFILE } from "../lib/constants.js";
|
|
7
8
|
import { countPatternHits } from "../lib/shadows.js";
|
|
8
9
|
import { buildDefaultTotopoYaml, readTotopoYaml, writeTotopoYaml } from "../lib/totopo-yaml.js";
|
|
9
10
|
import { readActiveProfile, writeActiveProfile } from "../lib/workspace-identity.js";
|
|
@@ -20,7 +21,7 @@ async function profileMenu(ctx) {
|
|
|
20
21
|
log.info("No profiles defined in totopo.yaml.");
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
const currentProfile = readActiveProfile(ctx.workspaceId) ??
|
|
24
|
+
const currentProfile = readActiveProfile(ctx.workspaceId) ?? PROFILE.default;
|
|
24
25
|
note(`Active profile: ${currentProfile}`, "Profiles");
|
|
25
26
|
if (profileNames.length <= 1) {
|
|
26
27
|
log.info("Only one profile defined. Add more profiles in totopo.yaml to switch between them.");
|
package/dist/lib/constants.js
CHANGED
|
@@ -28,3 +28,9 @@ 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
|
+
// Built-in profile names (must match keys in buildDefaultTotopoYaml in totopo-yaml.ts)
|
|
32
|
+
export const PROFILE = {
|
|
33
|
+
default: "default",
|
|
34
|
+
slim: "slim",
|
|
35
|
+
custom: "custom",
|
|
36
|
+
};
|
|
@@ -24,10 +24,10 @@ import { homedir } from "node:os";
|
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { log } from "@clack/prompts";
|
|
26
26
|
import { load as loadYaml } from "js-yaml";
|
|
27
|
-
import { AGENTS_DIR,
|
|
27
|
+
import { AGENTS_DIR, LOCK_FILE, PROFILE, SHADOWS_DIR, TOTOPO_DIR, TOTOPO_YAML, WORKSPACES_DIR } from "./constants.js";
|
|
28
28
|
import { safeRmSync } from "./safe-rm.js";
|
|
29
29
|
import { buildDefaultTotopoYaml, readTotopoYaml, slugifyForWorkspaceId, validateWorkspaceId, writeTotopoYaml, } from "./totopo-yaml.js";
|
|
30
|
-
import { findTotopoYamlDir, getWorkspacesBaseDir, initWorkspaceDir } from "./workspace-identity.js";
|
|
30
|
+
import { findTotopoYamlDir, getWorkspacesBaseDir, initWorkspaceDir, LOCK_KEYS } from "./workspace-identity.js";
|
|
31
31
|
function isV2ProjectDir(dirPath) {
|
|
32
32
|
return existsSync(join(dirPath, "meta.json"));
|
|
33
33
|
}
|
|
@@ -134,7 +134,7 @@ function migrateSingleV2Workspace(v2, existingIds) {
|
|
|
134
134
|
}
|
|
135
135
|
const newDir = join(getWorkspacesBaseDir(), workspaceId);
|
|
136
136
|
initWorkspaceDir(workspaceId, v2.projectRoot);
|
|
137
|
-
const oldAgents = join(getWorkspacesBaseDir(), v2.hashId,
|
|
137
|
+
const oldAgents = join(getWorkspacesBaseDir(), v2.hashId, "agents");
|
|
138
138
|
const newAgents = join(newDir, AGENTS_DIR);
|
|
139
139
|
if (existsSync(oldAgents)) {
|
|
140
140
|
try {
|
|
@@ -144,7 +144,7 @@ function migrateSingleV2Workspace(v2, existingIds) {
|
|
|
144
144
|
log.warn(`Could not copy agent memory for "${v2.displayName}"`);
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
const oldShadows = join(getWorkspacesBaseDir(), v2.hashId,
|
|
147
|
+
const oldShadows = join(getWorkspacesBaseDir(), v2.hashId, "shadows");
|
|
148
148
|
const newShadows = join(newDir, SHADOWS_DIR);
|
|
149
149
|
if (existsSync(oldShadows)) {
|
|
150
150
|
try {
|
|
@@ -166,7 +166,7 @@ function migrateSingleV2Workspace(v2, existingIds) {
|
|
|
166
166
|
* Stops running containers first because they have bind mounts into the old path.
|
|
167
167
|
*/
|
|
168
168
|
function migrateProjectsDir() {
|
|
169
|
-
const oldDir = join(homedir(),
|
|
169
|
+
const oldDir = join(homedir(), ".totopo", "projects");
|
|
170
170
|
const newDir = join(homedir(), TOTOPO_DIR, WORKSPACES_DIR);
|
|
171
171
|
if (!existsSync(oldDir))
|
|
172
172
|
return;
|
|
@@ -175,11 +175,11 @@ function migrateProjectsDir() {
|
|
|
175
175
|
safeRmSync(oldDir, { recursive: true });
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
-
const psResult = spawnSync("docker", ["ps", "--filter",
|
|
178
|
+
const psResult = spawnSync("docker", ["ps", "--filter", "name=totopo-", "--format", "{{.Names}}"], {
|
|
179
179
|
encoding: "utf8",
|
|
180
180
|
stdio: "pipe",
|
|
181
181
|
});
|
|
182
|
-
const projectContainerNames = new Set(entries.map((e) =>
|
|
182
|
+
const projectContainerNames = new Set(entries.map((e) => `totopo-${e}`));
|
|
183
183
|
const running = (psResult.stdout ?? "")
|
|
184
184
|
.trim()
|
|
185
185
|
.split("\n")
|
|
@@ -269,7 +269,7 @@ function migrateTotopoYaml(cwd) {
|
|
|
269
269
|
* API keys are now declared per-workspace via env_file in totopo.yaml.
|
|
270
270
|
*/
|
|
271
271
|
function migrateGlobalEnv() {
|
|
272
|
-
const globalEnv = join(homedir(),
|
|
272
|
+
const globalEnv = join(homedir(), ".totopo", ".env");
|
|
273
273
|
if (!existsSync(globalEnv))
|
|
274
274
|
return;
|
|
275
275
|
log.warn("Removed legacy ~/.totopo/.env - API keys are now declared per-workspace via env_file in totopo.yaml.\n" +
|
|
@@ -295,8 +295,30 @@ function migrateLockFileFormat() {
|
|
|
295
295
|
const [firstLine, secondLine] = lines;
|
|
296
296
|
if (!firstLine || firstLine.includes("="))
|
|
297
297
|
continue; // empty or already new format
|
|
298
|
-
const activeProfile = secondLine ??
|
|
299
|
-
writeFileSync(lockPath,
|
|
298
|
+
const activeProfile = secondLine ?? PROFILE.default;
|
|
299
|
+
writeFileSync(lockPath, `${LOCK_KEYS.workspaceRoot}=${firstLine}\n${LOCK_KEYS.activeProfile}=${activeProfile}\n${LOCK_KEYS.lastCliUpdate}=\n`);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// unreadable -- skip, will surface as a broken workspace elsewhere
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* v3-rc-8 and earlier: Rename the "yaml" key to "root" in .lock files.
|
|
308
|
+
* The "yaml" key name was misleading — it holds the workspace root path, not YAML content.
|
|
309
|
+
* Detects old format by presence of a line starting with "yaml=". Idempotent.
|
|
310
|
+
*/
|
|
311
|
+
function migrateLockKeyYamlToRoot() {
|
|
312
|
+
const baseDir = getWorkspacesBaseDir();
|
|
313
|
+
if (!existsSync(baseDir))
|
|
314
|
+
return;
|
|
315
|
+
for (const entry of readdirSync(baseDir)) {
|
|
316
|
+
const lockPath = join(baseDir, entry, LOCK_FILE);
|
|
317
|
+
try {
|
|
318
|
+
const content = readFileSync(lockPath, "utf8");
|
|
319
|
+
if (!content.includes("yaml="))
|
|
320
|
+
continue;
|
|
321
|
+
writeFileSync(lockPath, content.replace(/^yaml=/m, `${LOCK_KEYS.workspaceRoot}=`));
|
|
300
322
|
}
|
|
301
323
|
catch {
|
|
302
324
|
// unreadable -- skip, will surface as a broken workspace elsewhere
|
|
@@ -306,13 +328,16 @@ function migrateLockFileFormat() {
|
|
|
306
328
|
// Order matters: migrateProjectsDir must run before migrateV2Workspaces because
|
|
307
329
|
// step 2 scans ~/.totopo/workspaces/ which only exists after step 1 renames projects/.
|
|
308
330
|
// Steps 3 and 4 are independent of each other and of steps 1-2.
|
|
309
|
-
// migrateLockFileFormat must run last so all workspace
|
|
331
|
+
// migrateLockFileFormat and migrateLockKeyYamlToRoot must run last so all workspace
|
|
332
|
+
// dirs are in their final location first. migrateLockKeyYamlToRoot runs after
|
|
333
|
+
// migrateLockFileFormat so the latter always writes "root=" for freshly upgraded files.
|
|
310
334
|
const MIGRATIONS = [
|
|
311
335
|
{ from: "v3-rc-1/rc-2", description: "Rename ~/.totopo/projects/ to ~/.totopo/workspaces/", run: migrateProjectsDir },
|
|
312
336
|
{ from: "v2.x", description: "Hash-based dirs to workspace_id-based dirs + totopo.yaml", run: migrateV2Workspaces },
|
|
313
337
|
{ from: "v3-rc-1/rc-2", description: "Rename project_id to workspace_id in totopo.yaml", run: migrateTotopoYaml },
|
|
314
338
|
{ from: "v2.x", description: "Remove legacy ~/.totopo/.env global key file", run: migrateGlobalEnv },
|
|
315
339
|
{ from: "v3-rc-6", description: "Upgrade .lock files from positional to key=value format", run: migrateLockFileFormat },
|
|
340
|
+
{ from: "v3-rc-8", description: "Rename 'yaml' key to 'root' in .lock files", run: migrateLockKeyYamlToRoot },
|
|
316
341
|
];
|
|
317
342
|
/** Run all migrations in order. Called early in bin/totopo.js startup. */
|
|
318
343
|
export function runMigration(cwd) {
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
|
-
import { AGENTS_DIR, CONTAINER_NAME_PREFIX, LOCK_FILE, SHADOWS_DIR, TOTOPO_DIR, TOTOPO_YAML, WORKSPACES_DIR } from "./constants.js";
|
|
8
|
+
import { AGENTS_DIR, CONTAINER_NAME_PREFIX, LOCK_FILE, PROFILE, SHADOWS_DIR, TOTOPO_DIR, TOTOPO_YAML, WORKSPACES_DIR, } from "./constants.js";
|
|
9
9
|
import { readTotopoYaml } from "./totopo-yaml.js";
|
|
10
10
|
/** Maps LockFile field names to their corresponding keys written in the .lock file. */
|
|
11
|
-
const LOCK_KEYS = {
|
|
12
|
-
workspaceRoot: "
|
|
11
|
+
export const LOCK_KEYS = {
|
|
12
|
+
workspaceRoot: "root",
|
|
13
13
|
activeProfile: "profile",
|
|
14
14
|
lastCliUpdate: "last-cli-update",
|
|
15
15
|
};
|
|
@@ -53,7 +53,7 @@ function parseLockFile(workspaceId) {
|
|
|
53
53
|
return null;
|
|
54
54
|
return {
|
|
55
55
|
workspaceRoot: partial.workspaceRoot,
|
|
56
|
-
activeProfile: partial.activeProfile ??
|
|
56
|
+
activeProfile: partial.activeProfile ?? PROFILE.default,
|
|
57
57
|
lastCliUpdate: partial.lastCliUpdate ?? "",
|
|
58
58
|
};
|
|
59
59
|
}
|
|
@@ -77,7 +77,7 @@ export function writeLockFile(workspaceId, workspaceRoot) {
|
|
|
77
77
|
const existing = parseLockFile(workspaceId);
|
|
78
78
|
writeLockFileInternal(workspaceId, {
|
|
79
79
|
workspaceRoot,
|
|
80
|
-
activeProfile: existing?.activeProfile ??
|
|
80
|
+
activeProfile: existing?.activeProfile ?? PROFILE.default,
|
|
81
81
|
lastCliUpdate: existing?.lastCliUpdate ?? "",
|
|
82
82
|
});
|
|
83
83
|
}
|
|
@@ -105,7 +105,7 @@ export function writeLastCliUpdate(workspaceId, timestamp) {
|
|
|
105
105
|
}
|
|
106
106
|
// --- Workspace directory initialization --------------------------------------------------------------------------------------------------
|
|
107
107
|
/** Initialize ~/.totopo/workspaces/<workspace_id>/ with lock file and subdirs. */
|
|
108
|
-
export function initWorkspaceDir(workspaceId, workspaceRoot, activeProfile =
|
|
108
|
+
export function initWorkspaceDir(workspaceId, workspaceRoot, activeProfile = PROFILE.default) {
|
|
109
109
|
const dir = getWorkspaceDir(workspaceId);
|
|
110
110
|
mkdirSync(join(dir, AGENTS_DIR), { recursive: true });
|
|
111
111
|
mkdirSync(join(dir, SHADOWS_DIR), { recursive: true });
|