@fiodos/cli 0.1.3 → 0.1.4
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 +1 -1
- package/src/writeEnv.js +65 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiodos/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Fiodos CLI — analyzes your app's source code and generates the in-app voice-agent manifest, then wires the orb. Powers `npx @fiodos/cli analyze`.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
package/src/writeEnv.js
CHANGED
|
@@ -30,24 +30,36 @@ function readJsonSafe(file) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Decide which env
|
|
34
|
-
* null when there is no safe .env convention to write (Angular,
|
|
33
|
+
* Decide which env files + variable names the detected framework uses. Returns
|
|
34
|
+
* `file: null` when there is no safe .env convention to write (Angular,
|
|
35
|
+
* unknown). `candidates` is the list of env files the framework loads, ordered
|
|
36
|
+
* HIGHEST precedence first — so we can fix the value WHERE THE APP ACTUALLY
|
|
37
|
+
* READS IT instead of writing a second file that may or may not win. `file` is
|
|
38
|
+
* the default file to create when no candidate already defines the vars.
|
|
35
39
|
*/
|
|
36
40
|
function resolveEnvPlan(appRoot) {
|
|
37
41
|
const pkg = readJsonSafe(path.join(appRoot, 'package.json'));
|
|
38
42
|
const has = (rel) => fs.existsSync(path.join(appRoot, rel));
|
|
39
43
|
if (!pkg && !has('angular.json')) {
|
|
40
|
-
return { framework: null, file: null, keyVar: null, urlVar: null };
|
|
44
|
+
return { framework: null, file: null, candidates: [], keyVar: null, urlVar: null };
|
|
41
45
|
}
|
|
42
46
|
const deps = pkg ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) } : {};
|
|
43
47
|
|
|
44
48
|
if (deps.next) {
|
|
45
|
-
|
|
49
|
+
// Next dev precedence (first wins): .env.development.local > .env.local >
|
|
50
|
+
// .env.development > .env.
|
|
51
|
+
return {
|
|
52
|
+
framework: 'next',
|
|
53
|
+
file: '.env.local',
|
|
54
|
+
candidates: ['.env.development.local', '.env.local', '.env.development', '.env'],
|
|
55
|
+
keyVar: 'NEXT_PUBLIC_FYODOS_API_KEY',
|
|
56
|
+
urlVar: 'NEXT_PUBLIC_FYODOS_API_URL',
|
|
57
|
+
};
|
|
46
58
|
}
|
|
47
59
|
if (deps['@angular/core'] || has('angular.json')) {
|
|
48
60
|
// Angular has no .env convention (values live in environment.ts), so we
|
|
49
61
|
// never write a file blindly — the caller prints manual guidance instead.
|
|
50
|
-
return { framework: 'angular', file: null, keyVar: null, urlVar: null };
|
|
62
|
+
return { framework: 'angular', file: null, candidates: [], keyVar: null, urlVar: null };
|
|
51
63
|
}
|
|
52
64
|
if (
|
|
53
65
|
deps['@sveltejs/kit'] ||
|
|
@@ -58,9 +70,33 @@ function resolveEnvPlan(appRoot) {
|
|
|
58
70
|
deps['@vitejs/plugin-react'] ||
|
|
59
71
|
deps['react-scripts']
|
|
60
72
|
) {
|
|
61
|
-
|
|
73
|
+
// Vite dev precedence (first wins): .env.development.local >
|
|
74
|
+
// .env.development > .env.local > .env.
|
|
75
|
+
return {
|
|
76
|
+
framework: 'vite',
|
|
77
|
+
file: '.env',
|
|
78
|
+
candidates: ['.env.development.local', '.env.development', '.env.local', '.env'],
|
|
79
|
+
keyVar: 'VITE_FYODOS_API_KEY',
|
|
80
|
+
urlVar: 'VITE_FYODOS_API_URL',
|
|
81
|
+
};
|
|
62
82
|
}
|
|
63
|
-
return { framework: null, file: null, keyVar: null, urlVar: null };
|
|
83
|
+
return { framework: null, file: null, candidates: [], keyVar: null, urlVar: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Scan the framework's candidate env files (highest precedence first) for VAR.
|
|
88
|
+
* Returns { file, value } for the first (highest-precedence) file that defines
|
|
89
|
+
* it — i.e. the value the running app actually reads — or null if none do.
|
|
90
|
+
*/
|
|
91
|
+
function findEffectiveVar(appRoot, candidates, name) {
|
|
92
|
+
for (const rel of candidates) {
|
|
93
|
+
const filePath = path.join(appRoot, rel);
|
|
94
|
+
if (!fs.existsSync(filePath)) continue;
|
|
95
|
+
const lines = fs.readFileSync(filePath, 'utf8').replace(/\n$/, '').split('\n');
|
|
96
|
+
const value = readEnvVar(lines, name);
|
|
97
|
+
if (value !== undefined) return { file: rel, value };
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
64
100
|
}
|
|
65
101
|
|
|
66
102
|
/** Find the value of VAR in the parsed lines, or undefined if not present. */
|
|
@@ -145,14 +181,24 @@ async function offerWriteEnv({ appRoot, apiKey, apiUrl, defaultApiUrl, assumeYes
|
|
|
145
181
|
return 'manual (unknown framework)';
|
|
146
182
|
}
|
|
147
183
|
|
|
148
|
-
|
|
184
|
+
// Look across ALL the files the framework loads (not just the default one):
|
|
185
|
+
// the app may already keep its Fiodos vars in .env while we would otherwise
|
|
186
|
+
// create a separate .env.local — leaving the stale value the orb actually
|
|
187
|
+
// reads untouched. Fix the value WHERE THE APP READS IT.
|
|
188
|
+
const candidates = plan.candidates && plan.candidates.length ? plan.candidates : [plan.file];
|
|
189
|
+
const effectiveKey = findEffectiveVar(appRoot, candidates, plan.keyVar);
|
|
190
|
+
const effectiveUrl = findEffectiveVar(appRoot, candidates, plan.urlVar);
|
|
191
|
+
const currentKey = effectiveKey ? effectiveKey.value : undefined;
|
|
192
|
+
const currentUrl = effectiveUrl ? effectiveUrl.value : undefined;
|
|
193
|
+
|
|
194
|
+
// Write in the highest-precedence file that already defines either var (so the
|
|
195
|
+
// fix is the value the app loads). If none do, create the framework default.
|
|
196
|
+
const targetRel = (effectiveKey && effectiveKey.file) || (effectiveUrl && effectiveUrl.file) || plan.file;
|
|
197
|
+
const filePath = path.join(appRoot, targetRel);
|
|
149
198
|
const exists = fs.existsSync(filePath);
|
|
150
199
|
const raw = exists ? fs.readFileSync(filePath, 'utf8') : '';
|
|
151
200
|
const lines = raw.length ? raw.replace(/\n$/, '').split('\n') : [];
|
|
152
201
|
|
|
153
|
-
const currentKey = readEnvVar(lines, plan.keyVar);
|
|
154
|
-
const currentUrl = readEnvVar(lines, plan.urlVar);
|
|
155
|
-
|
|
156
202
|
// Only write a URL var when it differs from the public default (the SDK
|
|
157
203
|
// already defaults to production). But if a STALE url var exists and differs
|
|
158
204
|
// from what we published against, we must offer to fix it — that is exactly
|
|
@@ -163,7 +209,7 @@ async function offerWriteEnv({ appRoot, apiKey, apiUrl, defaultApiUrl, assumeYes
|
|
|
163
209
|
const keyMatches = currentKey === apiKey;
|
|
164
210
|
const urlOk = !urlNeedsFix && (wantUrl ? currentUrl === wantUrl : true);
|
|
165
211
|
if (keyMatches && urlOk) {
|
|
166
|
-
log(`✓ Your ${
|
|
212
|
+
log(`✓ Your ${targetRel} already uses the same key/URL as the publication. Nothing to change.`);
|
|
167
213
|
return 'already aligned';
|
|
168
214
|
}
|
|
169
215
|
|
|
@@ -189,7 +235,7 @@ async function offerWriteEnv({ appRoot, apiKey, apiUrl, defaultApiUrl, assumeYes
|
|
|
189
235
|
}
|
|
190
236
|
|
|
191
237
|
if (!changes.length) {
|
|
192
|
-
log(`✓ Your ${
|
|
238
|
+
log(`✓ Your ${targetRel} is already aligned.`);
|
|
193
239
|
return 'already aligned';
|
|
194
240
|
}
|
|
195
241
|
|
|
@@ -197,17 +243,17 @@ async function offerWriteEnv({ appRoot, apiKey, apiUrl, defaultApiUrl, assumeYes
|
|
|
197
243
|
const isOverwrite = (currentKey !== undefined && !keyMatches) || urlNeedsFix;
|
|
198
244
|
log(
|
|
199
245
|
`${isOverwrite ? '⚠︎ ' : ''}For the orb to find its manifest, your app should use the SAME ` +
|
|
200
|
-
`key/URL you just published with. Proposed in ${
|
|
246
|
+
`key/URL you just published with. Proposed in ${targetRel}: ${changes.join('; ')}.`,
|
|
201
247
|
);
|
|
202
248
|
|
|
203
249
|
let proceed = assumeYes;
|
|
204
250
|
if (!proceed) {
|
|
205
251
|
proceed = await askYesNo(
|
|
206
|
-
`◉ Fiodos · Write/update ${
|
|
252
|
+
`◉ Fiodos · Write/update ${targetRel} with the published key/URL? [yes/no] `,
|
|
207
253
|
);
|
|
208
254
|
}
|
|
209
255
|
if (!proceed) {
|
|
210
|
-
log(`Left your ${
|
|
256
|
+
log(`Left your ${targetRel} untouched. Do it by hand: ${changes.join('; ')}.`);
|
|
211
257
|
return 'declined by user';
|
|
212
258
|
}
|
|
213
259
|
|
|
@@ -219,10 +265,10 @@ async function offerWriteEnv({ appRoot, apiKey, apiUrl, defaultApiUrl, assumeYes
|
|
|
219
265
|
out = upsertEnvVar(out, plan.urlVar, apiUrl);
|
|
220
266
|
}
|
|
221
267
|
fs.writeFileSync(filePath, `${out.join('\n')}\n`);
|
|
222
|
-
log(`✓ ${
|
|
268
|
+
log(`✓ ${targetRel} updated (${changes.join('; ')}).`);
|
|
223
269
|
|
|
224
|
-
if (!isGitIgnored(appRoot,
|
|
225
|
-
log(`Reminder: add ${
|
|
270
|
+
if (!isGitIgnored(appRoot, targetRel)) {
|
|
271
|
+
log(`Reminder: add ${targetRel} to .gitignore so you don't commit your API key.`);
|
|
226
272
|
}
|
|
227
273
|
return 'written';
|
|
228
274
|
}
|