From 1b3e69320ebe1c969dfd1ad881c067e56e800402 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sun, 21 Jun 2026 22:08:21 +0200 Subject: [PATCH 1/2] chore: adopt nub fast command paths --- .github/workflows/ci.yml | 8 +- .github/workflows/ui-release.yml | 4 +- .github/workflows/zaneops-main-after-ci.yml | 2 +- .../workflows/zaneops-preview-after-ci.yml | 2 +- .../workflows/zaneops-preview-teardown.yml | 4 +- .../components/search/search-autocomplete.tsx | 6 +- apps/new-engine-ctl/package.json | 3 +- .../src/__tests__/ci-workflows.test.ts | 2 +- .../new-engine-ctl/src/orchestration/scope.ts | 28 ++++-- package.json | 15 ++-- pnpm-lock.yaml | 90 ++++++++++++++++++- pnpm-workspace.yaml | 1 + scripts/dev/run-zane-main-lane.sh | 2 + scripts/dev/run-zane-preview-lane.sh | 2 + vercel.json | 2 +- zerops.yml | 2 +- 16 files changed, 141 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c827e7a80..61bbf5657 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,13 +37,13 @@ jobs: cache: 'pnpm' - run: pnpm install --frozen-lockfile - - uses: nrwl/nx-set-shas@v4 + - uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4 - name: Determine affected projects id: determine_affected run: | base="${NX_BASE:-origin/master}" head="${NX_HEAD:-HEAD}" - projects_json="$(pnpm exec nx show projects --affected --json --base="$base" --head="$head")" + projects_json="$(./node_modules/.bin/nubx --node nx show projects --affected --json --base="$base" --head="$head")" projects="$(printf '%s' "$projects_json" | node -e 'let input = ""; process.stdin.on("data", chunk => input += chunk); process.stdin.on("end", () => { const projects = input.trim() ? JSON.parse(input) : []; if (!Array.isArray(projects)) { throw new Error("Expected an array from nx show projects --json"); } process.stdout.write(projects.join(",")); });')" echo "projects=${projects}" >> "$GITHUB_OUTPUT" echo "projects_csv=,${projects}," >> "$GITHUB_OUTPUT" @@ -57,6 +57,6 @@ jobs: - name: Lint Nx module boundaries run: pnpm lint:nx - name: Test new-engine-ctl CI wiring - run: pnpm exec nx run new-engine-ctl:test + run: ./node_modules/.bin/nubx --node nx run new-engine-ctl:test # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected - - run: pnpm exec nx affected -t lint test + - run: ./node_modules/.bin/nubx --node nx affected -t lint test diff --git a/.github/workflows/ui-release.yml b/.github/workflows/ui-release.yml index 6c4411181..23f66a5b4 100644 --- a/.github/workflows/ui-release.yml +++ b/.github/workflows/ui-release.yml @@ -43,7 +43,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Set Nx SHAs - uses: nrwl/nx-set-shas@v4 + uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4 with: main-branch-name: master @@ -58,7 +58,7 @@ jobs: exit 0 fi - AFFECTED=$(pnpm exec nx show projects --affected --base="$NX_BASE" --head="$NX_HEAD") + AFFECTED=$(./node_modules/.bin/nubx --node nx show projects --affected --base="$NX_BASE" --head="$NX_HEAD") echo "$AFFECTED" if echo "$AFFECTED" | grep -q "^ui-kit$"; then echo "is_affected=true" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/zaneops-main-after-ci.yml b/.github/workflows/zaneops-main-after-ci.yml index d084d7d0c..b539ab734 100644 --- a/.github/workflows/zaneops-main-after-ci.yml +++ b/.github/workflows/zaneops-main-after-ci.yml @@ -47,7 +47,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Build new-engine-ctl - run: pnpm exec nx run new-engine-ctl:build + run: ./node_modules/.bin/nubx --node nx run new-engine-ctl:build - name: Resolve base SHA id: base diff --git a/.github/workflows/zaneops-preview-after-ci.yml b/.github/workflows/zaneops-preview-after-ci.yml index 72a5407ff..1c156e5e4 100644 --- a/.github/workflows/zaneops-preview-after-ci.yml +++ b/.github/workflows/zaneops-preview-after-ci.yml @@ -52,7 +52,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Build new-engine-ctl - run: pnpm exec nx run new-engine-ctl:build + run: ./node_modules/.bin/nubx --node nx run new-engine-ctl:build - name: Resolve PR number id: pr diff --git a/.github/workflows/zaneops-preview-teardown.yml b/.github/workflows/zaneops-preview-teardown.yml index 5d0295f5d..1e6da8ff7 100644 --- a/.github/workflows/zaneops-preview-teardown.yml +++ b/.github/workflows/zaneops-preview-teardown.yml @@ -28,7 +28,9 @@ jobs: run: pnpm install --frozen-lockfile - name: Build new-engine-ctl - run: NX_ISOLATE_PLUGINS=false pnpm exec nx run new-engine-ctl:build + # Teardown only needs the checked-out CLI build artifact; keep Nx plugin + # execution in-process here to avoid worker-startup issues in cleanup runs. + run: NX_ISOLATE_PLUGINS=false ./node_modules/.bin/nubx --node nx run new-engine-ctl:build - name: Upload new-engine-ctl artifact uses: actions/upload-artifact@v4 diff --git a/apps/herbatika/src/components/search/search-autocomplete.tsx b/apps/herbatika/src/components/search/search-autocomplete.tsx index 20e577deb..8583368cb 100644 --- a/apps/herbatika/src/components/search/search-autocomplete.tsx +++ b/apps/herbatika/src/components/search/search-autocomplete.tsx @@ -46,7 +46,7 @@ export function SearchAutocomplete({ aria-controls={controller.hasItems ? controller.panelId : undefined} aria-expanded={controller.shouldShowPanel} aria-haspopup="listbox" - className={`${isMobile ? "px-350 text-sm" : "px-400"} border-none h-full font-verdana -outline-offset-1 outline outline-border-search focus-visible:outline-offset-0 focus-visible:outline-search-form-border-focused`} + className={`${isMobile ? "px-350 text-sm" : "px-400"} -outline-offset-1 h-full border-none font-verdana outline outline-border-search focus-visible:outline-search-form-border-focused focus-visible:outline-offset-0`} maxLength={SEARCH_AUTOCOMPLETE_MAX_QUERY_LENGTH} name="q" onFocus={controller.handleFocus} @@ -56,13 +56,13 @@ export function SearchAutocomplete({ /> diff --git a/apps/new-engine-ctl/package.json b/apps/new-engine-ctl/package.json index a8e9b9b54..42a317755 100644 --- a/apps/new-engine-ctl/package.json +++ b/apps/new-engine-ctl/package.json @@ -8,7 +8,7 @@ }, "scripts": { "build": "node ./scripts/build.mjs", - "dev": "tsx src/cli.ts", + "dev": "nub src/cli.ts", "lint": "biome check src scripts", "format": "biome format --write src scripts", "test": "vitest run", @@ -23,7 +23,6 @@ "@biomejs/biome": "^2.3.7", "@types/node": "^24.12.4", "esbuild": "^0.27.3", - "tsx": "^4.19.4", "vitest": "^4.0.18" } } diff --git a/apps/new-engine-ctl/src/__tests__/ci-workflows.test.ts b/apps/new-engine-ctl/src/__tests__/ci-workflows.test.ts index c8dff2255..fa63788fd 100644 --- a/apps/new-engine-ctl/src/__tests__/ci-workflows.test.ts +++ b/apps/new-engine-ctl/src/__tests__/ci-workflows.test.ts @@ -21,7 +21,7 @@ const previewBaselineCompleteEnvPattern = /PREVIEW_BASELINE_COMPLETE:/ const previewBaselineCompleteFlagPattern = /--preview-baseline-complete "\$PREVIEW_BASELINE_COMPLETE"/ const node24Pattern = /node-version: 24/ -const ciCtlTestPattern = /pnpm exec nx run new-engine-ctl:test/ +const ciCtlTestPattern = /nubx --node nx run new-engine-ctl:test/ const mainVerifyEnvironmentFallbackPattern = /ENVIRONMENT_NAME:\s*\$\{\{\s*needs\.deploy\.outputs\.environment_name\s*\|\|\s*secrets\.ZANEOPS_ZANE_PRODUCTION_ENVIRONMENT_NAME\s*\}\}/ const mainVerifySummaryEnvironmentFallbackPattern = diff --git a/apps/new-engine-ctl/src/orchestration/scope.ts b/apps/new-engine-ctl/src/orchestration/scope.ts index 4c85e4efe..9515130e7 100644 --- a/apps/new-engine-ctl/src/orchestration/scope.ts +++ b/apps/new-engine-ctl/src/orchestration/scope.ts @@ -1,6 +1,6 @@ import { execFile } from "node:child_process" import { mkdir, writeFile } from "node:fs/promises" -import { dirname, matchesGlob } from "node:path" +import { delimiter, dirname, join, matchesGlob } from "node:path" import { promisify } from "node:util" import type { ScopeCommandInput, ScopeResponse } from "../contracts/scope.js" @@ -26,6 +26,15 @@ type ExecResult = { stderr: string } +function withWorkspaceBinPath(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + return { + ...env, + PATH: [join(process.cwd(), "node_modules", ".bin"), env.PATH] + .filter(Boolean) + .join(delimiter), + } +} + function toCsv(values: string[]): string { return normalizeCsvToArray(values.join(",")).join(",") } @@ -111,9 +120,9 @@ async function resolveNxAffectedProjects(input: { }> { try { const result = await runCommand( - "pnpm", + "nubx", [ - "exec", + "--node", "nx", "show", "projects", @@ -124,7 +133,7 @@ async function resolveNxAffectedProjects(input: { ], { env: { - ...process.env, + ...withWorkspaceBinPath(process.env), NX_DAEMON: "false", NX_ISOLATE_PLUGINS: String(input.nxIsolatePlugins), }, @@ -141,7 +150,16 @@ async function resolveNxAffectedProjects(input: { (value): value is string => typeof value === "string" ), } - } catch { + } catch (error) { + if ( + error && + typeof error === "object" && + "code" in error && + error.code === "ENOENT" + ) { + throw new Error("Required command not found: nubx") + } + return { nxStatus: "fallback", projects: [], diff --git a/package.json b/package.json index 167b4616c..40d5cba8a 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,19 @@ "node": "24.x" }, "scripts": { - "test-all": "cross-env TEST_ALL_ESHOPS=true nx e2e e2e", - "test": "nx e2e e2e", + "test-all": "cross-env TEST_ALL_ESHOPS=true nubx --node nx e2e e2e", + "test": "nubx --node nx e2e e2e", "test:e2e:medusa-be:admin": "pnpm --dir apps/medusa-be test:e2e:admin", "test:e2e:medusa-be:http": "bash ./scripts/e2e/medusa-be-http.sh run", "test:e2e:medusa-be:http:debug": "MEDUSA_E2E_KEEP_STACK=1 bash ./scripts/e2e/medusa-be-http.sh run", - "dev:all:nx": "nx run-many --target=dev --projects=medusa-*", + "dev:all:nx": "nubx --node nx run-many --target=dev --projects=medusa-*", "start:fe-demo": "cd apps/frontend-demo && npm run start", "start:be": "cd apps/medusa-be && npm run start", - "start:all:nx": "nx run-many --target=start --projects=medusa-*", + "start:all:nx": "nubx --node nx run-many --target=start --projects=medusa-*", "build:be": "cd apps/medusa-be && npm run build", - "build:all:nx": "nx run-many --target=build --projects=medusa-*", - "lint:nx": "NX_DAEMON=false pnpm exec nx show projects > /dev/null && eslint --config eslint.nx.config.cjs \"apps/**/*.{ts,tsx,js,jsx}\" \"libs/**/*.{ts,tsx,js,jsx}\"", - "nx:affected": "NX_DAEMON=false pnpm exec nx affected -t lint test", + "build:all:nx": "nubx --node nx run-many --target=build --projects=medusa-*", + "lint:nx": "NX_DAEMON=false nubx --node nx show projects > /dev/null && eslint --config eslint.nx.config.cjs \"apps/**/*.{ts,tsx,js,jsx}\" \"libs/**/*.{ts,tsx,js,jsx}\"", + "nx:affected": "NX_DAEMON=false nubx --node nx affected -t lint test", "knip": "knip", "knip:report": "knip --reporter markdown > reports/knip-report.md", "knip:fix": "knip --fix", @@ -30,6 +30,7 @@ "@biomejs/biome": "^2.3.7", "@medusajs/deps": "2.16.0", "@medusajs/eslint-plugin": "2.16.0", + "@nubjs/nub": "0.1.14", "@nx/esbuild": "22.4.2", "@nx/eslint-plugin": "22.4.2", "@nx/js": "22.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aaf2b91b7..9b159b02d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,6 +37,9 @@ importers: '@medusajs/eslint-plugin': specifier: 2.16.0 version: 2.16.0(eslint@9.39.1(jiti@2.7.0))(typescript@5.9.3) + '@nubjs/nub': + specifier: 0.1.14 + version: 0.1.14 '@nx/esbuild': specifier: 22.4.2 version: 22.4.2(@babel/traverse@7.28.5)(@swc-node/register@1.11.1(@swc/core@1.15.3(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.15.3(@swc/helpers@0.5.18))(esbuild@0.28.0)(nx@22.4.2(@swc-node/register@1.11.1(@swc/core@1.15.3(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.15.3(@swc/helpers@0.5.18))) @@ -708,9 +711,6 @@ importers: esbuild: specifier: ^0.27.3 version: 0.27.3 - tsx: - specifier: ^4.19.4 - version: 4.19.4 vitest: specifier: ^4.0.18 version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.12.4)(esbuild@0.27.3)(happy-dom@20.9.0)(jiti@2.7.0)(jsdom@27.4.0)(less@4.1.3)(msw@2.12.10(@types/node@24.12.4)(typescript@5.9.3))(sass@1.84.0)(stylus@0.64.0)(terser@5.47.1)(tsx@4.19.4)(yaml@2.7.1) @@ -5058,6 +5058,55 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@nubjs/nub-darwin-arm64@0.1.14': + resolution: {integrity: sha512-tR4OOmeIebMdXf4q7XTqjx2r0uSt6Yctsym+YLB4npiqqRzcnw/zPc+E4zQ5JhLJoltnULL0fueoSzlXHLRxRQ==} + cpu: [arm64] + os: [darwin] + + '@nubjs/nub-darwin-x64@0.1.14': + resolution: {integrity: sha512-mM/+93NhR6rnHpURWzEGZnf73T7xo2JSbEXwpmuqmpNvQGZCOWBJBGPUziFGQtMjekGI2cjMivX3yVO+TN7LFg==} + cpu: [x64] + os: [darwin] + + '@nubjs/nub-linux-arm64-musl@0.1.14': + resolution: {integrity: sha512-Ihn/sJtEpnrYkz/oV9Qmu2j0ZrfpMy2uW25MHpwbJSU1Ov4SuTL5Jop/so0V6A0W/D8YKEV0vh77MPmYuUl43w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@nubjs/nub-linux-arm64@0.1.14': + resolution: {integrity: sha512-3HS0Vsk5n7Zo8qj5LEAbwaL6SB3lPRireiGpgykR5IkE5JF1v4BhR5CxhYlnT3VFLwestbw2QfwoTg5zyJrvIA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@nubjs/nub-linux-x64-musl@0.1.14': + resolution: {integrity: sha512-1MVdJgVHOUtynfWb5+w8/kWM0dFLAn1x/wiUxE8Nf+OPPWGhb7geVvIncf+S2XAzW6JoIxiKbKCMAlgmA+Cjmg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@nubjs/nub-linux-x64@0.1.14': + resolution: {integrity: sha512-2vqy2SFCJGRHzgSWta0uRUF28twG0MhIJwzd3eSfZ8+VqQ08VIMDGoKvtYANKM+PqAB45o9kUd/+nEAXh0WJFA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@nubjs/nub-win32-arm64@0.1.14': + resolution: {integrity: sha512-lIkZ9G1HRvTgr8pLO3EhgzfAojuK5rZoPRKOx1WobyJpXmUXsMq4uApE2dFA8ZSCrz0JcsrNdI0HtIRkfQeYIg==} + cpu: [arm64] + os: [win32] + + '@nubjs/nub-win32-x64@0.1.14': + resolution: {integrity: sha512-e7521+wFOA1gM1EJnmUgp0dVQwedZXCA90NdsNn4IJmgZ8kEhEQN0PRJoNndpZVqE69GFQ9iMqffwzWVmbx9Kw==} + cpu: [x64] + os: [win32] + + '@nubjs/nub@0.1.14': + resolution: {integrity: sha512-O1AQKJUFenqxJzNMz8htwB0V/3QPnNKAHxgK9MjH2IAHRJZlwlWHoM/+x7vBEkZYg97F1QO8inRxdP2J2bcE6g==} + engines: {node: '>=18.19.0'} + hasBin: true + '@nx/devkit@22.4.2': resolution: {integrity: sha512-ULInRB5YMcUfYdJRCoxrdAhshAv8on5LPVHRvUpk7RmirtLNTDSOL9q83pJD3ChFmwPYwEITPMOwvzm9ku6m7A==} peerDependencies: @@ -23995,6 +24044,41 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@nubjs/nub-darwin-arm64@0.1.14': + optional: true + + '@nubjs/nub-darwin-x64@0.1.14': + optional: true + + '@nubjs/nub-linux-arm64-musl@0.1.14': + optional: true + + '@nubjs/nub-linux-arm64@0.1.14': + optional: true + + '@nubjs/nub-linux-x64-musl@0.1.14': + optional: true + + '@nubjs/nub-linux-x64@0.1.14': + optional: true + + '@nubjs/nub-win32-arm64@0.1.14': + optional: true + + '@nubjs/nub-win32-x64@0.1.14': + optional: true + + '@nubjs/nub@0.1.14': + optionalDependencies: + '@nubjs/nub-darwin-arm64': 0.1.14 + '@nubjs/nub-darwin-x64': 0.1.14 + '@nubjs/nub-linux-arm64': 0.1.14 + '@nubjs/nub-linux-arm64-musl': 0.1.14 + '@nubjs/nub-linux-x64': 0.1.14 + '@nubjs/nub-linux-x64-musl': 0.1.14 + '@nubjs/nub-win32-arm64': 0.1.14 + '@nubjs/nub-win32-x64': 0.1.14 + '@nx/devkit@22.4.2(nx@22.4.2(@swc-node/register@1.11.1(@swc/core@1.15.3(@swc/helpers@0.5.18))(@swc/types@0.1.25)(typescript@5.9.3))(@swc/core@1.15.3(@swc/helpers@0.5.18)))': dependencies: '@zkochan/js-yaml': 0.0.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6e9a6a638..e87945e59 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -97,6 +97,7 @@ syncInjectedDepsAfterScripts: - build allowBuilds: + '@nubjs/nub': true '@biomejs/biome': true '@medusajs/telemetry': false '@parcel/watcher': true diff --git a/scripts/dev/run-zane-main-lane.sh b/scripts/dev/run-zane-main-lane.sh index 9d913c14f..2a5aa40be 100644 --- a/scripts/dev/run-zane-main-lane.sh +++ b/scripts/dev/run-zane-main-lane.sh @@ -141,7 +141,9 @@ require_tools() { common::require_command git common::require_command jq common::ensure_pnpm "$ROOT_DIR" + export PATH="${ROOT_DIR}/node_modules/.bin:${PATH}" common::require_command node + common::require_command nubx } ensure_ctl_built() { diff --git a/scripts/dev/run-zane-preview-lane.sh b/scripts/dev/run-zane-preview-lane.sh index de493cf88..2de1bda3c 100644 --- a/scripts/dev/run-zane-preview-lane.sh +++ b/scripts/dev/run-zane-preview-lane.sh @@ -135,7 +135,9 @@ require_tools() { common::require_command git common::require_command jq common::ensure_pnpm "$ROOT_DIR" + export PATH="${ROOT_DIR}/node_modules/.bin:${PATH}" common::require_command node + common::require_command nubx common::require_command sed } diff --git a/vercel.json b/vercel.json index 5614f2473..e3f5c4f30 100644 --- a/vercel.json +++ b/vercel.json @@ -2,7 +2,7 @@ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "cd libs/ui && pnpm run build:storybook", "devCommand": "cd libs/ui && pnpm run storybook", - "installCommand": "cd libs/ui && pnpm install --frozen-lockfile", + "installCommand": "pnpm -C libs/ui install --frozen-lockfile", "framework": null, "outputDirectory": "./libs/ui/storybook-static" } diff --git a/zerops.yml b/zerops.yml index e3f1da1dd..d5811df7d 100644 --- a/zerops.yml +++ b/zerops.yml @@ -82,7 +82,7 @@ zerops: rm -rf apps/!(frontend-demo) - pnpm i - if [ "${SKIP_NX_BOUNDARIES:-}" != "1" ]; then pnpm lint:nx; fi - - node_modules/.bin/nx run frontend-demo:build --skip-nx-cache --verbose + - node_modules/.bin/nubx --node nx run frontend-demo:build --skip-nx-cache --verbose - rm -rf apps/frontend-demo/node_modules - pnpm --filter=frontend-demo --prod deploy frontend-demo-temp-deploy - mv frontend-demo-temp-deploy/node_modules apps/frontend-demo/ From 5a92a997f7e8e428b8e6d78d1b546b9e440e183e Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Thu, 25 Jun 2026 19:14:16 +0200 Subject: [PATCH 2/2] fix: preserve path casing for nubx scope env --- .../src/__tests__/scope-preview.test.ts | 28 +++++++++++++++++-- .../new-engine-ctl/src/orchestration/scope.ts | 20 +++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/apps/new-engine-ctl/src/__tests__/scope-preview.test.ts b/apps/new-engine-ctl/src/__tests__/scope-preview.test.ts index 308c86145..3ff656d02 100644 --- a/apps/new-engine-ctl/src/__tests__/scope-preview.test.ts +++ b/apps/new-engine-ctl/src/__tests__/scope-preview.test.ts @@ -1,9 +1,9 @@ -import { dirname, join, resolve } from "node:path" +import { delimiter, dirname, join, resolve } from "node:path" import { fileURLToPath } from "node:url" import { expect, test } from "vitest" import { executePlan } from "../orchestration/plan.js" -import { executeScope } from "../orchestration/scope.js" +import { executeScope, withWorkspaceBinPath } from "../orchestration/scope.js" const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..") const stackManifestPath = join( @@ -17,6 +17,30 @@ const stackInputsPath = join( const explicitPreviewRejectPattern = /Explicit services are not deployable on lane preview: medusa-db/ const cloneToPreviewRejectPattern = /clone_to_preview is false/ +const workspaceBinPath = join(process.cwd(), "node_modules", ".bin") + +test("scope preserves existing Path casing when prefixing workspace bin", () => { + const env = withWorkspaceBinPath({ + Path: "C:\\Windows\\System32", + OTHER_VALUE: "kept", + }) + + expect(env.Path).toBe( + [workspaceBinPath, "C:\\Windows\\System32"].join(delimiter) + ) + expect(Object.hasOwn(env, "PATH")).toBe(false) + expect(env.OTHER_VALUE).toBe("kept") +}) + +test("scope removes duplicate path casing when prefixing workspace bin", () => { + const env = withWorkspaceBinPath({ + PATH: "/usr/bin", + Path: "C:\\Windows\\System32", + }) + + expect(env.PATH).toBe([workspaceBinPath, "/usr/bin"].join(delimiter)) + expect(Object.hasOwn(env, "Path")).toBe(false) +}) test("preview scope prepares DB credentials for first baseline replay", async () => { const result = await executeScope({ diff --git a/apps/new-engine-ctl/src/orchestration/scope.ts b/apps/new-engine-ctl/src/orchestration/scope.ts index 9515130e7..4e03dbdfa 100644 --- a/apps/new-engine-ctl/src/orchestration/scope.ts +++ b/apps/new-engine-ctl/src/orchestration/scope.ts @@ -26,10 +26,24 @@ type ExecResult = { stderr: string } -function withWorkspaceBinPath(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { +export function withWorkspaceBinPath( + env: NodeJS.ProcessEnv +): NodeJS.ProcessEnv { + const pathKey = + Object.keys(env).find((key) => key === "PATH") ?? + Object.keys(env).find((key) => key.toUpperCase() === "PATH") ?? + "PATH" + const nextEnv = { ...env } + + for (const key of Object.keys(nextEnv)) { + if (key !== pathKey && key.toUpperCase() === "PATH") { + delete nextEnv[key] + } + } + return { - ...env, - PATH: [join(process.cwd(), "node_modules", ".bin"), env.PATH] + ...nextEnv, + [pathKey]: [join(process.cwd(), "node_modules", ".bin"), env[pathKey]] .filter(Boolean) .join(delimiter), }