@caiqueoak/flow 0.3.2 → 0.3.3
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/README.md +2 -2
- package/package.json +1 -1
- package/src/cli.mjs +64 -27
package/README.md
CHANGED
|
@@ -48,9 +48,9 @@ Updates are explicit:
|
|
|
48
48
|
flow update
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
`flow update`
|
|
51
|
+
`flow update` updates the installation that provides the active CLI: the global package for `flow update`, or the project dependency for `npx flow update`. It uses npm's update operation and refreshes `/flow` for every coding agent configured in `.flow/config.yaml` without modifying canonical project state. If the project lockfile and the package on disk disagree, Flow safely reinstalls only its own package before updating.
|
|
52
52
|
|
|
53
|
-
On Windows, npm is invoked through
|
|
53
|
+
On Windows, npm is invoked through `cmd.exe` without Node's `shell: true` option, so `npm.cmd` executes without the `DEP0190` warning. When Flow is installed as a project dependency, `npx flow update` is equivalent.
|
|
54
54
|
|
|
55
55
|
There is no background update check or automatic update mechanism.
|
|
56
56
|
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -54,9 +54,11 @@ function question(rl, message) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function runNpm(npmArgs, options = {}) {
|
|
57
|
+
if (process.platform === 'win32') {
|
|
58
|
+
return execFileSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', npmCommand(), ...npmArgs], options);
|
|
59
|
+
}
|
|
57
60
|
return execFileSync(npmCommand(), npmArgs, {
|
|
58
|
-
...options
|
|
59
|
-
shell: process.platform === 'win32'
|
|
61
|
+
...options
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -190,17 +192,6 @@ async function initProject() {
|
|
|
190
192
|
else { info(); info('Flow is ready. Open a configured coding agent and invoke /flow.'); }
|
|
191
193
|
}
|
|
192
194
|
|
|
193
|
-
function dependencySection(root) {
|
|
194
|
-
const file = path.join(root, 'package.json');
|
|
195
|
-
if (!fs.existsSync(file)) return '--save-dev';
|
|
196
|
-
try {
|
|
197
|
-
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
198
|
-
if (pkg.dependencies?.[PACKAGE_NAME]) return '--save';
|
|
199
|
-
if (pkg.optionalDependencies?.[PACKAGE_NAME]) return '--save-optional';
|
|
200
|
-
} catch { /* use default */ }
|
|
201
|
-
return '--save-dev';
|
|
202
|
-
}
|
|
203
|
-
|
|
204
195
|
function containingNodeModules(packageRoot) {
|
|
205
196
|
let current = path.resolve(packageRoot);
|
|
206
197
|
while (true) {
|
|
@@ -211,44 +202,88 @@ function containingNodeModules(packageRoot) {
|
|
|
211
202
|
}
|
|
212
203
|
}
|
|
213
204
|
|
|
214
|
-
function
|
|
215
|
-
const
|
|
216
|
-
if (fs.existsSync(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
cwd: root,
|
|
220
|
-
packageRoot: projectPackageRoot,
|
|
221
|
-
mode: 'project'
|
|
222
|
-
};
|
|
223
|
-
}
|
|
205
|
+
function installedPackageVersion(packageRoot) {
|
|
206
|
+
const manifest = path.join(packageRoot, 'package.json');
|
|
207
|
+
if (!fs.existsSync(manifest)) return null;
|
|
208
|
+
return JSON.parse(fs.readFileSync(manifest, 'utf8')).version || null;
|
|
209
|
+
}
|
|
224
210
|
|
|
211
|
+
function lockedPackageVersion(root) {
|
|
212
|
+
const lockfile = path.join(root, 'package-lock.json');
|
|
213
|
+
if (!fs.existsSync(lockfile)) return null;
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(fs.readFileSync(lockfile, 'utf8')).packages?.[`node_modules/${PACKAGE_NAME}`]?.version || null;
|
|
216
|
+
} catch { return null; }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function globalUpdatePlan(root) {
|
|
225
220
|
try {
|
|
226
221
|
const globalNodeModules = path.resolve(runNpm(['root', '--global'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim());
|
|
227
222
|
const globalPackageRoot = path.join(globalNodeModules, '@caiqueoak', 'flow');
|
|
228
223
|
if (path.resolve(ROOT).startsWith(`${globalNodeModules}${path.sep}`) && fs.existsSync(path.join(globalPackageRoot, 'package.json'))) {
|
|
229
224
|
return {
|
|
230
|
-
npmArgs: ['
|
|
225
|
+
npmArgs: ['update', '--global', PACKAGE_NAME],
|
|
231
226
|
cwd: root,
|
|
232
227
|
packageRoot: globalPackageRoot,
|
|
233
228
|
mode: 'global'
|
|
234
229
|
};
|
|
235
230
|
}
|
|
236
231
|
} catch { /* fall through to local installation detection */ }
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function updatePlan(root) {
|
|
236
|
+
const global = globalUpdatePlan(root);
|
|
237
|
+
if (global) return global;
|
|
237
238
|
|
|
238
239
|
const nodeModules = containingNodeModules(ROOT);
|
|
239
240
|
if (nodeModules) {
|
|
240
241
|
const installRoot = path.dirname(nodeModules);
|
|
241
242
|
return {
|
|
242
|
-
npmArgs: ['
|
|
243
|
+
npmArgs: ['update', PACKAGE_NAME],
|
|
243
244
|
cwd: installRoot,
|
|
244
245
|
packageRoot: ROOT,
|
|
245
246
|
mode: 'local'
|
|
246
247
|
};
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
const projectPackageRoot = packagePath(root);
|
|
251
|
+
if (fs.existsSync(path.join(projectPackageRoot, 'package.json'))) {
|
|
252
|
+
return {
|
|
253
|
+
npmArgs: ['update', PACKAGE_NAME],
|
|
254
|
+
cwd: root,
|
|
255
|
+
packageRoot: projectPackageRoot,
|
|
256
|
+
mode: 'project'
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
249
260
|
fail('cannot determine how this Flow CLI was installed. Reinstall @caiqueoak/flow with npm, then run flow update again.');
|
|
250
261
|
}
|
|
251
262
|
|
|
263
|
+
function repairDivergentProjectInstall(plan) {
|
|
264
|
+
if (plan.mode === 'global') return;
|
|
265
|
+
const expected = lockedPackageVersion(plan.cwd);
|
|
266
|
+
const actual = installedPackageVersion(plan.packageRoot);
|
|
267
|
+
if (!expected || !actual || expected === actual) return;
|
|
268
|
+
|
|
269
|
+
const backup = path.join(plan.cwd, `.flow-update-backup-${process.pid}-${Date.now()}`);
|
|
270
|
+
info(`Repairing divergent ${PACKAGE_NAME} installation (${actual} on disk, ${expected} in package-lock.json)...`);
|
|
271
|
+
fs.renameSync(plan.packageRoot, backup);
|
|
272
|
+
try {
|
|
273
|
+
runNpm(['install'], { cwd: plan.cwd, stdio: 'inherit' });
|
|
274
|
+
if (installedPackageVersion(plan.packageRoot) !== expected) {
|
|
275
|
+
throw new Error(`npm install did not restore ${PACKAGE_NAME}@${expected}.`);
|
|
276
|
+
}
|
|
277
|
+
fs.rmSync(backup, { recursive: true, force: true });
|
|
278
|
+
} catch (error) {
|
|
279
|
+
try {
|
|
280
|
+
fs.rmSync(plan.packageRoot, { recursive: true, force: true });
|
|
281
|
+
fs.renameSync(backup, plan.packageRoot);
|
|
282
|
+
} catch { /* preserve the original npm error below */ }
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
252
287
|
function update() {
|
|
253
288
|
const root = projectRoot();
|
|
254
289
|
const config = readConfig(root);
|
|
@@ -257,8 +292,10 @@ function update() {
|
|
|
257
292
|
|
|
258
293
|
const plan = updatePlan(root);
|
|
259
294
|
info(`Updating ${PACKAGE_NAME} (${plan.mode} installation)...`);
|
|
260
|
-
try {
|
|
261
|
-
|
|
295
|
+
try {
|
|
296
|
+
repairDivergentProjectInstall(plan);
|
|
297
|
+
runNpm(plan.npmArgs, { cwd: plan.cwd, stdio: 'inherit' });
|
|
298
|
+
} catch { fail('npm update failed. Existing project state and installed skills were not intentionally removed.'); }
|
|
262
299
|
|
|
263
300
|
if (!fs.existsSync(path.join(plan.packageRoot, 'package.json'))) fail(`updated package not found at ${plan.packageRoot}.`);
|
|
264
301
|
const latest = JSON.parse(fs.readFileSync(path.join(plan.packageRoot, 'package.json'), 'utf8'));
|