angular-grab-monorepo 0.1.3

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 (99) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.playwright-mcp/console-2026-03-07T02-49-38-061Z.log +37 -0
  3. package/.playwright-mcp/console-2026-03-07T02-51-03-493Z.log +26 -0
  4. package/.playwright-mcp/console-2026-03-07T02-51-25-431Z.log +15 -0
  5. package/.playwright-mcp/console-2026-03-07T02-52-02-980Z.log +91 -0
  6. package/.playwright-mcp/page-2026-03-07T02-52-09-791Z.png +0 -0
  7. package/LICENSE +21 -0
  8. package/README.md +215 -0
  9. package/examples/angular-19-app/.editorconfig +17 -0
  10. package/examples/angular-19-app/.vscode/extensions.json +4 -0
  11. package/examples/angular-19-app/.vscode/launch.json +20 -0
  12. package/examples/angular-19-app/.vscode/mcp.json +9 -0
  13. package/examples/angular-19-app/.vscode/tasks.json +42 -0
  14. package/examples/angular-19-app/README.md +59 -0
  15. package/examples/angular-19-app/angular.json +79 -0
  16. package/examples/angular-19-app/package.json +42 -0
  17. package/examples/angular-19-app/public/favicon.ico +0 -0
  18. package/examples/angular-19-app/src/app/app.config.ts +13 -0
  19. package/examples/angular-19-app/src/app/app.css +37 -0
  20. package/examples/angular-19-app/src/app/app.html +25 -0
  21. package/examples/angular-19-app/src/app/app.routes.ts +3 -0
  22. package/examples/angular-19-app/src/app/app.spec.ts +23 -0
  23. package/examples/angular-19-app/src/app/app.ts +12 -0
  24. package/examples/angular-19-app/src/app/button/button.component.ts +25 -0
  25. package/examples/angular-19-app/src/app/card/card.component.ts +33 -0
  26. package/examples/angular-19-app/src/app/header/header.component.ts +31 -0
  27. package/examples/angular-19-app/src/app/popover/popover.component.ts +133 -0
  28. package/examples/angular-19-app/src/index.html +13 -0
  29. package/examples/angular-19-app/src/main.ts +6 -0
  30. package/examples/angular-19-app/src/styles.css +1 -0
  31. package/examples/angular-19-app/tsconfig.app.json +15 -0
  32. package/examples/angular-19-app/tsconfig.json +33 -0
  33. package/examples/angular-19-app/tsconfig.spec.json +15 -0
  34. package/package.json +22 -0
  35. package/packages/angular-grab/builders.json +14 -0
  36. package/packages/angular-grab/package.json +96 -0
  37. package/packages/angular-grab/src/angular/__tests__/context-builder.test.ts +216 -0
  38. package/packages/angular-grab/src/angular/angular-grab.service.ts +62 -0
  39. package/packages/angular-grab/src/angular/index.ts +13 -0
  40. package/packages/angular-grab/src/angular/provide-angular-grab.ts +22 -0
  41. package/packages/angular-grab/src/angular/resolvers/component-resolver.ts +71 -0
  42. package/packages/angular-grab/src/angular/resolvers/context-builder.ts +86 -0
  43. package/packages/angular-grab/src/angular/resolvers/ng-utils.ts +14 -0
  44. package/packages/angular-grab/src/angular/resolvers/source-resolver.ts +61 -0
  45. package/packages/angular-grab/src/builder/__tests__/builder.test.ts +72 -0
  46. package/packages/angular-grab/src/builder/builders/application/index.ts +13 -0
  47. package/packages/angular-grab/src/builder/builders/application/schema.json +7 -0
  48. package/packages/angular-grab/src/builder/builders/dev-server/index.ts +9 -0
  49. package/packages/angular-grab/src/builder/builders/dev-server/schema.json +7 -0
  50. package/packages/angular-grab/src/builder/index.ts +3 -0
  51. package/packages/angular-grab/src/cli/__tests__/cli.test.ts +239 -0
  52. package/packages/angular-grab/src/cli/commands/init.ts +106 -0
  53. package/packages/angular-grab/src/cli/index.ts +15 -0
  54. package/packages/angular-grab/src/cli/utils/detect-project.ts +78 -0
  55. package/packages/angular-grab/src/cli/utils/modify-angular-json.ts +42 -0
  56. package/packages/angular-grab/src/cli/utils/modify-app-config.ts +42 -0
  57. package/packages/angular-grab/src/core/__tests__/generate-snippet.test.ts +149 -0
  58. package/packages/angular-grab/src/core/__tests__/plugin-registry.test.ts +286 -0
  59. package/packages/angular-grab/src/core/__tests__/store.test.ts +118 -0
  60. package/packages/angular-grab/src/core/__tests__/utils.test.ts +85 -0
  61. package/packages/angular-grab/src/core/clipboard/copy.ts +104 -0
  62. package/packages/angular-grab/src/core/clipboard/generate-snippet.ts +38 -0
  63. package/packages/angular-grab/src/core/constants.ts +10 -0
  64. package/packages/angular-grab/src/core/grab.ts +596 -0
  65. package/packages/angular-grab/src/core/index.global.ts +13 -0
  66. package/packages/angular-grab/src/core/index.ts +19 -0
  67. package/packages/angular-grab/src/core/keyboard/keyboard-handler.ts +163 -0
  68. package/packages/angular-grab/src/core/overlay/crosshair.ts +107 -0
  69. package/packages/angular-grab/src/core/overlay/freeze-overlay.ts +239 -0
  70. package/packages/angular-grab/src/core/overlay/overlay-renderer.ts +180 -0
  71. package/packages/angular-grab/src/core/overlay/select-feedback.ts +108 -0
  72. package/packages/angular-grab/src/core/overlay/toast.ts +175 -0
  73. package/packages/angular-grab/src/core/picker/element-picker.ts +114 -0
  74. package/packages/angular-grab/src/core/plugins/plugin-registry.ts +83 -0
  75. package/packages/angular-grab/src/core/store.ts +52 -0
  76. package/packages/angular-grab/src/core/toolbar/actions-menu.ts +178 -0
  77. package/packages/angular-grab/src/core/toolbar/comment-popover.ts +235 -0
  78. package/packages/angular-grab/src/core/toolbar/copy-actions.ts +98 -0
  79. package/packages/angular-grab/src/core/toolbar/history-popover.ts +245 -0
  80. package/packages/angular-grab/src/core/toolbar/theme-manager.ts +188 -0
  81. package/packages/angular-grab/src/core/toolbar/toolbar-icons.ts +29 -0
  82. package/packages/angular-grab/src/core/toolbar/toolbar-renderer.ts +239 -0
  83. package/packages/angular-grab/src/core/types.ts +139 -0
  84. package/packages/angular-grab/src/core/utils.ts +16 -0
  85. package/packages/angular-grab/src/esbuild-plugin/__tests__/transform.test.ts +174 -0
  86. package/packages/angular-grab/src/esbuild-plugin/index.ts +3 -0
  87. package/packages/angular-grab/src/esbuild-plugin/plugin.ts +29 -0
  88. package/packages/angular-grab/src/esbuild-plugin/scan.ts +105 -0
  89. package/packages/angular-grab/src/esbuild-plugin/transform.ts +152 -0
  90. package/packages/angular-grab/src/vite-plugin/__tests__/plugin.test.ts +84 -0
  91. package/packages/angular-grab/src/vite-plugin/index.ts +19 -0
  92. package/packages/angular-grab/src/webpack-plugin/__tests__/plugin.test.ts +72 -0
  93. package/packages/angular-grab/src/webpack-plugin/index.ts +2 -0
  94. package/packages/angular-grab/src/webpack-plugin/loader.ts +15 -0
  95. package/packages/angular-grab/src/webpack-plugin/plugin.ts +20 -0
  96. package/packages/angular-grab/tsconfig.json +15 -0
  97. package/packages/angular-grab/tsup.config.ts +119 -0
  98. package/pnpm-workspace.yaml +3 -0
  99. package/turbo.json +21 -0
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ const PACKAGE_ROOT = join(__dirname, '..', '..', '..');
6
+
7
+ describe('builders.json', () => {
8
+ const buildersJson = JSON.parse(
9
+ readFileSync(join(PACKAGE_ROOT, 'builders.json'), 'utf8'),
10
+ );
11
+
12
+ it('has a builders property', () => {
13
+ expect(buildersJson.builders).toBeDefined();
14
+ expect(typeof buildersJson.builders).toBe('object');
15
+ });
16
+
17
+ it('defines an application builder', () => {
18
+ const app = buildersJson.builders.application;
19
+ expect(app).toBeDefined();
20
+ expect(app.implementation).toBeDefined();
21
+ expect(app.schema).toBeDefined();
22
+ expect(app.description).toBeDefined();
23
+ });
24
+
25
+ it('defines a dev-server builder', () => {
26
+ const devServer = buildersJson.builders['dev-server'];
27
+ expect(devServer).toBeDefined();
28
+ expect(devServer.implementation).toBeDefined();
29
+ expect(devServer.schema).toBeDefined();
30
+ expect(devServer.description).toBeDefined();
31
+ });
32
+
33
+ it('application builder points to correct implementation path', () => {
34
+ const app = buildersJson.builders.application;
35
+ expect(app.implementation).toContain('builders/application');
36
+ });
37
+
38
+ it('dev-server builder points to correct implementation path', () => {
39
+ const devServer = buildersJson.builders['dev-server'];
40
+ expect(devServer.implementation).toContain('builders/dev-server');
41
+ });
42
+ });
43
+
44
+ describe('package.json', () => {
45
+ const packageJson = JSON.parse(
46
+ readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'),
47
+ );
48
+
49
+ it('declares builders field pointing to builders.json', () => {
50
+ expect(packageJson.builders).toBe('./builders.json');
51
+ });
52
+
53
+ it('exports builders.json', () => {
54
+ expect(packageJson.exports['./builders.json']).toBe('./builders.json');
55
+ });
56
+ });
57
+
58
+ describe('schema files', () => {
59
+ it('application schema is valid JSON', () => {
60
+ const schema = JSON.parse(
61
+ readFileSync(join(PACKAGE_ROOT, 'src', 'builder', 'builders', 'application', 'schema.json'), 'utf8'),
62
+ );
63
+ expect(schema.type).toBe('object');
64
+ });
65
+
66
+ it('dev-server schema is valid JSON', () => {
67
+ const schema = JSON.parse(
68
+ readFileSync(join(PACKAGE_ROOT, 'src', 'builder', 'builders', 'dev-server', 'schema.json'), 'utf8'),
69
+ );
70
+ expect(schema.type).toBe('object');
71
+ });
72
+ });
@@ -0,0 +1,13 @@
1
+ import { createBuilder } from '@angular-devkit/architect';
2
+ import { buildApplication } from '@angular/build';
3
+ import { angularGrabEsbuildPlugin } from '../../../esbuild-plugin';
4
+
5
+ export default createBuilder(async function* (options: any, context) {
6
+ const isProduction = !!options.optimization;
7
+ yield* buildApplication(options, context, {
8
+ codePlugins: [angularGrabEsbuildPlugin({
9
+ rootDir: context.workspaceRoot,
10
+ enabled: !isProduction,
11
+ })],
12
+ });
13
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Angular Grab Application Builder",
4
+ "description": "Wraps @angular/build:application with angular-grab source injection",
5
+ "type": "object",
6
+ "additionalProperties": true
7
+ }
@@ -0,0 +1,9 @@
1
+ import { createBuilder } from '@angular-devkit/architect';
2
+ import { executeDevServerBuilder } from '@angular/build';
3
+ import { angularGrabEsbuildPlugin } from '../../../esbuild-plugin';
4
+
5
+ export default createBuilder(async function* (options: any, context) {
6
+ yield* executeDevServerBuilder(options, context, {
7
+ buildPlugins: [angularGrabEsbuildPlugin({ rootDir: context.workspaceRoot })],
8
+ });
9
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Angular Grab Dev Server Builder",
4
+ "description": "Wraps @angular/build:dev-server with angular-grab source injection",
5
+ "type": "object",
6
+ "additionalProperties": true
7
+ }
@@ -0,0 +1,3 @@
1
+ // angular-grab/builder
2
+ // This package provides Angular CLI builders that inject angular-grab source location mapping.
3
+ // Builders are resolved via builders.json — this file is not directly imported by users.
@@ -0,0 +1,239 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { findAngularJson, detectProject } from '../utils/detect-project';
6
+
7
+ describe('findAngularJson', () => {
8
+ let tempDir: string;
9
+
10
+ beforeEach(() => {
11
+ tempDir = mkdtempSync(join(tmpdir(), 'cli-test-'));
12
+ });
13
+
14
+ afterEach(() => {
15
+ rmSync(tempDir, { recursive: true, force: true });
16
+ });
17
+
18
+ it('finds angular.json in the given directory', () => {
19
+ writeFileSync(join(tempDir, 'angular.json'), '{}');
20
+ const result = findAngularJson(tempDir);
21
+ expect(result).toBe(join(tempDir, 'angular.json'));
22
+ });
23
+
24
+ it('finds angular.json in a parent directory', () => {
25
+ writeFileSync(join(tempDir, 'angular.json'), '{}');
26
+ const subDir = join(tempDir, 'src', 'app');
27
+ mkdirSync(subDir, { recursive: true });
28
+
29
+ const result = findAngularJson(subDir);
30
+ expect(result).toBe(join(tempDir, 'angular.json'));
31
+ });
32
+
33
+ it('returns null when no angular.json exists', () => {
34
+ const result = findAngularJson(tempDir);
35
+ expect(result).toBeNull();
36
+ });
37
+ });
38
+
39
+ describe('detectProject', () => {
40
+ let tempDir: string;
41
+
42
+ beforeEach(() => {
43
+ tempDir = mkdtempSync(join(tmpdir(), 'cli-test-'));
44
+ });
45
+
46
+ afterEach(() => {
47
+ rmSync(tempDir, { recursive: true, force: true });
48
+ });
49
+
50
+ function writeAngularJson(content: object): string {
51
+ const path = join(tempDir, 'angular.json');
52
+ writeFileSync(path, JSON.stringify(content));
53
+ return path;
54
+ }
55
+
56
+ it('detects project name from angular.json', () => {
57
+ const path = writeAngularJson({
58
+ projects: {
59
+ 'my-app': {
60
+ sourceRoot: 'src',
61
+ architect: {
62
+ build: { builder: '@angular/build:application' },
63
+ },
64
+ },
65
+ },
66
+ });
67
+
68
+ const info = detectProject(path);
69
+ expect(info.projectName).toBe('my-app');
70
+ });
71
+
72
+ it('detects "application" builder type', () => {
73
+ const path = writeAngularJson({
74
+ projects: {
75
+ app: {
76
+ architect: {
77
+ build: { builder: '@angular/build:application' },
78
+ },
79
+ },
80
+ },
81
+ });
82
+
83
+ const info = detectProject(path);
84
+ expect(info.builderType).toBe('application');
85
+ });
86
+
87
+ it('detects "browser-esbuild" builder type', () => {
88
+ const path = writeAngularJson({
89
+ projects: {
90
+ app: {
91
+ architect: {
92
+ build: { builder: '@angular-devkit/build-angular:browser-esbuild' },
93
+ },
94
+ },
95
+ },
96
+ });
97
+
98
+ const info = detectProject(path);
99
+ expect(info.builderType).toBe('browser-esbuild');
100
+ });
101
+
102
+ it('detects "browser" builder type', () => {
103
+ const path = writeAngularJson({
104
+ projects: {
105
+ app: {
106
+ architect: {
107
+ build: { builder: '@angular-devkit/build-angular:browser' },
108
+ },
109
+ },
110
+ },
111
+ });
112
+
113
+ const info = detectProject(path);
114
+ expect(info.builderType).toBe('browser');
115
+ });
116
+
117
+ it('detects angular-grab:application as "application"', () => {
118
+ const path = writeAngularJson({
119
+ projects: {
120
+ app: {
121
+ architect: {
122
+ build: { builder: 'angular-grab:application' },
123
+ },
124
+ },
125
+ },
126
+ });
127
+
128
+ const info = detectProject(path);
129
+ expect(info.builderType).toBe('application');
130
+ });
131
+
132
+ it('defaults sourceRoot to "src" when not specified', () => {
133
+ const path = writeAngularJson({
134
+ projects: {
135
+ app: {
136
+ architect: {
137
+ build: { builder: '@angular/build:application' },
138
+ },
139
+ },
140
+ },
141
+ });
142
+
143
+ const info = detectProject(path);
144
+ expect(info.sourceRoot).toBe('src');
145
+ });
146
+
147
+ it('uses custom sourceRoot when specified', () => {
148
+ const path = writeAngularJson({
149
+ projects: {
150
+ app: {
151
+ sourceRoot: 'projects/app/src',
152
+ architect: {
153
+ build: { builder: '@angular/build:application' },
154
+ },
155
+ },
156
+ },
157
+ });
158
+
159
+ const info = detectProject(path);
160
+ expect(info.sourceRoot).toBe('projects/app/src');
161
+ });
162
+
163
+ it('throws when no projects are found', () => {
164
+ const path = writeAngularJson({ projects: {} });
165
+ expect(() => detectProject(path)).toThrow('No projects found');
166
+ });
167
+
168
+ it('throws when no build target is found', () => {
169
+ const path = writeAngularJson({
170
+ projects: {
171
+ app: { architect: {} },
172
+ },
173
+ });
174
+
175
+ expect(() => detectProject(path)).toThrow('No build target found');
176
+ });
177
+
178
+ it('detects npm as default package manager', () => {
179
+ const path = writeAngularJson({
180
+ projects: {
181
+ app: {
182
+ architect: {
183
+ build: { builder: '@angular/build:application' },
184
+ },
185
+ },
186
+ },
187
+ });
188
+
189
+ const info = detectProject(path);
190
+ expect(info.packageManager).toBe('npm');
191
+ });
192
+
193
+ it('detects pnpm when pnpm-lock.yaml exists', () => {
194
+ writeFileSync(join(tempDir, 'pnpm-lock.yaml'), '');
195
+ const path = writeAngularJson({
196
+ projects: {
197
+ app: {
198
+ architect: {
199
+ build: { builder: '@angular/build:application' },
200
+ },
201
+ },
202
+ },
203
+ });
204
+
205
+ const info = detectProject(path);
206
+ expect(info.packageManager).toBe('pnpm');
207
+ });
208
+
209
+ it('detects yarn when yarn.lock exists', () => {
210
+ writeFileSync(join(tempDir, 'yarn.lock'), '');
211
+ const path = writeAngularJson({
212
+ projects: {
213
+ app: {
214
+ architect: {
215
+ build: { builder: '@angular/build:application' },
216
+ },
217
+ },
218
+ },
219
+ });
220
+
221
+ const info = detectProject(path);
222
+ expect(info.packageManager).toBe('yarn');
223
+ });
224
+
225
+ it('supports "targets" as an alternative to "architect"', () => {
226
+ const path = writeAngularJson({
227
+ projects: {
228
+ app: {
229
+ targets: {
230
+ build: { builder: '@angular/build:application' },
231
+ },
232
+ },
233
+ },
234
+ });
235
+
236
+ const info = detectProject(path);
237
+ expect(info.builderType).toBe('application');
238
+ });
239
+ });
@@ -0,0 +1,106 @@
1
+ import { execSync } from 'child_process';
2
+ import { findAngularJson, detectProject, type ProjectInfo } from '../utils/detect-project';
3
+ import { modifyAngularJson } from '../utils/modify-angular-json';
4
+ import { modifyAppConfig } from '../utils/modify-app-config';
5
+
6
+ const PACKAGES = ['angular-grab'];
7
+
8
+ function log(msg: string): void {
9
+ console.log(`\x1b[36m[angular-grab]\x1b[0m ${msg}`);
10
+ }
11
+
12
+ function warn(msg: string): void {
13
+ console.log(`\x1b[33m[angular-grab]\x1b[0m ${msg}`);
14
+ }
15
+
16
+ function getInstallCommand(pm: ProjectInfo['packageManager']): string {
17
+ const deps = PACKAGES.join(' ');
18
+ switch (pm) {
19
+ case 'pnpm':
20
+ return `pnpm add -D ${deps}`;
21
+ case 'yarn':
22
+ return `yarn add -D ${deps}`;
23
+ default:
24
+ return `npm install -D ${deps}`;
25
+ }
26
+ }
27
+
28
+ export async function init(): Promise<void> {
29
+ log('Initializing angular-grab...\n');
30
+
31
+ // 1. Find angular.json
32
+ const angularJsonPath = findAngularJson();
33
+ if (!angularJsonPath) {
34
+ throw new Error(
35
+ 'Could not find angular.json. Are you in an Angular project directory?',
36
+ );
37
+ }
38
+
39
+ // 2. Detect project info
40
+ const info = detectProject(angularJsonPath);
41
+ log(`Found project: ${info.projectName}`);
42
+ log(`Builder type: ${info.builderType}`);
43
+ log(`Package manager: ${info.packageManager}`);
44
+ console.log('');
45
+
46
+ // 3. Reject legacy builders
47
+ if (info.builderType === 'browser') {
48
+ throw new Error(
49
+ 'angular-grab requires the "application" builder (Angular 17+).\n' +
50
+ 'Please migrate from "@angular-devkit/build-angular:browser" to "@angular/build:application".\n' +
51
+ 'See: https://angular.dev/tools/cli/build-system-migration',
52
+ );
53
+ }
54
+
55
+ if (info.builderType === 'browser-esbuild') {
56
+ throw new Error(
57
+ 'angular-grab requires the "application" builder, not the transitional "browser-esbuild" builder.\n' +
58
+ 'Please migrate from "@angular-devkit/build-angular:browser-esbuild" to "@angular/build:application".\n' +
59
+ 'See: https://angular.dev/tools/cli/build-system-migration',
60
+ );
61
+ }
62
+
63
+ // 4. Install packages
64
+ log('Installing packages...');
65
+ const installCmd = getInstallCommand(info.packageManager);
66
+ try {
67
+ execSync(installCmd, {
68
+ cwd: info.projectRoot,
69
+ stdio: 'inherit',
70
+ });
71
+ } catch {
72
+ warn('Package installation failed. You may need to install manually:');
73
+ warn(` ${installCmd}`);
74
+ console.log('');
75
+ }
76
+
77
+ // 5. Modify angular.json
78
+ log('Updating angular.json...');
79
+ const jsonModified = modifyAngularJson(info.angularJsonPath, info.projectName);
80
+ if (jsonModified) {
81
+ log(' Swapped builders to angular-grab');
82
+ } else {
83
+ warn(' angular.json builders were already configured or could not be modified');
84
+ }
85
+
86
+ // 6. Try to add provideAngularGrab() to app config
87
+ log('Updating app.config.ts...');
88
+ const configModified = modifyAppConfig(info.projectRoot, info.sourceRoot);
89
+ if (configModified) {
90
+ log(' Added provideAngularGrab() to providers');
91
+ } else {
92
+ warn(' Could not modify app.config.ts (already configured or not found)');
93
+ warn(' Please add manually:');
94
+ warn(" import { provideAngularGrab } from 'angular-grab/angular';");
95
+ warn(' // then add provideAngularGrab() to your providers array');
96
+ }
97
+
98
+ console.log('');
99
+ log('\x1b[32mDone!\x1b[0m angular-grab is ready.');
100
+ console.log('');
101
+ console.log(' Next steps:');
102
+ console.log(' 1. Run \x1b[1mng serve\x1b[0m');
103
+ console.log(' 2. Hold \x1b[1mCmd+C\x1b[0m (Mac) or \x1b[1mCtrl+C\x1b[0m (Windows) and hover over elements');
104
+ console.log(' 3. Click to copy element context to clipboard');
105
+ console.log('');
106
+ }
@@ -0,0 +1,15 @@
1
+ import { init } from './commands/init';
2
+
3
+ const args = process.argv.slice(2);
4
+ const command = args[0];
5
+
6
+ if (command === 'init' || !command) {
7
+ init().catch((err) => {
8
+ console.error('\x1b[31mError:\x1b[0m', err.message);
9
+ process.exit(1);
10
+ });
11
+ } else {
12
+ console.error(`Unknown command: ${command}`);
13
+ console.log('\nUsage: npx angular-grab init');
14
+ process.exit(1);
15
+ }
@@ -0,0 +1,78 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+
4
+ export interface ProjectInfo {
5
+ angularJsonPath: string;
6
+ projectRoot: string;
7
+ projectName: string;
8
+ sourceRoot: string;
9
+ builderType: 'application' | 'browser-esbuild' | 'browser';
10
+ packageManager: 'pnpm' | 'yarn' | 'npm';
11
+ }
12
+
13
+ export function findAngularJson(startDir: string = process.cwd()): string | null {
14
+ let dir = startDir;
15
+ while (true) {
16
+ const candidate = join(dir, 'angular.json');
17
+ if (existsSync(candidate)) return candidate;
18
+
19
+ const parent = dirname(dir);
20
+ if (parent === dir) return null;
21
+ dir = parent;
22
+ }
23
+ }
24
+
25
+ export function detectProject(angularJsonPath: string): ProjectInfo {
26
+ const projectRoot = dirname(angularJsonPath);
27
+ const raw = readFileSync(angularJsonPath, 'utf8');
28
+ const angularJson = JSON.parse(raw);
29
+
30
+ const projects = angularJson.projects || {};
31
+ const projectNames = Object.keys(projects);
32
+ if (projectNames.length === 0) {
33
+ throw new Error('No projects found in angular.json');
34
+ }
35
+
36
+ const projectName = projectNames[0];
37
+ const project = projects[projectName];
38
+ if (!project) {
39
+ throw new Error(`Project "${projectName}" not found in angular.json`);
40
+ }
41
+
42
+ const sourceRoot = project.sourceRoot || 'src';
43
+ const targets = project.architect || project.targets;
44
+ const buildTarget = targets?.build;
45
+ if (!buildTarget) {
46
+ throw new Error(`No build target found for project "${projectName}"`);
47
+ }
48
+
49
+ const builder: string = buildTarget.builder || '';
50
+ let builderType: ProjectInfo['builderType'];
51
+
52
+ if (builder.includes(':application') || builder.includes('angular-grab:application')) {
53
+ builderType = 'application';
54
+ } else if (builder.includes(':browser-esbuild')) {
55
+ builderType = 'browser-esbuild';
56
+ } else if (builder.includes(':browser')) {
57
+ builderType = 'browser';
58
+ } else {
59
+ builderType = 'application';
60
+ }
61
+
62
+ const packageManager = detectPackageManager(projectRoot);
63
+
64
+ return {
65
+ angularJsonPath,
66
+ projectRoot,
67
+ projectName,
68
+ sourceRoot,
69
+ builderType,
70
+ packageManager,
71
+ };
72
+ }
73
+
74
+ function detectPackageManager(root: string): 'pnpm' | 'yarn' | 'npm' {
75
+ if (existsSync(join(root, 'pnpm-lock.yaml'))) return 'pnpm';
76
+ if (existsSync(join(root, 'yarn.lock'))) return 'yarn';
77
+ return 'npm';
78
+ }
@@ -0,0 +1,42 @@
1
+ import { readFileSync, writeFileSync } from 'fs';
2
+
3
+ export function modifyAngularJson(angularJsonPath: string, projectName: string): boolean {
4
+ const raw = readFileSync(angularJsonPath, 'utf8');
5
+ const angularJson = JSON.parse(raw);
6
+
7
+ const project = angularJson.projects?.[projectName];
8
+ const targets = project?.architect || project?.targets;
9
+ if (!targets) return false;
10
+
11
+ let modified = false;
12
+
13
+ const buildTarget = targets.build;
14
+ if (buildTarget?.builder) {
15
+ const currentBuilder: string = buildTarget.builder;
16
+ if (
17
+ currentBuilder === '@angular/build:application' ||
18
+ currentBuilder === '@angular-devkit/build-angular:application'
19
+ ) {
20
+ buildTarget.builder = 'angular-grab:application';
21
+ modified = true;
22
+ }
23
+ }
24
+
25
+ const serveTarget = targets.serve;
26
+ if (serveTarget?.builder) {
27
+ const currentBuilder: string = serveTarget.builder;
28
+ if (
29
+ currentBuilder === '@angular/build:dev-server' ||
30
+ currentBuilder === '@angular-devkit/build-angular:dev-server'
31
+ ) {
32
+ serveTarget.builder = 'angular-grab:dev-server';
33
+ modified = true;
34
+ }
35
+ }
36
+
37
+ if (modified) {
38
+ writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n', 'utf8');
39
+ }
40
+
41
+ return modified;
42
+ }
@@ -0,0 +1,42 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ export function modifyAppConfig(projectRoot: string, sourceRoot: string): boolean {
5
+ const configPath = join(projectRoot, sourceRoot, 'app', 'app.config.ts');
6
+ if (!existsSync(configPath)) return false;
7
+
8
+ let content = readFileSync(configPath, 'utf8');
9
+
10
+ // Skip if already has provideAngularGrab
11
+ if (content.includes('provideAngularGrab')) return false;
12
+
13
+ // Add import for provideAngularGrab
14
+ const importStatement = "import { provideAngularGrab } from 'angular-grab/angular';";
15
+
16
+ // Find the last import block end by matching `from '...'` or `from "..."` lines
17
+ const lines = content.split('\n');
18
+ let lastImportEndIndex = -1;
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const trimmed = lines[i].trimStart();
21
+ if (trimmed.startsWith('import ') || /\}\s*from\s+['"]/.test(trimmed) || /from\s+['"]/.test(trimmed)) {
22
+ lastImportEndIndex = i;
23
+ }
24
+ }
25
+
26
+ if (lastImportEndIndex >= 0) {
27
+ lines.splice(lastImportEndIndex + 1, 0, importStatement);
28
+ content = lines.join('\n');
29
+ } else {
30
+ content = importStatement + '\n' + content;
31
+ }
32
+
33
+ // Try to add provideAngularGrab() to the providers array
34
+ const providersMatch = content.match(/providers\s*:\s*\[/);
35
+ if (providersMatch && providersMatch.index != null) {
36
+ const insertPos = providersMatch.index + providersMatch[0].length;
37
+ content = content.slice(0, insertPos) + '\n provideAngularGrab(),' + content.slice(insertPos);
38
+ }
39
+
40
+ writeFileSync(configPath, content, 'utf8');
41
+ return true;
42
+ }