totopo 3.3.0-rc-1 → 3.3.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/bin/totopo.js +2 -2
- package/dist/lib/migrate-to-latest.js +85 -21
- package/dist/lib/safe-rm.js +7 -2
- package/package.json +1 -1
- package/templates/Dockerfile +1 -1
package/bin/totopo.js
CHANGED
|
@@ -50,10 +50,10 @@ if (!existsSync(new URL("../dist/commands/dev.js", import.meta.url))) {
|
|
|
50
50
|
process.exit(1);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// ---
|
|
53
|
+
// --- migrations check --------------------------------------------------------------------------------------------------------------------
|
|
54
54
|
try {
|
|
55
55
|
const { runMigration } = await import("../dist/lib/migrate-to-latest.js");
|
|
56
|
-
runMigration(process.cwd());
|
|
56
|
+
await runMigration(process.cwd(), false);
|
|
57
57
|
} catch {
|
|
58
58
|
// Non-fatal - migration failure should not block startup
|
|
59
59
|
}
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
// All migrations are idempotent - each checks if needed and skips if not.
|
|
23
23
|
// =========================================================================================================================================
|
|
24
24
|
import { spawnSync } from "node:child_process";
|
|
25
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
26
26
|
import { homedir } from "node:os";
|
|
27
27
|
import { join } from "node:path";
|
|
28
|
-
import { log } from "@clack/prompts";
|
|
28
|
+
import { confirm, isCancel, log } from "@clack/prompts";
|
|
29
29
|
import { load as loadYaml } from "js-yaml";
|
|
30
30
|
import { AGENTS_DIR, CONTAINER_STARTUP, LOCK_FILE, PROFILE, SHADOWS_DIR, TOTOPO_DIR, TOTOPO_YAML, WORKSPACES_DIR } from "./constants.js";
|
|
31
31
|
import { safeRmSync } from "./safe-rm.js";
|
|
@@ -151,6 +151,55 @@ function migrateSingleV2Workspace(v2, existingIds) {
|
|
|
151
151
|
// =========================================================================================================================================
|
|
152
152
|
// Migration steps - each is idempotent and checks if needed before acting
|
|
153
153
|
// =========================================================================================================================================
|
|
154
|
+
const V1_WORKSPACE_FILES = ["Dockerfile", "README.md", "post-start.mjs", "settings.json"];
|
|
155
|
+
function getCandidateWorkspaceRoots(cwd) {
|
|
156
|
+
const roots = [cwd];
|
|
157
|
+
const gitRoot = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
158
|
+
cwd,
|
|
159
|
+
encoding: "utf8",
|
|
160
|
+
stdio: "pipe",
|
|
161
|
+
});
|
|
162
|
+
const root = (gitRoot.stdout ?? "").trim();
|
|
163
|
+
if (gitRoot.status === 0 && root.length > 0 && root !== cwd)
|
|
164
|
+
roots.push(root);
|
|
165
|
+
return roots;
|
|
166
|
+
}
|
|
167
|
+
function detectLegacyV1WorkspaceDir(cwd) {
|
|
168
|
+
for (const root of getCandidateWorkspaceRoots(cwd)) {
|
|
169
|
+
const legacyDir = join(root, TOTOPO_DIR);
|
|
170
|
+
try {
|
|
171
|
+
if (!statSync(legacyDir).isDirectory())
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const hasLegacyFile = V1_WORKSPACE_FILES.some((file) => existsSync(join(legacyDir, file)));
|
|
178
|
+
if (hasLegacyFile)
|
|
179
|
+
return legacyDir;
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* v1.0.3 -> latest: Remove workspace-local .totopo/ artifacts.
|
|
185
|
+
* These files are now bundled in the totopo CLI package.
|
|
186
|
+
*/
|
|
187
|
+
async function migrateLegacyV1WorkspaceArtifacts(cwd, requireConfirmation = true) {
|
|
188
|
+
const legacyDir = detectLegacyV1WorkspaceDir(cwd);
|
|
189
|
+
if (!legacyDir)
|
|
190
|
+
return;
|
|
191
|
+
log.warn(`Found legacy v1 totopo artifacts at ${legacyDir}.\n` +
|
|
192
|
+
" Latest totopo bundles these files in the binary, so this directory can be safely removed.");
|
|
193
|
+
if (requireConfirmation) {
|
|
194
|
+
const shouldRemove = await confirm({ message: "Remove legacy .totopo/ directory?", initialValue: true });
|
|
195
|
+
if (isCancel(shouldRemove) || !shouldRemove) {
|
|
196
|
+
log.info("Kept legacy .totopo/ directory.");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
safeRmSync(legacyDir, { recursive: true, force: true });
|
|
201
|
+
log.success("Removed legacy .totopo/ directory.");
|
|
202
|
+
}
|
|
154
203
|
/**
|
|
155
204
|
* v3-rc-1/rc-2 → latest: Rename ~/.totopo/projects/ → ~/.totopo/workspaces/.
|
|
156
205
|
* Stops running containers first because they have bind mounts into the old path.
|
|
@@ -393,24 +442,31 @@ function migrateRemoveDeprecatedYamlFields(cwd) {
|
|
|
393
442
|
// migrateLockFileFormat and migrateLockKeyYamlToRoot must run last so all workspace
|
|
394
443
|
// dirs are in their final location first. migrateLockKeyYamlToRoot runs after
|
|
395
444
|
// migrateLockFileFormat so the latter always writes "root=" for freshly upgraded files.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
from: "v3
|
|
406
|
-
description: "Remove
|
|
407
|
-
run:
|
|
408
|
-
|
|
409
|
-
|
|
445
|
+
function buildMigrations(cwd, skipAnyConfirmations) {
|
|
446
|
+
return [
|
|
447
|
+
{
|
|
448
|
+
from: "v1.0.3",
|
|
449
|
+
description: "Remove workspace-local .totopo/ artifacts",
|
|
450
|
+
run: () => migrateLegacyV1WorkspaceArtifacts(cwd, !skipAnyConfirmations),
|
|
451
|
+
},
|
|
452
|
+
{ from: "v3-rc-1/rc-2", description: "Rename ~/.totopo/projects/ to ~/.totopo/workspaces/", run: migrateProjectsDir },
|
|
453
|
+
{ from: "v2.x", description: "Hash-based dirs to workspace_id-based dirs + totopo.yaml", run: migrateV2Workspaces },
|
|
454
|
+
{ from: "v3-rc-1/rc-2", description: "Rename project_id to workspace_id in totopo.yaml", run: () => migrateTotopoYaml(cwd) },
|
|
455
|
+
{ from: "v2.x", description: "Remove legacy ~/.totopo/.env global key file", run: migrateGlobalEnv },
|
|
456
|
+
{ from: "v3-rc-6", description: "Upgrade .lock files from positional to key=value format", run: migrateLockFileFormat },
|
|
457
|
+
{ from: "v3-rc-8", description: "Rename 'yaml' key to 'root' in .lock files", run: migrateLockKeyYamlToRoot },
|
|
458
|
+
{ from: "v3.1.0", description: "Remove last-cli-update key from .lock files", run: migrateRemoveLastCliUpdate },
|
|
459
|
+
{
|
|
460
|
+
from: "v3.2.1",
|
|
461
|
+
description: "Remove deprecated fields (schema_version, name, yaml-language-server) from totopo.yaml",
|
|
462
|
+
run: () => migrateRemoveDeprecatedYamlFields(cwd),
|
|
463
|
+
},
|
|
464
|
+
];
|
|
465
|
+
}
|
|
410
466
|
/** Run all migrations in order. Called early in bin/totopo.js startup. */
|
|
411
|
-
export function runMigration(cwd) {
|
|
412
|
-
for (const migration of
|
|
413
|
-
migration.run(
|
|
467
|
+
export async function runMigration(cwd, skipAnyConfirmations = true) {
|
|
468
|
+
for (const migration of buildMigrations(cwd, skipAnyConfirmations)) {
|
|
469
|
+
await migration.run();
|
|
414
470
|
}
|
|
415
471
|
}
|
|
416
472
|
// =========================================================================================================================================
|
|
@@ -421,8 +477,16 @@ export function runMigration(cwd) {
|
|
|
421
477
|
/** Check if a running container's image is stale (missing expected files/features). */
|
|
422
478
|
export function isImageStale(containerName) {
|
|
423
479
|
// v3.2.0: startup.mjs replaced post-start.mjs + update-ai-clis.mjs
|
|
424
|
-
const
|
|
425
|
-
if (
|
|
480
|
+
const startupCheck = spawnSync("docker", ["exec", containerName, "test", "-f", CONTAINER_STARTUP], { stdio: "pipe" });
|
|
481
|
+
if (startupCheck.status !== 0)
|
|
482
|
+
return true;
|
|
483
|
+
// v3.3.0: file added to the base image for artifact inspection
|
|
484
|
+
const fileCheck = spawnSync("docker", ["exec", containerName, "test", "-x", "/usr/bin/file"], { stdio: "pipe" });
|
|
485
|
+
if (fileCheck.status !== 0)
|
|
486
|
+
return true;
|
|
487
|
+
// v3.3.0: bubblewrap added for Codex sandboxing prerequisites
|
|
488
|
+
const bubblewrapCheck = spawnSync("docker", ["exec", containerName, "test", "-x", "/usr/bin/bwrap"], { stdio: "pipe" });
|
|
489
|
+
if (bubblewrapCheck.status !== 0)
|
|
426
490
|
return true;
|
|
427
491
|
return false;
|
|
428
492
|
}
|
package/dist/lib/safe-rm.js
CHANGED
|
@@ -13,14 +13,19 @@ const TEST_TMP_PREFIX = join(tmpdir(), `${CONTAINER_NAME_PREFIX}test-`);
|
|
|
13
13
|
/**
|
|
14
14
|
* Safe wrapper around rmSync. Throws if the path is outside a totopo-owned location:
|
|
15
15
|
* - ~/.totopo/ (workspace caches, agents, shadows, global config)
|
|
16
|
+
* - A directory named .totopo (legacy v1 workspace-local artifacts)
|
|
16
17
|
* - A file named totopo.yaml (workspace config file in any user workspace root)
|
|
17
18
|
* - <tmpdir>/totopo-test-* (test temp directories)
|
|
18
19
|
*/
|
|
19
20
|
export function safeRmSync(path, options) {
|
|
20
21
|
const r = resolve(path);
|
|
21
|
-
const ok = r === TOTOPO_HOME ||
|
|
22
|
+
const ok = r === TOTOPO_HOME ||
|
|
23
|
+
r.startsWith(TOTOPO_HOME + sep) ||
|
|
24
|
+
basename(r) === TOTOPO_DIR ||
|
|
25
|
+
basename(r) === TOTOPO_YAML ||
|
|
26
|
+
r.startsWith(TEST_TMP_PREFIX);
|
|
22
27
|
if (!ok) {
|
|
23
|
-
throw new Error(`safeRmSync: refusing to delete '${r}'
|
|
28
|
+
throw new Error(`safeRmSync: refusing to delete '${r}' - must be under ~/.totopo/, named .totopo, named totopo.yaml, or a test temp dir`);
|
|
24
29
|
}
|
|
25
30
|
rmSync(r, options);
|
|
26
31
|
}
|
package/package.json
CHANGED
package/templates/Dockerfile
CHANGED
|
@@ -23,7 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
23
23
|
# Build essentials
|
|
24
24
|
build-essential pkg-config libssl-dev \
|
|
25
25
|
# Utilities
|
|
26
|
-
jq unzip zip tree htop procps lsb-release gnupg ca-certificates sox \
|
|
26
|
+
jq unzip zip tree htop procps lsb-release gnupg ca-certificates sox file bubblewrap \
|
|
27
27
|
# Modern search/navigation tools
|
|
28
28
|
ripgrep fzf \
|
|
29
29
|
# Database clients
|