@fiodos/cli 0.1.23 → 0.1.25
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/changeRegistry.js +55 -1
- package/src/index.js +169 -3
- package/src/odata.js +529 -0
- package/src/wireHandlers.js +22 -0
- package/src/wireReactNative.js +52 -0
- package/src/wireSession.js +619 -0
- package/src/wireWeb.js +102 -0
- package/src/wireWebMount.js +87 -8
package/src/wireWeb.js
CHANGED
|
@@ -27,6 +27,8 @@ const {
|
|
|
27
27
|
registryRelImport,
|
|
28
28
|
addRegistriesToMountSource,
|
|
29
29
|
addRegistriesToBootstrapSource,
|
|
30
|
+
addSessionToMountSource,
|
|
31
|
+
addSessionToBootstrapSource,
|
|
30
32
|
buildEntryBootstrapBlock,
|
|
31
33
|
IMPORT_NAME,
|
|
32
34
|
WRAPPER_BASENAME,
|
|
@@ -499,6 +501,104 @@ function connectOrbRegistries(appRoot, opts = {}) {
|
|
|
499
501
|
}
|
|
500
502
|
}
|
|
501
503
|
|
|
504
|
+
/**
|
|
505
|
+
* Third pass — connect the generated SESSION module to the mounted orb, so the
|
|
506
|
+
* agent KNOWS whether the user is signed in (requiresAuth gate) and keys the
|
|
507
|
+
* conversation memory per user. Mirrors connectOrbRegistries: idempotent,
|
|
508
|
+
* reversible, never overrides a developer-wired isAuthenticated.
|
|
509
|
+
*/
|
|
510
|
+
function connectOrbSession(appRoot, opts = {}) {
|
|
511
|
+
const { sessionFileAbs } = opts;
|
|
512
|
+
if (!sessionFileAbs || !fs.existsSync(sessionFileAbs)) return { status: 'no-session-file' };
|
|
513
|
+
|
|
514
|
+
const framework = detectFramework(appRoot);
|
|
515
|
+
const target = detectTarget(appRoot, framework);
|
|
516
|
+
if (!target || !target.file) return { status: 'no-target' };
|
|
517
|
+
|
|
518
|
+
const bootstrapKinds = new Set(['vue-entry', 'svelte-entry', 'sveltekit-hooks']);
|
|
519
|
+
if (bootstrapKinds.has(target.kind)) {
|
|
520
|
+
let source;
|
|
521
|
+
try {
|
|
522
|
+
source = fs.readFileSync(target.file, 'utf8');
|
|
523
|
+
} catch {
|
|
524
|
+
return { status: 'not-mounted' };
|
|
525
|
+
}
|
|
526
|
+
const importPath = registryRelImport(target.file, sessionFileAbs);
|
|
527
|
+
const fileRel = path.relative(appRoot, target.file);
|
|
528
|
+
const backups = backupFiles([target.file]);
|
|
529
|
+
try {
|
|
530
|
+
const res = addSessionToBootstrapSource(source, importPath);
|
|
531
|
+
if (!res.changed) {
|
|
532
|
+
return { status: res.reason === 'already' ? 'already' : res.reason || 'not-mounted', file: fileRel };
|
|
533
|
+
}
|
|
534
|
+
fs.writeFileSync(target.file, res.source);
|
|
535
|
+
return { status: 'connected', file: fileRel };
|
|
536
|
+
} catch (e) {
|
|
537
|
+
revertFiles(backups);
|
|
538
|
+
return { status: 'failed', file: fileRel, error: e };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const reactKinds = new Set(['vite', 'next-pages', 'next-app']);
|
|
543
|
+
if (!reactKinds.has(target.kind)) return { status: 'unsupported-framework', framework };
|
|
544
|
+
|
|
545
|
+
let mountFile = target.file;
|
|
546
|
+
if (target.kind === 'next-app') {
|
|
547
|
+
const dir = path.dirname(target.file);
|
|
548
|
+
mountFile =
|
|
549
|
+
[path.join(dir, `${WRAPPER_BASENAME}.tsx`), path.join(dir, `${WRAPPER_BASENAME}.jsx`)].find((f) =>
|
|
550
|
+
fs.existsSync(f),
|
|
551
|
+
) || null;
|
|
552
|
+
if (!mountFile) return { status: 'not-mounted' };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let source;
|
|
556
|
+
try {
|
|
557
|
+
source = fs.readFileSync(mountFile, 'utf8');
|
|
558
|
+
} catch {
|
|
559
|
+
return { status: 'not-mounted' };
|
|
560
|
+
}
|
|
561
|
+
if (!/<FiodosAgent\b/.test(source)) return { status: 'not-mounted' };
|
|
562
|
+
|
|
563
|
+
const importPath = registryRelImport(mountFile, sessionFileAbs);
|
|
564
|
+
const fileRel = path.relative(appRoot, mountFile);
|
|
565
|
+
const backups = backupFiles([mountFile]);
|
|
566
|
+
try {
|
|
567
|
+
const res = addSessionToMountSource(source, importPath);
|
|
568
|
+
if (!res.changed) {
|
|
569
|
+
return { status: res.reason || 'not-mounted', file: fileRel };
|
|
570
|
+
}
|
|
571
|
+
fs.writeFileSync(mountFile, res.source);
|
|
572
|
+
return { status: 'connected', file: fileRel };
|
|
573
|
+
} catch (e) {
|
|
574
|
+
revertFiles(backups);
|
|
575
|
+
return { status: 'failed', file: fileRel, error: e };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function reportSessionConnectResult(result, colors = {}, opts = {}) {
|
|
580
|
+
const quiet = opts.quiet !== false;
|
|
581
|
+
if (!result) return;
|
|
582
|
+
const { blue, cyan, dim, reset } = colors;
|
|
583
|
+
const tag = `${cyan || ''}◉${reset || ''} ${blue || ''}Fiodos${reset || ''}`;
|
|
584
|
+
switch (result.status) {
|
|
585
|
+
case 'connected':
|
|
586
|
+
console.error(`${tag} · session connected to the orb mount (${result.file})`);
|
|
587
|
+
break;
|
|
588
|
+
case 'already':
|
|
589
|
+
if (!quiet) console.error(`${tag} · ${dim || ''}session already connected to the mount${reset || ''}`);
|
|
590
|
+
break;
|
|
591
|
+
case 'dev-wired':
|
|
592
|
+
if (!quiet) {
|
|
593
|
+
console.error(`${tag} · ${dim || ''}the mount already has its own isAuthenticated — left as is${reset || ''}`);
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
default:
|
|
597
|
+
if (!quiet) console.error(`${tag} · ${dim || ''}session not connected to the mount (${result.status})${reset || ''}`);
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
502
602
|
function reportRegistriesResult(result, colors = {}, opts = {}) {
|
|
503
603
|
const quiet = opts.quiet !== false;
|
|
504
604
|
if (!result) return;
|
|
@@ -530,6 +630,8 @@ module.exports = {
|
|
|
530
630
|
reportWireResult,
|
|
531
631
|
connectOrbRegistries,
|
|
532
632
|
reportRegistriesResult,
|
|
633
|
+
connectOrbSession,
|
|
634
|
+
reportSessionConnectResult,
|
|
533
635
|
detectFramework,
|
|
534
636
|
detectTarget,
|
|
535
637
|
};
|
package/src/wireWebMount.js
CHANGED
|
@@ -83,19 +83,32 @@ 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';
|
|
86
90
|
|
|
87
91
|
function buildEntryBootstrapBlock(opts = {}) {
|
|
88
|
-
const { registryImportPath } = opts;
|
|
92
|
+
const { registryImportPath, sessionImportPath } = opts;
|
|
89
93
|
const lines = [];
|
|
90
94
|
lines.push(ORB_SCRIPT_START);
|
|
91
95
|
lines.push(`import { createFiodosAgent as ${BOOTSTRAP_ALIAS} } from '@fiodos/web-core';`);
|
|
92
96
|
if (registryImportPath) {
|
|
93
97
|
lines.push(`import { ${BOOTSTRAP_REGISTRY_EXPORT} } from '${registryImportPath}';`);
|
|
94
98
|
}
|
|
99
|
+
if (sessionImportPath) {
|
|
100
|
+
lines.push(
|
|
101
|
+
`import { ${SESSION_IS_AUTH_EXPORT}, ${SESSION_USER_ID_EXPORT} } from '${sessionImportPath}';`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
95
104
|
lines.push(`${BOOTSTRAP_ALIAS}({`);
|
|
96
105
|
lines.push(' apiKey: import.meta.env.VITE_FYODOS_API_KEY,');
|
|
97
106
|
lines.push(' baseUrl: import.meta.env.VITE_FYODOS_API_URL,');
|
|
98
107
|
if (registryImportPath) lines.push(` registries: ${BOOTSTRAP_REGISTRY_EXPORT},`);
|
|
108
|
+
if (sessionImportPath) {
|
|
109
|
+
lines.push(` isAuthenticated: ${SESSION_IS_AUTH_EXPORT},`);
|
|
110
|
+
lines.push(` getUserId: ${SESSION_USER_ID_EXPORT},`);
|
|
111
|
+
}
|
|
99
112
|
lines.push(' mount: true,');
|
|
100
113
|
lines.push('});');
|
|
101
114
|
lines.push(ORB_SCRIPT_END);
|
|
@@ -123,8 +136,35 @@ function addRegistriesToBootstrapSource(source, registryImportPath) {
|
|
|
123
136
|
if (/\n\s*registries:\s/.test(source.slice(source.indexOf(ORB_SCRIPT_START)))) {
|
|
124
137
|
return { changed: false, source, reason: 'already' };
|
|
125
138
|
}
|
|
139
|
+
// Preserve a session connection a previous pass already added to the block.
|
|
140
|
+
const sessionImportPath = extractBootstrapImportPath(source, SESSION_IS_AUTH_EXPORT);
|
|
141
|
+
const stripped = stripMarkedRegion(source, ORB_SCRIPT_START, ORB_SCRIPT_END).replace(/\n{3,}$/, '\n');
|
|
142
|
+
const block = buildEntryBootstrapBlock({ registryImportPath, sessionImportPath });
|
|
143
|
+
const next = insertBlockAfterLastImport(stripped.trimEnd(), block);
|
|
144
|
+
return { changed: true, source: next.endsWith('\n') ? next : `${next}\n` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Import path of the module that exports `name` inside our bootstrap block (or null).
|
|
148
|
+
function extractBootstrapImportPath(source, name) {
|
|
149
|
+
const startIdx = source.indexOf(ORB_SCRIPT_START);
|
|
150
|
+
if (startIdx === -1) return null;
|
|
151
|
+
const endIdx = source.indexOf(ORB_SCRIPT_END, startIdx);
|
|
152
|
+
const block = source.slice(startIdx, endIdx === -1 ? undefined : endIdx);
|
|
153
|
+
const m = block.match(new RegExp(`import\\s*\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]([^'"]+)['"]`));
|
|
154
|
+
return m ? m[1] : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Idempotently rewrite an existing entry-bootstrap block to carry the generated
|
|
158
|
+
// SESSION connection (third pass, after session wiring). Preserves the
|
|
159
|
+
// registries the second pass added. Returns { changed, source, reason? }.
|
|
160
|
+
function addSessionToBootstrapSource(source, sessionImportPath) {
|
|
161
|
+
if (!source.includes(ORB_SCRIPT_START)) return { changed: false, source, reason: 'not-mounted' };
|
|
162
|
+
if (/\n\s*isAuthenticated:\s/.test(source.slice(source.indexOf(ORB_SCRIPT_START)))) {
|
|
163
|
+
return { changed: false, source, reason: 'already' };
|
|
164
|
+
}
|
|
165
|
+
const registryImportPath = extractBootstrapImportPath(source, BOOTSTRAP_REGISTRY_EXPORT);
|
|
126
166
|
const stripped = stripMarkedRegion(source, ORB_SCRIPT_START, ORB_SCRIPT_END).replace(/\n{3,}$/, '\n');
|
|
127
|
-
const block = buildEntryBootstrapBlock({ registryImportPath });
|
|
167
|
+
const block = buildEntryBootstrapBlock({ registryImportPath, sessionImportPath });
|
|
128
168
|
const next = insertBlockAfterLastImport(stripped.trimEnd(), block);
|
|
129
169
|
return { changed: true, source: next.endsWith('\n') ? next : `${next}\n` };
|
|
130
170
|
}
|
|
@@ -199,6 +239,41 @@ function addRegistriesToMountSource(source, exportName, importPath) {
|
|
|
199
239
|
return { changed: true, source: next };
|
|
200
240
|
}
|
|
201
241
|
|
|
242
|
+
// Idempotently connect the generated SESSION module to the mounted
|
|
243
|
+
// <FiodosAgent/>: adds `isAuthenticated={fyodosIsAuthenticated}` and
|
|
244
|
+
// `getUserId={fyodosGetUserId}` + the import. Skips (never overrides) a mount
|
|
245
|
+
// where the developer already wired their own isAuthenticated. Same
|
|
246
|
+
// reversibility model as addRegistriesToMountSource.
|
|
247
|
+
function addSessionToMountSource(source, importPath) {
|
|
248
|
+
const tagRe = /([ \t]*)<FiodosAgent\b[\s\S]*?\/>/;
|
|
249
|
+
const m = source.match(tagRe);
|
|
250
|
+
if (!m) return { changed: false, source, reason: 'not-mounted' };
|
|
251
|
+
const tag = m[0];
|
|
252
|
+
if (new RegExp(`isAuthenticated=\\{${escapeRe(SESSION_IS_AUTH_EXPORT)}\\}`).test(tag)) {
|
|
253
|
+
return { changed: false, source, reason: 'already' };
|
|
254
|
+
}
|
|
255
|
+
if (/\bisAuthenticated=/.test(tag)) {
|
|
256
|
+
return { changed: false, source, reason: 'dev-wired' };
|
|
257
|
+
}
|
|
258
|
+
const baseIndent = m[1] || '';
|
|
259
|
+
const propMatch = tag.match(/\n([ \t]*)(?:apiKey|api-key|apiUrl|baseUrl|registries)/);
|
|
260
|
+
const propIndent = propMatch ? propMatch[1] : `${baseIndent} `;
|
|
261
|
+
const hasUserId = /\bgetUserId=/.test(tag);
|
|
262
|
+
const props =
|
|
263
|
+
`\n${propIndent}isAuthenticated={${SESSION_IS_AUTH_EXPORT}}` +
|
|
264
|
+
(hasUserId ? '' : `\n${propIndent}getUserId={${SESSION_USER_ID_EXPORT}}`);
|
|
265
|
+
const newTag = tag.replace(/\n?[ \t]*\/>$/, `${props}\n${baseIndent}/>`);
|
|
266
|
+
let next = source.replace(tag, newTag);
|
|
267
|
+
const names = hasUserId
|
|
268
|
+
? SESSION_IS_AUTH_EXPORT
|
|
269
|
+
: `${SESSION_IS_AUTH_EXPORT}, ${SESSION_USER_ID_EXPORT}`;
|
|
270
|
+
const importLine = `import { ${names} } from '${importPath}';`;
|
|
271
|
+
if (!next.includes(importLine)) {
|
|
272
|
+
next = insertImportAfterLastImport(next, importLine);
|
|
273
|
+
}
|
|
274
|
+
return { changed: true, source: next };
|
|
275
|
+
}
|
|
276
|
+
|
|
202
277
|
/**
|
|
203
278
|
* Assess whether we can inject safely. Returns { ok, reason?, strategy, files, rel }.
|
|
204
279
|
*/
|
|
@@ -601,13 +676,15 @@ function writeConsentDoc(appRoot, plan) {
|
|
|
601
676
|
'Edits sit between `FYODOS:ORB:START` / `FYODOS:ORB:END` markers. Remove those',
|
|
602
677
|
'blocks (and the Fiodos import) to revert.',
|
|
603
678
|
'',
|
|
604
|
-
'## Apps with login (
|
|
679
|
+
'## Apps with login (auth + identity — wired AUTOMATICALLY)',
|
|
680
|
+
'',
|
|
681
|
+
'For apps with login the installer also detects WHERE your session state lives',
|
|
682
|
+
'and connects it on its own: it generates `src/fyodos/session.generated.*` and',
|
|
683
|
+
'adds `isAuthenticated={fyodosIsAuthenticated}` / `getUserId={fyodosGetUserId}`',
|
|
684
|
+
'to the mount (see `src/fyodos/FYODOS_SESSION.md`). Nothing to do by hand.',
|
|
605
685
|
'',
|
|
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):',
|
|
686
|
+
'ONLY if the installer reported it could not verify your session source (or you',
|
|
687
|
+
'want to override it), wire the props yourself:',
|
|
611
688
|
'',
|
|
612
689
|
'```tsx',
|
|
613
690
|
'<FiodosAgent',
|
|
@@ -653,6 +730,8 @@ module.exports = {
|
|
|
653
730
|
registryRelImport,
|
|
654
731
|
addRegistriesToMountSource,
|
|
655
732
|
addRegistriesToBootstrapSource,
|
|
733
|
+
addSessionToMountSource,
|
|
734
|
+
addSessionToBootstrapSource,
|
|
656
735
|
buildEntryBootstrapBlock,
|
|
657
736
|
IMPORT_NAME,
|
|
658
737
|
WRAPPER_BASENAME,
|