@ng-annotate/angular 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/schematics/ng-add/index.js +121 -28
- package/schematics/ng-add/index.ts +104 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-annotate/angular",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"schematics": "./schematics/collection.json",
|
|
5
5
|
"description": "Angular library for ng-annotate-mcp — browser overlay for annotating components and routing instructions to an AI agent",
|
|
6
6
|
"keywords": [
|
|
@@ -1,8 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.default = default_1;
|
|
4
37
|
const schematics_1 = require("@angular-devkit/schematics");
|
|
5
38
|
const tasks_1 = require("@angular-devkit/schematics/tasks");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
/** Insert `newImport` on the line after the last import statement (handles multi-line imports and blank lines between groups). */
|
|
42
|
+
function insertAfterLastImport(content, newImport) {
|
|
43
|
+
const lines = content.split('\n');
|
|
44
|
+
let lastImportLine = -1;
|
|
45
|
+
let inImport = false;
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
if (/^import\s/.test(lines[i]))
|
|
48
|
+
inImport = true;
|
|
49
|
+
if (inImport) {
|
|
50
|
+
lastImportLine = i;
|
|
51
|
+
if (lines[i].includes(';'))
|
|
52
|
+
inImport = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (lastImportLine < 0)
|
|
56
|
+
return newImport + '\n' + content;
|
|
57
|
+
lines.splice(lastImportLine + 1, 0, newImport);
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
/** Insert `newProvider` as the first item in the `providers: [...]` array, preserving indentation style. */
|
|
61
|
+
function insertIntoProviders(content, newProvider) {
|
|
62
|
+
return content.replace(/^(\s*)providers\s*:\s*\[/m, (match, indent) => {
|
|
63
|
+
return `${indent}providers: [\n${indent} ${newProvider},`;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
6
66
|
const MIN_ANGULAR_MAJOR = 21;
|
|
7
67
|
function checkAngularVersion() {
|
|
8
68
|
return (tree) => {
|
|
@@ -34,9 +94,7 @@ function addVitePlugin() {
|
|
|
34
94
|
context.logger.info('@ng-annotate/vite-plugin vite plugin already present, skipping.');
|
|
35
95
|
return;
|
|
36
96
|
}
|
|
37
|
-
|
|
38
|
-
content = content.replace(/(^import .+$(\r?\n)?)+/m, (match) => match + "import { ngAnnotateMcp } from '@ng-annotate/vite-plugin';\n");
|
|
39
|
-
// Insert spread into plugins array (handles `plugins: [` or `plugins:[`)
|
|
97
|
+
content = insertAfterLastImport(content, "import { ngAnnotateMcp } from '@ng-annotate/vite-plugin';");
|
|
40
98
|
content = content.replace(/plugins\s*:\s*\[/, 'plugins: [...ngAnnotateMcp(), ');
|
|
41
99
|
tree.overwrite(viteConfigPath, content);
|
|
42
100
|
context.logger.info(`✅ Added ngAnnotateMcp() to ${viteConfigPath}`);
|
|
@@ -61,20 +119,49 @@ function addProviders() {
|
|
|
61
119
|
context.logger.info('provideNgAnnotate already present, skipping.');
|
|
62
120
|
return;
|
|
63
121
|
}
|
|
64
|
-
|
|
65
|
-
content = content
|
|
66
|
-
// Insert into providers array
|
|
67
|
-
content = content.replace(/providers\s*:\s*\[/, 'providers: [\n provideNgAnnotate(),');
|
|
122
|
+
content = insertAfterLastImport(content, "import { provideNgAnnotate } from '@ng-annotate/angular';");
|
|
123
|
+
content = insertIntoProviders(content, 'provideNgAnnotate()');
|
|
68
124
|
tree.overwrite(appConfigPath, content);
|
|
69
125
|
context.logger.info(`✅ Added provideNgAnnotate() to ${appConfigPath}`);
|
|
70
126
|
};
|
|
71
127
|
}
|
|
128
|
+
/** Walk up from startDir until we find a .git folder; returns that directory or null. */
|
|
129
|
+
function findGitRoot(startDir) {
|
|
130
|
+
let dir = path.resolve(startDir);
|
|
131
|
+
while (true) {
|
|
132
|
+
if (fs.existsSync(path.join(dir, '.git')))
|
|
133
|
+
return dir;
|
|
134
|
+
const parent = path.dirname(dir);
|
|
135
|
+
if (parent === dir)
|
|
136
|
+
return null;
|
|
137
|
+
dir = parent;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Write a config file outside the schematic Tree (e.g. to a parent git root).
|
|
142
|
+
* Uses fs directly — intentionally bypasses Tree so it works for out-of-project paths.
|
|
143
|
+
*/
|
|
144
|
+
function writeOutsideTree(absPath, content, context) {
|
|
145
|
+
const dir = path.dirname(absPath);
|
|
146
|
+
if (!fs.existsSync(dir))
|
|
147
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
148
|
+
if (fs.existsSync(absPath)) {
|
|
149
|
+
context.logger.info(`${path.basename(absPath)} already exists at workspace root, skipping.`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
fs.writeFileSync(absPath, content, 'utf-8');
|
|
153
|
+
context.logger.info(`✅ Created ${path.basename(absPath)} at workspace root (${dir})`);
|
|
154
|
+
}
|
|
72
155
|
function addMcpConfig(options) {
|
|
73
156
|
return (tree, context) => {
|
|
74
|
-
const projectRoot = process.cwd()
|
|
75
|
-
const env = { NG_ANNOTATE_PROJECT_ROOT: projectRoot };
|
|
157
|
+
const projectRoot = process.cwd();
|
|
158
|
+
const env = { NG_ANNOTATE_PROJECT_ROOT: projectRoot.replace(/\\/g, '/') };
|
|
76
159
|
const isWindows = process.platform === 'win32';
|
|
77
160
|
const { aiTool } = options;
|
|
161
|
+
// Detect monorepo: if git root is a parent of the Angular project, VS Code is likely
|
|
162
|
+
// opened there — write configs to both the project folder and the git root.
|
|
163
|
+
const gitRoot = findGitRoot(projectRoot);
|
|
164
|
+
const isSubproject = gitRoot !== null && path.resolve(gitRoot) !== path.resolve(projectRoot);
|
|
78
165
|
if (aiTool === 'other') {
|
|
79
166
|
const claudeConfig = JSON.stringify({
|
|
80
167
|
mcpServers: {
|
|
@@ -104,41 +191,47 @@ function addMcpConfig(options) {
|
|
|
104
191
|
}
|
|
105
192
|
// .mcp.json — Claude Code (needs cmd /c on Windows to invoke npx.cmd)
|
|
106
193
|
if (aiTool === 'claude-code' || aiTool === 'both') {
|
|
194
|
+
const mcpConfig = JSON.stringify({
|
|
195
|
+
mcpServers: {
|
|
196
|
+
'ng-annotate': isWindows
|
|
197
|
+
? { command: 'cmd', args: ['/c', 'npx', '-y', '@ng-annotate/mcp-server'], env }
|
|
198
|
+
: { command: 'npx', args: ['-y', '@ng-annotate/mcp-server'], env },
|
|
199
|
+
},
|
|
200
|
+
}, null, 2) + '\n';
|
|
107
201
|
if (!tree.exists('.mcp.json')) {
|
|
108
|
-
|
|
109
|
-
mcpServers: {
|
|
110
|
-
'ng-annotate': isWindows
|
|
111
|
-
? { command: 'cmd', args: ['/c', 'npx', '-y', '@ng-annotate/mcp-server'], env }
|
|
112
|
-
: { command: 'npx', args: ['-y', '@ng-annotate/mcp-server'], env },
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
tree.create('.mcp.json', JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
202
|
+
tree.create('.mcp.json', mcpConfig);
|
|
116
203
|
context.logger.info('✅ Created .mcp.json');
|
|
117
204
|
}
|
|
118
205
|
else {
|
|
119
206
|
context.logger.info('.mcp.json already exists, skipping.');
|
|
120
207
|
}
|
|
208
|
+
if (isSubproject) {
|
|
209
|
+
writeOutsideTree(path.join(gitRoot, '.mcp.json'), mcpConfig, context);
|
|
210
|
+
}
|
|
121
211
|
}
|
|
122
212
|
// .vscode/mcp.json — VS Code Copilot
|
|
123
213
|
if (aiTool === 'vscode' || aiTool === 'both') {
|
|
214
|
+
const vscodeMcpConfig = JSON.stringify({
|
|
215
|
+
servers: {
|
|
216
|
+
'ng-annotate': {
|
|
217
|
+
type: 'stdio',
|
|
218
|
+
command: 'npx',
|
|
219
|
+
args: ['-y', '@ng-annotate/mcp-server'],
|
|
220
|
+
env,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
}, null, 2) + '\n';
|
|
124
224
|
const vscodeMcpPath = '.vscode/mcp.json';
|
|
125
225
|
if (!tree.exists(vscodeMcpPath)) {
|
|
126
|
-
|
|
127
|
-
servers: {
|
|
128
|
-
'ng-annotate': {
|
|
129
|
-
type: 'stdio',
|
|
130
|
-
command: 'npx',
|
|
131
|
-
args: ['-y', '@ng-annotate/mcp-server'],
|
|
132
|
-
env,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
tree.create(vscodeMcpPath, JSON.stringify(vscodeMcpConfig, null, 2) + '\n');
|
|
226
|
+
tree.create(vscodeMcpPath, vscodeMcpConfig);
|
|
137
227
|
context.logger.info('✅ Created .vscode/mcp.json');
|
|
138
228
|
}
|
|
139
229
|
else {
|
|
140
230
|
context.logger.info('.vscode/mcp.json already exists, skipping.');
|
|
141
231
|
}
|
|
232
|
+
if (isSubproject) {
|
|
233
|
+
writeOutsideTree(path.join(gitRoot, '.vscode', 'mcp.json'), vscodeMcpConfig, context);
|
|
234
|
+
}
|
|
142
235
|
}
|
|
143
236
|
};
|
|
144
237
|
}
|
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
import { Rule, SchematicContext, Tree, chain, SchematicsException } from '@angular-devkit/schematics';
|
|
2
2
|
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
/** Insert `newImport` on the line after the last import statement (handles multi-line imports and blank lines between groups). */
|
|
7
|
+
function insertAfterLastImport(content: string, newImport: string): string {
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
let lastImportLine = -1;
|
|
10
|
+
let inImport = false;
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
if (/^import\s/.test(lines[i])) inImport = true;
|
|
14
|
+
if (inImport) {
|
|
15
|
+
lastImportLine = i;
|
|
16
|
+
if (lines[i].includes(';')) inImport = false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (lastImportLine < 0) return newImport + '\n' + content;
|
|
21
|
+
lines.splice(lastImportLine + 1, 0, newImport);
|
|
22
|
+
return lines.join('\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Insert `newProvider` as the first item in the `providers: [...]` array, preserving indentation style. */
|
|
26
|
+
function insertIntoProviders(content: string, newProvider: string): string {
|
|
27
|
+
return content.replace(/^(\s*)providers\s*:\s*\[/m, (match, indent) => {
|
|
28
|
+
return `${indent}providers: [\n${indent} ${newProvider},`;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
3
31
|
|
|
4
32
|
interface Options {
|
|
5
33
|
aiTool: 'claude-code' | 'vscode' | 'both' | 'other';
|
|
@@ -49,13 +77,7 @@ function addVitePlugin(): Rule {
|
|
|
49
77
|
return;
|
|
50
78
|
}
|
|
51
79
|
|
|
52
|
-
|
|
53
|
-
content = content.replace(
|
|
54
|
-
/(^import .+$(\r?\n)?)+/m,
|
|
55
|
-
(match) => match + "import { ngAnnotateMcp } from '@ng-annotate/vite-plugin';\n",
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// Insert spread into plugins array (handles `plugins: [` or `plugins:[`)
|
|
80
|
+
content = insertAfterLastImport(content, "import { ngAnnotateMcp } from '@ng-annotate/vite-plugin';");
|
|
59
81
|
content = content.replace(/plugins\s*:\s*\[/, 'plugins: [...ngAnnotateMcp(), ');
|
|
60
82
|
|
|
61
83
|
tree.overwrite(viteConfigPath, content);
|
|
@@ -88,27 +110,53 @@ function addProviders(): Rule {
|
|
|
88
110
|
return;
|
|
89
111
|
}
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
content = content
|
|
93
|
-
/(^import .+$(\r?\n)?)+/m,
|
|
94
|
-
(match) => match + "import { provideNgAnnotate } from '@ng-annotate/angular';\n",
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// Insert into providers array
|
|
98
|
-
content = content.replace(/providers\s*:\s*\[/, 'providers: [\n provideNgAnnotate(),');
|
|
113
|
+
content = insertAfterLastImport(content, "import { provideNgAnnotate } from '@ng-annotate/angular';");
|
|
114
|
+
content = insertIntoProviders(content, 'provideNgAnnotate()');
|
|
99
115
|
|
|
100
116
|
tree.overwrite(appConfigPath, content);
|
|
101
117
|
context.logger.info(`✅ Added provideNgAnnotate() to ${appConfigPath}`);
|
|
102
118
|
};
|
|
103
119
|
}
|
|
104
120
|
|
|
121
|
+
/** Walk up from startDir until we find a .git folder; returns that directory or null. */
|
|
122
|
+
function findGitRoot(startDir: string): string | null {
|
|
123
|
+
let dir = path.resolve(startDir);
|
|
124
|
+
while (true) {
|
|
125
|
+
if (fs.existsSync(path.join(dir, '.git'))) return dir;
|
|
126
|
+
const parent = path.dirname(dir);
|
|
127
|
+
if (parent === dir) return null;
|
|
128
|
+
dir = parent;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Write a config file outside the schematic Tree (e.g. to a parent git root).
|
|
134
|
+
* Uses fs directly — intentionally bypasses Tree so it works for out-of-project paths.
|
|
135
|
+
*/
|
|
136
|
+
function writeOutsideTree(absPath: string, content: string, context: SchematicContext): void {
|
|
137
|
+
const dir = path.dirname(absPath);
|
|
138
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
139
|
+
if (fs.existsSync(absPath)) {
|
|
140
|
+
context.logger.info(`${path.basename(absPath)} already exists at workspace root, skipping.`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
fs.writeFileSync(absPath, content, 'utf-8');
|
|
144
|
+
context.logger.info(`✅ Created ${path.basename(absPath)} at workspace root (${dir})`);
|
|
145
|
+
}
|
|
146
|
+
|
|
105
147
|
function addMcpConfig(options: Options): Rule {
|
|
106
148
|
return (tree: Tree, context: SchematicContext) => {
|
|
107
|
-
const projectRoot = process.cwd()
|
|
108
|
-
const env = { NG_ANNOTATE_PROJECT_ROOT: projectRoot };
|
|
149
|
+
const projectRoot = process.cwd();
|
|
150
|
+
const env = { NG_ANNOTATE_PROJECT_ROOT: projectRoot.replace(/\\/g, '/') };
|
|
109
151
|
const isWindows = process.platform === 'win32';
|
|
110
152
|
const { aiTool } = options;
|
|
111
153
|
|
|
154
|
+
// Detect monorepo: if git root is a parent of the Angular project, VS Code is likely
|
|
155
|
+
// opened there — write configs to both the project folder and the git root.
|
|
156
|
+
const gitRoot = findGitRoot(projectRoot);
|
|
157
|
+
const isSubproject =
|
|
158
|
+
gitRoot !== null && path.resolve(gitRoot) !== path.resolve(projectRoot);
|
|
159
|
+
|
|
112
160
|
if (aiTool === 'other') {
|
|
113
161
|
const claudeConfig = JSON.stringify(
|
|
114
162
|
{
|
|
@@ -149,40 +197,60 @@ function addMcpConfig(options: Options): Rule {
|
|
|
149
197
|
|
|
150
198
|
// .mcp.json — Claude Code (needs cmd /c on Windows to invoke npx.cmd)
|
|
151
199
|
if (aiTool === 'claude-code' || aiTool === 'both') {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
200
|
+
const mcpConfig =
|
|
201
|
+
JSON.stringify(
|
|
202
|
+
{
|
|
203
|
+
mcpServers: {
|
|
204
|
+
'ng-annotate': isWindows
|
|
205
|
+
? { command: 'cmd', args: ['/c', 'npx', '-y', '@ng-annotate/mcp-server'], env }
|
|
206
|
+
: { command: 'npx', args: ['-y', '@ng-annotate/mcp-server'], env },
|
|
207
|
+
},
|
|
158
208
|
},
|
|
159
|
-
|
|
160
|
-
|
|
209
|
+
null,
|
|
210
|
+
2,
|
|
211
|
+
) + '\n';
|
|
212
|
+
|
|
213
|
+
if (!tree.exists('.mcp.json')) {
|
|
214
|
+
tree.create('.mcp.json', mcpConfig);
|
|
161
215
|
context.logger.info('✅ Created .mcp.json');
|
|
162
216
|
} else {
|
|
163
217
|
context.logger.info('.mcp.json already exists, skipping.');
|
|
164
218
|
}
|
|
219
|
+
|
|
220
|
+
if (isSubproject) {
|
|
221
|
+
writeOutsideTree(path.join(gitRoot!, '.mcp.json'), mcpConfig, context);
|
|
222
|
+
}
|
|
165
223
|
}
|
|
166
224
|
|
|
167
225
|
// .vscode/mcp.json — VS Code Copilot
|
|
168
226
|
if (aiTool === 'vscode' || aiTool === 'both') {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
227
|
+
const vscodeMcpConfig =
|
|
228
|
+
JSON.stringify(
|
|
229
|
+
{
|
|
230
|
+
servers: {
|
|
231
|
+
'ng-annotate': {
|
|
232
|
+
type: 'stdio',
|
|
233
|
+
command: 'npx',
|
|
234
|
+
args: ['-y', '@ng-annotate/mcp-server'],
|
|
235
|
+
env,
|
|
236
|
+
},
|
|
178
237
|
},
|
|
179
238
|
},
|
|
180
|
-
|
|
181
|
-
|
|
239
|
+
null,
|
|
240
|
+
2,
|
|
241
|
+
) + '\n';
|
|
242
|
+
|
|
243
|
+
const vscodeMcpPath = '.vscode/mcp.json';
|
|
244
|
+
if (!tree.exists(vscodeMcpPath)) {
|
|
245
|
+
tree.create(vscodeMcpPath, vscodeMcpConfig);
|
|
182
246
|
context.logger.info('✅ Created .vscode/mcp.json');
|
|
183
247
|
} else {
|
|
184
248
|
context.logger.info('.vscode/mcp.json already exists, skipping.');
|
|
185
249
|
}
|
|
250
|
+
|
|
251
|
+
if (isSubproject) {
|
|
252
|
+
writeOutsideTree(path.join(gitRoot!, '.vscode', 'mcp.json'), vscodeMcpConfig, context);
|
|
253
|
+
}
|
|
186
254
|
}
|
|
187
255
|
};
|
|
188
256
|
}
|