fix(migrate): preserve Windows shell semantics

This commit is contained in:
Mohamed Boudra
2026-07-19 01:39:15 +02:00
parent 44da3cdf05
commit 152cdcabad
2 changed files with 46 additions and 4 deletions
@@ -179,6 +179,30 @@ cwd = "apps/web"
});
});
test("skips variable commands on Windows while preserving ordinary commands", () => {
const inspected = inspectConductorProjectConfig(
emptyRepo("windows-variables"),
{
scripts: {
setup: "cd $CONDUCTOR_WORKSPACE_PATH",
run: {
service: { command: "serve --port $CONDUCTOR_PORT" },
plain: { command: "npm test" },
},
},
},
"win32",
);
expect(inspected.config).toEqual({ scripts: { plain: { command: "npm test" } } });
expect(inspected.notices.map((notice) => notice.message)).toEqual(
expect.arrayContaining([
"worktree.setup: Conductor variables use unsupported shell syntax on Windows: CONDUCTOR_WORKSPACE_PATH. Command was not imported.",
"scripts.service: Conductor variables use unsupported shell syntax on Windows: CONDUCTOR_PORT. Command was not imported.",
]),
);
});
function fixtureRepo(name: "current" | "legacy"): string {
const directory = mkdtempSync(path.join(os.tmpdir(), `paseo-migrate-${name}-`));
cleanup.push(directory);
@@ -58,8 +58,8 @@ export function inspectConductorProjectConfig(
notices.push(malformedSetting("scripts", "Expected a scripts table."));
}
mapLifecycle(scripts?.setup, "worktree.setup", "setup", config, notices);
mapLifecycle(scripts?.archive, "worktree.teardown", "teardown", config, notices);
mapLifecycle(scripts?.setup, "worktree.setup", "setup", config, notices, platform);
mapLifecycle(scripts?.archive, "worktree.teardown", "teardown", config, notices, platform);
mapRunScripts(scripts?.run, config, notices, platform);
mapMetadataPrompts(settings.prompts, config, notices);
reportUnsupported(repoRoot, settings, notices);
@@ -160,13 +160,14 @@ function mapLifecycle(
target: "setup" | "teardown",
config: PaseoConfigRaw,
notices: MigrationNotice[],
platform: NodeJS.Platform,
): void {
if (value === undefined) return;
if (typeof value !== "string" || value.trim().length === 0) {
notices.push(malformedSetting(key, "Expected a non-empty command string."));
return;
}
const command = rewriteExactCommand(value, "lifecycle", key, notices);
const command = rewriteExactCommand(value, "lifecycle", key, notices, platform);
if (!command) return;
config.worktree = { ...config.worktree, [target]: command };
}
@@ -337,7 +338,13 @@ function mapRunScript(
}
}
const rewritten = rewriteExactCommand(command, service ? "run" : "lifecycle", key, notices);
const rewritten = rewriteExactCommand(
command,
service ? "run" : "lifecycle",
key,
notices,
platform,
);
if (!rewritten) return;
if (service) {
const environmentName = scriptId.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
@@ -353,7 +360,18 @@ function rewriteExactCommand(
context: RewriteContext,
key: string,
notices: MigrationNotice[],
platform: NodeJS.Platform,
): string | null {
const sourceVariables = collectConductorVariables(command);
if (platform === "win32" && sourceVariables.length > 0) {
notices.push(
unsupportedSetting(
key,
`Conductor variables use unsupported shell syntax on Windows: ${sourceVariables.join(", ")}. Command was not imported.`,
),
);
return null;
}
let rewritten = command;
for (const [from, to] of [
["CONDUCTOR_WORKSPACE_PATH", "PASEO_WORKTREE_PATH"],