@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
package/dist/bin.js ADDED
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ import path from 'node:path';
3
+ import { loadConfig } from './config.js';
4
+ import { runConfigReference } from './config-reference.js';
5
+ import { runBundleAudit } from './bundle-audit.js';
6
+ import { parseCleanArgs, printCleanHelp, runCleaner } from './cleaner.js';
7
+ import { runDevGrid } from './dev-grid.js';
8
+ import { runDevWatch } from './dev-watch.js';
9
+ import { runEnvBootstrap, runEnvDoctor } from './environment.js';
10
+ import { printGuardHelp, runBuiltinGuard } from './guard-runner.js';
11
+ import { runJSDocReport } from './jsdoc-report.js';
12
+ import { runReadyService } from './ready-service.js';
13
+ import { runRepoCheck } from './repo-check.js';
14
+ import { runReleaseGate } from './release-gate.js';
15
+ import { listTaskCommands, printTaskHelp, resolveTaskName, runTask } from './tasks.js';
16
+ import { runUpgradeEngine } from './upgrade.js';
17
+ import { runValidateEngine } from './validate.js';
18
+ import { runWorkspaceCoverage, runWorkspaceTests, runWorkspaceTestTask } from './workspace-tests.js';
19
+ function printHelp(taskNames = []) {
20
+ console.info('Usage: webtoolkit <command> [options]');
21
+ console.info('');
22
+ console.info('Commands:');
23
+ console.info(' clean Remove cache, build, and temporary artifacts.');
24
+ console.info(' check Run the configured check task.');
25
+ console.info(' build Run the configured build task.');
26
+ console.info(' test Run the configured test task.');
27
+ console.info(' test-coverage Run the configured coverage task.');
28
+ console.info(' workspace-test <task> Run a workspace-local Vitest task.');
29
+ console.info(' release-gate Run the configured release gate task.');
30
+ console.info(' validate Run the configured validation task.');
31
+ console.info(' jsdoc-report Run the configured JSDoc report task.');
32
+ console.info(' upgrade Run the configured upgrade task.');
33
+ console.info(' performance-bundle-audit Run the configured bundle audit task.');
34
+ console.info(' dev-watch Run configured dev app watcher.');
35
+ console.info(' dev-grid Open configured dev terminal grid.');
36
+ console.info(' wait-service Wait for a service readiness endpoint.');
37
+ console.info(' env-bootstrap Prepare configured Node/Corepack environment.');
38
+ console.info(' env-doctor Validate configured Node/Corepack environment.');
39
+ console.info(' config [--help|--json] Show .webtoolkit-cli/config.json reference.');
40
+ console.info(' guard <name> Run a builtin guard engine.');
41
+ console.info(' run:<task> Run any configured task by name.');
42
+ if (taskNames.length > 0) {
43
+ console.info('');
44
+ console.info(`Configured tasks: ${taskNames.join(', ')}`);
45
+ }
46
+ console.info('');
47
+ console.info('Run `webtoolkit <command> --help` for command-specific options.');
48
+ }
49
+ function hasHelp(args) {
50
+ return args.includes('--help') || args.includes('-h');
51
+ }
52
+ function printEngineHelp(command) {
53
+ const help = {
54
+ test: [
55
+ 'Usage: webtoolkit test [test files...] [--filter <workspace/package>] [vitest args]',
56
+ 'Runs configured workspace tests.',
57
+ ],
58
+ 'test-coverage': [
59
+ 'Usage: webtoolkit test-coverage [--filter <workspace/package>]',
60
+ 'Runs configured workspace coverage tasks.',
61
+ ],
62
+ check: [
63
+ 'Usage: webtoolkit check',
64
+ 'Runs configured repository quality checks.',
65
+ ],
66
+ 'release-gate': [
67
+ 'Usage: webtoolkit release-gate [stage...]',
68
+ 'Runs configured critical release gate stages.',
69
+ ],
70
+ validate: [
71
+ 'Usage: webtoolkit validate',
72
+ 'Runs configured validation steps.',
73
+ ],
74
+ 'jsdoc-report': [
75
+ 'Usage: webtoolkit jsdoc-report [files...] [--write|--report|--no-report]',
76
+ 'Analyzes configured TypeScript paths for JSDoc coverage.',
77
+ ],
78
+ upgrade: [
79
+ 'Usage: webtoolkit upgrade [--yes] [--major] [--no-cooldown] [--days=N] [--isolated] [--verbose]',
80
+ 'Runs configured dependency upgrade workflow.',
81
+ ],
82
+ 'performance-bundle-audit': [
83
+ 'Usage: webtoolkit performance-bundle-audit [--top N] [--root path]',
84
+ 'Audits configured frontend build assets.',
85
+ ],
86
+ 'dev-watch': [
87
+ 'Usage: webtoolkit dev-watch [--apps=a,b] [--check-only] [--include-backend] [--silent]',
88
+ 'Runs configured frontend dev watchers and port preflight.',
89
+ ],
90
+ 'dev-grid': [
91
+ 'Usage: webtoolkit dev-grid [--silent] [--dry-run]',
92
+ 'Opens the configured Windows Terminal dev grid.',
93
+ ],
94
+ 'wait-service': [
95
+ 'Usage: webtoolkit wait-service [--url URL] [--name Name] [--timeout-ms N|never] [--interval-ms N] [--skip-ready-check]',
96
+ 'Waits for a service /ready endpoint.',
97
+ ],
98
+ 'env-bootstrap': [
99
+ 'Usage: webtoolkit env-bootstrap',
100
+ 'Prepares the configured Node/Corepack/package-manager environment.',
101
+ ],
102
+ 'env-doctor': [
103
+ 'Usage: webtoolkit env-doctor',
104
+ 'Validates the configured Node/Corepack/package-manager environment.',
105
+ ],
106
+ };
107
+ for (const line of help[command] ?? [`No help available for ${command}.`]) {
108
+ console.info(line);
109
+ }
110
+ }
111
+ async function main() {
112
+ const [command, ...args] = process.argv.slice(2);
113
+ if (command === 'config') {
114
+ runConfigReference(args);
115
+ return;
116
+ }
117
+ const { config, configPath } = await loadConfig(process.cwd());
118
+ const workspaceRoot = configPath ? path.dirname(path.dirname(configPath)) : process.cwd();
119
+ if (!command || command === '--help' || command === '-h') {
120
+ printHelp(listTaskCommands(config));
121
+ return;
122
+ }
123
+ if (command === 'clean') {
124
+ const options = parseCleanArgs(args);
125
+ await runCleaner(options, { cwd: workspaceRoot, config });
126
+ return;
127
+ }
128
+ if (command === 'clean-help') {
129
+ printCleanHelp();
130
+ return;
131
+ }
132
+ if (command === 'guard') {
133
+ const [guardName, ...guardArgs] = args;
134
+ if (!guardName || hasHelp(args)) {
135
+ printGuardHelp();
136
+ return;
137
+ }
138
+ runBuiltinGuard(guardName, guardArgs, process.cwd());
139
+ return;
140
+ }
141
+ if (command === 'test' && config.workspaceTests) {
142
+ if (hasHelp(args)) {
143
+ printEngineHelp(command);
144
+ return;
145
+ }
146
+ await runWorkspaceTests({ cwd: workspaceRoot, config }, args);
147
+ return;
148
+ }
149
+ if (command === 'check' && config.repoCheck) {
150
+ if (hasHelp(args)) {
151
+ printEngineHelp(command);
152
+ return;
153
+ }
154
+ runRepoCheck({ cwd: workspaceRoot, config });
155
+ return;
156
+ }
157
+ if (command === 'test-coverage' && config.workspaceTests) {
158
+ if (hasHelp(args)) {
159
+ printEngineHelp(command);
160
+ return;
161
+ }
162
+ await runWorkspaceCoverage({ cwd: workspaceRoot, config }, args);
163
+ return;
164
+ }
165
+ if (command === 'workspace-test') {
166
+ const [taskName, ...extraArgs] = args;
167
+ if (!taskName || hasHelp(args)) {
168
+ console.info('Usage: webtoolkit workspace-test <test|test:coverage> [vitest args]');
169
+ return;
170
+ }
171
+ runWorkspaceTestTask({ cwd: workspaceRoot, config }, taskName, extraArgs);
172
+ return;
173
+ }
174
+ if (command === 'release-gate' && config.releaseGate) {
175
+ if (hasHelp(args)) {
176
+ printEngineHelp(command);
177
+ return;
178
+ }
179
+ runReleaseGate({ cwd: workspaceRoot, config }, args);
180
+ return;
181
+ }
182
+ if (command === 'validate' && config.validate) {
183
+ if (hasHelp(args)) {
184
+ printEngineHelp(command);
185
+ return;
186
+ }
187
+ await runValidateEngine({ cwd: workspaceRoot, config });
188
+ return;
189
+ }
190
+ if (command === 'jsdoc-report' && config.jsdocReport) {
191
+ if (hasHelp(args)) {
192
+ printEngineHelp(command);
193
+ return;
194
+ }
195
+ await runJSDocReport({ cwd: workspaceRoot, config }, args);
196
+ return;
197
+ }
198
+ if (command === 'upgrade' && config.upgrade) {
199
+ if (hasHelp(args)) {
200
+ printEngineHelp(command);
201
+ return;
202
+ }
203
+ await runUpgradeEngine({ cwd: workspaceRoot, config }, args);
204
+ return;
205
+ }
206
+ if (command === 'performance-bundle-audit' && config.bundleAudit) {
207
+ if (hasHelp(args)) {
208
+ printEngineHelp(command);
209
+ return;
210
+ }
211
+ runBundleAudit({ cwd: workspaceRoot, config }, args);
212
+ return;
213
+ }
214
+ if (command === 'dev-watch' && config.devWatch) {
215
+ if (hasHelp(args)) {
216
+ printEngineHelp(command);
217
+ return;
218
+ }
219
+ await runDevWatch({ cwd: workspaceRoot, config }, args);
220
+ return;
221
+ }
222
+ if (command === 'dev-grid' && config.devGrid) {
223
+ if (hasHelp(args)) {
224
+ printEngineHelp(command);
225
+ return;
226
+ }
227
+ runDevGrid({ cwd: workspaceRoot, config }, args);
228
+ return;
229
+ }
230
+ if (command === 'wait-service') {
231
+ if (hasHelp(args)) {
232
+ printEngineHelp(command);
233
+ return;
234
+ }
235
+ await runReadyService({ cwd: workspaceRoot, config }, args);
236
+ return;
237
+ }
238
+ if (command === 'env-bootstrap') {
239
+ if (hasHelp(args)) {
240
+ printEngineHelp(command);
241
+ return;
242
+ }
243
+ runEnvBootstrap({ cwd: workspaceRoot, config });
244
+ return;
245
+ }
246
+ if (command === 'env-doctor') {
247
+ if (hasHelp(args)) {
248
+ printEngineHelp(command);
249
+ return;
250
+ }
251
+ runEnvDoctor({ cwd: workspaceRoot, config });
252
+ return;
253
+ }
254
+ const taskName = resolveTaskName(command);
255
+ if (taskName) {
256
+ if (hasHelp(args)) {
257
+ printTaskHelp(taskName, config);
258
+ return;
259
+ }
260
+ await runTask(taskName, { cwd: workspaceRoot, config }, args);
261
+ return;
262
+ }
263
+ throw new Error(`Unknown command: ${command}`);
264
+ }
265
+ main().catch((error) => {
266
+ console.error(error.message);
267
+ process.exit(1);
268
+ });
@@ -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 runBundleAudit(runtime: Runtime, rawArgs: string[]): void;
7
+ export {};
@@ -0,0 +1,111 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { extname, join, relative, resolve } from 'node:path';
3
+ import { brotliCompressSync, gzipSync } from 'node:zlib';
4
+ const colors = {
5
+ reset: '\x1b[0m',
6
+ bright: '\x1b[1m',
7
+ green: '\x1b[32m',
8
+ yellow: '\x1b[33m',
9
+ cyan: '\x1b[36m',
10
+ red: '\x1b[31m',
11
+ gray: '\x1b[90m',
12
+ };
13
+ const assetExtensions = new Set(['.js', '.css']);
14
+ function colorize(value, color) {
15
+ return `${color}${value}${colors.reset}`;
16
+ }
17
+ function formatBytes(bytes) {
18
+ if (bytes < 1024)
19
+ return `${bytes} B`;
20
+ const kib = bytes / 1024;
21
+ if (kib < 1024)
22
+ return `${kib.toFixed(1)} KiB`;
23
+ return `${(kib / 1024).toFixed(2)} MiB`;
24
+ }
25
+ function pad(value, width) {
26
+ return value.padEnd(width, ' ');
27
+ }
28
+ function compareAssetsByRawSizeDesc(left, right) {
29
+ return right.rawBytes - left.rawBytes || left.app.localeCompare(right.app) || left.file.localeCompare(right.file);
30
+ }
31
+ function collectBundleAssets(rootDir, appDirs) {
32
+ return appDirs.map((appDir) => {
33
+ const distDir = join(rootDir, appDir, 'dist');
34
+ const assetsDir = join(distDir, 'assets');
35
+ const statsHtmlPath = join(distDir, 'stats.html');
36
+ if (!existsSync(assetsDir)) {
37
+ return { app: appDir, assetsDir, statsHtmlPath, statsHtmlExists: existsSync(statsHtmlPath), assets: [] };
38
+ }
39
+ const assets = readdirSync(assetsDir)
40
+ .filter((file) => assetExtensions.has(extname(file)))
41
+ .map((file) => {
42
+ const fullPath = join(assetsDir, file);
43
+ const content = readFileSync(fullPath);
44
+ return {
45
+ app: appDir,
46
+ file,
47
+ rawBytes: statSync(fullPath).size,
48
+ gzipBytes: gzipSync(content).length,
49
+ brotliBytes: brotliCompressSync(content).length,
50
+ };
51
+ })
52
+ .sort(compareAssetsByRawSizeDesc);
53
+ return { app: appDir, assetsDir, statsHtmlPath, statsHtmlExists: existsSync(statsHtmlPath), assets };
54
+ });
55
+ }
56
+ export function runBundleAudit(runtime, rawArgs) {
57
+ const config = runtime.config.bundleAudit;
58
+ if (!config?.appDirs?.length) {
59
+ throw new Error('bundleAudit.appDirs is not configured.');
60
+ }
61
+ const args = new Map();
62
+ for (let index = 0; index < rawArgs.length; index += 1) {
63
+ const token = rawArgs[index];
64
+ if (!token.startsWith('--'))
65
+ continue;
66
+ const [key, value] = token.slice(2).split('=', 2);
67
+ if (value !== undefined)
68
+ args.set(key, value);
69
+ else if (rawArgs[index + 1] && !rawArgs[index + 1].startsWith('--')) {
70
+ args.set(key, rawArgs[index + 1]);
71
+ index += 1;
72
+ }
73
+ }
74
+ const rootDir = resolve(args.get('root') ?? runtime.cwd);
75
+ const top = Number(args.get('top') ?? String(config.top ?? 20));
76
+ const rawWarningBytes = config.rawWarningBytes ?? 1_000_000;
77
+ const stats = collectBundleAssets(rootDir, config.appDirs);
78
+ const allAssets = stats.flatMap((entry) => entry.assets).sort(compareAssetsByRawSizeDesc);
79
+ console.info(colorize('Frontend bundle audit', `${colors.bright}${colors.cyan}`));
80
+ console.info('');
81
+ console.info(colorize('Apps:', colors.bright));
82
+ for (const entry of stats) {
83
+ const relativeAssetsDir = relative(process.cwd(), entry.assetsDir) || entry.assetsDir;
84
+ const relativeStatsPath = relative(process.cwd(), entry.statsHtmlPath) || entry.statsHtmlPath;
85
+ const statsStatus = entry.statsHtmlExists ? colorize('stats.html ok', colors.green) : colorize('stats.html missing', colors.yellow);
86
+ console.info(`- ${colorize(entry.app, colors.cyan)}: ${entry.assets.length} JS/CSS assets, ${statsStatus} (${relativeAssetsDir}; ${relativeStatsPath})`);
87
+ }
88
+ console.info('');
89
+ if (allAssets.length === 0) {
90
+ console.info(colorize('No JS/CSS bundle assets found. Run the frontend builds before auditing.', colors.yellow));
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+ console.info(colorize(`Top ${Math.min(top, allAssets.length)} assets by raw size:`, colors.bright));
95
+ console.info(colorize([' ', pad('app', 28), pad('file', 54), pad('raw', 10), pad('gzip', 10), 'brotli'].join(' '), colors.gray));
96
+ for (const asset of allAssets.slice(0, top)) {
97
+ const marker = asset.rawBytes >= rawWarningBytes ? '!' : ' ';
98
+ const line = [
99
+ marker,
100
+ pad(asset.app, 28),
101
+ pad(asset.file, 54),
102
+ pad(formatBytes(asset.rawBytes), 10),
103
+ pad(formatBytes(asset.gzipBytes), 10),
104
+ formatBytes(asset.brotliBytes),
105
+ ].join(' ');
106
+ console.info(asset.rawBytes >= rawWarningBytes ? colorize(line, colors.red) : line);
107
+ }
108
+ const warnedCount = allAssets.filter((asset) => asset.rawBytes >= rawWarningBytes).length;
109
+ console.info('');
110
+ console.info(colorize(`Warning threshold: ${formatBytes(rawWarningBytes)} raw. Flagged assets: ${warnedCount}.`, warnedCount > 0 ? colors.yellow : colors.green));
111
+ }
@@ -0,0 +1,15 @@
1
+ import type { CleanLevel, CleanerOptions, Removal, WebToolkitCliConfig } from './config.js';
2
+ type CommandSpec = {
3
+ command: string;
4
+ args: string[];
5
+ };
6
+ type Runtime = {
7
+ cwd: string;
8
+ config: WebToolkitCliConfig;
9
+ };
10
+ export declare function parseLevel(value: string): CleanLevel | null;
11
+ export declare function printCleanHelp(): void;
12
+ export declare function parseCleanArgs(argv: string[]): CleanerOptions;
13
+ export declare function resolvePackageManagerCommand(packageManager: string, commandArgs: string[]): CommandSpec;
14
+ export declare function runCleaner(options: CleanerOptions, runtime: Runtime): Promise<Removal[]>;
15
+ export {};