@titannio/webtoolkit-cli 1.3.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +639 -0
  3. package/dist/bin.d.ts +2 -0
  4. package/dist/bin.js +268 -0
  5. package/dist/bundle-audit.d.ts +7 -0
  6. package/dist/bundle-audit.js +111 -0
  7. package/dist/cleaner.d.ts +15 -0
  8. package/dist/cleaner.js +359 -0
  9. package/dist/config-reference.d.ts +8 -0
  10. package/dist/config-reference.js +805 -0
  11. package/dist/config.d.ts +306 -0
  12. package/dist/config.js +173 -0
  13. package/dist/dev-grid.d.ts +7 -0
  14. package/dist/dev-grid.js +181 -0
  15. package/dist/dev-watch.d.ts +13 -0
  16. package/dist/dev-watch.js +184 -0
  17. package/dist/environment.d.ts +10 -0
  18. package/dist/environment.js +172 -0
  19. package/dist/guard-registry.d.ts +1 -0
  20. package/dist/guard-registry.js +17 -0
  21. package/dist/guard-runner.d.ts +4 -0
  22. package/dist/guard-runner.js +36 -0
  23. package/dist/guards/any-guard.d.ts +16 -0
  24. package/dist/guards/any-guard.js +121 -0
  25. package/dist/guards/assert-no-tests-in-dist.d.ts +1 -0
  26. package/dist/guards/assert-no-tests-in-dist.js +56 -0
  27. package/dist/guards/check-mojibake.d.ts +52 -0
  28. package/dist/guards/check-mojibake.js +378 -0
  29. package/dist/guards/code-pattern-guard.d.ts +71 -0
  30. package/dist/guards/code-pattern-guard.js +654 -0
  31. package/dist/guards/dal-service-repository-check.d.ts +13 -0
  32. package/dist/guards/dal-service-repository-check.js +264 -0
  33. package/dist/guards/dependency-cruiser-guard.d.ts +14 -0
  34. package/dist/guards/dependency-cruiser-guard.js +69 -0
  35. package/dist/guards/documentation-guard.d.ts +3 -0
  36. package/dist/guards/documentation-guard.js +370 -0
  37. package/dist/guards/guard-config.d.ts +19 -0
  38. package/dist/guards/guard-config.js +87 -0
  39. package/dist/guards/internal-link-guard.d.ts +12 -0
  40. package/dist/guards/internal-link-guard.js +272 -0
  41. package/dist/guards/package-surface-guard.d.ts +37 -0
  42. package/dist/guards/package-surface-guard.js +234 -0
  43. package/dist/guards/pnpm-workspace-config.d.ts +2 -0
  44. package/dist/guards/pnpm-workspace-config.js +40 -0
  45. package/dist/guards/rebuild-preflight.d.ts +29 -0
  46. package/dist/guards/rebuild-preflight.js +137 -0
  47. package/dist/guards/repository-hygiene-guard.d.ts +12 -0
  48. package/dist/guards/repository-hygiene-guard.js +70 -0
  49. package/dist/guards/schema-guard.d.ts +10 -0
  50. package/dist/guards/schema-guard.js +160 -0
  51. package/dist/guards/singleton-deps-guard.d.ts +21 -0
  52. package/dist/guards/singleton-deps-guard.js +183 -0
  53. package/dist/guards/tsconfig-guard.d.ts +5 -0
  54. package/dist/guards/tsconfig-guard.js +105 -0
  55. package/dist/guards/workspace-manifest-guard.d.ts +21 -0
  56. package/dist/guards/workspace-manifest-guard.js +210 -0
  57. package/dist/jsdoc-report.d.ts +7 -0
  58. package/dist/jsdoc-report.js +456 -0
  59. package/dist/process.d.ts +20 -0
  60. package/dist/process.js +86 -0
  61. package/dist/ready-service.d.ts +7 -0
  62. package/dist/ready-service.js +135 -0
  63. package/dist/release-gate.d.ts +7 -0
  64. package/dist/release-gate.js +35 -0
  65. package/dist/repo-check.d.ts +7 -0
  66. package/dist/repo-check.js +121 -0
  67. package/dist/tasks.d.ts +17 -0
  68. package/dist/tasks.js +195 -0
  69. package/dist/upgrade.d.ts +10 -0
  70. package/dist/upgrade.js +674 -0
  71. package/dist/validate.d.ts +7 -0
  72. package/dist/validate.js +51 -0
  73. package/dist/workspace-tests.d.ts +33 -0
  74. package/dist/workspace-tests.js +529 -0
  75. package/package.json +57 -0
@@ -0,0 +1,135 @@
1
+ import http from 'node:http';
2
+ import https from 'node:https';
3
+ const colors = {
4
+ reset: '\x1b[0m',
5
+ red: '\x1b[31m',
6
+ green: '\x1b[32m',
7
+ yellow: '\x1b[33m',
8
+ cyan: '\x1b[36m',
9
+ dim: '\x1b[2m',
10
+ };
11
+ const defaultPollIntervalMs = 1000;
12
+ function colorize(message, color) {
13
+ return `${color}${message}${colors.reset}`;
14
+ }
15
+ function getArgValue(args, name) {
16
+ const prefix = `--${name}=`;
17
+ const index = args.findIndex((arg) => arg === `--${name}`);
18
+ if (index >= 0)
19
+ return args[index + 1] ?? null;
20
+ return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length) ?? null;
21
+ }
22
+ function parseTimeoutMs(rawValue) {
23
+ if (!rawValue)
24
+ return null;
25
+ const normalized = rawValue.trim().toLowerCase();
26
+ if (normalized === 'never' || normalized === '0')
27
+ return null;
28
+ const value = Number(normalized);
29
+ if (!Number.isInteger(value) || value < 0) {
30
+ throw new Error(`Invalid timeout: ${rawValue}. Use a non-negative integer in milliseconds, 0, or "never".`);
31
+ }
32
+ return value;
33
+ }
34
+ function parseIntervalMs(rawValue) {
35
+ if (!rawValue)
36
+ return defaultPollIntervalMs;
37
+ const value = Number(rawValue.trim());
38
+ if (!Number.isInteger(value) || value <= 0) {
39
+ throw new Error(`Invalid interval: ${rawValue}. Use a positive integer in milliseconds.`);
40
+ }
41
+ return value;
42
+ }
43
+ function validateUrl(urlString) {
44
+ const url = new URL(urlString);
45
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
46
+ throw new Error(`Invalid protocol: ${url.protocol}. Only http: and https: are allowed.`);
47
+ }
48
+ return url;
49
+ }
50
+ function getReadinessUrl(url) {
51
+ return `${url.protocol}//${url.host}/ready`;
52
+ }
53
+ function checkReadiness(url) {
54
+ return new Promise((resolve) => {
55
+ const readinessUrl = getReadinessUrl(url);
56
+ const httpModule = url.protocol === 'https:' ? https : http;
57
+ const req = httpModule.get(readinessUrl, (res) => {
58
+ let data = '';
59
+ res.on('data', (chunk) => {
60
+ data += chunk.toString();
61
+ });
62
+ res.on('end', () => {
63
+ try {
64
+ const readinessStatus = JSON.parse(data);
65
+ if (res.statusCode === 200 && readinessStatus.status === 'ok') {
66
+ resolve({ success: true, status: 'ok', readinessData: readinessStatus });
67
+ return;
68
+ }
69
+ resolve({
70
+ success: false,
71
+ status: String(readinessStatus.status ?? 'unknown'),
72
+ readinessData: readinessStatus,
73
+ error: `HTTP ${res.statusCode}: service status ${String(readinessStatus.status ?? 'unknown')}`,
74
+ });
75
+ }
76
+ catch (error) {
77
+ resolve({ success: false, error: `Invalid JSON response: ${error.message}` });
78
+ }
79
+ });
80
+ });
81
+ req.on('error', (error) => {
82
+ resolve({ success: false, error: `Network error: ${error.code || error.message}` });
83
+ });
84
+ req.setTimeout(5000, () => {
85
+ req.destroy();
86
+ resolve({ success: false, error: 'Request timeout (5s)' });
87
+ });
88
+ });
89
+ }
90
+ function formatSeconds(milliseconds) {
91
+ const seconds = milliseconds / 1000;
92
+ const formatted = Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1).replace(/\.0$/u, '');
93
+ return `${formatted} ${seconds === 1 ? 'second' : 'seconds'}`;
94
+ }
95
+ function formatTimeout(timeoutMs) {
96
+ return timeoutMs === null ? 'no timeout' : `timeout: ${formatSeconds(timeoutMs)}`;
97
+ }
98
+ async function pollService(url, timeoutMs, intervalMs) {
99
+ const startedAt = Date.now();
100
+ let attempts = 0;
101
+ let lastResult = null;
102
+ while (timeoutMs === null || Date.now() - startedAt < timeoutMs) {
103
+ attempts += 1;
104
+ const result = await checkReadiness(url);
105
+ lastResult = result;
106
+ if (result.success)
107
+ return { success: true, attempts, elapsedMs: Date.now() - startedAt, lastResult };
108
+ const remainingMs = timeoutMs === null ? intervalMs : timeoutMs - (Date.now() - startedAt);
109
+ if (remainingMs > 0) {
110
+ await new Promise((resolve) => setTimeout(resolve, Math.min(intervalMs, remainingMs)));
111
+ }
112
+ }
113
+ return { success: false, attempts, elapsedMs: Date.now() - startedAt, lastResult };
114
+ }
115
+ export async function runReadyService(_runtime, rawArgs) {
116
+ if (rawArgs.includes('--skip-ready-check')) {
117
+ console.info(colorize('Service readiness check skipped; continuing startup.', colors.yellow));
118
+ return;
119
+ }
120
+ const serviceName = getArgValue(rawArgs, 'name') || process.env.READY_SERVICE_NAME || 'Backend';
121
+ const serviceUrl = getArgValue(rawArgs, 'url') || process.env.BACKEND_READY_URL || process.env.VITE_API_URL || 'http://localhost:3001';
122
+ const timeoutMs = parseTimeoutMs(getArgValue(rawArgs, 'timeout-ms') ?? process.env.BACKEND_READY_TIMEOUT_MS);
123
+ const intervalMs = parseIntervalMs(getArgValue(rawArgs, 'interval-ms') ?? process.env.BACKEND_READY_INTERVAL_MS);
124
+ const url = validateUrl(serviceUrl);
125
+ console.info(colorize(`Waiting for ${serviceName}: ${getReadinessUrl(url)} (interval: ${formatSeconds(intervalMs)}, ${formatTimeout(timeoutMs)})`, colors.cyan));
126
+ const result = await pollService(url, timeoutMs, intervalMs);
127
+ if (result.success) {
128
+ console.info(colorize(`${serviceName} ready after ${formatSeconds(result.elapsedMs)}; continuing startup.`, colors.green));
129
+ return;
130
+ }
131
+ console.error(colorize(`\nService did not become available after ${result.attempts} attempts.`, colors.red));
132
+ if (result.lastResult?.error)
133
+ console.error(colorize(` ${result.lastResult.error}`, colors.dim));
134
+ process.exit(1);
135
+ }
@@ -0,0 +1,7 @@
1
+ import type { WebToolkitCliConfig } from './config.js';
2
+ type Runtime = {
3
+ cwd: string;
4
+ config: WebToolkitCliConfig;
5
+ };
6
+ export declare function runReleaseGate(runtime: Runtime, requestedStages: string[]): void;
7
+ export {};
@@ -0,0 +1,35 @@
1
+ import { buildPackageManagerCommand, runCommandInherited } from './process.js';
2
+ function resolveStageCommand(config, stage) {
3
+ if (stage.command) {
4
+ return { command: stage.command, args: stage.args ?? [] };
5
+ }
6
+ if (stage.package && stage.script) {
7
+ return buildPackageManagerCommand(config.packageManager, ['--filter', stage.package, 'run', stage.script, ...(stage.files ?? [])]);
8
+ }
9
+ if (stage.package && stage.files?.length) {
10
+ return buildPackageManagerCommand(config.packageManager, ['--filter', stage.package, 'exec', 'vitest', 'run', ...stage.files]);
11
+ }
12
+ throw new Error(`Release gate stage "${stage.name}" must define command/args or package/files.`);
13
+ }
14
+ export function runReleaseGate(runtime, requestedStages) {
15
+ const stages = runtime.config.releaseGate?.stages;
16
+ if (!stages?.length) {
17
+ throw new Error('releaseGate.stages is not configured.');
18
+ }
19
+ const selectedStages = requestedStages.length === 0
20
+ ? stages
21
+ : stages.filter((stage) => requestedStages.includes(stage.name));
22
+ if (requestedStages.length > 0 && selectedStages.length !== requestedStages.length) {
23
+ const knownStages = new Set(stages.map((stage) => stage.name));
24
+ const unknownStages = requestedStages.filter((stageName) => !knownStages.has(stageName));
25
+ throw new Error(`[release-gate] unknown stage(s): ${unknownStages.join(', ')}`);
26
+ }
27
+ for (const stage of selectedStages) {
28
+ console.info(`\n[release-gate] running ${stage.name}`);
29
+ const command = resolveStageCommand(runtime.config, stage);
30
+ const code = runCommandInherited(command, runtime.cwd);
31
+ if (code !== 0)
32
+ process.exit(code);
33
+ }
34
+ console.info('\n[release-gate] all critical stages passed');
35
+ }
@@ -0,0 +1,7 @@
1
+ import type { WebToolkitCliConfig } from './config.js';
2
+ type Runtime = {
3
+ cwd: string;
4
+ config: WebToolkitCliConfig;
5
+ };
6
+ export declare function runRepoCheck(runtime: Runtime): void;
7
+ export {};
@@ -0,0 +1,121 @@
1
+ import { formatCommand, runCommandInherited } from './process.js';
2
+ import { builtinGuards, executeBuiltinGuard } from './guard-runner.js';
3
+ const colors = {
4
+ reset: '\x1b[0m',
5
+ bright: '\x1b[1m',
6
+ green: '\x1b[32m',
7
+ yellow: '\x1b[33m',
8
+ blue: '\x1b[34m',
9
+ cyan: '\x1b[36m',
10
+ red: '\x1b[31m',
11
+ };
12
+ function colorize(message, color) {
13
+ return `${color}${message}${colors.reset}`;
14
+ }
15
+ function visibleLength(value) {
16
+ return value.replace(/\x1B\[[0-9;?]*[ -/]*[@-~]/gu, '').length;
17
+ }
18
+ function padVisible(value, width) {
19
+ return `${value}${' '.repeat(Math.max(0, width - visibleLength(value)))}`;
20
+ }
21
+ function buildAsciiTable(results) {
22
+ const rows = results.map((result) => [
23
+ result.label,
24
+ result.status === 'OK'
25
+ ? colorize(result.status, colors.green)
26
+ : result.status === 'FAIL'
27
+ ? colorize(result.status, colors.red)
28
+ : result.status,
29
+ `${(result.durationMs / 1000).toFixed(2)}s`,
30
+ ]);
31
+ const headers = [colorize('Etapa', colors.yellow), colorize('Status', colors.yellow), colorize('Duracao', colors.yellow)];
32
+ const widths = headers.map((header, index) => Math.max(visibleLength(header), ...rows.map((row) => visibleLength(row[index]))));
33
+ const separator = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`;
34
+ const formatRow = (row) => `| ${row.map((cell, index) => padVisible(cell, widths[index])).join(' | ')} |`;
35
+ return [
36
+ separator,
37
+ formatRow(headers),
38
+ separator,
39
+ ...rows.map(formatRow),
40
+ separator,
41
+ ].join('\n');
42
+ }
43
+ function runCheck(step, runtime) {
44
+ const args = step.args ?? [];
45
+ if (step.builtinGuard) {
46
+ console.info('');
47
+ console.info(colorize(`Step: ${step.label}`, `${colors.bright}${colors.blue}`));
48
+ console.info(colorize(`> webtoolkit guard ${step.builtinGuard} ${args.join(' ')}`.trim(), colors.cyan));
49
+ console.info('');
50
+ return executeBuiltinGuard(step.builtinGuard, args, runtime.cwd);
51
+ }
52
+ const command = step.command;
53
+ console.info('');
54
+ console.info(colorize(`Step: ${step.label}`, `${colors.bright}${colors.blue}`));
55
+ console.info(colorize(`> ${formatCommand(command, args)}`, colors.cyan));
56
+ console.info('');
57
+ return runCommandInherited({
58
+ command,
59
+ args,
60
+ cwd: step.cwd,
61
+ env: step.env,
62
+ }, runtime.cwd);
63
+ }
64
+ function validateSteps(steps) {
65
+ for (const step of steps) {
66
+ const hasCommand = Boolean(step.command?.trim());
67
+ const hasBuiltinGuard = Boolean(step.builtinGuard?.trim());
68
+ if (hasCommand === hasBuiltinGuard) {
69
+ throw new Error(`Repo check step "${step.label}" must define exactly one of command or builtinGuard.`);
70
+ }
71
+ if (step.builtinGuard && !Object.hasOwn(builtinGuards, step.builtinGuard)) {
72
+ throw new Error(`Unknown builtin guard "${step.builtinGuard}". Available guards: ${Object.keys(builtinGuards).sort().join(', ')}.`);
73
+ }
74
+ }
75
+ }
76
+ export function runRepoCheck(runtime) {
77
+ const config = runtime.config.repoCheck;
78
+ if (!config?.steps?.length) {
79
+ throw new Error('repoCheck.steps is not configured.');
80
+ }
81
+ validateSteps(config.steps);
82
+ console.info(colorize(config.title ?? 'Starting Quality Assurance Checks...', `${colors.bright}${colors.yellow}`));
83
+ const failFast = config.failFast ?? false;
84
+ const results = [];
85
+ let hasFailure = false;
86
+ for (const step of config.steps) {
87
+ if (hasFailure && failFast) {
88
+ results.push({ label: step.label, status: 'SKIP', durationMs: 0 });
89
+ continue;
90
+ }
91
+ const startedAt = Date.now();
92
+ try {
93
+ const code = runCheck(step, runtime);
94
+ const durationMs = Date.now() - startedAt;
95
+ const status = code === 0 ? 'OK' : 'FAIL';
96
+ results.push({ label: step.label, status, durationMs });
97
+ if (status === 'OK') {
98
+ console.info(colorize(`[OK] ${step.label}`, colors.green));
99
+ }
100
+ else {
101
+ hasFailure = true;
102
+ console.error(`\n${colorize(`[FAIL] ${step.label} (exit code ${code})`, colors.red)}`);
103
+ }
104
+ }
105
+ catch (error) {
106
+ hasFailure = true;
107
+ results.push({ label: step.label, status: 'FAIL', durationMs: Date.now() - startedAt });
108
+ console.error(`\n${colorize(`[FAIL] ${step.label}: ${error.message}`, `${colors.bright}${colors.red}`)}`);
109
+ }
110
+ }
111
+ console.info('');
112
+ console.info(colorize('ASCII Summary', `${colors.bright}${colors.cyan}`));
113
+ console.info(buildAsciiTable(results));
114
+ console.info('');
115
+ if (hasFailure) {
116
+ console.error(`\n${colorize('One or more checks failed. Please fix the errors above before proceeding.', `${colors.bright}${colors.red}`)}\n`);
117
+ process.exit(1);
118
+ return;
119
+ }
120
+ console.info(`\n${colorize('All checks passed successfully!', `${colors.bright}${colors.green}`)}\n`);
121
+ }
@@ -0,0 +1,17 @@
1
+ import type { WebToolkitCliConfig } from './config.js';
2
+ type Runtime = {
3
+ cwd: string;
4
+ config: WebToolkitCliConfig;
5
+ };
6
+ export declare function formatTaskStatusLine(options: {
7
+ action: string;
8
+ label: string;
9
+ status?: 'OK' | 'FALHA' | 'SKIP';
10
+ durationMs?: number;
11
+ }): string;
12
+ export declare function resolveTaskName(command: string): string | null;
13
+ export declare function normalizePassthroughArgs(args: string[]): string[];
14
+ export declare function listTaskCommands(config: WebToolkitCliConfig): string[];
15
+ export declare function printTaskHelp(taskName: string, config: WebToolkitCliConfig): void;
16
+ export declare function runTask(taskName: string, runtime: Runtime, passthroughArgs?: string[]): Promise<void>;
17
+ export {};
package/dist/tasks.js ADDED
@@ -0,0 +1,195 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { executeBuiltinGuard } from './guard-runner.js';
4
+ const taskAliases = {
5
+ check: 'check',
6
+ build: 'build',
7
+ test: 'test',
8
+ 'test-coverage': 'testCoverage',
9
+ 'release-gate': 'releaseGate',
10
+ validate: 'validate',
11
+ 'jsdoc-report': 'jsdocReport',
12
+ upgrade: 'upgrade',
13
+ performance: 'performanceBundleAudit',
14
+ 'performance-bundle-audit': 'performanceBundleAudit',
15
+ };
16
+ function formatDuration(durationMs) {
17
+ return `${(durationMs / 1000).toFixed(2)}s`;
18
+ }
19
+ function resolveWorkingDirectory(runtime, step) {
20
+ if (!step.cwd)
21
+ return runtime.cwd;
22
+ if (path.isAbsolute(step.cwd))
23
+ return step.cwd;
24
+ return path.join(runtime.cwd, step.cwd);
25
+ }
26
+ function resolveCommand(step, args) {
27
+ if (!step.command) {
28
+ throw new Error(`Task step "${step.label}" must define command or builtinGuard.`);
29
+ }
30
+ if (process.platform !== 'win32') {
31
+ return { command: step.command, args, shell: false };
32
+ }
33
+ if (step.command === 'node' || step.command === process.execPath) {
34
+ return { command: step.command, args, shell: false };
35
+ }
36
+ return {
37
+ command: process.env.ComSpec ?? 'cmd.exe',
38
+ args: ['/d', '/s', '/c', step.command, ...args],
39
+ shell: false,
40
+ };
41
+ }
42
+ function colorizeResult(status) {
43
+ if (status === 'OK')
44
+ return `\x1b[32m${status}\x1b[0m`;
45
+ if (status === 'FALHA')
46
+ return `\x1b[31m${status}\x1b[0m`;
47
+ return `\x1b[90m${status}\x1b[0m`;
48
+ }
49
+ export function formatTaskStatusLine(options) {
50
+ const duration = options.durationMs === undefined ? '' : ` (${formatDuration(options.durationMs)})`;
51
+ const status = options.status === undefined ? '' : ` ${colorizeResult(options.status)}${duration}`;
52
+ return `- ${options.action} \x1b[1m${options.label.padEnd(16)}\x1b[0m...${status}`;
53
+ }
54
+ function getStepAction(step) {
55
+ return step.args?.includes('build') ? 'Building' : 'Running';
56
+ }
57
+ function runStep(step, runtime, passthroughArgs, defaultOutputMode) {
58
+ const args = [...(step.args ?? []), ...(step.appendArgs ? normalizePassthroughArgs(passthroughArgs) : [])];
59
+ const outputMode = step.outputMode ?? defaultOutputMode;
60
+ if (step.builtinGuard) {
61
+ return Promise.resolve({ code: executeBuiltinGuard(step.builtinGuard, args, runtime.cwd), output: '' });
62
+ }
63
+ const resolved = resolveCommand(step, args);
64
+ const child = spawn(resolved.command, resolved.args, {
65
+ cwd: resolveWorkingDirectory(runtime, step),
66
+ env: {
67
+ ...process.env,
68
+ FORCE_COLOR: '1',
69
+ ...(step.env ?? {}),
70
+ },
71
+ shell: resolved.shell,
72
+ stdio: outputMode === 'inherit' ? 'inherit' : ['inherit', 'pipe', 'pipe'],
73
+ });
74
+ let output = '';
75
+ if (outputMode === 'buffered') {
76
+ child.stdout?.on('data', (chunk) => {
77
+ output += chunk.toString();
78
+ });
79
+ child.stderr?.on('data', (chunk) => {
80
+ output += chunk.toString();
81
+ });
82
+ }
83
+ return new Promise((resolve, reject) => {
84
+ child.on('error', reject);
85
+ child.on('close', (code) => resolve({ code: code ?? 1, output }));
86
+ });
87
+ }
88
+ export function resolveTaskName(command) {
89
+ if (command.startsWith('run:'))
90
+ return command.slice('run:'.length);
91
+ return taskAliases[command] ?? null;
92
+ }
93
+ export function normalizePassthroughArgs(args) {
94
+ if (args[0] === '--')
95
+ return args.slice(1);
96
+ return args;
97
+ }
98
+ export function listTaskCommands(config) {
99
+ return Object.keys(config.tasks).sort();
100
+ }
101
+ export function printTaskHelp(taskName, config) {
102
+ const task = config.tasks[taskName];
103
+ if (!task) {
104
+ console.info(`Task "${taskName}" is not configured.`);
105
+ return;
106
+ }
107
+ console.info(task.title ?? `Task: ${taskName}`);
108
+ console.info('');
109
+ console.info('Steps:');
110
+ for (const step of task.steps) {
111
+ const command = step.builtinGuard ? `webtoolkit guard ${step.builtinGuard}` : step.command ?? '<builtin>';
112
+ console.info(` - ${step.label}: ${command} ${(step.args ?? []).join(' ')}`.trim());
113
+ }
114
+ if (task.steps.some((step) => step.appendArgs)) {
115
+ console.info('');
116
+ console.info('Additional arguments are appended to steps marked with appendArgs.');
117
+ }
118
+ }
119
+ export async function runTask(taskName, runtime, passthroughArgs = []) {
120
+ const task = runtime.config.tasks[taskName];
121
+ if (!task) {
122
+ const available = listTaskCommands(runtime.config);
123
+ throw new Error(available.length > 0
124
+ ? `Task "${taskName}" is not configured. Available tasks: ${available.join(', ')}.`
125
+ : `Task "${taskName}" is not configured.`);
126
+ }
127
+ const failFast = task.failFast ?? true;
128
+ const defaultOutputMode = task.outputMode ?? 'inherit';
129
+ let hasFailure = false;
130
+ console.info(task.title ?? `Running task: ${taskName}`);
131
+ for (const step of task.steps) {
132
+ const stepOutputMode = step.outputMode ?? defaultOutputMode;
133
+ if (hasFailure && failFast) {
134
+ console.info(formatTaskStatusLine({
135
+ action: getStepAction(step),
136
+ label: step.label,
137
+ status: 'SKIP',
138
+ durationMs: 0,
139
+ }));
140
+ continue;
141
+ }
142
+ const startLine = formatTaskStatusLine({
143
+ action: getStepAction(step),
144
+ label: step.label,
145
+ });
146
+ if (stepOutputMode === 'buffered') {
147
+ process.stdout.write(startLine);
148
+ }
149
+ else {
150
+ console.info('');
151
+ console.info(startLine);
152
+ console.info(`> ${step.label}`);
153
+ const displayCommand = step.builtinGuard ? `webtoolkit guard ${step.builtinGuard}` : step.command ?? '<builtin>';
154
+ console.info(`$ ${displayCommand} ${[...(step.args ?? []), ...(step.appendArgs ? normalizePassthroughArgs(passthroughArgs) : [])].join(' ')}`.trim());
155
+ }
156
+ const startedAt = Date.now();
157
+ const outcome = await runStep(step, runtime, passthroughArgs, defaultOutputMode);
158
+ const durationMs = Date.now() - startedAt;
159
+ if (outcome.code === 0) {
160
+ const completedLine = formatTaskStatusLine({
161
+ action: getStepAction(step),
162
+ label: step.label,
163
+ status: 'OK',
164
+ durationMs,
165
+ });
166
+ if (stepOutputMode === 'buffered') {
167
+ console.info(` ${colorizeResult('OK')} (${formatDuration(durationMs)})`);
168
+ }
169
+ else {
170
+ console.info(completedLine);
171
+ }
172
+ continue;
173
+ }
174
+ hasFailure = true;
175
+ const failedLine = formatTaskStatusLine({
176
+ action: getStepAction(step),
177
+ label: step.label,
178
+ status: 'FALHA',
179
+ durationMs,
180
+ });
181
+ if (stepOutputMode === 'buffered') {
182
+ console.info(` ${colorizeResult('FALHA')} (${formatDuration(durationMs)})`);
183
+ }
184
+ else {
185
+ console.info(failedLine);
186
+ }
187
+ if (outcome.output.trim()) {
188
+ console.info('');
189
+ console.info(outcome.output.trim());
190
+ }
191
+ }
192
+ if (hasFailure) {
193
+ throw new Error(`Task "${taskName}" failed.`);
194
+ }
195
+ }
@@ -0,0 +1,10 @@
1
+ import type { WebToolkitCliConfig } from './config.js';
2
+ type Runtime = {
3
+ cwd: string;
4
+ config: WebToolkitCliConfig;
5
+ };
6
+ export declare function mergeRejectLists(baseRejectList: string[], protectedDependencyNames: string[]): string[];
7
+ export declare function parseYesNo(answer: string, defaultValue: boolean): boolean | null;
8
+ export declare function formatYesNoPrompt(icon: string, question: string, defaultValue: boolean): string;
9
+ export declare function runUpgradeEngine(runtime: Runtime, rawArgs: string[]): Promise<void>;
10
+ export {};