@paradigma-inc/flywheel 0.1.95 → 0.1.99

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.
Files changed (30) hide show
  1. package/README.md +22 -4
  2. package/package.json +2 -1
  3. package/skills/flywheel-llm-proof-paper/SKILL.md +156 -0
  4. package/skills/flywheel-llm-proof-paper/agents/openai.yaml +4 -0
  5. package/skills/flywheel-llm-proof-paper/references/checks.md +60 -0
  6. package/skills/flywheel-llm-proof-paper/scripts/__pycache__/check_paper.cpython-311.pyc +0 -0
  7. package/skills/flywheel-llm-proof-paper/scripts/check_paper.py +1065 -0
  8. package/src/cli.mjs +27 -3
  9. package/src/public-command-metadata.mjs +44 -0
  10. package/src/runtime/vendor/flywheel-cli-dist/commands/_wait-polling.d.ts +21 -0
  11. package/src/runtime/vendor/flywheel-cli-dist/commands/_wait-polling.js +68 -2
  12. package/src/runtime/vendor/flywheel-cli-dist/commands/_wait-polling.js.map +1 -1
  13. package/src/runtime/vendor/flywheel-cli-dist/commands/compute-acquire.js +2 -8
  14. package/src/runtime/vendor/flywheel-cli-dist/commands/compute-acquire.js.map +1 -1
  15. package/src/runtime/vendor/flywheel-cli-dist/commands/feedback-create.js +4 -0
  16. package/src/runtime/vendor/flywheel-cli-dist/commands/feedback-create.js.map +1 -1
  17. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/compute.js +1 -1
  18. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/compute.js.map +1 -1
  19. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/resources.js +16 -0
  20. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/resources.js.map +1 -1
  21. package/src/runtime/vendor/manifest.json +1 -1
  22. package/src/setup/shared/prior-mode-detect.mjs +76 -9
  23. package/src/unified-cli.mjs +54 -16
  24. package/src/update/cache.mjs +124 -0
  25. package/src/update/command.mjs +438 -0
  26. package/src/update/install-state.mjs +135 -0
  27. package/src/update/refresh.mjs +393 -0
  28. package/src/update/registry.mjs +35 -0
  29. package/src/update/version.mjs +59 -0
  30. package/src/update/warning.mjs +109 -0
@@ -7,9 +7,12 @@ import {
7
7
  } from "./public-command-metadata.mjs";
8
8
  import { runRuntimeCli } from "./runtime/delegate.mjs";
9
9
  import { RUNTIME_ALIASES } from "./runtime/vendor/flywheel-cli-dist/commands/aliases.js";
10
+ import { runUpdateCommand } from "./update/command.mjs";
11
+ import { runCommandWithDeferredUpdateWarning } from "./update/warning.mjs";
10
12
 
11
13
  const ALIAS_RESERVED_COMMANDS = new Set([
12
14
  "setup",
15
+ "update",
13
16
  "uninstall",
14
17
  "completion",
15
18
  "help",
@@ -100,7 +103,14 @@ async function routeExplicitHelp(argv) {
100
103
  }
101
104
  const setupHelpArgv = setupHelpArgvFor(target, subcommand);
102
105
  if (setupHelpArgv) {
103
- await runSetupCli(["node", "flywheel", ...setupHelpArgv]);
106
+ process.exitCode = await runSetupCli(
107
+ ["node", "flywheel", ...setupHelpArgv],
108
+ { exitOverride: true },
109
+ );
110
+ return;
111
+ }
112
+ if (target === "update") {
113
+ process.exitCode = await runUpdateCommand(["--help"]);
104
114
  return;
105
115
  }
106
116
  if (target === undefined) {
@@ -120,6 +130,17 @@ async function routeExplicitHelp(argv) {
120
130
  process.exitCode = exitCode;
121
131
  }
122
132
 
133
+ async function runWithDeferredUpdateWarning(argv, runCommand) {
134
+ const exitCode = await runCommandWithDeferredUpdateWarning({
135
+ argv,
136
+ currentVersion: await loadPackageVersion(),
137
+ runCommand,
138
+ });
139
+ if (Number.isInteger(exitCode)) {
140
+ process.exitCode = exitCode;
141
+ }
142
+ }
143
+
123
144
  export async function runUnifiedCli(argv = process.argv.slice(2)) {
124
145
  if (!Array.isArray(argv)) {
125
146
  throw new Error("runUnifiedCli expects an argv array.");
@@ -132,13 +153,14 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
132
153
  }
133
154
 
134
155
  if (argv.length === 1 && USAGE_REQUEST_TOKENS.has(argv[0])) {
135
- const publicPackageVersion = await loadPackageVersion();
136
- const exitCode = await runRuntimeCli([
137
- argv[0],
138
- usageExtrasArg(),
139
- usageVersionArg(publicPackageVersion),
140
- ]);
141
- process.exitCode = exitCode;
156
+ await runWithDeferredUpdateWarning(argv, async () => {
157
+ const publicPackageVersion = await loadPackageVersion();
158
+ return await runRuntimeCli([
159
+ argv[0],
160
+ usageExtrasArg(),
161
+ usageVersionArg(publicPackageVersion),
162
+ ]);
163
+ });
142
164
  return;
143
165
  }
144
166
 
@@ -164,8 +186,9 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
164
186
  }
165
187
  }
166
188
  const passthrough = ["help", helpExtrasArg(), ...formatArgs];
167
- const exitCode = await runRuntimeCli(passthrough);
168
- process.exitCode = exitCode;
189
+ await runWithDeferredUpdateWarning(argv, async () =>
190
+ await runRuntimeCli(passthrough),
191
+ );
169
192
  return;
170
193
  }
171
194
 
@@ -178,8 +201,16 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
178
201
  }
179
202
 
180
203
  const [command] = argv;
204
+ if (command === "update") {
205
+ process.exitCode = await runUpdateCommand(argv.slice(1));
206
+ return;
207
+ }
208
+
181
209
  if (command === "help") {
182
- await routeExplicitHelp(argv);
210
+ await runWithDeferredUpdateWarning(argv, async () => {
211
+ await routeExplicitHelp(argv);
212
+ return process.exitCode ?? 0;
213
+ });
183
214
  return;
184
215
  }
185
216
 
@@ -188,16 +219,23 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
188
219
  command === "uninstall" ||
189
220
  command === "completion"
190
221
  ) {
191
- await runSetupCli(["node", "flywheel", ...argv]);
222
+ await runWithDeferredUpdateWarning(argv, async () => {
223
+ return await runSetupCli(["node", "flywheel", ...argv], {
224
+ exitOverride: true,
225
+ });
226
+ });
192
227
  return;
193
228
  }
194
229
 
195
230
  if (command.includes(":")) {
196
- const exitCode = await runRuntimeCli(argv);
197
- process.exitCode = exitCode;
231
+ await runWithDeferredUpdateWarning(argv, async () =>
232
+ await runRuntimeCli(argv),
233
+ );
198
234
  return;
199
235
  }
200
236
 
201
- process.stderr.write(`unknown command: ${command}\n`);
202
- process.exitCode = 1;
237
+ await runWithDeferredUpdateWarning(argv, async () => {
238
+ process.stderr.write(`unknown command: ${command}\n`);
239
+ return 1;
240
+ });
203
241
  }
@@ -0,0 +1,124 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ import { FLYWHEEL_PACKAGE_NAME } from "./version.mjs";
7
+
8
+ export const UPDATE_CHECK_CACHE_INTERVAL_MS = 24 * 60 * 60 * 1000;
9
+
10
+ export function resolveUpdateCheckCachePath({
11
+ env = process.env,
12
+ homeDir = os.homedir(),
13
+ } = {}) {
14
+ const override = String(env.FLYWHEEL_UPDATE_CHECK_CACHE_PATH || "").trim();
15
+ if (override) {
16
+ return override;
17
+ }
18
+ return path.join(homeDir, ".flywheel", "update-check.json");
19
+ }
20
+
21
+ function parseCache(raw) {
22
+ const parsed = JSON.parse(raw);
23
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
24
+ return null;
25
+ }
26
+ if (parsed.packageName !== FLYWHEEL_PACKAGE_NAME) {
27
+ return null;
28
+ }
29
+ if (typeof parsed.latestVersion !== "string" || !parsed.latestVersion.trim()) {
30
+ return null;
31
+ }
32
+ if (typeof parsed.nextCheckAt !== "string" || !parsed.nextCheckAt.trim()) {
33
+ return null;
34
+ }
35
+ const nextCheckAt = new Date(parsed.nextCheckAt);
36
+ if (Number.isNaN(nextCheckAt.getTime())) {
37
+ return null;
38
+ }
39
+ return parsed;
40
+ }
41
+
42
+ export async function readFreshUpdateCache({
43
+ cachePath = resolveUpdateCheckCachePath(),
44
+ now = () => new Date(),
45
+ } = {}) {
46
+ let raw;
47
+ try {
48
+ raw = await readFile(cachePath, "utf8");
49
+ } catch {
50
+ return null;
51
+ }
52
+
53
+ try {
54
+ const parsed = parseCache(raw);
55
+ if (!parsed) return null;
56
+ if (new Date(parsed.nextCheckAt).getTime() <= now().getTime()) {
57
+ return null;
58
+ }
59
+ return parsed;
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ async function finalCacheIsValid(cachePath) {
66
+ try {
67
+ return parseCache(await readFile(cachePath, "utf8")) !== null;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ export async function writeUpdateCacheAtomic({
74
+ cachePath = resolveUpdateCheckCachePath(),
75
+ latestVersion,
76
+ now = () => new Date(),
77
+ intervalMs = UPDATE_CHECK_CACHE_INTERVAL_MS,
78
+ pid = process.pid,
79
+ randomId = randomUUID,
80
+ } = {}) {
81
+ const checkedAt = now();
82
+ const payload = {
83
+ packageName: FLYWHEEL_PACKAGE_NAME,
84
+ latestVersion,
85
+ checkedAt: checkedAt.toISOString(),
86
+ nextCheckAt: new Date(checkedAt.getTime() + intervalMs).toISOString(),
87
+ };
88
+ const tmpPath = `${cachePath}.${pid}.${randomId()}.tmp`;
89
+ await mkdir(path.dirname(cachePath), { recursive: true });
90
+ await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, {
91
+ encoding: "utf8",
92
+ mode: 0o600,
93
+ });
94
+ try {
95
+ await rename(tmpPath, cachePath);
96
+ } catch (error) {
97
+ await rm(tmpPath, { force: true }).catch(() => {});
98
+ if (await finalCacheIsValid(cachePath)) {
99
+ return payload;
100
+ }
101
+ throw error;
102
+ }
103
+ return payload;
104
+ }
105
+
106
+ export async function getLatestVersionWithCache({
107
+ cachePath = resolveUpdateCheckCachePath(),
108
+ now = () => new Date(),
109
+ fetchLatestVersion,
110
+ intervalMs = UPDATE_CHECK_CACHE_INTERVAL_MS,
111
+ } = {}) {
112
+ const cached = await readFreshUpdateCache({ cachePath, now });
113
+ if (cached) {
114
+ return cached.latestVersion;
115
+ }
116
+ const latestVersion = await fetchLatestVersion();
117
+ await writeUpdateCacheAtomic({
118
+ cachePath,
119
+ latestVersion,
120
+ now,
121
+ intervalMs,
122
+ });
123
+ return latestVersion;
124
+ }
@@ -0,0 +1,438 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ import { loadPackageVersion } from "../public-command-metadata.mjs";
7
+ import { resolveFlywheelBinaryOnPath as defaultResolveFlywheelBinaryOnPath } from "../setup/modes/cli.mjs";
8
+ import { discoverInstalledFlywheelState } from "./install-state.mjs";
9
+ import { fetchLatestVersionFromRegistry } from "./registry.mjs";
10
+ import {
11
+ renderRefreshPlan,
12
+ refreshExistingSetup,
13
+ resolveDiscoveryServerUrl,
14
+ } from "./refresh.mjs";
15
+ import {
16
+ compareInstalledToLatest,
17
+ FLYWHEEL_PACKAGE_NAME,
18
+ } from "./version.mjs";
19
+
20
+ const PACKAGE_SPEC = `${FLYWHEEL_PACKAGE_NAME}@latest`;
21
+ const PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
22
+ const PUBLIC_NPM_REGISTRY_ARGS = [
23
+ `--registry=${PUBLIC_NPM_REGISTRY}`,
24
+ `--@paradigma-inc:registry=${PUBLIC_NPM_REGISTRY}`,
25
+ ];
26
+ const POSIX_MANUAL_INSTALL_COMMAND =
27
+ 'npm install -g --prefix "${FLYWHEEL_INSTALL_PREFIX:-$HOME/.local}" --registry=https://registry.npmjs.org/ --@paradigma-inc:registry=https://registry.npmjs.org/ @paradigma-inc/flywheel@latest';
28
+ const WINDOWS_MANUAL_INSTALL_COMMAND =
29
+ '$prefix = if ($env:FLYWHEEL_INSTALL_PREFIX) { $env:FLYWHEEL_INSTALL_PREFIX } else { "$env:USERPROFILE\\.local" }; npm install -g --prefix $prefix --registry=https://registry.npmjs.org/ --@paradigma-inc:registry=https://registry.npmjs.org/ @paradigma-inc/flywheel@latest';
30
+
31
+ function isWindowsCommandShim(filePath, platform) {
32
+ return (
33
+ platform === "win32" &&
34
+ [".bat", ".cmd"].includes(path.extname(filePath).toLowerCase())
35
+ );
36
+ }
37
+
38
+ function shouldUseWindowsCommandShell(filePath, platform) {
39
+ return (
40
+ platform === "win32" &&
41
+ (path.extname(filePath) === "" || isWindowsCommandShim(filePath, platform))
42
+ );
43
+ }
44
+
45
+ function quoteWindowsCommandArgument(value) {
46
+ return `"${String(value).replaceAll('"', '""')}"`;
47
+ }
48
+
49
+ function resolveWindowsCommandShell(env) {
50
+ return env.ComSpec || env.COMSPEC || "cmd.exe";
51
+ }
52
+
53
+ function buildWindowsCommandLine(file, args) {
54
+ return `"${[
55
+ quoteWindowsCommandArgument(file),
56
+ ...args.map((arg) => quoteWindowsCommandArgument(arg)),
57
+ ].join(" ")}"`;
58
+ }
59
+
60
+ function buildSpawnFileInvocation({ file, args, env, platform }) {
61
+ if (!shouldUseWindowsCommandShell(file, platform)) {
62
+ return { command: file, args };
63
+ }
64
+ return {
65
+ command: resolveWindowsCommandShell(env),
66
+ args: ["/d", "/s", "/c", buildWindowsCommandLine(file, args)],
67
+ };
68
+ }
69
+
70
+ async function defaultSpawnFile(file, args, options = {}) {
71
+ const {
72
+ env = process.env,
73
+ platform = process.platform,
74
+ ...spawnOptions
75
+ } = options;
76
+ const invocation = buildSpawnFileInvocation({ file, args, env, platform });
77
+ return await new Promise((resolve, reject) => {
78
+ const child = spawn(invocation.command, invocation.args, {
79
+ stdio: "inherit",
80
+ env,
81
+ ...spawnOptions,
82
+ });
83
+ child.on("error", reject);
84
+ child.on("close", (code, signal) => {
85
+ resolve({
86
+ exitCode: typeof code === "number" ? code : signal ? 1 : 0,
87
+ signal,
88
+ });
89
+ });
90
+ });
91
+ }
92
+
93
+ function manualInstallCommand(platform) {
94
+ return platform === "win32"
95
+ ? WINDOWS_MANUAL_INSTALL_COMMAND
96
+ : POSIX_MANUAL_INSTALL_COMMAND;
97
+ }
98
+
99
+ async function pathExists(filePath) {
100
+ try {
101
+ await access(filePath);
102
+ return true;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ function stripTrailingSlash(filePath) {
109
+ const parsed = path.parse(filePath);
110
+ let normalized = filePath;
111
+ while (
112
+ normalized.length > parsed.root.length &&
113
+ /[/\\]$/.test(normalized)
114
+ ) {
115
+ normalized = normalized.slice(0, -1);
116
+ }
117
+ return normalized;
118
+ }
119
+
120
+ function normalizeInstallPrefix(rawPrefix, { cwd, homeDir }) {
121
+ const raw = String(rawPrefix || "").trim();
122
+ if (raw === "~") {
123
+ return homeDir;
124
+ }
125
+ if (raw.startsWith(`~${path.sep}`) || raw.startsWith("~/")) {
126
+ return stripTrailingSlash(path.join(homeDir, raw.slice(2)));
127
+ }
128
+ const resolved = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
129
+ return stripTrailingSlash(resolved);
130
+ }
131
+
132
+ function parseFlags(argv) {
133
+ const flags = {
134
+ check: false,
135
+ dryRun: false,
136
+ yes: false,
137
+ help: false,
138
+ refreshExistingSetup: false,
139
+ };
140
+ const unknown = [];
141
+ for (const token of argv) {
142
+ if (token === "--check") flags.check = true;
143
+ else if (token === "--dry-run") flags.dryRun = true;
144
+ else if (token === "--yes") flags.yes = true;
145
+ else if (token === "--help" || token === "-h") flags.help = true;
146
+ else if (token === "--refresh-existing-setup") {
147
+ flags.refreshExistingSetup = true;
148
+ } else {
149
+ unknown.push(token);
150
+ }
151
+ }
152
+ return { flags, unknown };
153
+ }
154
+
155
+ function renderUpdateHelp() {
156
+ return [
157
+ "Usage: flywheel update [--check] [--dry-run] [--yes]",
158
+ "",
159
+ "Update the local Flywheel CLI package and refresh local setup artifacts.",
160
+ "",
161
+ "Options:",
162
+ " --check Print installed/latest version status without changing files.",
163
+ " --dry-run Print planned package, MCP, and skill refresh steps.",
164
+ " --yes Run prompt-free for agent execution.",
165
+ " -h, --help Show this help.",
166
+ "",
167
+ ].join("\n");
168
+ }
169
+
170
+ function deriveManagedPrefixFromFlywheelBinaryPath({
171
+ binaryPath,
172
+ platform = process.platform,
173
+ }) {
174
+ if (!binaryPath) {
175
+ return null;
176
+ }
177
+ const basename = path.basename(binaryPath).toLowerCase();
178
+ if (
179
+ platform === "win32" &&
180
+ ["flywheel", "flywheel.cmd", "flywheel.bat", "flywheel.exe"].includes(
181
+ basename,
182
+ )
183
+ ) {
184
+ return path.dirname(binaryPath);
185
+ }
186
+ if (basename !== "flywheel") {
187
+ return null;
188
+ }
189
+ const binDir = path.dirname(binaryPath);
190
+ if (path.basename(binDir).toLowerCase() !== "bin") {
191
+ return null;
192
+ }
193
+ return path.dirname(binDir);
194
+ }
195
+
196
+ async function resolveUpdatePrefix({
197
+ cwd = process.cwd(),
198
+ env = process.env,
199
+ homeDir = os.homedir(),
200
+ currentExecPath,
201
+ platform = process.platform,
202
+ resolveFlywheelBinaryOnPath = defaultResolveFlywheelBinaryOnPath,
203
+ } = {}) {
204
+ const explicitPrefix = String(env.FLYWHEEL_INSTALL_PREFIX || "").trim();
205
+ if (explicitPrefix) {
206
+ return normalizeInstallPrefix(explicitPrefix, { cwd, homeDir });
207
+ }
208
+ const currentPrefix = deriveManagedPrefixFromFlywheelBinaryPath({
209
+ binaryPath: currentExecPath,
210
+ platform,
211
+ });
212
+ if (currentPrefix) {
213
+ return currentPrefix;
214
+ }
215
+ const pathBinary = await resolveFlywheelBinaryOnPath({ env, platform });
216
+ const pathPrefix = deriveManagedPrefixFromFlywheelBinaryPath({
217
+ binaryPath: pathBinary,
218
+ platform,
219
+ });
220
+ return pathPrefix || path.join(homeDir, ".local");
221
+ }
222
+
223
+ function resolveFlywheelBinaryInPrefix({ prefix, platform = process.platform }) {
224
+ if (platform === "win32") {
225
+ return path.join(prefix, "flywheel.cmd");
226
+ }
227
+ return path.join(prefix, "bin", "flywheel");
228
+ }
229
+
230
+ function isPathWithin(childPath, parentPath) {
231
+ const relative = path.relative(
232
+ path.resolve(parentPath),
233
+ path.resolve(childPath),
234
+ );
235
+ return (
236
+ relative === "" ||
237
+ (!relative.startsWith("..") && !path.isAbsolute(relative))
238
+ );
239
+ }
240
+
241
+ async function canUseManagedPrefix({
242
+ prefix,
243
+ currentExecPath,
244
+ flywheelBinaryPath,
245
+ pathExistsFn = pathExists,
246
+ }) {
247
+ if (currentExecPath && isPathWithin(currentExecPath, prefix)) {
248
+ return true;
249
+ }
250
+ return await pathExistsFn(flywheelBinaryPath);
251
+ }
252
+
253
+ function statusLines(comparison) {
254
+ return [
255
+ `Installed ${FLYWHEEL_PACKAGE_NAME}: ${comparison.currentVersion}`,
256
+ `Latest ${FLYWHEEL_PACKAGE_NAME}: ${comparison.latestVersion}`,
257
+ `Status: ${comparison.status}`,
258
+ "",
259
+ ].join("\n");
260
+ }
261
+
262
+ function packageInstallArgs(prefix) {
263
+ return [
264
+ "install",
265
+ "-g",
266
+ "--prefix",
267
+ prefix,
268
+ ...PUBLIC_NPM_REGISTRY_ARGS,
269
+ PACKAGE_SPEC,
270
+ ];
271
+ }
272
+
273
+ function packageInstallCommand(prefix) {
274
+ return ["npm", ...packageInstallArgs(prefix)].join(" ");
275
+ }
276
+
277
+ async function runCheck({
278
+ currentVersion,
279
+ fetchLatestVersion,
280
+ stdout,
281
+ stderr,
282
+ }) {
283
+ try {
284
+ const latestVersion = await fetchLatestVersion();
285
+ const comparison = compareInstalledToLatest({
286
+ currentVersion,
287
+ latestVersion,
288
+ });
289
+ stdout.write(statusLines(comparison));
290
+ return 0;
291
+ } catch (error) {
292
+ const message = error instanceof Error ? error.message : String(error);
293
+ stderr.write(
294
+ `Unable to check latest ${FLYWHEEL_PACKAGE_NAME} version: ${message}\n`,
295
+ );
296
+ return 1;
297
+ }
298
+ }
299
+
300
+ export async function runUpdateCommand(argv = [], deps = {}) {
301
+ const {
302
+ cwd = process.cwd(),
303
+ env = process.env,
304
+ homeDir = os.homedir(),
305
+ platform = process.platform,
306
+ stdout = process.stdout,
307
+ stderr = process.stderr,
308
+ currentExecPath = process.argv[1],
309
+ currentVersion = await loadPackageVersion(),
310
+ fetchLatestVersion = () => fetchLatestVersionFromRegistry(),
311
+ spawnFile = defaultSpawnFile,
312
+ discoverInstalledState = discoverInstalledFlywheelState,
313
+ refreshSetup = refreshExistingSetup,
314
+ pathExistsFn = pathExists,
315
+ resolveFlywheelBinaryOnPath = defaultResolveFlywheelBinaryOnPath,
316
+ } = deps;
317
+ const { flags, unknown } = parseFlags(argv);
318
+
319
+ if (flags.help) {
320
+ stdout.write(renderUpdateHelp());
321
+ return 0;
322
+ }
323
+ if (unknown.length > 0) {
324
+ stderr.write(`unknown update option: ${unknown[0]}\n`);
325
+ return 1;
326
+ }
327
+
328
+ if (flags.check) {
329
+ return await runCheck({
330
+ currentVersion,
331
+ fetchLatestVersion,
332
+ stdout,
333
+ stderr,
334
+ });
335
+ }
336
+
337
+ const prefix = await resolveUpdatePrefix({
338
+ cwd,
339
+ env,
340
+ homeDir,
341
+ currentExecPath,
342
+ platform,
343
+ resolveFlywheelBinaryOnPath,
344
+ });
345
+ const flywheelBinaryPath = resolveFlywheelBinaryInPrefix({ prefix, platform });
346
+
347
+ if (flags.dryRun) {
348
+ const state = await discoverInstalledState({
349
+ cwd,
350
+ env,
351
+ serverUrl: resolveDiscoveryServerUrl({ env }),
352
+ });
353
+ stdout.write(
354
+ renderRefreshPlan({
355
+ state,
356
+ prefix,
357
+ packageInstallCommand: packageInstallCommand(prefix),
358
+ }),
359
+ );
360
+ return 0;
361
+ }
362
+
363
+ if (flags.refreshExistingSetup) {
364
+ try {
365
+ return await refreshSetup({
366
+ cwd,
367
+ env,
368
+ stdout,
369
+ stderr,
370
+ });
371
+ } catch (error) {
372
+ const stepName = error?.stepName || "refresh-existing-setup";
373
+ const message = error instanceof Error ? error.message : String(error);
374
+ stderr.write(
375
+ `Failed refresh-existing-setup step ${stepName}: ${message}\n`,
376
+ );
377
+ return Number(error?.exitCode || 1);
378
+ }
379
+ }
380
+
381
+ if (
382
+ !(await canUseManagedPrefix({
383
+ prefix,
384
+ currentExecPath,
385
+ flywheelBinaryPath,
386
+ pathExistsFn,
387
+ }))
388
+ ) {
389
+ stderr.write(
390
+ [
391
+ `Unable to update ${FLYWHEEL_PACKAGE_NAME}: no managed Flywheel binary was found under ${prefix}.`,
392
+ `Manual command: ${manualInstallCommand(platform)}`,
393
+ "",
394
+ ].join("\n"),
395
+ );
396
+ return 2;
397
+ }
398
+
399
+ stderr.write(`Updating ${FLYWHEEL_PACKAGE_NAME} with npm.\n`);
400
+ let npmResult;
401
+ try {
402
+ npmResult = await spawnFile(
403
+ "npm",
404
+ packageInstallArgs(prefix),
405
+ { env, platform },
406
+ );
407
+ } catch (error) {
408
+ const message = error instanceof Error ? error.message : String(error);
409
+ stderr.write(`Failed to update ${FLYWHEEL_PACKAGE_NAME}: ${message}\n`);
410
+ return 1;
411
+ }
412
+ const npmExitCode = Number(npmResult?.exitCode || 0);
413
+ if (npmExitCode !== 0) {
414
+ stderr.write(`Failed to update ${FLYWHEEL_PACKAGE_NAME}.\n`);
415
+ return npmExitCode;
416
+ }
417
+
418
+ stderr.write("Refreshing local Flywheel setup artifacts.\n");
419
+ let refreshResult;
420
+ try {
421
+ refreshResult = await spawnFile(
422
+ flywheelBinaryPath,
423
+ ["update", "--refresh-existing-setup", "--yes"],
424
+ { env, platform },
425
+ );
426
+ } catch (error) {
427
+ const message = error instanceof Error ? error.message : String(error);
428
+ stderr.write(`Failed refresh-existing-setup step spawn: ${message}\n`);
429
+ return 1;
430
+ }
431
+ const refreshExitCode = Number(refreshResult?.exitCode || 0);
432
+ if (refreshExitCode !== 0) {
433
+ stderr.write("Failed refresh-existing-setup step updated-binary.\n");
434
+ return refreshExitCode;
435
+ }
436
+
437
+ return 0;
438
+ }