fleetfizz 0.5.2 → 0.6.0

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 (3) hide show
  1. package/dist/fleetfizz.mjs +1688 -923
  2. package/package.json +2 -1
  3. package/schema.json +467 -17
@@ -22876,6 +22876,10 @@ function decodeUnknownEffect$1(schema, options) {
22876
22876
  const parser = run$3(schema.ast);
22877
22877
  return options === void 0 ? parser : (input, overrideOptions) => parser(input, mergeParseOptions(options, overrideOptions));
22878
22878
  }
22879
+ /** @internal */
22880
+ function decodeUnknownOption$1(schema, options) {
22881
+ return asOption(decodeUnknownEffect$1(schema, options));
22882
+ }
22879
22883
  /**
22880
22884
  * Creates a decoder for `unknown` input that reports failure safely as a
22881
22885
  * `Result`.
@@ -22961,6 +22965,16 @@ function runWithCompiler(compiler, ast) {
22961
22965
  function asExit(parser) {
22962
22966
  return (input, options) => runSyncExit(parser(input, options));
22963
22967
  }
22968
+ /** @internal */
22969
+ function asOption(parser) {
22970
+ const parserExit = asExit(parser);
22971
+ return (input, options) => {
22972
+ const exit = parserExit(input, options);
22973
+ if (isSuccess(exit)) return some(exit.value);
22974
+ getSchemaIssueOrThrow(exit.cause, "Option adapter can only return none for schema issues");
22975
+ return none();
22976
+ };
22977
+ }
22964
22978
  function asResult(parser) {
22965
22979
  const parserExit = asExit(parser);
22966
22980
  return (input, options) => {
@@ -24468,6 +24482,34 @@ function decodeUnknownEffect(schema, options) {
24468
24482
  */
24469
24483
  const decodeEffect = decodeUnknownEffect;
24470
24484
  /**
24485
+ * Decodes an `unknown` input against a schema, returning an `Option` that is
24486
+ * `Some` with the decoded value on success or `None` for schema mismatches.
24487
+ *
24488
+ * **When to use**
24489
+ *
24490
+ * Use when you do not know the input type statically and only need to know
24491
+ * whether decoding succeeded.
24492
+ *
24493
+ * **Details**
24494
+ *
24495
+ * Prefer this over {@link decodeUnknownExit} or {@link decodeUnknownEffect}
24496
+ * when you don't need error details. For input already typed as the schema's
24497
+ * `Encoded` type use {@link decodeOption}.
24498
+ * Options may be provided either when creating the decoder or when applying it;
24499
+ * application options override creation options.
24500
+ *
24501
+ * **Gotchas**
24502
+ *
24503
+ * Only causes made entirely of schema issues are converted to `None`. Causes
24504
+ * that contain defects, interruptions, or other non-schema reasons throw
24505
+ * instead.
24506
+ *
24507
+ * @stability stable
24508
+ * @category decoding
24509
+ * @since 3.10.0
24510
+ */
24511
+ const decodeUnknownOption = decodeUnknownOption$1;
24512
+ /**
24471
24513
  * Decodes an `unknown` input against a schema, returning a `Result` that
24472
24514
  * succeeds with the decoded value or fails with a {@link SchemaError} for schema
24473
24515
  * mismatches.
@@ -25893,145 +25935,6 @@ function defaultShouldQuit(input) {
25893
25935
  */
25894
25936
  const layer = /*#__PURE__*/ provideMerge(layer$11, /*#__PURE__*/ mergeAll(layer$7, layer$9, layer$5, layer$3, layer$2));
25895
25937
  //#endregion
25896
- //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/Console.js
25897
- /**
25898
- * Context reference for the current console service in the Effect system, allowing access to the active console implementation from within the Effect context.
25899
- *
25900
- * **When to use**
25901
- *
25902
- * Use when you need an effect to run against a provided console implementation,
25903
- * such as tests or alternate runtimes, rather than the default console.
25904
- *
25905
- * **Details**
25906
- *
25907
- * When no override is provided, the reference resolves to `globalThis.console`.
25908
- *
25909
- * **Example** (Accessing the current console)
25910
- *
25911
- * ```ts import.meta.vitest
25912
- * import { Console, Effect } from "effect"
25913
- *
25914
- * const messages: Array<unknown> = []
25915
- * const testConsole: Console.Console = Object.assign(Object.create(console), {
25916
- * log: (...args: ReadonlyArray<unknown>) => messages.push(...args)
25917
- * })
25918
- * const program = Console.consoleWith((console) =>
25919
- * Effect.sync(() => {
25920
- * console.log("Hello from current console!")
25921
- * })
25922
- * )
25923
- *
25924
- * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
25925
- * messages // => ["Hello from current console!"]
25926
- * ```
25927
- *
25928
- * @see {@link consoleWith} for using the current console service inside an effect
25929
- *
25930
- * @stability stable
25931
- * @category services
25932
- * @since 2.0.0
25933
- */
25934
- const Console = ConsoleRef;
25935
- /**
25936
- * Creates an Effect that provides access to the current console service and lets you perform operations with it within an Effect context.
25937
- *
25938
- * **Example** (Accessing the current console service)
25939
- *
25940
- * ```ts import.meta.vitest
25941
- * import { Console, Effect } from "effect"
25942
- *
25943
- * const messages: Array<unknown> = []
25944
- * const testConsole: Console.Console = Object.assign(Object.create(console), {
25945
- * log: (...args: ReadonlyArray<unknown>) => messages.push(...args),
25946
- * error: (...args: ReadonlyArray<unknown>) => messages.push(...args)
25947
- * })
25948
- * const program = Console.consoleWith((console) =>
25949
- * Effect.sync(() => {
25950
- * console.log("Hello, world!")
25951
- * console.error("This is an error message")
25952
- * })
25953
- * )
25954
- *
25955
- * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
25956
- * messages // => ["Hello, world!", "This is an error message"]
25957
- * ```
25958
- *
25959
- * @stability stable
25960
- * @category constructors
25961
- * @since 2.0.0
25962
- */
25963
- const consoleWith = (f) => withFiber$1((fiber) => f(fiber.getRef(Console)));
25964
- /**
25965
- * Writes an error-level message to the console, typically displayed with error
25966
- * styling by the active console implementation.
25967
- *
25968
- * **Example** (Writing error messages)
25969
- *
25970
- * ```ts import.meta.vitest
25971
- * import { Console, Effect } from "effect"
25972
- *
25973
- * const messages: Array<ReadonlyArray<unknown>> = []
25974
- * const testConsole: Console.Console = Object.assign(Object.create(console), {
25975
- * error: (...args: ReadonlyArray<unknown>) => messages.push(args)
25976
- * })
25977
- * const program = Effect.gen(function*() {
25978
- * yield* Console.error("Something went wrong!")
25979
- * yield* Console.error("Error details:", {
25980
- * code: 500,
25981
- * message: "Internal Server Error"
25982
- * })
25983
- * })
25984
- *
25985
- * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
25986
- * const expected = [
25987
- * ["Something went wrong!"],
25988
- * ["Error details:", { code: 500, message: "Internal Server Error" }]
25989
- * ]
25990
- * messages // => expected
25991
- * ```
25992
- *
25993
- * @stability stable
25994
- * @category accessors
25995
- * @since 2.0.0
25996
- */
25997
- const error = (...args) => consoleWith((console) => sync$1(() => {
25998
- console.error(...args);
25999
- }));
26000
- /**
26001
- * Logs a general-purpose message to the console.
26002
- *
26003
- * **Example** (Writing log messages)
26004
- *
26005
- * ```ts import.meta.vitest
26006
- * import { Console, Effect } from "effect"
26007
- *
26008
- * const messages: Array<ReadonlyArray<unknown>> = []
26009
- * const testConsole: Console.Console = Object.assign(Object.create(console), {
26010
- * log: (...args: ReadonlyArray<unknown>) => messages.push(args)
26011
- * })
26012
- * const program = Effect.gen(function*() {
26013
- * yield* Console.log("Hello, world!")
26014
- * yield* Console.log("User data:", { name: "John", age: 30 })
26015
- * yield* Console.log("Processing", 42, "items")
26016
- * })
26017
- *
26018
- * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
26019
- * const expected = [
26020
- * ["Hello, world!"],
26021
- * ["User data:", { name: "John", age: 30 }],
26022
- * ["Processing", 42, "items"]
26023
- * ]
26024
- * messages // => expected
26025
- * ```
26026
- *
26027
- * @stability stable
26028
- * @category accessors
26029
- * @since 2.0.0
26030
- */
26031
- const log = (...args) => consoleWith((console) => sync$1(() => {
26032
- console.log(...args);
26033
- }));
26034
- //#endregion
26035
25938
  //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/cli/CliError.js
26036
25939
  /**
26037
25940
  * Defines structured errors for the unstable CLI parser and runner.
@@ -29124,6 +29027,145 @@ const getParamMetadata = (param) => {
29124
29027
  });
29125
29028
  };
29126
29029
  //#endregion
29030
+ //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/Console.js
29031
+ /**
29032
+ * Context reference for the current console service in the Effect system, allowing access to the active console implementation from within the Effect context.
29033
+ *
29034
+ * **When to use**
29035
+ *
29036
+ * Use when you need an effect to run against a provided console implementation,
29037
+ * such as tests or alternate runtimes, rather than the default console.
29038
+ *
29039
+ * **Details**
29040
+ *
29041
+ * When no override is provided, the reference resolves to `globalThis.console`.
29042
+ *
29043
+ * **Example** (Accessing the current console)
29044
+ *
29045
+ * ```ts import.meta.vitest
29046
+ * import { Console, Effect } from "effect"
29047
+ *
29048
+ * const messages: Array<unknown> = []
29049
+ * const testConsole: Console.Console = Object.assign(Object.create(console), {
29050
+ * log: (...args: ReadonlyArray<unknown>) => messages.push(...args)
29051
+ * })
29052
+ * const program = Console.consoleWith((console) =>
29053
+ * Effect.sync(() => {
29054
+ * console.log("Hello from current console!")
29055
+ * })
29056
+ * )
29057
+ *
29058
+ * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
29059
+ * messages // => ["Hello from current console!"]
29060
+ * ```
29061
+ *
29062
+ * @see {@link consoleWith} for using the current console service inside an effect
29063
+ *
29064
+ * @stability stable
29065
+ * @category services
29066
+ * @since 2.0.0
29067
+ */
29068
+ const Console = ConsoleRef;
29069
+ /**
29070
+ * Creates an Effect that provides access to the current console service and lets you perform operations with it within an Effect context.
29071
+ *
29072
+ * **Example** (Accessing the current console service)
29073
+ *
29074
+ * ```ts import.meta.vitest
29075
+ * import { Console, Effect } from "effect"
29076
+ *
29077
+ * const messages: Array<unknown> = []
29078
+ * const testConsole: Console.Console = Object.assign(Object.create(console), {
29079
+ * log: (...args: ReadonlyArray<unknown>) => messages.push(...args),
29080
+ * error: (...args: ReadonlyArray<unknown>) => messages.push(...args)
29081
+ * })
29082
+ * const program = Console.consoleWith((console) =>
29083
+ * Effect.sync(() => {
29084
+ * console.log("Hello, world!")
29085
+ * console.error("This is an error message")
29086
+ * })
29087
+ * )
29088
+ *
29089
+ * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
29090
+ * messages // => ["Hello, world!", "This is an error message"]
29091
+ * ```
29092
+ *
29093
+ * @stability stable
29094
+ * @category constructors
29095
+ * @since 2.0.0
29096
+ */
29097
+ const consoleWith = (f) => withFiber$1((fiber) => f(fiber.getRef(Console)));
29098
+ /**
29099
+ * Writes an error-level message to the console, typically displayed with error
29100
+ * styling by the active console implementation.
29101
+ *
29102
+ * **Example** (Writing error messages)
29103
+ *
29104
+ * ```ts import.meta.vitest
29105
+ * import { Console, Effect } from "effect"
29106
+ *
29107
+ * const messages: Array<ReadonlyArray<unknown>> = []
29108
+ * const testConsole: Console.Console = Object.assign(Object.create(console), {
29109
+ * error: (...args: ReadonlyArray<unknown>) => messages.push(args)
29110
+ * })
29111
+ * const program = Effect.gen(function*() {
29112
+ * yield* Console.error("Something went wrong!")
29113
+ * yield* Console.error("Error details:", {
29114
+ * code: 500,
29115
+ * message: "Internal Server Error"
29116
+ * })
29117
+ * })
29118
+ *
29119
+ * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
29120
+ * const expected = [
29121
+ * ["Something went wrong!"],
29122
+ * ["Error details:", { code: 500, message: "Internal Server Error" }]
29123
+ * ]
29124
+ * messages // => expected
29125
+ * ```
29126
+ *
29127
+ * @stability stable
29128
+ * @category accessors
29129
+ * @since 2.0.0
29130
+ */
29131
+ const error = (...args) => consoleWith((console) => sync$1(() => {
29132
+ console.error(...args);
29133
+ }));
29134
+ /**
29135
+ * Logs a general-purpose message to the console.
29136
+ *
29137
+ * **Example** (Writing log messages)
29138
+ *
29139
+ * ```ts import.meta.vitest
29140
+ * import { Console, Effect } from "effect"
29141
+ *
29142
+ * const messages: Array<ReadonlyArray<unknown>> = []
29143
+ * const testConsole: Console.Console = Object.assign(Object.create(console), {
29144
+ * log: (...args: ReadonlyArray<unknown>) => messages.push(args)
29145
+ * })
29146
+ * const program = Effect.gen(function*() {
29147
+ * yield* Console.log("Hello, world!")
29148
+ * yield* Console.log("User data:", { name: "John", age: 30 })
29149
+ * yield* Console.log("Processing", 42, "items")
29150
+ * })
29151
+ *
29152
+ * Effect.runSync(Effect.provideService(program, Console.Console, testConsole))
29153
+ * const expected = [
29154
+ * ["Hello, world!"],
29155
+ * ["User data:", { name: "John", age: 30 }],
29156
+ * ["Processing", 42, "items"]
29157
+ * ]
29158
+ * messages // => expected
29159
+ * ```
29160
+ *
29161
+ * @stability stable
29162
+ * @category accessors
29163
+ * @since 2.0.0
29164
+ */
29165
+ const log = (...args) => consoleWith((console) => sync$1(() => {
29166
+ console.log(...args);
29167
+ }));
29168
+ //#endregion
29127
29169
  //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/cli/CliOutput.js
29128
29170
  /**
29129
29171
  * Formats CLI help and errors as text.
@@ -32440,7 +32482,7 @@ const runWith = (command, config) => {
32440
32482
  };
32441
32483
  //#endregion
32442
32484
  //#region package.json
32443
- var version = "0.5.2";
32485
+ var version = "0.6.0";
32444
32486
  //#endregion
32445
32487
  //#region ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
32446
32488
  /**
@@ -35097,51 +35139,45 @@ var import_semver = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
35097
35139
  })))(), 1);
35098
35140
  var BumpError = class extends TaggedError()("BumpError", {
35099
35141
  path: String$5,
35100
- package: String$5,
35142
+ at: String$5,
35101
35143
  reason: String$5
35102
35144
  }) {
35103
35145
  get message() {
35104
- return `${this.path}: cannot bump ${this.package}: ${this.reason}`;
35146
+ return `${this.path}: cannot bump ${this.at}: ${this.reason}`;
35105
35147
  }
35106
35148
  };
35107
- const rangePath = (pkg) => pkg === "vite-plus" ? ["vp", "version"] : pkg === "node" ? ["vp", "node"] : [
35108
- "vp",
35109
- "globals",
35110
- pkg
35111
- ];
35112
35149
  const bumpedRange = (current, installed) => import_semver.valid(current) !== null ? installed : `^${installed}`;
35113
- const bumpText = (text, pkg, installed) => {
35114
- const path = rangePath(pkg);
35150
+ const bumpText = (text, path, installed) => {
35115
35151
  const node = findNodeAtLocation(parseTree(text) ?? {
35116
35152
  type: "null",
35117
35153
  offset: 0,
35118
35154
  length: 0
35119
- }, path);
35155
+ }, [...path]);
35120
35156
  if (node === void 0 || node.type !== "string" || typeof node.value !== "string") return { reason: `${path.join(".")} is not a string literal; change it by hand` };
35121
35157
  const range = bumpedRange(node.value, installed);
35122
35158
  return {
35123
- text: applyEdits(text, modify(text, path, range, { formattingOptions: {
35159
+ text: applyEdits(text, modify(text, [...path], range, { formattingOptions: {
35124
35160
  insertSpaces: true,
35125
35161
  tabSize: 2
35126
35162
  } })),
35127
35163
  range
35128
35164
  };
35129
35165
  };
35130
- const bumpConfig = fn("bumpConfig")(function* (configPath, pkg, installed) {
35166
+ const bumpConfig = fn("bumpConfig")(function* (configPath, path, installed) {
35131
35167
  const fs = yield* FileSystem;
35132
35168
  const text = yield* fs.readFileString(configPath);
35133
- const result = bumpText(text, pkg, installed);
35169
+ const result = bumpText(text, path, installed);
35134
35170
  if ("reason" in result) return yield* new BumpError({
35135
35171
  path: configPath,
35136
- package: pkg,
35172
+ at: path.join("."),
35137
35173
  reason: result.reason
35138
35174
  });
35139
35175
  yield* fs.writeFileString(configPath, result.text);
35140
35176
  return result.range;
35141
35177
  });
35142
35178
  const Targeting = {
35143
- platforms: optionalKey(NonEmptyArray(Literals$2(["darwin", "linux"]))),
35144
- machines: optionalKey(NonEmptyArray(NonEmptyString))
35179
+ platforms: optionalKey(NonEmptyArray(Literals$2(["darwin", "linux"]))).annotateKey({ description: "`darwin`, `linux`, or both." }),
35180
+ machines: optionalKey(NonEmptyArray(NonEmptyString)).annotateKey({ description: "Machine names from `machines`." })
35145
35181
  };
35146
35182
  const Origin = String$5.check(isPattern(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/, {
35147
35183
  title: "GitHub origin",
@@ -35149,26 +35185,76 @@ const Origin = String$5.check(isPattern(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/, {
35149
35185
  }));
35150
35186
  const Ref = NonEmptyString;
35151
35187
  const isPin = (ref) => /^[0-9a-f]{40}$/.test(ref);
35188
+ const ProviderName = Literals$2([
35189
+ "vp",
35190
+ "brew",
35191
+ "cask",
35192
+ "repos"
35193
+ ]);
35152
35194
  const Range = NonEmptyString.check(makeFilter((range) => range === "latest" || import_semver.validRange(range) !== null, {
35153
35195
  title: "Range",
35154
- description: "a semver range, \"*\", or \"latest\""
35196
+ description: "a semver range (leave inside it, install below it, ask above it), \"*\" (install when missing, never upgrade), or \"latest\" (upgrade to the newest on every Ship)"
35155
35197
  }));
35156
35198
  const TrackedRepo = Struct({
35157
35199
  ...Targeting,
35158
- origin: Origin,
35159
- ref: Ref,
35160
- install: NonEmptyString,
35161
- name: optionalKey(NonEmptyString),
35162
- outputs: optionalKey(ArraySchema(NonEmptyString)),
35163
- files: optionalKey(Record(NonEmptyString, String$5))
35200
+ origin: Origin.annotateKey({ description: "The GitHub `owner/repo`." }),
35201
+ ref: Ref.annotateKey({ description: "A branch, or a full 40-character commit SHA to pin it." }),
35202
+ install: NonEmptyString.annotateKey({ description: "The Install Command, run in a login shell inside the Managed Clone." }),
35203
+ name: optionalKey(NonEmptyString).annotateKey({ description: "Names the clone's directory and the repo in output. Default: `<owner>-<repo>`." }),
35204
+ outputs: optionalKey(ArraySchema(NonEmptyString)).annotateKey({ description: "Paths outside the clone the Install Command produces. If one changes by hand, the repo reinstalls." }),
35205
+ files: optionalKey(Record(NonEmptyString, String$5)).annotateKey({ description: "Gitignored files written into the clone before every install, keyed by path. Never secrets." })
35164
35206
  });
35207
+ const Global = Union([Range, Struct({
35208
+ ...Targeting,
35209
+ range: Range
35210
+ })]);
35211
+ const PACKAGE_MANAGERS = [
35212
+ "npm",
35213
+ "pnpm",
35214
+ "yarn",
35215
+ "bun"
35216
+ ];
35217
+ const NOT_GLOBALS = {
35218
+ vp: "the vp Item is vp itself; set it with vp.version",
35219
+ mode: "the mode Item is vp's managed mode",
35220
+ node: "set Node with vp.node",
35221
+ ...Object.fromEntries(PACKAGE_MANAGERS.map((pm) => [pm, `set ${pm} with vp.${pm}`])),
35222
+ "vite-plus": "set vp with vp.version",
35223
+ fleetfizz: "a Ship installs the fleetfizz that ships"
35224
+ };
35165
35225
  const VpSection = Struct({
35166
35226
  ...Targeting,
35167
- version: optionalKey(Range),
35168
- node: optionalKey(Range),
35169
- globals: optionalKey(Record(NonEmptyString, Range))
35227
+ dependsOn: optionalKey(ArraySchema(ProviderName)).annotateKey({ description: "Providers whose Items must succeed on a Machine before vp changes there." }),
35228
+ version: optionalKey(Range).annotateKey({ description: "The vp version. Pin it exactly." }),
35229
+ node: optionalKey(Range).annotateKey({ description: "The default Node version. Pin it exactly." }),
35230
+ npm: optionalKey(Range).annotateKey({ description: "The default npm version." }),
35231
+ pnpm: optionalKey(Range).annotateKey({ description: "The default pnpm version." }),
35232
+ yarn: optionalKey(Range).annotateKey({ description: "The default Yarn version." }),
35233
+ bun: optionalKey(Range).annotateKey({ description: "The default Bun version." }),
35234
+ globals: optionalKey(Record(NonEmptyString, Global).check(makeFilter((globals) => {
35235
+ const name = Object.keys(globals).find((n) => n in NOT_GLOBALS);
35236
+ return name === void 0 || `"${name}" is not a Global: ${NOT_GLOBALS[name]}`;
35237
+ }, {
35238
+ title: "Globals",
35239
+ description: "npm packages other than vp, node, a package manager, vite-plus, and fleetfizz"
35240
+ }))).annotateKey({ description: "Global packages, each with a Range, or `{ range, platforms?, machines? }` to target one." })
35170
35241
  });
35171
- const MachineSettings = Struct({ clones: optionalKey(NonEmptyString) });
35242
+ const HomebrewRange = Literals$2(["*", "latest"]).annotate({ description: "\"*\" (install when missing, never upgrade) or \"latest\" (brew update, then upgrade on every Ship)" });
35243
+ const HomebrewItems = Record(NonEmptyString, Union([HomebrewRange, Struct({
35244
+ ...Targeting,
35245
+ range: HomebrewRange
35246
+ })]));
35247
+ const homebrewSection = (items, noun) => Struct({
35248
+ ...Targeting,
35249
+ dependsOn: optionalKey(ArraySchema(ProviderName)).annotateKey({ description: "Providers whose Items must succeed on a Machine before this one changes there." }),
35250
+ items: optionalKey(items).annotateKey({ description: `${noun}, each \`"*"\` or \`"latest"\`, or \`{ range, platforms?, machines? }\` to target one. A tapped name (\`owner/tap/name\`) taps on install.` })
35251
+ });
35252
+ const BrewSection = homebrewSection(HomebrewItems.check(makeFilter((items) => !Object.hasOwn(items, "brew") || "\"brew\" is Homebrew itself", {
35253
+ title: "Formulae",
35254
+ description: "Formulae other than brew"
35255
+ })), "Formulae");
35256
+ const CaskSection = homebrewSection(HomebrewItems, "Casks");
35257
+ const MachineSettings = Struct({ clones: optionalKey(NonEmptyString).annotateKey({ description: `Where this Machine keeps its Managed Clones. \`~\` expands on that Machine. Default: \`~/.local/share/fleetfizz/clones\`.` }) });
35172
35258
  const FleetConfig = Struct({
35173
35259
  $schema: optionalKey(String$5),
35174
35260
  shippedAt: optionalKey(String$5),
@@ -35177,9 +35263,11 @@ const FleetConfig = Struct({
35177
35263
  machines: Record(NonEmptyString, MachineSettings).check(makeFilter((machines) => Object.keys(machines).length > 0, {
35178
35264
  title: "Fleet",
35179
35265
  description: "at least one Machine"
35180
- })),
35181
- repos: optionalKey(ArraySchema(TrackedRepo)),
35182
- vp: optionalKey(VpSection)
35266
+ })).annotateKey({ description: "The Fleet, keyed by each Machine's tailnet name. A tailnet member that is not listed is not part of the Fleet." }),
35267
+ repos: optionalKey(ArraySchema(TrackedRepo)).annotateKey({ description: "Each Tracked Repo is cloned on every targeted Machine and installed from its origin ref." }),
35268
+ vp: optionalKey(VpSection).annotateKey({ description: "The vite-plus toolchain." }),
35269
+ brew: optionalKey(BrewSection).annotateKey({ description: "Homebrew Formulae. Needs Homebrew on the Machine; a Ship never installs it." }),
35270
+ cask: optionalKey(CaskSection).annotateKey({ description: "Homebrew Casks, macOS only. Needs Homebrew on the Machine." })
35183
35271
  });
35184
35272
  const repoName = (repo) => repo.name ?? repo.origin.replace("/", "-");
35185
35273
  const SELF_PACKAGE = "fleetfizz";
@@ -35308,8 +35396,10 @@ const vpEnv = [
35308
35396
  ].join("\n");
35309
35397
  const prelude = [
35310
35398
  vpEnv,
35399
+ `export PATH="$PATH:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin"`,
35311
35400
  `ff_json() { printf '%s' "$1" | sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g' | tr -d '\\n'; }`,
35312
- `ff_state() { printf 'ff-state %s\\n' "$1"; }`
35401
+ `ff_state() { printf 'ff-state %s\\n' "$1"; }`,
35402
+ `ff_at() { ff_state "{\\"provider\\":\\"$(ff_json "$1")\\",\\"fact\\":\\"at\\",\\"item\\":\\"$(ff_json "$2")\\",\\"at\\":\\"$(ff_json "$3")\\"}"; }`
35313
35403
  ].join("\n");
35314
35404
  const fingerprintFn = [
35315
35405
  "ff_fp() {",
@@ -35324,470 +35414,52 @@ const fingerprintFn = [
35324
35414
  const STATE_DIR = "~/.local/state/fleetfizz";
35325
35415
  const RECORDS_DIR = `${STATE_DIR}/records`;
35326
35416
  const CONFIG_DIR_SH = "\"${XDG_CONFIG_HOME:-$HOME/.config}/fleetfizz\"";
35417
+ const recordDir = (provider) => `${quotePath(RECORDS_DIR)}/${quote(provider)}`;
35418
+ const recordFile = (provider, item) => `${recordDir(provider)}/${quote(`${item}.json`)}`;
35419
+ const LEGACY_RECORD_PROVIDER = "repos";
35420
+ const recordFact = (provider, item) => [
35421
+ `ff_r=${recordFile(provider, item)}`,
35422
+ ...provider === LEGACY_RECORD_PROVIDER ? [`[ -f "$ff_r" ] || ff_r=${quotePath(RECORDS_DIR)}/${quote(`${item}.json`)}`] : [],
35423
+ `if [ -f "$ff_r" ]; then ff_state "{\\"fact\\":\\"record\\",\\"provider\\":\\"${provider}\\",\\"item\\":\\"$(ff_json ${quote(item)})\\",\\"record\\":$(tr -d '\\n' < "$ff_r")}"; fi`
35424
+ ];
35327
35425
  const checkScript = (input) => {
35328
35426
  const lines = [
35329
35427
  prelude,
35330
35428
  fingerprintFn,
35331
- `ff_state "{\\"fact\\":\\"home\\",\\"path\\":\\"$(ff_json "$HOME")\\"}"`,
35332
- "if command -v vp >/dev/null 2>&1; then",
35333
- ` ff_v=$(command vp --version 2>/dev/null | sed -n 's/^vp v\\{0,1\\}\\([^ ]*\\).*/\\1/p' | head -n 1)`,
35334
- ` ff_state "{\\"fact\\":\\"vp\\",\\"version\\":\\"$(ff_json "$ff_v")\\",\\"path\\":\\"$(ff_json "$(command -v vp)")\\"}"`,
35335
- ` ff_n=$(command vp env default 2>/dev/null | sed -n 's/^Default Node.js version: //p' | head -n 1)`,
35336
- ` if [ -n "$ff_n" ]; then ff_state "{\\"fact\\":\\"node\\",\\"version\\":\\"$(ff_json "$ff_n")\\"}"; else ff_state '{"fact":"node","version":null}'; fi`,
35337
- ` ff_g=$(command vp list --global --json 2>/dev/null | tr -d '\\n')`,
35338
- ` ff_state "{\\"fact\\":\\"globals\\",\\"list\\":\${ff_g:-[]}}"`,
35339
- "else",
35340
- ` ff_state '{"fact":"vp","version":null}'`,
35341
- "fi",
35342
- `ff_root=${quotePath(input.cloneRoot)}`,
35343
- `if [ -d "$ff_root" ]; then ff_present=true; else ff_present=false; fi`,
35344
- `ff_state "{\\"fact\\":\\"cloneRoot\\",\\"path\\":\\"$(ff_json "$ff_root")\\",\\"present\\":$ff_present}"`
35429
+ `ff_state "{\\"fact\\":\\"home\\",\\"path\\":\\"$(ff_json "$HOME")\\"}"`
35345
35430
  ];
35346
- for (const repo of input.repos) {
35347
- const name = quote(repo.name);
35348
- lines.push(`ff_d="$ff_root"/${name}`, `if [ -d "$ff_d/.git" ]; then`, ` ff_state "{\\"fact\\":\\"repo\\",\\"name\\":\\"$(ff_json ${name})\\",\\"present\\":true,\\"head\\":\\"$(ff_json "$(git -C "$ff_d" rev-parse HEAD 2>/dev/null)")\\",\\"origin\\":\\"$(ff_json "$(git -C "$ff_d" remote get-url origin 2>/dev/null)")\\"}"`, "else", ` ff_state "{\\"fact\\":\\"repo\\",\\"name\\":\\"$(ff_json ${name})\\",\\"present\\":false}"`, "fi");
35349
- for (const output of repo.outputs) lines.push(`ff_p=${quotePath(output)}; ff_f=$(ff_fp "$ff_p")`, `if [ -n "$ff_f" ]; then ff_state "{\\"fact\\":\\"output\\",\\"repo\\":\\"$(ff_json ${name})\\",\\"path\\":\\"$(ff_json ${quote(output)})\\",\\"fingerprint\\":\\"$(ff_json "$ff_f")\\"}"; else ff_state "{\\"fact\\":\\"output\\",\\"repo\\":\\"$(ff_json ${name})\\",\\"path\\":\\"$(ff_json ${quote(output)})\\",\\"fingerprint\\":null}"; fi`);
35350
- }
35351
- for (const repo of input.repos) {
35352
- const name = quote(repo.name);
35353
- lines.push(`ff_r=${quotePath(RECORDS_DIR)}/${quote(`${repo.name}.json`)}`, `if [ -f "$ff_r" ]; then ff_state "{\\"fact\\":\\"record\\",\\"name\\":\\"$(ff_json ${name})\\",\\"record\\":$(tr -d '\\n' < "$ff_r")}"; fi`);
35431
+ for (const { provider, items } of input.providers) {
35432
+ lines.push(provider.observe(items, input.scope));
35433
+ if (provider.rule === "record") for (const item of items) lines.push(...recordFact(provider.name, item.name));
35354
35434
  }
35355
35435
  lines.push(`ff_c=${CONFIG_DIR_SH}/${quote(CONFIG_FILE)}`, `if [ -f "$ff_c" ]; then ff_state "{\\"fact\\":\\"config\\",\\"present\\":true,\\"base64\\":\\"$(base64 < "$ff_c" | tr -d '\\n')\\"}"; else ff_state '{"fact":"config","present":false}'; fi`);
35356
35436
  lines.push(`ff_state '{"fact":"end"}'`);
35357
35437
  return lines.join("\n");
35358
35438
  };
35359
35439
  //#endregion
35360
- //#region ../../packages/core/src/range.ts
35361
- const newest$1 = (available) => available.filter((v) => import_semver.prerelease(v) === null).sort(import_semver.rcompare)[0];
35362
- const decideRange = (range, installed, available) => {
35363
- if (range === "latest") {
35364
- const version = newest$1(available);
35365
- if (version === void 0) return { kind: "unavailable" };
35366
- return installed === version ? { kind: "leave" } : {
35367
- kind: "install",
35368
- version
35369
- };
35370
- }
35371
- if (range === "*") {
35372
- if (installed !== void 0) return { kind: "leave" };
35373
- const version = newest$1(available);
35374
- return version === void 0 ? { kind: "unavailable" } : {
35375
- kind: "install",
35376
- version
35377
- };
35378
- }
35379
- if (installed !== void 0 && import_semver.satisfies(installed, range)) return { kind: "leave" };
35380
- if (installed !== void 0 && import_semver.gtr(installed, range)) return {
35381
- kind: "ask",
35382
- installed
35383
- };
35384
- const version = import_semver.maxSatisfying([...available], range);
35385
- return version === null ? { kind: "unavailable" } : {
35386
- kind: "install",
35387
- version
35388
- };
35389
- };
35390
- //#endregion
35391
- //#region ../../packages/core/src/record.ts
35392
- const sameRecord = (a, b) => Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([k, v]) => b[k] === v);
35393
- const decideInstall = (record, now, rebuild) => {
35394
- if (rebuild) return {
35395
- run: true,
35396
- reason: "--rebuild"
35397
- };
35398
- if (record === void 0) return {
35399
- run: true,
35400
- reason: "never installed"
35401
- };
35402
- if (record.commit !== now.commit) return {
35403
- run: true,
35404
- reason: `new commit ${now.commit.slice(0, 7)}`
35405
- };
35406
- if (record.path !== now.path) return {
35407
- run: true,
35408
- reason: "clone moved"
35409
- };
35410
- if (record.install !== now.install) return {
35411
- run: true,
35412
- reason: "install command changed"
35413
- };
35414
- if (!sameRecord(record.files, now.files)) return {
35415
- run: true,
35416
- reason: "files changed"
35417
- };
35418
- for (const [path, fingerprint] of Object.entries(now.outputs)) if (fingerprint === void 0 || record.outputs[path] !== fingerprint) return {
35419
- run: true,
35420
- reason: `drift: ${path}`
35421
- };
35422
- return { run: false };
35423
- };
35424
- //#endregion
35425
- //#region ../../packages/core/src/targeting.ts
35426
- const targets = (targeting, machine) => (targeting.platforms?.includes(machine.platform) ?? true) && (targeting.machines?.includes(machine.name) ?? true);
35427
- //#endregion
35428
- //#region ../../packages/core/src/plan.ts
35429
- const VP_PACKAGE = "vite-plus";
35430
- const NODE_PACKAGE = "node";
35431
- const expandHome = (path, home) => path === "~" ? home : path.startsWith("~/") ? `${home}${path.slice(1)}` : path;
35432
- const isHomebrew = (path) => path !== void 0 && (path.includes("/homebrew/") || path.includes("/Cellar/"));
35433
- const planVp = (input) => {
35434
- const out = {
35435
- steps: [],
35436
- questions: [],
35437
- skipped: [],
35438
- failures: []
35439
- };
35440
- const { state, machine } = input;
35441
- const vp = input.config.vp;
35442
- const targeted = vp !== void 0 && targets(vp, machine);
35443
- const missing = state.vp === void 0 || state.vp.version === null;
35444
- if (missing) out.steps.push({ kind: "bootstrap" });
35445
- const installed = missing ? void 0 : state.vp?.version ?? void 0;
35446
- const decide = (pkg, range, current, shown = pkg) => {
35447
- if (range === void 0) return void 0;
35448
- const decision = decideRange(range, current, input.versions[pkg] ?? []);
35449
- switch (decision.kind) {
35450
- case "leave": return;
35451
- case "install": return decision.version;
35452
- case "unavailable":
35453
- out.failures.push({
35454
- what: shown,
35455
- reason: `no published version satisfies ${range}`
35456
- });
35457
- return;
35458
- case "ask": {
35459
- const answer = input.answers[pkg];
35460
- if (answer === "downgrade") {
35461
- const target = decideRange(range, void 0, input.versions[pkg] ?? []);
35462
- if (target.kind === "install") return target.version;
35463
- out.failures.push({
35464
- what: shown,
35465
- reason: `no published version satisfies ${range}`
35466
- });
35467
- return;
35468
- }
35469
- if (answer === "skip") {
35470
- out.skipped.push({
35471
- what: shown,
35472
- reason: `${decision.installed} is above ${range}`
35473
- });
35474
- return;
35475
- }
35476
- out.questions.push({
35477
- machine: machine.name,
35478
- package: pkg,
35479
- installed: decision.installed,
35480
- range
35481
- });
35482
- return;
35483
- }
35484
- }
35485
- };
35486
- const nodeNow = missing ? void 0 : state.node ?? void 0;
35487
- let nodeTarget;
35488
- if (targeted) {
35489
- const vpTarget = decide(VP_PACKAGE, vp.version, installed, "vp");
35490
- if (vpTarget !== void 0) {
35491
- if (isHomebrew(state.vp?.path)) out.failures.push({
35492
- what: "vp",
35493
- reason: `vp ${installed} was installed with Homebrew; run brew upgrade vite-plus to get ${vpTarget}`
35494
- });
35495
- else out.steps.push({
35496
- kind: "vp",
35497
- from: installed ?? null,
35498
- version: vpTarget
35499
- });
35500
- }
35501
- nodeTarget = decide(NODE_PACKAGE, vp.node, nodeNow, "node");
35502
- if (nodeTarget !== void 0) out.steps.push({
35503
- kind: "node",
35504
- from: nodeNow ?? null,
35505
- version: nodeTarget
35506
- });
35507
- }
35508
- const nodeDefault = nodeTarget ?? nodeNow;
35509
- const byName = new Map((missing ? [] : state.globals).map((g) => [g.name, g]));
35510
- const installs = [];
35511
- const wanted = Object.entries(targeted ? vp.globals ?? {} : {});
35512
- for (const [name, range] of [...wanted, [SELF_PACKAGE, input.self]]) {
35513
- const current = byName.get(name);
35514
- const version = name === "fleetfizz" ? decideSelf(current?.version, input.self, out) : decide(name, range, current?.version);
35515
- if (version !== void 0) installs.push(current === void 0 ? {
35516
- name,
35517
- version
35518
- } : {
35519
- name,
35520
- from: current.version,
35521
- version
35522
- });
35523
- else if (current !== void 0 && nodeDefault !== void 0 && current.platform?.node !== void 0 && current.platform.node !== nodeDefault) out.steps.push({
35524
- kind: "reinstall",
35525
- name,
35526
- version: current.version
35527
- });
35528
- }
35529
- if (installs.length > 0) out.steps.push({
35530
- kind: "globals",
35531
- packages: installs
35532
- });
35533
- return out;
35534
- };
35535
- const decideSelf = (installed, self, out) => {
35536
- if (installed === void 0 || import_semver.lt(installed, self)) return self;
35537
- if (import_semver.gt(installed, self)) out.failures.push({
35538
- what: SELF_PACKAGE,
35539
- reason: `runs ${SELF_PACKAGE} ${installed}, newer than this ${self}; upgrade here first`
35540
- });
35541
- };
35542
- const planRepos = (input) => {
35543
- const repos = [];
35544
- const failures = [];
35545
- const { config, machine, state } = input;
35546
- const home = state.home ?? "~";
35547
- const root = config.machines?.[machine.name]?.clones ?? "~/.local/share/fleetfizz/clones";
35548
- for (const repo of config.repos ?? []) {
35549
- if (!targets(repo, machine)) continue;
35550
- const name = repoName(repo);
35551
- if (state.cloneRoot !== void 0 && !state.cloneRoot.present && !root.startsWith("~")) {
35552
- failures.push({
35553
- what: name,
35554
- reason: `clone root not mounted: ${state.cloneRoot.path}`
35555
- });
35556
- continue;
35557
- }
35558
- const current = state.repos[name];
35559
- const expectedOrigin = `https://github.com/${repo.origin}.git`;
35560
- if (current?.present && current.origin !== void 0 && current.origin !== expectedOrigin) {
35561
- failures.push({
35562
- what: name,
35563
- reason: `managed clone has origin ${current.origin}, expected ${expectedOrigin}`
35564
- });
35565
- continue;
35566
- }
35567
- const path = `${root}/${name}`;
35568
- const expanded = expandHome(path, home);
35569
- const files = repo.files ?? {};
35570
- const outputs = repo.outputs ?? [];
35571
- const rebuild = input.rebuild === "all" || input.rebuild !== "none" && input.rebuild.includes(name);
35572
- const decision = decideInstall(state.records[name], {
35573
- commit: current?.head ?? "",
35574
- path: expanded,
35575
- install: repo.install,
35576
- files,
35577
- outputs: Object.fromEntries(outputs.map((o) => [o, state.outputs[name]?.[o] ?? void 0]))
35578
- }, rebuild);
35579
- repos.push({
35580
- name,
35581
- origin: repo.origin,
35582
- ref: repo.ref,
35583
- pin: isPin(repo.ref),
35584
- path,
35585
- expanded,
35586
- ...current?.head === void 0 ? {} : { head: current.head },
35587
- install: repo.install,
35588
- files,
35589
- outputs,
35590
- decision
35591
- });
35592
- }
35593
- return {
35594
- repos,
35595
- failures
35596
- };
35597
- };
35598
- const plan = (input) => {
35599
- const vp = planVp(input);
35600
- const repos = planRepos(input);
35601
- return {
35602
- machine: input.machine,
35603
- vp: vp.steps,
35604
- repos: repos.repos,
35605
- questions: vp.questions,
35606
- skipped: vp.skipped,
35607
- failures: [...vp.failures.map((f) => ({
35608
- ...f,
35609
- section: "vp"
35610
- })), ...repos.failures.map((f) => ({
35611
- ...f,
35612
- section: "repos"
35613
- }))]
35614
- };
35615
- };
35616
- //#endregion
35617
- //#region ../../packages/core/src/state.ts
35618
- const GlobalEntry = Struct({
35619
- name: String$5,
35620
- version: String$5,
35621
- platform: optionalKey(Struct({ node: optionalKey(String$5) }))
35622
- });
35623
- const InstallRecordSchema = Struct({
35624
- commit: String$5,
35625
- path: String$5,
35626
- install: String$5,
35627
- files: Record(String$5, String$5),
35628
- outputs: Record(String$5, String$5)
35629
- });
35630
- const Fact = Union([
35631
- Struct({
35632
- fact: Literal("home"),
35633
- path: String$5
35634
- }),
35635
- Struct({ fact: Literal("end") }),
35636
- Struct({
35637
- fact: Literal("vp"),
35638
- version: NullOr(String$5),
35639
- path: optionalKey(String$5)
35640
- }),
35641
- Struct({
35642
- fact: Literal("node"),
35643
- version: NullOr(String$5)
35644
- }),
35645
- Struct({
35646
- fact: Literal("globals"),
35647
- list: ArraySchema(GlobalEntry)
35648
- }),
35649
- Struct({
35650
- fact: Literal("cloneRoot"),
35651
- path: String$5,
35652
- present: Boolean$4
35653
- }),
35654
- Struct({
35655
- fact: Literal("repo"),
35656
- name: String$5,
35657
- present: Boolean$4,
35658
- head: optionalKey(String$5),
35659
- origin: optionalKey(String$5)
35660
- }),
35661
- Struct({
35662
- fact: Literal("record"),
35663
- name: String$5,
35664
- record: InstallRecordSchema
35665
- }),
35666
- Struct({
35667
- fact: Literal("config"),
35668
- present: Boolean$4,
35669
- base64: optionalKey(String$5)
35670
- }),
35671
- Struct({
35672
- fact: Literal("output"),
35673
- repo: String$5,
35674
- path: String$5,
35675
- fingerprint: NullOr(String$5)
35676
- })
35677
- ]);
35678
- const emptyState = {
35679
- complete: false,
35680
- globals: [],
35681
- repos: {},
35682
- records: {},
35683
- outputs: {}
35684
- };
35685
- const decodeBase64 = (base64) => new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
35686
- const assembleState = (facts) => {
35687
- let state = emptyState;
35688
- for (const fact of facts) switch (fact.fact) {
35689
- case "home":
35690
- state = {
35691
- ...state,
35692
- home: fact.path
35693
- };
35694
- break;
35695
- case "end":
35696
- state = {
35697
- ...state,
35698
- complete: true
35699
- };
35700
- break;
35701
- case "vp":
35702
- state = {
35703
- ...state,
35704
- vp: fact.path === void 0 ? { version: fact.version } : {
35705
- version: fact.version,
35706
- path: fact.path
35707
- }
35708
- };
35709
- break;
35710
- case "node":
35711
- state = {
35712
- ...state,
35713
- node: fact.version
35714
- };
35715
- break;
35716
- case "globals":
35717
- state = {
35718
- ...state,
35719
- globals: fact.list
35720
- };
35721
- break;
35722
- case "cloneRoot":
35723
- state = {
35724
- ...state,
35725
- cloneRoot: {
35726
- path: fact.path,
35727
- present: fact.present
35728
- }
35729
- };
35730
- break;
35731
- case "repo": {
35732
- const repo = {
35733
- present: fact.present,
35734
- ...fact.head === void 0 ? {} : { head: fact.head },
35735
- ...fact.origin === void 0 ? {} : { origin: fact.origin }
35736
- };
35737
- state = {
35738
- ...state,
35739
- repos: {
35740
- ...state.repos,
35741
- [fact.name]: repo
35742
- }
35743
- };
35744
- break;
35745
- }
35746
- case "record":
35747
- state = {
35748
- ...state,
35749
- records: {
35750
- ...state.records,
35751
- [fact.name]: fact.record
35752
- }
35753
- };
35754
- break;
35755
- case "config":
35756
- if (fact.present && fact.base64 !== void 0) state = {
35757
- ...state,
35758
- config: decodeBase64(fact.base64)
35759
- };
35760
- break;
35761
- case "output": state = {
35762
- ...state,
35763
- outputs: {
35764
- ...state.outputs,
35765
- [fact.repo]: {
35766
- ...state.outputs[fact.repo],
35767
- [fact.path]: fact.fingerprint
35768
- }
35769
- }
35770
- };
35771
- }
35772
- return state;
35773
- };
35774
- //#endregion
35775
35440
  //#region ../../packages/remote/src/protocol.ts
35441
+ const Fact = Record(String$5, Unknown);
35776
35442
  const StepStart = Struct({ id: String$5 });
35777
35443
  const StepDone = Struct({
35778
35444
  id: String$5,
35779
35445
  ok: Boolean$4,
35780
35446
  code: optionalKey(Number$2)
35781
35447
  });
35782
- const decodeFact = decodeUnknownResult(fromJsonString(Fact));
35448
+ const StepSkip = Struct({
35449
+ id: String$5,
35450
+ reason: String$5
35451
+ });
35452
+ const decodeFact$3 = decodeUnknownResult(fromJsonString(Fact));
35783
35453
  const decodeStart = decodeUnknownResult(fromJsonString(StepStart));
35784
35454
  const decodeDone = decodeUnknownResult(fromJsonString(StepDone));
35455
+ const decodeSkip = decodeUnknownResult(fromJsonString(StepSkip));
35785
35456
  const STATE = "ff-state ";
35786
35457
  const STEP = "ff-step ";
35787
35458
  const DONE = "ff-done ";
35459
+ const SKIP = "ff-skip ";
35788
35460
  const parseLine = (line) => {
35789
35461
  if (line.startsWith(STATE)) {
35790
- const result = decodeFact(line.slice(9));
35462
+ const result = decodeFact$3(line.slice(9));
35791
35463
  if (result._tag === "Success") return {
35792
35464
  kind: "state",
35793
35465
  fact: result.success
@@ -35804,6 +35476,12 @@ const parseLine = (line) => {
35804
35476
  kind: "done",
35805
35477
  done: result.success
35806
35478
  };
35479
+ } else if (line.startsWith(SKIP)) {
35480
+ const result = decodeSkip(line.slice(8));
35481
+ if (result._tag === "Success") return {
35482
+ kind: "skip",
35483
+ skip: result.success
35484
+ };
35807
35485
  }
35808
35486
  return {
35809
35487
  kind: "text",
@@ -35812,95 +35490,48 @@ const parseLine = (line) => {
35812
35490
  };
35813
35491
  const stepLine = (id) => `printf 'ff-step {"id":"%s"}\\n' '${id}'`;
35814
35492
  const doneLine = (id, ok, code) => code === void 0 ? `printf 'ff-done {"id":"%s","ok":${ok}}\\n' '${id}'` : `printf 'ff-done {"id":"%s","ok":${ok},"code":%s}\\n' '${id}' "${code}"`;
35493
+ const skipLine = (id, reason) => `printf 'ff-skip {"id":"%s","reason":"%s"}\\n' '${id}' ${reason}`;
35815
35494
  //#endregion
35816
35495
  //#region ../../packages/remote/src/apply.ts
35817
- const jsonString = encodeSync(fromJsonString(String$5));
35818
- const jsonRecord = encodeSync(fromJsonString(Record(String$5, String$5)));
35819
- const stepId = (repo, step) => `repo:${repo}:${step}`;
35820
- const isFetchStep = (id) => id.startsWith("repo:") && id.endsWith(":fetch");
35821
- const vpStepId = (step) => step.kind === "reinstall" ? `vp:reinstall:${step.name}` : step.kind === "globals" ? "vp:globals" : `vp:${step.kind}`;
35822
35496
  const CONFIG_STEP = "config";
35823
- const VP_INSTALL = [
35824
- "curl -fsSL https://vite.plus | bash",
35825
- ...vpEnv.split("\n"),
35826
- "{ hash -r 2>/dev/null || true; }"
35827
- ].join(" && ");
35828
- const marked = (id, run, then = []) => [
35497
+ const marked = (id, run, failed = []) => [
35829
35498
  stepLine(id),
35830
35499
  ...run,
35831
35500
  `ff_code=$?`,
35832
- `if [ "$ff_code" -ne 0 ]; then ${doneLine(id, false, "$ff_code")}; else`,
35501
+ `if [ "$ff_code" -ne 0 ]; then`,
35502
+ doneLine(id, false, "$ff_code"),
35503
+ ...failed,
35504
+ "else",
35833
35505
  doneLine(id, true),
35834
- ...then,
35835
35506
  "fi"
35836
35507
  ];
35837
- const vpStep = (step) => {
35838
- const id = vpStepId(step);
35839
- const command = (() => {
35840
- switch (step.kind) {
35841
- case "bootstrap": return VP_INSTALL;
35842
- case "vp": return `command vp upgrade ${quote(step.version)}`;
35843
- case "node": return `command vp env default ${quote(step.version)}`;
35844
- case "reinstall": return `command vp install -g ${quote(`${step.name}@${step.version}`)}`;
35845
- case "globals": return `command vp install -g ${step.packages.map((p) => quote(`${p.name}@${p.version}`)).join(" ")}`;
35846
- }
35847
- })();
35848
- return marked(id, [step.kind === "bootstrap" ? command : `( ${command} )`]);
35849
- };
35850
35508
  const configStep = () => {
35851
35509
  const dir = CONFIG_DIR_SH;
35852
35510
  const write = (variable, file) => `printf '%s' "$${variable}" | base64 -d > ${dir}/${quote(`${file}.tmp`)} && mv -f ${dir}/${quote(`${file}.tmp`)} ${dir}/${quote(file)}`;
35853
35511
  return marked(CONFIG_STEP, [`( mkdir -p ${dir} && ${write("ff_config", CONFIG_FILE)} && ${write("ff_schema", SCHEMA_FILE)} )`]);
35854
35512
  };
35855
- const writeRecord = (repo) => {
35856
- return [
35857
- `ff_outs=""`,
35858
- ...repo.outputs.map((o) => `ff_fpv=$(ff_fp ${quotePath(o)}); if [ -n "$ff_fpv" ]; then ff_outs="$ff_outs,\\"$(ff_json ${quote(o)})\\":\\"$(ff_json "$ff_fpv")\\""; fi`),
35859
- `mkdir -p ${quotePath(RECORDS_DIR)}`,
35860
- `printf '%s\\n' "{\\"commit\\":\\"$ff_after\\",\\"path\\":\\"$(ff_json "$ff_d")\\",\\"install\\":$(ff_lit ${quote(jsonString(repo.install))}),\\"files\\":$(ff_lit ${quote(jsonRecord(repo.files))}),\\"outputs\\":{\${ff_outs#,}}}" > ${quotePath(RECORDS_DIR)}/${quote(`${repo.name}.json.tmp`)}`,
35861
- `mv -f ${quotePath(RECORDS_DIR)}/${quote(`${repo.name}.json.tmp`)} ${quotePath(RECORDS_DIR)}/${quote(`${repo.name}.json`)}`
35513
+ const providerSteps = (plan) => {
35514
+ if (plan.decisions.length === 0 && plan.steps.length === 0) return [];
35515
+ const held = `ff_held_${plan.name}`;
35516
+ const items = [...new Set(plan.steps.flatMap((step) => step.items))];
35517
+ const flag = (item) => `ff_f_${plan.name}_${items.indexOf(item)}`;
35518
+ const step = (s) => [
35519
+ `if [ -n "${s.items.map((item) => `\${${flag(item)}:-}`).join("")}" ]; then`,
35520
+ skipLine(s.id, "'an earlier step failed'"),
35521
+ "else",
35522
+ ...marked(s.id, s.run, [...s.items.map((item) => `${flag(item)}=1`), `${held}=1`]),
35523
+ "fi"
35862
35524
  ];
35863
- };
35864
- const writeFiles = (repo) => Object.entries(repo.files).flatMap(([path, content]) => [`mkdir -p "$(dirname "$ff_d"/${quote(path)})"`, `printf '%s' ${quote(content)} > "$ff_d"/${quote(path)}`]);
35865
- const repoSteps = (repo, input) => {
35866
- const fetch = stepId(repo.name, "fetch");
35867
- const install = stepId(repo.name, "install");
35868
- const url = `https://github.com/${repo.origin}.git`;
35869
- const sync = repo.pin ? [
35870
- `if [ "$(git -C "$ff_d" rev-parse HEAD)" != ${quote(repo.ref)} ]; then ff_git -C "$ff_d" fetch --quiet origin ${quote(repo.ref)}; fi`,
35871
- `git -C "$ff_d" checkout --quiet --detach ${quote(repo.ref)}`,
35872
- `git -C "$ff_d" reset --quiet --hard ${quote(repo.ref)}`
35873
- ] : [`ff_git -C "$ff_d" fetch --quiet origin ${quote(repo.ref)}`, `git -C "$ff_d" reset --quiet --hard ${quote(`origin/${repo.ref}`)}`];
35874
- const envPrefix = `FLEETFIZZ_MACHINE=${quote(input.env.machine)} FLEETFIZZ_PLATFORM=${quote(input.env.platform)} FLEETFIZZ_MACHINES=${quote(input.env.machines.join(","))}`;
35525
+ const why = [`ff_why=""`, ...plan.dependsOn.map((dep) => `if [ -z "$ff_why" ] && [ -n "\${ff_held_${dep}:-}" ]; then ff_why=${quote(dep)}; fi`)];
35526
+ if (plan.steps.length === 0) return [...why, `if [ -n "$ff_why" ]; then ${held}=1; fi`];
35875
35527
  return [
35876
- `ff_d="$ff_root"/${quote(repo.name)}`,
35877
- `ff_before=$(git -C "$ff_d" rev-parse HEAD 2>/dev/null || true)`,
35878
- ...marked(fetch, [
35879
- "(",
35880
- " set -e",
35881
- ` if [ ! -d "$ff_d/.git" ]; then ff_git clone --quiet ${quote(url)} "$ff_d"; fi`,
35882
- ` if [ "$(git -C "$ff_d" remote get-url origin)" != ${quote(url)} ]; then echo "origin is $(git -C "$ff_d" remote get-url origin), expected ${url}"; exit 1; fi`,
35883
- ...sync.map((l) => ` ${l}`),
35884
- ` git -C "$ff_d" clean --quiet -fd`,
35885
- ...writeFiles(repo).map((l) => ` ${l}`),
35886
- ")"
35887
- ], [
35888
- `ff_after=$(git -C "$ff_d" rev-parse HEAD)`,
35889
- `ff_state "{\\"fact\\":\\"repo\\",\\"name\\":\\"$(ff_json ${quote(repo.name)})\\",\\"present\\":true,\\"head\\":\\"$(ff_json "$ff_after")\\"}"`,
35890
- `if [ ${repo.decision.run ? "true" : "false"} = true ] || [ "$ff_after" != "$ff_before" ]; then`,
35891
- ` ${stepLine(install)}`,
35892
- ` (cd "$ff_d" && ${envPrefix} "\${SHELL:-/bin/sh}" -lc ${quote(repo.install)})`,
35893
- ` ff_code=$?`,
35894
- ` if [ "$ff_code" -ne 0 ]; then ${doneLine(install, false, "$ff_code")}; else`,
35895
- " (",
35896
- " set -e",
35897
- ...writeRecord(repo).map((l) => ` ${l}`),
35898
- " )",
35899
- ` ff_code=$?`,
35900
- ` if [ "$ff_code" -ne 0 ]; then echo "could not write the install record"; ${doneLine(install, false, "$ff_code")}; else ${doneLine(install, true)}; fi`,
35901
- " fi",
35902
- "fi"
35903
- ])
35528
+ ...why,
35529
+ `if [ -n "$ff_why" ]; then`,
35530
+ `${held}=1`,
35531
+ ...plan.steps.map((s) => skipLine(s.id, `"depends on $ff_why"`)),
35532
+ "else",
35533
+ ...plan.steps.flatMap(step),
35534
+ "fi"
35904
35535
  ];
35905
35536
  };
35906
35537
  const applyStdin = (token, config, schema) => [
@@ -35914,24 +35545,18 @@ const encodeBase64 = (text) => {
35914
35545
  for (const byte of bytes) binary += String.fromCharCode(byte);
35915
35546
  return btoa(binary);
35916
35547
  };
35917
- const applyScript = (input) => {
35918
- const lines = [
35919
- prelude,
35920
- fingerprintFn,
35921
- `ff_lit() { printf '%s' "$1"; }`,
35922
- `IFS= read -r ff_token || ff_token=""`,
35923
- `IFS= read -r ff_config || ff_config=""`,
35924
- `IFS= read -r ff_schema || ff_schema=""`,
35925
- `ff_git() { if [ -n "$ff_token" ]; then FF_TOKEN="$ff_token" git -c credential.helper= -c 'credential.helper=!f() { echo username=x-access-token; echo "password=$FF_TOKEN"; }; f' "$@"; else git "$@"; fi; }`,
35926
- `ff_root=${quotePath(input.cloneRoot)}`,
35927
- ...input.cloneRoot.startsWith("~") ? [`mkdir -p "$ff_root"`] : []
35928
- ];
35929
- lines.push(...configStep());
35930
- for (const step of input.plan.vp) lines.push(...vpStep(step));
35931
- for (const repo of input.plan.repos) lines.push(...repoSteps(repo, input));
35932
- lines.push(doneLine("end", true));
35933
- return lines.join("\n");
35934
- };
35548
+ const applyScript = (plan) => [
35549
+ prelude,
35550
+ fingerprintFn,
35551
+ `ff_lit() { printf '%s' "$1"; }`,
35552
+ `IFS= read -r ff_token || ff_token=""`,
35553
+ `IFS= read -r ff_config || ff_config=""`,
35554
+ `IFS= read -r ff_schema || ff_schema=""`,
35555
+ `ff_git() { if [ -n "$ff_token" ]; then FF_TOKEN="$ff_token" git -c credential.helper= -c 'credential.helper=!f() { echo username=x-access-token; echo "password=$FF_TOKEN"; }; f' "$@"; else git "$@"; fi; }`,
35556
+ ...configStep(),
35557
+ ...plan.providers.flatMap(providerSteps),
35558
+ doneLine("end", true)
35559
+ ].join("\n");
35935
35560
  //#endregion
35936
35561
  //#region ../../packages/remote/src/tailnet.ts
35937
35562
  const StatusNode = Struct({
@@ -64199,6 +63824,333 @@ const useWindowSize = () => {
64199
63824
  return size;
64200
63825
  };
64201
63826
  //#endregion
63827
+ //#region ../../packages/core/src/targeting.ts
63828
+ const targets = (targeting, machine) => (targeting.platforms?.includes(machine.platform) ?? true) && (targeting.machines?.includes(machine.name) ?? true);
63829
+ //#endregion
63830
+ //#region ../../packages/core/src/engine.ts
63831
+ const isObjectSection = (section) => !Array.isArray(section);
63832
+ const sectionTargeting = (section) => isObjectSection(section) ? section : {};
63833
+ const dependenciesOf = (provider, config) => {
63834
+ const section = config[provider.name];
63835
+ const extra = section !== void 0 && isObjectSection(section) ? section.dependsOn ?? [] : [];
63836
+ return [.../* @__PURE__ */ new Set([...provider.dependsOn, ...extra])];
63837
+ };
63838
+ const targetedItems = (provider, config, machine) => {
63839
+ const section = config[provider.name];
63840
+ const scoped = section !== void 0 && targets(sectionTargeting(section), machine) ? section : void 0;
63841
+ return provider.items(scoped).filter((item) => targets(item, machine));
63842
+ };
63843
+ const machinesNamed = (provider, config) => {
63844
+ const section = config[provider.name];
63845
+ return [...section === void 0 ? [] : sectionTargeting(section).machines ?? [], ...provider.items(section).flatMap((item) => item.machines ?? [])];
63846
+ };
63847
+ const hold = (decisions, by) => decisions.map((d) => d.kind === "change" || d.kind === "ask" ? {
63848
+ kind: "skip",
63849
+ item: d.item,
63850
+ ...d.kind === "change" && d.from !== void 0 ? { at: d.from } : {},
63851
+ ...d.kind === "ask" && d.at !== void 0 ? { at: d.at } : {},
63852
+ reason: `depends on ${by}`
63853
+ } : d);
63854
+ const planMachine = (input) => {
63855
+ const ctx = {
63856
+ ...input.ctx,
63857
+ home: input.state.home ?? "~"
63858
+ };
63859
+ const plans = /* @__PURE__ */ new Map();
63860
+ const held = /* @__PURE__ */ new Set();
63861
+ for (const provider of input.providers) {
63862
+ const dependsOn = dependenciesOf(provider, ctx.config);
63863
+ const items = targetedItems(provider, ctx.config, ctx.machine);
63864
+ const decisions = provider.decide({
63865
+ items,
63866
+ observed: provider.parse(input.state.facts[provider.name] ?? [], input.state.records[provider.name] ?? {}),
63867
+ resolved: input.resolved[provider.name],
63868
+ ctx
63869
+ });
63870
+ const by = dependsOn.find((name) => {
63871
+ const dep = plans.get(name);
63872
+ return dep !== void 0 && dep.decisions.length > 0 && (held.has(name) || dep.decisions.some((d) => d.kind === "fail"));
63873
+ });
63874
+ if (by !== void 0) held.add(provider.name);
63875
+ plans.set(provider.name, {
63876
+ name: provider.name,
63877
+ rule: provider.rule,
63878
+ dependsOn,
63879
+ decisions: by === void 0 ? decisions : hold(decisions, by),
63880
+ steps: by === void 0 ? provider.act(decisions, ctx) : []
63881
+ });
63882
+ }
63883
+ return {
63884
+ machine: ctx.machine,
63885
+ providers: [...plans.values()]
63886
+ };
63887
+ };
63888
+ //#endregion
63889
+ //#region ../../packages/core/src/range.ts
63890
+ const newest = (available) => available.filter((v) => import_semver.prerelease(v) === null).sort(import_semver.rcompare)[0];
63891
+ const decideRange = (range, installed, available) => {
63892
+ if (range === "latest") {
63893
+ const version = newest(available);
63894
+ if (version === void 0) return { kind: "unavailable" };
63895
+ return installed === version ? { kind: "leave" } : {
63896
+ kind: "install",
63897
+ version
63898
+ };
63899
+ }
63900
+ if (range === "*") {
63901
+ if (installed !== void 0) return { kind: "leave" };
63902
+ const version = newest(available);
63903
+ return version === void 0 ? { kind: "unavailable" } : {
63904
+ kind: "install",
63905
+ version
63906
+ };
63907
+ }
63908
+ if (installed !== void 0 && import_semver.satisfies(installed, range)) return { kind: "leave" };
63909
+ if (installed !== void 0 && import_semver.gtr(installed, range)) return {
63910
+ kind: "ask",
63911
+ installed
63912
+ };
63913
+ const version = import_semver.maxSatisfying([...available], range);
63914
+ return version === null ? { kind: "unavailable" } : {
63915
+ kind: "install",
63916
+ version
63917
+ };
63918
+ };
63919
+ //#endregion
63920
+ //#region ../../packages/core/src/provider.ts
63921
+ const address = (provider, item) => `${provider}:${item}`;
63922
+ const parseAddress = (text) => {
63923
+ const at = text.indexOf(":");
63924
+ return at <= 0 || at === text.length - 1 ? void 0 : {
63925
+ provider: text.slice(0, at),
63926
+ item: text.slice(at + 1)
63927
+ };
63928
+ };
63929
+ const QUESTION_CHOICES = {
63930
+ range: [
63931
+ "bump",
63932
+ "skip",
63933
+ "downgrade"
63934
+ ],
63935
+ config: ["overwrite", "stop"],
63936
+ mode: ["managed", "skip"]
63937
+ };
63938
+ const isQuestionKind = (kind) => kind in QUESTION_CHOICES;
63939
+ const rangeQuestion = (subject, installed, range, bumpPath) => ({
63940
+ kind: "range",
63941
+ subject,
63942
+ found: installed,
63943
+ problem: `is above ${range}`,
63944
+ choices: [
63945
+ {
63946
+ value: "bump",
63947
+ title: "bump the config"
63948
+ },
63949
+ {
63950
+ value: "skip",
63951
+ title: "skip on these Machines (leave as is, shown as skipped)"
63952
+ },
63953
+ {
63954
+ value: "downgrade",
63955
+ title: "downgrade into the range"
63956
+ }
63957
+ ],
63958
+ default: "skip",
63959
+ bump: {
63960
+ path: bumpPath,
63961
+ range
63962
+ }
63963
+ });
63964
+ const decideRangeItem = (input) => {
63965
+ const { item, range, installed, available } = input;
63966
+ const at = installed === void 0 ? {} : { at: installed };
63967
+ const unavailable = {
63968
+ kind: "fail",
63969
+ item,
63970
+ ...at,
63971
+ reason: `no published version satisfies ${range}`
63972
+ };
63973
+ const install = (version) => ({
63974
+ kind: "change",
63975
+ item,
63976
+ ...installed === void 0 ? {} : { from: installed },
63977
+ to: version,
63978
+ change: version
63979
+ });
63980
+ const decision = decideRange(range, installed, available);
63981
+ switch (decision.kind) {
63982
+ case "leave": return {
63983
+ kind: "leave",
63984
+ item,
63985
+ ...at
63986
+ };
63987
+ case "install": return install(decision.version);
63988
+ case "unavailable": return unavailable;
63989
+ case "ask": {
63990
+ const subject = address(input.provider, item);
63991
+ const answer = input.ctx.answer("range", subject);
63992
+ if (answer === "skip") return {
63993
+ kind: "skip",
63994
+ item,
63995
+ ...at,
63996
+ reason: `is above ${range}`
63997
+ };
63998
+ if (answer === "downgrade") {
63999
+ const target = decideRange(range, void 0, available);
64000
+ return target.kind === "install" ? install(target.version) : unavailable;
64001
+ }
64002
+ return {
64003
+ kind: "ask",
64004
+ item,
64005
+ ...at,
64006
+ question: rangeQuestion(subject, decision.installed, range, input.bumpPath)
64007
+ };
64008
+ }
64009
+ }
64010
+ };
64011
+ const orderProviders = (all, dependsOn) => {
64012
+ const byName = new Map(all.map((p) => [p.name, p]));
64013
+ const order = [];
64014
+ const done = /* @__PURE__ */ new Set();
64015
+ const visit = (p, path) => {
64016
+ if (done.has(p.name)) return void 0;
64017
+ if (path.includes(p.name)) return [...path.slice(path.indexOf(p.name)), p.name];
64018
+ for (const name of dependsOn(p)) {
64019
+ const dep = byName.get(name);
64020
+ const cycle = dep === void 0 ? void 0 : visit(dep, [...path, p.name]);
64021
+ if (cycle !== void 0) return cycle;
64022
+ }
64023
+ done.add(p.name);
64024
+ order.push(p);
64025
+ };
64026
+ for (const p of all) {
64027
+ const cycle = visit(p, []);
64028
+ if (cycle !== void 0) return { cycle };
64029
+ }
64030
+ return { order };
64031
+ };
64032
+ //#endregion
64033
+ //#region ../../packages/core/src/record.ts
64034
+ const sameRecord = (a, b) => Object.keys(a).length === Object.keys(b).length && Object.entries(a).every(([k, v]) => b[k] === v);
64035
+ const decideInstall = (record, now, rebuild) => {
64036
+ if (rebuild) return {
64037
+ run: true,
64038
+ reason: "--rebuild"
64039
+ };
64040
+ if (record === void 0) return {
64041
+ run: true,
64042
+ reason: "never installed"
64043
+ };
64044
+ if (record.commit !== now.commit) return {
64045
+ run: true,
64046
+ reason: `new commit ${now.commit.slice(0, 7)}`
64047
+ };
64048
+ if (record.path !== now.path) return {
64049
+ run: true,
64050
+ reason: "clone moved"
64051
+ };
64052
+ if (record.install !== now.install) return {
64053
+ run: true,
64054
+ reason: "install command changed"
64055
+ };
64056
+ if (!sameRecord(record.files, now.files)) return {
64057
+ run: true,
64058
+ reason: "files changed"
64059
+ };
64060
+ for (const [path, fingerprint] of Object.entries(now.outputs)) if (fingerprint === void 0 || record.outputs[path] !== fingerprint) return {
64061
+ run: true,
64062
+ reason: `drift: ${path}`
64063
+ };
64064
+ return { run: false };
64065
+ };
64066
+ //#endregion
64067
+ //#region ../../packages/core/src/state.ts
64068
+ const EngineFact = Union([
64069
+ Struct({
64070
+ fact: Literal("home"),
64071
+ path: String$5
64072
+ }),
64073
+ Struct({ fact: Literal("end") }),
64074
+ Struct({
64075
+ fact: Literal("config"),
64076
+ present: Boolean$4,
64077
+ base64: optionalKey(String$5)
64078
+ }),
64079
+ Struct({
64080
+ fact: Literal("record"),
64081
+ provider: String$5,
64082
+ item: String$5,
64083
+ record: Unknown
64084
+ })
64085
+ ]);
64086
+ const ProviderFact = Struct({ provider: String$5 });
64087
+ const AtFact = Struct({
64088
+ provider: String$5,
64089
+ fact: Literal("at"),
64090
+ item: String$5,
64091
+ at: String$5
64092
+ });
64093
+ const decodeEngineFact = decodeUnknownOption(EngineFact);
64094
+ const decodeProviderFact = decodeUnknownOption(ProviderFact);
64095
+ const emptyState = {
64096
+ complete: false,
64097
+ records: {},
64098
+ facts: {}
64099
+ };
64100
+ const decodeBase64 = (base64) => new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
64101
+ const assembleState = (facts) => {
64102
+ let state = emptyState;
64103
+ for (const raw of facts) {
64104
+ const fact = decodeEngineFact(raw);
64105
+ if (fact._tag === "Some") {
64106
+ const f = fact.value;
64107
+ switch (f.fact) {
64108
+ case "home":
64109
+ state = {
64110
+ ...state,
64111
+ home: f.path
64112
+ };
64113
+ break;
64114
+ case "end":
64115
+ state = {
64116
+ ...state,
64117
+ complete: true
64118
+ };
64119
+ break;
64120
+ case "config":
64121
+ if (f.present && f.base64 !== void 0) state = {
64122
+ ...state,
64123
+ config: decodeBase64(f.base64)
64124
+ };
64125
+ break;
64126
+ case "record": state = {
64127
+ ...state,
64128
+ records: {
64129
+ ...state.records,
64130
+ [f.provider]: {
64131
+ ...state.records[f.provider],
64132
+ [f.item]: f.record
64133
+ }
64134
+ }
64135
+ };
64136
+ }
64137
+ continue;
64138
+ }
64139
+ const owned = decodeProviderFact(raw);
64140
+ if (owned._tag === "Some") {
64141
+ const { provider } = owned.value;
64142
+ state = {
64143
+ ...state,
64144
+ facts: {
64145
+ ...state.facts,
64146
+ [provider]: [...state.facts[provider] ?? [], raw]
64147
+ }
64148
+ };
64149
+ }
64150
+ }
64151
+ return state;
64152
+ };
64153
+ //#endregion
64202
64154
  //#region src/ledger.ts
64203
64155
  const Note = Struct({
64204
64156
  kind: Literals$2([
@@ -64211,8 +64163,8 @@ const Note = Struct({
64211
64163
  const Item = Struct({
64212
64164
  name: String$5,
64213
64165
  kind: Literals$2([
64214
- "version",
64215
- "repo",
64166
+ "range",
64167
+ "record",
64216
64168
  "config"
64217
64169
  ]),
64218
64170
  from: optionalKey(String$5),
@@ -64229,7 +64181,10 @@ const Item = Struct({
64229
64181
  doing: optionalKey(String$5),
64230
64182
  detail: optionalKey(String$5),
64231
64183
  output: optionalKey(ArraySchema(String$5)),
64232
- steps: ArraySchema(String$5),
64184
+ steps: ArraySchema(Struct({
64185
+ id: String$5,
64186
+ doing: String$5
64187
+ })),
64233
64188
  started: optionalKey(Number$2),
64234
64189
  ended: optionalKey(Number$2)
64235
64190
  });
@@ -64258,88 +64213,56 @@ const LedgerState = Struct({
64258
64213
  started: Number$2,
64259
64214
  ended: optionalKey(Number$2)
64260
64215
  });
64261
- const short = (sha) => sha === void 0 ? void 0 : sha.slice(0, 7);
64262
- const doingOf = (id) => id === "config" ? "copying" : isFetchStep(id) ? "fetching" : "installing";
64263
- const itemsOf = (p, state) => {
64264
- const items = /* @__PURE__ */ new Map();
64265
- const key = (kind, name) => `${kind}:${name}`;
64266
- const base = (name, from, kind = "version") => ({
64267
- name,
64268
- kind,
64269
- ...from === void 0 ? {} : { from },
64270
- changes: false,
64271
- status: "idle",
64272
- steps: []
64273
- });
64274
- const set = (name, change, kind = "version") => items.set(key(kind, name), change(items.get(key(kind, name)) ?? base(name, void 0, kind)));
64275
- const plans = (id, to, from, reason) => ({ from: checked, ...item }) => {
64276
- const now = from === void 0 ? checked : from ?? void 0;
64277
- return {
64278
- ...item,
64279
- ...now === void 0 ? {} : { from: now },
64280
- to,
64281
- ...reason === void 0 ? {} : { reason },
64216
+ const itemsOf = (p) => [...p.providers.flatMap((pp) => pp.decisions.map((d) => {
64217
+ const base = {
64218
+ name: address(pp.name, d.item),
64219
+ kind: pp.rule,
64220
+ steps: pp.steps.filter((step) => step.items.includes(d.item)).map(({ id, doing }) => ({
64221
+ id,
64222
+ doing
64223
+ }))
64224
+ };
64225
+ switch (d.kind) {
64226
+ case "change": return {
64227
+ ...base,
64228
+ ...d.from === void 0 ? {} : { from: d.from },
64229
+ to: d.to,
64230
+ ...d.reason === void 0 ? {} : { reason: d.reason },
64282
64231
  changes: true,
64283
- steps: [...item.steps, id]
64232
+ status: "idle"
64233
+ };
64234
+ case "leave": return {
64235
+ ...base,
64236
+ ...d.at === void 0 ? {} : { from: d.at },
64237
+ changes: false,
64238
+ status: "idle"
64239
+ };
64240
+ case "skip":
64241
+ case "fail": return {
64242
+ ...base,
64243
+ ...d.at === void 0 ? {} : { from: d.at },
64244
+ changes: d.kind === "fail",
64245
+ status: d.kind === "fail" ? "failed" : "skipped",
64246
+ detail: d.reason
64247
+ };
64248
+ case "ask": return {
64249
+ ...base,
64250
+ ...d.at === void 0 ? {} : { from: d.at },
64251
+ changes: false,
64252
+ status: "skipped",
64253
+ detail: d.question.problem
64284
64254
  };
64285
- };
64286
- const vp = state.vp?.version ?? void 0;
64287
- items.set(key("version", "vp"), base("vp", vp));
64288
- if (state.node != null) items.set(key("version", "node"), base("node", state.node));
64289
- for (const g of state.globals) items.set(key("version", g.name), base(g.name, g.version));
64290
- for (const step of p.vp) {
64291
- const id = vpStepId(step);
64292
- switch (step.kind) {
64293
- case "bootstrap":
64294
- set("vp", plans(id, "latest", null));
64295
- break;
64296
- case "vp":
64297
- case "node":
64298
- set(step.kind, plans(id, step.version, step.from));
64299
- break;
64300
- case "reinstall":
64301
- set(step.name, plans(id, step.version, step.version, "reinstall"));
64302
- break;
64303
- case "globals": for (const pkg of step.packages) set(pkg.name, plans(id, pkg.version, pkg.from ?? null));
64304
- }
64305
- }
64306
- for (const repo of p.repos) {
64307
- const from = short(state.records[repo.name]?.commit);
64308
- const to = short(repo.head);
64309
- items.set(key("repo", repo.name), {
64310
- name: repo.name,
64311
- kind: "repo",
64312
- ...from === void 0 ? {} : { from },
64313
- ...to === void 0 ? {} : { to },
64314
- ...repo.decision.run ? { reason: repo.decision.reason } : {},
64315
- changes: repo.decision.run,
64316
- status: "idle",
64317
- steps: [stepId(repo.name, "fetch"), stepId(repo.name, "install")]
64318
- });
64319
64255
  }
64320
- for (const s of p.skipped) set(s.what, (i) => ({
64321
- ...i,
64322
- status: "skipped",
64323
- detail: s.reason
64324
- }));
64325
- for (const f of p.failures) {
64326
- const kind = f.section === "repos" ? "repo" : "version";
64327
- set(f.what, (i) => ({
64328
- ...i,
64329
- changes: true,
64330
- status: "failed",
64331
- detail: f.reason
64332
- }), kind);
64333
- }
64334
- items.set(key("config", CONFIG_STEP), {
64335
- name: CONFIG_STEP,
64336
- kind: "config",
64337
- changes: false,
64338
- status: "idle",
64339
- steps: [CONFIG_STEP]
64340
- });
64341
- return [...items.values()];
64342
- };
64256
+ })), {
64257
+ name: CONFIG_STEP,
64258
+ kind: "config",
64259
+ changes: false,
64260
+ status: "idle",
64261
+ steps: [{
64262
+ id: CONFIG_STEP,
64263
+ doing: "copying"
64264
+ }]
64265
+ }];
64343
64266
  const outcomeOf = (m, dryRun) => m.status === "skipped" ? "skipped" : m.status === "failed" || m.items.some((i) => i.status === "failed") ? "failed" : m.status === "ok" || m.status === "planned" && dryRun ? "ok" : void 0;
64344
64267
  const anyFailed = (state) => state.machines.some((m) => outcomeOf(m, state.dryRun) === "failed");
64345
64268
  const makeLedger = fn("makeLedger")(function* (input) {
@@ -64373,7 +64296,7 @@ const makeLedger = fn("makeLedger")(function* (input) {
64373
64296
  }));
64374
64297
  const items = (name, id, change) => machine(name, (m, now) => ({
64375
64298
  ...m,
64376
- items: m.items.map((i) => i.steps.includes(id) ? change(i, now) : i)
64299
+ items: m.items.map((i) => i.steps.some((step) => step.id === id) ? change(i, now) : i)
64377
64300
  }));
64378
64301
  const store = {
64379
64302
  get: () => snapshot,
@@ -64414,25 +64337,24 @@ const makeLedger = fn("makeLedger")(function* (input) {
64414
64337
  ...output.length === 0 ? {} : { output },
64415
64338
  ended: now
64416
64339
  })),
64417
- plan: (name, p, state) => machine(name, (m) => ({
64340
+ plan: (name, p) => machine(name, (m) => ({
64418
64341
  ...m,
64419
64342
  status: "planned",
64420
- items: itemsOf(p, state)
64343
+ items: itemsOf(p)
64421
64344
  })),
64422
64345
  start: (name) => machine(name, (m, now) => ({
64423
64346
  ...m,
64424
64347
  status: "running",
64425
64348
  started: now
64426
64349
  })),
64427
- stepStarted: (name, id) => items(name, id, (item, now) => item.status === "failed" ? item : {
64350
+ stepStarted: (name, id) => items(name, id, (item, now) => item.status === "failed" || item.status === "skipped" ? item : {
64428
64351
  ...item,
64429
- changes: item.changes || !(id === "config" || isFetchStep(id)),
64430
64352
  status: "running",
64431
- doing: doingOf(id),
64353
+ doing: item.steps.find((step) => step.id === id)?.doing ?? "running",
64432
64354
  started: item.started ?? now
64433
64355
  }),
64434
64356
  stepDone: (name, id, result) => items(name, id, (item, now) => {
64435
- if (item.status === "failed") return item;
64357
+ if (item.status === "failed" || item.status === "skipped") return item;
64436
64358
  if (!result.ok) return {
64437
64359
  ...item,
64438
64360
  changes: true,
@@ -64441,25 +64363,31 @@ const makeLedger = fn("makeLedger")(function* (input) {
64441
64363
  ...result.output.length === 0 ? {} : { output: result.output },
64442
64364
  ended: now
64443
64365
  };
64366
+ if (item.steps.at(-1)?.id !== id) return item;
64444
64367
  const { doing: _, ...rest } = item;
64445
- if (isFetchStep(id)) return item.changes ? item : {
64368
+ return {
64446
64369
  ...rest,
64447
- status: "idle",
64370
+ status: "ok",
64448
64371
  ended: now
64449
64372
  };
64373
+ }),
64374
+ stepSkipped: (name, id, reason) => items(name, id, (item, now) => {
64375
+ if (item.status === "failed") return item;
64376
+ const { doing: _, ...rest } = item;
64450
64377
  return {
64451
64378
  ...rest,
64452
- status: "ok",
64379
+ status: "skipped",
64380
+ detail: reason,
64453
64381
  ended: now
64454
64382
  };
64455
64383
  }),
64456
- fetched: (name, repo, head) => machine(name, (m) => ({
64384
+ reached: (name, item, at) => machine(name, (m) => ({
64457
64385
  ...m,
64458
- items: m.items.map((i) => i.kind === "repo" && i.name === repo ? {
64386
+ items: m.items.map((i) => i.name !== item ? i : {
64459
64387
  ...i,
64460
- to: head.slice(0, 7),
64461
- changes: i.changes || head.slice(0, 7) !== i.from
64462
- } : i)
64388
+ to: at,
64389
+ changes: i.status === "failed" || i.from !== at
64390
+ })
64463
64391
  })),
64464
64392
  finish: (name, outcome) => machine(name, (m, now) => ({
64465
64393
  ...m,
@@ -64801,6 +64729,10 @@ const Cell = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
64801
64729
  })
64802
64730
  });
64803
64731
  const value = (i) => i.to === void 0 ? i.from ?? "" : i.from === void 0 ? `+ ${i.to}` : i.from === i.to ? i.to : `${i.from} → ${i.to}`;
64732
+ const unchanged = (i) => ({
64733
+ text: "",
64734
+ detail: i.kind === "record" ? "up to date" : void 0
64735
+ });
64804
64736
  const statusOf = (i, dryRun) => {
64805
64737
  switch (i.status) {
64806
64738
  case "running": return {
@@ -64822,7 +64754,8 @@ const statusOf = (i, dryRun) => {
64822
64754
  text: "✓ copied",
64823
64755
  tone: "dim"
64824
64756
  };
64825
- if (i.kind === "repo") return i.from !== void 0 && i.from === i.to ? {
64757
+ if (!i.changes) return unchanged(i);
64758
+ if (i.kind === "record") return i.from !== void 0 && i.from === i.to ? {
64826
64759
  text: "✓ rebuilt",
64827
64760
  tone: "ok",
64828
64761
  detail: i.reason
@@ -64841,10 +64774,7 @@ const statusOf = (i, dryRun) => {
64841
64774
  tone: "dim",
64842
64775
  detail: i.reason
64843
64776
  };
64844
- return {
64845
- text: "",
64846
- detail: i.kind === "repo" ? "up to date" : void 0
64847
- };
64777
+ return unchanged(i);
64848
64778
  }
64849
64779
  };
64850
64780
  const countsOf = (m) => {
@@ -65187,6 +65117,757 @@ const report = fn("report")(function* () {
65187
65117
  ]
65188
65118
  })));
65189
65119
  });
65120
+ const decodeFact$2 = decodeUnknownOption(Union([Struct({
65121
+ fact: Literal("brew"),
65122
+ version: NullOr(String$5)
65123
+ }), AtFact]));
65124
+ const NO_BREW = "Homebrew is not installed; install it first";
65125
+ const homebrew = (name) => {
65126
+ const flag = name === "brew" ? "--formula" : "--cask";
65127
+ const lookup = name === "brew" ? "\"$ff_i\"" : "\"${ff_i##*/}\"";
65128
+ const report = (names) => `for ff_i in ${names.map(quote).join(" ")}; do ff_v=$(brew list --versions ${flag} ${lookup} 2>/dev/null | awk '{print $NF}'); [ -z "$ff_v" ] || ff_at ${name} "$ff_i" "$ff_v"; done`;
65129
+ const step = (what, batch) => {
65130
+ const names = batch.map((c) => c.name);
65131
+ const update = batch.some((c) => c.range === "latest") ? "brew update && " : "";
65132
+ return {
65133
+ id: `${name}:${name === "brew" ? "formulae" : "casks"}:${what}`,
65134
+ items: names,
65135
+ doing: what === "install" ? "installing" : "upgrading",
65136
+ run: [[
65137
+ `( ${update}NONINTERACTIVE=1 brew ${what} ${flag} ${names.map(quote).join(" ")}`,
65138
+ "ff_rc=$?",
65139
+ report(names),
65140
+ "exit $ff_rc )"
65141
+ ].join("; ")]
65142
+ };
65143
+ };
65144
+ return {
65145
+ name,
65146
+ rule: "range",
65147
+ dependsOn: name === "cask" ? ["brew"] : [],
65148
+ items(section) {
65149
+ if (section === void 0) return [];
65150
+ const packages = Object.entries(section.items ?? {}).map(([item, entry]) => typeof entry === "string" ? {
65151
+ name: item,
65152
+ kind: "package",
65153
+ range: entry
65154
+ } : {
65155
+ ...entry,
65156
+ name: item,
65157
+ kind: "package"
65158
+ });
65159
+ return name === "brew" ? [{
65160
+ name: "brew",
65161
+ kind: "homebrew"
65162
+ }, ...packages] : packages;
65163
+ },
65164
+ observe: (items) => [
65165
+ "if command -v brew >/dev/null 2>&1; then",
65166
+ ` ff_state "{\\"provider\\":\\"${name}\\",\\"fact\\":\\"brew\\",\\"version\\":\\"$(ff_json "$(brew --version 2>/dev/null | head -n 1 | sed 's/^Homebrew //')")\\"}"`,
65167
+ ` ${report(items.flatMap((i) => i.kind === "package" ? [i.name] : []))}`,
65168
+ "else",
65169
+ ` ff_state '{"provider":"${name}","fact":"brew","version":null}'`,
65170
+ "fi"
65171
+ ].join("\n"),
65172
+ parse(facts) {
65173
+ let brew;
65174
+ const at = /* @__PURE__ */ new Map();
65175
+ for (const raw of facts) {
65176
+ const fact = decodeFact$2(raw);
65177
+ if (fact._tag === "None") continue;
65178
+ const f = fact.value;
65179
+ if (f.fact === "brew") brew = f.version ?? void 0;
65180
+ else at.set(f.item, f.at);
65181
+ }
65182
+ return {
65183
+ ...brew === void 0 ? {} : { brew },
65184
+ at
65185
+ };
65186
+ },
65187
+ resolve: () => void_,
65188
+ decide({ items, observed, ctx }) {
65189
+ return items.map((item) => {
65190
+ const installed = observed.at.get(item.name);
65191
+ if (item.kind === "homebrew") return observed.brew === void 0 ? {
65192
+ kind: "fail",
65193
+ item: item.name,
65194
+ reason: NO_BREW
65195
+ } : {
65196
+ kind: "leave",
65197
+ item: item.name,
65198
+ at: observed.brew
65199
+ };
65200
+ if (name === "cask" && ctx.machine.platform !== "darwin") return {
65201
+ kind: "fail",
65202
+ item: item.name,
65203
+ reason: "Casks need macOS"
65204
+ };
65205
+ if (observed.brew === void 0) return name === "brew" ? {
65206
+ kind: "skip",
65207
+ item: item.name,
65208
+ reason: "needs Homebrew"
65209
+ } : {
65210
+ kind: "fail",
65211
+ item: item.name,
65212
+ reason: NO_BREW
65213
+ };
65214
+ if (installed === void 0) return {
65215
+ kind: "change",
65216
+ item: item.name,
65217
+ to: "latest",
65218
+ change: {
65219
+ kind: "install",
65220
+ name: item.name,
65221
+ range: item.range
65222
+ }
65223
+ };
65224
+ if (item.range === "latest") return {
65225
+ kind: "change",
65226
+ item: item.name,
65227
+ from: installed,
65228
+ to: "latest",
65229
+ change: {
65230
+ kind: "upgrade",
65231
+ name: item.name,
65232
+ range: item.range
65233
+ }
65234
+ };
65235
+ return {
65236
+ kind: "leave",
65237
+ item: item.name,
65238
+ at: installed
65239
+ };
65240
+ });
65241
+ },
65242
+ act(decisions) {
65243
+ const changes = decisions.flatMap((d) => d.kind === "change" ? [d.change] : []);
65244
+ return ["install", "upgrade"].flatMap((what) => {
65245
+ const batch = changes.filter((c) => c.kind === what);
65246
+ return batch.length === 0 ? [] : [step(what, batch)];
65247
+ });
65248
+ }
65249
+ };
65250
+ };
65251
+ const brew = homebrew("brew");
65252
+ const cask = homebrew("cask");
65253
+ const decodeFact$1 = decodeUnknownOption(Union([
65254
+ Struct({
65255
+ fact: Literal("cloneRoot"),
65256
+ path: String$5,
65257
+ present: Boolean$4
65258
+ }),
65259
+ Struct({
65260
+ fact: Literal("repo"),
65261
+ name: String$5,
65262
+ present: Boolean$4,
65263
+ head: optionalKey(String$5),
65264
+ origin: optionalKey(String$5)
65265
+ }),
65266
+ Struct({
65267
+ fact: Literal("output"),
65268
+ repo: String$5,
65269
+ path: String$5,
65270
+ fingerprint: NullOr(String$5)
65271
+ })
65272
+ ]));
65273
+ const decodeRecord = decodeUnknownOption(Struct({
65274
+ commit: String$5,
65275
+ path: String$5,
65276
+ install: String$5,
65277
+ files: Record(String$5, String$5),
65278
+ outputs: Record(String$5, String$5)
65279
+ }));
65280
+ const cloneRoot = (config, machine) => config.machines[machine]?.clones ?? "~/.local/share/fleetfizz/clones";
65281
+ const originUrl = (origin) => `https://github.com/${origin}.git`;
65282
+ const short = (commit) => commit.slice(0, 7);
65283
+ const expandHome = (path, home) => path === "~" ? home : path.startsWith("~/") ? `${home}${path.slice(1)}` : path;
65284
+ const jsonString = encodeSync(fromJsonString(String$5));
65285
+ const jsonRecord = encodeSync(fromJsonString(Record(String$5, String$5)));
65286
+ const lsRemote = fn("lsRemote")(function* (repo, token) {
65287
+ const spawner = yield* ChildProcessSpawner;
65288
+ const auth = token === void 0 ? [] : [
65289
+ "-c",
65290
+ "credential.helper=",
65291
+ "-c",
65292
+ "credential.helper=!f() { echo username=x-access-token; echo \"password=$FF_TOKEN\"; }; f"
65293
+ ];
65294
+ return yield* scoped(gen(function* () {
65295
+ const handle = yield* spawner.spawn(make$12("git", [
65296
+ ...auth,
65297
+ "ls-remote",
65298
+ originUrl(repo.origin),
65299
+ `refs/heads/${repo.ref}`
65300
+ ], {
65301
+ stdout: "pipe",
65302
+ stderr: "pipe",
65303
+ env: {
65304
+ GIT_TERMINAL_PROMPT: "0",
65305
+ ...token === void 0 ? {} : { FF_TOKEN: token }
65306
+ },
65307
+ extendEnv: true
65308
+ }));
65309
+ const [stdout, stderr, code] = yield* all([
65310
+ runCollect(decodeText(handle.stdout)),
65311
+ runCollect(decodeText(handle.stderr)),
65312
+ handle.exitCode
65313
+ ], { concurrency: "unbounded" });
65314
+ if (code !== 0) {
65315
+ const lines = stderr.join("").trim().split("\n");
65316
+ return { error: lines[lines.length - 1] || `git ls-remote exited ${code}` };
65317
+ }
65318
+ const commit = stdout.join("").split(/\s/)[0];
65319
+ return commit === void 0 || commit === "" ? { error: `no branch ${repo.ref} on origin` } : { commit };
65320
+ })).pipe(orElseSucceed(() => ({ error: "could not run git ls-remote" })));
65321
+ });
65322
+ const observe = (items, root) => {
65323
+ if (items.length === 0) return "";
65324
+ const lines = [
65325
+ `ff_root=${quotePath(root)}`,
65326
+ `if [ -d "$ff_root" ]; then ff_present=true; else ff_present=false; fi`,
65327
+ `ff_state "{\\"provider\\":\\"repos\\",\\"fact\\":\\"cloneRoot\\",\\"path\\":\\"$(ff_json "$ff_root")\\",\\"present\\":$ff_present}"`
65328
+ ];
65329
+ for (const repo of items) {
65330
+ const name = quote(repo.name);
65331
+ lines.push(`ff_d="$ff_root"/${name}`, `if [ -d "$ff_d/.git" ]; then`, ` ff_state "{\\"provider\\":\\"repos\\",\\"fact\\":\\"repo\\",\\"name\\":\\"$(ff_json ${name})\\",\\"present\\":true,\\"head\\":\\"$(ff_json "$(git -C "$ff_d" rev-parse HEAD 2>/dev/null)")\\",\\"origin\\":\\"$(ff_json "$(git -C "$ff_d" remote get-url origin 2>/dev/null)")\\"}"`, "else", ` ff_state "{\\"provider\\":\\"repos\\",\\"fact\\":\\"repo\\",\\"name\\":\\"$(ff_json ${name})\\",\\"present\\":false}"`, "fi");
65332
+ for (const output of repo.outputs ?? []) lines.push(`ff_p=${quotePath(output)}; ff_f=$(ff_fp "$ff_p")`, `if [ -n "$ff_f" ]; then ff_fv="\\"$(ff_json "$ff_f")\\""; else ff_fv=null; fi`, `ff_state "{\\"provider\\":\\"repos\\",\\"fact\\":\\"output\\",\\"repo\\":\\"$(ff_json ${name})\\",\\"path\\":\\"$(ff_json ${quote(output)})\\",\\"fingerprint\\":$ff_fv}"`);
65333
+ }
65334
+ return lines.join("\n");
65335
+ };
65336
+ const decideRepo = (repo, root, observed, resolved, ctx) => {
65337
+ const clone = observed.clones.get(repo.name);
65338
+ const record = observed.records.get(repo.name);
65339
+ const at = clone?.head === void 0 ? {} : { at: short(clone.head) };
65340
+ const fail = (reason) => ({
65341
+ kind: "fail",
65342
+ item: repo.name,
65343
+ ...at,
65344
+ reason
65345
+ });
65346
+ if (observed.cloneRoot?.present === false && !root.startsWith("~")) return fail(`clone root not mounted: ${observed.cloneRoot.path}`);
65347
+ const url = originUrl(repo.origin);
65348
+ if (clone?.present && clone.origin !== void 0 && clone.origin !== url) return fail(`managed clone has origin ${clone.origin}, expected ${url}`);
65349
+ const target = isPin(repo.ref) ? { commit: repo.ref } : resolved[repo.name];
65350
+ if (target === void 0) return fail(`could not resolve ${repo.ref}`);
65351
+ if ("error" in target) return fail(target.error);
65352
+ const { commit } = target;
65353
+ const outputs = observed.outputs.get(repo.name);
65354
+ const install = decideInstall(record, {
65355
+ commit,
65356
+ path: expandHome(`${root}/${repo.name}`, ctx.home),
65357
+ install: repo.install,
65358
+ files: repo.files ?? {},
65359
+ outputs: Object.fromEntries((repo.outputs ?? []).map((o) => [o, outputs?.get(o)]))
65360
+ }, ctx.rebuild(address("repos", repo.name)));
65361
+ const reason = install.run ? install.reason : !clone?.present ? "not cloned" : clone.head !== commit ? `checked out at ${short(clone.head ?? "")}` : void 0;
65362
+ const change = {
65363
+ item: repo,
65364
+ root,
65365
+ commit
65366
+ };
65367
+ if (reason === void 0) return {
65368
+ kind: "leave",
65369
+ item: repo.name,
65370
+ at: short(commit),
65371
+ upkeep: change
65372
+ };
65373
+ const from = record?.commit ?? clone?.head;
65374
+ return {
65375
+ kind: "change",
65376
+ item: repo.name,
65377
+ ...from === void 0 ? {} : { from: short(from) },
65378
+ to: short(commit),
65379
+ reason,
65380
+ change
65381
+ };
65382
+ };
65383
+ const fetchStep = ({ item, root, commit }) => {
65384
+ const url = originUrl(item.origin);
65385
+ const sync = isPin(item.ref) ? [
65386
+ `if [ "$(git -C "$ff_d" rev-parse HEAD)" != ${quote(commit)} ]; then ff_git -C "$ff_d" fetch --quiet origin ${quote(commit)}; fi`,
65387
+ `git -C "$ff_d" checkout --quiet --detach ${quote(commit)}`,
65388
+ `git -C "$ff_d" reset --quiet --hard ${quote(commit)}`
65389
+ ] : [`ff_git -C "$ff_d" fetch --quiet origin ${quote(item.ref)}`, `git -C "$ff_d" reset --quiet --hard ${quote(commit)}`];
65390
+ const files = Object.entries(item.files ?? {}).flatMap(([path, content]) => [`mkdir -p "$(dirname "$ff_d"/${quote(path)})"`, `printf '%s' ${quote(content)} > "$ff_d"/${quote(path)}`]);
65391
+ return {
65392
+ id: `repos:${item.name}:fetch`,
65393
+ items: [item.name],
65394
+ doing: "fetching",
65395
+ run: [
65396
+ `ff_root=${quotePath(root)}`,
65397
+ ...root.startsWith("~") ? [`mkdir -p "$ff_root"`] : [],
65398
+ `ff_d="$ff_root"/${quote(item.name)}`,
65399
+ "(",
65400
+ " set -e",
65401
+ ` if [ ! -d "$ff_d/.git" ]; then ff_git clone --quiet ${quote(url)} "$ff_d"; fi`,
65402
+ ` if [ "$(git -C "$ff_d" remote get-url origin)" != ${quote(url)} ]; then echo "origin is $(git -C "$ff_d" remote get-url origin), expected ${url}"; exit 1; fi`,
65403
+ ...[
65404
+ ...sync,
65405
+ `git -C "$ff_d" clean --quiet -fd`,
65406
+ ...files
65407
+ ].map((l) => ` ${l}`),
65408
+ ")"
65409
+ ]
65410
+ };
65411
+ };
65412
+ const installStep = ({ item, root }, ctx) => {
65413
+ const env = [
65414
+ `FLEETFIZZ_MACHINE=${quote(ctx.machine.name)}`,
65415
+ `FLEETFIZZ_PLATFORM=${quote(ctx.machine.platform)}`,
65416
+ `FLEETFIZZ_MACHINES=${quote(Object.keys(ctx.config.machines).join(","))}`
65417
+ ].join(" ");
65418
+ const tmp = `${recordDir("repos")}/${quote(`${item.name}.json.tmp`)}`;
65419
+ const record = [
65420
+ `ff_after=$(git -C "$ff_d" rev-parse HEAD)`,
65421
+ `ff_outs=""`,
65422
+ ...(item.outputs ?? []).map((o) => `ff_fpv=$(ff_fp ${quotePath(o)}); if [ -n "$ff_fpv" ]; then ff_outs="$ff_outs,\\"$(ff_json ${quote(o)})\\":\\"$(ff_json "$ff_fpv")\\""; fi`),
65423
+ `mkdir -p ${recordDir("repos")}`,
65424
+ `printf '%s\\n' "{\\"commit\\":\\"$ff_after\\",\\"path\\":\\"$(ff_json "$ff_d")\\",\\"install\\":$(ff_lit ${quote(jsonString(item.install))}),\\"files\\":$(ff_lit ${quote(jsonRecord(item.files ?? {}))}),\\"outputs\\":{\${ff_outs#,}}}" > ${tmp}`,
65425
+ `mv -f ${tmp} ${recordFile("repos", item.name)}`,
65426
+ `rm -f ${quotePath(RECORDS_DIR)}/${quote(`${item.name}.json`)}`
65427
+ ];
65428
+ return {
65429
+ id: `repos:${item.name}:install`,
65430
+ items: [item.name],
65431
+ doing: "installing",
65432
+ run: [
65433
+ `ff_root=${quotePath(root)}`,
65434
+ `ff_d="$ff_root"/${quote(item.name)}`,
65435
+ `(cd "$ff_d" && ${env} "\${SHELL:-/bin/sh}" -lc ${quote(item.install)})`,
65436
+ `ff_rc=$?`,
65437
+ `if [ "$ff_rc" -eq 0 ]; then`,
65438
+ " (",
65439
+ " set -e",
65440
+ ...record.map((l) => ` ${l}`),
65441
+ " )",
65442
+ ` ff_rc=$?`,
65443
+ ` if [ "$ff_rc" -ne 0 ]; then echo "could not write the install record"; fi`,
65444
+ "fi",
65445
+ `(exit "$ff_rc")`
65446
+ ]
65447
+ };
65448
+ };
65449
+ const repos = {
65450
+ name: "repos",
65451
+ rule: "record",
65452
+ dependsOn: ["vp"],
65453
+ items: (section) => (section ?? []).map((repo) => ({
65454
+ ...repo,
65455
+ name: repoName(repo)
65456
+ })),
65457
+ observe: (items, scope) => observe(items, cloneRoot(scope.config, scope.machine.name)),
65458
+ parse(facts, records) {
65459
+ let root;
65460
+ const clones = /* @__PURE__ */ new Map();
65461
+ const outputs = /* @__PURE__ */ new Map();
65462
+ for (const raw of facts) {
65463
+ const fact = decodeFact$1(raw);
65464
+ if (fact._tag === "None") continue;
65465
+ const f = fact.value;
65466
+ switch (f.fact) {
65467
+ case "cloneRoot":
65468
+ root = {
65469
+ path: f.path,
65470
+ present: f.present
65471
+ };
65472
+ break;
65473
+ case "repo":
65474
+ clones.set(f.name, {
65475
+ present: f.present,
65476
+ ...f.head === void 0 || f.head === "" ? {} : { head: f.head },
65477
+ ...f.origin === void 0 || f.origin === "" ? {} : { origin: f.origin }
65478
+ });
65479
+ break;
65480
+ case "output": {
65481
+ const paths = outputs.get(f.repo) ?? /* @__PURE__ */ new Map();
65482
+ paths.set(f.path, f.fingerprint ?? void 0);
65483
+ outputs.set(f.repo, paths);
65484
+ break;
65485
+ }
65486
+ }
65487
+ }
65488
+ const decoded = /* @__PURE__ */ new Map();
65489
+ for (const [name, raw] of Object.entries(records)) {
65490
+ const record = decodeRecord(raw);
65491
+ if (record._tag === "Some") decoded.set(name, record.value);
65492
+ }
65493
+ return {
65494
+ ...root === void 0 ? {} : { cloneRoot: root },
65495
+ clones,
65496
+ outputs,
65497
+ records: decoded
65498
+ };
65499
+ },
65500
+ resolve: (items, { token }) => forEach$1(items.filter((repo) => !isPin(repo.ref)), (repo) => lsRemote(repo, token).pipe(map$5((result) => [repo.name, result])), { concurrency: 8 }).pipe(map$5((entries) => Object.fromEntries(entries))),
65501
+ decide: ({ items, observed, resolved, ctx }) => {
65502
+ const root = cloneRoot(ctx.config, ctx.machine.name);
65503
+ return items.map((repo) => decideRepo(repo, root, observed, resolved, ctx));
65504
+ },
65505
+ act: (decisions, ctx) => decisions.flatMap((d) => d.kind === "change" ? [fetchStep(d.change), installStep(d.change, ctx)] : d.kind === "leave" && d.upkeep !== void 0 ? [fetchStep(d.upkeep)] : [])
65506
+ };
65507
+ //#endregion
65508
+ //#region ../../packages/providers/src/vp/index.ts
65509
+ const VP_PACKAGE = "vite-plus";
65510
+ const TOOL_PACKAGES = {
65511
+ node: ["node"],
65512
+ npm: ["npm"],
65513
+ pnpm: ["pnpm"],
65514
+ yarn: ["yarn", "@yarnpkg/cli-dist"],
65515
+ bun: ["bun"]
65516
+ };
65517
+ const GlobalEntry = Struct({
65518
+ name: String$5,
65519
+ version: String$5,
65520
+ platform: optionalKey(Struct({ node: optionalKey(String$5) }))
65521
+ });
65522
+ const decodeFact = decodeUnknownOption(Union([
65523
+ Struct({
65524
+ fact: Literal("vp"),
65525
+ version: NullOr(String$5),
65526
+ path: optionalKey(String$5)
65527
+ }),
65528
+ Struct({
65529
+ fact: Literal("default"),
65530
+ tool: String$5,
65531
+ version: String$5
65532
+ }),
65533
+ Struct({
65534
+ fact: Literal("mode"),
65535
+ mode: Literals$2(["managed", "system-first"])
65536
+ }),
65537
+ Struct({
65538
+ fact: Literal("globals"),
65539
+ list: ArraySchema(GlobalEntry)
65540
+ })
65541
+ ]));
65542
+ const VP_INSTALL = [
65543
+ "curl -fsSL https://vite.plus | bash",
65544
+ ...vpEnv.split("\n"),
65545
+ "{ hash -r 2>/dev/null || true; }"
65546
+ ].join(" && ");
65547
+ const isHomebrew = (path) => path !== void 0 && (path.includes("/homebrew/") || path.includes("/Cellar/"));
65548
+ const OBSERVE = [
65549
+ "if command -v vp >/dev/null 2>&1; then",
65550
+ ` ff_v=$(command vp --version 2>/dev/null | sed -n 's/^vp v\\{0,1\\}\\([^ ]*\\).*/\\1/p' | head -n 1)`,
65551
+ ` ff_state "{\\"provider\\":\\"vp\\",\\"fact\\":\\"vp\\",\\"version\\":\\"$(ff_json "$ff_v")\\",\\"path\\":\\"$(ff_json "$(command -v vp)")\\"}"`,
65552
+ " if command vp env doctor 2>/dev/null | grep -q 'system-first mode'; then ff_m=system-first; else ff_m=managed; fi",
65553
+ ` ff_state "{\\"provider\\":\\"vp\\",\\"fact\\":\\"mode\\",\\"mode\\":\\"$ff_m\\"}"`,
65554
+ ` command vp env default 2>/dev/null | sed -n -e 's/^Default Node.js version: /node /p' -e 's/^Default \\([a-z]*\\) version: /\\1 /p' | while read -r ff_t ff_n; do`,
65555
+ ` ff_state "{\\"provider\\":\\"vp\\",\\"fact\\":\\"default\\",\\"tool\\":\\"$(ff_json "$ff_t")\\",\\"version\\":\\"$(ff_json "$ff_n")\\"}"`,
65556
+ " done",
65557
+ ` ff_g=$(command vp list --global --json 2>/dev/null | tr -d '\\n')`,
65558
+ ` ff_state "{\\"provider\\":\\"vp\\",\\"fact\\":\\"globals\\",\\"list\\":\${ff_g:-[]}}"`,
65559
+ "else",
65560
+ ` ff_state '{"provider":"vp","fact":"vp","version":null}'`,
65561
+ "fi"
65562
+ ].join("\n");
65563
+ const decideSelf = (item, installed, self) => {
65564
+ const at = installed === void 0 ? {} : { at: installed };
65565
+ if (installed === void 0 || import_semver.lt(installed, self)) return {
65566
+ kind: "change",
65567
+ item,
65568
+ ...installed === void 0 ? {} : { from: installed },
65569
+ to: self,
65570
+ change: {
65571
+ kind: "global",
65572
+ name: item,
65573
+ version: self
65574
+ }
65575
+ };
65576
+ if (import_semver.gt(installed, self)) return {
65577
+ kind: "fail",
65578
+ item,
65579
+ ...at,
65580
+ reason: `runs ${SELF_PACKAGE} ${installed}, newer than this ${self}; upgrade here first`
65581
+ };
65582
+ return {
65583
+ kind: "leave",
65584
+ item,
65585
+ ...at
65586
+ };
65587
+ };
65588
+ const MODE_SUBJECT = address("vp", "mode");
65589
+ const NOT_MANAGED = "needs managed mode";
65590
+ const decideMode = (observed, ctx) => {
65591
+ if (observed.vp !== void 0 && observed.mode !== "system-first") return {
65592
+ kind: "leave",
65593
+ item: "mode",
65594
+ at: "managed"
65595
+ };
65596
+ const from = observed.mode === void 0 ? {} : { from: observed.mode };
65597
+ const answer = observed.vp === void 0 ? "managed" : ctx.answer("mode", MODE_SUBJECT);
65598
+ if (answer === "managed") return {
65599
+ kind: "change",
65600
+ item: "mode",
65601
+ ...from,
65602
+ to: "managed",
65603
+ change: { kind: "mode" }
65604
+ };
65605
+ if (answer === "skip") return {
65606
+ kind: "skip",
65607
+ item: "mode",
65608
+ at: "system-first",
65609
+ reason: NOT_MANAGED
65610
+ };
65611
+ return {
65612
+ kind: "ask",
65613
+ item: "mode",
65614
+ at: "system-first",
65615
+ question: {
65616
+ kind: "mode",
65617
+ subject: MODE_SUBJECT,
65618
+ found: "system-first",
65619
+ problem: NOT_MANAGED,
65620
+ choices: [{
65621
+ value: "managed",
65622
+ title: "switch to managed mode"
65623
+ }, {
65624
+ value: "skip",
65625
+ title: "skip Node and package managers on these Machines"
65626
+ }],
65627
+ default: "skip"
65628
+ }
65629
+ };
65630
+ };
65631
+ const decideRanged = (item, observed, resolved, ctx) => {
65632
+ const installed = item.kind === "vp" ? observed.vp?.version : item.kind === "default" ? observed.defaults.get(item.name) : observed.globals.get(item.name)?.version;
65633
+ const decision = decideRangeItem({
65634
+ provider: "vp",
65635
+ item: item.name,
65636
+ range: item.range,
65637
+ installed,
65638
+ available: item.packages.flatMap((p) => resolved[p] ?? []),
65639
+ bumpPath: item.bumpPath,
65640
+ ctx
65641
+ });
65642
+ if (item.kind === "vp" && observed.vp === void 0) {
65643
+ if (decision.kind === "change") return {
65644
+ ...decision,
65645
+ change: {
65646
+ kind: "bootstrap",
65647
+ version: decision.change
65648
+ }
65649
+ };
65650
+ if (decision.kind === "fail" && (item.range === "*" || item.range === "latest")) return {
65651
+ kind: "change",
65652
+ item: item.name,
65653
+ to: "latest",
65654
+ change: { kind: "bootstrap" }
65655
+ };
65656
+ return decision;
65657
+ }
65658
+ if (decision.kind !== "change") return decision;
65659
+ const version = decision.change;
65660
+ if (item.kind === "vp" && isHomebrew(observed.vp?.path)) return {
65661
+ kind: "fail",
65662
+ item: item.name,
65663
+ ...installed === void 0 ? {} : { at: installed },
65664
+ reason: `vp ${installed} was installed with Homebrew; run brew upgrade vite-plus to get ${version}`
65665
+ };
65666
+ const change = item.kind === "vp" ? {
65667
+ kind: "upgrade",
65668
+ version
65669
+ } : item.kind === "default" ? {
65670
+ kind: "default",
65671
+ tool: item.name,
65672
+ version
65673
+ } : {
65674
+ kind: "global",
65675
+ name: item.name,
65676
+ version
65677
+ };
65678
+ return {
65679
+ ...decision,
65680
+ change
65681
+ };
65682
+ };
65683
+ //#endregion
65684
+ //#region ../../packages/providers/src/index.ts
65685
+ const PROVIDERS = [
65686
+ {
65687
+ name: "vp",
65688
+ rule: "range",
65689
+ dependsOn: [],
65690
+ items(section) {
65691
+ const globals = Object.entries(section?.globals ?? {}).map(([name, global]) => {
65692
+ const targeting = typeof global === "string" ? {} : global;
65693
+ return {
65694
+ ...targeting.platforms === void 0 ? {} : { platforms: targeting.platforms },
65695
+ ...targeting.machines === void 0 ? {} : { machines: targeting.machines },
65696
+ name,
65697
+ kind: "global",
65698
+ packages: [name],
65699
+ range: typeof global === "string" ? global : global.range,
65700
+ bumpPath: typeof global === "string" ? [
65701
+ "vp",
65702
+ "globals",
65703
+ name
65704
+ ] : [
65705
+ "vp",
65706
+ "globals",
65707
+ name,
65708
+ "range"
65709
+ ]
65710
+ };
65711
+ });
65712
+ const defaults = ["node", ...PACKAGE_MANAGERS].flatMap((tool) => {
65713
+ const range = section?.[tool];
65714
+ return range === void 0 ? [] : [{
65715
+ name: tool,
65716
+ kind: "default",
65717
+ packages: TOOL_PACKAGES[tool],
65718
+ range,
65719
+ bumpPath: ["vp", tool]
65720
+ }];
65721
+ });
65722
+ return [
65723
+ {
65724
+ name: "vp",
65725
+ kind: "vp",
65726
+ packages: [VP_PACKAGE],
65727
+ range: section?.version ?? "*",
65728
+ bumpPath: ["vp", "version"]
65729
+ },
65730
+ {
65731
+ name: "mode",
65732
+ kind: "mode"
65733
+ },
65734
+ ...defaults,
65735
+ ...globals,
65736
+ {
65737
+ name: SELF_PACKAGE,
65738
+ kind: "self"
65739
+ }
65740
+ ];
65741
+ },
65742
+ observe: () => OBSERVE,
65743
+ parse(facts) {
65744
+ let vpFact;
65745
+ let mode;
65746
+ const defaults = /* @__PURE__ */ new Map();
65747
+ let globals = [];
65748
+ for (const raw of facts) {
65749
+ const fact = decodeFact(raw);
65750
+ if (fact._tag === "None") continue;
65751
+ const f = fact.value;
65752
+ if (f.fact === "vp" && f.version !== null) vpFact = {
65753
+ version: f.version,
65754
+ ...f.path === void 0 ? {} : { path: f.path }
65755
+ };
65756
+ else if (f.fact === "mode") mode = f.mode;
65757
+ else if (f.fact === "default") defaults.set(f.tool, f.version);
65758
+ else if (f.fact === "globals") globals = f.list;
65759
+ }
65760
+ if (vpFact === void 0) return {
65761
+ defaults: /* @__PURE__ */ new Map(),
65762
+ globals: /* @__PURE__ */ new Map()
65763
+ };
65764
+ return {
65765
+ vp: vpFact,
65766
+ ...mode === void 0 ? {} : { mode },
65767
+ defaults,
65768
+ globals: new Map(globals.map((g) => [g.name, g]))
65769
+ };
65770
+ },
65771
+ resolve: (items) => resolveVersions(items.flatMap((item) => "packages" in item ? item.packages : [])),
65772
+ decide({ items, observed, resolved, ctx }) {
65773
+ const mode = decideMode(observed, ctx);
65774
+ const managed = mode.kind === "leave" || mode.kind === "change";
65775
+ const decisions = items.map((item) => {
65776
+ switch (item.kind) {
65777
+ case "mode": return mode;
65778
+ case "self": return decideSelf(item.name, observed.globals.get(item.name)?.version, ctx.self);
65779
+ case "default":
65780
+ if (!managed) {
65781
+ const at = observed.defaults.get(item.name);
65782
+ return {
65783
+ kind: "skip",
65784
+ item: item.name,
65785
+ ...at === void 0 ? {} : { at },
65786
+ reason: NOT_MANAGED
65787
+ };
65788
+ }
65789
+ return decideRanged(item, observed, resolved, ctx);
65790
+ default: return decideRanged(item, observed, resolved, ctx);
65791
+ }
65792
+ });
65793
+ const nodeChange = decisions.find((d) => d.kind === "change" && d.change.kind === "default" && d.change.tool === "node");
65794
+ const nodeDefault = nodeChange?.kind === "change" ? nodeChange.to : observed.defaults.get("node");
65795
+ return decisions.map((d, i) => {
65796
+ const item = items[i];
65797
+ const current = observed.globals.get(d.item);
65798
+ const node = current?.platform?.node;
65799
+ if (d.kind !== "leave" || item === void 0 || item.kind !== "global" && item.kind !== "self" || current === void 0 || node === void 0 || nodeDefault === void 0 || node === nodeDefault) return d;
65800
+ return {
65801
+ kind: "change",
65802
+ item: d.item,
65803
+ from: current.version,
65804
+ to: current.version,
65805
+ reason: `Node ${node} → ${nodeDefault}`,
65806
+ change: {
65807
+ kind: "reinstall",
65808
+ name: d.item,
65809
+ version: current.version
65810
+ }
65811
+ };
65812
+ });
65813
+ },
65814
+ act(decisions) {
65815
+ const changes = decisions.flatMap((d) => d.kind === "change" ? [d.change] : []);
65816
+ const steps = [];
65817
+ for (const change of changes) switch (change.kind) {
65818
+ case "bootstrap":
65819
+ steps.push({
65820
+ id: "vp:vp:install",
65821
+ items: ["vp"],
65822
+ doing: "installing",
65823
+ run: [change.version === void 0 ? VP_INSTALL : `${VP_INSTALL} && command vp upgrade ${quote(change.version)}`]
65824
+ });
65825
+ break;
65826
+ case "upgrade":
65827
+ steps.push({
65828
+ id: "vp:vp:upgrade",
65829
+ items: ["vp"],
65830
+ doing: "upgrading",
65831
+ run: [`( command vp upgrade ${quote(change.version)} )`]
65832
+ });
65833
+ break;
65834
+ case "mode":
65835
+ steps.push({
65836
+ id: "vp:mode:on",
65837
+ items: ["mode"],
65838
+ doing: "switching",
65839
+ run: ["( command vp env on )"]
65840
+ });
65841
+ break;
65842
+ case "default":
65843
+ steps.push({
65844
+ id: `vp:${change.tool}:default`,
65845
+ items: [change.tool],
65846
+ doing: "installing",
65847
+ run: [`( command vp env default ${quote(change.tool === "node" ? change.version : `${change.tool}@${change.version}`)} )`]
65848
+ });
65849
+ break;
65850
+ case "reinstall": steps.push({
65851
+ id: `vp:${change.name}:reinstall`,
65852
+ items: [change.name],
65853
+ doing: "reinstalling",
65854
+ run: [`( command vp install -g ${quote(`${change.name}@${change.version}`)} )`]
65855
+ });
65856
+ }
65857
+ const installs = changes.flatMap((c) => c.kind === "global" ? [c] : []);
65858
+ if (installs.length > 0) steps.push({
65859
+ id: "vp:globals:install",
65860
+ items: installs.map((c) => c.name),
65861
+ doing: "installing",
65862
+ run: [`( command vp install -g ${installs.map((c) => quote(`${c.name}@${c.version}`)).join(" ")} )`]
65863
+ });
65864
+ return steps;
65865
+ }
65866
+ },
65867
+ brew,
65868
+ cask,
65869
+ repos
65870
+ ];
65190
65871
  //#endregion
65191
65872
  //#region src/live.tsx
65192
65873
  const cell = (initial) => {
@@ -65272,6 +65953,7 @@ const noLive = { ask: void 0 };
65272
65953
  const canRedraw = () => Boolean(process.stdout.isTTY) && process.env.TERM !== "dumb";
65273
65954
  //#endregion
65274
65955
  //#region src/machine.ts
65956
+ const decodeAt = decodeUnknownOption(AtFact);
65275
65957
  const runOn = fn("runOn")(function* (target, script, secrets, onLine, ledger) {
65276
65958
  let first;
65277
65959
  const run = yield* runScript(target, script, secrets, (line) => {
@@ -65341,10 +66023,17 @@ const apply = fn("apply")(function* (target, p, script, secrets, ledger) {
65341
66023
  });
65342
66024
  break;
65343
66025
  }
66026
+ case "skip":
66027
+ yield* ledger.stepSkipped(target.name, line.skip.id, line.skip.reason);
66028
+ break;
65344
66029
  case "text":
65345
66030
  if (line.text.trim() !== "") (current?.output ?? head).push(line.text.trimEnd());
65346
66031
  break;
65347
- case "state": if (line.fact.fact === "repo" && line.fact.head !== void 0) yield* ledger.fetched(target.name, line.fact.name, line.fact.head);
66032
+ case "state": {
66033
+ const at = decodeAt(line.fact);
66034
+ if (at._tag === "Some") yield* ledger.reached(target.name, address(at.value.provider, at.value.item), at.value.at);
66035
+ break;
66036
+ }
65348
66037
  }
65349
66038
  }), ledger);
65350
66039
  const finish = fn("finish")(function* (outcome) {
@@ -65365,43 +66054,53 @@ const apply = fn("apply")(function* (target, p, script, secrets, ledger) {
65365
66054
  yield* ledger.fail(target.name, ["apply did not finish", ...head]);
65366
66055
  return yield* finish("failed");
65367
66056
  }
65368
- return yield* finish(failed.length > 0 || p.failures.length > 0 ? "failed" : "ok");
66057
+ const planFailed = p.providers.some((pp) => pp.decisions.some((d) => d.kind === "fail"));
66058
+ return yield* finish(failed.length > 0 || planFailed ? "failed" : "ok");
65369
66059
  });
65370
66060
  //#endregion
65371
66061
  //#region src/questions.ts
65372
- const newest = (group) => [...group].map((q) => q.installed).sort((a, b) => b.localeCompare(a, void 0, { numeric: true }))[0] ?? "";
65373
- const askQuestions = fn("askQuestions")(function* (questions, flags, ledger, ask) {
65374
- const groups = /* @__PURE__ */ new Map();
65375
- for (const q of questions) groups.set(q.package, [...groups.get(q.package) ?? [], q]);
65376
- const decided = {};
65377
- for (const [pkg, group] of groups) {
65378
- const where = group.map((q) => `${q.installed} on ${q.machine}`).join(", ");
65379
- const range = group[0]?.range ?? "";
65380
- const bump = {
65381
- kind: "bump",
65382
- installed: newest(group)
65383
- };
65384
- const bumpTitle = `bump the config to ${bumpedRange(range, newest(group))}`;
65385
- const answer = (decision) => decision.kind === "bump" ? bumpTitle : decision.kind === "skip" ? "skip on these Machines" : "downgrade into the range";
65386
- yield* ledger.note("ask", `${pkg} ${where} is above ${range}`);
65387
- const decision = flags.bump ? bump : flags.enforce ? { kind: "downgrade" } : ask === void 0 ? { kind: "skip" } : yield* ask([
65388
- {
65389
- title: bumpTitle,
65390
- value: bump
65391
- },
65392
- {
65393
- title: "skip on these Machines (leave as is, shown as skipped)",
65394
- value: { kind: "skip" }
65395
- },
65396
- {
65397
- title: "downgrade into the range",
65398
- value: { kind: "downgrade" }
65399
- }
65400
- ]);
65401
- yield* ledger.note("answer", answer(decision));
65402
- decided[pkg] = decision;
66062
+ const parseAnswers = (raw) => {
66063
+ const given = /* @__PURE__ */ new Map();
66064
+ for (const entry of raw) {
66065
+ const at = entry.lastIndexOf("=");
66066
+ const key = entry.slice(0, at);
66067
+ const choice = entry.slice(at + 1);
66068
+ const kind = key.split(":")[0] ?? "";
66069
+ if (at <= 0 || !isQuestionKind(kind)) return { invalid: `--answer ${entry}: expected kind=choice or kind:subject=choice, kind one of ${Object.keys(QUESTION_CHOICES).join(", ")}` };
66070
+ const choices = QUESTION_CHOICES[kind];
66071
+ if (!choices.includes(choice)) return { invalid: `--answer ${entry}: ${kind} takes ${choices.join(", ")}` };
66072
+ given.set(key, choice);
66073
+ }
66074
+ return { answer: (kind, subject) => given.get(`${kind}:${subject}`) ?? given.get(kind) };
66075
+ };
66076
+ const newestOf = (found) => found.toSorted((a, b) => b.localeCompare(a, void 0, { numeric: true }))[0] ?? "";
66077
+ const askQuestions = fn("askQuestions")(function* (input) {
66078
+ const groups = Map.groupBy(input.asked, (a) => `${a.question.kind}:${a.question.subject}`);
66079
+ const answered = [];
66080
+ for (const group of groups.values()) {
66081
+ const question = group[0]?.question;
66082
+ if (question === void 0) continue;
66083
+ const newest = newestOf(group.map((a) => a.question.found));
66084
+ const where = group.map((a) => `${a.question.found} on ${a.machine}`).join(", ");
66085
+ yield* input.ledger.note("ask", `${question.subject} ${where} ${question.problem}`);
66086
+ if (input.dryRun) {
66087
+ yield* input.ledger.note("answer", "not asked (dry run)");
66088
+ continue;
66089
+ }
66090
+ const choices = question.choices.map((c) => ({
66091
+ value: c.value,
66092
+ title: c.value === "bump" && question.bump !== void 0 ? `bump the config to ${bumpedRange(question.bump.range, newest)}` : c.title
66093
+ }));
66094
+ const answer = input.answer(question.kind, question.subject) ?? (input.ask === void 0 ? question.default : yield* input.ask(choices));
66095
+ yield* input.ledger.note("answer", choices.find((c) => c.value === answer)?.title ?? answer);
66096
+ answered.push({
66097
+ question,
66098
+ machines: group.map((a) => a.machine),
66099
+ newest,
66100
+ answer
66101
+ });
65403
66102
  }
65404
- return decided;
66103
+ return answered;
65405
66104
  });
65406
66105
  //#endregion
65407
66106
  //#region src/ship.ts
@@ -65412,22 +66111,19 @@ const unknownMachines = (config, tailnet, only) => {
65412
66111
  const known = new Set(tailnet.fleet.map((m) => m.name));
65413
66112
  const referenced = [
65414
66113
  ...Object.keys(config.machines),
65415
- ...(config.repos ?? []).flatMap((r) => r.machines ?? []),
65416
- ...config.vp?.machines ?? [],
66114
+ ...PROVIDERS.flatMap((p) => machinesNamed(p, config)),
65417
66115
  ...only
65418
66116
  ];
65419
66117
  return [...new Set(referenced.filter((name) => !known.has(name)))];
65420
66118
  };
65421
- const packagesToResolve = (config) => {
65422
- const vp = config.vp;
65423
- if (vp === void 0) return [];
65424
- return [
65425
- ...vp.version === void 0 ? [] : [VP_PACKAGE],
65426
- ...vp.node === void 0 ? [] : [NODE_PACKAGE],
65427
- ...Object.keys(vp.globals ?? {})
65428
- ];
66119
+ const unshippable = (config, tailnet, only, source) => {
66120
+ const unknown = unknownMachines(config, tailnet, only);
66121
+ if (unknown.length > 0) return `unknown Machine: ${unknown.join(", ")} (the tailnet has: ${tailnet.fleet.map((m) => m.name).join(", ")})`;
66122
+ const fleet = new Set(Object.keys(config.machines));
66123
+ if (!fleet.has(tailnet.self.name)) return `this Machine (${tailnet.self.name}) is not in machines in ${source}`;
66124
+ const outsideFleet = only.filter((name) => !fleet.has(name));
66125
+ if (outsideFleet.length > 0) return `not in machines in ${source}: ${outsideFleet.join(", ")}`;
65429
66126
  };
65430
- const rebuildOf = (flags) => flags.rebuild.length === 0 ? "none" : flags.rebuild.includes("all") ? "all" : flags.rebuild;
65431
66127
  const staleConfigs = fn("staleConfigs")(function* (config, own, checked, ledger) {
65432
66128
  const copies = [];
65433
66129
  for (const { machine, state } of checked) {
@@ -65436,24 +66132,43 @@ const staleConfigs = fn("staleConfigs")(function* (config, own, checked, ledger)
65436
66132
  if (parsed._tag === "Failure") yield* ledger.note("info", `the config on ${machine.name} does not parse; it will be overwritten`);
65437
66133
  else copies.push({
65438
66134
  machine: machine.name,
65439
- config: parsed.success
66135
+ config: parsed.success,
66136
+ text: state.config
65440
66137
  });
65441
66138
  }
65442
- return staleCopies(config, copies, own).map((c) => `${c.machine} (shipped ${c.config.shippedAt} from ${c.config.shippedFrom ?? "unknown"})`);
66139
+ return [...Map.groupBy(staleCopies(config, copies, own), (c) => c.text)].flatMap(([text, [first, ...rest]]) => {
66140
+ if (first === void 0) return [];
66141
+ const { config, machine } = first;
66142
+ const machines = [machine, ...rest.map((c) => c.machine)];
66143
+ return [{
66144
+ text,
66145
+ config,
66146
+ machines,
66147
+ description: `${machines.join(", ")} (shipped ${config.shippedAt} from ${config.shippedFrom ?? "unknown"})`
66148
+ }];
66149
+ });
65443
66150
  });
65444
66151
  const schemaJson = encodeSync(fromJsonString(Unknown, { space: 2 }));
65445
66152
  const ship = fn("ship")(function* (flags) {
66153
+ while ((yield* shipPass(flags)) === "adopted");
66154
+ });
66155
+ const shipPass = fn("shipPass")(function* (flags) {
65446
66156
  const fs = yield* FileSystem;
66157
+ const parsed = parseAnswers(flags.answers);
66158
+ if ("invalid" in parsed) return yield* fail$1(new ShipError(parsed.invalid));
66159
+ const given = parsed.answer;
66160
+ const notAddress = flags.rebuild.find((r) => r !== "all" && !PROVIDERS.some((p) => p.rule === "record" && p.name === parseAddress(r)?.provider));
66161
+ if (notAddress !== void 0) return yield* fail$1(new ShipError(`--rebuild ${notAddress}: expected a record Item (repos:<name>) or all`));
65447
66162
  const path = flags.config ?? configPath(process.env);
65448
66163
  const config = yield* loadConfig(path);
66164
+ const ordered = orderProviders(PROVIDERS, (p) => dependenciesOf(p, config));
66165
+ if ("cycle" in ordered) return yield* fail$1(new ShipError(`dependsOn cycle: ${ordered.cycle.join(" → ")}`));
66166
+ const providers = ordered.order;
65449
66167
  const tailnet = yield* readTailnet();
65450
- const unknown = unknownMachines(config, tailnet, flags.only);
65451
- if (unknown.length > 0) return yield* fail$1(new ShipError(`unknown Machine: ${unknown.join(", ")} (the tailnet has: ${tailnet.fleet.map((m) => m.name).join(", ")})`));
66168
+ const problem = unshippable(config, tailnet, flags.only, path);
66169
+ if (problem !== void 0) return yield* fail$1(new ShipError(problem));
65452
66170
  const fleet = new Set(Object.keys(config.machines));
65453
66171
  const own = flags.config === void 0 ? tailnet.self.name : void 0;
65454
- if (!fleet.has(tailnet.self.name)) return yield* fail$1(new ShipError(`this Machine (${tailnet.self.name}) is not in machines in ${path}`));
65455
- const outsideFleet = flags.only.filter((name) => !fleet.has(name));
65456
- if (outsideFleet.length > 0) return yield* fail$1(new ShipError(`not in machines in ${path}: ${outsideFleet.join(", ")}`));
65457
66172
  const order = Object.keys(config.machines);
65458
66173
  const targetsList = tailnet.fleet.filter((m) => fleet.has(m.name) && (flags.only.length === 0 || flags.only.includes(m.name))).toSorted((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
65459
66174
  const count = `${targetsList.length} machine${targetsList.length === 1 ? "" : "s"}`;
@@ -65467,8 +66182,10 @@ const ship = fn("ship")(function* (flags) {
65467
66182
  });
65468
66183
  const redraw = canRedraw();
65469
66184
  let changing = false;
66185
+ let adopted = false;
65470
66186
  const finish = fn("finish")(function* () {
65471
66187
  const state = yield* ledger.end;
66188
+ if (adopted) return;
65472
66189
  const saved = changing && (yield* saveReport(state).pipe(as(true), catch_((e) => error(`could not save the report for fizz report: ${e.message}`).pipe(as(false)))));
65473
66190
  yield* log(ledgerText(state, redraw ? {
65474
66191
  full: false,
@@ -65482,11 +66199,6 @@ const ship = fn("ship")(function* (flags) {
65482
66199
  });
65483
66200
  yield* addFinalizer(() => finish());
65484
66201
  const live = redraw ? yield* startLive(store, color) : noLive;
65485
- const cloneRootOf = (machine) => config.machines[machine.name]?.clones ?? "~/.local/share/fleetfizz/clones";
65486
- const checkRepos = (config.repos ?? []).map((r) => ({
65487
- name: repoName(r),
65488
- outputs: r.outputs ?? []
65489
- }));
65490
66202
  const checked = yield* forEach$1(targetsList, fn("checkOne")(function* (machine) {
65491
66203
  const local = machine.name === tailnet.self.name;
65492
66204
  if (!machine.online) {
@@ -65497,9 +66209,19 @@ const ship = fn("ship")(function* (flags) {
65497
66209
  yield* ledger.skip(machine.name, "no Tailscale SSH server");
65498
66210
  return;
65499
66211
  }
66212
+ const identity = {
66213
+ name: machine.name,
66214
+ platform: machine.platform
66215
+ };
65500
66216
  const script = checkScript({
65501
- cloneRoot: cloneRootOf(machine),
65502
- repos: checkRepos
66217
+ scope: {
66218
+ machine: identity,
66219
+ config
66220
+ },
66221
+ providers: providers.map((provider) => ({
66222
+ provider,
66223
+ items: targetedItems(provider, config, identity)
66224
+ }))
65503
66225
  });
65504
66226
  const result = yield* check({
65505
66227
  name: machine.name,
@@ -65515,58 +66237,107 @@ const ship = fn("ship")(function* (flags) {
65515
66237
  }), { concurrency: "unbounded" }).pipe(map$5((list) => list.filter((g) => g !== void 0)));
65516
66238
  const stale = yield* staleConfigs(config, own, checked, ledger);
65517
66239
  if (stale.length > 0) {
65518
- const what = `newer config on ${stale.join(", ")}`;
66240
+ const what = `newer config on ${stale.map((s) => s.description).join(", ")}`;
66241
+ const answers = stale.flatMap((s) => s.machines).map((m) => given("config", m));
65519
66242
  if (flags.dryRun) yield* ledger.note("info", `${what}; a Ship would ask before overwriting it`);
65520
- else if (flags.overwriteConfig) yield* ledger.note("info", `${what}; overwriting it (--overwrite-config)`);
66243
+ else if (answers.every((a) => a === "overwrite")) yield* ledger.note("info", `${what}; overwriting it, as answered`);
66244
+ else if (answers.includes("stop")) return yield* fail$1(new ShipError("aborted: the config here is older"));
65521
66245
  else if (live.ask === void 0) return yield* fail$1(new ShipError(`${what}; copy it here first, or pass --overwrite-config`));
65522
66246
  else {
66247
+ const usable = [];
66248
+ for (const s of stale) {
66249
+ const why = unshippable(s.config, tailnet, flags.only, `the config on ${s.machines[0]}`);
66250
+ if (why === void 0) usable.push(s);
66251
+ else yield* ledger.note("info", `cannot ship from here: ${why}`);
66252
+ }
65523
66253
  yield* ledger.note("ask", what);
65524
- const overwrite = yield* live.ask([{
65525
- title: "overwrite it with this Machine's config",
65526
- value: true
65527
- }, {
65528
- title: "stop",
65529
- value: false
65530
- }]);
65531
- yield* ledger.note("answer", overwrite ? "overwrite it" : "stop");
65532
- if (!overwrite) return yield* fail$1(new ShipError("aborted: the config here is older"));
66254
+ const choice = yield* live.ask([
66255
+ ...usable.map((s) => ({
66256
+ title: `use the config on ${s.machines[0]} (replaces this one)`,
66257
+ value: s
66258
+ })),
66259
+ {
66260
+ title: "overwrite it with this Machine's config",
66261
+ value: "overwrite"
66262
+ },
66263
+ {
66264
+ title: "stop",
66265
+ value: "stop"
66266
+ }
66267
+ ]);
66268
+ if (choice === "stop") {
66269
+ yield* ledger.note("answer", "stop");
66270
+ return yield* fail$1(new ShipError("aborted: the config here is older"));
66271
+ }
66272
+ if (choice === "overwrite") yield* ledger.note("answer", "overwrite it");
66273
+ else {
66274
+ yield* ledger.note("answer", `use the config on ${choice.machines[0]}`);
66275
+ yield* fs.writeFileString(path, choice.text);
66276
+ adopted = true;
66277
+ return "adopted";
66278
+ }
65533
66279
  }
65534
66280
  }
65535
- const versions = yield* resolveVersions(packagesToResolve(config));
65536
- const planAll = (cfg, answers) => checked.map((g) => ({
66281
+ const targeted = providers.map((provider) => {
66282
+ const items = /* @__PURE__ */ new Map();
66283
+ for (const { machine } of checked) for (const item of targetedItems(provider, config, machine)) items.set(item.name, item);
66284
+ return {
66285
+ provider,
66286
+ items: [...items.values()]
66287
+ };
66288
+ });
66289
+ const lent = targeted.some(({ provider, items }) => provider.name === "repos" && items.length > 0) ? yield* readLentToken() : { token: void 0 };
66290
+ if (lent.token === void 0 && lent.reason !== void 0) yield* ledger.note("info", `no gh token (${lent.reason}); private repos will fail`);
66291
+ const resolved = Object.fromEntries(yield* forEach$1(targeted, ({ provider, items }) => provider.resolve(items, lent.token === void 0 ? {} : { token: lent.token }).pipe(map$5((r) => [provider.name, r])), { concurrency: "unbounded" }));
66292
+ const planAll = (cfg, answer) => checked.map((g) => ({
65537
66293
  ...g,
65538
- plan: plan({
65539
- config: cfg,
65540
- machine: {
65541
- name: g.machine.name,
65542
- platform: g.machine.platform
65543
- },
66294
+ plan: planMachine({
66295
+ providers,
65544
66296
  state: g.state,
65545
- self: flags.version,
65546
- versions,
65547
- rebuild: rebuildOf(flags),
65548
- answers
66297
+ resolved,
66298
+ ctx: {
66299
+ machine: {
66300
+ name: g.machine.name,
66301
+ platform: g.machine.platform
66302
+ },
66303
+ config: cfg,
66304
+ self: flags.version,
66305
+ rebuild: (address) => flags.rebuild.includes("all") || flags.rebuild.includes(address),
66306
+ answer
66307
+ }
65549
66308
  })
65550
66309
  }));
65551
66310
  let cfg = config;
65552
- let planned = planAll(cfg, {});
65553
- const questions = planned.flatMap((g) => g.plan.questions);
65554
- if (questions.length > 0 && flags.dryRun) for (const q of questions) {
65555
- yield* ledger.note("ask", `${q.package} ${q.installed} on ${q.machine} is above ${q.range}`);
65556
- yield* ledger.note("answer", "not asked (dry run)");
65557
- }
65558
- if (questions.length > 0 && !flags.dryRun) {
65559
- const decided = yield* askQuestions(questions, flags, ledger, live.ask);
65560
- for (const [pkg, decision] of Object.entries(decided)) {
65561
- if (decision.kind !== "bump") continue;
66311
+ const decided = /* @__PURE__ */ new Map();
66312
+ const answer = (kind, subject) => decided.get(`${kind}:${subject}`) ?? given(kind, subject);
66313
+ let planned = planAll(cfg, answer);
66314
+ for (;;) {
66315
+ const asked = planned.flatMap((g) => g.plan.providers.flatMap((pp) => pp.decisions.flatMap((d) => d.kind === "ask" ? [{
66316
+ machine: g.machine.name,
66317
+ question: d.question
66318
+ }] : [])));
66319
+ const answered = yield* askQuestions({
66320
+ asked,
66321
+ answer,
66322
+ ledger,
66323
+ ask: live.ask,
66324
+ dryRun: flags.dryRun
66325
+ });
66326
+ let progressed = false;
66327
+ for (const { question, newest, answer: choice } of answered) {
66328
+ const key = `${question.kind}:${question.subject}`;
66329
+ progressed ||= !decided.has(key);
66330
+ decided.set(key, choice);
66331
+ if (choice !== "bump" || question.bump === void 0) continue;
65562
66332
  changing = true;
65563
- yield* bumpConfig(path, pkg, decision.installed);
66333
+ const range = yield* bumpConfig(path, question.bump.path, newest);
66334
+ progressed ||= range !== question.bump.range;
66335
+ cfg = yield* loadConfig(path);
65564
66336
  }
65565
- if (Object.values(decided).some((d) => d.kind === "bump")) cfg = yield* loadConfig(path);
65566
- const answers = Object.fromEntries(Object.entries(decided).flatMap(([pkg, d]) => d.kind === "bump" ? [] : [[pkg, d.kind]]));
65567
- planned = planAll(cfg, answers);
66337
+ if (!progressed) break;
66338
+ planned = planAll(cfg, answer);
65568
66339
  }
65569
- for (const { machine, plan: p, state } of planned) yield* ledger.plan(machine.name, p, state);
66340
+ for (const { machine, plan: p } of planned) yield* ledger.plan(machine.name, p);
65570
66341
  if (flags.dryRun) return;
65571
66342
  const shippedAt = formatIso(yield* now);
65572
66343
  changing = true;
@@ -65580,44 +66351,36 @@ const ship = fn("ship")(function* (flags) {
65580
66351
  const stamped = stampWith(planned.map((g) => g.machine.name));
65581
66352
  yield* fs.writeFileString(path, stamped);
65582
66353
  const schema = `${schemaJson(fleetConfigJsonSchema())}\n`;
65583
- const lent = planned.some((g) => g.plan.repos.length > 0) ? yield* readLentToken() : { token: void 0 };
65584
- if (lent.token === void 0 && lent.reason !== void 0) yield* ledger.note("info", `no gh token (${lent.reason}); private repos will fail`);
65585
- const machineNames = [...fleet];
65586
66354
  const stdin = applyStdin(lent.token, stamped, schema);
65587
66355
  const delivered = yield* forEach$1(planned, fn("applyOne")(function* ({ machine, local, plan: p }) {
65588
- const script = applyScript({
65589
- plan: p,
65590
- cloneRoot: cloneRootOf(machine),
65591
- env: {
65592
- machine: machine.name,
65593
- platform: machine.platform,
65594
- machines: machineNames
65595
- }
65596
- });
65597
66356
  return (yield* apply({
65598
66357
  name: machine.name,
65599
66358
  local
65600
- }, p, script, stdin, ledger)).done.includes("config") ? machine.name : void 0;
66359
+ }, p, applyScript(p), stdin, ledger)).done.includes("config") ? machine.name : void 0;
65601
66360
  }), { concurrency: "unbounded" }).pipe(map$5((list) => list.filter((name) => name !== void 0)));
65602
66361
  yield* fs.writeFileString(path, stampWith(delivered));
65603
66362
  }, scoped);
65604
66363
  //#endregion
65605
- //#region src/bin.ts
66364
+ //#region src/command.ts
65606
66365
  const shipCommand = make("ship", {
65607
66366
  dryRun: Boolean$1("dry-run").pipe(withDefault(false), withDescription$1("Check and plan only. Print every Machine's plan and change nothing.")),
65608
- only: String$1("only").pipe(atLeast(0), withDescription$1("Ship only to these Machines (repeat the flag).")),
65609
- rebuild: String$1("rebuild").pipe(atLeast(0), withDescription$1("Run a repo's Install Command even when its record matches; `all` for every repo.")),
65610
- bump: Boolean$1("bump").pipe(withDefault(false), withDescription$1("Answer every above-range question by bumping the config.")),
65611
- enforce: Boolean$1("enforce").pipe(withDefault(false), withDescription$1("Answer every above-range question by downgrading into the range.")),
65612
- overwriteConfig: Boolean$1("overwrite-config").pipe(withDefault(false), withDescription$1("Ship even when another Machine holds a newer config, overwriting it with this one.")),
65613
- config: String$1("config").pipe(optional, withDescription$1("Config file. Default: $XDG_CONFIG_HOME/fleetfizz/config.jsonc."))
66367
+ only: String$1("only").pipe(atLeast(0), withMetavar("machine"), withDescription$1("Ship only to this Machine. Repeat the flag for more.")),
66368
+ rebuild: String$1("rebuild").pipe(atLeast(0), withMetavar("item"), withDescription$1("Rebuild a record Item (`repos:<name>`) even when it is up to date. `all` for every one.")),
66369
+ answer: String$1("answer").pipe(atLeast(0), withMetavar("answer"), withDescription$1("Answer a Question in advance: `kind=choice` or `kind:subject=choice`. Repeat the flag for more.")),
66370
+ bump: Boolean$1("bump").pipe(withDefault(false), withDescription$1("Answer every above-range Question by bumping the config (`--answer range=bump`).")),
66371
+ enforce: Boolean$1("enforce").pipe(withDefault(false), withDescription$1("Answer every above-range Question by downgrading into the Range (`--answer range=downgrade`).")),
66372
+ overwriteConfig: Boolean$1("overwrite-config").pipe(withDefault(false), withDescription$1("Ship even when another Machine holds a newer config, overwriting it (`--answer config=overwrite`).")),
66373
+ config: String$1("config").pipe(optional, withMetavar("path"), withDescription$1("The config file. Default: `$XDG_CONFIG_HOME/fleetfizz/config.jsonc`."))
65614
66374
  }).pipe(withDescription("Ship the config to every reachable Machine."), withHandler((flags) => ship({
65615
66375
  dryRun: flags.dryRun,
65616
66376
  only: flags.only,
65617
66377
  rebuild: flags.rebuild,
65618
- bump: flags.bump,
65619
- enforce: flags.enforce,
65620
- overwriteConfig: flags.overwriteConfig,
66378
+ answers: [
66379
+ ...flags.answer,
66380
+ ...flags.bump ? ["range=bump"] : [],
66381
+ ...flags.enforce ? ["range=downgrade"] : [],
66382
+ ...flags.overwriteConfig ? ["config=overwrite"] : []
66383
+ ],
65621
66384
  config: flags.config._tag === "Some" ? flags.config.value : void 0,
65622
66385
  version
65623
66386
  }).pipe(catch_((e) => gen(function* () {
@@ -65625,6 +66388,8 @@ const shipCommand = make("ship", {
65625
66388
  process.exitCode = 1;
65626
66389
  })))));
65627
66390
  const reportCommand = make("report", {}).pipe(withDescription("Print the full Ledger of the last Ship from this Machine."), withHandler(() => report()));
66391
+ //#endregion
66392
+ //#region src/bin.ts
65628
66393
  run(make("fizz").pipe(withDescription("Keep every Machine on your tailnet in line with one config."), withSubcommands([shipCommand, reportCommand])), { version }).pipe(provide(layer), runMain);
65629
66394
  //#endregion
65630
66395
  export {};