@fiodos/cli 0.1.23 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/aiAnalyze.js +6 -4
- package/src/changeRegistry.js +109 -1
- package/src/collect.js +16 -2
- package/src/index.js +535 -7
- package/src/odata.js +529 -0
- package/src/shopifyTheme.js +300 -0
- package/src/verify.js +124 -1
- package/src/wireHandlers.js +115 -7
- package/src/wireReactNative.js +52 -0
- package/src/wireScreen.js +562 -0
- package/src/wireSession.js +755 -0
- package/src/wireWeb.js +215 -0
- package/src/wireWebMount.js +171 -8
package/src/wireWeb.js
CHANGED
|
@@ -27,6 +27,10 @@ const {
|
|
|
27
27
|
registryRelImport,
|
|
28
28
|
addRegistriesToMountSource,
|
|
29
29
|
addRegistriesToBootstrapSource,
|
|
30
|
+
addSessionToMountSource,
|
|
31
|
+
addSessionToBootstrapSource,
|
|
32
|
+
addScreenToMountSource,
|
|
33
|
+
addScreenToBootstrapSource,
|
|
30
34
|
buildEntryBootstrapBlock,
|
|
31
35
|
IMPORT_NAME,
|
|
32
36
|
WRAPPER_BASENAME,
|
|
@@ -499,6 +503,213 @@ function connectOrbRegistries(appRoot, opts = {}) {
|
|
|
499
503
|
}
|
|
500
504
|
}
|
|
501
505
|
|
|
506
|
+
/**
|
|
507
|
+
* Third pass — connect the generated SESSION module to the mounted orb, so the
|
|
508
|
+
* agent KNOWS whether the user is signed in (requiresAuth gate) and keys the
|
|
509
|
+
* conversation memory per user. Mirrors connectOrbRegistries: idempotent,
|
|
510
|
+
* reversible, never overrides a developer-wired isAuthenticated.
|
|
511
|
+
*/
|
|
512
|
+
function connectOrbSession(appRoot, opts = {}) {
|
|
513
|
+
const { sessionFileAbs } = opts;
|
|
514
|
+
if (!sessionFileAbs || !fs.existsSync(sessionFileAbs)) return { status: 'no-session-file' };
|
|
515
|
+
|
|
516
|
+
// Only connect the capability getter when the generated module actually
|
|
517
|
+
// exports it — referencing a missing export would break the app's build.
|
|
518
|
+
let sessionHasCapabilities = false;
|
|
519
|
+
try {
|
|
520
|
+
sessionHasCapabilities = fs
|
|
521
|
+
.readFileSync(sessionFileAbs, 'utf8')
|
|
522
|
+
.includes('fyodosGetSessionCapabilities');
|
|
523
|
+
} catch {
|
|
524
|
+
sessionHasCapabilities = false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const framework = detectFramework(appRoot);
|
|
528
|
+
const target = detectTarget(appRoot, framework);
|
|
529
|
+
if (!target || !target.file) return { status: 'no-target' };
|
|
530
|
+
|
|
531
|
+
const bootstrapKinds = new Set(['vue-entry', 'svelte-entry', 'sveltekit-hooks']);
|
|
532
|
+
if (bootstrapKinds.has(target.kind)) {
|
|
533
|
+
let source;
|
|
534
|
+
try {
|
|
535
|
+
source = fs.readFileSync(target.file, 'utf8');
|
|
536
|
+
} catch {
|
|
537
|
+
return { status: 'not-mounted' };
|
|
538
|
+
}
|
|
539
|
+
const importPath = registryRelImport(target.file, sessionFileAbs);
|
|
540
|
+
const fileRel = path.relative(appRoot, target.file);
|
|
541
|
+
const backups = backupFiles([target.file]);
|
|
542
|
+
try {
|
|
543
|
+
const res = addSessionToBootstrapSource(source, importPath, { sessionHasCapabilities });
|
|
544
|
+
if (!res.changed) {
|
|
545
|
+
return { status: res.reason === 'already' ? 'already' : res.reason || 'not-mounted', file: fileRel };
|
|
546
|
+
}
|
|
547
|
+
fs.writeFileSync(target.file, res.source);
|
|
548
|
+
return { status: 'connected', file: fileRel };
|
|
549
|
+
} catch (e) {
|
|
550
|
+
revertFiles(backups);
|
|
551
|
+
return { status: 'failed', file: fileRel, error: e };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const reactKinds = new Set(['vite', 'next-pages', 'next-app']);
|
|
556
|
+
if (!reactKinds.has(target.kind)) return { status: 'unsupported-framework', framework };
|
|
557
|
+
|
|
558
|
+
let mountFile = target.file;
|
|
559
|
+
if (target.kind === 'next-app') {
|
|
560
|
+
const dir = path.dirname(target.file);
|
|
561
|
+
mountFile =
|
|
562
|
+
[path.join(dir, `${WRAPPER_BASENAME}.tsx`), path.join(dir, `${WRAPPER_BASENAME}.jsx`)].find((f) =>
|
|
563
|
+
fs.existsSync(f),
|
|
564
|
+
) || null;
|
|
565
|
+
if (!mountFile) return { status: 'not-mounted' };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
let source;
|
|
569
|
+
try {
|
|
570
|
+
source = fs.readFileSync(mountFile, 'utf8');
|
|
571
|
+
} catch {
|
|
572
|
+
return { status: 'not-mounted' };
|
|
573
|
+
}
|
|
574
|
+
if (!/<FiodosAgent\b/.test(source)) return { status: 'not-mounted' };
|
|
575
|
+
|
|
576
|
+
const importPath = registryRelImport(mountFile, sessionFileAbs);
|
|
577
|
+
const fileRel = path.relative(appRoot, mountFile);
|
|
578
|
+
const backups = backupFiles([mountFile]);
|
|
579
|
+
try {
|
|
580
|
+
const res = addSessionToMountSource(source, importPath, { sessionHasCapabilities });
|
|
581
|
+
if (!res.changed) {
|
|
582
|
+
return { status: res.reason || 'not-mounted', file: fileRel };
|
|
583
|
+
}
|
|
584
|
+
fs.writeFileSync(mountFile, res.source);
|
|
585
|
+
return { status: 'connected', file: fileRel };
|
|
586
|
+
} catch (e) {
|
|
587
|
+
revertFiles(backups);
|
|
588
|
+
return { status: 'failed', file: fileRel, error: e };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Fourth pass — connect the generated SCREEN-TRUTH module to the mounted orb,
|
|
594
|
+
* so the agent knows WHICH SCREEN the user is on even when screens are client
|
|
595
|
+
* state instead of URLs. Mirrors connectOrbSession: idempotent, reversible,
|
|
596
|
+
* never overrides a developer-wired getCurrentRoute.
|
|
597
|
+
*/
|
|
598
|
+
function connectOrbScreen(appRoot, opts = {}) {
|
|
599
|
+
const { screenFileAbs } = opts;
|
|
600
|
+
if (!screenFileAbs || !fs.existsSync(screenFileAbs)) return { status: 'no-screen-file' };
|
|
601
|
+
|
|
602
|
+
const framework = detectFramework(appRoot);
|
|
603
|
+
const target = detectTarget(appRoot, framework);
|
|
604
|
+
if (!target || !target.file) return { status: 'no-target' };
|
|
605
|
+
|
|
606
|
+
const bootstrapKinds = new Set(['vue-entry', 'svelte-entry', 'sveltekit-hooks']);
|
|
607
|
+
if (bootstrapKinds.has(target.kind)) {
|
|
608
|
+
let source;
|
|
609
|
+
try {
|
|
610
|
+
source = fs.readFileSync(target.file, 'utf8');
|
|
611
|
+
} catch {
|
|
612
|
+
return { status: 'not-mounted' };
|
|
613
|
+
}
|
|
614
|
+
const importPath = registryRelImport(target.file, screenFileAbs);
|
|
615
|
+
const fileRel = path.relative(appRoot, target.file);
|
|
616
|
+
const backups = backupFiles([target.file]);
|
|
617
|
+
try {
|
|
618
|
+
const res = addScreenToBootstrapSource(source, importPath);
|
|
619
|
+
if (!res.changed) {
|
|
620
|
+
return { status: res.reason === 'already' ? 'already' : res.reason || 'not-mounted', file: fileRel };
|
|
621
|
+
}
|
|
622
|
+
fs.writeFileSync(target.file, res.source);
|
|
623
|
+
return { status: 'connected', file: fileRel };
|
|
624
|
+
} catch (e) {
|
|
625
|
+
revertFiles(backups);
|
|
626
|
+
return { status: 'failed', file: fileRel, error: e };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const reactKinds = new Set(['vite', 'next-pages', 'next-app']);
|
|
631
|
+
if (!reactKinds.has(target.kind)) return { status: 'unsupported-framework', framework };
|
|
632
|
+
|
|
633
|
+
let mountFile = target.file;
|
|
634
|
+
if (target.kind === 'next-app') {
|
|
635
|
+
const dir = path.dirname(target.file);
|
|
636
|
+
mountFile =
|
|
637
|
+
[path.join(dir, `${WRAPPER_BASENAME}.tsx`), path.join(dir, `${WRAPPER_BASENAME}.jsx`)].find((f) =>
|
|
638
|
+
fs.existsSync(f),
|
|
639
|
+
) || null;
|
|
640
|
+
if (!mountFile) return { status: 'not-mounted' };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let source;
|
|
644
|
+
try {
|
|
645
|
+
source = fs.readFileSync(mountFile, 'utf8');
|
|
646
|
+
} catch {
|
|
647
|
+
return { status: 'not-mounted' };
|
|
648
|
+
}
|
|
649
|
+
if (!/<FiodosAgent\b/.test(source)) return { status: 'not-mounted' };
|
|
650
|
+
|
|
651
|
+
const importPath = registryRelImport(mountFile, screenFileAbs);
|
|
652
|
+
const fileRel = path.relative(appRoot, mountFile);
|
|
653
|
+
const backups = backupFiles([mountFile]);
|
|
654
|
+
try {
|
|
655
|
+
const res = addScreenToMountSource(source, importPath);
|
|
656
|
+
if (!res.changed) {
|
|
657
|
+
return { status: res.reason || 'not-mounted', file: fileRel };
|
|
658
|
+
}
|
|
659
|
+
fs.writeFileSync(mountFile, res.source);
|
|
660
|
+
return { status: 'connected', file: fileRel };
|
|
661
|
+
} catch (e) {
|
|
662
|
+
revertFiles(backups);
|
|
663
|
+
return { status: 'failed', file: fileRel, error: e };
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function reportScreenConnectResult(result, colors = {}, opts = {}) {
|
|
668
|
+
const quiet = opts.quiet !== false;
|
|
669
|
+
if (!result) return;
|
|
670
|
+
const { blue, cyan, dim, reset } = colors;
|
|
671
|
+
const tag = `${cyan || ''}◉${reset || ''} ${blue || ''}Fiodos${reset || ''}`;
|
|
672
|
+
switch (result.status) {
|
|
673
|
+
case 'connected':
|
|
674
|
+
console.error(`${tag} · screen truth connected to the orb mount (${result.file})`);
|
|
675
|
+
break;
|
|
676
|
+
case 'already':
|
|
677
|
+
if (!quiet) console.error(`${tag} · ${dim || ''}screen truth already connected to the mount${reset || ''}`);
|
|
678
|
+
break;
|
|
679
|
+
case 'dev-wired':
|
|
680
|
+
if (!quiet) {
|
|
681
|
+
console.error(`${tag} · ${dim || ''}the mount already has its own getCurrentRoute — left as is${reset || ''}`);
|
|
682
|
+
}
|
|
683
|
+
break;
|
|
684
|
+
default:
|
|
685
|
+
if (!quiet) console.error(`${tag} · ${dim || ''}screen truth not connected to the mount (${result.status})${reset || ''}`);
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function reportSessionConnectResult(result, colors = {}, opts = {}) {
|
|
691
|
+
const quiet = opts.quiet !== false;
|
|
692
|
+
if (!result) return;
|
|
693
|
+
const { blue, cyan, dim, reset } = colors;
|
|
694
|
+
const tag = `${cyan || ''}◉${reset || ''} ${blue || ''}Fiodos${reset || ''}`;
|
|
695
|
+
switch (result.status) {
|
|
696
|
+
case 'connected':
|
|
697
|
+
console.error(`${tag} · session connected to the orb mount (${result.file})`);
|
|
698
|
+
break;
|
|
699
|
+
case 'already':
|
|
700
|
+
if (!quiet) console.error(`${tag} · ${dim || ''}session already connected to the mount${reset || ''}`);
|
|
701
|
+
break;
|
|
702
|
+
case 'dev-wired':
|
|
703
|
+
if (!quiet) {
|
|
704
|
+
console.error(`${tag} · ${dim || ''}the mount already has its own isAuthenticated — left as is${reset || ''}`);
|
|
705
|
+
}
|
|
706
|
+
break;
|
|
707
|
+
default:
|
|
708
|
+
if (!quiet) console.error(`${tag} · ${dim || ''}session not connected to the mount (${result.status})${reset || ''}`);
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
502
713
|
function reportRegistriesResult(result, colors = {}, opts = {}) {
|
|
503
714
|
const quiet = opts.quiet !== false;
|
|
504
715
|
if (!result) return;
|
|
@@ -530,6 +741,10 @@ module.exports = {
|
|
|
530
741
|
reportWireResult,
|
|
531
742
|
connectOrbRegistries,
|
|
532
743
|
reportRegistriesResult,
|
|
744
|
+
connectOrbSession,
|
|
745
|
+
reportSessionConnectResult,
|
|
746
|
+
connectOrbScreen,
|
|
747
|
+
reportScreenConnectResult,
|
|
533
748
|
detectFramework,
|
|
534
749
|
detectTarget,
|
|
535
750
|
};
|
package/src/wireWebMount.js
CHANGED
|
@@ -83,19 +83,45 @@ function insertBlockAfterLastImport(source, block) {
|
|
|
83
83
|
// FYODOS:ORB markers → idempotent (re-runs strip & rewrite) and reversible.
|
|
84
84
|
const BOOTSTRAP_ALIAS = '__fiodosCreateAgent';
|
|
85
85
|
const BOOTSTRAP_REGISTRY_EXPORT = 'fyodosGeneratedRegistries';
|
|
86
|
+
// Exports of the generated session module (see wireSession.js). Connected to
|
|
87
|
+
// the mount automatically so auth awareness needs NO manual step.
|
|
88
|
+
const SESSION_IS_AUTH_EXPORT = 'fyodosIsAuthenticated';
|
|
89
|
+
const SESSION_USER_ID_EXPORT = 'fyodosGetUserId';
|
|
90
|
+
// Capability flags (audience map) — only connected when the generated session
|
|
91
|
+
// module actually exports it (older generated files do not).
|
|
92
|
+
const SESSION_CAPS_EXPORT = 'fyodosGetSessionCapabilities';
|
|
93
|
+
// Export of the generated screen-truth module (see wireScreen.js). Connected
|
|
94
|
+
// to the mount automatically so state-driven screen awareness needs NO manual step.
|
|
95
|
+
const SCREEN_ROUTE_EXPORT = 'fyodosGetCurrentRoute';
|
|
86
96
|
|
|
87
97
|
function buildEntryBootstrapBlock(opts = {}) {
|
|
88
|
-
const { registryImportPath } = opts;
|
|
98
|
+
const { registryImportPath, sessionImportPath, screenImportPath, sessionHasCapabilities } = opts;
|
|
89
99
|
const lines = [];
|
|
90
100
|
lines.push(ORB_SCRIPT_START);
|
|
91
101
|
lines.push(`import { createFiodosAgent as ${BOOTSTRAP_ALIAS} } from '@fiodos/web-core';`);
|
|
92
102
|
if (registryImportPath) {
|
|
93
103
|
lines.push(`import { ${BOOTSTRAP_REGISTRY_EXPORT} } from '${registryImportPath}';`);
|
|
94
104
|
}
|
|
105
|
+
if (sessionImportPath) {
|
|
106
|
+
const sessionNames = [SESSION_IS_AUTH_EXPORT, SESSION_USER_ID_EXPORT];
|
|
107
|
+
if (sessionHasCapabilities) sessionNames.push(SESSION_CAPS_EXPORT);
|
|
108
|
+
lines.push(`import { ${sessionNames.join(', ')} } from '${sessionImportPath}';`);
|
|
109
|
+
}
|
|
110
|
+
if (screenImportPath) {
|
|
111
|
+
lines.push(`import { ${SCREEN_ROUTE_EXPORT} } from '${screenImportPath}';`);
|
|
112
|
+
}
|
|
95
113
|
lines.push(`${BOOTSTRAP_ALIAS}({`);
|
|
96
114
|
lines.push(' apiKey: import.meta.env.VITE_FYODOS_API_KEY,');
|
|
97
115
|
lines.push(' baseUrl: import.meta.env.VITE_FYODOS_API_URL,');
|
|
98
116
|
if (registryImportPath) lines.push(` registries: ${BOOTSTRAP_REGISTRY_EXPORT},`);
|
|
117
|
+
if (sessionImportPath) {
|
|
118
|
+
lines.push(` isAuthenticated: ${SESSION_IS_AUTH_EXPORT},`);
|
|
119
|
+
lines.push(` getUserId: ${SESSION_USER_ID_EXPORT},`);
|
|
120
|
+
if (sessionHasCapabilities) {
|
|
121
|
+
lines.push(` getSessionCapabilities: ${SESSION_CAPS_EXPORT},`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (screenImportPath) lines.push(` getCurrentRoute: ${SCREEN_ROUTE_EXPORT},`);
|
|
99
125
|
lines.push(' mount: true,');
|
|
100
126
|
lines.push('});');
|
|
101
127
|
lines.push(ORB_SCRIPT_END);
|
|
@@ -123,8 +149,70 @@ function addRegistriesToBootstrapSource(source, registryImportPath) {
|
|
|
123
149
|
if (/\n\s*registries:\s/.test(source.slice(source.indexOf(ORB_SCRIPT_START)))) {
|
|
124
150
|
return { changed: false, source, reason: 'already' };
|
|
125
151
|
}
|
|
152
|
+
// Preserve session/screen connections previous passes already added.
|
|
153
|
+
const sessionImportPath = extractBootstrapImportPath(source, SESSION_IS_AUTH_EXPORT);
|
|
154
|
+
const sessionHasCapabilities = Boolean(extractBootstrapImportPath(source, SESSION_CAPS_EXPORT));
|
|
155
|
+
const screenImportPath = extractBootstrapImportPath(source, SCREEN_ROUTE_EXPORT);
|
|
126
156
|
const stripped = stripMarkedRegion(source, ORB_SCRIPT_START, ORB_SCRIPT_END).replace(/\n{3,}$/, '\n');
|
|
127
|
-
const block = buildEntryBootstrapBlock({
|
|
157
|
+
const block = buildEntryBootstrapBlock({
|
|
158
|
+
registryImportPath,
|
|
159
|
+
sessionImportPath,
|
|
160
|
+
screenImportPath,
|
|
161
|
+
sessionHasCapabilities,
|
|
162
|
+
});
|
|
163
|
+
const next = insertBlockAfterLastImport(stripped.trimEnd(), block);
|
|
164
|
+
return { changed: true, source: next.endsWith('\n') ? next : `${next}\n` };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Import path of the module that exports `name` inside our bootstrap block (or null).
|
|
168
|
+
function extractBootstrapImportPath(source, name) {
|
|
169
|
+
const startIdx = source.indexOf(ORB_SCRIPT_START);
|
|
170
|
+
if (startIdx === -1) return null;
|
|
171
|
+
const endIdx = source.indexOf(ORB_SCRIPT_END, startIdx);
|
|
172
|
+
const block = source.slice(startIdx, endIdx === -1 ? undefined : endIdx);
|
|
173
|
+
const m = block.match(new RegExp(`import\\s*\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]([^'"]+)['"]`));
|
|
174
|
+
return m ? m[1] : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Idempotently rewrite an existing entry-bootstrap block to carry the generated
|
|
178
|
+
// SESSION connection (third pass, after session wiring). Preserves the
|
|
179
|
+
// registries the second pass added. Returns { changed, source, reason? }.
|
|
180
|
+
function addSessionToBootstrapSource(source, sessionImportPath, opts = {}) {
|
|
181
|
+
if (!source.includes(ORB_SCRIPT_START)) return { changed: false, source, reason: 'not-mounted' };
|
|
182
|
+
if (/\n\s*isAuthenticated:\s/.test(source.slice(source.indexOf(ORB_SCRIPT_START)))) {
|
|
183
|
+
return { changed: false, source, reason: 'already' };
|
|
184
|
+
}
|
|
185
|
+
const registryImportPath = extractBootstrapImportPath(source, BOOTSTRAP_REGISTRY_EXPORT);
|
|
186
|
+
const screenImportPath = extractBootstrapImportPath(source, SCREEN_ROUTE_EXPORT);
|
|
187
|
+
const stripped = stripMarkedRegion(source, ORB_SCRIPT_START, ORB_SCRIPT_END).replace(/\n{3,}$/, '\n');
|
|
188
|
+
const block = buildEntryBootstrapBlock({
|
|
189
|
+
registryImportPath,
|
|
190
|
+
sessionImportPath,
|
|
191
|
+
screenImportPath,
|
|
192
|
+
sessionHasCapabilities: Boolean(opts.sessionHasCapabilities),
|
|
193
|
+
});
|
|
194
|
+
const next = insertBlockAfterLastImport(stripped.trimEnd(), block);
|
|
195
|
+
return { changed: true, source: next.endsWith('\n') ? next : `${next}\n` };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Idempotently rewrite an existing entry-bootstrap block to carry the generated
|
|
199
|
+
// SCREEN-TRUTH connection (fourth pass, after screen wiring). Preserves the
|
|
200
|
+
// registries and session earlier passes added. Returns { changed, source, reason? }.
|
|
201
|
+
function addScreenToBootstrapSource(source, screenImportPath) {
|
|
202
|
+
if (!source.includes(ORB_SCRIPT_START)) return { changed: false, source, reason: 'not-mounted' };
|
|
203
|
+
if (/\n\s*getCurrentRoute:\s/.test(source.slice(source.indexOf(ORB_SCRIPT_START)))) {
|
|
204
|
+
return { changed: false, source, reason: 'already' };
|
|
205
|
+
}
|
|
206
|
+
const registryImportPath = extractBootstrapImportPath(source, BOOTSTRAP_REGISTRY_EXPORT);
|
|
207
|
+
const sessionImportPath = extractBootstrapImportPath(source, SESSION_IS_AUTH_EXPORT);
|
|
208
|
+
const sessionHasCapabilities = Boolean(extractBootstrapImportPath(source, SESSION_CAPS_EXPORT));
|
|
209
|
+
const stripped = stripMarkedRegion(source, ORB_SCRIPT_START, ORB_SCRIPT_END).replace(/\n{3,}$/, '\n');
|
|
210
|
+
const block = buildEntryBootstrapBlock({
|
|
211
|
+
registryImportPath,
|
|
212
|
+
sessionImportPath,
|
|
213
|
+
screenImportPath,
|
|
214
|
+
sessionHasCapabilities,
|
|
215
|
+
});
|
|
128
216
|
const next = insertBlockAfterLastImport(stripped.trimEnd(), block);
|
|
129
217
|
return { changed: true, source: next.endsWith('\n') ? next : `${next}\n` };
|
|
130
218
|
}
|
|
@@ -199,6 +287,74 @@ function addRegistriesToMountSource(source, exportName, importPath) {
|
|
|
199
287
|
return { changed: true, source: next };
|
|
200
288
|
}
|
|
201
289
|
|
|
290
|
+
// Idempotently connect the generated SESSION module to the mounted
|
|
291
|
+
// <FiodosAgent/>: adds `isAuthenticated={fyodosIsAuthenticated}` and
|
|
292
|
+
// `getUserId={fyodosGetUserId}` + the import. Skips (never overrides) a mount
|
|
293
|
+
// where the developer already wired their own isAuthenticated. Same
|
|
294
|
+
// reversibility model as addRegistriesToMountSource.
|
|
295
|
+
function addSessionToMountSource(source, importPath, opts = {}) {
|
|
296
|
+
const withCapabilities = Boolean(opts.sessionHasCapabilities);
|
|
297
|
+
const tagRe = /([ \t]*)<FiodosAgent\b[\s\S]*?\/>/;
|
|
298
|
+
const m = source.match(tagRe);
|
|
299
|
+
if (!m) return { changed: false, source, reason: 'not-mounted' };
|
|
300
|
+
const tag = m[0];
|
|
301
|
+
if (new RegExp(`isAuthenticated=\\{${escapeRe(SESSION_IS_AUTH_EXPORT)}\\}`).test(tag)) {
|
|
302
|
+
return { changed: false, source, reason: 'already' };
|
|
303
|
+
}
|
|
304
|
+
if (/\bisAuthenticated=/.test(tag)) {
|
|
305
|
+
return { changed: false, source, reason: 'dev-wired' };
|
|
306
|
+
}
|
|
307
|
+
const baseIndent = m[1] || '';
|
|
308
|
+
const propMatch = tag.match(/\n([ \t]*)(?:apiKey|api-key|apiUrl|baseUrl|registries)/);
|
|
309
|
+
const propIndent = propMatch ? propMatch[1] : `${baseIndent} `;
|
|
310
|
+
const hasUserId = /\bgetUserId=/.test(tag);
|
|
311
|
+
const addCaps = withCapabilities && !/\bgetSessionCapabilities=/.test(tag);
|
|
312
|
+
const props =
|
|
313
|
+
`\n${propIndent}isAuthenticated={${SESSION_IS_AUTH_EXPORT}}` +
|
|
314
|
+
(hasUserId ? '' : `\n${propIndent}getUserId={${SESSION_USER_ID_EXPORT}}`) +
|
|
315
|
+
(addCaps ? `\n${propIndent}getSessionCapabilities={${SESSION_CAPS_EXPORT}}` : '');
|
|
316
|
+
const newTag = tag.replace(/\n?[ \t]*\/>$/, `${props}\n${baseIndent}/>`);
|
|
317
|
+
let next = source.replace(tag, newTag);
|
|
318
|
+
const nameList = [SESSION_IS_AUTH_EXPORT];
|
|
319
|
+
if (!hasUserId) nameList.push(SESSION_USER_ID_EXPORT);
|
|
320
|
+
if (addCaps) nameList.push(SESSION_CAPS_EXPORT);
|
|
321
|
+
const importLine = `import { ${nameList.join(', ')} } from '${importPath}';`;
|
|
322
|
+
if (!next.includes(importLine)) {
|
|
323
|
+
next = insertImportAfterLastImport(next, importLine);
|
|
324
|
+
}
|
|
325
|
+
return { changed: true, source: next };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Idempotently connect the generated SCREEN-TRUTH module to the mounted
|
|
329
|
+
// <FiodosAgent/>: adds `getCurrentRoute={fyodosGetCurrentRoute}` + the import.
|
|
330
|
+
// Skips (never overrides) a mount where the developer already wired their own
|
|
331
|
+
// getCurrentRoute. Same reversibility model as addSessionToMountSource.
|
|
332
|
+
function addScreenToMountSource(source, importPath) {
|
|
333
|
+
const tagRe = /([ \t]*)<FiodosAgent\b[\s\S]*?\/>/;
|
|
334
|
+
const m = source.match(tagRe);
|
|
335
|
+
if (!m) return { changed: false, source, reason: 'not-mounted' };
|
|
336
|
+
const tag = m[0];
|
|
337
|
+
if (new RegExp(`getCurrentRoute=\\{${escapeRe(SCREEN_ROUTE_EXPORT)}\\}`).test(tag)) {
|
|
338
|
+
return { changed: false, source, reason: 'already' };
|
|
339
|
+
}
|
|
340
|
+
if (/\bgetCurrentRoute=/.test(tag)) {
|
|
341
|
+
return { changed: false, source, reason: 'dev-wired' };
|
|
342
|
+
}
|
|
343
|
+
const baseIndent = m[1] || '';
|
|
344
|
+
const propMatch = tag.match(/\n([ \t]*)(?:apiKey|api-key|apiUrl|baseUrl|registries)/);
|
|
345
|
+
const propIndent = propMatch ? propMatch[1] : `${baseIndent} `;
|
|
346
|
+
const newTag = tag.replace(
|
|
347
|
+
/\n?[ \t]*\/>$/,
|
|
348
|
+
`\n${propIndent}getCurrentRoute={${SCREEN_ROUTE_EXPORT}}\n${baseIndent}/>`,
|
|
349
|
+
);
|
|
350
|
+
let next = source.replace(tag, newTag);
|
|
351
|
+
const importLine = `import { ${SCREEN_ROUTE_EXPORT} } from '${importPath}';`;
|
|
352
|
+
if (!next.includes(importLine)) {
|
|
353
|
+
next = insertImportAfterLastImport(next, importLine);
|
|
354
|
+
}
|
|
355
|
+
return { changed: true, source: next };
|
|
356
|
+
}
|
|
357
|
+
|
|
202
358
|
/**
|
|
203
359
|
* Assess whether we can inject safely. Returns { ok, reason?, strategy, files, rel }.
|
|
204
360
|
*/
|
|
@@ -601,13 +757,15 @@ function writeConsentDoc(appRoot, plan) {
|
|
|
601
757
|
'Edits sit between `FYODOS:ORB:START` / `FYODOS:ORB:END` markers. Remove those',
|
|
602
758
|
'blocks (and the Fiodos import) to revert.',
|
|
603
759
|
'',
|
|
604
|
-
'## Apps with login (
|
|
760
|
+
'## Apps with login (auth + identity — wired AUTOMATICALLY)',
|
|
761
|
+
'',
|
|
762
|
+
'For apps with login the installer also detects WHERE your session state lives',
|
|
763
|
+
'and connects it on its own: it generates `src/fyodos/session.generated.*` and',
|
|
764
|
+
'adds `isAuthenticated={fyodosIsAuthenticated}` / `getUserId={fyodosGetUserId}`',
|
|
765
|
+
'to the mount (see `src/fyodos/FYODOS_SESSION.md`). Nothing to do by hand.',
|
|
605
766
|
'',
|
|
606
|
-
'
|
|
607
|
-
'
|
|
608
|
-
'connect that in one place. Without it the agent can loop ("do you want to sign',
|
|
609
|
-
"in?\") and can't tell it already signed you in. Add these props to",
|
|
610
|
-
'`<FiodosAgent/>` (a commented scaffold is already next to the mount):',
|
|
767
|
+
'ONLY if the installer reported it could not verify your session source (or you',
|
|
768
|
+
'want to override it), wire the props yourself:',
|
|
611
769
|
'',
|
|
612
770
|
'```tsx',
|
|
613
771
|
'<FiodosAgent',
|
|
@@ -653,7 +811,12 @@ module.exports = {
|
|
|
653
811
|
registryRelImport,
|
|
654
812
|
addRegistriesToMountSource,
|
|
655
813
|
addRegistriesToBootstrapSource,
|
|
814
|
+
addSessionToMountSource,
|
|
815
|
+
addSessionToBootstrapSource,
|
|
816
|
+
addScreenToMountSource,
|
|
817
|
+
addScreenToBootstrapSource,
|
|
656
818
|
buildEntryBootstrapBlock,
|
|
657
819
|
IMPORT_NAME,
|
|
658
820
|
WRAPPER_BASENAME,
|
|
821
|
+
SESSION_CAPS_EXPORT,
|
|
659
822
|
};
|