@midscene/test 1.13.2-beta-20260923035443.0 → 1.13.3

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/dist/lib/0~160.js CHANGED
@@ -217,7 +217,7 @@ export default defineTestProject<ProjectContext>({
217
217
  'cases/example.yaml': 'web' === platform ? 'cases:\n - name: Open example.com\n steps:\n - gotoUrl: https://example.com\n - aiAssert: The page displays the heading Example Domain\n' : 'computer' === platform ? 'cases:\n - name: Inspect the desktop\n steps:\n - aiAsk: Describe the current desktop\n' : 'cases:\n - name: Inspect the home screen\n steps:\n - home: {}\n - aiAsk: Describe the current screen\n',
218
218
  '.env.example': `${modelEnv}\n${env}`,
219
219
  '.gitignore': 'node_modules/\n.env\nmidscene_run/\n',
220
- 'README.md': `# ${name}\n\nA Midscene Test project for ${platform}.\n\nIf you skipped installation during creation, run \`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\`. The \`postinstall\` script automatically generates \`midscene-node-reference.md\` after each dependency installation. If lifecycle scripts are disabled, run \`${packageManager} run nodes\` manually.\n\nCopy \`.env.example\` to \`.env\`, then choose and configure a model using the [supported models and setup guide](https://midscenejs.com/model-common-config.html). Do not commit \`.env\` to your repository; inject the model configuration as environment variables in CI.\n\n${instructions}\n\nRun tests with \`${packageManager} test\`. Read \`midscene-node-reference.md\` for the available Nodes and their inputs.\n\nAfter changing Node registrations in \`midscene.config.ts\`, run \`${packageManager} run nodes\` to refresh the reference. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\n\nReports are written to \`midscene_run/report/\`.\n`
220
+ 'README.md': `# ${name}\n\nA Midscene Test project for ${platform}.\n\nIf you skipped installation during creation, run \`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\`. The \`postinstall\` script automatically generates \`midscene-node-spec.${platform}.md\` after each dependency installation. If lifecycle scripts are disabled, run \`${packageManager} run nodes\` manually.\n\nCopy \`.env.example\` to \`.env\`, then choose and configure a model using the [supported models and setup guide](https://midscenejs.com/model-common-config.html). Do not commit \`.env\` to your repository; inject the model configuration as environment variables in CI.\n\n${instructions}\n\nRun tests with \`${packageManager} test\`. Read \`midscene-node-spec.${platform}.md\` for the available Nodes and their inputs.\n\nAfter changing Node registrations in \`midscene.config.ts\`, run \`${packageManager} run nodes\` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\n\nReports are written to \`midscene_run/report/\`.\n`
221
221
  };
222
222
  }
223
223
  const createParser = ()=>yargs_default()().scriptName('midscene-test create').usage('Usage: $0 [directory] [options]').parserConfiguration({
@@ -243,8 +243,8 @@ export default defineTestProject<ProjectContext>({
243
243
  }).option('skip-install', {
244
244
  type: 'boolean',
245
245
  default: false,
246
- description: 'Create files without installing dependencies or generating midscene-node-reference.md'
247
- }).demandCommand(0, 1, '', 'Only one project directory is allowed.').example('$0', 'Choose a directory and platform interactively').example('$0 my-tests --platform web', 'Create a Web project').epilogue("Optionally installs dependencies with the selected package manager. The generated postinstall script creates midscene-node-reference.md after installation.\nExisting files are never overwritten. Setup and tests are not run during creation.").help('help').alias('help', 'h').version(false).strict().exitProcess(false).showHelpOnFail(false).fail((message, error)=>{
246
+ description: 'Create files without installing dependencies or generating Node Specs'
247
+ }).demandCommand(0, 1, '', 'Only one project directory is allowed.').example('$0', 'Choose a directory and platform interactively').example('$0 my-tests --platform web', 'Create a Web project').epilogue("Optionally installs dependencies with the selected package manager. The generated postinstall script creates a Project-specific Node Spec after installation.\nExisting files are never overwritten. Setup and tests are not run during creation.").help('help').alias('help', 'h').version(false).strict().exitProcess(false).showHelpOnFail(false).fail((message, error)=>{
248
248
  throw error ?? new Error(message);
249
249
  }).wrap(100);
250
250
  function parseCreateArgs(args, onOutput) {
@@ -329,7 +329,7 @@ export default defineTestProject<ProjectContext>({
329
329
  selectPlatform,
330
330
  selectPackageManager,
331
331
  confirmInstall: ()=>(0, prompts_.confirm)({
332
- message: 'Install dependencies and generate midscene-node-reference.md now?',
332
+ message: 'Install dependencies and generate Node Specs now?',
333
333
  default: true
334
334
  }),
335
335
  runPackageManager,
@@ -357,7 +357,7 @@ export default defineTestProject<ProjectContext>({
357
357
  const files = createProjectFiles(name, platform, packageManager);
358
358
  checkDestinations(root, [
359
359
  ...Object.keys(files),
360
- 'midscene-node-reference.md',
360
+ `midscene-node-spec.${platform}.md`,
361
361
  'pnpm-lock.yaml',
362
362
  'package-lock.json',
363
363
  'npm-shrinkwrap.json'
@@ -379,29 +379,29 @@ export default defineTestProject<ProjectContext>({
379
379
  if (error instanceof Error && [
380
380
  'ExitPromptError',
381
381
  'AbortPromptError'
382
- ].includes(error.name)) throw new Error(`Project creation cancelled. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-reference.md.`);
382
+ ].includes(error.name)) throw new Error(`Project creation cancelled. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-spec.${platform}.md.`);
383
383
  throw error;
384
384
  }
385
- if (!install) return void io.log(`Project files ready: ${root}\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-reference.md.\nThen follow README.md to configure your model and run tests.`);
385
+ if (!install) return void io.log(`Project files ready: ${root}\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-spec.${platform}.md.\nThen follow README.md to configure your model and run tests.`);
386
386
  io.log(`Installing dependencies with ${packageManager}...`);
387
387
  try {
388
388
  await services.runPackageManager(packageManager, commands.install, root);
389
389
  } catch (error) {
390
- throw new Error(`Dependency installation or postinstall Node reference generation failed. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\n${error instanceof Error ? error.message : String(error)}`);
390
+ throw new Error(`Dependency installation or postinstall Node Spec generation failed. Project files are preserved in ${root}.\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\n${error instanceof Error ? error.message : String(error)}`);
391
391
  }
392
392
  try {
393
- const referencePath = (0, external_node_path_.resolve)(root, 'midscene-node-reference.md');
394
- const generatedByPostinstall = Boolean(statIfPresent(referencePath));
393
+ const specPath = (0, external_node_path_.resolve)(root, `midscene-node-spec.${platform}.md`);
394
+ const generatedByPostinstall = Boolean(statIfPresent(specPath));
395
395
  if (!generatedByPostinstall) {
396
- io.log('Generating Node reference...');
396
+ io.log('Generating Node Spec...');
397
397
  await services.runPackageManager(packageManager, commands.describe, root);
398
398
  }
399
- const markdown = (0, external_node_fs_.readFileSync)(referencePath, 'utf8');
400
- if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) throw new Error('nodes did not generate a valid Node reference.');
399
+ const markdown = (0, external_node_fs_.readFileSync)(specPath, 'utf8');
400
+ if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) throw new Error('nodes did not generate a valid Node Spec.');
401
401
  } catch (error) {
402
- throw new Error(`Node reference generation failed. Project files are preserved in ${root}.\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\n${error instanceof Error ? error.message : String(error)}`);
402
+ throw new Error(`Node Spec generation failed. Project files are preserved in ${root}.\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\n${error instanceof Error ? error.message : String(error)}`);
403
403
  }
404
- io.log(`Project ready: ${root}\nNode reference: ${(0, external_node_path_.resolve)(root, 'midscene-node-reference.md')}\nNext: copy .env.example to .env and configure your model.${'web' === platform ? `\nInstall Chromium: ${commands.installChromium}` : 'computer' === platform ? '\nPrepare desktop dependencies and permissions as described in README.md.' : '\nConfigure your device connection in .env.'}\nRun tests from the project directory: ${packageManager} test`);
404
+ io.log(`Project ready: ${root}\nNode Spec: ${(0, external_node_path_.resolve)(root, `midscene-node-spec.${platform}.md`)}\nNext: copy .env.example to .env and configure your model.${'web' === platform ? `\nInstall Chromium: ${commands.installChromium}` : 'computer' === platform ? '\nPrepare desktop dependencies and permissions as described in README.md.' : '\nConfigure your device connection in .env.'}\nRun tests from the project directory: ${packageManager} test`);
405
405
  }
406
406
  }
407
407
  };
@@ -1 +1 @@
1
- {"version":3,"file":"0~160.js","sources":["../../src/cli/create-package-manager.ts","../../src/cli/create-template.ts","../../src/cli/create-command.ts"],"sourcesContent":["export const createPackageManagers = ['npm', 'pnpm'] as const;\nexport type CreatePackageManager = (typeof createPackageManagers)[number];\n\nexport const packageManagerCommands: Record<\n CreatePackageManager,\n { install: string[]; describe: string[]; installChromium: string }\n> = {\n npm: {\n install: ['install', '--workspaces=false'],\n describe: [\n 'exec',\n '--no',\n '--workspaces=false',\n '--',\n 'midscene-test',\n 'nodes',\n ],\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n installChromium: 'pnpm exec playwright install chromium',\n },\n};\n\nexport function detectPackageManager(userAgent?: string): CreatePackageManager {\n return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';\n}\n","import { version } from '../../package.json';\nimport {\n type CreatePackageManager,\n packageManagerCommands,\n} from './create-package-manager';\n\nexport const createPlatforms = [\n 'web',\n 'android',\n 'ios',\n 'harmony',\n 'computer',\n] as const;\nexport type CreatePlatform = (typeof createPlatforms)[number];\n\nexport const createPlatformLabels: Record<CreatePlatform, string> = {\n web: 'Web (Playwright)',\n android: 'Android',\n ios: 'iOS',\n harmony: 'HarmonyOS',\n computer: 'Desktop (Computer)',\n};\n\nconst platformImports: Record<CreatePlatform, string> = {\n web: `import { PlaywrightAgent } from '@midscene/web/playwright/agent';\nimport { chromium, type Page } from 'playwright';`,\n android: `import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';`,\n ios: `import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';`,\n harmony: `import { HarmonyAgent, agentFromHdcDevice } from '@midscene/harmony';`,\n computer: `import { ComputerAgent, agentForComputer } from '@midscene/computer';`,\n};\n\nconst agentClasses: Record<CreatePlatform, string> = {\n web: 'PlaywrightAgent',\n android: 'AndroidAgent',\n ios: 'IOSAgent',\n harmony: 'HarmonyAgent',\n computer: 'ComputerAgent',\n};\n\nconst platformSetup: Record<CreatePlatform, string> = {\n web: ` const browser = await chromium.launch({ headless: env.HEADLESS !== 'false' });\n onTeardown(() => browser.close());\n const page = await browser.newPage();\n const context: ProjectContext = { page };\n onTeardown(async () => { await context.agent?.destroy(); });\n return context;`,\n android: ` const agent = await agentFromAdbDevice(env.ANDROID_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n ios: ` const port = Number(env.WDA_PORT || 8100);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error('WDA_PORT must be an integer between 1 and 65535.');\n }\n const agent = await agentFromWebDriverAgent({\n wdaHost: env.WDA_HOST || 'localhost',\n wdaPort: port,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n harmony: ` const agent = await agentFromHdcDevice(env.HARMONY_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n computer: ` const agent = await agentForComputer({\n displayId: env.COMPUTER_DISPLAY_ID || undefined,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n};\n\nconst platformEnv: Record<CreatePlatform, string> = {\n web: 'HEADLESS=true\\n',\n android:\n '# Optional: choose a device from adb devices.\\nANDROID_DEVICE_ID=\\n',\n ios: 'WDA_HOST=localhost\\nWDA_PORT=8100\\n',\n harmony:\n '# Optional: choose a device from hdc list targets.\\nHARMONY_DEVICE_ID=\\n',\n computer:\n '# Optional: select a display; leave empty for the default display.\\nCOMPUTER_DISPLAY_ID=\\n# Enable Xvfb on headless Linux after installing its dependencies.\\nMIDSCENE_COMPUTER_HEADLESS_LINUX=false\\n',\n};\n\nconst modelEnv = `# Copy this file to .env before running tests.\n# Do not commit .env to your repository. In CI, inject these values as environment variables instead.\n# Choose a supported model and copy its four configuration values from:\n# https://midscenejs.com/model-common-config.html\n# For the complete environment variable reference, see:\n# https://midscenejs.com/model-config.html\nMIDSCENE_MODEL_BASE_URL=\nMIDSCENE_MODEL_API_KEY=\nMIDSCENE_MODEL_NAME=\nMIDSCENE_MODEL_FAMILY=\n`;\n\nconst platformInstructions: Record<Exclude<CreatePlatform, 'web'>, string> = {\n android:\n 'Connect an Android device and verify it with `adb devices`. Set ANDROID_DEVICE_ID to select a device.',\n ios: 'Start WebDriverAgent and set WDA_HOST and WDA_PORT for your iOS device.',\n harmony:\n 'Connect a HarmonyOS device and verify it with `hdc list targets`. Set HARMONY_DEVICE_ID to select a device. Set HDC_HOME if hdc is not on PATH.',\n computer:\n 'Prepare the local desktop using the [desktop setup guide](https://midscenejs.com/platforms/desktop.html), including system dependencies and permissions. Set COMPUTER_DISPLAY_ID to select a display. For headless Linux, install Xvfb and enable MIDSCENE_COMPUTER_HEADLESS_LINUX.',\n};\n\nexport function createProjectFiles(\n name: string,\n platform: CreatePlatform,\n packageManager: CreatePackageManager,\n): Record<string, string> {\n const instructions =\n platform === 'web'\n ? `Install Chromium before the first test: \\`${packageManagerCommands[packageManager].installChromium}\\`.`\n : platformInstructions[platform];\n const agentClass = agentClasses[platform];\n const config = `import { fileURLToPath } from 'node:url';\nimport { config as loadEnv } from 'dotenv';\nimport type { NodeExecutionContext } from '@midscene/test';\nimport { defineProjectSetup, defineTestProject } from '@midscene/test/config';\nimport { createMidsceneNodes } from '@midscene/test/midscene';\n${platformImports[platform]}\n\nloadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });\n\ninterface ProjectContext {\n${platform === 'web' ? ' page: Page;\\n agent?: PlaywrightAgent;' : ` agent: ${agentClass};`}\n}\n\nconst getAgent = ({ context }: NodeExecutionContext<unknown, ProjectContext>) => {\n${platform === 'web' ? ' context.agent ??= new PlaywrightAgent(context.page);' : ''}\n return context.agent;\n};\n\nconst setup = defineProjectSetup<ProjectContext>({\n name: '${platform}',\n async setup({ env, onTeardown }) {\n${platformSetup[platform]}\n },\n});\n\nexport default defineTestProject<ProjectContext>({\n projects: [{\n name: '${platform}',\n setup,\n files: { include: ['cases/**/*.{yaml,yml}'] },\n }],\n nodes: [\n ...createMidsceneNodes<ProjectContext>({ agentClass: ${agentClass}, getAgent }),\n ],\n});\n`;\n const dependencies: Record<string, string> = {\n '@midscene/test': version,\n [`@midscene/${platform}`]: version,\n '@types/node': '^20.0.0',\n dotenv: '^16.4.5',\n typescript: '^5.8.3',\n ...(platform === 'web' ? { playwright: '^1.45.0' } : {}),\n };\n const env = platformEnv[platform];\n return {\n 'package.json': `${JSON.stringify(\n {\n name,\n private: true,\n type: 'module',\n scripts: {\n test: 'midscene-test',\n nodes: 'midscene-test nodes',\n postinstall: 'midscene-test nodes',\n },\n engines: { node: '^20.19.0 || ^22.12.0 || >=24.0.0' },\n devDependencies: dependencies,\n },\n null,\n 2,\n )}\\n`,\n 'midscene.config.ts': config,\n 'tsconfig.json': `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n types: ['node'],\n },\n include: ['midscene.config.ts'],\n },\n null,\n 2,\n )}\\n`,\n 'cases/example.yaml':\n platform === 'web'\n ? 'cases:\\n - name: Open example.com\\n steps:\\n - gotoUrl: https://example.com\\n - aiAssert: The page displays the heading Example Domain\\n'\n : platform === 'computer'\n ? 'cases:\\n - name: Inspect the desktop\\n steps:\\n - aiAsk: Describe the current desktop\\n'\n : 'cases:\\n - name: Inspect the home screen\\n steps:\\n - home: {}\\n - aiAsk: Describe the current screen\\n',\n '.env.example': `${modelEnv}\\n${env}`,\n '.gitignore': 'node_modules/\\n.env\\nmidscene_run/\\n',\n 'README.md': `# ${name}\\n\\nA Midscene Test project for ${platform}.\\n\\nIf you skipped installation during creation, run \\`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\\`. The \\`postinstall\\` script automatically generates \\`midscene-node-reference.md\\` after each dependency installation. If lifecycle scripts are disabled, run \\`${packageManager} run nodes\\` manually.\\n\\nCopy \\`.env.example\\` to \\`.env\\`, then choose and configure a model using the [supported models and setup guide](https://midscenejs.com/model-common-config.html). Do not commit \\`.env\\` to your repository; inject the model configuration as environment variables in CI.\\n\\n${instructions}\\n\\nRun tests with \\`${packageManager} test\\`. Read \\`midscene-node-reference.md\\` for the available Nodes and their inputs.\\n\\nAfter changing Node registrations in \\`midscene.config.ts\\`, run \\`${packageManager} run nodes\\` to refresh the reference. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\\n\\nReports are written to \\`midscene_run/report/\\`.\\n`,\n };\n}\n","import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, resolve } from 'node:path';\nimport { confirm, input, select } from '@inquirer/prompts';\nimport { execa } from 'execa';\nimport yargs from 'yargs/yargs';\nimport {\n type CreatePackageManager,\n createPackageManagers,\n detectPackageManager,\n packageManagerCommands,\n} from './create-package-manager';\nimport {\n type CreatePlatform,\n createPlatformLabels,\n createPlatforms,\n createProjectFiles,\n} from './create-template';\nimport type { TestCliIO } from './test-command';\n\nconst createParser = () =>\n yargs()\n .scriptName('midscene-test create')\n .usage('Usage: $0 [directory] [options]')\n .parserConfiguration({\n 'parse-positional-numbers': false,\n 'greedy-arrays': false,\n 'camel-case-expansion': false,\n 'boolean-negation': false,\n })\n .option('platform', {\n type: 'string',\n choices: createPlatforms,\n requiresArg: true,\n description: 'Platform preset (prompt if omitted)',\n })\n .option('package-manager', {\n type: 'string',\n choices: createPackageManagers,\n requiresArg: true,\n description:\n 'Package manager (prompt if omitted; detect without prompts, falling back to npm)',\n })\n .option('yes', {\n type: 'boolean',\n alias: 'y',\n default: false,\n description:\n 'Disable prompts and install dependencies; directory and platform must be supplied',\n })\n .option('skip-install', {\n type: 'boolean',\n default: false,\n description:\n 'Create files without installing dependencies or generating midscene-node-reference.md',\n })\n .demandCommand(0, 1, '', 'Only one project directory is allowed.')\n .example('$0', 'Choose a directory and platform interactively')\n .example('$0 my-tests --platform web', 'Create a Web project')\n .epilogue(\n 'Optionally installs dependencies with the selected package manager. The generated postinstall script creates midscene-node-reference.md after installation.\\nExisting files are never overwritten. Setup and tests are not run during creation.',\n )\n .help('help')\n .alias('help', 'h')\n .version(false)\n .strict()\n .exitProcess(false)\n .showHelpOnFail(false)\n .fail((message, error) => {\n throw error ?? new Error(message);\n })\n .wrap(100);\n\nexport interface CreateOptions {\n directory?: string;\n platform?: CreatePlatform;\n packageManager?: CreatePackageManager;\n yes: boolean;\n skipInstall: boolean;\n help: boolean;\n}\n\nexport function parseCreateArgs(\n args: string[],\n onOutput?: (message: string) => void,\n): CreateOptions {\n const values = createParser().parseSync(args, {}, (error, _argv, output) => {\n if (error) throw error;\n if (output) onOutput?.(output);\n });\n const directory = values._[0] === undefined ? undefined : String(values._[0]);\n if (directory !== undefined && !directory.trim()) {\n throw new Error('Project directory must not be empty.');\n }\n return {\n directory,\n platform: values.platform,\n packageManager: values['package-manager'],\n yes: values.yes ?? false,\n skipInstall: values['skip-install'] ?? false,\n help: values.help === true,\n };\n}\n\nexport interface CreateServices {\n cwd: string;\n interactive: boolean;\n userAgent: string | undefined;\n promptDirectory(): Promise<string>;\n selectPlatform(): Promise<CreatePlatform>;\n selectPackageManager(\n defaultValue: CreatePackageManager,\n ): Promise<CreatePackageManager>;\n confirmInstall(): Promise<boolean>;\n runPackageManager(\n packageManager: CreatePackageManager,\n args: string[],\n cwd: string,\n captureOutput?: boolean,\n ): Promise<string>;\n}\n\nconst promptDirectory = () =>\n input({\n message: 'Project directory:',\n default: 'my-tests',\n validate: (value) =>\n value.trim().length > 0 || 'Project directory must not be empty.',\n });\n\nconst selectPlatform = () =>\n select<CreatePlatform>({\n message: 'Select a platform:',\n choices: createPlatforms.map((platform) => ({\n name: createPlatformLabels[platform],\n value: platform,\n })),\n });\n\nconst selectPackageManager: CreateServices['selectPackageManager'] = (\n defaultValue,\n) =>\n select<CreatePackageManager>({\n message: 'Select a package manager:',\n choices: createPackageManagers.map((value) => ({ name: value, value })),\n default: defaultValue,\n });\n\nconst runPackageManager: CreateServices['runPackageManager'] = async (\n packageManager,\n args,\n cwd,\n captureOutput = false,\n) => {\n const result = await execa(packageManager, args, {\n cwd,\n stdin: 'inherit',\n stdout: captureOutput ? 'pipe' : 'inherit',\n stderr: 'inherit',\n stripFinalNewline: false,\n });\n return result.stdout ?? '';\n};\n\nconst statIfPresent = (path: string) => {\n try {\n return lstatSync(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\n/** Check every destination before writing anything, including dangling links. */\nfunction checkDestinations(root: string, filenames: string[]): void {\n const conflicts: string[] = [];\n for (const filename of filenames) {\n const path = resolve(root, filename);\n if (statIfPresent(path)) conflicts.push(path);\n let parent = dirname(path);\n while (true) {\n const stat = statIfPresent(parent);\n if (stat && !stat.isDirectory()) conflicts.push(parent);\n if (parent === root || parent === dirname(parent)) break;\n parent = dirname(parent);\n }\n }\n if (conflicts.length) {\n throw new Error(\n `Cannot create project; these paths already exist or are not directories:\\n${[...new Set(conflicts)].join('\\n')}\\nChoose another directory or move the conflicting files.`,\n );\n }\n}\n\nexport async function runCreateCommand(\n args: string[],\n io: TestCliIO,\n overrides: Partial<CreateServices> = {},\n): Promise<void> {\n const options = parseCreateArgs(args, (message) => io.log(message));\n if (options.help) return;\n const services: CreateServices = {\n cwd: process.cwd(),\n interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n userAgent: process.env.npm_config_user_agent,\n promptDirectory,\n selectPlatform,\n selectPackageManager,\n confirmInstall: () =>\n confirm({\n message:\n 'Install dependencies and generate midscene-node-reference.md now?',\n default: true,\n }),\n runPackageManager,\n ...overrides,\n };\n if (\n (!options.directory || !options.platform) &&\n (options.yes || !services.interactive)\n ) {\n throw new Error(\n 'Project directory and --platform are required without prompts. Example: midscene-test create my-tests --platform web',\n );\n }\n let directory: string;\n let platform: CreatePlatform;\n let packageManager: CreatePackageManager;\n try {\n directory = options.directory ?? (await services.promptDirectory()).trim();\n platform = options.platform ?? (await services.selectPlatform());\n const defaultPackageManager = detectPackageManager(services.userAgent);\n packageManager =\n options.packageManager ??\n (services.interactive && !options.yes\n ? await services.selectPackageManager(defaultPackageManager)\n : defaultPackageManager);\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error('Project creation cancelled.');\n }\n throw error;\n }\n const root = resolve(services.cwd, directory);\n const name =\n basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9._-]/g, '-')\n .replace(/^[._-]+/, '') || 'midscene-tests';\n const commands = packageManagerCommands[packageManager];\n const files = createProjectFiles(name, platform, packageManager);\n checkDestinations(root, [\n ...Object.keys(files),\n 'midscene-node-reference.md',\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n ]);\n for (const [filename, content] of Object.entries(files)) {\n const path = resolve(root, filename);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, { flag: 'wx' });\n }\n io.log(`Created ${platform} project files in ${root}`);\n let install = !options.skipInstall;\n if (install && services.interactive && !options.yes) {\n try {\n install = await services.confirmInstall();\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error(\n `Project creation cancelled. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-reference.md.`,\n );\n }\n throw error;\n }\n }\n if (!install) {\n io.log(\n `Project files ready: ${root}\\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-reference.md.\\nThen follow README.md to configure your model and run tests.`,\n );\n return;\n }\n io.log(`Installing dependencies with ${packageManager}...`);\n try {\n await services.runPackageManager(packageManager, commands.install, root);\n } catch (error) {\n throw new Error(\n `Dependency installation or postinstall Node reference generation failed. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n try {\n const referencePath = resolve(root, 'midscene-node-reference.md');\n const generatedByPostinstall = Boolean(statIfPresent(referencePath));\n // Fall back to explicit generation if lifecycle scripts were disabled.\n if (!generatedByPostinstall) {\n io.log('Generating Node reference...');\n await services.runPackageManager(packageManager, commands.describe, root);\n }\n const markdown = readFileSync(referencePath, 'utf8');\n if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) {\n throw new Error('nodes did not generate a valid Node reference.');\n }\n } catch (error) {\n throw new Error(\n `Node reference generation failed. Project files are preserved in ${root}.\\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n io.log(\n `Project ready: ${root}\\nNode reference: ${resolve(root, 'midscene-node-reference.md')}\\nNext: copy .env.example to .env and configure your model.${platform === 'web' ? `\\nInstall Chromium: ${commands.installChromium}` : platform === 'computer' ? '\\nPrepare desktop dependencies and permissions as described in README.md.' : '\\nConfigure your device connection in .env.'}\\nRun tests from the project directory: ${packageManager} test`,\n );\n}\n"],"names":["createPackageManagers","packageManagerCommands","detectPackageManager","userAgent","createPlatforms","createPlatformLabels","platformImports","agentClasses","platformSetup","platformEnv","modelEnv","platformInstructions","createProjectFiles","name","platform","packageManager","instructions","agentClass","config","dependencies","version","env","JSON","createParser","yargs","message","error","Error","parseCreateArgs","args","onOutput","values","_argv","output","directory","undefined","String","promptDirectory","input","value","selectPlatform","select","selectPackageManager","defaultValue","runPackageManager","cwd","captureOutput","result","execa","statIfPresent","path","lstatSync","checkDestinations","root","filenames","conflicts","filename","resolve","parent","dirname","stat","Set","runCreateCommand","io","overrides","options","services","process","Boolean","confirm","defaultPackageManager","basename","commands","files","Object","content","mkdirSync","writeFileSync","install","referencePath","generatedByPostinstall","markdown","readFileSync"],"mappings":";;;;;;;;;;;;;;;QAAO,MAAMA,wBAAwB;YAAC;YAAO;SAAO;QAG7C,MAAMC,yBAGT;YACF,KAAK;gBACH,SAAS;oBAAC;oBAAW;iBAAqB;gBAC1C,UAAU;oBACR;oBACA;oBACA;oBACA;oBACA;oBACA;iBACD;gBACD,iBAAiB;YACnB;YACA,MAAM;gBACJ,SAAS;oBAAC;oBAAW;iBAAqB;gBAC1C,UAAU;oBAAC;oBAAQ;oBAAiB;iBAAQ;gBAC5C,iBAAiB;YACnB;QACF;QAEO,SAASC,qBAAqBC,SAAkB;YACrD,OAAOA,WAAW,WAAW,WAAW,SAAS;QACnD;;QCtBO,MAAMC,kBAAkB;YAC7B;YACA;YACA;YACA;YACA;SACD;QAGM,MAAMC,uBAAuD;YAClE,KAAK;YACL,SAAS;YACT,KAAK;YACL,SAAS;YACT,UAAU;QACZ;QAEA,MAAMC,kBAAkD;YACtD,KAAK,CAAC;iDACyC,CAAC;YAChD,SAAS;YACT,KAAK;YACL,SAAS;YACT,UAAU;QACZ;QAEA,MAAMC,eAA+C;YACnD,KAAK;YACL,SAAS;YACT,KAAK;YACL,SAAS;YACT,UAAU;QACZ;QAEA,MAAMC,gBAAgD;YACpD,KAAK,CAAC;;;;;mBAKW,CAAC;YAClB,SAAS,CAAC;;qBAES,CAAC;YACpB,KAAK,CAAC;;;;;;;;;qBASa,CAAC;YACpB,SAAS,CAAC;;qBAES,CAAC;YACpB,UAAU,CAAC;;;;qBAIQ,CAAC;QACtB;QAEA,MAAMC,cAA8C;YAClD,KAAK;YACL,SACE;YACF,KAAK;YACL,SACE;YACF,UACE;QACJ;QAEA,MAAMC,WAAW,CAAC;;;;;;;;;;AAUlB,CAAC;QAED,MAAMC,uBAAuE;YAC3E,SACE;YACF,KAAK;YACL,SACE;YACF,UACE;QACJ;QAEO,SAASC,mBACdC,IAAY,EACZC,QAAwB,EACxBC,cAAoC;YAEpC,MAAMC,eACJF,AAAa,UAAbA,WACI,CAAC,0CAA0C,EAAEb,sBAAsB,CAACc,eAAe,CAAC,eAAe,CAAC,GAAG,CAAC,GACxGJ,oBAAoB,CAACG,SAAS;YACpC,MAAMG,aAAaV,YAAY,CAACO,SAAS;YACzC,MAAMI,SAAS,CAAC;;;;;AAKlB,EAAEZ,eAAe,CAACQ,SAAS,CAAC;;;;;AAK5B,EAAEA,AAAa,UAAbA,WAAqB,8CAA8C,CAAC,SAAS,EAAEG,WAAW,CAAC,CAAC,CAAC;;;;AAI/F,EAAEH,AAAa,UAAbA,WAAqB,2DAA2D,GAAG;;;;;SAK5E,EAAEA,SAAS;;AAEpB,EAAEN,aAAa,CAACM,SAAS,CAAC;;;;;;WAMf,EAAEA,SAAS;;;;;yDAKmC,EAAEG,WAAW;;;AAGtE,CAAC;YACC,MAAME,eAAuC;gBAC3C,kBAAkBC,UAAAA,EAAOA;gBACzB,CAAC,CAAC,UAAU,EAAEN,UAAU,CAAC,EAAEM,UAAAA,EAAOA;gBAClC,eAAe;gBACf,QAAQ;gBACR,YAAY;gBACZ,GAAIN,AAAa,UAAbA,WAAqB;oBAAE,YAAY;gBAAU,IAAI,CAAC,CAAC;YACzD;YACA,MAAMO,MAAMZ,WAAW,CAACK,SAAS;YACjC,OAAO;gBACL,gBAAgB,GAAGQ,KAAK,SAAS,CAC/B;oBACET;oBACA,SAAS;oBACT,MAAM;oBACN,SAAS;wBACP,MAAM;wBACN,OAAO;wBACP,aAAa;oBACf;oBACA,SAAS;wBAAE,MAAM;oBAAmC;oBACpD,iBAAiBM;gBACnB,GACA,MACA,GACA,EAAE,CAAC;gBACL,sBAAsBD;gBACtB,iBAAiB,GAAGI,KAAK,SAAS,CAChC;oBACE,iBAAiB;wBACf,QAAQ;wBACR,QAAQ;wBACR,kBAAkB;wBAClB,QAAQ;wBACR,QAAQ;wBACR,cAAc;wBACd,OAAO;4BAAC;yBAAO;oBACjB;oBACA,SAAS;wBAAC;qBAAqB;gBACjC,GACA,MACA,GACA,EAAE,CAAC;gBACL,sBACER,AAAa,UAAbA,WACI,2JACAA,AAAa,eAAbA,WACE,qGACA;gBACR,gBAAgB,GAAGJ,SAAS,EAAE,EAAEW,KAAK;gBACrC,cAAc;gBACd,aAAa,CAAC,EAAE,EAAER,KAAK,gCAAgC,EAAEC,SAAS,wDAAwD,EAAEC,eAAe,CAAC,EAAEd,sBAAsB,CAACc,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,mKAAmK,EAAEA,eAAe,2SAA2S,EAAEC,aAAa,qBAAqB,EAAED,eAAe,6JAA6J,EAAEA,eAAe,yPAAyP,CAAC;YACroC;QACF;QCvLA,MAAMQ,eAAe,IACnBC,kBACG,UAAU,CAAC,wBACX,KAAK,CAAC,mCACN,mBAAmB,CAAC;gBACnB,4BAA4B;gBAC5B,iBAAiB;gBACjB,wBAAwB;gBACxB,oBAAoB;YACtB,GACC,MAAM,CAAC,YAAY;gBAClB,MAAM;gBACN,SAASpB;gBACT,aAAa;gBACb,aAAa;YACf,GACC,MAAM,CAAC,mBAAmB;gBACzB,MAAM;gBACN,SAASJ;gBACT,aAAa;gBACb,aACE;YACJ,GACC,MAAM,CAAC,OAAO;gBACb,MAAM;gBACN,OAAO;gBACP,SAAS;gBACT,aACE;YACJ,GACC,MAAM,CAAC,gBAAgB;gBACtB,MAAM;gBACN,SAAS;gBACT,aACE;YACJ,GACC,aAAa,CAAC,GAAG,GAAG,IAAI,0CACxB,OAAO,CAAC,MAAM,iDACd,OAAO,CAAC,8BAA8B,wBACtC,QAAQ,CACP,mPAED,IAAI,CAAC,QACL,KAAK,CAAC,QAAQ,KACd,OAAO,CAAC,OACR,MAAM,GACN,WAAW,CAAC,OACZ,cAAc,CAAC,OACf,IAAI,CAAC,CAACyB,SAASC;gBACd,MAAMA,SAAS,IAAIC,MAAMF;YAC3B,GACC,IAAI,CAAC;QAWH,SAASG,gBACdC,IAAc,EACdC,QAAoC;YAEpC,MAAMC,SAASR,eAAe,SAAS,CAACM,MAAM,CAAC,GAAG,CAACH,OAAOM,OAAOC;gBAC/D,IAAIP,OAAO,MAAMA;gBACjB,IAAIO,QAAQH,WAAWG;YACzB;YACA,MAAMC,YAAYH,AAAgBI,WAAhBJ,OAAO,CAAC,CAAC,EAAE,GAAiBI,SAAYC,OAAOL,OAAO,CAAC,CAAC,EAAE;YAC5E,IAAIG,AAAcC,WAAdD,aAA2B,CAACA,UAAU,IAAI,IAC5C,MAAM,IAAIP,MAAM;YAElB,OAAO;gBACLO;gBACA,UAAUH,OAAO,QAAQ;gBACzB,gBAAgBA,MAAM,CAAC,kBAAkB;gBACzC,KAAKA,OAAO,GAAG,IAAI;gBACnB,aAAaA,MAAM,CAAC,eAAe,IAAI;gBACvC,MAAMA,AAAgB,SAAhBA,OAAO,IAAI;YACnB;QACF;QAoBA,MAAMM,kBAAkB,IACtBC,AAAAA,IAAAA,SAAAA,KAAAA,AAAAA,EAAM;gBACJ,SAAS;gBACT,SAAS;gBACT,UAAU,CAACC,QACTA,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK;YAC/B;QAEF,MAAMC,iBAAiB,IACrBC,AAAAA,IAAAA,SAAAA,MAAAA,AAAAA,EAAuB;gBACrB,SAAS;gBACT,SAASrC,gBAAgB,GAAG,CAAC,CAACU,WAAc;wBAC1C,MAAMT,oBAAoB,CAACS,SAAS;wBACpC,OAAOA;oBACT;YACF;QAEF,MAAM4B,uBAA+D,CACnEC,eAEAF,AAAAA,IAAAA,SAAAA,MAAAA,AAAAA,EAA6B;gBAC3B,SAAS;gBACT,SAASzC,sBAAsB,GAAG,CAAC,CAACuC,QAAW;wBAAE,MAAMA;wBAAOA;oBAAM;gBACpE,SAASI;YACX;QAEF,MAAMC,oBAAyD,OAC7D7B,gBACAc,MACAgB,KACAC,gBAAgB,KAAK;YAErB,MAAMC,SAAS,MAAMC,AAAAA,IAAAA,gBAAAA,KAAAA,AAAAA,EAAMjC,gBAAgBc,MAAM;gBAC/CgB;gBACA,OAAO;gBACP,QAAQC,gBAAgB,SAAS;gBACjC,QAAQ;gBACR,mBAAmB;YACrB;YACA,OAAOC,OAAO,MAAM,IAAI;QAC1B;QAEA,MAAME,gBAAgB,CAACC;YACrB,IAAI;gBACF,OAAOC,AAAAA,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUD;YACnB,EAAE,OAAOxB,OAAO;gBACd,IAAKA,AAAyC,aAAzCA,MAAgC,IAAI,EAAe;gBACxD,MAAMA;YACR;QACF;QAGA,SAAS0B,kBAAkBC,IAAY,EAAEC,SAAmB;YAC1D,MAAMC,YAAsB,EAAE;YAC9B,KAAK,MAAMC,YAAYF,UAAW;gBAChC,MAAMJ,OAAOO,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQJ,MAAMG;gBAC3B,IAAIP,cAAcC,OAAOK,UAAU,IAAI,CAACL;gBACxC,IAAIQ,SAASC,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQT;gBACrB,MAAO,KAAM;oBACX,MAAMU,OAAOX,cAAcS;oBAC3B,IAAIE,QAAQ,CAACA,KAAK,WAAW,IAAIL,UAAU,IAAI,CAACG;oBAChD,IAAIA,WAAWL,QAAQK,WAAWC,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQD,SAAS;oBACnDA,SAASC,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQD;gBACnB;YACF;YACA,IAAIH,UAAU,MAAM,EAClB,MAAM,IAAI5B,MACR,CAAC,0EAA0E,EAAE;mBAAI,IAAIkC,IAAIN;aAAW,CAAC,IAAI,CAAC,MAAM,yDAAyD,CAAC;QAGhL;QAEO,eAAeO,iBACpBjC,IAAc,EACdkC,EAAa,EACbC,YAAqC,CAAC,CAAC;YAEvC,MAAMC,UAAUrC,gBAAgBC,MAAM,CAACJ,UAAYsC,GAAG,GAAG,CAACtC;YAC1D,IAAIwC,QAAQ,IAAI,EAAE;YAClB,MAAMC,WAA2B;gBAC/B,KAAKC,QAAQ,GAAG;gBAChB,aAAaC,QAAQD,QAAQ,KAAK,CAAC,KAAK,IAAIA,QAAQ,MAAM,CAAC,KAAK;gBAChE,WAAWA,QAAQ,GAAG,CAAC,qBAAqB;gBAC5C9B;gBACAG;gBACAE;gBACA,gBAAgB,IACd2B,AAAAA,IAAAA,SAAAA,OAAAA,AAAAA,EAAQ;wBACN,SACE;wBACF,SAAS;oBACX;gBACFzB;gBACA,GAAGoB,SAAS;YACd;YACA,IACG,EAACC,QAAQ,SAAS,IAAI,CAACA,QAAQ,QAAO,KACtCA,CAAAA,QAAQ,GAAG,IAAI,CAACC,SAAS,WAAU,GAEpC,MAAM,IAAIvC,MACR;YAGJ,IAAIO;YACJ,IAAIpB;YACJ,IAAIC;YACJ,IAAI;gBACFmB,YAAY+B,QAAQ,SAAS,IAAK,OAAMC,SAAS,eAAe,EAAC,EAAG,IAAI;gBACxEpD,WAAWmD,QAAQ,QAAQ,IAAK,MAAMC,SAAS,cAAc;gBAC7D,MAAMI,wBAAwBpE,qBAAqBgE,SAAS,SAAS;gBACrEnD,iBACEkD,QAAQ,cAAc,IACrBC,CAAAA,SAAS,WAAW,IAAI,CAACD,QAAQ,GAAG,GACjC,MAAMC,SAAS,oBAAoB,CAACI,yBACpCA,qBAAoB;YAC5B,EAAE,OAAO5C,OAAO;gBACd,IACEA,iBAAiBC,SACjB;oBAAC;oBAAmB;iBAAmB,CAAC,QAAQ,CAACD,MAAM,IAAI,GAE3D,MAAM,IAAIC,MAAM;gBAElB,MAAMD;YACR;YACA,MAAM2B,OAAOI,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQS,SAAS,GAAG,EAAEhC;YACnC,MAAMrB,OACJ0D,AAAAA,IAAAA,oBAAAA,QAAAA,AAAAA,EAASlB,MACN,WAAW,GACX,OAAO,CAAC,iBAAiB,KACzB,OAAO,CAAC,WAAW,OAAO;YAC/B,MAAMmB,WAAWvE,sBAAsB,CAACc,eAAe;YACvD,MAAM0D,QAAQ7D,mBAAmBC,MAAMC,UAAUC;YACjDqC,kBAAkBC,MAAM;mBACnBqB,OAAO,IAAI,CAACD;gBACf;gBACA;gBACA;gBACA;aACD;YACD,KAAK,MAAM,CAACjB,UAAUmB,QAAQ,IAAID,OAAO,OAAO,CAACD,OAAQ;gBACvD,MAAMvB,OAAOO,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQJ,MAAMG;gBAC3BoB,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUjB,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQT,OAAO;oBAAE,WAAW;gBAAK;gBAC3C2B,IAAAA,kBAAAA,aAAAA,AAAAA,EAAc3B,MAAMyB,SAAS;oBAAE,MAAM;gBAAK;YAC5C;YACAZ,GAAG,GAAG,CAAC,CAAC,QAAQ,EAAEjD,SAAS,kBAAkB,EAAEuC,MAAM;YACrD,IAAIyB,UAAU,CAACb,QAAQ,WAAW;YAClC,IAAIa,WAAWZ,SAAS,WAAW,IAAI,CAACD,QAAQ,GAAG,EACjD,IAAI;gBACFa,UAAU,MAAMZ,SAAS,cAAc;YACzC,EAAE,OAAOxC,OAAO;gBACd,IACEA,iBAAiBC,SACjB;oBAAC;oBAAmB;iBAAmB,CAAC,QAAQ,CAACD,MAAM,IAAI,GAE3D,MAAM,IAAIC,MACR,CAAC,2DAA2D,EAAE0B,KAAK,0BAA0B,EAAEtC,eAAe,CAAC,EAAEyD,SAAS,OAAO,CAAC,IAAI,CAAC,KAAK,iEAAiE,CAAC;gBAGlN,MAAM9C;YACR;YAEF,IAAI,CAACoD,SAAS,YACZf,GAAG,GAAG,CACJ,CAAC,qBAAqB,EAAEV,KAAK,YAAY,EAAEtC,eAAe,CAAC,EAAEyD,SAAS,OAAO,CAAC,IAAI,CAAC,KAAK,yJAAyJ,CAAC;YAItPT,GAAG,GAAG,CAAC,CAAC,6BAA6B,EAAEhD,eAAe,GAAG,CAAC;YAC1D,IAAI;gBACF,MAAMmD,SAAS,iBAAiB,CAACnD,gBAAgByD,SAAS,OAAO,EAAEnB;YACrE,EAAE,OAAO3B,OAAO;gBACd,MAAM,IAAIC,MACR,CAAC,wGAAwG,EAAE0B,KAAK,0BAA0B,EAAEtC,eAAe,CAAC,EAAEyD,SAAS,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,EAAEzD,eAAe,wIAAwI,EAAEA,eAAe,aAAa,EAAEW,iBAAiBC,QAAQD,MAAM,OAAO,GAAGU,OAAOV,QAAQ;YAErb;YACA,IAAI;gBACF,MAAMqD,gBAAgBtB,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQJ,MAAM;gBACpC,MAAM2B,yBAAyBZ,QAAQnB,cAAc8B;gBAErD,IAAI,CAACC,wBAAwB;oBAC3BjB,GAAG,GAAG,CAAC;oBACP,MAAMG,SAAS,iBAAiB,CAACnD,gBAAgByD,SAAS,QAAQ,EAAEnB;gBACtE;gBACA,MAAM4B,WAAWC,AAAAA,IAAAA,kBAAAA,YAAAA,AAAAA,EAAaH,eAAe;gBAC7C,IAAI,CAACE,SAAS,UAAU,CAAC,6CACvB,MAAM,IAAItD,MAAM;YAEpB,EAAE,OAAOD,OAAO;gBACd,MAAM,IAAIC,MACR,CAAC,iEAAiE,EAAE0B,KAAK,uLAAuL,EAAEtC,eAAe,sCAAsC,EAAEW,iBAAiBC,QAAQD,MAAM,OAAO,GAAGU,OAAOV,QAAQ;YAErX;YACAqC,GAAG,GAAG,CACJ,CAAC,eAAe,EAAEV,KAAK,kBAAkB,EAAEI,AAAAA,IAAAA,oBAAAA,OAAAA,AAAAA,EAAQJ,MAAM,8BAA8B,2DAA2D,EAAEvC,AAAa,UAAbA,WAAqB,CAAC,oBAAoB,EAAE0D,SAAS,eAAe,EAAE,GAAG1D,AAAa,eAAbA,WAA0B,8EAA8E,8CAA8C,wCAAwC,EAAEC,eAAe,KAAK,CAAC;QAEtb"}
1
+ {"version":3,"file":"0~160.js","sources":["../../src/cli/create-package-manager.ts","../../src/cli/create-template.ts","../../src/cli/create-command.ts"],"sourcesContent":["export const createPackageManagers = ['npm', 'pnpm'] as const;\nexport type CreatePackageManager = (typeof createPackageManagers)[number];\n\nexport const packageManagerCommands: Record<\n CreatePackageManager,\n { install: string[]; describe: string[]; installChromium: string }\n> = {\n npm: {\n install: ['install', '--workspaces=false'],\n describe: [\n 'exec',\n '--no',\n '--workspaces=false',\n '--',\n 'midscene-test',\n 'nodes',\n ],\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n installChromium: 'pnpm exec playwright install chromium',\n },\n};\n\nexport function detectPackageManager(userAgent?: string): CreatePackageManager {\n return userAgent?.startsWith('pnpm/') ? 'pnpm' : 'npm';\n}\n","import { version } from '../../package.json';\nimport {\n type CreatePackageManager,\n packageManagerCommands,\n} from './create-package-manager';\n\nexport const createPlatforms = [\n 'web',\n 'android',\n 'ios',\n 'harmony',\n 'computer',\n] as const;\nexport type CreatePlatform = (typeof createPlatforms)[number];\n\nexport const createPlatformLabels: Record<CreatePlatform, string> = {\n web: 'Web (Playwright)',\n android: 'Android',\n ios: 'iOS',\n harmony: 'HarmonyOS',\n computer: 'Desktop (Computer)',\n};\n\nconst platformImports: Record<CreatePlatform, string> = {\n web: `import { PlaywrightAgent } from '@midscene/web/playwright/agent';\nimport { chromium, type Page } from 'playwright';`,\n android: `import { AndroidAgent, agentFromAdbDevice } from '@midscene/android';`,\n ios: `import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';`,\n harmony: `import { HarmonyAgent, agentFromHdcDevice } from '@midscene/harmony';`,\n computer: `import { ComputerAgent, agentForComputer } from '@midscene/computer';`,\n};\n\nconst agentClasses: Record<CreatePlatform, string> = {\n web: 'PlaywrightAgent',\n android: 'AndroidAgent',\n ios: 'IOSAgent',\n harmony: 'HarmonyAgent',\n computer: 'ComputerAgent',\n};\n\nconst platformSetup: Record<CreatePlatform, string> = {\n web: ` const browser = await chromium.launch({ headless: env.HEADLESS !== 'false' });\n onTeardown(() => browser.close());\n const page = await browser.newPage();\n const context: ProjectContext = { page };\n onTeardown(async () => { await context.agent?.destroy(); });\n return context;`,\n android: ` const agent = await agentFromAdbDevice(env.ANDROID_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n ios: ` const port = Number(env.WDA_PORT || 8100);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error('WDA_PORT must be an integer between 1 and 65535.');\n }\n const agent = await agentFromWebDriverAgent({\n wdaHost: env.WDA_HOST || 'localhost',\n wdaPort: port,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n harmony: ` const agent = await agentFromHdcDevice(env.HARMONY_DEVICE_ID || undefined);\n onTeardown(() => agent.destroy());\n return { agent };`,\n computer: ` const agent = await agentForComputer({\n displayId: env.COMPUTER_DISPLAY_ID || undefined,\n });\n onTeardown(() => agent.destroy());\n return { agent };`,\n};\n\nconst platformEnv: Record<CreatePlatform, string> = {\n web: 'HEADLESS=true\\n',\n android:\n '# Optional: choose a device from adb devices.\\nANDROID_DEVICE_ID=\\n',\n ios: 'WDA_HOST=localhost\\nWDA_PORT=8100\\n',\n harmony:\n '# Optional: choose a device from hdc list targets.\\nHARMONY_DEVICE_ID=\\n',\n computer:\n '# Optional: select a display; leave empty for the default display.\\nCOMPUTER_DISPLAY_ID=\\n# Enable Xvfb on headless Linux after installing its dependencies.\\nMIDSCENE_COMPUTER_HEADLESS_LINUX=false\\n',\n};\n\nconst modelEnv = `# Copy this file to .env before running tests.\n# Do not commit .env to your repository. In CI, inject these values as environment variables instead.\n# Choose a supported model and copy its four configuration values from:\n# https://midscenejs.com/model-common-config.html\n# For the complete environment variable reference, see:\n# https://midscenejs.com/model-config.html\nMIDSCENE_MODEL_BASE_URL=\nMIDSCENE_MODEL_API_KEY=\nMIDSCENE_MODEL_NAME=\nMIDSCENE_MODEL_FAMILY=\n`;\n\nconst platformInstructions: Record<Exclude<CreatePlatform, 'web'>, string> = {\n android:\n 'Connect an Android device and verify it with `adb devices`. Set ANDROID_DEVICE_ID to select a device.',\n ios: 'Start WebDriverAgent and set WDA_HOST and WDA_PORT for your iOS device.',\n harmony:\n 'Connect a HarmonyOS device and verify it with `hdc list targets`. Set HARMONY_DEVICE_ID to select a device. Set HDC_HOME if hdc is not on PATH.',\n computer:\n 'Prepare the local desktop using the [desktop setup guide](https://midscenejs.com/platforms/desktop.html), including system dependencies and permissions. Set COMPUTER_DISPLAY_ID to select a display. For headless Linux, install Xvfb and enable MIDSCENE_COMPUTER_HEADLESS_LINUX.',\n};\n\nexport function createProjectFiles(\n name: string,\n platform: CreatePlatform,\n packageManager: CreatePackageManager,\n): Record<string, string> {\n const instructions =\n platform === 'web'\n ? `Install Chromium before the first test: \\`${packageManagerCommands[packageManager].installChromium}\\`.`\n : platformInstructions[platform];\n const agentClass = agentClasses[platform];\n const config = `import { fileURLToPath } from 'node:url';\nimport { config as loadEnv } from 'dotenv';\nimport type { NodeExecutionContext } from '@midscene/test';\nimport { defineProjectSetup, defineTestProject } from '@midscene/test/config';\nimport { createMidsceneNodes } from '@midscene/test/midscene';\n${platformImports[platform]}\n\nloadEnv({ path: fileURLToPath(new URL('.env', import.meta.url)) });\n\ninterface ProjectContext {\n${platform === 'web' ? ' page: Page;\\n agent?: PlaywrightAgent;' : ` agent: ${agentClass};`}\n}\n\nconst getAgent = ({ context }: NodeExecutionContext<unknown, ProjectContext>) => {\n${platform === 'web' ? ' context.agent ??= new PlaywrightAgent(context.page);' : ''}\n return context.agent;\n};\n\nconst setup = defineProjectSetup<ProjectContext>({\n name: '${platform}',\n async setup({ env, onTeardown }) {\n${platformSetup[platform]}\n },\n});\n\nexport default defineTestProject<ProjectContext>({\n projects: [{\n name: '${platform}',\n setup,\n files: { include: ['cases/**/*.{yaml,yml}'] },\n }],\n nodes: [\n ...createMidsceneNodes<ProjectContext>({ agentClass: ${agentClass}, getAgent }),\n ],\n});\n`;\n const dependencies: Record<string, string> = {\n '@midscene/test': version,\n [`@midscene/${platform}`]: version,\n '@types/node': '^20.0.0',\n dotenv: '^16.4.5',\n typescript: '^5.8.3',\n ...(platform === 'web' ? { playwright: '^1.45.0' } : {}),\n };\n const env = platformEnv[platform];\n return {\n 'package.json': `${JSON.stringify(\n {\n name,\n private: true,\n type: 'module',\n scripts: {\n test: 'midscene-test',\n nodes: 'midscene-test nodes',\n postinstall: 'midscene-test nodes',\n },\n engines: { node: '^20.19.0 || ^22.12.0 || >=24.0.0' },\n devDependencies: dependencies,\n },\n null,\n 2,\n )}\\n`,\n 'midscene.config.ts': config,\n 'tsconfig.json': `${JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n strict: true,\n noEmit: true,\n skipLibCheck: true,\n types: ['node'],\n },\n include: ['midscene.config.ts'],\n },\n null,\n 2,\n )}\\n`,\n 'cases/example.yaml':\n platform === 'web'\n ? 'cases:\\n - name: Open example.com\\n steps:\\n - gotoUrl: https://example.com\\n - aiAssert: The page displays the heading Example Domain\\n'\n : platform === 'computer'\n ? 'cases:\\n - name: Inspect the desktop\\n steps:\\n - aiAsk: Describe the current desktop\\n'\n : 'cases:\\n - name: Inspect the home screen\\n steps:\\n - home: {}\\n - aiAsk: Describe the current screen\\n',\n '.env.example': `${modelEnv}\\n${env}`,\n '.gitignore': 'node_modules/\\n.env\\nmidscene_run/\\n',\n 'README.md': `# ${name}\\n\\nA Midscene Test project for ${platform}.\\n\\nIf you skipped installation during creation, run \\`${packageManager} ${packageManagerCommands[packageManager].install.join(' ')}\\`. The \\`postinstall\\` script automatically generates \\`midscene-node-spec.${platform}.md\\` after each dependency installation. If lifecycle scripts are disabled, run \\`${packageManager} run nodes\\` manually.\\n\\nCopy \\`.env.example\\` to \\`.env\\`, then choose and configure a model using the [supported models and setup guide](https://midscenejs.com/model-common-config.html). Do not commit \\`.env\\` to your repository; inject the model configuration as environment variables in CI.\\n\\n${instructions}\\n\\nRun tests with \\`${packageManager} test\\`. Read \\`midscene-node-spec.${platform}.md\\` for the available Nodes and their inputs.\\n\\nAfter changing Node registrations in \\`midscene.config.ts\\`, run \\`${packageManager} run nodes\\` to refresh the spec. This loads the configuration without connecting to a device or running tests. Extension factories must only acquire runtime resources inside Node execution.\\n\\nReports are written to \\`midscene_run/report/\\`.\\n`,\n };\n}\n","import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, dirname, resolve } from 'node:path';\nimport { confirm, input, select } from '@inquirer/prompts';\nimport { execa } from 'execa';\nimport yargs from 'yargs/yargs';\nimport {\n type CreatePackageManager,\n createPackageManagers,\n detectPackageManager,\n packageManagerCommands,\n} from './create-package-manager';\nimport {\n type CreatePlatform,\n createPlatformLabels,\n createPlatforms,\n createProjectFiles,\n} from './create-template';\nimport type { TestCliIO } from './test-command';\n\nconst createParser = () =>\n yargs()\n .scriptName('midscene-test create')\n .usage('Usage: $0 [directory] [options]')\n .parserConfiguration({\n 'parse-positional-numbers': false,\n 'greedy-arrays': false,\n 'camel-case-expansion': false,\n 'boolean-negation': false,\n })\n .option('platform', {\n type: 'string',\n choices: createPlatforms,\n requiresArg: true,\n description: 'Platform preset (prompt if omitted)',\n })\n .option('package-manager', {\n type: 'string',\n choices: createPackageManagers,\n requiresArg: true,\n description:\n 'Package manager (prompt if omitted; detect without prompts, falling back to npm)',\n })\n .option('yes', {\n type: 'boolean',\n alias: 'y',\n default: false,\n description:\n 'Disable prompts and install dependencies; directory and platform must be supplied',\n })\n .option('skip-install', {\n type: 'boolean',\n default: false,\n description:\n 'Create files without installing dependencies or generating Node Specs',\n })\n .demandCommand(0, 1, '', 'Only one project directory is allowed.')\n .example('$0', 'Choose a directory and platform interactively')\n .example('$0 my-tests --platform web', 'Create a Web project')\n .epilogue(\n 'Optionally installs dependencies with the selected package manager. The generated postinstall script creates a Project-specific Node Spec after installation.\\nExisting files are never overwritten. Setup and tests are not run during creation.',\n )\n .help('help')\n .alias('help', 'h')\n .version(false)\n .strict()\n .exitProcess(false)\n .showHelpOnFail(false)\n .fail((message, error) => {\n throw error ?? new Error(message);\n })\n .wrap(100);\n\nexport interface CreateOptions {\n directory?: string;\n platform?: CreatePlatform;\n packageManager?: CreatePackageManager;\n yes: boolean;\n skipInstall: boolean;\n help: boolean;\n}\n\nexport function parseCreateArgs(\n args: string[],\n onOutput?: (message: string) => void,\n): CreateOptions {\n const values = createParser().parseSync(args, {}, (error, _argv, output) => {\n if (error) throw error;\n if (output) onOutput?.(output);\n });\n const directory = values._[0] === undefined ? undefined : String(values._[0]);\n if (directory !== undefined && !directory.trim()) {\n throw new Error('Project directory must not be empty.');\n }\n return {\n directory,\n platform: values.platform,\n packageManager: values['package-manager'],\n yes: values.yes ?? false,\n skipInstall: values['skip-install'] ?? false,\n help: values.help === true,\n };\n}\n\nexport interface CreateServices {\n cwd: string;\n interactive: boolean;\n userAgent: string | undefined;\n promptDirectory(): Promise<string>;\n selectPlatform(): Promise<CreatePlatform>;\n selectPackageManager(\n defaultValue: CreatePackageManager,\n ): Promise<CreatePackageManager>;\n confirmInstall(): Promise<boolean>;\n runPackageManager(\n packageManager: CreatePackageManager,\n args: string[],\n cwd: string,\n captureOutput?: boolean,\n ): Promise<string>;\n}\n\nconst promptDirectory = () =>\n input({\n message: 'Project directory:',\n default: 'my-tests',\n validate: (value) =>\n value.trim().length > 0 || 'Project directory must not be empty.',\n });\n\nconst selectPlatform = () =>\n select<CreatePlatform>({\n message: 'Select a platform:',\n choices: createPlatforms.map((platform) => ({\n name: createPlatformLabels[platform],\n value: platform,\n })),\n });\n\nconst selectPackageManager: CreateServices['selectPackageManager'] = (\n defaultValue,\n) =>\n select<CreatePackageManager>({\n message: 'Select a package manager:',\n choices: createPackageManagers.map((value) => ({ name: value, value })),\n default: defaultValue,\n });\n\nconst runPackageManager: CreateServices['runPackageManager'] = async (\n packageManager,\n args,\n cwd,\n captureOutput = false,\n) => {\n const result = await execa(packageManager, args, {\n cwd,\n stdin: 'inherit',\n stdout: captureOutput ? 'pipe' : 'inherit',\n stderr: 'inherit',\n stripFinalNewline: false,\n });\n return result.stdout ?? '';\n};\n\nconst statIfPresent = (path: string) => {\n try {\n return lstatSync(path);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n};\n\n/** Check every destination before writing anything, including dangling links. */\nfunction checkDestinations(root: string, filenames: string[]): void {\n const conflicts: string[] = [];\n for (const filename of filenames) {\n const path = resolve(root, filename);\n if (statIfPresent(path)) conflicts.push(path);\n let parent = dirname(path);\n while (true) {\n const stat = statIfPresent(parent);\n if (stat && !stat.isDirectory()) conflicts.push(parent);\n if (parent === root || parent === dirname(parent)) break;\n parent = dirname(parent);\n }\n }\n if (conflicts.length) {\n throw new Error(\n `Cannot create project; these paths already exist or are not directories:\\n${[...new Set(conflicts)].join('\\n')}\\nChoose another directory or move the conflicting files.`,\n );\n }\n}\n\nexport async function runCreateCommand(\n args: string[],\n io: TestCliIO,\n overrides: Partial<CreateServices> = {},\n): Promise<void> {\n const options = parseCreateArgs(args, (message) => io.log(message));\n if (options.help) return;\n const services: CreateServices = {\n cwd: process.cwd(),\n interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n userAgent: process.env.npm_config_user_agent,\n promptDirectory,\n selectPlatform,\n selectPackageManager,\n confirmInstall: () =>\n confirm({\n message: 'Install dependencies and generate Node Specs now?',\n default: true,\n }),\n runPackageManager,\n ...overrides,\n };\n if (\n (!options.directory || !options.platform) &&\n (options.yes || !services.interactive)\n ) {\n throw new Error(\n 'Project directory and --platform are required without prompts. Example: midscene-test create my-tests --platform web',\n );\n }\n let directory: string;\n let platform: CreatePlatform;\n let packageManager: CreatePackageManager;\n try {\n directory = options.directory ?? (await services.promptDirectory()).trim();\n platform = options.platform ?? (await services.selectPlatform());\n const defaultPackageManager = detectPackageManager(services.userAgent);\n packageManager =\n options.packageManager ??\n (services.interactive && !options.yes\n ? await services.selectPackageManager(defaultPackageManager)\n : defaultPackageManager);\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error('Project creation cancelled.');\n }\n throw error;\n }\n const root = resolve(services.cwd, directory);\n const name =\n basename(root)\n .toLowerCase()\n .replace(/[^a-z0-9._-]/g, '-')\n .replace(/^[._-]+/, '') || 'midscene-tests';\n const commands = packageManagerCommands[packageManager];\n const files = createProjectFiles(name, platform, packageManager);\n checkDestinations(root, [\n ...Object.keys(files),\n `midscene-node-spec.${platform}.md`,\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n ]);\n for (const [filename, content] of Object.entries(files)) {\n const path = resolve(root, filename);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, content, { flag: 'wx' });\n }\n io.log(`Created ${platform} project files in ${root}`);\n let install = !options.skipInstall;\n if (install && services.interactive && !options.yes) {\n try {\n install = await services.confirmInstall();\n } catch (error) {\n if (\n error instanceof Error &&\n ['ExitPromptError', 'AbortPromptError'].includes(error.name)\n ) {\n throw new Error(\n `Project creation cancelled. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')} to install dependencies and generate midscene-node-spec.${platform}.md.`,\n );\n }\n throw error;\n }\n }\n if (!install) {\n io.log(\n `Project files ready: ${root}\\nNext: run ${packageManager} ${commands.install.join(' ')} in the project directory. The postinstall script will generate midscene-node-spec.${platform}.md.\\nThen follow README.md to configure your model and run tests.`,\n );\n return;\n }\n io.log(`Installing dependencies with ${packageManager}...`);\n try {\n await services.runPackageManager(packageManager, commands.install, root);\n } catch (error) {\n throw new Error(\n `Dependency installation or postinstall Node Spec generation failed. Project files are preserved in ${root}.\\nIn that directory, run ${packageManager} ${commands.install.join(' ')}, then ${packageManager} run nodes if lifecycle scripts are disabled.\\nIf postinstall failed, check midscene.config.ts and the Node package factories, then run ${packageManager} run nodes.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n try {\n const specPath = resolve(root, `midscene-node-spec.${platform}.md`);\n const generatedByPostinstall = Boolean(statIfPresent(specPath));\n // Fall back to explicit generation if lifecycle scripts were disabled.\n if (!generatedByPostinstall) {\n io.log('Generating Node Spec...');\n await services.runPackageManager(packageManager, commands.describe, root);\n }\n const markdown = readFileSync(specPath, 'utf8');\n if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) {\n throw new Error('nodes did not generate a valid Node Spec.');\n }\n } catch (error) {\n throw new Error(\n `Node Spec generation failed. Project files are preserved in ${root}.\\nCheck that each Node package exports createMidsceneTestNodes(options), returns a Node array synchronously, and does not register duplicate names.\\nFix midscene.config.ts, then run ${packageManager} run nodes in the project directory.\\n${error instanceof Error ? error.message : String(error)}`,\n );\n }\n io.log(\n `Project ready: ${root}\\nNode Spec: ${resolve(root, `midscene-node-spec.${platform}.md`)}\\nNext: copy .env.example to .env and configure your model.${platform === 'web' ? `\\nInstall Chromium: ${commands.installChromium}` : platform === 'computer' ? '\\nPrepare desktop dependencies and permissions as described in README.md.' : '\\nConfigure your device connection in .env.'}\\nRun tests from the project directory: ${packageManager} test`,\n );\n}\n"],"names":["createPackageManagers","packageManagerCommands","detectPackageManager","userAgent","createPlatforms","createPlatformLabels","platformImports","agentClasses","platformSetup","platformEnv","modelEnv","platformInstructions","createProjectFiles","name","platform","packageManager","instructions","agentClass","config","dependencies","version","env","JSON","createParser","yargs","message","error","Error","parseCreateArgs","args","onOutput","values","_argv","output","directory","undefined","String","promptDirectory","input","value","selectPlatform","select","selectPackageManager","defaultValue","runPackageManager","cwd","captureOutput","result","execa","statIfPresent","path","lstatSync","checkDestinations","root","filenames","conflicts","filename","resolve","parent","dirname","stat","Set","runCreateCommand","io","overrides","options","services","process","Boolean","confirm","defaultPackageManager","basename","commands","files","Object","content","mkdirSync","writeFileSync","install","specPath","generatedByPostinstall","markdown","readFileSync"],"mappings":"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"}
@@ -16,7 +16,9 @@ var __webpack_modules__ = {
16
16
  module.exports = require("yargs/yargs");
17
17
  },
18
18
  "./package.json" (module) {
19
- module.exports = JSON.parse('{"rE":"1.13.2-beta-20260923035443.0"}');
19
+ module.exports = {
20
+ rE: "1.13.3"
21
+ };
20
22
  }
21
23
  };
22
24
  var __webpack_module_cache__ = {};
@@ -81,7 +83,7 @@ var __webpack_exports__ = {};
81
83
  var package_0 = __webpack_require__("./package.json");
82
84
  const v4_namespaceObject = require("zod/v4");
83
85
  const GENERATED_COMMENT = '<!-- Generated by `midscene-test nodes`. Do not edit directly. -->';
84
- const sortNodesForReference = (nodes)=>{
86
+ const sortNodesForSpec = (nodes)=>{
85
87
  const priority = (name)=>'aiAct' === name ? 0 : 'aiAssert' === name ? 1 : 2;
86
88
  return [
87
89
  ...nodes
@@ -101,7 +103,7 @@ var __webpack_exports__ = {};
101
103
  const renderWorkflowLocations = (location)=>{
102
104
  const sections = [
103
105
  '## Where to use these Nodes',
104
- 'Use these Nodes in Workflow Documents (`.yaml` or `.yml` files) selected by an Execution Project. Case file patterns and the config file path below are relative to this reference file. Case file patterns use glob syntax. A custom `--config` path does not change where case files are selected.',
106
+ 'Use these Nodes in Workflow Documents (`.yaml` or `.yml` files) selected by an Execution Project. Case file patterns and the config file path below are relative to this spec file. Case file patterns use glob syntax. A custom `--config` path does not change where case files are selected.',
105
107
  'Projects created by `midscene-test create` select `cases/**/*.{yaml,yml}` by default, so you can write test cases in `cases/example.yaml` or subdirectories of `cases/`. If an Execution Project has no `files` configuration, Midscene Test searches `**/*.{yaml,yml}` recursively instead. Check `projects[].files` in your configuration before choosing a file location.'
106
108
  ];
107
109
  if (location) {
@@ -191,13 +193,14 @@ Each Node's returned \`data\` is saved on that Step's result. Results are not na
191
193
  }
192
194
  return sections.join('\n\n');
193
195
  };
194
- const renderNodeReference = (nodes, location)=>{
196
+ const renderNodeSpec = (nodes, location)=>{
195
197
  const warnings = [];
196
- const sortedNodes = sortNodesForReference(nodes);
198
+ const sortedNodes = sortNodesForSpec(nodes);
197
199
  const sections = [
198
200
  GENERATED_COMMENT,
199
- '# Midscene Test Node Reference',
200
- 'This document describes the nodes registered by the current Test Project.',
201
+ '# Midscene Test Node Spec',
202
+ 'This document describes the Nodes available in YAML test files (`.yaml` or `.yml`) for the Midscene Test framework (`@midscene/test`). It lists the Nodes registered by the current Test Project, their inputs, and how to call them in test steps and lifecycle hooks.',
203
+ "Learn about the framework and YAML test authoring:\n\n- [Midscene Test overview](https://midscenejs.com/midscene-test/overview): framework concepts and the role of Nodes.\n- [Create and use test projects](https://midscenejs.com/midscene-test/use): write YAML cases, run tests, and inspect results.\n- [Configure test projects](https://midscenejs.com/midscene-test/configuration): register Nodes, select case files, and manage runtime resources.",
201
204
  renderNodeOverview(sortedNodes),
202
205
  renderWorkflowLocations(location),
203
206
  WORKFLOW_STEP_CONTRACT
@@ -1599,24 +1602,44 @@ Legacy tasks/flow YAML and its CLI options belong to the midscene command.
1599
1602
  const projectName = options.projectNames?.[0];
1600
1603
  const selectedProjects = projectName ? project.projects.filter((candidate)=>candidate.name === projectName) : project.projects;
1601
1604
  if (0 === selectedProjects.length) throw new Error(`Unknown Midscene project: ${projectName}`);
1602
- const registry = selectedProjects[0].nodes;
1603
- if (selectedProjects.some((candidate)=>candidate.nodes.names().length !== registry.names().length || candidate.nodes.definitions().some((node)=>registry.get(node.name) !== node))) throw new Error('Projects have different Nodes. Use nodes --project <name> to select one.');
1604
- const nodes = sortNodesForReference(registry.definitions());
1605
- const document = renderNodeReference(nodes, {
1606
- configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
1607
- projects: selectedProjects.map((executionProject)=>({
1608
- name: executionProject.name,
1609
- files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
1610
- }))
1605
+ const filenames = new Map();
1606
+ const owners = new Map();
1607
+ for (const executionProject of project.projects){
1608
+ const safeName = executionProject.name.normalize('NFC').replace(/[<>:"/\\|?*\u0000-\u001f]/g, '-').replace(/[. ]+$/g, '-');
1609
+ const filename = project.hasExplicitProjects ? `midscene-node-spec.${safeName}.md` : 'midscene-node-spec.md';
1610
+ const key = filename.toLowerCase();
1611
+ const owner = owners.get(key);
1612
+ if (void 0 !== owner) throw new Error(`Projects "${owner}" and "${executionProject.name}" generate the same Node Spec filename: ${filename}`);
1613
+ owners.set(key, executionProject.name);
1614
+ filenames.set(executionProject.name, filename);
1615
+ }
1616
+ const specs = selectedProjects.map((executionProject)=>{
1617
+ const nodes = sortNodesForSpec(executionProject.nodes.definitions());
1618
+ const document = renderNodeSpec(nodes, {
1619
+ configPath: configPath ? (0, external_node_path_.relative)(configSearchRoot, configPath).split(external_node_path_.sep).join('/') : void 0,
1620
+ projects: [
1621
+ {
1622
+ name: executionProject.name,
1623
+ files: executionProject.files ?? DEFAULT_TEST_FILE_SELECTION
1624
+ }
1625
+ ]
1626
+ });
1627
+ return {
1628
+ executionProject,
1629
+ nodes,
1630
+ document
1631
+ };
1611
1632
  });
1612
- for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
1613
- const referencePath = (0, external_node_path_.resolve)(configSearchRoot, 'midscene-node-reference.md');
1614
- (0, external_node_fs_.writeFileSync)(referencePath, document.markdown);
1615
- if (projectName) io.log(`Execution Project: ${projectName}`);
1616
- io.log(`Registered Nodes (${nodes.length}):`);
1617
- if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
1618
- for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
1619
- io.log(`\nNode reference generated: ${referencePath}`);
1633
+ for (const { executionProject, nodes, document } of specs){
1634
+ for (const warning of document.warnings)io.error(`midscene-test nodes: ${warning}`);
1635
+ const specPath = (0, external_node_path_.resolve)(configSearchRoot, filenames.get(executionProject.name));
1636
+ (0, external_node_fs_.writeFileSync)(specPath, document.markdown);
1637
+ if (project.hasExplicitProjects || projectName) io.log(`Execution Project: ${executionProject.name}`);
1638
+ io.log(`Registered Nodes (${nodes.length}):`);
1639
+ if (0 === nodes.length) io.log('No nodes are registered by the current Test Project.');
1640
+ for (const node of nodes)io.log(`- ${node.name}: ${node.description?.trim() || "Description not declared."}`);
1641
+ io.log(`\nNode Spec generated: ${specPath}`);
1642
+ }
1620
1643
  };
1621
1644
  async function runTestCli(args, io = defaultCliIO) {
1622
1645
  try {