angular-render-scan-cli 0.1.10

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/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # angular-render-scan-cli
2
+
3
+ CLI installer for [angular-render-scan](https://www.npmjs.com/package/angular-render-scan).
4
+
5
+ ## Usage
6
+
7
+ ```sh
8
+ npx angular-render-scan-cli init
9
+ ```
10
+
11
+ The CLI detects Angular CLI and Nx workspaces (`angular.json`, `workspace.json`, `nx.json`, or `project.json`), finds your `main.ts` or `app.config.ts`, installs `angular-render-scan`, and injects `provideAngularRenderScan()` into your Angular providers.
12
+
13
+ ## Options
14
+
15
+ ```sh
16
+ npx angular-render-scan-cli init --dry-run
17
+ npx angular-render-scan-cli init --script-tag
18
+ npx angular-render-scan-cli --help
19
+ ```
20
+
21
+ - `--dry-run`: preview changes without writing files.
22
+ - `--script-tag`: add the CDN script tag instead of provider setup.
23
+ - `--force`: patch even if `angular-render-scan` is already present.
24
+
25
+ ## Docs
26
+
27
+ https://github.com/edisonaugusthy/angular-render-scan
package/bin/cli.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // Entry point for `npx angular-render-scan-cli`
3
+ // This thin shim imports the compiled TypeScript and calls run().
4
+
5
+ import { run } from '../dist/index.js';
6
+
7
+ run().catch((err) => {
8
+ console.error(err);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * angular-render-scan CLI
3
+ *
4
+ * Usage: npx angular-render-scan-cli init
5
+ * npx angular-render-scan-cli init --force
6
+ *
7
+ * Automatically detects your Angular project structure and inserts
8
+ * provideAngularRenderScan() into your bootstrap providers, or adds
9
+ * the script tag to index.html for non-provider mode.
10
+ */
11
+ export declare function runInit(args: string[]): Promise<void>;
12
+ export declare function run(): Promise<void>;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkYH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqH3D;AAED,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAqCzC"}
package/dist/index.js ADDED
@@ -0,0 +1,501 @@
1
+ /**
2
+ * angular-render-scan CLI
3
+ *
4
+ * Usage: npx angular-render-scan-cli init
5
+ * npx angular-render-scan-cli init --force
6
+ *
7
+ * Automatically detects your Angular project structure and inserts
8
+ * provideAngularRenderScan() into your bootstrap providers, or adds
9
+ * the script tag to index.html for non-provider mode.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ // ─── ANSI colour helpers (no deps) ───────────────────────────────────────────
14
+ const c = {
15
+ reset: '\x1b[0m',
16
+ bold: '\x1b[1m',
17
+ dim: '\x1b[2m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ cyan: '\x1b[36m',
21
+ red: '\x1b[31m',
22
+ blue: '\x1b[34m',
23
+ gray: '\x1b[90m'
24
+ };
25
+ function log(msg) { process.stdout.write(msg + '\n'); }
26
+ function ok(msg) { log(`${c.green}✔${c.reset} ${msg}`); }
27
+ function warn(msg) { log(`${c.yellow}⚠${c.reset} ${msg}`); }
28
+ function err(msg) { log(`${c.red}✖${c.reset} ${msg}`); }
29
+ function info(msg) { log(`${c.cyan}ℹ${c.reset} ${msg}`); }
30
+ function step(n, total, msg) {
31
+ log(`${c.gray}[${n}/${total}]${c.reset} ${msg}`);
32
+ }
33
+ // ─── File discovery helpers ───────────────────────────────────────────────────
34
+ function findUp(filename, startDir = process.cwd()) {
35
+ let dir = startDir;
36
+ for (let i = 0; i < 10; i++) {
37
+ const candidate = path.join(dir, filename);
38
+ if (fs.existsSync(candidate))
39
+ return candidate;
40
+ const parent = path.dirname(dir);
41
+ if (parent === dir)
42
+ return null;
43
+ dir = parent;
44
+ }
45
+ return null;
46
+ }
47
+ function readFile(filePath) {
48
+ return fs.readFileSync(filePath, 'utf-8');
49
+ }
50
+ function writeFile(filePath, content) {
51
+ fs.writeFileSync(filePath, content, 'utf-8');
52
+ }
53
+ function findWorkspaceConfig() {
54
+ return findUp('angular.json') ?? findUp('workspace.json') ?? findUp('nx.json') ?? findUp('project.json');
55
+ }
56
+ /**
57
+ * Find the Angular app entry file. Priority:
58
+ * 1. build target main/browser from angular.json, workspace.json, or Nx project.json
59
+ * 2. src/main.ts relative to workspace/project root
60
+ * 3. apps/[name]/src/main.ts (Nx monorepo)
61
+ * 3. Any main.ts near the angular.json
62
+ */
63
+ function findMainTs(workspaceConfigPath) {
64
+ const root = path.dirname(workspaceConfigPath);
65
+ const configName = path.basename(workspaceConfigPath);
66
+ // Try to parse Angular/Nx workspace files for the configured app entrypoint.
67
+ try {
68
+ const config = JSON.parse(readFile(workspaceConfigPath));
69
+ const directProject = configName === 'project.json'
70
+ ? entrypointFromProjectConfig(config, root, root)
71
+ : null;
72
+ if (directProject)
73
+ return directProject;
74
+ for (const project of projectConfigsFromWorkspace(config, root)) {
75
+ const mainFile = entrypointFromProjectConfig(project.config, root, project.projectRoot);
76
+ if (mainFile)
77
+ return mainFile;
78
+ }
79
+ }
80
+ catch {
81
+ // Ignore parse errors
82
+ }
83
+ // Fallback: common locations
84
+ const candidates = [
85
+ path.join(root, 'src', 'main.ts'),
86
+ path.join(root, 'src', 'main.server.ts'),
87
+ ...glob(path.join(root, 'apps'), 'main.ts', 4),
88
+ ...glob(path.join(root, 'packages'), 'main.ts', 4)
89
+ ];
90
+ return candidates.find(f => fs.existsSync(f)) ?? null;
91
+ }
92
+ function projectConfigsFromWorkspace(workspaceConfig, workspaceRoot) {
93
+ const refs = [];
94
+ const projects = workspaceConfig.projects ?? {};
95
+ for (const [name, value] of Object.entries(projects)) {
96
+ if (typeof value === 'string') {
97
+ const projectRoot = path.resolve(workspaceRoot, value);
98
+ const projectJsonPath = path.join(projectRoot, 'project.json');
99
+ if (fs.existsSync(projectJsonPath)) {
100
+ refs.push({ config: JSON.parse(readFile(projectJsonPath)), projectRoot });
101
+ }
102
+ continue;
103
+ }
104
+ if (value && typeof value === 'object') {
105
+ const projectRoot = path.resolve(workspaceRoot, String(value.root ?? ''));
106
+ refs.push({ config: value, projectRoot });
107
+ }
108
+ else {
109
+ const projectRoot = path.resolve(workspaceRoot, 'apps', name);
110
+ const projectJsonPath = path.join(projectRoot, 'project.json');
111
+ if (fs.existsSync(projectJsonPath)) {
112
+ refs.push({ config: JSON.parse(readFile(projectJsonPath)), projectRoot });
113
+ }
114
+ }
115
+ }
116
+ for (const projectJsonPath of glob(workspaceRoot, 'project.json', 5)) {
117
+ const projectRoot = path.dirname(projectJsonPath);
118
+ if (!refs.some(ref => ref.projectRoot === projectRoot)) {
119
+ refs.push({ config: JSON.parse(readFile(projectJsonPath)), projectRoot });
120
+ }
121
+ }
122
+ return refs;
123
+ }
124
+ function entrypointFromProjectConfig(project, workspaceRoot, projectRoot) {
125
+ const targets = project.targets ?? project.architect ?? {};
126
+ const buildTarget = targets.build ?? targets['build-browser'] ?? targets.application ?? null;
127
+ const options = buildTarget?.options ?? {};
128
+ const candidates = [
129
+ options.main,
130
+ options.browser,
131
+ options.server
132
+ ].filter((candidate) => typeof candidate === 'string');
133
+ for (const candidate of candidates) {
134
+ const resolved = resolveProjectFile(workspaceRoot, projectRoot, candidate);
135
+ if (fs.existsSync(resolved))
136
+ return resolved;
137
+ }
138
+ return null;
139
+ }
140
+ function resolveProjectFile(workspaceRoot, projectRoot, filePath) {
141
+ if (path.isAbsolute(filePath))
142
+ return filePath;
143
+ const workspaceRelative = path.resolve(workspaceRoot, filePath);
144
+ if (fs.existsSync(workspaceRelative))
145
+ return workspaceRelative;
146
+ return path.resolve(projectRoot, filePath);
147
+ }
148
+ function glob(dir, filename, maxDepth) {
149
+ const results = [];
150
+ if (!fs.existsSync(dir) || maxDepth <= 0)
151
+ return results;
152
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
153
+ const full = path.join(dir, entry.name);
154
+ if (entry.isDirectory()) {
155
+ results.push(...glob(full, filename, maxDepth - 1));
156
+ }
157
+ else if (entry.name === filename) {
158
+ results.push(full);
159
+ }
160
+ }
161
+ return results;
162
+ }
163
+ /**
164
+ * Find app.config.ts — preferred over main.ts for standalone Angular apps.
165
+ */
166
+ function findAppConfig(mainTsPath) {
167
+ const dir = path.dirname(mainTsPath);
168
+ const appDir = path.join(dir, 'app');
169
+ const candidates = [
170
+ path.join(dir, 'app.config.ts'),
171
+ path.join(appDir, 'app.config.ts'),
172
+ ];
173
+ // Also try to read the import from main.ts
174
+ try {
175
+ const mainContent = readFile(mainTsPath);
176
+ const match = mainContent.match(/from\s+['"]([^'"]*app\.config[^'"]*)['"]/);
177
+ if (match) {
178
+ const relative = match[1];
179
+ const resolved = path.resolve(dir, relative + '.ts').replace(/\.ts\.ts$/, '.ts');
180
+ if (fs.existsSync(resolved))
181
+ return resolved;
182
+ }
183
+ }
184
+ catch {
185
+ // Ignore
186
+ }
187
+ return candidates.find(f => fs.existsSync(f)) ?? null;
188
+ }
189
+ /**
190
+ * Find index.html for script-tag fallback.
191
+ */
192
+ function findIndexHtml(workspaceConfigPath) {
193
+ const root = path.dirname(workspaceConfigPath);
194
+ const candidates = [
195
+ path.join(root, 'src', 'index.html'),
196
+ path.join(root, 'index.html'),
197
+ ...glob(path.join(root, 'apps'), 'index.html', 4),
198
+ ...glob(path.join(root, 'packages'), 'index.html', 4)
199
+ ];
200
+ return candidates.find(f => fs.existsSync(f)) ?? null;
201
+ }
202
+ // ─── Patch logic ─────────────────────────────────────────────────────────────
203
+ const IMPORT_STATEMENT = `import { provideAngularRenderScan } from 'angular-render-scan';`;
204
+ const PROVIDER_CALL = `provideAngularRenderScan()`;
205
+ /** Check if the file already has the provider set up */
206
+ function alreadyPatched(content) {
207
+ return content.includes('angular-render-scan') || content.includes('provideAngularRenderScan');
208
+ }
209
+ function findMatchingSquareBracket(content, openingBracketIndex) {
210
+ let depth = 0;
211
+ for (let i = openingBracketIndex; i < content.length; i++) {
212
+ const char = content[i];
213
+ if (char === '[')
214
+ depth++;
215
+ if (char === ']')
216
+ depth--;
217
+ if (depth === 0)
218
+ return i;
219
+ }
220
+ return -1;
221
+ }
222
+ function insertProviderIntoProvidersArray(content, providersMatch) {
223
+ if (providersMatch.index === undefined)
224
+ return null;
225
+ const openingBracketIndex = providersMatch.index + providersMatch[0].lastIndexOf('[');
226
+ const closingBracketIndex = findMatchingSquareBracket(content, openingBracketIndex);
227
+ if (closingBracketIndex === -1)
228
+ return null;
229
+ const currentLineStart = content.lastIndexOf('\n', providersMatch.index) + 1;
230
+ const baseIndent = content.slice(currentLineStart, providersMatch.index).match(/^\s*/)?.[0] ?? '';
231
+ const providerIndent = `${baseIndent} `;
232
+ const inside = content.slice(openingBracketIndex + 1, closingBracketIndex).trim();
233
+ if (!inside) {
234
+ return content.slice(0, openingBracketIndex + 1) + PROVIDER_CALL + content.slice(closingBracketIndex);
235
+ }
236
+ return (content.slice(0, openingBracketIndex + 1) +
237
+ `\n${providerIndent}${PROVIDER_CALL},\n${providerIndent}${inside}\n${baseIndent}` +
238
+ content.slice(closingBracketIndex));
239
+ }
240
+ /**
241
+ * Insert provideAngularRenderScan() into an app.config.ts that uses
242
+ * provideRouter / other Angular providers.
243
+ *
244
+ * Handles patterns like:
245
+ * providers: [provideRouter(routes), provideHttpClient()]
246
+ * providers: [
247
+ * provideRouter(routes),
248
+ * ]
249
+ */
250
+ function patchAppConfig(content) {
251
+ // Add import after the last existing import
252
+ let patched = content;
253
+ if (!patched.includes(IMPORT_STATEMENT)) {
254
+ // Find a good insertion point: after the last import line
255
+ const lastImportIdx = patched.lastIndexOf('\nimport ');
256
+ if (lastImportIdx === -1)
257
+ return null;
258
+ const endOfLastImport = patched.indexOf('\n', lastImportIdx + 1);
259
+ patched =
260
+ patched.slice(0, endOfLastImport + 1) +
261
+ IMPORT_STATEMENT + '\n' +
262
+ patched.slice(endOfLastImport + 1);
263
+ }
264
+ // Insert into providers array
265
+ // Handles both single-line and multi-line providers arrays
266
+ const providersMatch = patched.match(/providers:\s*\[/);
267
+ if (!providersMatch)
268
+ return null;
269
+ return insertProviderIntoProvidersArray(patched, providersMatch);
270
+ }
271
+ /**
272
+ * Patch bootstrapApplication() call in main.ts by injecting the provider.
273
+ */
274
+ function patchMainTs(content) {
275
+ let patched = content;
276
+ if (!patched.includes(IMPORT_STATEMENT)) {
277
+ const lastImportIdx = patched.lastIndexOf('\nimport ');
278
+ if (lastImportIdx === -1)
279
+ return null;
280
+ const endOfLastImport = patched.indexOf('\n', lastImportIdx + 1);
281
+ patched =
282
+ patched.slice(0, endOfLastImport + 1) +
283
+ IMPORT_STATEMENT + '\n' +
284
+ patched.slice(endOfLastImport + 1);
285
+ }
286
+ // Find the providers: [...] inside bootstrapApplication
287
+ const bootstrapMatch = patched.match(/bootstrapApplication\s*\(/);
288
+ if (!bootstrapMatch || bootstrapMatch.index === undefined)
289
+ return null;
290
+ const providersMatch = patched.match(/providers:\s*\[/);
291
+ if (providersMatch && providersMatch.index !== undefined) {
292
+ return insertProviderIntoProvidersArray(patched, providersMatch);
293
+ }
294
+ // No providers array yet — add one in the config object
295
+ // Pattern: bootstrapApplication(AppComponent, { OR bootstrapApplication(AppComponent)
296
+ const configObjMatch = patched.match(/bootstrapApplication\s*\(\s*\w+\s*,\s*\{/);
297
+ if (configObjMatch && configObjMatch.index !== undefined) {
298
+ const insertIdx = configObjMatch.index + configObjMatch[0].length;
299
+ patched =
300
+ patched.slice(0, insertIdx) +
301
+ `\n providers: [\n ${PROVIDER_CALL}\n ],` +
302
+ patched.slice(insertIdx);
303
+ return patched;
304
+ }
305
+ // bootstrapApplication(AppComponent) with no config object
306
+ const simpleBootstrap = patched.match(/bootstrapApplication\s*\(\s*(\w+)\s*\)/);
307
+ if (simpleBootstrap && simpleBootstrap.index !== undefined) {
308
+ const end = simpleBootstrap.index + simpleBootstrap[0].length - 1; // before closing )
309
+ patched =
310
+ patched.slice(0, end) +
311
+ `, {\n providers: [\n ${PROVIDER_CALL}\n ]\n}` +
312
+ patched.slice(end);
313
+ return patched;
314
+ }
315
+ return null;
316
+ }
317
+ /**
318
+ * Add the CDN script tag to index.html as a fallback.
319
+ */
320
+ function patchIndexHtml(content) {
321
+ if (content.includes('angular-render-scan'))
322
+ return null;
323
+ const scriptTag = ` <!-- angular-render-scan: remove this script before production builds -->\n <script src="https://unpkg.com/angular-render-scan/dist/auto.global.js"></script>`;
324
+ // Insert before </body>
325
+ const bodyClose = content.lastIndexOf('</body>');
326
+ if (bodyClose === -1)
327
+ return null;
328
+ return content.slice(0, bodyClose) + scriptTag + '\n' + content.slice(bodyClose);
329
+ }
330
+ // ─── Package installation check ───────────────────────────────────────────────
331
+ function isPackageInstalled(cwd) {
332
+ const pkgPath = path.join(cwd, 'node_modules', 'angular-render-scan');
333
+ return fs.existsSync(pkgPath);
334
+ }
335
+ function getPackageManager(cwd) {
336
+ if (fs.existsSync(path.join(cwd, 'bun.lockb')))
337
+ return 'bun';
338
+ if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
339
+ return 'pnpm';
340
+ if (fs.existsSync(path.join(cwd, 'yarn.lock')))
341
+ return 'yarn';
342
+ return 'npm';
343
+ }
344
+ function installCommand(pm) {
345
+ switch (pm) {
346
+ case 'bun': return 'bun add -D angular-render-scan';
347
+ case 'pnpm': return 'pnpm add -D angular-render-scan';
348
+ case 'yarn': return 'yarn add -D angular-render-scan';
349
+ default: return 'npm install -D angular-render-scan';
350
+ }
351
+ }
352
+ // ─── Entry point ──────────────────────────────────────────────────────────────
353
+ export async function runInit(args) {
354
+ const force = args.includes('--force') || args.includes('-f');
355
+ const dryRun = args.includes('--dry-run');
356
+ const scriptTagOnly = args.includes('--script-tag');
357
+ log('');
358
+ log(`${c.bold}${c.blue} angular-render-scan${c.reset}${c.bold} init${c.reset}`);
359
+ log(`${c.gray} Zero-friction Angular performance profiler setup${c.reset}`);
360
+ log('');
361
+ const TOTAL = 4;
362
+ // ── Step 1: Find Angular/Nx workspace config ───────────────────────────────
363
+ step(1, TOTAL, 'Locating Angular workspace…');
364
+ const workspaceConfigPath = findWorkspaceConfig();
365
+ if (!workspaceConfigPath) {
366
+ err('Could not find angular.json, workspace.json, nx.json, or project.json. Are you inside an Angular project?');
367
+ process.exit(1);
368
+ }
369
+ ok(`Found ${path.basename(workspaceConfigPath)} at ${c.gray}${path.relative(process.cwd(), workspaceConfigPath)}${c.reset}`);
370
+ const projectRoot = path.dirname(workspaceConfigPath);
371
+ // ── Step 2: Check/install package ─────────────────────────────────────────
372
+ step(2, TOTAL, 'Checking package installation…');
373
+ if (isPackageInstalled(projectRoot)) {
374
+ ok('angular-render-scan is already installed');
375
+ }
376
+ else {
377
+ const pm = getPackageManager(projectRoot);
378
+ const cmd = installCommand(pm);
379
+ warn(`angular-render-scan is not installed. Run:\n\n ${c.cyan}${cmd}${c.reset}\n`);
380
+ info('After installing, run this command again to complete setup.');
381
+ process.exit(0);
382
+ }
383
+ // ── Step 3: Locate source files ────────────────────────────────────────────
384
+ step(3, TOTAL, 'Locating Angular entry files…');
385
+ const mainTsPath = findMainTs(workspaceConfigPath);
386
+ if (!mainTsPath) {
387
+ err('Could not locate main.ts. Please patch manually (see docs).');
388
+ process.exit(1);
389
+ }
390
+ ok(`Found main.ts at ${c.gray}${path.relative(projectRoot, mainTsPath)}${c.reset}`);
391
+ const appConfigPath = findAppConfig(mainTsPath);
392
+ if (appConfigPath) {
393
+ ok(`Found app.config.ts at ${c.gray}${path.relative(projectRoot, appConfigPath)}${c.reset}`);
394
+ }
395
+ else {
396
+ info('app.config.ts not found — will patch main.ts directly');
397
+ }
398
+ // ── Step 4: Patch files ────────────────────────────────────────────────────
399
+ step(4, TOTAL, 'Patching bootstrap configuration…');
400
+ if (scriptTagOnly) {
401
+ // Script-tag mode: patch index.html
402
+ const indexHtmlPath = findIndexHtml(workspaceConfigPath);
403
+ if (!indexHtmlPath) {
404
+ err('Could not locate index.html. Please add the script tag manually.');
405
+ process.exit(1);
406
+ }
407
+ const indexContent = readFile(indexHtmlPath);
408
+ if (alreadyPatched(indexContent)) {
409
+ ok(`${c.gray}${path.relative(projectRoot, indexHtmlPath)}${c.reset} already contains angular-render-scan`);
410
+ }
411
+ else {
412
+ const patched = patchIndexHtml(indexContent);
413
+ if (!patched) {
414
+ err('Could not patch index.html. Please add the script tag manually.');
415
+ process.exit(1);
416
+ }
417
+ if (!dryRun)
418
+ writeFile(indexHtmlPath, patched);
419
+ ok(`Patched ${c.gray}${path.relative(projectRoot, indexHtmlPath)}${c.reset} with CDN script tag`);
420
+ }
421
+ }
422
+ else {
423
+ // Provider mode: patch app.config.ts or main.ts
424
+ const targetPath = appConfigPath ?? mainTsPath;
425
+ const targetContent = readFile(targetPath);
426
+ const targetRelative = path.relative(projectRoot, targetPath);
427
+ if (!force && alreadyPatched(targetContent)) {
428
+ ok(`${c.gray}${targetRelative}${c.reset} already contains angular-render-scan — skipping (use --force to re-patch)`);
429
+ }
430
+ else {
431
+ const patcher = appConfigPath ? patchAppConfig : patchMainTs;
432
+ const patched = patcher(targetContent);
433
+ if (!patched) {
434
+ warn(`Could not auto-patch ${c.gray}${targetRelative}${c.reset}. Please add manually:`);
435
+ log('');
436
+ log(` ${c.cyan}${IMPORT_STATEMENT}${c.reset}`);
437
+ log('');
438
+ log(` // Add to providers array in bootstrapApplication() or ApplicationConfig:`);
439
+ log(` ${c.cyan}${PROVIDER_CALL}${c.reset}`);
440
+ log('');
441
+ }
442
+ else {
443
+ if (!dryRun)
444
+ writeFile(targetPath, patched);
445
+ ok(`Patched ${c.gray}${targetRelative}${c.reset}`);
446
+ if (dryRun) {
447
+ log('');
448
+ log(`${c.gray}--- dry run output ---${c.reset}`);
449
+ log(patched);
450
+ log(`${c.gray}--- end dry run ---${c.reset}`);
451
+ }
452
+ }
453
+ }
454
+ }
455
+ // ── Done ──────────────────────────────────────────────────────────────────
456
+ log('');
457
+ log(`${c.bold}${c.green} Setup complete!${c.reset}`);
458
+ log('');
459
+ log(` Start your app in development mode and interact with the UI:`);
460
+ log(` ${c.gray}ng serve${c.reset}`);
461
+ log('');
462
+ log(` You should see component render outlines on screen.`);
463
+ log(` The toolbar appears in the bottom-right corner.`);
464
+ log('');
465
+ log(` ${c.dim}Docs: https://github.com/edisonaugusthy/angular-render-scan${c.reset}`);
466
+ log('');
467
+ }
468
+ export async function run() {
469
+ const [, , command, ...rest] = process.argv;
470
+ if (!command || command === 'init') {
471
+ await runInit(rest);
472
+ return;
473
+ }
474
+ if (command === '--help' || command === '-h' || command === 'help') {
475
+ log('');
476
+ log(`${c.bold}Usage:${c.reset} npx angular-render-scan-cli [command] [options]`);
477
+ log('');
478
+ log(`${c.bold}Commands:${c.reset}`);
479
+ log(' init Auto-patch your Angular project (default)');
480
+ log('');
481
+ log(`${c.bold}Options for init:${c.reset}`);
482
+ log(' --force Patch even if angular-render-scan is already present');
483
+ log(' --dry-run Show what would be patched without writing files');
484
+ log(' --script-tag Use CDN script tag in index.html instead of provider');
485
+ log('');
486
+ log(`${c.bold}Examples:${c.reset}`);
487
+ log(' npx angular-render-scan-cli init');
488
+ log(' npx angular-render-scan-cli init --dry-run');
489
+ log(' npx angular-render-scan-cli init --script-tag');
490
+ log('');
491
+ return;
492
+ }
493
+ if (command === '--version' || command === '-v') {
494
+ const pkgPath = new URL('../package.json', import.meta.url);
495
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
496
+ log(pkg.version);
497
+ return;
498
+ }
499
+ err(`Unknown command: ${command}. Run with --help for usage.`);
500
+ process.exit(1);
501
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "angular-render-scan-cli",
3
+ "version": "0.1.10",
4
+ "description": "CLI installer for angular-render-scan — zero-friction Angular performance profiler setup.",
5
+ "keywords": [
6
+ "angular",
7
+ "cli",
8
+ "performance",
9
+ "change-detection",
10
+ "angular-render-scan"
11
+ ],
12
+ "license": "MIT",
13
+ "homepage": "https://github.com/edisonaugusthy/angular-render-scan#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/edisonaugusthy/angular-render-scan/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/edisonaugusthy/angular-render-scan.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json",
26
+ "prepack": "npm run build"
27
+ },
28
+ "bin": {
29
+ "angular-render-scan-cli": "./bin/cli.js",
30
+ "angular-render-scan": "./bin/cli.js"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "bin",
35
+ "package.json",
36
+ "README.md"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ },
42
+ "dependencies": {
43
+ "picocolors": "^1.1.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.9.1",
47
+ "typescript": "^5.9.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ }
52
+ }