@pipelab/test-utils 1.0.0-beta.31 → 1.0.0-beta.33

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @pipelab/test-utils
2
2
 
3
+ ## 1.0.0-beta.33
4
+
5
+ ### Patch Changes
6
+
7
+ - sd
8
+ - Updated dependencies
9
+ - @pipelab/plugin-core@1.0.0-beta.33
10
+
11
+ ## 1.0.0-beta.32
12
+
13
+ ### Patch Changes
14
+
15
+ - sd
16
+ - Updated dependencies
17
+ - @pipelab/plugin-core@1.0.0-beta.32
18
+
3
19
  ## 1.0.0-beta.31
4
20
 
5
21
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -84,9 +84,18 @@ const runCLI = async (args, options = {}) => {
84
84
  */
85
85
  const runAction = async (runner, options) => {
86
86
  const outputs = {};
87
+ let realPnpm = "pnpm";
88
+ try {
89
+ const { execSync } = require("child_process");
90
+ const isWindows = process.platform === "win32";
91
+ const lines = execSync(isWindows ? "where pnpm" : "which pnpm", { encoding: "utf8" }).split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
92
+ if (isWindows) realPnpm = lines.find((l) => l.endsWith(".cmd") || l.endsWith(".exe")) || lines[0] || "pnpm";
93
+ else realPnpm = lines[0] || "pnpm";
94
+ realPnpm = realPnpm.replace(/\\/g, "\\\\");
95
+ } catch (e) {}
87
96
  const pnpmShimPath = (0, node_path.join)(options.sandboxPath, "pnpm-shim.cjs");
88
97
  await (0, node_fs_promises.writeFile)(pnpmShimPath, `const { spawnSync } = require('child_process');
89
- const result = spawnSync('pnpm', process.argv.slice(2), { stdio: 'inherit', shell: true });
98
+ const result = spawnSync('${realPnpm}', process.argv.slice(2), { stdio: 'inherit', shell: true });
90
99
  process.exit(result.status ?? 0);`);
91
100
  const context = {
92
101
  inputs: options.inputs,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;iBAmBgB,eAAA,CAAgB,QAAA;AAAA,cAWnB,SAAA;AAAA,cACA,KAAA;AAAA,cACA,OAAA;;AAFb;;;cAQa,aAAA,GAAuB,MAAA,aAAc,OAAA;;;;;;;;;;;;qCAyBxB,OAAA,WACJ,OAAA;IACL,SAAA;EAAA,MAA4B,OAAA;;;;;;cAyChC,MAAA,GACX,IAAA,YACA,OAAA;EACE,GAAA;EACA,GAAA,GAAM,MAAA;AAAA,MACF,OAAA,CADQ,KAAA,CACR,MAAA;;;;;;;;;cAkBK,SAAA,aAA6B,MAAA,EACxC,MAAA,EAAQ,YAAA,CAAa,CAAA,GACrB,OAAA;EACE,MAAA,EAAQ,gBAAA,CAAiB,CAAA;EACzB,WAAA;EACA,QAAA,GAAW,MAAA;AAAA,MAEZ,OAAA;EAAU,OAAA,EAAS,MAAA;AAAA;AA9BtB;;;AAAA,cAmHa,cAAA,GAAwB,QAAA,UAAkB,OAAA;EAAW,SAAA;AAAA,MAAyB,OAAA,CAM1F,KAAA,CAN0F,MAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;iBAmBgB,eAAA,CAAgB,QAAA;AAAA,cAWnB,SAAA;AAAA,cACA,KAAA;AAAA,cACA,OAAA;;AAFb;;;cAQa,aAAA,GAAuB,MAAA,aAAc,OAAA;;;;;;;;;;;;qCAyBxB,OAAA,WACJ,OAAA;IACL,SAAA;EAAA,MAA4B,OAAA;;;;;;cAyChC,MAAA,GACX,IAAA,YACA,OAAA;EACE,GAAA;EACA,GAAA,GAAM,MAAA;AAAA,MACF,OAAA,CADQ,KAAA,CACR,MAAA;;;;;;;;;cAkBK,SAAA,aAA6B,MAAA,EACxC,MAAA,EAAQ,YAAA,CAAa,CAAA,GACrB,OAAA;EACE,MAAA,EAAQ,gBAAA,CAAiB,CAAA;EACzB,WAAA;EACA,QAAA,GAAW,MAAA;AAAA,MAEZ,OAAA;EAAU,OAAA,EAAS,MAAA;AAAA;AA9BtB;;;AAAA,cAqIa,cAAA,GAAwB,QAAA,UAAkB,OAAA;EAAW,SAAA;AAAA,MAAyB,OAAA,CAM1F,KAAA,CAN0F,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;iBAmBgB,eAAA,CAAgB,QAAA;AAAA,cAWnB,SAAA;AAAA,cACA,KAAA;AAAA,cACA,OAAA;;AAFb;;;cAQa,aAAA,GAAuB,MAAA,aAAc,OAAA;;;;;;;;;;;;qCAyBxB,OAAA,WACJ,OAAA;IACL,SAAA;EAAA,MAA4B,OAAA;;;;;;cAyChC,MAAA,GACX,IAAA,YACA,OAAA;EACE,GAAA;EACA,GAAA,GAAM,MAAA;AAAA,MACF,OAAA,CADQ,OAAA,CACR,MAAA;;;;;;;;;cAkBK,SAAA,aAA6B,MAAA,EACxC,MAAA,EAAQ,YAAA,CAAa,CAAA,GACrB,OAAA;EACE,MAAA,EAAQ,gBAAA,CAAiB,CAAA;EACzB,WAAA;EACA,QAAA,GAAW,MAAA;AAAA,MAEZ,OAAA;EAAU,OAAA,EAAS,MAAA;AAAA;AA9BtB;;;AAAA,cAmHa,cAAA,GAAwB,QAAA,UAAkB,OAAA;EAAW,SAAA;AAAA,MAAyB,OAAA,CAM1F,OAAA,CAN0F,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;iBAmBgB,eAAA,CAAgB,QAAA;AAAA,cAWnB,SAAA;AAAA,cACA,KAAA;AAAA,cACA,OAAA;;AAFb;;;cAQa,aAAA,GAAuB,MAAA,aAAc,OAAA;;;;;;;;;;;;qCAyBxB,OAAA,WACJ,OAAA;IACL,SAAA;EAAA,MAA4B,OAAA;;;;;;cAyChC,MAAA,GACX,IAAA,YACA,OAAA;EACE,GAAA;EACA,GAAA,GAAM,MAAA;AAAA,MACF,OAAA,CADQ,OAAA,CACR,MAAA;;;;;;;;;cAkBK,SAAA,aAA6B,MAAA,EACxC,MAAA,EAAQ,YAAA,CAAa,CAAA,GACrB,OAAA;EACE,MAAA,EAAQ,gBAAA,CAAiB,CAAA;EACzB,WAAA;EACA,QAAA,GAAW,MAAA;AAAA,MAEZ,OAAA;EAAU,OAAA,EAAS,MAAA;AAAA;AA9BtB;;;AAAA,cAqIa,cAAA,GAAwB,QAAA,UAAkB,OAAA;EAAW,SAAA;AAAA,MAAyB,OAAA,CAM1F,OAAA,CAN0F,MAAA"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire } from "node:module";
1
2
  import { dirname, join, resolve } from "node:path";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { chmod, mkdir, rm, writeFile } from "node:fs/promises";
@@ -5,6 +6,9 @@ import { existsSync } from "node:fs";
5
6
  import { tmpdir } from "node:os";
6
7
  import { PipelabContext } from "@pipelab/plugin-core";
7
8
  import { execa } from "execa";
9
+ //#region \0rolldown/runtime.js
10
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
+ //#endregion
8
12
  //#region src/index.ts
9
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
14
  /**
@@ -83,9 +87,18 @@ const runCLI = async (args, options = {}) => {
83
87
  */
84
88
  const runAction = async (runner, options) => {
85
89
  const outputs = {};
90
+ let realPnpm = "pnpm";
91
+ try {
92
+ const { execSync } = __require("child_process");
93
+ const isWindows = process.platform === "win32";
94
+ const lines = execSync(isWindows ? "where pnpm" : "which pnpm", { encoding: "utf8" }).split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
95
+ if (isWindows) realPnpm = lines.find((l) => l.endsWith(".cmd") || l.endsWith(".exe")) || lines[0] || "pnpm";
96
+ else realPnpm = lines[0] || "pnpm";
97
+ realPnpm = realPnpm.replace(/\\/g, "\\\\");
98
+ } catch (e) {}
86
99
  const pnpmShimPath = join(options.sandboxPath, "pnpm-shim.cjs");
87
100
  await writeFile(pnpmShimPath, `const { spawnSync } = require('child_process');
88
- const result = spawnSync('pnpm', process.argv.slice(2), { stdio: 'inherit', shell: true });
101
+ const result = spawnSync('${realPnpm}', process.argv.slice(2), { stdio: 'inherit', shell: true });
89
102
  process.exit(result.status ?? 0);`);
90
103
  const context = {
91
104
  inputs: options.inputs,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["existsSyncSync"],"sources":["../src/index.ts"],"sourcesContent":["import { resolve, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { mkdir, writeFile, chmod, rm } from \"node:fs/promises\";\nimport { existsSync as existsSyncSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport {\n type ActionRunner,\n type ActionRunnerData,\n type Action,\n PipelabContext,\n} from \"@pipelab/plugin-core\";\nimport { execa } from \"execa\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Finds the monorepo root by looking for pnpm-workspace.yaml.\n */\nexport function findProjectRoot(startDir: string): string {\n let curr = startDir;\n while (curr !== dirname(curr)) {\n if (existsSyncSync(join(curr, \"pnpm-workspace.yaml\"))) {\n return curr;\n }\n curr = dirname(curr);\n }\n throw new Error(\"Could not find project root (pnpm-workspace.yaml)\");\n}\n\nexport const isWindows = process.platform === \"win32\";\nexport const isMac = process.platform === \"darwin\";\nexport const isLinux = process.platform === \"linux\";\n\n/**\n * Creates a unique sandbox directory and returns a bundle containing its path\n * and a pre-filled remove utility.\n */\nexport const createSandbox = async (prefix: string) => {\n const sandboxPath = join(tmpdir(), `${prefix}-${Math.random().toString(36).substring(7)}`);\n\n const paths = {\n input: join(sandboxPath, \"input\"),\n output: join(sandboxPath, \"output\"),\n userData: join(sandboxPath, \"user-data\"),\n project: join(sandboxPath, \"project\"),\n thirdparty: join(sandboxPath, \"thirdparty\"),\n };\n\n await mkdir(sandboxPath, { recursive: true });\n await Promise.all([\n mkdir(paths.input, { recursive: true }),\n mkdir(paths.output, { recursive: true }),\n mkdir(paths.userData, { recursive: true }),\n ]);\n\n return {\n path: sandboxPath,\n paths,\n /**\n * Creates a mock binary/script in the sandbox.\n */\n mockBinary: async (\n relativePath: string,\n content?: string,\n options: { extension?: string | false } = {},\n ) => {\n const fullPath = join(sandboxPath, relativePath);\n let platformPath = fullPath;\n\n if (options.extension === false) {\n // Use exactly the path provided\n } else if (options.extension) {\n platformPath = `${fullPath}.${options.extension}`;\n } else {\n const hasExtension = /\\.[a-z0-9]+$/i.test(relativePath);\n platformPath = hasExtension ? fullPath : isWindows ? `${fullPath}.cmd` : `${fullPath}.sh`;\n }\n\n const defaultContent = isWindows\n ? `@echo off\\necho Mock Binary Execution: %*\\nexit /b 0`\n : `#!/bin/bash\\necho \"Mock Binary Execution: $@\"\\nexit 0`;\n\n await mkdir(dirname(platformPath), { recursive: true });\n await writeFile(platformPath, content || defaultContent);\n\n if (!isWindows) {\n await chmod(platformPath, 0o755);\n }\n\n return platformPath;\n },\n remove: async () => {\n await rm(sandboxPath, {\n recursive: true,\n force: true,\n maxRetries: 20,\n retryDelay: 500,\n });\n },\n };\n};\n\n/**\n * Runs the Pipelab CLI out-of-process.\n */\nexport const runCLI = async (\n args: string[],\n options: {\n cwd?: string;\n env?: Record<string, string>;\n } = {},\n) => {\n const projectRoot = findProjectRoot(__dirname);\n const cliPath = resolve(projectRoot, \"apps/cli/src/index.ts\");\n\n return execa(\"tsx\", [cliPath, ...args], {\n cwd: options.cwd || projectRoot,\n env: {\n ...process.env,\n PIPELAB_DISABLE_HISTORY: \"true\",\n ...options.env,\n },\n });\n};\n\n/**\n * Runs a Pipelab action runner directly in-process for focused testing.\n */\nexport const runAction = async <A extends Action>(\n runner: ActionRunner<A>,\n options: {\n inputs: ActionRunnerData<A>[\"inputs\"];\n sandboxPath: string;\n extraEnv?: Record<string, string>;\n },\n): Promise<{ outputs: Record<string, unknown> }> => {\n const outputs: Record<string, unknown> = {};\n\n // Create a pnpm shim because ensureNPMPackage expects a JS file runnable by node\n const pnpmShimPath = join(options.sandboxPath, \"pnpm-shim.cjs\");\n await writeFile(\n pnpmShimPath,\n `const { spawnSync } = require('child_process');\nconst result = spawnSync('pnpm', process.argv.slice(2), { stdio: 'inherit', shell: true });\nprocess.exit(result.status ?? 0);`,\n );\n\n const context: ActionRunnerData<A> = {\n inputs: options.inputs,\n log: (...args: any[]) => {\n console.log(\"[Runner Log]\", ...args);\n },\n setOutput: (key: any, value: any) => {\n console.log(`[Runner Output] ${key} = ${value}`);\n outputs[key] = value;\n },\n cwd: options.sandboxPath,\n paths: {\n cache: join(options.sandboxPath, \"cache\"),\n pnpm: pnpmShimPath,\n node: process.execPath,\n userData: join(options.sandboxPath, \"user-data\"),\n modules: join(options.sandboxPath, \"modules\"),\n thirdparty: join(options.sandboxPath, \"thirdparty\"),\n },\n api: {\n fetchAsset: async (packageName: string) => {\n const sandboxAssetPath = join(options.sandboxPath, \"assets\", packageName);\n if (existsSyncSync(sandboxAssetPath)) {\n return sandboxAssetPath;\n }\n // Fallback to real monorepo assets\n // Normalize: remove @pipelab/ prefix if present\n const folderName = packageName.startsWith(\"@pipelab/\")\n ? packageName.replace(\"@pipelab/\", \"\")\n : packageName;\n\n const projectRoot = findProjectRoot(__dirname);\n return join(projectRoot, \"assets\", folderName);\n },\n },\n // @ts-ignore - Mocking BrowserWindow\n browserWindow: undefined,\n abortSignal: new AbortController().signal,\n context: new PipelabContext({\n userDataPath: join(options.sandboxPath, \"user-data\"),\n }),\n // @ts-ignore - Mocking setMeta\n setMeta: () => {},\n meta: {} as any,\n };\n\n // Set environment variables for the test if provided\n const originalEnv = { ...process.env };\n if (options.extraEnv) {\n Object.assign(process.env, options.extraEnv);\n }\n\n try {\n await runner(context);\n } finally {\n // Restore original environment\n if (options.extraEnv) {\n // Remove keys that were added\n for (const key in options.extraEnv) {\n if (!(key in originalEnv)) {\n delete process.env[key];\n }\n }\n // Restore original values\n Object.assign(process.env, originalEnv);\n }\n }\n\n return { outputs };\n};\n\n/**\n * Runs the packaged electron app (smoke test)\n */\nexport const runElectronApp = async (app_path: string, options: { timeoutMs?: number } = {}) => {\n const child = execa(app_path, [], {\n cleanup: true,\n timeout: options.timeoutMs || 120000,\n });\n return child;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["existsSyncSync"],"sources":["../src/index.ts"],"sourcesContent":["import { resolve, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { mkdir, writeFile, chmod, rm } from \"node:fs/promises\";\nimport { existsSync as existsSyncSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport {\n type ActionRunner,\n type ActionRunnerData,\n type Action,\n PipelabContext,\n} from \"@pipelab/plugin-core\";\nimport { execa } from \"execa\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Finds the monorepo root by looking for pnpm-workspace.yaml.\n */\nexport function findProjectRoot(startDir: string): string {\n let curr = startDir;\n while (curr !== dirname(curr)) {\n if (existsSyncSync(join(curr, \"pnpm-workspace.yaml\"))) {\n return curr;\n }\n curr = dirname(curr);\n }\n throw new Error(\"Could not find project root (pnpm-workspace.yaml)\");\n}\n\nexport const isWindows = process.platform === \"win32\";\nexport const isMac = process.platform === \"darwin\";\nexport const isLinux = process.platform === \"linux\";\n\n/**\n * Creates a unique sandbox directory and returns a bundle containing its path\n * and a pre-filled remove utility.\n */\nexport const createSandbox = async (prefix: string) => {\n const sandboxPath = join(tmpdir(), `${prefix}-${Math.random().toString(36).substring(7)}`);\n\n const paths = {\n input: join(sandboxPath, \"input\"),\n output: join(sandboxPath, \"output\"),\n userData: join(sandboxPath, \"user-data\"),\n project: join(sandboxPath, \"project\"),\n thirdparty: join(sandboxPath, \"thirdparty\"),\n };\n\n await mkdir(sandboxPath, { recursive: true });\n await Promise.all([\n mkdir(paths.input, { recursive: true }),\n mkdir(paths.output, { recursive: true }),\n mkdir(paths.userData, { recursive: true }),\n ]);\n\n return {\n path: sandboxPath,\n paths,\n /**\n * Creates a mock binary/script in the sandbox.\n */\n mockBinary: async (\n relativePath: string,\n content?: string,\n options: { extension?: string | false } = {},\n ) => {\n const fullPath = join(sandboxPath, relativePath);\n let platformPath = fullPath;\n\n if (options.extension === false) {\n // Use exactly the path provided\n } else if (options.extension) {\n platformPath = `${fullPath}.${options.extension}`;\n } else {\n const hasExtension = /\\.[a-z0-9]+$/i.test(relativePath);\n platformPath = hasExtension ? fullPath : isWindows ? `${fullPath}.cmd` : `${fullPath}.sh`;\n }\n\n const defaultContent = isWindows\n ? `@echo off\\necho Mock Binary Execution: %*\\nexit /b 0`\n : `#!/bin/bash\\necho \"Mock Binary Execution: $@\"\\nexit 0`;\n\n await mkdir(dirname(platformPath), { recursive: true });\n await writeFile(platformPath, content || defaultContent);\n\n if (!isWindows) {\n await chmod(platformPath, 0o755);\n }\n\n return platformPath;\n },\n remove: async () => {\n await rm(sandboxPath, {\n recursive: true,\n force: true,\n maxRetries: 20,\n retryDelay: 500,\n });\n },\n };\n};\n\n/**\n * Runs the Pipelab CLI out-of-process.\n */\nexport const runCLI = async (\n args: string[],\n options: {\n cwd?: string;\n env?: Record<string, string>;\n } = {},\n) => {\n const projectRoot = findProjectRoot(__dirname);\n const cliPath = resolve(projectRoot, \"apps/cli/src/index.ts\");\n\n return execa(\"tsx\", [cliPath, ...args], {\n cwd: options.cwd || projectRoot,\n env: {\n ...process.env,\n PIPELAB_DISABLE_HISTORY: \"true\",\n ...options.env,\n },\n });\n};\n\n/**\n * Runs a Pipelab action runner directly in-process for focused testing.\n */\nexport const runAction = async <A extends Action>(\n runner: ActionRunner<A>,\n options: {\n inputs: ActionRunnerData<A>[\"inputs\"];\n sandboxPath: string;\n extraEnv?: Record<string, string>;\n },\n): Promise<{ outputs: Record<string, unknown> }> => {\n const outputs: Record<string, unknown> = {};\n\n let realPnpm = \"pnpm\";\n try {\n const { execSync } = require(\"child_process\");\n const isWindows = process.platform === \"win32\";\n const cmd = isWindows ? \"where pnpm\" : \"which pnpm\";\n const stdout = execSync(cmd, { encoding: \"utf8\" });\n const lines = stdout.split(/\\r?\\n/).map(l => l.trim()).filter(Boolean);\n if (isWindows) {\n realPnpm = lines.find(l => l.endsWith(\".cmd\") || l.endsWith(\".exe\")) || lines[0] || \"pnpm\";\n } else {\n realPnpm = lines[0] || \"pnpm\";\n }\n // Need to escape backslashes for JS string literal\n realPnpm = realPnpm.replace(/\\\\/g, \"\\\\\\\\\");\n } catch (e) {\n // fallback\n }\n\n // Create a pnpm shim because ensureNPMPackage expects a JS file runnable by node\n const pnpmShimPath = join(options.sandboxPath, \"pnpm-shim.cjs\");\n await writeFile(\n pnpmShimPath,\n `const { spawnSync } = require('child_process');\nconst result = spawnSync('${realPnpm}', process.argv.slice(2), { stdio: 'inherit', shell: true });\nprocess.exit(result.status ?? 0);`,\n );\n\n const context: ActionRunnerData<A> = {\n inputs: options.inputs,\n log: (...args: any[]) => {\n console.log(\"[Runner Log]\", ...args);\n },\n setOutput: (key: any, value: any) => {\n console.log(`[Runner Output] ${key} = ${value}`);\n outputs[key] = value;\n },\n cwd: options.sandboxPath,\n paths: {\n cache: join(options.sandboxPath, \"cache\"),\n pnpm: pnpmShimPath,\n node: process.execPath,\n userData: join(options.sandboxPath, \"user-data\"),\n modules: join(options.sandboxPath, \"modules\"),\n thirdparty: join(options.sandboxPath, \"thirdparty\"),\n },\n api: {\n fetchAsset: async (packageName: string) => {\n const sandboxAssetPath = join(options.sandboxPath, \"assets\", packageName);\n if (existsSyncSync(sandboxAssetPath)) {\n return sandboxAssetPath;\n }\n // Fallback to real monorepo assets\n // Normalize: remove @pipelab/ prefix if present\n const folderName = packageName.startsWith(\"@pipelab/\")\n ? packageName.replace(\"@pipelab/\", \"\")\n : packageName;\n\n const projectRoot = findProjectRoot(__dirname);\n return join(projectRoot, \"assets\", folderName);\n },\n },\n // @ts-ignore - Mocking BrowserWindow\n browserWindow: undefined,\n abortSignal: new AbortController().signal,\n context: new PipelabContext({\n userDataPath: join(options.sandboxPath, \"user-data\"),\n }),\n // @ts-ignore - Mocking setMeta\n setMeta: () => {},\n meta: {} as any,\n };\n\n // Set environment variables for the test if provided\n const originalEnv = { ...process.env };\n if (options.extraEnv) {\n Object.assign(process.env, options.extraEnv);\n }\n\n try {\n await runner(context);\n } finally {\n // Restore original environment\n if (options.extraEnv) {\n // Remove keys that were added\n for (const key in options.extraEnv) {\n if (!(key in originalEnv)) {\n delete process.env[key];\n }\n }\n // Restore original values\n Object.assign(process.env, originalEnv);\n }\n }\n\n return { outputs };\n};\n\n/**\n * Runs the packaged electron app (smoke test)\n */\nexport const runElectronApp = async (app_path: string, options: { timeoutMs?: number } = {}) => {\n const child = execa(app_path, [], {\n cleanup: true,\n timeout: options.timeoutMs || 120000,\n });\n return child;\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelab/test-utils",
3
- "version": "1.0.0-beta.31",
3
+ "version": "1.0.0-beta.33",
4
4
  "private": false,
5
5
  "description": "Testing utilities for Pipelab plugins and monorepo",
6
6
  "license": "FSL-1.1-MIT",
@@ -27,13 +27,13 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "execa": "9.5.1",
30
- "@pipelab/plugin-core": "1.0.0-beta.31"
30
+ "@pipelab/plugin-core": "1.0.0-beta.33"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "24.12.2",
34
34
  "tsdown": "0.21.2",
35
35
  "typescript": "^5.0.0",
36
- "@pipelab/tsconfig": "1.0.0-beta.24"
36
+ "@pipelab/tsconfig": "1.0.0-beta.26"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "tsdown",
package/src/index.ts CHANGED
@@ -137,12 +137,30 @@ export const runAction = async <A extends Action>(
137
137
  ): Promise<{ outputs: Record<string, unknown> }> => {
138
138
  const outputs: Record<string, unknown> = {};
139
139
 
140
+ let realPnpm = "pnpm";
141
+ try {
142
+ const { execSync } = require("child_process");
143
+ const isWindows = process.platform === "win32";
144
+ const cmd = isWindows ? "where pnpm" : "which pnpm";
145
+ const stdout = execSync(cmd, { encoding: "utf8" });
146
+ const lines = stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
147
+ if (isWindows) {
148
+ realPnpm = lines.find(l => l.endsWith(".cmd") || l.endsWith(".exe")) || lines[0] || "pnpm";
149
+ } else {
150
+ realPnpm = lines[0] || "pnpm";
151
+ }
152
+ // Need to escape backslashes for JS string literal
153
+ realPnpm = realPnpm.replace(/\\/g, "\\\\");
154
+ } catch (e) {
155
+ // fallback
156
+ }
157
+
140
158
  // Create a pnpm shim because ensureNPMPackage expects a JS file runnable by node
141
159
  const pnpmShimPath = join(options.sandboxPath, "pnpm-shim.cjs");
142
160
  await writeFile(
143
161
  pnpmShimPath,
144
162
  `const { spawnSync } = require('child_process');
145
- const result = spawnSync('pnpm', process.argv.slice(2), { stdio: 'inherit', shell: true });
163
+ const result = spawnSync('${realPnpm}', process.argv.slice(2), { stdio: 'inherit', shell: true });
146
164
  process.exit(result.status ?? 0);`,
147
165
  );
148
166