@midscene/test 1.14.1-beta-20261008033312.0 → 1.15.0
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/es/1~214.mjs +6 -2
- package/dist/es/1~214.mjs.map +1 -1
- package/dist/es/1~77.mjs +95 -1
- package/dist/es/1~77.mjs.map +1 -1
- package/dist/es/cli/model-command.mjs +95 -1
- package/dist/es/cli/model-command.mjs.map +1 -1
- package/dist/es/test-cli.mjs +85 -30
- package/dist/es/test-cli.mjs.map +1 -1
- package/dist/lib/0~160.js +6 -2
- package/dist/lib/0~160.js.map +1 -1
- package/dist/lib/0~703.js +95 -1
- package/dist/lib/0~703.js.map +1 -1
- package/dist/lib/cli/model-command.js +95 -1
- package/dist/lib/cli/model-command.js.map +1 -1
- package/dist/lib/test-cli.js +85 -30
- package/dist/lib/test-cli.js.map +1 -1
- package/package.json +13 -13
package/dist/es/1~214.mjs
CHANGED
|
@@ -405,7 +405,9 @@ export default defineTestProject<ProjectContext>({
|
|
|
405
405
|
try {
|
|
406
406
|
await services.runPackageManager(packageManager, commands.install, root);
|
|
407
407
|
} catch (error) {
|
|
408
|
-
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)}
|
|
408
|
+
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)}`, {
|
|
409
|
+
cause: error
|
|
410
|
+
});
|
|
409
411
|
}
|
|
410
412
|
try {
|
|
411
413
|
const specPath = (0, external_node_path_.resolve)(root, `midscene-node-spec.${platform}.md`);
|
|
@@ -417,7 +419,9 @@ export default defineTestProject<ProjectContext>({
|
|
|
417
419
|
const markdown = (0, external_node_fs_.readFileSync)(specPath, 'utf8');
|
|
418
420
|
if (!markdown.startsWith('<!-- Generated by `midscene-test nodes`.')) throw new Error('nodes did not generate a valid Node Spec.');
|
|
419
421
|
} catch (error) {
|
|
420
|
-
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)}
|
|
422
|
+
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)}`, {
|
|
423
|
+
cause: error
|
|
424
|
+
});
|
|
421
425
|
}
|
|
422
426
|
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.\nVerify the model from the project directory: ${commands.verifyModel}${'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`);
|
|
423
427
|
}
|
package/dist/es/1~214.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1~214.mjs","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 {\n install: string[];\n describe: string[];\n installChromium: string;\n verifyModel: string;\n }\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 verifyModel: 'npm exec -- midscene-test model verify',\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n verifyModel: 'pnpm exec midscene-test model verify',\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 wdaBaseUrl = env.WDA_BASE_URL || undefined;\n if (wdaBaseUrl && (env.WDA_HOST !== undefined || env.WDA_PORT !== undefined)) {\n throw new Error('WDA_BASE_URL cannot be used with WDA_HOST or WDA_PORT.');\n }\n 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 wdaMjpegUrl = env.WDA_MJPEG_URL || undefined;\n if (wdaMjpegUrl && env.WDA_MJPEG_PORT !== undefined) {\n throw new Error('WDA_MJPEG_URL cannot be used with WDA_MJPEG_PORT.');\n }\n const mjpegPort = env.WDA_MJPEG_PORT === undefined ? undefined : Number(env.WDA_MJPEG_PORT);\n if (mjpegPort !== undefined && (!Number.isInteger(mjpegPort) || mjpegPort < 1 || mjpegPort > 65535)) {\n throw new Error('WDA_MJPEG_PORT must be an integer between 1 and 65535.');\n }\n const agent = await agentFromWebDriverAgent({\n ...(wdaBaseUrl\n ? { wdaBaseUrl }\n : { wdaHost: env.WDA_HOST || 'localhost', wdaPort: port }),\n ...(wdaMjpegUrl ? { wdaMjpegUrl } : {}),\n ...(mjpegPort !== undefined ? { wdaMjpegPort: mjpegPort } : {}),\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\\n# WDA_PORT=8100\\n# WDA_BASE_URL=https://gateway.example/device/wda\\n# WDA_MJPEG_PORT=9100\\n# WDA_MJPEG_URL=https://gateway.example/device/mjpeg\\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 = (\n packageManager: CreatePackageManager,\n) => `# 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\n# After configuring .env, verify model connectivity and Midscene compatibility from the project directory:\n# ${packageManagerCommands[packageManager].verifyModel}\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, or set WDA_BASE_URL for a gateway URL with a path prefix. Set WDA_MJPEG_URL for a separate remote MJPEG stream, or WDA_MJPEG_PORT for a direct connection.',\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(packageManager)}\\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\\nAfter configuring \\`.env\\`, run \\`${packageManagerCommands[packageManager].verifyModel}\\` from the project directory to check model connectivity and Midscene compatibility. This check does not require a browser or device. If it fails, see [Model debugging and observability](https://midscenejs.com/model-debugging-observability.html) before running tests.\\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 copy .env.example to .env and configure your model.\\nVerify the model from the project directory: ${commands.verifyModel}\\nFollow README.md to prepare your runtime 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.\\nVerify the model from the project directory: ${commands.verifyModel}${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","packageManager","platformInstructions","createProjectFiles","name","platform","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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"}
|
|
1
|
+
{"version":3,"file":"1~214.mjs","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 {\n install: string[];\n describe: string[];\n installChromium: string;\n verifyModel: string;\n }\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 verifyModel: 'npm exec -- midscene-test model verify',\n installChromium: 'npm exec -- playwright install chromium',\n },\n pnpm: {\n install: ['install', '--ignore-workspace'],\n describe: ['exec', 'midscene-test', 'nodes'],\n verifyModel: 'pnpm exec midscene-test model verify',\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 wdaBaseUrl = env.WDA_BASE_URL || undefined;\n if (wdaBaseUrl && (env.WDA_HOST !== undefined || env.WDA_PORT !== undefined)) {\n throw new Error('WDA_BASE_URL cannot be used with WDA_HOST or WDA_PORT.');\n }\n 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 wdaMjpegUrl = env.WDA_MJPEG_URL || undefined;\n if (wdaMjpegUrl && env.WDA_MJPEG_PORT !== undefined) {\n throw new Error('WDA_MJPEG_URL cannot be used with WDA_MJPEG_PORT.');\n }\n const mjpegPort = env.WDA_MJPEG_PORT === undefined ? undefined : Number(env.WDA_MJPEG_PORT);\n if (mjpegPort !== undefined && (!Number.isInteger(mjpegPort) || mjpegPort < 1 || mjpegPort > 65535)) {\n throw new Error('WDA_MJPEG_PORT must be an integer between 1 and 65535.');\n }\n const agent = await agentFromWebDriverAgent({\n ...(wdaBaseUrl\n ? { wdaBaseUrl }\n : { wdaHost: env.WDA_HOST || 'localhost', wdaPort: port }),\n ...(wdaMjpegUrl ? { wdaMjpegUrl } : {}),\n ...(mjpegPort !== undefined ? { wdaMjpegPort: mjpegPort } : {}),\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\\n# WDA_PORT=8100\\n# WDA_BASE_URL=https://gateway.example/device/wda\\n# WDA_MJPEG_PORT=9100\\n# WDA_MJPEG_URL=https://gateway.example/device/mjpeg\\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 = (\n packageManager: CreatePackageManager,\n) => `# 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\n# After configuring .env, verify model connectivity and Midscene compatibility from the project directory:\n# ${packageManagerCommands[packageManager].verifyModel}\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, or set WDA_BASE_URL for a gateway URL with a path prefix. Set WDA_MJPEG_URL for a separate remote MJPEG stream, or WDA_MJPEG_PORT for a direct connection.',\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(packageManager)}\\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\\nAfter configuring \\`.env\\`, run \\`${packageManagerCommands[packageManager].verifyModel}\\` from the project directory to check model connectivity and Midscene compatibility. This check does not require a browser or device. If it fails, see [Model debugging and observability](https://midscenejs.com/model-debugging-observability.html) before running tests.\\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 copy .env.example to .env and configure your model.\\nVerify the model from the project directory: ${commands.verifyModel}\\nFollow README.md to prepare your runtime 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 { cause: 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 { cause: 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.\\nVerify the model from the project directory: ${commands.verifyModel}${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","packageManager","platformInstructions","createProjectFiles","name","platform","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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"}
|
package/dist/es/1~77.mjs
CHANGED
|
@@ -30,6 +30,7 @@ export const __webpack_modules__ = {
|
|
|
30
30
|
|
|
31
31
|
Verify model connectivity and Midscene compatibility for default, planning, and insight models.
|
|
32
32
|
Reads .env from the current working directory, overriding shell environment variables.
|
|
33
|
+
Shows endpoints, model names, request counts, and per-check results with duration.
|
|
33
34
|
Returns exit code 0 on success and 1 on failure.
|
|
34
35
|
|
|
35
36
|
Options:
|
|
@@ -38,6 +39,26 @@ Options:
|
|
|
38
39
|
function assertNoModelVerifyOptions(args, commandName) {
|
|
39
40
|
for (const arg of args)throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);
|
|
40
41
|
}
|
|
42
|
+
function formatTable(headers, rows) {
|
|
43
|
+
const widths = headers.map((header, index)=>Math.max(header.length, ...rows.map((row)=>row[index].length)));
|
|
44
|
+
const formatRow = (row)=>row.map((cell, index)=>cell.padEnd(widths[index])).join(' ').trimEnd();
|
|
45
|
+
return [
|
|
46
|
+
formatRow(headers),
|
|
47
|
+
...rows.map(formatRow)
|
|
48
|
+
].join('\n');
|
|
49
|
+
}
|
|
50
|
+
function displayEndpoint(endpoint) {
|
|
51
|
+
try {
|
|
52
|
+
const url = new URL(endpoint);
|
|
53
|
+
url.username = '';
|
|
54
|
+
url.password = '';
|
|
55
|
+
url.search = '';
|
|
56
|
+
url.hash = '';
|
|
57
|
+
return url.toString();
|
|
58
|
+
} catch {
|
|
59
|
+
return '(invalid endpoint URL)';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
41
62
|
function shellSingleQuote(value) {
|
|
42
63
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
43
64
|
}
|
|
@@ -131,7 +152,7 @@ Options:
|
|
|
131
152
|
return 0;
|
|
132
153
|
}
|
|
133
154
|
assertNoModelVerifyOptions(args, commandName);
|
|
134
|
-
io.stdout('Model verify started. This usually
|
|
155
|
+
io.stdout('Model verify started. This usually completes within 30 seconds.');
|
|
135
156
|
deps.loadDotenv();
|
|
136
157
|
io.stdout('');
|
|
137
158
|
const defaultModelConfig = deps.getModelConfig('default');
|
|
@@ -151,11 +172,84 @@ Options:
|
|
|
151
172
|
modelConfig: insightModelConfig
|
|
152
173
|
}
|
|
153
174
|
]);
|
|
175
|
+
const baseURLs = new Map();
|
|
176
|
+
const modelRows = [
|
|
177
|
+
[
|
|
178
|
+
'planning',
|
|
179
|
+
planningModelConfig,
|
|
180
|
+
'Text response'
|
|
181
|
+
],
|
|
182
|
+
[
|
|
183
|
+
'insight',
|
|
184
|
+
insightModelConfig,
|
|
185
|
+
'Image understanding'
|
|
186
|
+
],
|
|
187
|
+
[
|
|
188
|
+
'default',
|
|
189
|
+
defaultModelConfig,
|
|
190
|
+
'AI locate'
|
|
191
|
+
]
|
|
192
|
+
].map(([intent, config, check])=>{
|
|
193
|
+
const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;
|
|
194
|
+
const endpoint = baseURL ? displayEndpoint(String(baseURL)) : 'provider default';
|
|
195
|
+
if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);
|
|
196
|
+
return [
|
|
197
|
+
intent,
|
|
198
|
+
config.modelName,
|
|
199
|
+
check,
|
|
200
|
+
`[${baseURLs.get(endpoint)}]`
|
|
201
|
+
];
|
|
202
|
+
});
|
|
203
|
+
io.stdout(formatTable([
|
|
204
|
+
'Model role',
|
|
205
|
+
'Model',
|
|
206
|
+
'Check',
|
|
207
|
+
'Endpoint'
|
|
208
|
+
], modelRows));
|
|
209
|
+
io.stdout('');
|
|
210
|
+
for (const [endpoint, index] of baseURLs)io.stdout(`[${index}] Base URL: ${endpoint}`);
|
|
211
|
+
io.stdout('\nRunning 3 checks...\n');
|
|
154
212
|
const result = await deps.verifyModel({
|
|
155
213
|
defaultModelConfig,
|
|
156
214
|
planningModelConfig,
|
|
157
215
|
insightModelConfig
|
|
158
216
|
});
|
|
217
|
+
const checkLabels = {
|
|
218
|
+
text: 'Text response',
|
|
219
|
+
vision: 'Image understanding',
|
|
220
|
+
aiLocate: 'AI locate'
|
|
221
|
+
};
|
|
222
|
+
io.stdout(formatTable([
|
|
223
|
+
'Model role',
|
|
224
|
+
'Check',
|
|
225
|
+
'Result',
|
|
226
|
+
'Requests',
|
|
227
|
+
'Time'
|
|
228
|
+
], result.checks.map((check)=>[
|
|
229
|
+
check.intent,
|
|
230
|
+
checkLabels[check.name],
|
|
231
|
+
check.passed ? 'PASS' : 'FAIL',
|
|
232
|
+
String(check.requestCount),
|
|
233
|
+
`${check.durationMs} ms`
|
|
234
|
+
])));
|
|
235
|
+
const requestEndpoints = new Map();
|
|
236
|
+
for (const check of result.checks){
|
|
237
|
+
for (const endpoint of check.endpoints){
|
|
238
|
+
const display = displayEndpoint(endpoint);
|
|
239
|
+
if (!requestEndpoints.has(display)) requestEndpoints.set(display, new Set());
|
|
240
|
+
requestEndpoints.get(display).add(check.intent);
|
|
241
|
+
}
|
|
242
|
+
if (!check.passed && check.message) io.stdout(`\n${check.intent}: ${check.message}`);
|
|
243
|
+
}
|
|
244
|
+
if (requestEndpoints.size > 0) {
|
|
245
|
+
io.stdout('\nRequest endpoints:');
|
|
246
|
+
for (const [endpoint, intents] of requestEndpoints)io.stdout(` ${[
|
|
247
|
+
...intents
|
|
248
|
+
].join(', ')}: ${endpoint}`);
|
|
249
|
+
}
|
|
250
|
+
io.stdout('');
|
|
251
|
+
const passedChecks = result.checks.filter((check)=>check.passed).length;
|
|
252
|
+
io.stdout(`Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`);
|
|
159
253
|
if (result.passed) {
|
|
160
254
|
io.stdout('✅ Model verify passed.');
|
|
161
255
|
return 0;
|
package/dist/es/1~77.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"1~77.mjs","sources":["../../src/cli/dotenv-loader.ts","../../src/cli/model-command.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n","import { runConnectivityTest } from '@midscene/core';\nimport {\n type IModelConfig,\n type TIntent,\n globalModelConfigManager,\n} from '@midscene/shared/env';\nimport chalk from 'chalk';\nimport { loadDotenvConfig } from './dotenv-loader';\n\nconst DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';\nconst MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';\nconst modelCommandUsage = (commandName: string) => `Usage:\n ${commandName} model verify\n\nVerify model connectivity and Midscene compatibility for default, planning, and insight models.\nReads .env from the current working directory, overriding shell environment variables.\nReturns exit code 0 on success and 1 on failure.\n\nOptions:\n --help, -h Show this help\n`;\n\ninterface ModelCommandIO {\n stdout: (message: string) => void;\n stderr: (message: string) => void;\n}\n\ninterface ModelCommandDeps {\n loadDotenv: () => void;\n getModelConfig: (intent: TIntent) => IModelConfig;\n verifyModel: typeof runConnectivityTest;\n}\n\nexport interface CurlCommandItem {\n intents: TIntent[];\n curl: string;\n usesDefaultBaseURL: boolean;\n}\n\nfunction assertNoModelVerifyOptions(args: string[], commandName: string) {\n for (const arg of args) {\n throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);\n }\n}\n\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildChatCompletionsUrl(baseURL?: string): string {\n const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(\n /\\/+$/,\n '',\n );\n if (normalizedBaseURL.endsWith('/chat/completions')) {\n return normalizedBaseURL;\n }\n return `${normalizedBaseURL}/chat/completions`;\n}\n\nfunction buildCurlCommand(modelConfig: IModelConfig): string {\n const payload = {\n model: modelConfig.modelName,\n messages: [{ role: 'user', content: 'What is 1+1?' }],\n };\n\n return [\n `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\\\`,\n ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\\\`,\n ` -H ${shellSingleQuote('Content-Type: application/json')} \\\\`,\n ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`,\n ].join('\\n');\n}\n\nfunction buildCurlDedupKey(modelConfig: IModelConfig): string {\n return JSON.stringify({\n baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,\n apiKey: modelConfig.openaiApiKey || '',\n modelName: modelConfig.modelName,\n });\n}\n\nexport function buildModelVerifyCurlCommands(\n configs: Array<{ intent: TIntent; modelConfig: IModelConfig }>,\n): CurlCommandItem[] {\n const commandMap = new Map<string, CurlCommandItem>();\n\n for (const item of configs) {\n const key = buildCurlDedupKey(item.modelConfig);\n const existing = commandMap.get(key);\n if (existing) {\n existing.intents.push(item.intent);\n continue;\n }\n commandMap.set(key, {\n intents: [item.intent],\n curl: buildCurlCommand(item.modelConfig),\n usesDefaultBaseURL: !item.modelConfig.openaiBaseURL,\n });\n }\n\n return [...commandMap.values()];\n}\n\nfunction formatModelVerifyFailureOutput(\n message: string | undefined,\n curlCommands: CurlCommandItem[],\n): string {\n const details = message?.trim() || 'No failure details were generated.';\n const curlSection = curlCommands\n .map((item) => {\n const baseUrlNote = item.usesDefaultBaseURL\n ? ' (base URL not configured; using OpenAI SDK default)'\n : '';\n return chalk.gray(\n `# ${item.intents.join(', ')}${baseUrlNote}\\n${item.curl}`,\n );\n })\n .join('\\n\\n');\n\n return [\n chalk.red.bold('❌ Model verify failed with messages:'),\n MODEL_VERIFY_SEPARATOR,\n '',\n details,\n '',\n MODEL_VERIFY_SEPARATOR,\n 'Generated curl requests for basic API connectivity:',\n 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',\n 'These commands contain your API key. Do not share them publicly.',\n '',\n curlSection,\n ].join('\\n');\n}\n\nfunction getDefaultDeps(io: ModelCommandIO): ModelCommandDeps {\n return {\n loadDotenv: () => {\n loadDotenvConfig({\n dotenvDebug: true,\n dotenvOverride: true,\n log: io.stdout,\n });\n },\n getModelConfig: (intent) => globalModelConfigManager.getModelConfig(intent),\n verifyModel: runConnectivityTest,\n };\n}\n\nasync function runModelVerifyCommand(\n args: string[],\n deps: ModelCommandDeps,\n io: ModelCommandIO,\n commandName: string,\n): Promise<number> {\n try {\n if (args.includes('--help') || args.includes('-h')) {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n assertNoModelVerifyOptions(args, commandName);\n io.stdout('Model verify started. This usually takes about 5 seconds.\\n');\n deps.loadDotenv();\n io.stdout('');\n\n const defaultModelConfig = deps.getModelConfig('default');\n const planningModelConfig = deps.getModelConfig('planning');\n const insightModelConfig = deps.getModelConfig('insight');\n const curlCommands = buildModelVerifyCurlCommands([\n { intent: 'default', modelConfig: defaultModelConfig },\n { intent: 'planning', modelConfig: planningModelConfig },\n { intent: 'insight', modelConfig: insightModelConfig },\n ]);\n\n const result = await deps.verifyModel({\n defaultModelConfig,\n planningModelConfig,\n insightModelConfig,\n });\n\n if (result.passed) {\n io.stdout('✅ Model verify passed.');\n return 0;\n }\n\n io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));\n return 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(\n [\n MODEL_VERIFY_SEPARATOR,\n '❌ Model verify failed with messages:',\n '',\n message,\n MODEL_VERIFY_SEPARATOR,\n ].join('\\n'),\n );\n return 1;\n }\n}\n\nexport async function runModelCommand(\n rawArgs: string[],\n deps?: Partial<ModelCommandDeps>,\n io: ModelCommandIO = {\n stdout: console.log,\n stderr: console.error,\n },\n commandName = 'midscene-test',\n): Promise<number> {\n const [, action, ...restArgs] = rawArgs;\n const mergedDeps = {\n ...getDefaultDeps(io),\n ...deps,\n };\n\n if (!action || action === '--help' || action === '-h') {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n if (action === 'verify') {\n return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);\n }\n\n if (action === 'eval') {\n io.stderr(\n `${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`,\n );\n return 1;\n }\n\n io.stderr(\n `Unknown ${commandName} model command: ${action}\\n\\n${modelCommandUsage(commandName)}`,\n );\n return 1;\n}\n\nexport { loadDotenvConfig, type DotenvLoadOptions } from './dotenv-loader';\n"],"names":["loadDotenvConfig","options","dotEnvConfigFile","join","process","existsSync","dotenv","DEFAULT_OPENAI_BASE_URL","MODEL_VERIFY_SEPARATOR","modelCommandUsage","commandName","assertNoModelVerifyOptions","args","arg","Error","shellSingleQuote","value","buildChatCompletionsUrl","baseURL","normalizedBaseURL","buildCurlCommand","modelConfig","payload","JSON","buildCurlDedupKey","buildModelVerifyCurlCommands","configs","commandMap","Map","item","key","existing","formatModelVerifyFailureOutput","message","curlCommands","details","curlSection","baseUrlNote","chalk","getDefaultDeps","io","intent","globalModelConfigManager","runConnectivityTest","runModelVerifyCommand","deps","defaultModelConfig","planningModelConfig","insightModelConfig","result","error","String","runModelCommand","rawArgs","console","action","restArgs","mergedDeps"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"1~77.mjs","sources":["../../src/cli/dotenv-loader.ts","../../src/cli/model-command.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n","import { runConnectivityTest } from '@midscene/core';\nimport {\n type IModelConfig,\n type TIntent,\n globalModelConfigManager,\n} from '@midscene/shared/env';\nimport chalk from 'chalk';\nimport { loadDotenvConfig } from './dotenv-loader';\n\nconst DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';\nconst MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';\nconst modelCommandUsage = (commandName: string) => `Usage:\n ${commandName} model verify\n\nVerify model connectivity and Midscene compatibility for default, planning, and insight models.\nReads .env from the current working directory, overriding shell environment variables.\nShows endpoints, model names, request counts, and per-check results with duration.\nReturns exit code 0 on success and 1 on failure.\n\nOptions:\n --help, -h Show this help\n`;\n\ninterface ModelCommandIO {\n stdout: (message: string) => void;\n stderr: (message: string) => void;\n}\n\ninterface ModelCommandDeps {\n loadDotenv: () => void;\n getModelConfig: (intent: TIntent) => IModelConfig;\n verifyModel: typeof runConnectivityTest;\n}\n\nexport interface CurlCommandItem {\n intents: TIntent[];\n curl: string;\n usesDefaultBaseURL: boolean;\n}\n\nfunction assertNoModelVerifyOptions(args: string[], commandName: string) {\n for (const arg of args) {\n throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);\n }\n}\n\nfunction formatTable(headers: string[], rows: string[][]): string {\n const widths = headers.map((header, index) =>\n Math.max(header.length, ...rows.map((row) => row[index].length)),\n );\n const formatRow = (row: string[]) =>\n row\n .map((cell, index) => cell.padEnd(widths[index]))\n .join(' ')\n .trimEnd();\n return [formatRow(headers), ...rows.map(formatRow)].join('\\n');\n}\n\nfunction displayEndpoint(endpoint: string): string {\n try {\n const url = new URL(endpoint);\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.toString();\n } catch {\n return '(invalid endpoint URL)';\n }\n}\n\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildChatCompletionsUrl(baseURL?: string): string {\n const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(\n /\\/+$/,\n '',\n );\n if (normalizedBaseURL.endsWith('/chat/completions')) {\n return normalizedBaseURL;\n }\n return `${normalizedBaseURL}/chat/completions`;\n}\n\nfunction buildCurlCommand(modelConfig: IModelConfig): string {\n const payload = {\n model: modelConfig.modelName,\n messages: [{ role: 'user', content: 'What is 1+1?' }],\n };\n\n return [\n `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\\\`,\n ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\\\`,\n ` -H ${shellSingleQuote('Content-Type: application/json')} \\\\`,\n ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`,\n ].join('\\n');\n}\n\nfunction buildCurlDedupKey(modelConfig: IModelConfig): string {\n return JSON.stringify({\n baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,\n apiKey: modelConfig.openaiApiKey || '',\n modelName: modelConfig.modelName,\n });\n}\n\nexport function buildModelVerifyCurlCommands(\n configs: Array<{ intent: TIntent; modelConfig: IModelConfig }>,\n): CurlCommandItem[] {\n const commandMap = new Map<string, CurlCommandItem>();\n\n for (const item of configs) {\n const key = buildCurlDedupKey(item.modelConfig);\n const existing = commandMap.get(key);\n if (existing) {\n existing.intents.push(item.intent);\n continue;\n }\n commandMap.set(key, {\n intents: [item.intent],\n curl: buildCurlCommand(item.modelConfig),\n usesDefaultBaseURL: !item.modelConfig.openaiBaseURL,\n });\n }\n\n return [...commandMap.values()];\n}\n\nfunction formatModelVerifyFailureOutput(\n message: string | undefined,\n curlCommands: CurlCommandItem[],\n): string {\n const details = message?.trim() || 'No failure details were generated.';\n const curlSection = curlCommands\n .map((item) => {\n const baseUrlNote = item.usesDefaultBaseURL\n ? ' (base URL not configured; using OpenAI SDK default)'\n : '';\n return chalk.gray(\n `# ${item.intents.join(', ')}${baseUrlNote}\\n${item.curl}`,\n );\n })\n .join('\\n\\n');\n\n return [\n chalk.red.bold('❌ Model verify failed with messages:'),\n MODEL_VERIFY_SEPARATOR,\n '',\n details,\n '',\n MODEL_VERIFY_SEPARATOR,\n 'Generated curl requests for basic API connectivity:',\n 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',\n 'These commands contain your API key. Do not share them publicly.',\n '',\n curlSection,\n ].join('\\n');\n}\n\nfunction getDefaultDeps(io: ModelCommandIO): ModelCommandDeps {\n return {\n loadDotenv: () => {\n loadDotenvConfig({\n dotenvDebug: true,\n dotenvOverride: true,\n log: io.stdout,\n });\n },\n getModelConfig: (intent) => globalModelConfigManager.getModelConfig(intent),\n verifyModel: runConnectivityTest,\n };\n}\n\nasync function runModelVerifyCommand(\n args: string[],\n deps: ModelCommandDeps,\n io: ModelCommandIO,\n commandName: string,\n): Promise<number> {\n try {\n if (args.includes('--help') || args.includes('-h')) {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n assertNoModelVerifyOptions(args, commandName);\n io.stdout(\n 'Model verify started. This usually completes within 30 seconds.',\n );\n deps.loadDotenv();\n io.stdout('');\n\n const defaultModelConfig = deps.getModelConfig('default');\n const planningModelConfig = deps.getModelConfig('planning');\n const insightModelConfig = deps.getModelConfig('insight');\n const curlCommands = buildModelVerifyCurlCommands([\n { intent: 'default', modelConfig: defaultModelConfig },\n { intent: 'planning', modelConfig: planningModelConfig },\n { intent: 'insight', modelConfig: insightModelConfig },\n ]);\n\n const baseURLs = new Map<string, number>();\n const modelRows = (\n [\n ['planning', planningModelConfig, 'Text response'],\n ['insight', insightModelConfig, 'Image understanding'],\n ['default', defaultModelConfig, 'AI locate'],\n ] as const\n ).map(([intent, config, check]) => {\n const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;\n const endpoint = baseURL\n ? displayEndpoint(String(baseURL))\n : 'provider default';\n if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);\n return [intent, config.modelName, check, `[${baseURLs.get(endpoint)}]`];\n });\n io.stdout(\n formatTable(['Model role', 'Model', 'Check', 'Endpoint'], modelRows),\n );\n io.stdout('');\n for (const [endpoint, index] of baseURLs) {\n io.stdout(`[${index}] Base URL: ${endpoint}`);\n }\n io.stdout('\\nRunning 3 checks...\\n');\n\n const result = await deps.verifyModel({\n defaultModelConfig,\n planningModelConfig,\n insightModelConfig,\n });\n\n const checkLabels = {\n text: 'Text response',\n vision: 'Image understanding',\n aiLocate: 'AI locate',\n };\n io.stdout(\n formatTable(\n ['Model role', 'Check', 'Result', 'Requests', 'Time'],\n result.checks.map((check) => [\n check.intent,\n checkLabels[check.name],\n check.passed ? 'PASS' : 'FAIL',\n String(check.requestCount),\n `${check.durationMs} ms`,\n ]),\n ),\n );\n const requestEndpoints = new Map<string, Set<string>>();\n for (const check of result.checks) {\n for (const endpoint of check.endpoints) {\n const display = displayEndpoint(endpoint);\n if (!requestEndpoints.has(display))\n requestEndpoints.set(display, new Set());\n requestEndpoints.get(display)!.add(check.intent);\n }\n if (!check.passed && check.message)\n io.stdout(`\\n${check.intent}: ${check.message}`);\n }\n if (requestEndpoints.size > 0) {\n io.stdout('\\nRequest endpoints:');\n for (const [endpoint, intents] of requestEndpoints) {\n io.stdout(` ${[...intents].join(', ')}: ${endpoint}`);\n }\n }\n io.stdout('');\n const passedChecks = result.checks.filter((check) => check.passed).length;\n io.stdout(\n `Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`,\n );\n\n if (result.passed) {\n io.stdout('✅ Model verify passed.');\n return 0;\n }\n\n io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));\n return 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(\n [\n MODEL_VERIFY_SEPARATOR,\n '❌ Model verify failed with messages:',\n '',\n message,\n MODEL_VERIFY_SEPARATOR,\n ].join('\\n'),\n );\n return 1;\n }\n}\n\nexport async function runModelCommand(\n rawArgs: string[],\n deps?: Partial<ModelCommandDeps>,\n io: ModelCommandIO = {\n stdout: console.log,\n stderr: console.error,\n },\n commandName = 'midscene-test',\n): Promise<number> {\n const [, action, ...restArgs] = rawArgs;\n const mergedDeps = {\n ...getDefaultDeps(io),\n ...deps,\n };\n\n if (!action || action === '--help' || action === '-h') {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n if (action === 'verify') {\n return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);\n }\n\n if (action === 'eval') {\n io.stderr(\n `${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`,\n );\n return 1;\n }\n\n io.stderr(\n `Unknown ${commandName} model command: ${action}\\n\\n${modelCommandUsage(commandName)}`,\n );\n return 1;\n}\n\nexport { loadDotenvConfig, type DotenvLoadOptions } from './dotenv-loader';\n"],"names":["loadDotenvConfig","options","dotEnvConfigFile","join","process","existsSync","dotenv","DEFAULT_OPENAI_BASE_URL","MODEL_VERIFY_SEPARATOR","modelCommandUsage","commandName","assertNoModelVerifyOptions","args","arg","Error","formatTable","headers","rows","widths","header","index","Math","row","formatRow","cell","displayEndpoint","endpoint","url","URL","shellSingleQuote","value","buildChatCompletionsUrl","baseURL","normalizedBaseURL","buildCurlCommand","modelConfig","payload","JSON","buildCurlDedupKey","buildModelVerifyCurlCommands","configs","commandMap","Map","item","key","existing","formatModelVerifyFailureOutput","message","curlCommands","details","curlSection","baseUrlNote","chalk","getDefaultDeps","io","intent","globalModelConfigManager","runConnectivityTest","runModelVerifyCommand","deps","defaultModelConfig","planningModelConfig","insightModelConfig","baseURLs","modelRows","config","check","String","result","checkLabels","requestEndpoints","display","Set","intents","passedChecks","error","runModelCommand","rawArgs","console","action","restArgs","mergedDeps"],"mappings":"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"}
|
|
@@ -21,6 +21,7 @@ const modelCommandUsage = (commandName)=>`Usage:
|
|
|
21
21
|
|
|
22
22
|
Verify model connectivity and Midscene compatibility for default, planning, and insight models.
|
|
23
23
|
Reads .env from the current working directory, overriding shell environment variables.
|
|
24
|
+
Shows endpoints, model names, request counts, and per-check results with duration.
|
|
24
25
|
Returns exit code 0 on success and 1 on failure.
|
|
25
26
|
|
|
26
27
|
Options:
|
|
@@ -29,6 +30,26 @@ Options:
|
|
|
29
30
|
function assertNoModelVerifyOptions(args, commandName) {
|
|
30
31
|
for (const arg of args)throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);
|
|
31
32
|
}
|
|
33
|
+
function formatTable(headers, rows) {
|
|
34
|
+
const widths = headers.map((header, index)=>Math.max(header.length, ...rows.map((row)=>row[index].length)));
|
|
35
|
+
const formatRow = (row)=>row.map((cell, index)=>cell.padEnd(widths[index])).join(' ').trimEnd();
|
|
36
|
+
return [
|
|
37
|
+
formatRow(headers),
|
|
38
|
+
...rows.map(formatRow)
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
41
|
+
function displayEndpoint(endpoint) {
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL(endpoint);
|
|
44
|
+
url.username = '';
|
|
45
|
+
url.password = '';
|
|
46
|
+
url.search = '';
|
|
47
|
+
url.hash = '';
|
|
48
|
+
return url.toString();
|
|
49
|
+
} catch {
|
|
50
|
+
return '(invalid endpoint URL)';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
32
53
|
function shellSingleQuote(value) {
|
|
33
54
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
34
55
|
}
|
|
@@ -122,7 +143,7 @@ async function runModelVerifyCommand(args, deps, io, commandName) {
|
|
|
122
143
|
return 0;
|
|
123
144
|
}
|
|
124
145
|
assertNoModelVerifyOptions(args, commandName);
|
|
125
|
-
io.stdout('Model verify started. This usually
|
|
146
|
+
io.stdout('Model verify started. This usually completes within 30 seconds.');
|
|
126
147
|
deps.loadDotenv();
|
|
127
148
|
io.stdout('');
|
|
128
149
|
const defaultModelConfig = deps.getModelConfig('default');
|
|
@@ -142,11 +163,84 @@ async function runModelVerifyCommand(args, deps, io, commandName) {
|
|
|
142
163
|
modelConfig: insightModelConfig
|
|
143
164
|
}
|
|
144
165
|
]);
|
|
166
|
+
const baseURLs = new Map();
|
|
167
|
+
const modelRows = [
|
|
168
|
+
[
|
|
169
|
+
'planning',
|
|
170
|
+
planningModelConfig,
|
|
171
|
+
'Text response'
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
'insight',
|
|
175
|
+
insightModelConfig,
|
|
176
|
+
'Image understanding'
|
|
177
|
+
],
|
|
178
|
+
[
|
|
179
|
+
'default',
|
|
180
|
+
defaultModelConfig,
|
|
181
|
+
'AI locate'
|
|
182
|
+
]
|
|
183
|
+
].map(([intent, config, check])=>{
|
|
184
|
+
const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;
|
|
185
|
+
const endpoint = baseURL ? displayEndpoint(String(baseURL)) : 'provider default';
|
|
186
|
+
if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);
|
|
187
|
+
return [
|
|
188
|
+
intent,
|
|
189
|
+
config.modelName,
|
|
190
|
+
check,
|
|
191
|
+
`[${baseURLs.get(endpoint)}]`
|
|
192
|
+
];
|
|
193
|
+
});
|
|
194
|
+
io.stdout(formatTable([
|
|
195
|
+
'Model role',
|
|
196
|
+
'Model',
|
|
197
|
+
'Check',
|
|
198
|
+
'Endpoint'
|
|
199
|
+
], modelRows));
|
|
200
|
+
io.stdout('');
|
|
201
|
+
for (const [endpoint, index] of baseURLs)io.stdout(`[${index}] Base URL: ${endpoint}`);
|
|
202
|
+
io.stdout('\nRunning 3 checks...\n');
|
|
145
203
|
const result = await deps.verifyModel({
|
|
146
204
|
defaultModelConfig,
|
|
147
205
|
planningModelConfig,
|
|
148
206
|
insightModelConfig
|
|
149
207
|
});
|
|
208
|
+
const checkLabels = {
|
|
209
|
+
text: 'Text response',
|
|
210
|
+
vision: 'Image understanding',
|
|
211
|
+
aiLocate: 'AI locate'
|
|
212
|
+
};
|
|
213
|
+
io.stdout(formatTable([
|
|
214
|
+
'Model role',
|
|
215
|
+
'Check',
|
|
216
|
+
'Result',
|
|
217
|
+
'Requests',
|
|
218
|
+
'Time'
|
|
219
|
+
], result.checks.map((check)=>[
|
|
220
|
+
check.intent,
|
|
221
|
+
checkLabels[check.name],
|
|
222
|
+
check.passed ? 'PASS' : 'FAIL',
|
|
223
|
+
String(check.requestCount),
|
|
224
|
+
`${check.durationMs} ms`
|
|
225
|
+
])));
|
|
226
|
+
const requestEndpoints = new Map();
|
|
227
|
+
for (const check of result.checks){
|
|
228
|
+
for (const endpoint of check.endpoints){
|
|
229
|
+
const display = displayEndpoint(endpoint);
|
|
230
|
+
if (!requestEndpoints.has(display)) requestEndpoints.set(display, new Set());
|
|
231
|
+
requestEndpoints.get(display).add(check.intent);
|
|
232
|
+
}
|
|
233
|
+
if (!check.passed && check.message) io.stdout(`\n${check.intent}: ${check.message}`);
|
|
234
|
+
}
|
|
235
|
+
if (requestEndpoints.size > 0) {
|
|
236
|
+
io.stdout('\nRequest endpoints:');
|
|
237
|
+
for (const [endpoint, intents] of requestEndpoints)io.stdout(` ${[
|
|
238
|
+
...intents
|
|
239
|
+
].join(', ')}: ${endpoint}`);
|
|
240
|
+
}
|
|
241
|
+
io.stdout('');
|
|
242
|
+
const passedChecks = result.checks.filter((check)=>check.passed).length;
|
|
243
|
+
io.stdout(`Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`);
|
|
150
244
|
if (result.passed) {
|
|
151
245
|
io.stdout('✅ Model verify passed.');
|
|
152
246
|
return 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli/model-command.mjs","sources":["../../../src/cli/dotenv-loader.ts","../../../src/cli/model-command.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n","import { runConnectivityTest } from '@midscene/core';\nimport {\n type IModelConfig,\n type TIntent,\n globalModelConfigManager,\n} from '@midscene/shared/env';\nimport chalk from 'chalk';\nimport { loadDotenvConfig } from './dotenv-loader';\n\nconst DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';\nconst MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';\nconst modelCommandUsage = (commandName: string) => `Usage:\n ${commandName} model verify\n\nVerify model connectivity and Midscene compatibility for default, planning, and insight models.\nReads .env from the current working directory, overriding shell environment variables.\nReturns exit code 0 on success and 1 on failure.\n\nOptions:\n --help, -h Show this help\n`;\n\ninterface ModelCommandIO {\n stdout: (message: string) => void;\n stderr: (message: string) => void;\n}\n\ninterface ModelCommandDeps {\n loadDotenv: () => void;\n getModelConfig: (intent: TIntent) => IModelConfig;\n verifyModel: typeof runConnectivityTest;\n}\n\nexport interface CurlCommandItem {\n intents: TIntent[];\n curl: string;\n usesDefaultBaseURL: boolean;\n}\n\nfunction assertNoModelVerifyOptions(args: string[], commandName: string) {\n for (const arg of args) {\n throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);\n }\n}\n\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildChatCompletionsUrl(baseURL?: string): string {\n const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(\n /\\/+$/,\n '',\n );\n if (normalizedBaseURL.endsWith('/chat/completions')) {\n return normalizedBaseURL;\n }\n return `${normalizedBaseURL}/chat/completions`;\n}\n\nfunction buildCurlCommand(modelConfig: IModelConfig): string {\n const payload = {\n model: modelConfig.modelName,\n messages: [{ role: 'user', content: 'What is 1+1?' }],\n };\n\n return [\n `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\\\`,\n ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\\\`,\n ` -H ${shellSingleQuote('Content-Type: application/json')} \\\\`,\n ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`,\n ].join('\\n');\n}\n\nfunction buildCurlDedupKey(modelConfig: IModelConfig): string {\n return JSON.stringify({\n baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,\n apiKey: modelConfig.openaiApiKey || '',\n modelName: modelConfig.modelName,\n });\n}\n\nexport function buildModelVerifyCurlCommands(\n configs: Array<{ intent: TIntent; modelConfig: IModelConfig }>,\n): CurlCommandItem[] {\n const commandMap = new Map<string, CurlCommandItem>();\n\n for (const item of configs) {\n const key = buildCurlDedupKey(item.modelConfig);\n const existing = commandMap.get(key);\n if (existing) {\n existing.intents.push(item.intent);\n continue;\n }\n commandMap.set(key, {\n intents: [item.intent],\n curl: buildCurlCommand(item.modelConfig),\n usesDefaultBaseURL: !item.modelConfig.openaiBaseURL,\n });\n }\n\n return [...commandMap.values()];\n}\n\nfunction formatModelVerifyFailureOutput(\n message: string | undefined,\n curlCommands: CurlCommandItem[],\n): string {\n const details = message?.trim() || 'No failure details were generated.';\n const curlSection = curlCommands\n .map((item) => {\n const baseUrlNote = item.usesDefaultBaseURL\n ? ' (base URL not configured; using OpenAI SDK default)'\n : '';\n return chalk.gray(\n `# ${item.intents.join(', ')}${baseUrlNote}\\n${item.curl}`,\n );\n })\n .join('\\n\\n');\n\n return [\n chalk.red.bold('❌ Model verify failed with messages:'),\n MODEL_VERIFY_SEPARATOR,\n '',\n details,\n '',\n MODEL_VERIFY_SEPARATOR,\n 'Generated curl requests for basic API connectivity:',\n 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',\n 'These commands contain your API key. Do not share them publicly.',\n '',\n curlSection,\n ].join('\\n');\n}\n\nfunction getDefaultDeps(io: ModelCommandIO): ModelCommandDeps {\n return {\n loadDotenv: () => {\n loadDotenvConfig({\n dotenvDebug: true,\n dotenvOverride: true,\n log: io.stdout,\n });\n },\n getModelConfig: (intent) => globalModelConfigManager.getModelConfig(intent),\n verifyModel: runConnectivityTest,\n };\n}\n\nasync function runModelVerifyCommand(\n args: string[],\n deps: ModelCommandDeps,\n io: ModelCommandIO,\n commandName: string,\n): Promise<number> {\n try {\n if (args.includes('--help') || args.includes('-h')) {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n assertNoModelVerifyOptions(args, commandName);\n io.stdout('Model verify started. This usually takes about 5 seconds.\\n');\n deps.loadDotenv();\n io.stdout('');\n\n const defaultModelConfig = deps.getModelConfig('default');\n const planningModelConfig = deps.getModelConfig('planning');\n const insightModelConfig = deps.getModelConfig('insight');\n const curlCommands = buildModelVerifyCurlCommands([\n { intent: 'default', modelConfig: defaultModelConfig },\n { intent: 'planning', modelConfig: planningModelConfig },\n { intent: 'insight', modelConfig: insightModelConfig },\n ]);\n\n const result = await deps.verifyModel({\n defaultModelConfig,\n planningModelConfig,\n insightModelConfig,\n });\n\n if (result.passed) {\n io.stdout('✅ Model verify passed.');\n return 0;\n }\n\n io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));\n return 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(\n [\n MODEL_VERIFY_SEPARATOR,\n '❌ Model verify failed with messages:',\n '',\n message,\n MODEL_VERIFY_SEPARATOR,\n ].join('\\n'),\n );\n return 1;\n }\n}\n\nexport async function runModelCommand(\n rawArgs: string[],\n deps?: Partial<ModelCommandDeps>,\n io: ModelCommandIO = {\n stdout: console.log,\n stderr: console.error,\n },\n commandName = 'midscene-test',\n): Promise<number> {\n const [, action, ...restArgs] = rawArgs;\n const mergedDeps = {\n ...getDefaultDeps(io),\n ...deps,\n };\n\n if (!action || action === '--help' || action === '-h') {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n if (action === 'verify') {\n return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);\n }\n\n if (action === 'eval') {\n io.stderr(\n `${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`,\n );\n return 1;\n }\n\n io.stderr(\n `Unknown ${commandName} model command: ${action}\\n\\n${modelCommandUsage(commandName)}`,\n );\n return 1;\n}\n\nexport { loadDotenvConfig, type DotenvLoadOptions } from './dotenv-loader';\n"],"names":["loadDotenvConfig","options","dotEnvConfigFile","join","process","existsSync","dotenv","DEFAULT_OPENAI_BASE_URL","MODEL_VERIFY_SEPARATOR","modelCommandUsage","commandName","assertNoModelVerifyOptions","args","arg","Error","shellSingleQuote","value","buildChatCompletionsUrl","baseURL","normalizedBaseURL","buildCurlCommand","modelConfig","payload","JSON","buildCurlDedupKey","buildModelVerifyCurlCommands","configs","commandMap","Map","item","key","existing","formatModelVerifyFailureOutput","message","curlCommands","details","curlSection","baseUrlNote","chalk","getDefaultDeps","io","intent","globalModelConfigManager","runConnectivityTest","runModelVerifyCommand","deps","defaultModelConfig","planningModelConfig","insightModelConfig","result","error","String","runModelCommand","rawArgs","console","action","restArgs","mergedDeps"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli/model-command.mjs","sources":["../../../src/cli/dotenv-loader.ts","../../../src/cli/model-command.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport dotenv from 'dotenv';\n\nexport interface DotenvLoadOptions {\n dotenvOverride?: boolean;\n dotenvDebug?: boolean;\n cwd?: string;\n log?: (message: string) => void;\n}\n\nexport function loadDotenvConfig(options: DotenvLoadOptions = {}) {\n const dotEnvConfigFile = join(options.cwd ?? process.cwd(), '.env');\n if (!existsSync(dotEnvConfigFile)) {\n return;\n }\n\n options.log?.(` Env file: ${dotEnvConfigFile}`);\n dotenv.config({\n path: dotEnvConfigFile,\n debug: options.dotenvDebug,\n override: options.dotenvOverride,\n });\n}\n","import { runConnectivityTest } from '@midscene/core';\nimport {\n type IModelConfig,\n type TIntent,\n globalModelConfigManager,\n} from '@midscene/shared/env';\nimport chalk from 'chalk';\nimport { loadDotenvConfig } from './dotenv-loader';\n\nconst DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';\nconst MODEL_VERIFY_SEPARATOR = '────────────────────────────────────────';\nconst modelCommandUsage = (commandName: string) => `Usage:\n ${commandName} model verify\n\nVerify model connectivity and Midscene compatibility for default, planning, and insight models.\nReads .env from the current working directory, overriding shell environment variables.\nShows endpoints, model names, request counts, and per-check results with duration.\nReturns exit code 0 on success and 1 on failure.\n\nOptions:\n --help, -h Show this help\n`;\n\ninterface ModelCommandIO {\n stdout: (message: string) => void;\n stderr: (message: string) => void;\n}\n\ninterface ModelCommandDeps {\n loadDotenv: () => void;\n getModelConfig: (intent: TIntent) => IModelConfig;\n verifyModel: typeof runConnectivityTest;\n}\n\nexport interface CurlCommandItem {\n intents: TIntent[];\n curl: string;\n usesDefaultBaseURL: boolean;\n}\n\nfunction assertNoModelVerifyOptions(args: string[], commandName: string) {\n for (const arg of args) {\n throw new Error(`Unknown option for ${commandName} model verify: ${arg}`);\n }\n}\n\nfunction formatTable(headers: string[], rows: string[][]): string {\n const widths = headers.map((header, index) =>\n Math.max(header.length, ...rows.map((row) => row[index].length)),\n );\n const formatRow = (row: string[]) =>\n row\n .map((cell, index) => cell.padEnd(widths[index]))\n .join(' ')\n .trimEnd();\n return [formatRow(headers), ...rows.map(formatRow)].join('\\n');\n}\n\nfunction displayEndpoint(endpoint: string): string {\n try {\n const url = new URL(endpoint);\n url.username = '';\n url.password = '';\n url.search = '';\n url.hash = '';\n return url.toString();\n } catch {\n return '(invalid endpoint URL)';\n }\n}\n\nfunction shellSingleQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildChatCompletionsUrl(baseURL?: string): string {\n const normalizedBaseURL = (baseURL || DEFAULT_OPENAI_BASE_URL).replace(\n /\\/+$/,\n '',\n );\n if (normalizedBaseURL.endsWith('/chat/completions')) {\n return normalizedBaseURL;\n }\n return `${normalizedBaseURL}/chat/completions`;\n}\n\nfunction buildCurlCommand(modelConfig: IModelConfig): string {\n const payload = {\n model: modelConfig.modelName,\n messages: [{ role: 'user', content: 'What is 1+1?' }],\n };\n\n return [\n `curl -X POST ${shellSingleQuote(buildChatCompletionsUrl(modelConfig.openaiBaseURL))} \\\\`,\n ` -H ${shellSingleQuote(`Authorization: Bearer ${modelConfig.openaiApiKey || ''}`)} \\\\`,\n ` -H ${shellSingleQuote('Content-Type: application/json')} \\\\`,\n ` -d ${shellSingleQuote(JSON.stringify(payload, null, 2))}`,\n ].join('\\n');\n}\n\nfunction buildCurlDedupKey(modelConfig: IModelConfig): string {\n return JSON.stringify({\n baseURL: modelConfig.openaiBaseURL || DEFAULT_OPENAI_BASE_URL,\n apiKey: modelConfig.openaiApiKey || '',\n modelName: modelConfig.modelName,\n });\n}\n\nexport function buildModelVerifyCurlCommands(\n configs: Array<{ intent: TIntent; modelConfig: IModelConfig }>,\n): CurlCommandItem[] {\n const commandMap = new Map<string, CurlCommandItem>();\n\n for (const item of configs) {\n const key = buildCurlDedupKey(item.modelConfig);\n const existing = commandMap.get(key);\n if (existing) {\n existing.intents.push(item.intent);\n continue;\n }\n commandMap.set(key, {\n intents: [item.intent],\n curl: buildCurlCommand(item.modelConfig),\n usesDefaultBaseURL: !item.modelConfig.openaiBaseURL,\n });\n }\n\n return [...commandMap.values()];\n}\n\nfunction formatModelVerifyFailureOutput(\n message: string | undefined,\n curlCommands: CurlCommandItem[],\n): string {\n const details = message?.trim() || 'No failure details were generated.';\n const curlSection = curlCommands\n .map((item) => {\n const baseUrlNote = item.usesDefaultBaseURL\n ? ' (base URL not configured; using OpenAI SDK default)'\n : '';\n return chalk.gray(\n `# ${item.intents.join(', ')}${baseUrlNote}\\n${item.curl}`,\n );\n })\n .join('\\n\\n');\n\n return [\n chalk.red.bold('❌ Model verify failed with messages:'),\n MODEL_VERIFY_SEPARATOR,\n '',\n details,\n '',\n MODEL_VERIFY_SEPARATOR,\n 'Generated curl requests for basic API connectivity:',\n 'If the error is a basic connectivity issue, use these requests to test the base URL, API key, and model name directly.',\n 'These commands contain your API key. Do not share them publicly.',\n '',\n curlSection,\n ].join('\\n');\n}\n\nfunction getDefaultDeps(io: ModelCommandIO): ModelCommandDeps {\n return {\n loadDotenv: () => {\n loadDotenvConfig({\n dotenvDebug: true,\n dotenvOverride: true,\n log: io.stdout,\n });\n },\n getModelConfig: (intent) => globalModelConfigManager.getModelConfig(intent),\n verifyModel: runConnectivityTest,\n };\n}\n\nasync function runModelVerifyCommand(\n args: string[],\n deps: ModelCommandDeps,\n io: ModelCommandIO,\n commandName: string,\n): Promise<number> {\n try {\n if (args.includes('--help') || args.includes('-h')) {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n assertNoModelVerifyOptions(args, commandName);\n io.stdout(\n 'Model verify started. This usually completes within 30 seconds.',\n );\n deps.loadDotenv();\n io.stdout('');\n\n const defaultModelConfig = deps.getModelConfig('default');\n const planningModelConfig = deps.getModelConfig('planning');\n const insightModelConfig = deps.getModelConfig('insight');\n const curlCommands = buildModelVerifyCurlCommands([\n { intent: 'default', modelConfig: defaultModelConfig },\n { intent: 'planning', modelConfig: planningModelConfig },\n { intent: 'insight', modelConfig: insightModelConfig },\n ]);\n\n const baseURLs = new Map<string, number>();\n const modelRows = (\n [\n ['planning', planningModelConfig, 'Text response'],\n ['insight', insightModelConfig, 'Image understanding'],\n ['default', defaultModelConfig, 'AI locate'],\n ] as const\n ).map(([intent, config, check]) => {\n const baseURL = config.openaiExtraConfig?.baseURL ?? config.openaiBaseURL;\n const endpoint = baseURL\n ? displayEndpoint(String(baseURL))\n : 'provider default';\n if (!baseURLs.has(endpoint)) baseURLs.set(endpoint, baseURLs.size + 1);\n return [intent, config.modelName, check, `[${baseURLs.get(endpoint)}]`];\n });\n io.stdout(\n formatTable(['Model role', 'Model', 'Check', 'Endpoint'], modelRows),\n );\n io.stdout('');\n for (const [endpoint, index] of baseURLs) {\n io.stdout(`[${index}] Base URL: ${endpoint}`);\n }\n io.stdout('\\nRunning 3 checks...\\n');\n\n const result = await deps.verifyModel({\n defaultModelConfig,\n planningModelConfig,\n insightModelConfig,\n });\n\n const checkLabels = {\n text: 'Text response',\n vision: 'Image understanding',\n aiLocate: 'AI locate',\n };\n io.stdout(\n formatTable(\n ['Model role', 'Check', 'Result', 'Requests', 'Time'],\n result.checks.map((check) => [\n check.intent,\n checkLabels[check.name],\n check.passed ? 'PASS' : 'FAIL',\n String(check.requestCount),\n `${check.durationMs} ms`,\n ]),\n ),\n );\n const requestEndpoints = new Map<string, Set<string>>();\n for (const check of result.checks) {\n for (const endpoint of check.endpoints) {\n const display = displayEndpoint(endpoint);\n if (!requestEndpoints.has(display))\n requestEndpoints.set(display, new Set());\n requestEndpoints.get(display)!.add(check.intent);\n }\n if (!check.passed && check.message)\n io.stdout(`\\n${check.intent}: ${check.message}`);\n }\n if (requestEndpoints.size > 0) {\n io.stdout('\\nRequest endpoints:');\n for (const [endpoint, intents] of requestEndpoints) {\n io.stdout(` ${[...intents].join(', ')}: ${endpoint}`);\n }\n }\n io.stdout('');\n const passedChecks = result.checks.filter((check) => check.passed).length;\n io.stdout(\n `Requests sent: ${result.requestCount}. Checks: ${passedChecks} passed, ${result.checks.length - passedChecks} failed.`,\n );\n\n if (result.passed) {\n io.stdout('✅ Model verify passed.');\n return 0;\n }\n\n io.stderr(formatModelVerifyFailureOutput(result.message, curlCommands));\n return 1;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(\n [\n MODEL_VERIFY_SEPARATOR,\n '❌ Model verify failed with messages:',\n '',\n message,\n MODEL_VERIFY_SEPARATOR,\n ].join('\\n'),\n );\n return 1;\n }\n}\n\nexport async function runModelCommand(\n rawArgs: string[],\n deps?: Partial<ModelCommandDeps>,\n io: ModelCommandIO = {\n stdout: console.log,\n stderr: console.error,\n },\n commandName = 'midscene-test',\n): Promise<number> {\n const [, action, ...restArgs] = rawArgs;\n const mergedDeps = {\n ...getDefaultDeps(io),\n ...deps,\n };\n\n if (!action || action === '--help' || action === '-h') {\n io.stdout(modelCommandUsage(commandName));\n return 0;\n }\n\n if (action === 'verify') {\n return runModelVerifyCommand(restArgs, mergedDeps, io, commandName);\n }\n\n if (action === 'eval') {\n io.stderr(\n `${commandName} model eval is not implemented yet. It is reserved for future model evaluation suites.`,\n );\n return 1;\n }\n\n io.stderr(\n `Unknown ${commandName} model command: ${action}\\n\\n${modelCommandUsage(commandName)}`,\n );\n return 1;\n}\n\nexport { loadDotenvConfig, type DotenvLoadOptions } from './dotenv-loader';\n"],"names":["loadDotenvConfig","options","dotEnvConfigFile","join","process","existsSync","dotenv","DEFAULT_OPENAI_BASE_URL","MODEL_VERIFY_SEPARATOR","modelCommandUsage","commandName","assertNoModelVerifyOptions","args","arg","Error","formatTable","headers","rows","widths","header","index","Math","row","formatRow","cell","displayEndpoint","endpoint","url","URL","shellSingleQuote","value","buildChatCompletionsUrl","baseURL","normalizedBaseURL","buildCurlCommand","modelConfig","payload","JSON","buildCurlDedupKey","buildModelVerifyCurlCommands","configs","commandMap","Map","item","key","existing","formatModelVerifyFailureOutput","message","curlCommands","details","curlSection","baseUrlNote","chalk","getDefaultDeps","io","intent","globalModelConfigManager","runConnectivityTest","runModelVerifyCommand","deps","defaultModelConfig","planningModelConfig","insightModelConfig","baseURLs","modelRows","config","check","String","result","checkLabels","requestEndpoints","display","Set","intents","passedChecks","error","runModelCommand","rawArgs","console","action","restArgs","mergedDeps"],"mappings":"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"}
|