From af244d0f025887df705a27a09a484bd599ee708f Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 19 Jul 2026 02:01:39 +0200 Subject: [PATCH] fix(migrate): share service command semantics --- .../sources/conductor/project-config.test.ts | 14 +++++++++++ .../src/sources/conductor/project-config.ts | 23 +++++++++++++++++-- packages/protocol/src/service-env-name.ts | 6 +++++ .../src/server/workspace-service-env.ts | 10 +++----- 4 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 packages/protocol/src/service-env-name.ts diff --git a/packages/migrate/src/sources/conductor/project-config.test.ts b/packages/migrate/src/sources/conductor/project-config.test.ts index 4f2775e7d..2bfcd50b8 100644 --- a/packages/migrate/src/sources/conductor/project-config.test.ts +++ b/packages/migrate/src/sources/conductor/project-config.test.ts @@ -79,6 +79,15 @@ command = "echo $CONDUCTOR_UNSUPPORTED_VALUE" [scripts.run.unsupported_substitution] command = "serve --port \${CONDUCTOR_PORT/3000/3001}" + +[scripts.run.comment] +command = "npm test # $CONDUCTOR_PORT" + +[scripts.run.api] +command = "serve --port $CONDUCTOR_PORT" + +[scripts.run.api_] +command = "serve-other --port $CONDUCTOR_PORT" `, ); @@ -86,6 +95,8 @@ command = "serve --port \${CONDUCTOR_PORT/3000/3001}" expect(inspected.config).toEqual({ scripts: { + api: { command: "serve --port $PASEO_PORT", type: "service" }, + comment: { command: "npm test # $CONDUCTOR_PORT" }, safe: { command: "npm test" }, service: { command: "serve --port $PASEO_PORT", type: "service" }, }, @@ -97,6 +108,7 @@ command = "serve --port \${CONDUCTOR_PORT/3000/3001}" "scripts.escape.cwd: Absolute or escaping cwd values are not imported.", "scripts.unknown_variable: Unsupported Conductor variables: CONDUCTOR_UNSUPPORTED_VALUE. Command was not imported.", "scripts.unsupported_substitution: Unsupported Conductor variables: CONDUCTOR_PORT. Command was not imported.", + 'scripts.api_: Service environment name collides with "api" (API).', "settings.unknown_project_setting: Unknown Conductor setting.", ]), ); @@ -192,6 +204,7 @@ test("skips variable commands on Windows while preserving ordinary commands", () run: { service: { command: "serve --port $CONDUCTOR_PORT" }, plain: { command: "npm test" }, + args: { command: "npm", args: ["run", "dev"] }, }, }, }, @@ -203,6 +216,7 @@ test("skips variable commands on Windows while preserving ordinary commands", () 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.", + "scripts.args.args: Script arguments are not imported on Windows.", ]), ); }); diff --git a/packages/migrate/src/sources/conductor/project-config.ts b/packages/migrate/src/sources/conductor/project-config.ts index 05d9d468c..4ccd1401f 100644 --- a/packages/migrate/src/sources/conductor/project-config.ts +++ b/packages/migrate/src/sources/conductor/project-config.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join, posix, relative } from "node:path"; import type { PaseoConfigRaw, PaseoScriptEntryRaw } from "@getpaseo/protocol/messages"; +import { normalizeServiceEnvName } from "@getpaseo/protocol/service-env-name"; import { parse as parseToml } from "smol-toml"; import type { MigrationNotice } from "../../types.js"; @@ -294,6 +295,12 @@ function mapRunScript( if (script.icon !== undefined) { notices.push(unsupportedSetting(`${key}.icon`, "Script icons are not imported.")); } + if (platform === "win32" && script.args && script.args.length > 0) { + notices.push( + unsupportedSetting(`${key}.args`, "Script arguments are not imported on Windows."), + ); + return; + } let command = appendArgs(script.command, script.args ?? []); if (script.cwd) { @@ -324,7 +331,7 @@ function mapRunScript( const service = containsShellVariable(command, "CONDUCTOR_PORT"); if (service) { - const environmentName = scriptId.toUpperCase().replace(/[^A-Z0-9]+/g, "_"); + const environmentName = normalizeServiceEnvName(scriptId); const collision = serviceNames.get(environmentName); if (collision) { notices.push( @@ -347,7 +354,7 @@ function mapRunScript( ); if (!rewritten) return; if (service) { - const environmentName = scriptId.toUpperCase().replace(/[^A-Z0-9]+/g, "_"); + const environmentName = normalizeServiceEnvName(scriptId); serviceNames.set(environmentName, scriptId); } const entry: PaseoScriptEntryRaw = { command: rewritten }; @@ -632,6 +639,18 @@ function activeShellMask(command: string): string { index += 1; continue; } + if ( + quote === null && + character === "#" && + (index === 0 || /[\s;|&()<>]/.test(command[index - 1])) + ) { + while (index < command.length && command[index] !== "\n") { + mask[index] = " "; + index += 1; + } + index -= 1; + continue; + } if (quote === "double") { if (character === '"') { mask[index] = " "; diff --git a/packages/protocol/src/service-env-name.ts b/packages/protocol/src/service-env-name.ts new file mode 100644 index 000000000..dfdd0dc81 --- /dev/null +++ b/packages/protocol/src/service-env-name.ts @@ -0,0 +1,6 @@ +export function normalizeServiceEnvName(scriptName: string): string { + return scriptName + .toUpperCase() + .replace(/[^A-Z0-9]+/g, "_") + .replace(/^_+|_+$/g, ""); +} diff --git a/packages/server/src/server/workspace-service-env.ts b/packages/server/src/server/workspace-service-env.ts index e7a486294..3c42b6551 100644 --- a/packages/server/src/server/workspace-service-env.ts +++ b/packages/server/src/server/workspace-service-env.ts @@ -1,4 +1,7 @@ import { projectServiceProxyUrls } from "./service-proxy.js"; +import { normalizeServiceEnvName } from "@getpaseo/protocol/service-env-name"; + +export { normalizeServiceEnvName } from "@getpaseo/protocol/service-env-name"; export interface WorkspaceServicePeer { scriptName: string; @@ -15,13 +18,6 @@ export interface BuildWorkspaceServiceEnvOptions { peers: readonly WorkspaceServicePeer[]; } -export function normalizeServiceEnvName(scriptName: string): string { - return scriptName - .toUpperCase() - .replace(/[^A-Z0-9]+/g, "_") - .replace(/^_+|_+$/g, ""); -} - export function buildWorkspaceServiceEnv( options: BuildWorkspaceServiceEnvOptions, ): Record {