@lensmcp/nx-plugin 1.21.5 → 1.22.1
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-dev/agent-dev.d.ts +11 -0
- package/executors/agent-dev/agent-dev.js +3 -3
- package/executors/agent-verify/agent-verify.d.ts +36 -0
- package/executors/agent-verify/agent-verify.js +3 -3
- package/generators/init/init.d.ts +35 -0
- package/generators/init/init.js +6 -6
- package/generators/setup-nest/setup-nest.js +7 -7
- package/package.json +1 -1
|
@@ -23,4 +23,15 @@ interface ExecutorResult {
|
|
|
23
23
|
* what's happening. Ctrl-C triggers a clean teardown.
|
|
24
24
|
*/
|
|
25
25
|
export default function agentDevExecutor(options: AgentDevExecutorSchema, context: ExecutorContext): Promise<ExecutorResult>;
|
|
26
|
+
/**
|
|
27
|
+
* May automatic capture point a browser at this URL?
|
|
28
|
+
*
|
|
29
|
+
* Loopback only, unless `LENSMCP_CAPTURE_ALLOW_REMOTE=1` says otherwise. `agent-dev` starts capture
|
|
30
|
+
* by DEFAULT, so `openUrl` is the one place a workspace config decides what a browser on the
|
|
31
|
+
* developer's machine renders — and, with the sandbox restored (issues/codex-security/60), a target
|
|
32
|
+
* on the open internet is a decision worth stating rather than inheriting.
|
|
33
|
+
*
|
|
34
|
+
* Anything that does not parse, and any scheme other than http/https, is not local.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isLocalCaptureTarget(url: string, env?: NodeJS.ProcessEnv): boolean;
|
|
26
37
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var L=Object.defineProperty;var
|
|
2
|
-
`+
|
|
1
|
+
"use strict";var L=Object.defineProperty;var P=(t,o)=>L(t,"name",{value:o,configurable:!0});var M=Object.defineProperty,d=P((t,o)=>M(t,"name",{value:o,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=agentDevExecutor,exports.isLocalCaptureTarget=isLocalCaptureTarget;const node_child_process_1=require("node:child_process"),node_fs_1=require("node:fs"),node_path_1=require("node:path"),lens_frontend_1=require("../../lens-frontend");async function agentDevExecutor(t,o){const e={kind:t.kind??"vite-react",chrome:t.chrome??!0,headless:t.headless??!0,node:t.node??!0,devTarget:t.devTarget,projectRoot:t.projectRoot,port:t.port,openUrl:t.openUrl,devCommand:t.devCommand,wsPort:t.wsPort};if(e.kind!=="vite-react"&&e.kind!=="nestjs"&&e.kind!=="imported")return console.error(`[agent-dev] supported kinds are "vite-react", "nestjs", "imported" (got "${e.kind}").`),{success:!1};const r=o.projectName,p=e.projectRoot?(0,node_path_1.resolve)(o.root,e.projectRoot):r&&o.projectsConfigurations?(0,node_path_1.resolve)(o.root,o.projectsConfigurations.projects[r]?.root??"."):o.root;if(e.kind==="nestjs")return runNestjs(p,o);if(e.kind==="imported")return runImported(p,o,{devCommand:e.devCommand,chrome:e.chrome,headless:e.headless,openUrl:e.openUrl,wsPort:e.wsPort??5747,node:e.node});const l=(0,lens_frontend_1.findMcpBundle)(o.root);l||console.warn("[agent-dev] No lensmcp-mcp bundle found \u2014 skipping MCP server. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/main.js`).");const h=[],c=new Map;let s=!1;function u(n,i,v,f,w=!1,a){const C=(0,node_child_process_1.spawn)(i,v,{cwd:p,env:{...process.env,...f??{}},stdio:"inherit"});return h.push(C),C.on("exit",(S,E)=>{if(!s){if(w){const N=c.get(n)??0;if(a&&N<a.max){c.set(n,N+1),console.warn(`[agent-dev] ${n} exited (code=${S} sig=${E}); respawning in ${Math.round(a.delayMs/1e3)}s (${N+1}/${a.max}).`),setTimeout(()=>{s||u(n,i,v,f,w,a)},a.delayMs).unref?.();return}console.warn(`[agent-dev] ${n} exited (code=${S} sig=${E}); continuing.`);return}console.warn(`[agent-dev] ${n} exited unexpectedly (code=${S} sig=${E}); shutting down others.`),m()}}),C}P(u,"l"),d(u,"spawnChild");function m(){if(!s){s=!0;for(const n of h)try{n.kill("SIGTERM")}catch{}}}P(m,"u"),d(m,"shutdown"),process.on("SIGINT",m),process.on("SIGTERM",m);const g=process.env.LENSMCP_EVENT_FILE??(0,node_path_1.join)(o.root,".lensmcp","events.jsonl");try{(0,node_fs_1.mkdirSync)((0,node_path_1.dirname)(g),{recursive:!0});const n=(0,node_fs_1.statSync)(g,{throwIfNoEntry:!1});!n||Date.now()-n.mtimeMs>6e4?(0,node_fs_1.writeFileSync)(g,""):console.log("[agent-dev] event bridge is active (written <60s ago) \u2014 appending, not truncating.")}catch{}const _={LENSMCP_EVENT_FILE:g};console.log(`[agent-dev] event bridge \u2192 ${g}`);try{(0,lens_frontend_1.spawnLensFrontend)({projectRoot:p,workspaceRoot:o.root,...r?{project:r}:{},eventFile:g,...e.port!==void 0?{port:e.port}:{},chrome:e.chrome,headless:e.headless,...e.openUrl?{openUrl:e.openUrl}:{},log:d(n=>console.log(n),"log")},u)}catch(n){return console.error(n.message),m(),{success:!1}}return l&&(console.log(`[agent-dev] starting LensMCP MCP server (${l})`),u("lensmcp-mcp",process.execPath,[l],{..._,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:process.env.LENSMCP_PORT??"3000"})),new Promise(n=>{const i=d(()=>{s?Promise.allSettled(h.map(v=>new Promise(f=>{v.exitCode!==null?f():v.on("exit",()=>f())}))).then(()=>n({success:!0})):setTimeout(i,500)},"tick");i()})}P(agentDevExecutor,"agentDevExecutor"),d(agentDevExecutor,"agentDevExecutor");async function runNestjs(t,o){const e=o.projectName,r=(e?o.projectsConfigurations?.projects[e]?.targets?.build:void 0)?.options?.outputPath,p=[...typeof r=="string"?[(0,node_path_1.join)(o.root,r,"main.js")]:[],(0,node_path_1.join)(t,"dist","main.js"),(0,node_path_1.join)(o.root,"dist",(0,node_path_1.relative)(o.root,t),"main.js")],l=p.find(c=>(0,node_fs_1.existsSync)(c));if(!l)return console.error(`[agent-dev] No built NestJS entry found. Looked in:
|
|
2
|
+
`+p.map(c=>` \u2022 ${c}`).join(`
|
|
3
3
|
`)+`
|
|
4
|
-
Build first: \`nx build ${
|
|
4
|
+
Build first: \`nx build ${e??"<project>"}\`.`),{success:!1};console.log(`[agent-dev] node ${l}`);const h=(0,node_child_process_1.spawn)(process.execPath,[l],{cwd:t,stdio:"inherit",env:{...process.env,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_EVENT_FILE:process.env.LENSMCP_EVENT_FILE??(0,node_path_1.join)(o.root,".lensmcp","events.jsonl")}});return new Promise(c=>{const s=d(()=>{try{h.kill("SIGTERM")}catch{}},"onSignal");process.on("SIGINT",s),process.on("SIGTERM",s),h.on("exit",u=>{process.off("SIGINT",s),process.off("SIGTERM",s),c({success:u===0})})})}P(runNestjs,"runNestjs"),d(runNestjs,"runNestjs");async function runImported(t,o,e){if(!e.devCommand)return console.error('[agent-dev] kind="imported" needs a `devCommand` (your dev-server command, e.g. "npm run dev").\n Add it to the agent-dev target options, or pass `--devCommand "npm run dev"`.'),{success:!1};const r=(0,lens_frontend_1.findBridgeBundle)(o.root);if(!r)return console.error("[agent-dev] No lensmcp bridge bundle found. Install the self-contained CLI: `npm i -D lensmcp` (it ships `bundled/bridge.js`)."),{success:!1};const p=(0,lens_frontend_1.findMcpBundle)(o.root);p||console.warn("[agent-dev] No lensmcp-mcp bundle found \u2014 skipping MCP server. Install the self-contained CLI: `npm i -D lensmcp`.");const l=[],h=new Map;let c=!1;function s(n,i,v,f,w=!1,a,C=!1){const S=(0,node_child_process_1.spawn)(i,v,{cwd:t,env:{...process.env,...f??{}},stdio:"inherit",shell:C});return l.push(S),S.on("exit",(E,N)=>{if(!c){if(w){const T=h.get(n)??0;if(a&&T<a.max){h.set(n,T+1),console.warn(`[agent-dev] ${n} exited (code=${E} sig=${N}); respawning in ${Math.round(a.delayMs/1e3)}s (${T+1}/${a.max}).`),setTimeout(()=>{c||s(n,i,v,f,w,a,C)},a.delayMs).unref?.();return}console.warn(`[agent-dev] ${n} exited (code=${E} sig=${N}); continuing.`);return}console.warn(`[agent-dev] ${n} exited unexpectedly (code=${E} sig=${N}); shutting down others.`),u()}}),S}P(s,"r"),d(s,"spawnChild");function u(){if(!c){c=!0;for(const n of l)try{n.kill("SIGTERM")}catch{}}}P(u,"l"),d(u,"shutdown"),process.on("SIGINT",u),process.on("SIGTERM",u);const m=process.env.LENSMCP_EVENT_FILE??(0,node_path_1.join)(o.root,".lensmcp","events.jsonl");try{(0,node_fs_1.mkdirSync)((0,node_path_1.dirname)(m),{recursive:!0});const n=(0,node_fs_1.statSync)(m,{throwIfNoEntry:!1});!n||Date.now()-n.mtimeMs>6e4?(0,node_fs_1.writeFileSync)(m,""):console.log("[agent-dev] event bridge is active (written <60s ago) \u2014 appending, not truncating.")}catch{}const g={LENSMCP_EVENT_FILE:m};console.log(`[agent-dev] event bridge \u2192 ${m}`),console.log(`[agent-dev] starting bridge \u2192 ws://127.0.0.1:${e.wsPort}`),s("bridge",process.execPath,[r],{...g,LENSMCP_WS_HOST:process.env.LENSMCP_WS_HOST??"127.0.0.1",LENSMCP_WS_PORT:String(e.wsPort)}),p&&(console.log(`[agent-dev] starting LensMCP MCP server (${p})`),s("lensmcp-mcp",process.execPath,[p],{...g,LENSMCP_TRANSPORT:process.env.LENSMCP_TRANSPORT??"http",LENSMCP_PORT:process.env.LENSMCP_PORT??"3000"}));const _={...g,LENSMCP_WS_PORT:String(e.wsPort)};if(e.node)if([t,o.root].some(n=>(0,node_fs_1.existsSync)((0,node_path_1.join)(n,"node_modules","@lensmcp","node-instrumentation")))){const n="--import @lensmcp/node-instrumentation/register";_.NODE_OPTIONS=process.env.NODE_OPTIONS?`${process.env.NODE_OPTIONS} ${n}`:n,console.log("[agent-dev] backend taps: NODE_OPTIONS += --import @lensmcp/node-instrumentation/register")}else console.warn("[agent-dev] @lensmcp/node-instrumentation not installed \u2014 skipping backend taps. Add it: `npm i -D @lensmcp/node-instrumentation`.");if(console.log(`[agent-dev] front-end seam: add \`import '@lensmcp/client-runtime/auto'\` to your app entry (connects to ws://127.0.0.1:${e.wsPort}).`),console.log(`[agent-dev] starting dev command: ${e.devCommand}`),s("dev",e.devCommand,[],_,!1,void 0,!0),e.chrome){const n=(0,lens_frontend_1.findCaptureRunner)(o.root),i=e.openUrl;n?i?(console.log(`[agent-dev] starting browser capture \u2192 ${i}`),s("browser-capture",process.execPath,[n],{...g,LENSMCP_DEV_URL:i,LENSMCP_TOKENS_FILE:(0,node_path_1.join)(o.root,"lensmcp.tokens.json"),LENSMCP_RULES_FILE:(0,node_path_1.join)(o.root,"lensmcp.rules.json"),LENSMCP_HEADLESS:e.headless===!1?"false":"true"},!0,{delayMs:1e4,max:5})):console.warn("[agent-dev] no `openUrl` provided \u2014 skipping Chrome capture (set openUrl to your dev URL)."):console.warn("[agent-dev] browser-capture runner not found \u2014 skipping live capture. Install the self-contained CLI: `npm i -D lensmcp`.")}return new Promise(n=>{const i=d(()=>{c?Promise.allSettled(l.map(v=>new Promise(f=>{v.exitCode!==null?f():v.on("exit",()=>f())}))).then(()=>n({success:!0})):setTimeout(i,500)},"tick");i()})}P(runImported,"runImported"),d(runImported,"runImported");function isLocalCaptureTarget(t,o=process.env){if(o.LENSMCP_CAPTURE_ALLOW_REMOTE==="1")return!0;let e;try{e=new URL(t)}catch{return!1}if(e.protocol!=="http:"&&e.protocol!=="https:")return!1;const r=e.hostname.toLowerCase();return r==="localhost"||r==="[::1]"||r==="::1"?!0:/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(r)}P(isLocalCaptureTarget,"isLocalCaptureTarget"),d(isLocalCaptureTarget,"isLocalCaptureTarget");
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import type { ExecutorContext } from '@nx/devkit';
|
|
2
3
|
import type { AgentVerifyExecutorSchema } from './schema';
|
|
3
4
|
interface ExecutorResult {
|
|
4
5
|
success: boolean;
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* `errored` is not `failed` (issues/codex-security/53). A stage that could not RUN — the binary
|
|
9
|
+
* vanished, the child was killed, its output overran the capture buffer — says nothing about whether
|
|
10
|
+
* the code is correct, and reporting it as a failure tells the agent its fix did not work when the
|
|
11
|
+
* verification never reached a verdict.
|
|
12
|
+
*/
|
|
13
|
+
type StageResult = 'passed' | 'failed' | 'skipped' | 'errored';
|
|
6
14
|
/**
|
|
7
15
|
* `agent-verify` — the deterministic verification loop the agent runs
|
|
8
16
|
* to know "did my fix work?".
|
|
@@ -18,4 +26,32 @@ interface ExecutorResult {
|
|
|
18
26
|
* from `latest.json`.
|
|
19
27
|
*/
|
|
20
28
|
export default function agentVerifyExecutor(options: AgentVerifyExecutorSchema, context: ExecutorContext): Promise<ExecutorResult>;
|
|
29
|
+
interface StageOutcome {
|
|
30
|
+
result: StageResult;
|
|
31
|
+
exitCode?: number;
|
|
32
|
+
summary?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a verify sub-command, capturing its output and re-emitting it INDENTED.
|
|
36
|
+
*
|
|
37
|
+
* agent-verify deliberately runs commands that may fail — it IS the agent's
|
|
38
|
+
* edit→verify loop, so a failing stage is a normal, expected outcome. In CI a
|
|
39
|
+
* raw tool error printed at column 0 (`file(line,col): error TS....`) is picked
|
|
40
|
+
* up by GitHub's problem matchers and turned into a spurious build *annotation*.
|
|
41
|
+
* Indenting every line by two spaces keeps the output fully readable while
|
|
42
|
+
* defeating the matchers (they anchor on a non-space first character), so a
|
|
43
|
+
* verify failure never masquerades as a CI error.
|
|
44
|
+
*
|
|
45
|
+
* `r.error` is inspected, because "the tool could not be run" and "the tool said no" are different
|
|
46
|
+
* answers and only one of them is about the code.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runStageCommand(label: string, bin: string, args: string[], cwd: string, spawn?: typeof spawnSync): StageOutcome;
|
|
49
|
+
/** The spawn result → a stage outcome. Split out so every branch is reachable from a test. */
|
|
50
|
+
export declare function classifyStage(r: {
|
|
51
|
+
error?: Error & {
|
|
52
|
+
code?: string;
|
|
53
|
+
};
|
|
54
|
+
status?: number | null;
|
|
55
|
+
signal?: NodeJS.Signals | null;
|
|
56
|
+
}): StageOutcome;
|
|
21
57
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`),(0,node_fs_1.writeFileSync)((0,node_path_1.join)(
|
|
3
|
-
`)}catch(t){console.warn(`[agent-verify] could not write report: ${t.message}`)}for(const t of
|
|
1
|
+
"use strict";var v=Object.defineProperty;var u=(e,o)=>v(e,"name",{value:o,configurable:!0});var g=Object.defineProperty,d=u((e,o)=>g(e,"name",{value:o,configurable:!0}),"d");Object.defineProperty(exports,"__esModule",{value:!0}),exports.default=agentVerifyExecutor,exports.runStageCommand=runStageCommand,exports.classifyStage=classifyStage;const node_child_process_1=require("node:child_process"),node_fs_1=require("node:fs"),node_path_1=require("node:path");async function agentVerifyExecutor(e,o){const r={kind:e.kind??"vite-react",skipTypecheck:e.skipTypecheck??!1,skipLint:e.skipLint??!1,skipBuild:e.skipBuild??!1,projectRoot:e.projectRoot},n=o.projectName??null,i=r.projectRoot?(0,node_path_1.resolve)(o.root,r.projectRoot):n&&o.projectsConfigurations?(0,node_path_1.resolve)(o.root,o.projectsConfigurations.projects[n]?.root??"."):o.root,c=Date.now(),s=[];if(r.skipTypecheck)s.push({name:"typecheck",result:"skipped",durationMs:0});else{const t=locateBin("tsc",[i,o.root]),a=resolveFirstExisting(["tsconfig.json"],i);if(!t||!a)s.push({name:"typecheck",result:"skipped",durationMs:0,summary:"no tsc/tsconfig"});else{const l=Date.now(),y=runStageCommand("tsc --noEmit",t,["--noEmit","-p",a],i);s.push({name:"typecheck",durationMs:Date.now()-l,...y})}}if(r.skipLint)s.push({name:"lint",result:"skipped",durationMs:0});else{const t=locateBin("eslint",[i,o.root]);if(!t)s.push({name:"lint",result:"skipped",durationMs:0,summary:"no eslint"});else{const a=Date.now(),l=runStageCommand("eslint .",t,["."],i);s.push({name:"lint",durationMs:Date.now()-a,...l})}}if(r.skipBuild)s.push({name:"build",result:"skipped",durationMs:0});else{const t=locateBin("vite",[i,o.root]);if(!t)s.push({name:"build",result:"skipped",durationMs:0,summary:"no vite"});else{const a=resolveFirstExisting(["vite.config.ts","vite.config.js","vite.config.mjs"],i)??(0,node_path_1.join)(i,"vite.config.ts"),l=Date.now(),y=runStageCommand("vite build",t,["build","--config",a,i],i);s.push({name:"build",durationMs:Date.now()-l,...y})}}const f=s.some(t=>t.result==="errored")?"errored":s.some(t=>t.result==="failed")?"failed":"passed",p={schemaVersion:1,project:n,timestamp:new Date(c).toISOString(),durationMs:Date.now()-c,status:f,stages:s},m=(0,node_path_1.join)(o.root,".lensmcp","verifications");try{(0,node_fs_1.mkdirSync)(m,{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)(m,a),JSON.stringify(p,null,2)+`
|
|
2
|
+
`),(0,node_fs_1.writeFileSync)((0,node_path_1.join)(m,"latest.json"),JSON.stringify(p,null,2)+`
|
|
3
|
+
`)}catch(t){console.warn(`[agent-verify] could not write report: ${t.message}`)}for(const t of p.stages){const a=t.exitCode!==void 0?` (exit ${t.exitCode})`:t.summary?` \u2014 ${t.summary}`:"";console.log(`[agent-verify] ${t.name}: ${t.result}${a}`)}return console.log(`[agent-verify] ${f} in ${p.durationMs}ms`),{success:f==="passed"}}u(agentVerifyExecutor,"agentVerifyExecutor"),d(agentVerifyExecutor,"agentVerifyExecutor");const STAGE_MAX_BUFFER=(()=>{const e=Number(process.env.LENSMCP_VERIFY_MAX_BUFFER);return Number.isFinite(e)&&e>0?Math.floor(e):64*1024*1024})();function runStageCommand(e,o,r,n,i=node_child_process_1.spawnSync){console.log(`[agent-verify] ${e}`);const c=i(o,r,{cwd:n,encoding:"utf8",maxBuffer:STAGE_MAX_BUFFER}),s=`${c.stdout??""}${c.stderr??""}`;return s.trim()&&process.stdout.write(s.replace(/^(?=.)/gm," ")),classifyStage(c)}u(runStageCommand,"runStageCommand"),d(runStageCommand,"runStageCommand");function classifyStage(e){return e.error?{result:"errored",summary:e.error.code==="ENOBUFS"?`output exceeded the ${STAGE_MAX_BUFFER}-byte capture buffer \u2014 the verification did not reach a verdict (raise LENSMCP_VERIFY_MAX_BUFFER)`:`could not run: ${e.error.message}`}:e.status===null||e.status===void 0?{result:"errored",summary:`terminated${e.signal?` by ${e.signal}`:""} without an exit status`}:{result:e.status===0?"passed":"failed",exitCode:e.status}}u(classifyStage,"classifyStage"),d(classifyStage,"classifyStage");function locateBin(e,o){for(const r of o){const n=(0,node_path_1.join)(r,"node_modules",".bin",e);if((0,node_fs_1.existsSync)(n))return n}}u(locateBin,"locateBin"),d(locateBin,"locateBin");function resolveFirstExisting(e,o){for(const r of e){const n=(0,node_path_1.join)(o,r);if((0,node_fs_1.existsSync)(n))return n}}u(resolveFirstExisting,"resolveFirstExisting"),d(resolveFirstExisting,"resolveFirstExisting"),node_path_1.dirname;
|
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { type Tree } from '@nx/devkit';
|
|
2
2
|
import type { InitGeneratorSchema } from './schema';
|
|
3
3
|
export declare function initGenerator(tree: Tree, rawOptions?: InitGeneratorSchema): Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* The stdio MCP server entry a host agent uses to launch LensMCP.
|
|
6
|
+
*
|
|
7
|
+
* issues/codex-security/61: this was `npx -y lensmcp mcp` — an UNVERSIONED specifier that `-y`
|
|
8
|
+
* installs without a word. The spec is not resolved through the workspace lockfile, so every
|
|
9
|
+
* uncached launch could fetch whatever the registry currently answers with, and package lifecycle
|
|
10
|
+
* and bin code then runs with the coding agent user's privileges. A compromised publisher,
|
|
11
|
+
* namespace or registry is the whole attack, and the entry that invites it is committed to the
|
|
12
|
+
* repository, where nobody re-reads it.
|
|
13
|
+
*
|
|
14
|
+
* Two branches, both of which pin:
|
|
15
|
+
*
|
|
16
|
+
* - the workspace already has `lensmcp` installed → `npx --no`, which resolves through
|
|
17
|
+
* `node_modules` and FAILS rather than fetching. The lockfile is then the pin, which is the
|
|
18
|
+
* strongest available and the common case immediately after this generator runs;
|
|
19
|
+
* - it does not → an exact `lensmcp@<version>` taken from this plugin's own version. The
|
|
20
|
+
* `@lensmcp/*` closure is released together with exact internal pins, so the plugin's version is
|
|
21
|
+
* the CLI's. `--yes` stays on that branch deliberately: an MCP host launches a stdio server with
|
|
22
|
+
* no TTY, so a suppressed prompt does not become a visible question — it becomes a hang. The
|
|
23
|
+
* mutable reference was the defect; the pin is what removes it, and the committed specifier is
|
|
24
|
+
* now something a reviewer can audit.
|
|
25
|
+
*/
|
|
26
|
+
export declare function mcpServerEntry(workspaceRoot: string, deps?: {
|
|
27
|
+
exists?: (p: string) => boolean;
|
|
28
|
+
version?: string;
|
|
29
|
+
}): {
|
|
30
|
+
command: string;
|
|
31
|
+
args: string[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* This plugin's own version, for the pin above. Walks up from the compiled module rather than
|
|
35
|
+
* hard-coding a depth, because the published package root and the source tree sit at different
|
|
36
|
+
* levels. Returns undefined rather than guessing — an unpinned specifier is better than a wrong one.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pluginVersion(from?: string): string | undefined;
|
|
4
39
|
export default initGenerator;
|
package/generators/init/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"use strict";var f=Object.defineProperty;var p=(
|
|
2
|
-
`).some(
|
|
1
|
+
"use strict";var f=Object.defineProperty;var p=(s,r)=>f(s,"name",{value:r,configurable:!0});var d=Object.defineProperty,c=p((s,r)=>d(s,"name",{value:r,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.initGenerator=initGenerator,exports.mcpServerEntry=mcpServerEntry,exports.pluginVersion=pluginVersion;const node_fs_1=require("node:fs"),node_path_1=require("node:path"),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(s,r={}){const n={skipFormat:r.skipFormat??!1,registerHostConfig:r.registerHostConfig??"auto"},e=[];if(s.exists("nx.json"))(0,devkit_1.updateJson)(s,"nx.json",i=>{i.plugins=Array.isArray(i.plugins)?i.plugins:[],i.plugins.some(a=>typeof a=="string"?a==="@lensmcp/nx-plugin":a&&typeof a=="object"&&"plugin"in a?a.plugin==="@lensmcp/nx-plugin":!1)||(i.plugins.push("@lensmcp/nx-plugin"),e.push("nx.json: registered @lensmcp/nx-plugin"));const g=i.lensmcp??{};return i.lensmcp=mergeDeep(DEFAULT_LENSMCP_CONFIG,g),(!("lensmcp"in i)||Object.keys(g).length===0)&&e.push("nx.json: added workspace-wide `lensmcp` config block"),i});else throw new Error("No nx.json found at the workspace root. Are you in a Nx workspace?");const t=".gitignore",o=s.exists(t)?s.read(t,"utf-8")??"":"";if(!o.split(`
|
|
2
|
+
`).some(i=>i.trim()===LENSMCP_GITIGNORE_ENTRY)){const i=o.endsWith(`
|
|
3
3
|
`)?"":`
|
|
4
|
-
`;
|
|
4
|
+
`;s.write(t,`${o}${i}
|
|
5
5
|
${LENSMCP_GITIGNORE_MARKER}
|
|
6
6
|
${LENSMCP_GITIGNORE_ENTRY}
|
|
7
|
-
`),e.push(`${
|
|
8
|
-
`),
|
|
9
|
-
`),
|
|
7
|
+
`),e.push(`${t}: added \`${LENSMCP_GITIGNORE_ENTRY}\``)}const l=(0,devkit_1.joinPathFragments)(".lensmcp",".keep");s.exists(l)||(s.write(l,""),e.push(`${l}: created`));const m=(0,devkit_1.joinPathFragments)(".lensmcp","install-trail.json"),u=(s.exists(m)?JSON.parse(s.read(m,"utf-8")??"{}"):void 0)??{schemaVersion:1,installedAt:new Date().toISOString(),steps:[]};u.steps.push(...e),s.write(m,JSON.stringify(u,null,2)+`
|
|
8
|
+
`),n.registerHostConfig!=="never"&®isterWorkspaceMcpConfig(s,e),n.skipFormat||await(0,devkit_1.formatFiles)(s)}p(initGenerator,"initGenerator"),c(initGenerator,"initGenerator");function mcpServerEntry(s,r={}){if((r.exists??node_fs_1.existsSync)((0,node_path_1.join)(s,"node_modules",".bin","lensmcp")))return{command:"npx",args:["--no","lensmcp","mcp"]};const n="version"in r?r.version:pluginVersion();return{command:"npx",args:["--yes",n?`lensmcp@${n}`:"lensmcp","mcp"]}}p(mcpServerEntry,"mcpServerEntry"),c(mcpServerEntry,"mcpServerEntry");function pluginVersion(s=__dirname){let r=s;for(let n=0;n<6;n++){const e=(0,node_path_1.join)(r,"package.json");try{if((0,node_fs_1.existsSync)(e)){const o=JSON.parse((0,node_fs_1.readFileSync)(e,"utf-8"));if(o.name==="@lensmcp/nx-plugin"&&typeof o.version=="string")return o.version}}catch{}const t=(0,node_path_1.dirname)(r);if(t===r)break;r=t}}p(pluginVersion,"pluginVersion"),c(pluginVersion,"pluginVersion");function registerWorkspaceMcpConfig(s,r){const n=".mcp.json";let e={};if(s.exists(n))try{e=JSON.parse(s.read(n,"utf-8")??"{}")}catch{e={}}if((!e||typeof e!="object")&&(e={}),(!e.mcpServers||typeof e.mcpServers!="object")&&(e.mcpServers={}),e.mcpServers.lensmcp)return;const t=mcpServerEntry(s.root);e.mcpServers.lensmcp=t,s.write(n,JSON.stringify(e,null,2)+`
|
|
9
|
+
`),r.push(`.mcp.json: registered lensmcp MCP server (stdio, ${t.args.join(" ")})`)}p(registerWorkspaceMcpConfig,"registerWorkspaceMcpConfig"),c(registerWorkspaceMcpConfig,"registerWorkspaceMcpConfig"),exports.default=initGenerator;function mergeDeep(s,r){const n={...s};for(const[e,t]of Object.entries(r))t!==null&&typeof t=="object"&&!Array.isArray(t)&&n[e]!==null&&typeof n[e]=="object"&&!Array.isArray(n[e])?n[e]=mergeDeep(n[e],t):n[e]=t;return n}p(mergeDeep,"mergeDeep"),c(mergeDeep,"mergeDeep");
|
|
@@ -1,8 +1,8 @@
|
|
|
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
|
|
2
|
-
Expected a \`NestFactory.create(AppModule)\` bootstrap call. Edit manually: replace it with \`createLensmcpNestApp(AppModule, { projectName: '${
|
|
3
|
-
(Or use the module form: add \`${IMPORT_LINE}\` and push ${MODULE_CALL.replace("__PROJECT__",
|
|
4
|
-
${BOOTSTRAP_IMPORT}`+e.slice(
|
|
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
|
|
6
|
-
`,r);return
|
|
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 t={project:r.project,skipFormat:r.skipFormat??!1},n=(0,devkit_1.readProjectConfiguration)(e,t.project),s=findMain(e,n.root);if(!s)throw new Error(`setup-nest: no main.ts found under ${n.root}/src.`);const o=e.read(s,"utf-8")??"",p=patchMainBootstrap(o,t.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: '${t.project}' })\` and import it from '@lensmcp/nest-instrumentation'.
|
|
3
|
+
(Or use the module form: add \`${IMPORT_LINE}\` and push ${MODULE_CALL.replace("__PROJECT__",t.project)} into the @Module imports array.)`);p!==o&&e.write(s,p);const a={...n.targets??{}};a["agent-dev"]||(a["agent-dev"]={executor:"@lensmcp/nx-plugin:agent-dev",options:{kind:"nestjs"}},n.targets=a,(0,devkit_1.updateProjectConfiguration)(e,t.project,n)),t.skipFormat||await(0,devkit_1.formatFiles)(e)}c(setupNestGenerator,"setupNestGenerator"),i(setupNestGenerator,"setupNestGenerator"),exports.default=setupNestGenerator;function findMain(e,r){for(const t of["src/main.ts","main.ts","src/index.ts"]){const n=(0,devkit_1.joinPathFragments)(r,t);if(e.exists(n))return n}}c(findMain,"findMain"),i(findMain,"findMain");function findAppModule(e,r){for(const t of["src/app.module.ts","app.module.ts"]){const n=(0,devkit_1.joinPathFragments)(r,t);if(e.exists(n))return n}}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 t=CREATE_CALL_RE.exec(e);if(!t)return null;const n=t.index+t[0].length-1,s=matchingParen(e,n);if(s===-1)return null;const o=e.slice(n+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${(t[1]??"").trim()}(${p.trim()}, ${d})`;let l=e.slice(0,t.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)],t=r.length?r[r.length-1]:null;if(t&&t.index!==void 0){const n=t.index+t[0].length;return e.slice(0,n)+`
|
|
4
|
+
${BOOTSTRAP_IMPORT}`+e.slice(n)}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 t=r.names.filter(s=>s!=="NestFactory");if(t.length===0)return e.slice(0,r.start)+e.slice(r.end).replace(/^\n/,"");const n=`import { ${t.join(", ")} } from '@nestjs/core';`;return e.slice(0,r.start)+n+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 t=r[1].split(",").map(p=>p.trim()).filter(Boolean),n=r.index,s=r.index+r[0].length,o=e.slice(0,n)+e.slice(s);return{match:{start:n,end:s,names:t},rest:o}}c(stripNestFactoryImportSpan,"stripNestFactoryImportSpan"),i(stripNestFactoryImportSpan,"stripNestFactoryImportSpan");function matchingParen(e,r){let t=0;for(let n=r;n<e.length;n++){const s=skipNonCode(e,n);if(s>n){n=s-1;continue}const o=e[n];if(o==="(")t++;else if(o===")"&&(t--,t===0))return n}return-1}c(matchingParen,"matchingParen"),i(matchingParen,"matchingParen");function splitTopLevelArgs(e){let r=0;for(let t=0;t<e.length;t++){const n=skipNonCode(e,t);if(n>t){t=n-1;continue}const s=e[t];if(s==="("||s==="["||s==="{")r++;else if(s===")"||s==="]"||s==="}")r--;else if(s===","&&r===0)return{moduleArg:e.slice(0,t),restArg:e.slice(t+1)}}return{moduleArg:e,restArg:""}}c(splitTopLevelArgs,"splitTopLevelArgs"),i(splitTopLevelArgs,"splitTopLevelArgs");function skipNonCode(e,r){const t=e[r];if(t==='"'||t==="'"||t==="`"){for(let n=r+1;n<e.length;n++){if(e[n]==="\\"){n++;continue}if(e[n]===t)return n+1}return e.length}if(t==="/"&&e[r+1]==="/"){const n=e.indexOf(`
|
|
6
|
+
`,r);return n===-1?e.length:n}if(t==="/"&&e[r+1]==="*"){const n=e.indexOf("*/",r+2);return n===-1?e.length:n+2}return r}c(skipNonCode,"skipNonCode"),i(skipNonCode,"skipNonCode");function patchAppModule(e,r){const t=e.includes("from '@lensmcp/nest-instrumentation'")||e.includes('from "@lensmcp/nest-instrumentation"'),n=/LensmcpModule\s*\.\s*forRoot\s*\(/.test(e);if(t&&n)return e;const s=e.indexOf("imports:");if(s===-1||e.indexOf("[",s)===-1)return null;let o=e;if(!t){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
7
|
${IMPORT_LINE}`+e.slice(g)}else o=`${IMPORT_LINE}
|
|
8
|
-
`+e}if(
|
|
8
|
+
`+e}if(n)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");
|