@fiodos/cli 0.1.0
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/LICENSE +77 -0
- package/README.md +128 -0
- package/package.json +30 -0
- package/src/aiAnalyze.js +469 -0
- package/src/ast.js +263 -0
- package/src/collect.js +115 -0
- package/src/graph.js +160 -0
- package/src/index.js +667 -0
- package/src/llm.js +144 -0
- package/src/loadEnv.js +81 -0
- package/src/patterns.js +28 -0
- package/src/postWireTest.js +91 -0
- package/src/renderProbe.js +333 -0
- package/src/renderProbeVue.js +136 -0
- package/src/routes.js +81 -0
- package/src/verify.js +172 -0
- package/src/verifyWire.js +215 -0
- package/src/wireHandlers.js +1789 -0
- package/src/wireWeb.js +295 -0
- package/src/wireWebMount.js +435 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wireWebMount — safe, reversible orb injection for every web framework.
|
|
3
|
+
* Used by wireWeb.js after terminal consent [y/N]. Markers FYODOS:ORB:START/END
|
|
4
|
+
* make edits idempotent and easy to remove.
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const IMPORT_NAME = 'FiodosAgentMount';
|
|
12
|
+
const WRAPPER_BASENAME = 'fyodos-agent-mount';
|
|
13
|
+
|
|
14
|
+
const ORB_JSX_START = '{/* FYODOS:ORB:START (auto-generated — safe to remove) */}';
|
|
15
|
+
const ORB_JSX_END = '{/* FYODOS:ORB:END */}';
|
|
16
|
+
const ORB_HTML_START = '<!-- FYODOS:ORB:START (auto-generated — safe to remove) -->';
|
|
17
|
+
const ORB_HTML_END = '<!-- FYODOS:ORB:END -->';
|
|
18
|
+
const ORB_SCRIPT_START = '// FYODOS:ORB:START (auto-generated — safe to remove)';
|
|
19
|
+
const ORB_SCRIPT_END = '// FYODOS:ORB:END';
|
|
20
|
+
|
|
21
|
+
function escapeRe(s) {
|
|
22
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function stripMarkedRegion(source, start, end) {
|
|
26
|
+
const re = new RegExp(`${escapeRe(start)}[\\s\\S]*?${escapeRe(end)}\\n?`, 'g');
|
|
27
|
+
return source.replace(re, '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readFile(file) {
|
|
31
|
+
return fs.readFileSync(file, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writeFile(file, content) {
|
|
35
|
+
fs.writeFileSync(file, content);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function backupFiles(files) {
|
|
39
|
+
const backups = {};
|
|
40
|
+
for (const f of files) {
|
|
41
|
+
if (fs.existsSync(f)) backups[f] = readFile(f);
|
|
42
|
+
}
|
|
43
|
+
return backups;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function revertFiles(backups) {
|
|
47
|
+
for (const [f, content] of Object.entries(backups)) {
|
|
48
|
+
writeFile(f, content);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function insertImportAfterLastImport(source, importLine) {
|
|
53
|
+
const lines = source.split('\n');
|
|
54
|
+
let lastImport = -1;
|
|
55
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
56
|
+
if (/^\s*import\b/.test(lines[i])) lastImport = i;
|
|
57
|
+
}
|
|
58
|
+
if (lastImport === -1) return `${importLine}\n${source}`;
|
|
59
|
+
lines.splice(lastImport + 1, 0, importLine);
|
|
60
|
+
return lines.join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function envExpr(framework, kind, ts) {
|
|
64
|
+
if (framework === 'vite' || framework === 'vue' || framework === 'svelte' || framework === 'sveltekit') {
|
|
65
|
+
if (kind === 'key') {
|
|
66
|
+
return ts ? 'import.meta.env.VITE_FYODOS_API_KEY!' : 'import.meta.env.VITE_FYODOS_API_KEY';
|
|
67
|
+
}
|
|
68
|
+
return 'import.meta.env.VITE_FYODOS_API_URL';
|
|
69
|
+
}
|
|
70
|
+
const key = `process.env.NEXT_PUBLIC_FYODOS_API_KEY${ts ? '!' : ''}`;
|
|
71
|
+
const url = 'process.env.NEXT_PUBLIC_FYODOS_API_URL';
|
|
72
|
+
return kind === 'key' ? key : url;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function agentJsx(framework, ts, indent = ' ') {
|
|
76
|
+
const keyExpr = envExpr(framework, 'key', ts);
|
|
77
|
+
const urlExpr = envExpr(framework, 'url', ts);
|
|
78
|
+
return (
|
|
79
|
+
`${indent}<FiodosAgent\n` +
|
|
80
|
+
`${indent} apiKey={${keyExpr}}\n` +
|
|
81
|
+
`${indent} apiUrl={${urlExpr}}\n` +
|
|
82
|
+
`${indent}/>`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isAlreadyMounted(source) {
|
|
87
|
+
return /FiodosAgent|fyodos-agent|FYODOS:ORB:START/.test(source);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Assess whether we can inject safely. Returns { ok, reason?, strategy, files, rel }.
|
|
92
|
+
*/
|
|
93
|
+
function assessMount(target, framework, appRoot) {
|
|
94
|
+
if (!target || !target.file) {
|
|
95
|
+
return { ok: false, reason: 'could not locate a framework entry file (e.g. src/main.tsx, App.vue, +layout.svelte)' };
|
|
96
|
+
}
|
|
97
|
+
const rel = path.relative(appRoot, target.file);
|
|
98
|
+
const source = readFile(target.file);
|
|
99
|
+
if (isAlreadyMounted(source)) {
|
|
100
|
+
return { ok: false, reason: 'orb already present', already: true, rel };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
switch (target.kind) {
|
|
104
|
+
case 'next-app': {
|
|
105
|
+
if (!source.includes('</body>')) {
|
|
106
|
+
return { ok: false, reason: `${rel} has no </body> — cannot mount the orb wrapper safely` };
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, strategy: 'next-app', files: [target.file], rel, framework, target };
|
|
109
|
+
}
|
|
110
|
+
case 'next-pages':
|
|
111
|
+
case 'vite': {
|
|
112
|
+
if (/createRoot\s*\(/.test(source) && /\.render\s*\(/.test(source)) {
|
|
113
|
+
return { ok: true, strategy: 'vite-main', files: [target.file], rel, framework, target };
|
|
114
|
+
}
|
|
115
|
+
if (/return\s*\([\s\S]*/.test(source)) {
|
|
116
|
+
return { ok: true, strategy: 'react-return', files: [target.file], rel, framework, target };
|
|
117
|
+
}
|
|
118
|
+
return { ok: false, reason: `${rel} is not a recognizable React entry (no createRoot().render() or JSX return)` };
|
|
119
|
+
}
|
|
120
|
+
case 'vue': {
|
|
121
|
+
if (!/<template[\s>]/.test(source)) {
|
|
122
|
+
return { ok: false, reason: `${rel} has no <template> block` };
|
|
123
|
+
}
|
|
124
|
+
if (!/<script/.test(source)) {
|
|
125
|
+
return { ok: false, reason: `${rel} has no <script> block for imports` };
|
|
126
|
+
}
|
|
127
|
+
return { ok: true, strategy: 'vue-sfc', files: [target.file], rel, framework, target };
|
|
128
|
+
}
|
|
129
|
+
case 'svelte':
|
|
130
|
+
case 'sveltekit': {
|
|
131
|
+
if (!/<script/.test(source)) {
|
|
132
|
+
return { ok: false, reason: `${rel} has no <script> block` };
|
|
133
|
+
}
|
|
134
|
+
return { ok: true, strategy: 'svelte-sfc', files: [target.file], rel, framework, target };
|
|
135
|
+
}
|
|
136
|
+
case 'angular': {
|
|
137
|
+
const templatePath = resolveAngularTemplate(target.file, source);
|
|
138
|
+
if (!templatePath) {
|
|
139
|
+
return { ok: false, reason: `${rel} uses inline template — paste the snippet manually (external template only for now)` };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
strategy: 'angular-component',
|
|
144
|
+
files: [target.file, templatePath],
|
|
145
|
+
rel,
|
|
146
|
+
framework,
|
|
147
|
+
target,
|
|
148
|
+
templatePath,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
return { ok: false, reason: `unsupported mount kind "${target.kind}"` };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveAngularTemplate(tsFile, tsSource) {
|
|
157
|
+
const inline = /template\s*:\s*`/.test(tsSource) || /template\s*:\s*'/.test(tsSource);
|
|
158
|
+
if (inline) return null;
|
|
159
|
+
const m = tsSource.match(/templateUrl:\s*['"]([^'"]+)['"]/);
|
|
160
|
+
if (!m) return null;
|
|
161
|
+
const p = path.join(path.dirname(tsFile), m[1]);
|
|
162
|
+
return fs.existsSync(p) ? p : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function describePlan(plan) {
|
|
166
|
+
const lines = [`Will edit: ${plan.files.map((f) => path.basename(f)).join(', ')}`];
|
|
167
|
+
switch (plan.strategy) {
|
|
168
|
+
case 'next-app':
|
|
169
|
+
lines.push(`Create ${WRAPPER_BASENAME} client wrapper next to layout`);
|
|
170
|
+
lines.push(`Import wrapper and mount before </body>`);
|
|
171
|
+
break;
|
|
172
|
+
case 'vite-main':
|
|
173
|
+
lines.push('Add import { FiodosAgent } from @fiodos/react');
|
|
174
|
+
lines.push('Mount <FiodosAgent /> inside createRoot().render() (marked FYODOS:ORB:*)');
|
|
175
|
+
break;
|
|
176
|
+
case 'react-return':
|
|
177
|
+
lines.push('Add import { FiodosAgent } from @fiodos/react');
|
|
178
|
+
lines.push('Mount <FiodosAgent /> inside the root JSX return (marked FYODOS:ORB:*)');
|
|
179
|
+
break;
|
|
180
|
+
case 'vue-sfc':
|
|
181
|
+
lines.push('Add @fiodos/vue import + env vars in <script>');
|
|
182
|
+
lines.push('Mount <FiodosAgent /> in <template> (marked FYODOS:ORB:*)');
|
|
183
|
+
break;
|
|
184
|
+
case 'svelte-sfc':
|
|
185
|
+
lines.push('Add @fiodos/svelte import in <script>');
|
|
186
|
+
lines.push('Mount <FiodosAgent /> in layout (marked FYODOS:ORB:*)');
|
|
187
|
+
break;
|
|
188
|
+
case 'angular-component':
|
|
189
|
+
lines.push('Add FiodosAgentComponent to @Component imports');
|
|
190
|
+
lines.push('Mount <fyodos-agent> in template (marked FYODOS:ORB:*)');
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
return lines;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function writeWrapper(layoutFile, framework, ts) {
|
|
199
|
+
const dir = path.dirname(layoutFile);
|
|
200
|
+
const ext = ts ? '.tsx' : '.jsx';
|
|
201
|
+
const wrapperFile = path.join(dir, `${WRAPPER_BASENAME}${ext}`);
|
|
202
|
+
if (fs.existsSync(wrapperFile) && /FiodosAgent/.test(readFile(wrapperFile))) {
|
|
203
|
+
return wrapperFile;
|
|
204
|
+
}
|
|
205
|
+
const body =
|
|
206
|
+
`'use client';\n\n` +
|
|
207
|
+
`import { FiodosAgent } from '@fiodos/react';\n\n` +
|
|
208
|
+
`export default function ${IMPORT_NAME}() {\n` +
|
|
209
|
+
` return (\n` +
|
|
210
|
+
`${agentJsx(framework, ts, ' ')}\n` +
|
|
211
|
+
` );\n` +
|
|
212
|
+
`}\n`;
|
|
213
|
+
writeFile(wrapperFile, body);
|
|
214
|
+
return wrapperFile;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function injectNextApp(plan) {
|
|
218
|
+
const { target, framework } = plan;
|
|
219
|
+
const ts = target.ext === '.tsx';
|
|
220
|
+
let source = stripMarkedRegion(readFile(target.file), ORB_HTML_START, ORB_HTML_END);
|
|
221
|
+
writeWrapper(target.file, framework, ts);
|
|
222
|
+
const importLine = `import ${IMPORT_NAME} from './${WRAPPER_BASENAME}';`;
|
|
223
|
+
if (!source.includes(importLine)) {
|
|
224
|
+
source = insertImportAfterLastImport(source, importLine);
|
|
225
|
+
}
|
|
226
|
+
source = source.replace(/([^\n]*)<\/body>/, (_m, before) => {
|
|
227
|
+
const indent = (before.match(/^(\s*)/) || ['', ''])[1];
|
|
228
|
+
const mount = `${indent} <${IMPORT_NAME} />\n`;
|
|
229
|
+
return before.trim() === ''
|
|
230
|
+
? `${mount}${before}</body>`
|
|
231
|
+
: `${before}\n${mount}${indent}</body>`;
|
|
232
|
+
});
|
|
233
|
+
writeFile(target.file, source);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function injectViteMain(plan) {
|
|
237
|
+
const { target, framework } = plan;
|
|
238
|
+
const ts = target.ext === '.tsx';
|
|
239
|
+
let source = stripMarkedRegion(readFile(target.file), ORB_JSX_START, ORB_JSX_END);
|
|
240
|
+
const importLine = `import { FiodosAgent } from '@fiodos/react';`;
|
|
241
|
+
if (!source.includes(importLine)) {
|
|
242
|
+
source = insertImportAfterLastImport(source, importLine);
|
|
243
|
+
}
|
|
244
|
+
const orbBlock = [
|
|
245
|
+
` ${ORB_JSX_START}`,
|
|
246
|
+
agentJsx(framework === 'next' ? 'next' : 'vite', ts, ' '),
|
|
247
|
+
` ${ORB_JSX_END}`,
|
|
248
|
+
].join('\n');
|
|
249
|
+
const anchors = ['</StrictMode>', '</React.StrictMode>'];
|
|
250
|
+
for (const anchor of anchors) {
|
|
251
|
+
if (source.includes(anchor)) {
|
|
252
|
+
source = source.replace(anchor, `${orbBlock}\n ${anchor}`);
|
|
253
|
+
writeFile(target.file, source);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const renderIdx = source.search(/\.render\s*\(/);
|
|
258
|
+
if (renderIdx === -1) throw new Error('no .render( found');
|
|
259
|
+
const closeIdx = source.indexOf(');', renderIdx);
|
|
260
|
+
if (closeIdx === -1) throw new Error('no render close');
|
|
261
|
+
source = `${source.slice(0, closeIdx)}\n ${orbBlock}\n ${source.slice(closeIdx)}`;
|
|
262
|
+
writeFile(target.file, source);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function injectReactReturn(plan) {
|
|
266
|
+
const { target, framework } = plan;
|
|
267
|
+
const ts = target.ext === '.tsx';
|
|
268
|
+
let source = stripMarkedRegion(readFile(target.file), ORB_JSX_START, ORB_JSX_END);
|
|
269
|
+
const importLine = `import { FiodosAgent } from '@fiodos/react';`;
|
|
270
|
+
if (!source.includes(importLine)) {
|
|
271
|
+
source = insertImportAfterLastImport(source, importLine);
|
|
272
|
+
}
|
|
273
|
+
const fw = framework === 'next' ? 'next' : 'vite';
|
|
274
|
+
const orbBlock = [ORB_JSX_START, agentJsx(fw, ts, ' '), ORB_JSX_END].join('\n');
|
|
275
|
+
const anchors = ['</AppProvider>', '</BrowserRouter>', '</RouterProvider>', '</div>', '</>'];
|
|
276
|
+
for (const anchor of anchors) {
|
|
277
|
+
const idx = source.lastIndexOf(anchor);
|
|
278
|
+
if (idx !== -1) {
|
|
279
|
+
source = `${source.slice(0, idx)}${orbBlock}\n ${source.slice(idx)}`;
|
|
280
|
+
writeFile(target.file, source);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const ret = source.lastIndexOf('return (');
|
|
285
|
+
if (ret === -1) throw new Error('no return ( found');
|
|
286
|
+
const close = source.indexOf(');', ret);
|
|
287
|
+
if (close === -1) throw new Error('no return close');
|
|
288
|
+
source = `${source.slice(0, close)}\n ${orbBlock}\n ${source.slice(close)}`;
|
|
289
|
+
writeFile(target.file, source);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function injectVueSfc(plan) {
|
|
293
|
+
const { target } = plan;
|
|
294
|
+
let source = stripMarkedRegion(readFile(target.file), ORB_HTML_START, ORB_HTML_END);
|
|
295
|
+
if (!/from ['"]@fyodos\/vue['"]/.test(source)) {
|
|
296
|
+
source = source.replace(
|
|
297
|
+
/(<script[^>]*>)/,
|
|
298
|
+
`$1\nimport { FiodosAgent } from '@fiodos/vue';\nconst fyodosApiKey = import.meta.env.VITE_FYODOS_API_KEY;\nconst fyodosApiUrl = import.meta.env.VITE_FYODOS_API_URL;\n`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const block = [
|
|
302
|
+
ORB_HTML_START,
|
|
303
|
+
' <FiodosAgent :api-key="fyodosApiKey" :base-url="fyodosApiUrl" />',
|
|
304
|
+
ORB_HTML_END,
|
|
305
|
+
].join('\n');
|
|
306
|
+
source = source.replace('</template>', `${block}\n</template>`);
|
|
307
|
+
writeFile(target.file, source);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function injectSvelteSfc(plan) {
|
|
311
|
+
const { target } = plan;
|
|
312
|
+
let source = stripMarkedRegion(readFile(target.file), ORB_HTML_START, ORB_HTML_END);
|
|
313
|
+
if (!/from ['"]@fyodos\/svelte/.test(source)) {
|
|
314
|
+
source = source.replace(
|
|
315
|
+
/(<script[^>]*>)/,
|
|
316
|
+
`$1\n\timport FiodosAgent from '@fiodos/svelte/FiodosAgent.svelte';\n`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
const block = [
|
|
320
|
+
ORB_HTML_START,
|
|
321
|
+
'<FiodosAgent apiKey={import.meta.env.VITE_FYODOS_API_KEY} baseUrl={import.meta.env.VITE_FYODOS_API_URL} />',
|
|
322
|
+
ORB_HTML_END,
|
|
323
|
+
].join('\n');
|
|
324
|
+
if (source.includes('</main>')) {
|
|
325
|
+
source = source.replace('</main>', `</main>\n\n${block}`);
|
|
326
|
+
} else {
|
|
327
|
+
source = `${source.trimEnd()}\n\n${block}\n`;
|
|
328
|
+
}
|
|
329
|
+
writeFile(target.file, source);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function injectAngularComponent(plan) {
|
|
333
|
+
const { target, templatePath } = plan;
|
|
334
|
+
let ts = stripMarkedRegion(readFile(target.file), ORB_SCRIPT_START, ORB_SCRIPT_END);
|
|
335
|
+
let html = stripMarkedRegion(readFile(templatePath), ORB_HTML_START, ORB_HTML_END);
|
|
336
|
+
|
|
337
|
+
if (!/FiodosAgentComponent/.test(ts)) {
|
|
338
|
+
ts = insertImportAfterLastImport(ts, `import { FiodosAgentComponent } from '@fiodos/angular/angular';`);
|
|
339
|
+
}
|
|
340
|
+
if (!/FiodosAgentComponent/.test(ts)) {
|
|
341
|
+
ts = ts.replace(/imports:\s*\[/, 'imports: [FiodosAgentComponent, ');
|
|
342
|
+
}
|
|
343
|
+
const envPath = path.join(path.dirname(target.file), '../environments/environment.ts');
|
|
344
|
+
const hasEnv = fs.existsSync(envPath);
|
|
345
|
+
if (hasEnv && !/from ['"].*environment['"]/.test(ts)) {
|
|
346
|
+
const relEnv = path.relative(path.dirname(target.file), envPath).replace(/\\/g, '/').replace(/\.ts$/, '');
|
|
347
|
+
ts = insertImportAfterLastImport(ts, `import { environment } from '${relEnv.startsWith('.') ? relEnv : `./${relEnv}`}';`);
|
|
348
|
+
}
|
|
349
|
+
if (!/fyodosApiKey/.test(ts)) {
|
|
350
|
+
ts = ts.replace(
|
|
351
|
+
/export class (\w+) \{/,
|
|
352
|
+
hasEnv
|
|
353
|
+
? `export class $1 {\n readonly fyodosApiKey = environment.fyodosApiKey;\n readonly fyodosApiUrl = environment.fyodosApiUrl;`
|
|
354
|
+
: `export class $1 {\n readonly fyodosApiKey = 'YOUR_FYODOS_API_KEY';`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const block = [ORB_HTML_START, '<fyodos-agent [apiKey]="fyodosApiKey" [baseUrl]="fyodosApiUrl" />', ORB_HTML_END].join('\n');
|
|
359
|
+
html = `${html.trimEnd()}\n${block}\n`;
|
|
360
|
+
writeFile(target.file, ts);
|
|
361
|
+
writeFile(templatePath, html);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function applyMount(plan) {
|
|
365
|
+
switch (plan.strategy) {
|
|
366
|
+
case 'next-app':
|
|
367
|
+
injectNextApp(plan);
|
|
368
|
+
break;
|
|
369
|
+
case 'vite-main':
|
|
370
|
+
injectViteMain(plan);
|
|
371
|
+
break;
|
|
372
|
+
case 'react-return':
|
|
373
|
+
injectReactReturn(plan);
|
|
374
|
+
break;
|
|
375
|
+
case 'vue-sfc':
|
|
376
|
+
injectVueSfc(plan);
|
|
377
|
+
break;
|
|
378
|
+
case 'svelte-sfc':
|
|
379
|
+
injectSvelteSfc(plan);
|
|
380
|
+
break;
|
|
381
|
+
case 'angular-component':
|
|
382
|
+
injectAngularComponent(plan);
|
|
383
|
+
break;
|
|
384
|
+
default:
|
|
385
|
+
throw new Error(`unknown strategy ${plan.strategy}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function verifyMounted(plan) {
|
|
390
|
+
for (const f of plan.files) {
|
|
391
|
+
const src = readFile(f);
|
|
392
|
+
if (!/FiodosAgent|fyodos-agent|FYODOS:ORB:START/.test(src)) return false;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function writeConsentDoc(appRoot, plan) {
|
|
398
|
+
const dir = path.join(appRoot, 'src', 'fyodos');
|
|
399
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
400
|
+
const doc = [
|
|
401
|
+
'# Fiodos orb mount (auto-generated)',
|
|
402
|
+
'',
|
|
403
|
+
`Date: ${new Date().toISOString()}`,
|
|
404
|
+
`Strategy: ${plan.strategy}`,
|
|
405
|
+
`Files edited:`,
|
|
406
|
+
...plan.files.map((f) => `- ${path.relative(appRoot, f)}`),
|
|
407
|
+
'',
|
|
408
|
+
'Changes are wrapped in FYODOS:ORB:START / FYODOS:ORB:END markers.',
|
|
409
|
+
'Remove those blocks (and the Fiodos import) to revert manually.',
|
|
410
|
+
'',
|
|
411
|
+
'Planned edits:',
|
|
412
|
+
...describePlan(plan).map((l) => `- ${l}`),
|
|
413
|
+
'',
|
|
414
|
+
].join('\n');
|
|
415
|
+
writeFile(path.join(dir, 'FYODOS_ORB_MOUNT.md'), doc);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
module.exports = {
|
|
419
|
+
ORB_HTML_START,
|
|
420
|
+
ORB_HTML_END,
|
|
421
|
+
ORB_JSX_START,
|
|
422
|
+
ORB_JSX_END,
|
|
423
|
+
agentJsx,
|
|
424
|
+
envExpr,
|
|
425
|
+
assessMount,
|
|
426
|
+
describePlan,
|
|
427
|
+
applyMount,
|
|
428
|
+
verifyMounted,
|
|
429
|
+
backupFiles,
|
|
430
|
+
revertFiles,
|
|
431
|
+
writeConsentDoc,
|
|
432
|
+
isAlreadyMounted,
|
|
433
|
+
IMPORT_NAME,
|
|
434
|
+
WRAPPER_BASENAME,
|
|
435
|
+
};
|