@untestutils/cli 0.5.0 → 0.5.1

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/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@untestutils/cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "CLI for untestutils: init, convert, ai, doctor, and monorepo commands",
5
- "license": "MIT",
6
5
  "homepage": "https://s00d.github.io/untestutils/",
6
+ "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/s00d/untestutils.git",
@@ -40,9 +40,9 @@
40
40
  "pathe": "^2.0.3",
41
41
  "tinyglobby": "^0.2.17",
42
42
  "tsx": "^4.23.13",
43
- "@untestutils/ai": "0.5.0",
44
- "@untestutils/core": "0.5.0",
45
- "@untestutils/perf": "0.5.0"
43
+ "@untestutils/ai": "0.5.1",
44
+ "@untestutils/core": "0.5.1",
45
+ "@untestutils/perf": "0.5.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^26.6.1",
@@ -1,51 +1,53 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { consola } from 'consola';
3
3
  import { resolve } from 'pathe';
4
4
  import { aiTest, aiTestFromFile } from '@untestutils/ai';
5
5
  import { writeText } from '../utils/fs';
6
6
 
7
- export const aiCommand = defineCommand({
7
+ const aiArgs = {
8
+ prompt: {
9
+ type: 'string',
10
+ description: 'Scenario text',
11
+ alias: 'p',
12
+ },
13
+ file: {
14
+ type: 'string',
15
+ description: 'Markdown with frontmatter (id/root/recipes/focus)',
16
+ alias: 'f',
17
+ },
18
+ id: {
19
+ type: 'string',
20
+ description: 'Generation id',
21
+ default: 'cli',
22
+ },
23
+ root: {
24
+ type: 'string',
25
+ description: 'Project root',
26
+ default: '.',
27
+ },
28
+ recipes: {
29
+ type: 'string',
30
+ description: 'Comma-separated recipe ids',
31
+ default: '',
32
+ },
33
+ focus: {
34
+ type: 'string',
35
+ description: 'Comma-separated focus file hints',
36
+ default: '',
37
+ },
38
+ out: {
39
+ type: 'string',
40
+ description: 'Copy generated file to this path',
41
+ },
42
+ } as const;
43
+
44
+ export const aiCommand: CommandDef<typeof aiArgs> = defineCommand({
8
45
  meta: {
9
46
  name: 'ai',
10
47
  alias: ['generate'],
11
48
  description: 'Generate an e2e test from a prompt or markdown file',
12
49
  },
13
- args: {
14
- prompt: {
15
- type: 'string',
16
- description: 'Scenario text',
17
- alias: 'p',
18
- },
19
- file: {
20
- type: 'string',
21
- description: 'Markdown with frontmatter (id/root/recipes/focus)',
22
- alias: 'f',
23
- },
24
- id: {
25
- type: 'string',
26
- description: 'Generation id',
27
- default: 'cli',
28
- },
29
- root: {
30
- type: 'string',
31
- description: 'Project root',
32
- default: '.',
33
- },
34
- recipes: {
35
- type: 'string',
36
- description: 'Comma-separated recipe ids',
37
- default: '',
38
- },
39
- focus: {
40
- type: 'string',
41
- description: 'Comma-separated focus file hints',
42
- default: '',
43
- },
44
- out: {
45
- type: 'string',
46
- description: 'Copy generated file to this path',
47
- },
48
- },
50
+ args: aiArgs,
49
51
  async run({ args }) {
50
52
  const root = resolve(String(args.root));
51
53
  const recipes = String(args.recipes || '')
@@ -1,33 +1,35 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { consola } from 'consola';
3
3
  import { resolve, dirname, basename, join } from 'pathe';
4
4
  import { convertTestFile } from '@untestutils/ai';
5
5
  import { writeText, pathExists } from '../utils/fs';
6
6
  import { copyFile } from 'node:fs/promises';
7
7
 
8
- export const convertCommand = defineCommand({
8
+ const convertArgs = {
9
+ path: {
10
+ type: 'positional',
11
+ description: 'Test file path',
12
+ required: true,
13
+ },
14
+ 'in-place': {
15
+ type: 'boolean',
16
+ description: 'Overwrite source (writes .bak backup)',
17
+ default: false,
18
+ alias: 'i',
19
+ },
20
+ root: {
21
+ type: 'string',
22
+ description: 'Project root for AI tools',
23
+ default: '.',
24
+ },
25
+ } as const;
26
+
27
+ export const convertCommand: CommandDef<typeof convertArgs> = defineCommand({
9
28
  meta: {
10
29
  name: 'convert',
11
30
  description: 'Convert existing tests to the untestutils API via AI',
12
31
  },
13
- args: {
14
- path: {
15
- type: 'positional',
16
- description: 'Test file path',
17
- required: true,
18
- },
19
- 'in-place': {
20
- type: 'boolean',
21
- description: 'Overwrite source (writes .bak backup)',
22
- default: false,
23
- alias: 'i',
24
- },
25
- root: {
26
- type: 'string',
27
- description: 'Project root for AI tools',
28
- default: '.',
29
- },
30
- },
32
+ args: convertArgs,
31
33
  async run({ args }) {
32
34
  const file = resolve(String(args.path));
33
35
  if (!(await pathExists(file))) {
@@ -1,50 +1,52 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { consola } from 'consola';
3
3
  import { resolve } from 'pathe';
4
4
  import { coverMissingTests } from '@untestutils/ai';
5
5
 
6
- export const coverCommand = defineCommand({
6
+ const coverArgs = {
7
+ root: {
8
+ type: 'string',
9
+ description: 'Project root',
10
+ default: '.',
11
+ },
12
+ recipes: {
13
+ type: 'string',
14
+ description: 'Comma-separated recipe ids',
15
+ default: '',
16
+ },
17
+ focus: {
18
+ type: 'string',
19
+ description: 'Comma-separated focus paths',
20
+ default: '',
21
+ },
22
+ 'base-url': {
23
+ type: 'string',
24
+ description: 'Live app URL for browser_snapshot tools',
25
+ },
26
+ browser: {
27
+ type: 'boolean',
28
+ description: 'Force browser tools',
29
+ default: false,
30
+ },
31
+ 'out-dir': {
32
+ type: 'string',
33
+ description: 'Directory for new tests',
34
+ default: 'tests/e2e',
35
+ },
36
+ write: {
37
+ type: 'boolean',
38
+ description: 'Write files into the project',
39
+ default: true,
40
+ },
41
+ } as const;
42
+
43
+ export const coverCommand: CommandDef<typeof coverArgs> = defineCommand({
7
44
  meta: {
8
45
  name: 'cover',
9
46
  alias: ['write-tests'],
10
47
  description: 'Write missing e2e tests via AI (explores recipes/pages; optional browser)',
11
48
  },
12
- args: {
13
- root: {
14
- type: 'string',
15
- description: 'Project root',
16
- default: '.',
17
- },
18
- recipes: {
19
- type: 'string',
20
- description: 'Comma-separated recipe ids',
21
- default: '',
22
- },
23
- focus: {
24
- type: 'string',
25
- description: 'Comma-separated focus paths',
26
- default: '',
27
- },
28
- 'base-url': {
29
- type: 'string',
30
- description: 'Live app URL for browser_snapshot tools',
31
- },
32
- browser: {
33
- type: 'boolean',
34
- description: 'Force browser tools',
35
- default: false,
36
- },
37
- 'out-dir': {
38
- type: 'string',
39
- description: 'Directory for new tests',
40
- default: 'tests/e2e',
41
- },
42
- write: {
43
- type: 'boolean',
44
- description: 'Write files into the project',
45
- default: true,
46
- },
47
- },
49
+ args: coverArgs,
48
50
  async run({ args }) {
49
51
  const recipes = String(args.recipes || '')
50
52
  .split(',')
@@ -1,4 +1,4 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { consola } from 'consola';
3
3
  import { existsSync } from 'node:fs';
4
4
  import { resolve, join } from 'pathe';
@@ -17,18 +17,20 @@ function canResolve(id: string, cwd: string): boolean {
17
17
  }
18
18
  }
19
19
 
20
- export const doctorCommand = defineCommand({
20
+ const doctorArgs = {
21
+ cwd: {
22
+ type: 'string',
23
+ description: 'Project directory',
24
+ default: '.',
25
+ },
26
+ } as const;
27
+
28
+ export const doctorCommand: CommandDef<typeof doctorArgs> = defineCommand({
21
29
  meta: {
22
30
  name: 'doctor',
23
31
  description: 'Check untestutils environment and configs',
24
32
  },
25
- args: {
26
- cwd: {
27
- type: 'string',
28
- description: 'Project directory',
29
- default: '.',
30
- },
31
- },
33
+ args: doctorArgs,
32
34
  async run({ args }) {
33
35
  const cwd = resolve(String(args.cwd));
34
36
  const checks: Check[] = [];
@@ -1,4 +1,4 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { consola } from 'consola';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { resolve } from 'pathe';
@@ -6,55 +6,57 @@ import { execSync } from 'node:child_process';
6
6
  import { fixBrokenTests } from '@untestutils/ai';
7
7
  import { pathExists, writeText } from '../utils/fs';
8
8
 
9
- export const fixCommand = defineCommand({
9
+ const fixArgs = {
10
+ path: {
11
+ type: 'positional',
12
+ description: 'Failing test file',
13
+ required: true,
14
+ },
15
+ log: {
16
+ type: 'string',
17
+ description: 'Path to failure log file (or use --run)',
18
+ alias: 'l',
19
+ },
20
+ run: {
21
+ type: 'string',
22
+ description: 'Shell command that fails (stdout/stderr captured as log)',
23
+ },
24
+ root: {
25
+ type: 'string',
26
+ description: 'Project root',
27
+ default: '.',
28
+ },
29
+ recipes: {
30
+ type: 'string',
31
+ description: 'Comma-separated recipe ids',
32
+ default: '',
33
+ },
34
+ 'base-url': {
35
+ type: 'string',
36
+ description: 'App base URL for Playwright page tools',
37
+ },
38
+ browser: {
39
+ type: 'boolean',
40
+ description: 'Enable browser tools even without --base-url',
41
+ default: false,
42
+ },
43
+ write: {
44
+ type: 'boolean',
45
+ description: 'Overwrite the failing file in place',
46
+ default: true,
47
+ },
48
+ out: {
49
+ type: 'string',
50
+ description: 'Write fixed source to this path instead',
51
+ },
52
+ } as const;
53
+
54
+ export const fixCommand: CommandDef<typeof fixArgs> = defineCommand({
10
55
  meta: {
11
56
  name: 'fix',
12
57
  description: 'Fix a broken untestutils test via AI (failure log + optional browser tools)',
13
58
  },
14
- args: {
15
- path: {
16
- type: 'positional',
17
- description: 'Failing test file',
18
- required: true,
19
- },
20
- log: {
21
- type: 'string',
22
- description: 'Path to failure log file (or use --run)',
23
- alias: 'l',
24
- },
25
- run: {
26
- type: 'string',
27
- description: 'Shell command that fails (stdout/stderr captured as log)',
28
- },
29
- root: {
30
- type: 'string',
31
- description: 'Project root',
32
- default: '.',
33
- },
34
- recipes: {
35
- type: 'string',
36
- description: 'Comma-separated recipe ids',
37
- default: '',
38
- },
39
- 'base-url': {
40
- type: 'string',
41
- description: 'App base URL for Playwright page tools',
42
- },
43
- browser: {
44
- type: 'boolean',
45
- description: 'Enable browser tools even without --base-url',
46
- default: false,
47
- },
48
- write: {
49
- type: 'boolean',
50
- description: 'Overwrite the failing file in place',
51
- default: true,
52
- },
53
- out: {
54
- type: 'string',
55
- description: 'Write fixed source to this path instead',
56
- },
57
- },
59
+ args: fixArgs,
58
60
  async run({ args }) {
59
61
  const file = resolve(String(args.path));
60
62
  if (!(await pathExists(file))) {
@@ -1,3 +1,4 @@
1
+ import type { SubCommandsDef } from 'citty';
1
2
  import { initCommand } from './init';
2
3
  import { convertCommand } from './convert';
3
4
  import { aiCommand } from './ai';
@@ -6,7 +7,7 @@ import { fixCommand } from './fix';
6
7
  import { coverCommand } from './cover';
7
8
  import { perfCommand } from './perf';
8
9
 
9
- export const commands = {
10
+ export const commands: SubCommandsDef = {
10
11
  init: initCommand,
11
12
  convert: convertCommand,
12
13
  ai: aiCommand,
@@ -1,4 +1,4 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { consola } from 'consola';
3
3
  import { addDependency } from 'nypm';
4
4
  import { resolve } from 'pathe';
@@ -6,34 +6,36 @@ import { applyPreset, peersForPreset, type InitPreset } from '../utils/templates
6
6
 
7
7
  const PRESETS: InitPreset[] = ['vitest', 'playwright', 'nuxt', 'full'];
8
8
 
9
- export const initCommand = defineCommand({
9
+ const initArgs = {
10
+ preset: {
11
+ type: 'string',
12
+ description: 'Preset: vitest | playwright | nuxt | full',
13
+ default: 'vitest',
14
+ alias: 'p',
15
+ },
16
+ cwd: {
17
+ type: 'string',
18
+ description: 'Project directory',
19
+ default: '.',
20
+ },
21
+ force: {
22
+ type: 'boolean',
23
+ description: 'Overwrite existing files',
24
+ default: false,
25
+ },
26
+ install: {
27
+ type: 'boolean',
28
+ description: 'Install peer dependencies via nypm',
29
+ default: true,
30
+ },
31
+ } as const;
32
+
33
+ export const initCommand: CommandDef<typeof initArgs> = defineCommand({
10
34
  meta: {
11
35
  name: 'init',
12
36
  description: 'Add base untestutils configs and example tests to a project',
13
37
  },
14
- args: {
15
- preset: {
16
- type: 'string',
17
- description: 'Preset: vitest | playwright | nuxt | full',
18
- default: 'vitest',
19
- alias: 'p',
20
- },
21
- cwd: {
22
- type: 'string',
23
- description: 'Project directory',
24
- default: '.',
25
- },
26
- force: {
27
- type: 'boolean',
28
- description: 'Overwrite existing files',
29
- default: false,
30
- },
31
- install: {
32
- type: 'boolean',
33
- description: 'Install peer dependencies via nypm',
34
- default: true,
35
- },
36
- },
38
+ args: initArgs,
37
39
  async run({ args }) {
38
40
  const preset = String(args.preset) as InitPreset;
39
41
  if (!PRESETS.includes(preset)) {
@@ -1,9 +1,35 @@
1
- import { defineCommand } from 'citty';
1
+ import { defineCommand, type CommandDef } from 'citty';
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { resolve } from 'pathe';
4
4
  import { consola } from 'consola';
5
5
 
6
- export const perfCommand = defineCommand({
6
+ const perfArgs = {
7
+ config: {
8
+ type: 'string',
9
+ required: true,
10
+ description: 'Path to perf.config.ts exporting definePerfSuite(...) or default suite',
11
+ },
12
+ only: {
13
+ type: 'string',
14
+ description: 'Target id(s), comma-separated',
15
+ },
16
+ runs: {
17
+ type: 'string',
18
+ description: 'Override consecutive runs',
19
+ },
20
+ skipLoad: {
21
+ type: 'boolean',
22
+ default: false,
23
+ description: 'Measure builds only (skip autocannon/artillery)',
24
+ },
25
+ json: {
26
+ type: 'boolean',
27
+ default: false,
28
+ description: 'Also write JSON report under artifactsDir',
29
+ },
30
+ } as const;
31
+
32
+ export const perfCommand: CommandDef<typeof perfArgs> = defineCommand({
7
33
  meta: {
8
34
  name: 'perf',
9
35
  description: [
@@ -15,31 +41,7 @@ export const perfCommand = defineCommand({
15
41
  ' untestutils perf --config ./perf.config.ts --runs 3 --json',
16
42
  ].join('\n'),
17
43
  },
18
- args: {
19
- config: {
20
- type: 'string',
21
- required: true,
22
- description: 'Path to perf.config.ts exporting definePerfSuite(...) or default suite',
23
- },
24
- only: {
25
- type: 'string',
26
- description: 'Target id(s), comma-separated',
27
- },
28
- runs: {
29
- type: 'string',
30
- description: 'Override consecutive runs',
31
- },
32
- skipLoad: {
33
- type: 'boolean',
34
- default: false,
35
- description: 'Measure builds only (skip autocannon/artillery)',
36
- },
37
- json: {
38
- type: 'boolean',
39
- default: false,
40
- description: 'Also write JSON report under artifactsDir',
41
- },
42
- },
44
+ args: perfArgs,
43
45
  async run({ args }) {
44
46
  const configPath = resolve(args.config);
45
47
  const mod = (await import(pathToFileURL(configPath).href)) as {
package/src/main.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
- import { defineCommand } from 'citty';
4
+ import { defineCommand, type CommandDef } from 'citty';
5
5
  import { commands } from './commands';
6
6
 
7
7
  function readOwnVersion(): string {
@@ -27,7 +27,7 @@ function readOwnVersion(): string {
27
27
  return '0.0.0';
28
28
  }
29
29
 
30
- export const main = defineCommand({
30
+ export const main: CommandDef = defineCommand({
31
31
  meta: {
32
32
  name: 'untestutils',
33
33
  version: readOwnVersion(),
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
4
 
5
5
  /** Untestutils monorepo root when the CLI runs inside this repository. */
6
- export function findMonorepoRoot(start = process.cwd()): string | null {
6
+ export function findMonorepoRoot(start: string = process.cwd()): string | null {
7
7
  let dir = start;
8
8
  for (let i = 0; i < 12; i++) {
9
9
  if (