@lensmcp/nx-plugin 1.18.4 → 1.18.6
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/executors/agent-build/agent-build.js +2 -86
- package/executors/agent-dev/agent-dev.js +4 -425
- package/executors/agent-verify/agent-verify.js +3 -166
- package/generators/init/init.js +9 -167
- package/generators/setup-nest/setup-nest.js +8 -281
- package/generators/setup-vite/setup-vite.js +5 -125
- package/index.js +1 -21
- package/lens-frontend.js +1 -205
- package/package.json +1 -1
|
@@ -1,166 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const node_child_process_1 = require("node:child_process");
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
|
-
/**
|
|
8
|
-
* `agent-verify` — the deterministic verification loop the agent runs
|
|
9
|
-
* to know "did my fix work?".
|
|
10
|
-
*
|
|
11
|
-
* Phase 2 covers the frontend slice: typecheck (`tsc --noEmit`), lint
|
|
12
|
-
* (`eslint`), and build (`vite build`). Each stage runs sequentially;
|
|
13
|
-
* a stage failure marks the verify as `failed` but later stages still
|
|
14
|
-
* run so the agent gets a complete picture, not just the first error.
|
|
15
|
-
*
|
|
16
|
-
* The report is written to `.lensmcp/verifications/<project>-<ts>.json`
|
|
17
|
-
* and to `.lensmcp/verifications/latest.json` for easy resource access.
|
|
18
|
-
* The MCP-side `agent://latest-verification` resource (Phase 2.5) reads
|
|
19
|
-
* from `latest.json`.
|
|
20
|
-
*/
|
|
21
|
-
async function agentVerifyExecutor(options, context) {
|
|
22
|
-
const opts = {
|
|
23
|
-
kind: options.kind ?? 'vite-react',
|
|
24
|
-
skipTypecheck: options.skipTypecheck ?? false,
|
|
25
|
-
skipLint: options.skipLint ?? false,
|
|
26
|
-
skipBuild: options.skipBuild ?? false,
|
|
27
|
-
projectRoot: options.projectRoot,
|
|
28
|
-
};
|
|
29
|
-
const projectName = context.projectName ?? null;
|
|
30
|
-
const projectRoot = opts.projectRoot
|
|
31
|
-
? (0, node_path_1.resolve)(context.root, opts.projectRoot)
|
|
32
|
-
: projectName && context.projectsConfigurations
|
|
33
|
-
? (0, node_path_1.resolve)(context.root, context.projectsConfigurations.projects[projectName]?.root ?? '.')
|
|
34
|
-
: context.root;
|
|
35
|
-
const startedAt = Date.now();
|
|
36
|
-
const stages = [];
|
|
37
|
-
// 1. typecheck — `tsc --noEmit -p <root>` if a tsconfig is present.
|
|
38
|
-
if (!opts.skipTypecheck) {
|
|
39
|
-
const tscBin = locateBin('tsc', [projectRoot, context.root]);
|
|
40
|
-
const tsconfig = resolveFirstExisting(['tsconfig.json'], projectRoot);
|
|
41
|
-
if (!tscBin || !tsconfig) {
|
|
42
|
-
stages.push({ name: 'typecheck', result: 'skipped', durationMs: 0, summary: 'no tsc/tsconfig' });
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
const t0 = Date.now();
|
|
46
|
-
const r = runStageCommand('tsc --noEmit', tscBin, ['--noEmit', '-p', tsconfig], projectRoot);
|
|
47
|
-
stages.push({
|
|
48
|
-
name: 'typecheck',
|
|
49
|
-
result: r.status === 0 ? 'passed' : 'failed',
|
|
50
|
-
durationMs: Date.now() - t0,
|
|
51
|
-
exitCode: r.status ?? -1,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
stages.push({ name: 'typecheck', result: 'skipped', durationMs: 0 });
|
|
57
|
-
}
|
|
58
|
-
// 2. lint — `eslint .` if eslint is present.
|
|
59
|
-
if (!opts.skipLint) {
|
|
60
|
-
const eslintBin = locateBin('eslint', [projectRoot, context.root]);
|
|
61
|
-
if (!eslintBin) {
|
|
62
|
-
stages.push({ name: 'lint', result: 'skipped', durationMs: 0, summary: 'no eslint' });
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const t0 = Date.now();
|
|
66
|
-
const r = runStageCommand('eslint .', eslintBin, ['.'], projectRoot);
|
|
67
|
-
stages.push({
|
|
68
|
-
name: 'lint',
|
|
69
|
-
result: r.status === 0 ? 'passed' : 'failed',
|
|
70
|
-
durationMs: Date.now() - t0,
|
|
71
|
-
exitCode: r.status ?? -1,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
stages.push({ name: 'lint', result: 'skipped', durationMs: 0 });
|
|
77
|
-
}
|
|
78
|
-
// 3. build — `vite build` if vite is present.
|
|
79
|
-
if (!opts.skipBuild) {
|
|
80
|
-
const viteBin = locateBin('vite', [projectRoot, context.root]);
|
|
81
|
-
if (!viteBin) {
|
|
82
|
-
stages.push({ name: 'build', result: 'skipped', durationMs: 0, summary: 'no vite' });
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
const viteConfig = resolveFirstExisting(['vite.config.ts', 'vite.config.js', 'vite.config.mjs'], projectRoot) ??
|
|
86
|
-
(0, node_path_1.join)(projectRoot, 'vite.config.ts');
|
|
87
|
-
const t0 = Date.now();
|
|
88
|
-
const r = runStageCommand('vite build', viteBin, ['build', '--config', viteConfig, projectRoot], projectRoot);
|
|
89
|
-
stages.push({
|
|
90
|
-
name: 'build',
|
|
91
|
-
result: r.status === 0 ? 'passed' : 'failed',
|
|
92
|
-
durationMs: Date.now() - t0,
|
|
93
|
-
exitCode: r.status ?? -1,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
stages.push({ name: 'build', result: 'skipped', durationMs: 0 });
|
|
99
|
-
}
|
|
100
|
-
const status = stages.some((s) => s.result === 'failed') ? 'failed' : 'passed';
|
|
101
|
-
const report = {
|
|
102
|
-
schemaVersion: 1,
|
|
103
|
-
project: projectName,
|
|
104
|
-
timestamp: new Date(startedAt).toISOString(),
|
|
105
|
-
durationMs: Date.now() - startedAt,
|
|
106
|
-
status,
|
|
107
|
-
stages,
|
|
108
|
-
};
|
|
109
|
-
const outDir = (0, node_path_1.join)(context.root, '.lensmcp', 'verifications');
|
|
110
|
-
try {
|
|
111
|
-
(0, node_fs_1.mkdirSync)(outDir, { recursive: true });
|
|
112
|
-
const ts = startedAt;
|
|
113
|
-
// Project names like `@lensmcp/example-web` contain `/`. Flatten so
|
|
114
|
-
// the report filename is a single path segment.
|
|
115
|
-
const safeName = (projectName ?? 'app').replace(/[^a-zA-Z0-9_-]+/g, '-');
|
|
116
|
-
const filename = `${safeName}-${ts}.json`;
|
|
117
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outDir, filename), JSON.stringify(report, null, 2) + '\n');
|
|
118
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outDir, 'latest.json'), JSON.stringify(report, null, 2) + '\n');
|
|
119
|
-
}
|
|
120
|
-
catch (e) {
|
|
121
|
-
console.warn(`[agent-verify] could not write report: ${e.message}`);
|
|
122
|
-
}
|
|
123
|
-
for (const s of report.stages) {
|
|
124
|
-
console.log(`[agent-verify] ${s.name}: ${s.result}${s.exitCode !== undefined ? ` (exit ${s.exitCode})` : ''}`);
|
|
125
|
-
}
|
|
126
|
-
console.log(`[agent-verify] ${status} in ${report.durationMs}ms`);
|
|
127
|
-
return { success: status === 'passed' };
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Run a verify sub-command, capturing its output and re-emitting it INDENTED.
|
|
131
|
-
*
|
|
132
|
-
* agent-verify deliberately runs commands that may fail — it IS the agent's
|
|
133
|
-
* edit→verify loop, so a failing stage is a normal, expected outcome. In CI a
|
|
134
|
-
* raw tool error printed at column 0 (`file(line,col): error TS....`) is picked
|
|
135
|
-
* up by GitHub's problem matchers and turned into a spurious build *annotation*.
|
|
136
|
-
* Indenting every line by two spaces keeps the output fully readable while
|
|
137
|
-
* defeating the matchers (they anchor on a non-space first character), so a
|
|
138
|
-
* verify failure never masquerades as a CI error.
|
|
139
|
-
*/
|
|
140
|
-
function runStageCommand(label, bin, args, cwd) {
|
|
141
|
-
console.log(`[agent-verify] ${label}`);
|
|
142
|
-
const r = (0, node_child_process_1.spawnSync)(bin, args, { cwd, encoding: 'utf8' });
|
|
143
|
-
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`;
|
|
144
|
-
if (out.trim())
|
|
145
|
-
process.stdout.write(out.replace(/^(?=.)/gm, ' '));
|
|
146
|
-
return r;
|
|
147
|
-
}
|
|
148
|
-
function locateBin(name, roots) {
|
|
149
|
-
for (const root of roots) {
|
|
150
|
-
const candidate = (0, node_path_1.join)(root, 'node_modules', '.bin', name);
|
|
151
|
-
if ((0, node_fs_1.existsSync)(candidate))
|
|
152
|
-
return candidate;
|
|
153
|
-
}
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
function resolveFirstExisting(names, root) {
|
|
157
|
-
for (const n of names) {
|
|
158
|
-
const p = (0, node_path_1.join)(root, n);
|
|
159
|
-
if ((0, node_fs_1.existsSync)(p))
|
|
160
|
-
return p;
|
|
161
|
-
}
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
// Unused helpers, kept available for future re-use by tools that may
|
|
165
|
-
// drive the report differently.
|
|
166
|
-
void node_path_1.dirname;
|
|
1
|
+
"use strict";var k=Object.defineProperty;var d=(s,e)=>k(s,"name",{value:e,configurable:!0});var g=Object.defineProperty,l=d((s,e)=>g(s,"name",{value:e,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=agentVerifyExecutor;const node_child_process_1=require("node:child_process"),node_fs_1=require("node:fs"),node_path_1=require("node:path");async function agentVerifyExecutor(s,e){const i={kind:s.kind??"vite-react",skipTypecheck:s.skipTypecheck??!1,skipLint:s.skipLint??!1,skipBuild:s.skipBuild??!1,projectRoot:s.projectRoot},n=e.projectName??null,o=i.projectRoot?(0,node_path_1.resolve)(e.root,i.projectRoot):n&&e.projectsConfigurations?(0,node_path_1.resolve)(e.root,e.projectsConfigurations.projects[n]?.root??"."):e.root,c=Date.now(),r=[];if(i.skipTypecheck)r.push({name:"typecheck",result:"skipped",durationMs:0});else{const t=locateBin("tsc",[o,e.root]),a=resolveFirstExisting(["tsconfig.json"],o);if(!t||!a)r.push({name:"typecheck",result:"skipped",durationMs:0,summary:"no tsc/tsconfig"});else{const u=Date.now(),p=runStageCommand("tsc --noEmit",t,["--noEmit","-p",a],o);r.push({name:"typecheck",result:p.status===0?"passed":"failed",durationMs:Date.now()-u,exitCode:p.status??-1})}}if(i.skipLint)r.push({name:"lint",result:"skipped",durationMs:0});else{const t=locateBin("eslint",[o,e.root]);if(!t)r.push({name:"lint",result:"skipped",durationMs:0,summary:"no eslint"});else{const a=Date.now(),u=runStageCommand("eslint .",t,["."],o);r.push({name:"lint",result:u.status===0?"passed":"failed",durationMs:Date.now()-a,exitCode:u.status??-1})}}if(i.skipBuild)r.push({name:"build",result:"skipped",durationMs:0});else{const t=locateBin("vite",[o,e.root]);if(!t)r.push({name:"build",result:"skipped",durationMs:0,summary:"no vite"});else{const a=resolveFirstExisting(["vite.config.ts","vite.config.js","vite.config.mjs"],o)??(0,node_path_1.join)(o,"vite.config.ts"),u=Date.now(),p=runStageCommand("vite build",t,["build","--config",a,o],o);r.push({name:"build",result:p.status===0?"passed":"failed",durationMs:Date.now()-u,exitCode:p.status??-1})}}const m=r.some(t=>t.result==="failed")?"failed":"passed",f={schemaVersion:1,project:n,timestamp:new Date(c).toISOString(),durationMs:Date.now()-c,status:m,stages:r},y=(0,node_path_1.join)(e.root,".lensmcp","verifications");try{(0,node_fs_1.mkdirSync)(y,{recursive:!0});const t=c,a=`${(n??"app").replace(/[^a-zA-Z0-9_-]+/g,"-")}-${t}.json`;(0,node_fs_1.writeFileSync)((0,node_path_1.join)(y,a),JSON.stringify(f,null,2)+`
|
|
2
|
+
`),(0,node_fs_1.writeFileSync)((0,node_path_1.join)(y,"latest.json"),JSON.stringify(f,null,2)+`
|
|
3
|
+
`)}catch(t){console.warn(`[agent-verify] could not write report: ${t.message}`)}for(const t of f.stages)console.log(`[agent-verify] ${t.name}: ${t.result}${t.exitCode!==void 0?` (exit ${t.exitCode})`:""}`);return console.log(`[agent-verify] ${m} in ${f.durationMs}ms`),{success:m==="passed"}}d(agentVerifyExecutor,"agentVerifyExecutor"),l(agentVerifyExecutor,"agentVerifyExecutor");function runStageCommand(s,e,i,n){console.log(`[agent-verify] ${s}`);const o=(0,node_child_process_1.spawnSync)(e,i,{cwd:n,encoding:"utf8"}),c=`${o.stdout??""}${o.stderr??""}`;return c.trim()&&process.stdout.write(c.replace(/^(?=.)/gm," ")),o}d(runStageCommand,"runStageCommand"),l(runStageCommand,"runStageCommand");function locateBin(s,e){for(const i of e){const n=(0,node_path_1.join)(i,"node_modules",".bin",s);if((0,node_fs_1.existsSync)(n))return n}}d(locateBin,"locateBin"),l(locateBin,"locateBin");function resolveFirstExisting(s,e){for(const i of s){const n=(0,node_path_1.join)(e,i);if((0,node_fs_1.existsSync)(n))return n}}d(resolveFirstExisting,"resolveFirstExisting"),l(resolveFirstExisting,"resolveFirstExisting"),node_path_1.dirname;
|
package/generators/init/init.js
CHANGED
|
@@ -1,167 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
retention: {
|
|
11
|
-
sessions: 5,
|
|
12
|
-
events: 5000,
|
|
13
|
-
screenshots: 50,
|
|
14
|
-
buildReports: 20,
|
|
15
|
-
visualFrames: 200,
|
|
16
|
-
heapSnapshots: 3,
|
|
17
|
-
tracesWithErrors: 20,
|
|
18
|
-
verifications: 10,
|
|
19
|
-
},
|
|
20
|
-
redaction: {
|
|
21
|
-
headers: ['authorization', 'cookie', 'set-cookie', 'x-api-key'],
|
|
22
|
-
paths: ['password', 'token', 'secret', 'apiKey', 'api_key'],
|
|
23
|
-
},
|
|
24
|
-
channels: {
|
|
25
|
-
blockingStatusChanged: { enabled: true, minSeverity: 'error' },
|
|
26
|
-
memoryLeakSuspected: { enabled: true, minSeverity: 'warning' },
|
|
27
|
-
bundleRegression: { enabled: true, minSeverity: 'warning' },
|
|
28
|
-
visualViolationBlocking: { enabled: true, minSeverity: 'error' },
|
|
29
|
-
verificationCompleted: { enabled: true, minSeverity: 'info' },
|
|
30
|
-
},
|
|
31
|
-
// Tunable detection thresholds — when each signal fires. Edit to taste;
|
|
32
|
-
// the server reads these at boot (see @lensmcp/core resolveThresholds).
|
|
33
|
-
thresholds: {
|
|
34
|
-
dbInLoop: 5,
|
|
35
|
-
nPlusOne: 3,
|
|
36
|
-
renderStorm: 5,
|
|
37
|
-
slowRouteMs: 500,
|
|
38
|
-
slowRenderMs: 16,
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
const LENSMCP_GITIGNORE_MARKER = '# LensMCP runtime artifacts (per-session storage)';
|
|
42
|
-
const LENSMCP_GITIGNORE_ENTRY = '.lensmcp/';
|
|
43
|
-
async function initGenerator(tree, rawOptions = {}) {
|
|
44
|
-
const options = {
|
|
45
|
-
skipFormat: rawOptions.skipFormat ?? false,
|
|
46
|
-
registerHostConfig: rawOptions.registerHostConfig ?? 'auto',
|
|
47
|
-
};
|
|
48
|
-
const steps = [];
|
|
49
|
-
// 1. Register the plugin + lensmcp config in nx.json (idempotent).
|
|
50
|
-
if (tree.exists('nx.json')) {
|
|
51
|
-
(0, devkit_1.updateJson)(tree, 'nx.json', (nx) => {
|
|
52
|
-
nx.plugins = Array.isArray(nx.plugins) ? nx.plugins : [];
|
|
53
|
-
const alreadyRegistered = nx.plugins.some((p) => {
|
|
54
|
-
if (typeof p === 'string')
|
|
55
|
-
return p === '@lensmcp/nx-plugin';
|
|
56
|
-
if (p && typeof p === 'object' && 'plugin' in p) {
|
|
57
|
-
return p.plugin === '@lensmcp/nx-plugin';
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
});
|
|
61
|
-
if (!alreadyRegistered) {
|
|
62
|
-
nx.plugins.push('@lensmcp/nx-plugin');
|
|
63
|
-
steps.push('nx.json: registered @lensmcp/nx-plugin');
|
|
64
|
-
}
|
|
65
|
-
const existing = (nx.lensmcp ?? {});
|
|
66
|
-
nx.lensmcp = mergeDeep(DEFAULT_LENSMCP_CONFIG, existing);
|
|
67
|
-
if (!('lensmcp' in nx) || Object.keys(existing).length === 0) {
|
|
68
|
-
steps.push('nx.json: added workspace-wide `lensmcp` config block');
|
|
69
|
-
}
|
|
70
|
-
return nx;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
throw new Error('No nx.json found at the workspace root. Are you in a Nx workspace?');
|
|
75
|
-
}
|
|
76
|
-
// 2. .gitignore — add `.lensmcp/` line (idempotent).
|
|
77
|
-
const gitignorePath = '.gitignore';
|
|
78
|
-
const existing = tree.exists(gitignorePath) ? tree.read(gitignorePath, 'utf-8') ?? '' : '';
|
|
79
|
-
if (!existing.split('\n').some((l) => l.trim() === LENSMCP_GITIGNORE_ENTRY)) {
|
|
80
|
-
const trailingNewline = existing.endsWith('\n') ? '' : '\n';
|
|
81
|
-
tree.write(gitignorePath, `${existing}${trailingNewline}\n${LENSMCP_GITIGNORE_MARKER}\n${LENSMCP_GITIGNORE_ENTRY}\n`);
|
|
82
|
-
steps.push(`${gitignorePath}: added \`${LENSMCP_GITIGNORE_ENTRY}\``);
|
|
83
|
-
}
|
|
84
|
-
// 3. Reserve the .lensmcp/ directory with a sentinel file.
|
|
85
|
-
const keepPath = (0, devkit_1.joinPathFragments)('.lensmcp', '.keep');
|
|
86
|
-
if (!tree.exists(keepPath)) {
|
|
87
|
-
tree.write(keepPath, '');
|
|
88
|
-
steps.push(`${keepPath}: created`);
|
|
89
|
-
}
|
|
90
|
-
// 4. Write an install trail so subsequent re-runs can read what was
|
|
91
|
-
// done and (in future generators) un-install precisely.
|
|
92
|
-
const trailPath = (0, devkit_1.joinPathFragments)('.lensmcp', 'install-trail.json');
|
|
93
|
-
const existingTrail = tree.exists(trailPath)
|
|
94
|
-
? JSON.parse(tree.read(trailPath, 'utf-8') ?? '{}')
|
|
95
|
-
: undefined;
|
|
96
|
-
const trail = existingTrail ?? {
|
|
97
|
-
schemaVersion: 1,
|
|
98
|
-
installedAt: new Date().toISOString(),
|
|
99
|
-
steps: [],
|
|
100
|
-
};
|
|
101
|
-
trail.steps.push(...steps);
|
|
102
|
-
tree.write(trailPath, JSON.stringify(trail, null, 2) + '\n');
|
|
103
|
-
// 5. Register the LensMCP MCP server in the workspace `.mcp.json` so any
|
|
104
|
-
// coding agent (Claude Code, Cursor, …) opening this project gets the
|
|
105
|
-
// lens automatically. Workspace-local edits happen here, in the Nx
|
|
106
|
-
// tree (idempotent). Global agent configs (~/.claude, ~/.cursor) are
|
|
107
|
-
// only touched by `lensmcp` when `registerHostConfig: 'always'`,
|
|
108
|
-
// since those live outside the workspace.
|
|
109
|
-
if (options.registerHostConfig !== 'never') {
|
|
110
|
-
registerWorkspaceMcpConfig(tree, steps);
|
|
111
|
-
}
|
|
112
|
-
if (!options.skipFormat) {
|
|
113
|
-
await (0, devkit_1.formatFiles)(tree);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/** The stdio MCP server entry a host agent uses to launch LensMCP. */
|
|
117
|
-
const LENSMCP_MCP_SERVER_ENTRY = {
|
|
118
|
-
command: 'npx',
|
|
119
|
-
args: ['-y', 'lensmcp', 'mcp'],
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* Ensure `<workspace>/.mcp.json` has a `lensmcp` MCP server entry.
|
|
123
|
-
* Idempotent: creates the file if absent, merges into existing
|
|
124
|
-
* `mcpServers`, and never clobbers a `lensmcp` entry the user already
|
|
125
|
-
* customised.
|
|
126
|
-
*/
|
|
127
|
-
function registerWorkspaceMcpConfig(tree, steps) {
|
|
128
|
-
const path = '.mcp.json';
|
|
129
|
-
let config = {};
|
|
130
|
-
if (tree.exists(path)) {
|
|
131
|
-
try {
|
|
132
|
-
config = JSON.parse(tree.read(path, 'utf-8') ?? '{}');
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
config = {};
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (!config || typeof config !== 'object')
|
|
139
|
-
config = {};
|
|
140
|
-
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
141
|
-
config.mcpServers = {};
|
|
142
|
-
}
|
|
143
|
-
if (config.mcpServers['lensmcp'])
|
|
144
|
-
return; // already registered — leave it
|
|
145
|
-
config.mcpServers['lensmcp'] = { ...LENSMCP_MCP_SERVER_ENTRY };
|
|
146
|
-
tree.write(path, JSON.stringify(config, null, 2) + '\n');
|
|
147
|
-
steps.push('.mcp.json: registered lensmcp MCP server (stdio)');
|
|
148
|
-
}
|
|
149
|
-
exports.default = initGenerator;
|
|
150
|
-
// -------- helpers --------
|
|
151
|
-
function mergeDeep(defaults, overrides) {
|
|
152
|
-
const out = { ...defaults };
|
|
153
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
154
|
-
if (value !== null &&
|
|
155
|
-
typeof value === 'object' &&
|
|
156
|
-
!Array.isArray(value) &&
|
|
157
|
-
out[key] !== null &&
|
|
158
|
-
typeof out[key] === 'object' &&
|
|
159
|
-
!Array.isArray(out[key])) {
|
|
160
|
-
out[key] = mergeDeep(out[key], value);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
out[key] = value;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return out;
|
|
167
|
-
}
|
|
1
|
+
"use strict";var f=Object.defineProperty;var p=(n,t)=>f(n,"name",{value:t,configurable:!0});var m=Object.defineProperty,a=p((n,t)=>m(n,"name",{value:t,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.initGenerator=initGenerator;const devkit_1=require("@nx/devkit"),DEFAULT_LENSMCP_CONFIG={schemaVersion:1,storage:"memory",transport:"stdio",retention:{sessions:5,events:5e3,screenshots:50,buildReports:20,visualFrames:200,heapSnapshots:3,tracesWithErrors:20,verifications:10},redaction:{headers:["authorization","cookie","set-cookie","x-api-key"],paths:["password","token","secret","apiKey","api_key"]},channels:{blockingStatusChanged:{enabled:!0,minSeverity:"error"},memoryLeakSuspected:{enabled:!0,minSeverity:"warning"},bundleRegression:{enabled:!0,minSeverity:"warning"},visualViolationBlocking:{enabled:!0,minSeverity:"error"},verificationCompleted:{enabled:!0,minSeverity:"info"}},thresholds:{dbInLoop:5,nPlusOne:3,renderStorm:5,slowRouteMs:500,slowRenderMs:16}},LENSMCP_GITIGNORE_MARKER="# LensMCP runtime artifacts (per-session storage)",LENSMCP_GITIGNORE_ENTRY=".lensmcp/";async function initGenerator(n,t={}){const s={skipFormat:t.skipFormat??!1,registerHostConfig:t.registerHostConfig??"auto"},e=[];if(n.exists("nx.json"))(0,devkit_1.updateJson)(n,"nx.json",r=>{r.plugins=Array.isArray(r.plugins)?r.plugins:[],r.plugins.some(o=>typeof o=="string"?o==="@lensmcp/nx-plugin":o&&typeof o=="object"&&"plugin"in o?o.plugin==="@lensmcp/nx-plugin":!1)||(r.plugins.push("@lensmcp/nx-plugin"),e.push("nx.json: registered @lensmcp/nx-plugin"));const d=r.lensmcp??{};return r.lensmcp=mergeDeep(DEFAULT_LENSMCP_CONFIG,d),(!("lensmcp"in r)||Object.keys(d).length===0)&&e.push("nx.json: added workspace-wide `lensmcp` config block"),r});else throw new Error("No nx.json found at the workspace root. Are you in a Nx workspace?");const i=".gitignore",c=n.exists(i)?n.read(i,"utf-8")??"":"";if(!c.split(`
|
|
2
|
+
`).some(r=>r.trim()===LENSMCP_GITIGNORE_ENTRY)){const r=c.endsWith(`
|
|
3
|
+
`)?"":`
|
|
4
|
+
`;n.write(i,`${c}${r}
|
|
5
|
+
${LENSMCP_GITIGNORE_MARKER}
|
|
6
|
+
${LENSMCP_GITIGNORE_ENTRY}
|
|
7
|
+
`),e.push(`${i}: added \`${LENSMCP_GITIGNORE_ENTRY}\``)}const l=(0,devkit_1.joinPathFragments)(".lensmcp",".keep");n.exists(l)||(n.write(l,""),e.push(`${l}: created`));const u=(0,devkit_1.joinPathFragments)(".lensmcp","install-trail.json"),g=(n.exists(u)?JSON.parse(n.read(u,"utf-8")??"{}"):void 0)??{schemaVersion:1,installedAt:new Date().toISOString(),steps:[]};g.steps.push(...e),n.write(u,JSON.stringify(g,null,2)+`
|
|
8
|
+
`),s.registerHostConfig!=="never"&®isterWorkspaceMcpConfig(n,e),s.skipFormat||await(0,devkit_1.formatFiles)(n)}p(initGenerator,"initGenerator"),a(initGenerator,"initGenerator");const LENSMCP_MCP_SERVER_ENTRY={command:"npx",args:["-y","lensmcp","mcp"]};function registerWorkspaceMcpConfig(n,t){const s=".mcp.json";let e={};if(n.exists(s))try{e=JSON.parse(n.read(s,"utf-8")??"{}")}catch{e={}}(!e||typeof e!="object")&&(e={}),(!e.mcpServers||typeof e.mcpServers!="object")&&(e.mcpServers={}),!e.mcpServers.lensmcp&&(e.mcpServers.lensmcp={...LENSMCP_MCP_SERVER_ENTRY},n.write(s,JSON.stringify(e,null,2)+`
|
|
9
|
+
`),t.push(".mcp.json: registered lensmcp MCP server (stdio)"))}p(registerWorkspaceMcpConfig,"registerWorkspaceMcpConfig"),a(registerWorkspaceMcpConfig,"registerWorkspaceMcpConfig"),exports.default=initGenerator;function mergeDeep(n,t){const s={...n};for(const[e,i]of Object.entries(t))i!==null&&typeof i=="object"&&!Array.isArray(i)&&s[e]!==null&&typeof s[e]=="object"&&!Array.isArray(s[e])?s[e]=mergeDeep(s[e],i):s[e]=i;return s}p(mergeDeep,"mergeDeep"),a(mergeDeep,"mergeDeep");
|
|
@@ -1,281 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
exports.setupNestGenerator =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
// Legacy module-style wiring, kept for the manual-fallback hint.
|
|
10
|
-
const IMPORT_LINE = `import { LensmcpModule } from '@lensmcp/nest-instrumentation';`;
|
|
11
|
-
const MODULE_CALL = "LensmcpModule.forRoot({ projectName: '__PROJECT__' })";
|
|
12
|
-
/**
|
|
13
|
-
* Wires LensMCP into a host NestJS project with **no app-code edits**.
|
|
14
|
-
* Idempotent.
|
|
15
|
-
*
|
|
16
|
-
* Zero-config strategy (Phase 8): rewrite the bootstrap in `src/main.ts`
|
|
17
|
-
* so `NestFactory.create(AppModule, opts)` becomes
|
|
18
|
-
* `createLensmcpNestApp(AppModule, { projectName: '<project>', nestOptions: opts })`.
|
|
19
|
-
* `createLensmcpNestApp` wires `LensmcpModule` + the provider tracker +
|
|
20
|
-
* auto-instruments every provider's methods under the hood, so the app
|
|
21
|
-
* module and the providers stay untouched.
|
|
22
|
-
*
|
|
23
|
-
* 1. Find the project's `src/main.ts` (the conventional Nest entry).
|
|
24
|
-
* 2. Replace the `NestFactory.create(...)` call with
|
|
25
|
-
* `createLensmcpNestApp(...)`, threading the original 2nd arg through
|
|
26
|
-
* as `nestOptions` and adding the `@lensmcp/nest-instrumentation`
|
|
27
|
-
* import (dropping the now-unused `NestFactory` import when nothing
|
|
28
|
-
* else uses it). String/AST-lite — if the file doesn't follow the
|
|
29
|
-
* canonical shape we print a precise hint and exit non-zero.
|
|
30
|
-
* 3. Add an `agent-dev` Nx target.
|
|
31
|
-
*/
|
|
32
|
-
async function setupNestGenerator(tree, rawOptions) {
|
|
33
|
-
const options = {
|
|
34
|
-
project: rawOptions.project,
|
|
35
|
-
skipFormat: rawOptions.skipFormat ?? false,
|
|
36
|
-
};
|
|
37
|
-
const project = (0, devkit_1.readProjectConfiguration)(tree, options.project);
|
|
38
|
-
const mainPath = findMain(tree, project.root);
|
|
39
|
-
if (!mainPath) {
|
|
40
|
-
throw new Error(`setup-nest: no main.ts found under ${project.root}/src.`);
|
|
41
|
-
}
|
|
42
|
-
const original = tree.read(mainPath, 'utf-8') ?? '';
|
|
43
|
-
const patched = patchMainBootstrap(original, options.project);
|
|
44
|
-
if (patched === null) {
|
|
45
|
-
throw new Error(`setup-nest: could not safely patch ${mainPath}.\n` +
|
|
46
|
-
`Expected a \`NestFactory.create(AppModule)\` bootstrap call. ` +
|
|
47
|
-
`Edit manually: replace it with \`createLensmcpNestApp(AppModule, ` +
|
|
48
|
-
`{ projectName: '${options.project}' })\` and import it from ` +
|
|
49
|
-
`'@lensmcp/nest-instrumentation'.\n` +
|
|
50
|
-
`(Or use the module form: add \`${IMPORT_LINE}\` and push ` +
|
|
51
|
-
`${MODULE_CALL.replace('__PROJECT__', options.project)} into the ` +
|
|
52
|
-
`@Module imports array.)`);
|
|
53
|
-
}
|
|
54
|
-
if (patched !== original) {
|
|
55
|
-
tree.write(mainPath, patched);
|
|
56
|
-
}
|
|
57
|
-
const targets = { ...(project.targets ?? {}) };
|
|
58
|
-
if (!targets['agent-dev']) {
|
|
59
|
-
targets['agent-dev'] = {
|
|
60
|
-
executor: '@lensmcp/nx-plugin:agent-dev',
|
|
61
|
-
options: {
|
|
62
|
-
kind: 'nestjs',
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
project.targets = targets;
|
|
66
|
-
(0, devkit_1.updateProjectConfiguration)(tree, options.project, project);
|
|
67
|
-
}
|
|
68
|
-
if (!options.skipFormat) {
|
|
69
|
-
await (0, devkit_1.formatFiles)(tree);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
exports.default = setupNestGenerator;
|
|
73
|
-
// ---------- helpers ----------
|
|
74
|
-
function findMain(tree, root) {
|
|
75
|
-
for (const name of ['src/main.ts', 'main.ts', 'src/index.ts']) {
|
|
76
|
-
const p = (0, devkit_1.joinPathFragments)(root, name);
|
|
77
|
-
if (tree.exists(p))
|
|
78
|
-
return p;
|
|
79
|
-
}
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
function findAppModule(tree, root) {
|
|
83
|
-
for (const name of ['src/app.module.ts', 'app.module.ts']) {
|
|
84
|
-
const p = (0, devkit_1.joinPathFragments)(root, name);
|
|
85
|
-
if (tree.exists(p))
|
|
86
|
-
return p;
|
|
87
|
-
}
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
const CREATE_CALL_RE = /NestFactory\s*\.\s*create\s*(?:<[\s\S]*?>)?\s*\(/;
|
|
91
|
-
/**
|
|
92
|
-
* Rewrite a Nest `main.ts` bootstrap to the zero-config form. Returns the
|
|
93
|
-
* original string unchanged when already converted (idempotent), `null`
|
|
94
|
-
* when no `NestFactory.create(...)` call is present (caller decides), or
|
|
95
|
-
* the rewritten source otherwise.
|
|
96
|
-
*/
|
|
97
|
-
function patchMainBootstrap(src, projectName) {
|
|
98
|
-
// Already zero-config — nothing to do.
|
|
99
|
-
if (/createLensmcpNestApp\s*\(/.test(src))
|
|
100
|
-
return src;
|
|
101
|
-
const m = CREATE_CALL_RE.exec(src);
|
|
102
|
-
if (!m)
|
|
103
|
-
return null;
|
|
104
|
-
const openParen = m.index + m[0].length - 1; // index of the '('
|
|
105
|
-
const closeParen = matchingParen(src, openParen);
|
|
106
|
-
if (closeParen === -1)
|
|
107
|
-
return null;
|
|
108
|
-
const argsStr = src.slice(openParen + 1, closeParen);
|
|
109
|
-
const { moduleArg, restArg } = splitTopLevelArgs(argsStr);
|
|
110
|
-
if (!moduleArg.trim())
|
|
111
|
-
return null;
|
|
112
|
-
const proj = projectName.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
113
|
-
const rest = restArg.trim();
|
|
114
|
-
const optsObject = rest
|
|
115
|
-
? `{ projectName: '${proj}', nestOptions: ${rest} }`
|
|
116
|
-
: `{ projectName: '${proj}' }`;
|
|
117
|
-
const replacement = `createLensmcpNestApp(${moduleArg.trim()}, ${optsObject})`;
|
|
118
|
-
let out = src.slice(0, m.index) + replacement + src.slice(closeParen + 1);
|
|
119
|
-
out = addBootstrapImport(out);
|
|
120
|
-
out = dropUnusedNestFactoryImport(out);
|
|
121
|
-
return out;
|
|
122
|
-
}
|
|
123
|
-
/** Insert the `createLensmcpNestApp` import after the last import, unless
|
|
124
|
-
* the symbol is already imported from `@lensmcp/nest-instrumentation`. */
|
|
125
|
-
function addBootstrapImport(src) {
|
|
126
|
-
const already = /createLensmcpNestApp[\s\S]*?from\s*['"]@lensmcp\/nest-instrumentation['"]/.test(src);
|
|
127
|
-
if (already)
|
|
128
|
-
return src;
|
|
129
|
-
const importLines = [...src.matchAll(/^\s*import .+;?\s*$/gm)];
|
|
130
|
-
const last = importLines.length ? importLines[importLines.length - 1] : null;
|
|
131
|
-
if (last && last.index !== undefined) {
|
|
132
|
-
const at = last.index + last[0].length;
|
|
133
|
-
return src.slice(0, at) + `\n${BOOTSTRAP_IMPORT}` + src.slice(at);
|
|
134
|
-
}
|
|
135
|
-
return `${BOOTSTRAP_IMPORT}\n${src}`;
|
|
136
|
-
}
|
|
137
|
-
/** Strip `NestFactory` from its `@nestjs/core` named import when nothing
|
|
138
|
-
* in the file references `NestFactory` any more. */
|
|
139
|
-
function dropUnusedNestFactoryImport(src) {
|
|
140
|
-
// Reference check excludes the import statement itself.
|
|
141
|
-
if (/\bNestFactory\b/.test(stripNestFactoryImportSpan(src).rest))
|
|
142
|
-
return src;
|
|
143
|
-
const { match } = stripNestFactoryImportSpan(src);
|
|
144
|
-
if (!match)
|
|
145
|
-
return src;
|
|
146
|
-
const names = match.names.filter((n) => n !== 'NestFactory');
|
|
147
|
-
if (names.length === 0) {
|
|
148
|
-
// Remove the whole import statement (and its trailing newline).
|
|
149
|
-
return src.slice(0, match.start) + src.slice(match.end).replace(/^\n/, '');
|
|
150
|
-
}
|
|
151
|
-
const rebuilt = `import { ${names.join(', ')} } from '@nestjs/core';`;
|
|
152
|
-
return src.slice(0, match.start) + rebuilt + src.slice(match.end);
|
|
153
|
-
}
|
|
154
|
-
/** Locate the `@nestjs/core` named import; return its span + names and the
|
|
155
|
-
* source with that span removed (so the caller can test references that
|
|
156
|
-
* live *outside* the import). */
|
|
157
|
-
function stripNestFactoryImportSpan(src) {
|
|
158
|
-
const re = /import\s*\{([^}]*)\}\s*from\s*['"]@nestjs\/core['"];?/;
|
|
159
|
-
const m = re.exec(src);
|
|
160
|
-
if (!m)
|
|
161
|
-
return { match: null, rest: src };
|
|
162
|
-
const names = m[1]
|
|
163
|
-
.split(',')
|
|
164
|
-
.map((s) => s.trim())
|
|
165
|
-
.filter(Boolean);
|
|
166
|
-
const start = m.index;
|
|
167
|
-
const end = m.index + m[0].length;
|
|
168
|
-
const rest = src.slice(0, start) + src.slice(end);
|
|
169
|
-
return { match: { start, end, names }, rest };
|
|
170
|
-
}
|
|
171
|
-
/** Index of the `)` matching the `(` at `openIdx`, skipping strings,
|
|
172
|
-
* template literals and comments. Returns -1 if unbalanced. */
|
|
173
|
-
function matchingParen(src, openIdx) {
|
|
174
|
-
let depth = 0;
|
|
175
|
-
for (let i = openIdx; i < src.length; i++) {
|
|
176
|
-
const skip = skipNonCode(src, i);
|
|
177
|
-
if (skip > i) {
|
|
178
|
-
i = skip - 1;
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
const ch = src[i];
|
|
182
|
-
if (ch === '(')
|
|
183
|
-
depth++;
|
|
184
|
-
else if (ch === ')') {
|
|
185
|
-
depth--;
|
|
186
|
-
if (depth === 0)
|
|
187
|
-
return i;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return -1;
|
|
191
|
-
}
|
|
192
|
-
/** Split call arguments at the first top-level comma. */
|
|
193
|
-
function splitTopLevelArgs(argsStr) {
|
|
194
|
-
let depth = 0;
|
|
195
|
-
for (let i = 0; i < argsStr.length; i++) {
|
|
196
|
-
const skip = skipNonCode(argsStr, i);
|
|
197
|
-
if (skip > i) {
|
|
198
|
-
i = skip - 1;
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
const ch = argsStr[i];
|
|
202
|
-
if (ch === '(' || ch === '[' || ch === '{')
|
|
203
|
-
depth++;
|
|
204
|
-
else if (ch === ')' || ch === ']' || ch === '}')
|
|
205
|
-
depth--;
|
|
206
|
-
else if (ch === ',' && depth === 0) {
|
|
207
|
-
return {
|
|
208
|
-
moduleArg: argsStr.slice(0, i),
|
|
209
|
-
restArg: argsStr.slice(i + 1),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return { moduleArg: argsStr, restArg: '' };
|
|
214
|
-
}
|
|
215
|
-
/** If position `i` starts a string/template/comment, return the index just
|
|
216
|
-
* past it; otherwise return `i`. */
|
|
217
|
-
function skipNonCode(src, i) {
|
|
218
|
-
const ch = src[i];
|
|
219
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
220
|
-
for (let j = i + 1; j < src.length; j++) {
|
|
221
|
-
if (src[j] === '\\') {
|
|
222
|
-
j++;
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (src[j] === ch)
|
|
226
|
-
return j + 1;
|
|
227
|
-
}
|
|
228
|
-
return src.length;
|
|
229
|
-
}
|
|
230
|
-
if (ch === '/' && src[i + 1] === '/') {
|
|
231
|
-
const nl = src.indexOf('\n', i);
|
|
232
|
-
return nl === -1 ? src.length : nl;
|
|
233
|
-
}
|
|
234
|
-
if (ch === '/' && src[i + 1] === '*') {
|
|
235
|
-
const end = src.indexOf('*/', i + 2);
|
|
236
|
-
return end === -1 ? src.length : end + 2;
|
|
237
|
-
}
|
|
238
|
-
return i;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Legacy module-style wiring. Adds `LensmcpModule.forRoot(...)` into the
|
|
242
|
-
* `@Module({ imports: [...] })` array. Superseded by the `main.ts`
|
|
243
|
-
* bootstrap rewrite ({@link patchMainBootstrap}) but kept for hosts whose
|
|
244
|
-
* entry file doesn't follow the canonical `NestFactory.create` shape.
|
|
245
|
-
* Idempotent. Returns `null` when no `@Module` imports array is found.
|
|
246
|
-
*/
|
|
247
|
-
function patchAppModule(src, projectName) {
|
|
248
|
-
const hasImport = src.includes("from '@lensmcp/nest-instrumentation'") ||
|
|
249
|
-
src.includes('from "@lensmcp/nest-instrumentation"');
|
|
250
|
-
const hasModuleCall = /LensmcpModule\s*\.\s*forRoot\s*\(/.test(src);
|
|
251
|
-
if (hasImport && hasModuleCall)
|
|
252
|
-
return src;
|
|
253
|
-
const importsAnchor = src.indexOf('imports:');
|
|
254
|
-
if (importsAnchor === -1)
|
|
255
|
-
return null;
|
|
256
|
-
const bracketStart = src.indexOf('[', importsAnchor);
|
|
257
|
-
if (bracketStart === -1)
|
|
258
|
-
return null;
|
|
259
|
-
let withImport = src;
|
|
260
|
-
if (!hasImport) {
|
|
261
|
-
const importLines = [...src.matchAll(/^\s*import .+;?\s*$/gm)];
|
|
262
|
-
const lastImport = importLines.length > 0 ? importLines[importLines.length - 1] : null;
|
|
263
|
-
if (lastImport && lastImport.index !== undefined) {
|
|
264
|
-
const insertAt = lastImport.index + lastImport[0].length;
|
|
265
|
-
withImport = src.slice(0, insertAt) + `\n${IMPORT_LINE}` + src.slice(insertAt);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
withImport = `${IMPORT_LINE}\n` + src;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (hasModuleCall)
|
|
272
|
-
return withImport;
|
|
273
|
-
const adjBracket = withImport.indexOf('[', withImport.indexOf('imports:'));
|
|
274
|
-
const before = withImport.slice(0, adjBracket + 1);
|
|
275
|
-
const after = withImport.slice(adjBracket + 1);
|
|
276
|
-
const trimmedAfter = after.replace(/^\s*/, '');
|
|
277
|
-
const startsClosed = trimmedAfter.startsWith(']');
|
|
278
|
-
const separator = startsClosed ? '' : ', ';
|
|
279
|
-
const call = MODULE_CALL.replace('__PROJECT__', projectName);
|
|
280
|
-
return `${before}${call}${separator}${after}`;
|
|
281
|
-
}
|
|
1
|
+
"use strict";var h=Object.defineProperty;var c=(e,r)=>h(e,"name",{value:r,configurable:!0});var A=Object.defineProperty,i=c((e,r)=>A(e,"name",{value:r,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.setupNestGenerator=setupNestGenerator,exports.findAppModule=findAppModule,exports.patchMainBootstrap=patchMainBootstrap,exports.patchAppModule=patchAppModule;const devkit_1=require("@nx/devkit"),BOOTSTRAP_IMPORT="import { createLensmcpNestApp } from '@lensmcp/nest-instrumentation';",IMPORT_LINE="import { LensmcpModule } from '@lensmcp/nest-instrumentation';",MODULE_CALL="LensmcpModule.forRoot({ projectName: '__PROJECT__' })";async function setupNestGenerator(e,r){const n={project:r.project,skipFormat:r.skipFormat??!1},t=(0,devkit_1.readProjectConfiguration)(e,n.project),s=findMain(e,t.root);if(!s)throw new Error(`setup-nest: no main.ts found under ${t.root}/src.`);const o=e.read(s,"utf-8")??"",p=patchMainBootstrap(o,n.project);if(p===null)throw new Error(`setup-nest: could not safely patch ${s}.
|
|
2
|
+
Expected a \`NestFactory.create(AppModule)\` bootstrap call. Edit manually: replace it with \`createLensmcpNestApp(AppModule, { projectName: '${n.project}' })\` and import it from '@lensmcp/nest-instrumentation'.
|
|
3
|
+
(Or use the module form: add \`${IMPORT_LINE}\` and push ${MODULE_CALL.replace("__PROJECT__",n.project)} into the @Module imports array.)`);p!==o&&e.write(s,p);const a={...t.targets??{}};a["agent-dev"]||(a["agent-dev"]={executor:"@lensmcp/nx-plugin:agent-dev",options:{kind:"nestjs"}},t.targets=a,(0,devkit_1.updateProjectConfiguration)(e,n.project,t)),n.skipFormat||await(0,devkit_1.formatFiles)(e)}c(setupNestGenerator,"setupNestGenerator"),i(setupNestGenerator,"setupNestGenerator"),exports.default=setupNestGenerator;function findMain(e,r){for(const n of["src/main.ts","main.ts","src/index.ts"]){const t=(0,devkit_1.joinPathFragments)(r,n);if(e.exists(t))return t}}c(findMain,"findMain"),i(findMain,"findMain");function findAppModule(e,r){for(const n of["src/app.module.ts","app.module.ts"]){const t=(0,devkit_1.joinPathFragments)(r,n);if(e.exists(t))return t}}c(findAppModule,"findAppModule"),i(findAppModule,"findAppModule");const CREATE_CALL_RE=/NestFactory\s*\.\s*create\s*(?:<[\s\S]*?>)?\s*\(/;function patchMainBootstrap(e,r){if(/createLensmcpNestApp\s*\(/.test(e))return e;const n=CREATE_CALL_RE.exec(e);if(!n)return null;const t=n.index+n[0].length-1,s=matchingParen(e,t);if(s===-1)return null;const o=e.slice(t+1,s),{moduleArg:p,restArg:a}=splitTopLevelArgs(o);if(!p.trim())return null;const u=r.replace(/\\/g,"\\\\").replace(/'/g,"\\'"),m=a.trim(),d=m?`{ projectName: '${u}', nestOptions: ${m} }`:`{ projectName: '${u}' }`,f=`createLensmcpNestApp(${p.trim()}, ${d})`;let l=e.slice(0,n.index)+f+e.slice(s+1);return l=addBootstrapImport(l),l=dropUnusedNestFactoryImport(l),l}c(patchMainBootstrap,"patchMainBootstrap"),i(patchMainBootstrap,"patchMainBootstrap");function addBootstrapImport(e){if(/createLensmcpNestApp[\s\S]*?from\s*['"]@lensmcp\/nest-instrumentation['"]/.test(e))return e;const r=[...e.matchAll(/^\s*import .+;?\s*$/gm)],n=r.length?r[r.length-1]:null;if(n&&n.index!==void 0){const t=n.index+n[0].length;return e.slice(0,t)+`
|
|
4
|
+
${BOOTSTRAP_IMPORT}`+e.slice(t)}return`${BOOTSTRAP_IMPORT}
|
|
5
|
+
${e}`}c(addBootstrapImport,"addBootstrapImport"),i(addBootstrapImport,"addBootstrapImport");function dropUnusedNestFactoryImport(e){if(/\bNestFactory\b/.test(stripNestFactoryImportSpan(e).rest))return e;const{match:r}=stripNestFactoryImportSpan(e);if(!r)return e;const n=r.names.filter(s=>s!=="NestFactory");if(n.length===0)return e.slice(0,r.start)+e.slice(r.end).replace(/^\n/,"");const t=`import { ${n.join(", ")} } from '@nestjs/core';`;return e.slice(0,r.start)+t+e.slice(r.end)}c(dropUnusedNestFactoryImport,"dropUnusedNestFactoryImport"),i(dropUnusedNestFactoryImport,"dropUnusedNestFactoryImport");function stripNestFactoryImportSpan(e){const r=/import\s*\{([^}]*)\}\s*from\s*['"]@nestjs\/core['"];?/.exec(e);if(!r)return{match:null,rest:e};const n=r[1].split(",").map(p=>p.trim()).filter(Boolean),t=r.index,s=r.index+r[0].length,o=e.slice(0,t)+e.slice(s);return{match:{start:t,end:s,names:n},rest:o}}c(stripNestFactoryImportSpan,"stripNestFactoryImportSpan"),i(stripNestFactoryImportSpan,"stripNestFactoryImportSpan");function matchingParen(e,r){let n=0;for(let t=r;t<e.length;t++){const s=skipNonCode(e,t);if(s>t){t=s-1;continue}const o=e[t];if(o==="(")n++;else if(o===")"&&(n--,n===0))return t}return-1}c(matchingParen,"matchingParen"),i(matchingParen,"matchingParen");function splitTopLevelArgs(e){let r=0;for(let n=0;n<e.length;n++){const t=skipNonCode(e,n);if(t>n){n=t-1;continue}const s=e[n];if(s==="("||s==="["||s==="{")r++;else if(s===")"||s==="]"||s==="}")r--;else if(s===","&&r===0)return{moduleArg:e.slice(0,n),restArg:e.slice(n+1)}}return{moduleArg:e,restArg:""}}c(splitTopLevelArgs,"splitTopLevelArgs"),i(splitTopLevelArgs,"splitTopLevelArgs");function skipNonCode(e,r){const n=e[r];if(n==='"'||n==="'"||n==="`"){for(let t=r+1;t<e.length;t++){if(e[t]==="\\"){t++;continue}if(e[t]===n)return t+1}return e.length}if(n==="/"&&e[r+1]==="/"){const t=e.indexOf(`
|
|
6
|
+
`,r);return t===-1?e.length:t}if(n==="/"&&e[r+1]==="*"){const t=e.indexOf("*/",r+2);return t===-1?e.length:t+2}return r}c(skipNonCode,"skipNonCode"),i(skipNonCode,"skipNonCode");function patchAppModule(e,r){const n=e.includes("from '@lensmcp/nest-instrumentation'")||e.includes('from "@lensmcp/nest-instrumentation"'),t=/LensmcpModule\s*\.\s*forRoot\s*\(/.test(e);if(n&&t)return e;const s=e.indexOf("imports:");if(s===-1||e.indexOf("[",s)===-1)return null;let o=e;if(!n){const f=[...e.matchAll(/^\s*import .+;?\s*$/gm)],l=f.length>0?f[f.length-1]:null;if(l&&l.index!==void 0){const g=l.index+l[0].length;o=e.slice(0,g)+`
|
|
7
|
+
${IMPORT_LINE}`+e.slice(g)}else o=`${IMPORT_LINE}
|
|
8
|
+
`+e}if(t)return o;const p=o.indexOf("[",o.indexOf("imports:")),a=o.slice(0,p+1),u=o.slice(p+1),m=u.replace(/^\s*/,"").startsWith("]")?"":", ",d=MODULE_CALL.replace("__PROJECT__",r);return`${a}${d}${m}${u}`}c(patchAppModule,"patchAppModule"),i(patchAppModule,"patchAppModule");
|