fleetfizz 0.6.0 → 0.8.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 +340 -154
  2. package/package.json +1 -1
  3. package/schema.json +1019 -245
@@ -4299,6 +4299,18 @@ const findFirst$1 = /*#__PURE__*/ dual(2, (self, f) => {
4299
4299
  //#endregion
4300
4300
  //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/Array.js
4301
4301
  /**
4302
+ * Works with JavaScript arrays, readonly arrays, and non-empty arrays.
4303
+ *
4304
+ * The helpers cover common collection work such as creating arrays, reading
4305
+ * elements, transforming values, sorting, grouping, splitting, combining, and
4306
+ * reducing many values to one result. Helpers that change contents return new
4307
+ * arrays and preserve non-empty array types when the result is guaranteed to
4308
+ * contain values.
4309
+ *
4310
+ * @stability stable
4311
+ * @since 2.0.0
4312
+ */
4313
+ /**
4302
4314
  * Exposes the global array constructor.
4303
4315
  *
4304
4316
  * **When to use**
@@ -4562,6 +4574,17 @@ const findLast = /*#__PURE__*/ dual(2, (self, f) => {
4562
4574
  }
4563
4575
  return none();
4564
4576
  });
4577
+ const hashBucketsAdd = (buckets, value) => {
4578
+ const hash$1 = hash(value);
4579
+ const bucket = buckets.get(hash$1);
4580
+ if (bucket === void 0) {
4581
+ buckets.set(hash$1, [value]);
4582
+ return true;
4583
+ }
4584
+ for (const previous of bucket) if (equals$1(previous, value)) return false;
4585
+ bucket.push(value);
4586
+ return true;
4587
+ };
4565
4588
  /**
4566
4589
  * Creates an empty array.
4567
4590
  *
@@ -4658,6 +4681,38 @@ const map$8 = /*#__PURE__*/ dual(2, (self, f) => self.map(f));
4658
4681
  * @since 2.0.0
4659
4682
  */
4660
4683
  const reduce = /*#__PURE__*/ dual(3, (self, b, f) => fromIterable$1(self).reduce((b, a, i) => f(b, a, i), b));
4684
+ /**
4685
+ * Removes duplicates using `Equal.equivalence()`, preserving the order of the
4686
+ * first occurrence.
4687
+ *
4688
+ * **When to use**
4689
+ *
4690
+ * Use to remove repeated values from an iterable when Effect's default equality
4691
+ * is the right comparison, preserving the first occurrence.
4692
+ *
4693
+ * **Example** (Removing duplicates)
4694
+ *
4695
+ * ```ts import.meta.vitest
4696
+ * import { Array } from "effect"
4697
+ *
4698
+ * Array.dedupe([1, 2, 1, 3, 2, 4]) // => [1, 2, 3, 4]
4699
+ * ```
4700
+ *
4701
+ * @see {@link dedupeWith} — use custom equality
4702
+ * @see {@link dedupeAdjacent} — only dedupes consecutive elements
4703
+ *
4704
+ * @stability stable
4705
+ * @category deduplication
4706
+ * @since 2.0.0
4707
+ */
4708
+ const dedupe = (self) => {
4709
+ const input = fromIterable$1(self);
4710
+ if (input.length < 2) return [...input];
4711
+ const buckets = /* @__PURE__ */ new Map();
4712
+ const out = [];
4713
+ for (const value of input) if (hashBucketsAdd(buckets, value)) out.push(value);
4714
+ return out;
4715
+ };
4661
4716
  //#endregion
4662
4717
  //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/Duration.js
4663
4718
  const TypeId$31 = "~effect/Duration";
@@ -20340,13 +20395,13 @@ function canPreserveEncodingChecks(ast) {
20340
20395
  * @category transforming
20341
20396
  * @since 4.0.0
20342
20397
  */
20343
- const toType = /*#__PURE__*/ memoizeIdempotent((ast) => {
20398
+ const toType$1 = /*#__PURE__*/ memoizeIdempotent((ast) => {
20344
20399
  const owner = getContextOwner(ast);
20345
20400
  if (owner !== ast) {
20346
- const type = toType(owner);
20401
+ const type = toType$1(owner);
20347
20402
  return type === owner && ast.encoding === void 0 ? ast : replaceContext(type, ast.context);
20348
20403
  }
20349
- const type = "recur" in ast ? ast.recur(toType) : ast;
20404
+ const type = "recur" in ast ? ast.recur(toType$1) : ast;
20350
20405
  if ("encodingChecks" in type && type.encodingChecks) {
20351
20406
  const checks = canPreserveEncodingChecks(ast) ? type.encodingChecks : isArrays(type) || isObjects(type) || isDeclaration(type) && type.typeParameters.length > 0 ? extractStructuralChecks(type.encodingChecks) : void 0;
20352
20407
  return copy$1(type, {
@@ -20384,7 +20439,7 @@ const toType = /*#__PURE__*/ memoizeIdempotent((ast) => {
20384
20439
  * @since 4.0.0
20385
20440
  */
20386
20441
  const toEncoded = /*#__PURE__*/ memoizeIdempotent((ast) => {
20387
- return toType(flip(ast));
20442
+ return toType$1(flip(ast));
20388
20443
  });
20389
20444
  function flipEncoding(ast, encoding) {
20390
20445
  const links = encoding;
@@ -22738,7 +22793,7 @@ function makeEffect(schema) {
22738
22793
  const ast = schema.ast;
22739
22794
  let parser;
22740
22795
  return (input, options) => {
22741
- return (parser ??= runWithCompiler(constructorCompiler, toType(ast)))(input, options?.disableChecks ? options?.parseOptions ? {
22796
+ return (parser ??= runWithCompiler(constructorCompiler, toType$1(ast)))(input, options?.disableChecks ? options?.parseOptions ? {
22742
22797
  ...options.parseOptions,
22743
22798
  disableChecks: true
22744
22799
  } : { disableChecks: true } : options?.parseOptions);
@@ -22799,7 +22854,7 @@ function makeOption(schema) {
22799
22854
  * @since 4.0.0
22800
22855
  */
22801
22856
  function make$5(schema) {
22802
- return makeConstructorSync(toType(schema.ast));
22857
+ return makeConstructorSync(toType$1(schema.ast));
22803
22858
  }
22804
22859
  /**
22805
22860
  * Creates a type guard that checks whether an input satisfies the schema's decoded
@@ -22841,7 +22896,7 @@ function makeIs(ast) {
22841
22896
  }
22842
22897
  /** @internal */
22843
22898
  function _is(ast) {
22844
- const typeAST = toType(ast);
22899
+ const typeAST = toType$1(ast);
22845
22900
  let guard = (input) => {
22846
22901
  guard = makeIs(typeAST);
22847
22902
  return guard(input);
@@ -24100,7 +24155,7 @@ function toRepresentations(asts, options) {
24100
24155
  }
24101
24156
  function visitChecks(checks) {
24102
24157
  checks?.forEach((check) => {
24103
- check.annotations?.representation?.schemas?.forEach((schema) => visit(toType(schema)));
24158
+ check.annotations?.representation?.schemas?.forEach((schema) => visit(toType$1(schema)));
24104
24159
  if (check._tag === "FilterGroup") visitChecks(check.checks);
24105
24160
  });
24106
24161
  }
@@ -24240,7 +24295,7 @@ function toRepresentations(asts, options) {
24240
24295
  const { representation, ...ordinary } = annotations;
24241
24296
  const projected = representation === void 0 ? void 0 : representation.schemas === void 0 ? representation : {
24242
24297
  ...representation,
24243
- schemas: representation.schemas.map((schema) => recur(toType(schema)))
24298
+ schemas: representation.schemas.map((schema) => recur(toType$1(schema)))
24244
24299
  };
24245
24300
  return {
24246
24301
  ...projected === void 0 ? void 0 : { representation: projected },
@@ -24296,6 +24351,20 @@ function isSchemaError$1(u) {
24296
24351
  }
24297
24352
  //#endregion
24298
24353
  //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/Schema.js
24354
+ /**
24355
+ * Describes data shapes and how unknown input becomes trusted values.
24356
+ *
24357
+ * A schema can validate input, decode it into an application type, and encode
24358
+ * that value back to another representation. This module contains the main
24359
+ * schema, codec, decoder, and encoder APIs, together with schemas for common
24360
+ * JavaScript values and Effect data types. It also supports refinements,
24361
+ * transformations, defaults, classes, JSON Schema generation, test data
24362
+ * generation, formatting, equivalence, optics, and differs derived from schema
24363
+ * definitions.
24364
+ *
24365
+ * @stability stable
24366
+ * @since 4.0.0
24367
+ */
24299
24368
  const TypeId$5 = TypeId$6;
24300
24369
  /**
24301
24370
  * Error thrown or returned when schema decoding or encoding fails.
@@ -24753,6 +24822,15 @@ const optional$2 = /*#__PURE__*/ lambda((self) => {
24753
24822
  return make$3(optional$3(self.ast), { schema });
24754
24823
  });
24755
24824
  /**
24825
+ * Extracts the type-side schema: sets `Encoded` to equal the decoded `Type`,
24826
+ * discarding the encoding transformation path.
24827
+ *
24828
+ * @stability stable
24829
+ * @category transforming
24830
+ * @since 4.0.0
24831
+ */
24832
+ const toType = /*#__PURE__*/ lambda((schema) => make$3(toType$1(schema.ast), { schema }));
24833
+ /**
24756
24834
  * Creates a schema for a single literal value (string, number, bigint, boolean, or null).
24757
24835
  *
24758
24836
  * **Example** (Defining a string literal)
@@ -25286,6 +25364,45 @@ function isPattern(regExp, annotations) {
25286
25364
  ...annotations
25287
25365
  });
25288
25366
  }
25367
+ /**
25368
+ * Validates that a value has at least the specified length. Works with strings
25369
+ * and arrays.
25370
+ *
25371
+ * **Details**
25372
+ *
25373
+ * JSON Schema:
25374
+ *
25375
+ * The bound must be finite; it is rounded down and clamped to zero. For arrays,
25376
+ * this check corresponds to `minItems`. JavaScript counts UTF-16 code units
25377
+ * while JSON Schema counts Unicode code points, so strings use
25378
+ * `minLength: Math.ceil(minLength / 2)`, the tightest lower bound that cannot
25379
+ * reject a string accepted by this check.
25380
+ *
25381
+ * Arbitrary:
25382
+ *
25383
+ * During arbitrary generation, this applies a `minLength`
25384
+ * constraint to ensure generated strings or arrays have at least the required
25385
+ * length.
25386
+ *
25387
+ * **Example** (Checking minimum length)
25388
+ *
25389
+ * ```ts import.meta.vitest
25390
+ * import { Schema } from "effect"
25391
+ *
25392
+ * const NonEmptyStringSchema = Schema.String.check(Schema.isMinLength(1))
25393
+ * const NonEmptyArraySchema = Schema.Array(Schema.Number).check(Schema.isMinLength(1))
25394
+ * Schema.is(NonEmptyStringSchema)("a") // => true
25395
+ * Schema.is(NonEmptyArraySchema)([1]) // => true
25396
+ * ```
25397
+ *
25398
+ * @stability stable
25399
+ * @category validation
25400
+ * @since 4.0.0
25401
+ */
25402
+ function isMinLength(minLength, annotations) {
25403
+ minLength = normalizeCardinality(minLength);
25404
+ return makeIsMinLength(minLength, Math.ceil(minLength / 2), annotations);
25405
+ }
25289
25406
  function makeIsMinLength(minLength, minCodePoints, annotations) {
25290
25407
  return makeFilter((input) => input.length >= minLength, {
25291
25408
  expected: `a value with a length of at least ${minLength}`,
@@ -25326,6 +25443,39 @@ function makeIsMinLength(minLength, minCodePoints, annotations) {
25326
25443
  function isNonEmpty(annotations) {
25327
25444
  return makeIsMinLength(1, 1, annotations);
25328
25445
  }
25446
+ function normalizeCardinality(value) {
25447
+ if (!globalThis.Number.isFinite(value)) throw new globalThis.RangeError(`Expected a finite number, got ${value}`);
25448
+ return Math.max(0, Math.floor(value));
25449
+ }
25450
+ /**
25451
+ * Validates that all items in an array are unique according to Effect equality.
25452
+ *
25453
+ * **Details**
25454
+ *
25455
+ * JSON Schema:
25456
+ * This check corresponds to the `uniqueItems: true` constraint in JSON Schema.
25457
+ *
25458
+ * Arbitrary:
25459
+ * During arbitrary generation, this applies a node-local identity selector
25460
+ * for constructive uniqueness using Effect equality.
25461
+ *
25462
+ * @stability stable
25463
+ * @category validation
25464
+ * @since 4.0.0
25465
+ */
25466
+ function isUnique(annotations) {
25467
+ return makeFilter((input) => dedupe(input).length === input.length, {
25468
+ expected: "an array with unique items",
25469
+ representation: {
25470
+ id: "effect/schema/isUnique",
25471
+ payload: null
25472
+ },
25473
+ toJsonSchema: () => ({ uniqueItems: true }),
25474
+ toCode: () => ({ runtime: "Schema.isUnique()" }),
25475
+ arbitraryConstraint: { uniqueBy: identity },
25476
+ ...annotations
25477
+ });
25478
+ }
25329
25479
  /**
25330
25480
  * Schema for non-empty strings. Validates that a string has at least one
25331
25481
  * character.
@@ -32482,7 +32632,7 @@ const runWith = (command, config) => {
32482
32632
  };
32483
32633
  //#endregion
32484
32634
  //#region package.json
32485
- var version = "0.6.0";
32635
+ var version = "0.8.0";
32486
32636
  //#endregion
32487
32637
  //#region ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
32488
32638
  /**
@@ -35148,15 +35298,18 @@ var BumpError = class extends TaggedError()("BumpError", {
35148
35298
  };
35149
35299
  const bumpedRange = (current, installed) => import_semver.valid(current) !== null ? installed : `^${installed}`;
35150
35300
  const bumpText = (text, path, installed) => {
35151
- const node = findNodeAtLocation(parseTree(text) ?? {
35301
+ const tree = parseTree(text) ?? {
35152
35302
  type: "null",
35153
35303
  offset: 0,
35154
35304
  length: 0
35155
- }, [...path]);
35305
+ };
35306
+ const shorthand = path.slice(0, -1);
35307
+ const at = findNodeAtLocation(tree, [...path]) === void 0 && findNodeAtLocation(tree, shorthand)?.type === "string" ? shorthand : path;
35308
+ const node = findNodeAtLocation(tree, [...at]);
35156
35309
  if (node === void 0 || node.type !== "string" || typeof node.value !== "string") return { reason: `${path.join(".")} is not a string literal; change it by hand` };
35157
35310
  const range = bumpedRange(node.value, installed);
35158
35311
  return {
35159
- text: applyEdits(text, modify(text, [...path], range, { formattingOptions: {
35312
+ text: applyEdits(text, modify(text, [...at], range, { formattingOptions: {
35160
35313
  insertSpaces: true,
35161
35314
  tabSize: 2
35162
35315
  } })),
@@ -35200,14 +35353,25 @@ const TrackedRepo = Struct({
35200
35353
  origin: Origin.annotateKey({ description: "The GitHub `owner/repo`." }),
35201
35354
  ref: Ref.annotateKey({ description: "A branch, or a full 40-character commit SHA to pin it." }),
35202
35355
  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." })
35356
+ outputs: optionalKey(ArraySchema(NonEmptyString)).annotateKey({ description: "Paths outside the clone the Install Command produces. If one changes by hand, the repo reinstalls." })
35357
+ });
35358
+ const withShorthand = (entry, value, shorthand) => Union([entry, value.pipe(decodeTo(toType(entry), transform$1(shorthand)))]);
35359
+ const variants = (entry) => Union([entry, NonEmptyArray(entry)]);
35360
+ const dependsOn = optionalKey(ArraySchema(ProviderName)).annotateKey({ description: "Providers whose Items must succeed on a Machine before this one changes there." });
35361
+ const section = (settings, items, description) => Struct({
35362
+ ...Targeting,
35363
+ dependsOn,
35364
+ ...settings,
35365
+ items: optionalKey(items).annotateKey({ description })
35206
35366
  });
35207
- const Global = Union([Range, Struct({
35367
+ const VARIANTS = "or a list of Variants, of which a Machine gets the first that targets it";
35368
+ const Global = withShorthand(Struct({
35208
35369
  ...Targeting,
35209
35370
  range: Range
35210
- })]);
35371
+ }), Range, {
35372
+ decode: (range) => ({ range }),
35373
+ encode: (global) => global.range
35374
+ });
35211
35375
  const PACKAGE_MANAGERS = [
35212
35376
  "npm",
35213
35377
  "pnpm",
@@ -35222,54 +35386,53 @@ const NOT_GLOBALS = {
35222
35386
  "vite-plus": "set vp with vp.version",
35223
35387
  fleetfizz: "a Ship installs the fleetfizz that ships"
35224
35388
  };
35225
- const VpSection = Struct({
35226
- ...Targeting,
35227
- dependsOn: optionalKey(ArraySchema(ProviderName)).annotateKey({ description: "Providers whose Items must succeed on a Machine before vp changes there." }),
35389
+ const VpSection = section({
35228
35390
  version: optionalKey(Range).annotateKey({ description: "The vp version. Pin it exactly." }),
35229
35391
  node: optionalKey(Range).annotateKey({ description: "The default Node version. Pin it exactly." }),
35230
35392
  npm: optionalKey(Range).annotateKey({ description: "The default npm version." }),
35231
35393
  pnpm: optionalKey(Range).annotateKey({ description: "The default pnpm version." }),
35232
35394
  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." })
35241
- });
35395
+ bun: optionalKey(Range).annotateKey({ description: "The default Bun version." })
35396
+ }, Record(NonEmptyString, variants(Global)).check(makeFilter((globals) => {
35397
+ const name = Object.keys(globals).find((n) => n in NOT_GLOBALS);
35398
+ return name === void 0 || `"${name}" is not a Global: ${NOT_GLOBALS[name]}`;
35399
+ }, {
35400
+ title: "Globals",
35401
+ description: "npm packages other than vp, node, a package manager, vite-plus, and fleetfizz"
35402
+ })), `Globals, keyed by package. Each is a Range, \`{ range, platforms?, machines? }\` to target it, ${VARIANTS}.`);
35242
35403
  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({
35404
+ const HomebrewItems = Record(NonEmptyString, variants(withShorthand(Struct({
35244
35405
  ...Targeting,
35245
35406
  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", {
35407
+ }), HomebrewRange, {
35408
+ decode: (range) => ({ range }),
35409
+ encode: (entry) => entry.range
35410
+ })));
35411
+ const homebrewDescription = (noun) => `${noun}, keyed by name. Each is \`"*"\` or \`"latest"\`, \`{ range, platforms?, machines? }\` to target it, ${VARIANTS}. A tapped name (\`owner/tap/name\`) taps on install.`;
35412
+ const BrewSection = section({}, HomebrewItems.check(makeFilter((items) => !Object.hasOwn(items, "brew") || "\"brew\" is Homebrew itself", {
35253
35413
  title: "Formulae",
35254
35414
  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\`.` }) });
35415
+ })), homebrewDescription("Formulae"));
35416
+ const CaskSection = section({}, HomebrewItems, homebrewDescription("Casks"));
35417
+ const DEFAULT_CLONE_ROOT = "~/.local/share/fleetfizz/clones";
35418
+ const ReposSection = section({ cloneRoot: optionalKey(variants(withShorthand(Struct({
35419
+ ...Targeting,
35420
+ path: NonEmptyString
35421
+ }), NonEmptyString, {
35422
+ decode: (path) => ({ path }),
35423
+ encode: (root) => root.path
35424
+ }))).annotateKey({ description: `Where a Machine keeps its Managed Clones: a path, or a list of \`{ path, platforms?, machines? }\` where a Machine gets the first that targets it. \`~\` expands on that Machine. Default: \`${DEFAULT_CLONE_ROOT}\`.` }) }, Record(NonEmptyString, variants(TrackedRepo)), `Tracked Repos, keyed by name, which names the Managed Clone and the \`repos:<name>\` Item. Each is \`{ origin, ref, install, ... }\`, ${VARIANTS}.`);
35258
35425
  const FleetConfig = Struct({
35259
35426
  $schema: optionalKey(String$5),
35260
35427
  shippedAt: optionalKey(String$5),
35261
35428
  shippedFrom: optionalKey(NonEmptyString),
35262
35429
  shippedTo: optionalKey(Record(NonEmptyString, String$5)),
35263
- machines: Record(NonEmptyString, MachineSettings).check(makeFilter((machines) => Object.keys(machines).length > 0, {
35264
- title: "Fleet",
35265
- description: "at least one Machine"
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." }),
35430
+ machines: ArraySchema(NonEmptyString).check(isMinLength(1, { message: "list at least one Machine" }), isUnique({ message: "list each Machine once" })).annotateKey({ description: "The Fleet: each Machine's tailnet name. A tailnet member that is not listed is not part of the Fleet." }),
35431
+ repos: optionalKey(ReposSection).annotateKey({ description: "Each Tracked Repo is cloned on every targeted Machine and installed from its origin ref." }),
35268
35432
  vp: optionalKey(VpSection).annotateKey({ description: "The vite-plus toolchain." }),
35269
35433
  brew: optionalKey(BrewSection).annotateKey({ description: "Homebrew Formulae. Needs Homebrew on the Machine; a Ship never installs it." }),
35270
35434
  cask: optionalKey(CaskSection).annotateKey({ description: "Homebrew Casks, macOS only. Needs Homebrew on the Machine." })
35271
35435
  });
35272
- const repoName = (repo) => repo.name ?? repo.origin.replace("/", "-");
35273
35436
  const SELF_PACKAGE = "fleetfizz";
35274
35437
  //#endregion
35275
35438
  //#region ../../packages/config/src/jsonSchema.ts
@@ -35302,17 +35465,33 @@ var ConfigNotFound = class extends TaggedError()("ConfigNotFound", { path: Strin
35302
35465
  }
35303
35466
  };
35304
35467
  const lineOf = (text, offset) => text.slice(0, offset).split("\n").length;
35305
- const semanticProblems = (config) => {
35468
+ const repeatedKeys = (text) => {
35306
35469
  const problems = [];
35307
- const seen = /* @__PURE__ */ new Set();
35308
- for (const repo of config.repos ?? []) {
35309
- const name = repoName(repo);
35310
- if (seen.has(name)) problems.push(`repos: duplicate name "${name}"`);
35311
- seen.add(name);
35312
- }
35313
- if (config.vp?.globals?.["fleetfizz"] !== void 0) problems.push(`vp.globals: ${SELF_PACKAGE} is pinned by the running version; remove it`);
35470
+ const visit = (node, path) => {
35471
+ if (node.type === "array") node.children?.forEach((child, i) => visit(child, [...path, i]));
35472
+ if (node.type !== "object") return;
35473
+ const seen = /* @__PURE__ */ new Set();
35474
+ for (const [key, value] of (node.children ?? []).map((property) => property.children ?? [])) {
35475
+ if (key === void 0) continue;
35476
+ const name = String(key.value);
35477
+ if (seen.has(name)) problems.push(`line ${lineOf(text, key.offset)}: "${name}" is repeated${path.length > 0 ? ` in ${path.join(".")}` : ""}`);
35478
+ seen.add(name);
35479
+ if (value !== void 0) visit(value, [...path, name]);
35480
+ }
35481
+ };
35482
+ const tree = parseTree(text, void 0, { allowTrailingComma: true });
35483
+ if (tree !== void 0) visit(tree, []);
35314
35484
  return problems;
35315
35485
  };
35486
+ const movedKeys = (parsed) => {
35487
+ if (typeof parsed !== "object" || parsed === null) return [];
35488
+ const config = parsed;
35489
+ return [
35490
+ ...typeof config.machines === "object" && config.machines !== null && !Array.isArray(config.machines) ? ["machines is now a list of names, and machines.<name>.clones is repos.cloneRoot"] : [],
35491
+ ...Array.isArray(config.repos) ? ["repos is now { items: { <name>: { origin, ref, install } } }, keyed by each repo's name"] : [],
35492
+ ...typeof config.vp === "object" && config.vp !== null && "globals" in config.vp ? ["vp.globals is now vp.items"] : []
35493
+ ];
35494
+ };
35316
35495
  const parseConfig = fn("parseConfig")(function* (path, text) {
35317
35496
  const parseErrors = [];
35318
35497
  const parsed = parse(text, parseErrors, { allowTrailingComma: true });
@@ -35320,16 +35499,20 @@ const parseConfig = fn("parseConfig")(function* (path, text) {
35320
35499
  path,
35321
35500
  problems: parseErrors.map((e) => `line ${lineOf(text, e.offset)}: ${printParseErrorCode(e.error)}`)
35322
35501
  });
35323
- const config = yield* decodeUnknownEffect(FleetConfig, { onExcessProperty: "error" })(parsed).pipe(mapError((error) => new ConfigError({
35502
+ const moved = movedKeys(parsed);
35503
+ if (moved.length > 0) return yield* new ConfigError({
35324
35504
  path,
35325
- problems: [formatIssue(error.issue)]
35326
- })));
35327
- const problems = semanticProblems(config);
35328
- if (problems.length > 0) return yield* new ConfigError({
35505
+ problems: moved
35506
+ });
35507
+ const repeated = repeatedKeys(text);
35508
+ if (repeated.length > 0) return yield* new ConfigError({
35329
35509
  path,
35330
- problems
35510
+ problems: repeated
35331
35511
  });
35332
- return config;
35512
+ return yield* decodeUnknownEffect(FleetConfig, { onExcessProperty: "error" })(parsed).pipe(mapError((error) => new ConfigError({
35513
+ path,
35514
+ problems: [formatIssue(error.issue)]
35515
+ })));
35333
35516
  });
35334
35517
  const formatIssue = makeFormatterDefault();
35335
35518
  const loadConfig = fn("loadConfig")(function* (path) {
@@ -35371,7 +35554,7 @@ const stampText = (text, stamp) => {
35371
35554
  const machinesOf = (config) => {
35372
35555
  if (typeof config !== "object" || config === null || !("machines" in config)) return [];
35373
35556
  const value = config.machines;
35374
- return typeof value === "object" && value !== null ? Object.keys(value) : [];
35557
+ return Array.isArray(value) ? value.filter((name) => typeof name === "string") : [];
35375
35558
  };
35376
35559
  const shippedToOf = (config) => {
35377
35560
  if (typeof config !== "object" || config === null || !("shippedTo" in config)) return {};
@@ -63826,24 +64009,46 @@ const useWindowSize = () => {
63826
64009
  //#endregion
63827
64010
  //#region ../../packages/core/src/targeting.ts
63828
64011
  const targets = (targeting, machine) => (targeting.platforms?.includes(machine.platform) ?? true) && (targeting.machines?.includes(machine.name) ?? true);
64012
+ const isVariantList = (entry) => Array.isArray(entry);
64013
+ const variantFor = (entry, machine) => {
64014
+ if (!isVariantList(entry)) return targets(entry, machine) ? { variant: entry } : void 0;
64015
+ const index = entry.findIndex((variant) => targets(variant, machine));
64016
+ const variant = entry[index];
64017
+ return variant === void 0 ? void 0 : {
64018
+ variant,
64019
+ index
64020
+ };
64021
+ };
63829
64022
  //#endregion
63830
64023
  //#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
- };
64024
+ const dependenciesOf = (provider, config) => [.../* @__PURE__ */ new Set([...provider.dependsOn, ...config[provider.name]?.dependsOn ?? []])];
64025
+ const withoutTargeting = ({ platforms: _platforms, machines: _machines, ...def }) => def;
63838
64026
  const targetedItems = (provider, config, machine) => {
63839
64027
  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));
64028
+ if (section === void 0 || !targets(section, machine)) return provider.items(void 0, []);
64029
+ const listed = Object.entries(section.items ?? {}).flatMap(([name, entry]) => {
64030
+ const chosen = variantFor(entry, machine);
64031
+ if (chosen === void 0) return [];
64032
+ const at = chosen.index === void 0 ? [] : [chosen.index];
64033
+ return [{
64034
+ name,
64035
+ def: withoutTargeting(chosen.variant),
64036
+ path: [
64037
+ provider.name,
64038
+ "items",
64039
+ name,
64040
+ ...at
64041
+ ]
64042
+ }];
64043
+ });
64044
+ return provider.items(section, listed);
63842
64045
  };
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 ?? [])];
64046
+ const machineNames = (value) => {
64047
+ if (Array.isArray(value)) return value.flatMap(machineNames);
64048
+ if (typeof value !== "object" || value === null) return [];
64049
+ return Object.entries(value).flatMap(([key, v]) => key === "machines" && Array.isArray(v) && v.every((name) => typeof name === "string") ? v : machineNames(v));
63846
64050
  };
64051
+ const machinesNamed = (provider, config) => machineNames(config[provider.name]);
63847
64052
  const hold = (decisions, by) => decisions.map((d) => d.kind === "change" || d.kind === "ask" ? {
63848
64053
  kind: "skip",
63849
64054
  item: d.item,
@@ -64031,7 +64236,6 @@ const orderProviders = (all, dependsOn) => {
64031
64236
  };
64032
64237
  //#endregion
64033
64238
  //#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
64239
  const decideInstall = (record, now, rebuild) => {
64036
64240
  if (rebuild) return {
64037
64241
  run: true,
@@ -64053,10 +64257,6 @@ const decideInstall = (record, now, rebuild) => {
64053
64257
  run: true,
64054
64258
  reason: "install command changed"
64055
64259
  };
64056
- if (!sameRecord(record.files, now.files)) return {
64057
- run: true,
64058
- reason: "files changed"
64059
- };
64060
64260
  for (const [path, fingerprint] of Object.entries(now.outputs)) if (fingerprint === void 0 || record.outputs[path] !== fingerprint) return {
64061
64261
  run: true,
64062
64262
  reason: `drift: ${path}`
@@ -65122,7 +65322,7 @@ const decodeFact$2 = decodeUnknownOption(Union([Struct({
65122
65322
  version: NullOr(String$5)
65123
65323
  }), AtFact]));
65124
65324
  const NO_BREW = "Homebrew is not installed; install it first";
65125
- const homebrew = (name) => {
65325
+ function homebrew(name) {
65126
65326
  const flag = name === "brew" ? "--formula" : "--cask";
65127
65327
  const lookup = name === "brew" ? "\"$ff_i\"" : "\"${ff_i##*/}\"";
65128
65328
  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`;
@@ -65145,17 +65345,13 @@ const homebrew = (name) => {
65145
65345
  name,
65146
65346
  rule: "range",
65147
65347
  dependsOn: name === "cask" ? ["brew"] : [],
65148
- items(section) {
65348
+ items(section, listed) {
65149
65349
  if (section === void 0) return [];
65150
- const packages = Object.entries(section.items ?? {}).map(([item, entry]) => typeof entry === "string" ? {
65350
+ const packages = listed.map(({ name: item, def }) => ({
65151
65351
  name: item,
65152
65352
  kind: "package",
65153
- range: entry
65154
- } : {
65155
- ...entry,
65156
- name: item,
65157
- kind: "package"
65158
- });
65353
+ range: def.range
65354
+ }));
65159
65355
  return name === "brew" ? [{
65160
65356
  name: "brew",
65161
65357
  kind: "homebrew"
@@ -65247,7 +65443,7 @@ const homebrew = (name) => {
65247
65443
  });
65248
65444
  }
65249
65445
  };
65250
- };
65446
+ }
65251
65447
  const brew = homebrew("brew");
65252
65448
  const cask = homebrew("cask");
65253
65449
  const decodeFact$1 = decodeUnknownOption(Union([
@@ -65274,15 +65470,17 @@ const decodeRecord = decodeUnknownOption(Struct({
65274
65470
  commit: String$5,
65275
65471
  path: String$5,
65276
65472
  install: String$5,
65277
- files: Record(String$5, String$5),
65278
65473
  outputs: Record(String$5, String$5)
65279
65474
  }));
65280
- const cloneRoot = (config, machine) => config.machines[machine]?.clones ?? "~/.local/share/fleetfizz/clones";
65475
+ const branchKey = (repo) => `${repo.origin}#${repo.ref}`;
65476
+ const cloneRoot = (config, machine) => {
65477
+ const root = config.repos?.cloneRoot;
65478
+ return (root === void 0 ? void 0 : variantFor(root, machine))?.variant.path ?? "~/.local/share/fleetfizz/clones";
65479
+ };
65281
65480
  const originUrl = (origin) => `https://github.com/${origin}.git`;
65282
65481
  const short = (commit) => commit.slice(0, 7);
65283
65482
  const expandHome = (path, home) => path === "~" ? home : path.startsWith("~/") ? `${home}${path.slice(1)}` : path;
65284
65483
  const jsonString = encodeSync(fromJsonString(String$5));
65285
- const jsonRecord = encodeSync(fromJsonString(Record(String$5, String$5)));
65286
65484
  const lsRemote = fn("lsRemote")(function* (repo, token) {
65287
65485
  const spawner = yield* ChildProcessSpawner;
65288
65486
  const auth = token === void 0 ? [] : [
@@ -65346,7 +65544,7 @@ const decideRepo = (repo, root, observed, resolved, ctx) => {
65346
65544
  if (observed.cloneRoot?.present === false && !root.startsWith("~")) return fail(`clone root not mounted: ${observed.cloneRoot.path}`);
65347
65545
  const url = originUrl(repo.origin);
65348
65546
  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];
65547
+ const target = isPin(repo.ref) ? { commit: repo.ref } : resolved[branchKey(repo)];
65350
65548
  if (target === void 0) return fail(`could not resolve ${repo.ref}`);
65351
65549
  if ("error" in target) return fail(target.error);
65352
65550
  const { commit } = target;
@@ -65355,7 +65553,6 @@ const decideRepo = (repo, root, observed, resolved, ctx) => {
65355
65553
  commit,
65356
65554
  path: expandHome(`${root}/${repo.name}`, ctx.home),
65357
65555
  install: repo.install,
65358
- files: repo.files ?? {},
65359
65556
  outputs: Object.fromEntries((repo.outputs ?? []).map((o) => [o, outputs?.get(o)]))
65360
65557
  }, ctx.rebuild(address("repos", repo.name)));
65361
65558
  const reason = install.run ? install.reason : !clone?.present ? "not cloned" : clone.head !== commit ? `checked out at ${short(clone.head ?? "")}` : void 0;
@@ -65387,7 +65584,6 @@ const fetchStep = ({ item, root, commit }) => {
65387
65584
  `git -C "$ff_d" checkout --quiet --detach ${quote(commit)}`,
65388
65585
  `git -C "$ff_d" reset --quiet --hard ${quote(commit)}`
65389
65586
  ] : [`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
65587
  return {
65392
65588
  id: `repos:${item.name}:fetch`,
65393
65589
  items: [item.name],
@@ -65400,11 +65596,7 @@ const fetchStep = ({ item, root, commit }) => {
65400
65596
  " set -e",
65401
65597
  ` if [ ! -d "$ff_d/.git" ]; then ff_git clone --quiet ${quote(url)} "$ff_d"; fi`,
65402
65598
  ` 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}`),
65599
+ ...[...sync, `git -C "$ff_d" clean --quiet -fd`].map((l) => ` ${l}`),
65408
65600
  ")"
65409
65601
  ]
65410
65602
  };
@@ -65413,7 +65605,7 @@ const installStep = ({ item, root }, ctx) => {
65413
65605
  const env = [
65414
65606
  `FLEETFIZZ_MACHINE=${quote(ctx.machine.name)}`,
65415
65607
  `FLEETFIZZ_PLATFORM=${quote(ctx.machine.platform)}`,
65416
- `FLEETFIZZ_MACHINES=${quote(Object.keys(ctx.config.machines).join(","))}`
65608
+ `FLEETFIZZ_MACHINES=${quote(ctx.config.machines.join(","))}`
65417
65609
  ].join(" ");
65418
65610
  const tmp = `${recordDir("repos")}/${quote(`${item.name}.json.tmp`)}`;
65419
65611
  const record = [
@@ -65421,7 +65613,7 @@ const installStep = ({ item, root }, ctx) => {
65421
65613
  `ff_outs=""`,
65422
65614
  ...(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
65615
  `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}`,
65616
+ `printf '%s\\n' "{\\"commit\\":\\"$ff_after\\",\\"path\\":\\"$(ff_json "$ff_d")\\",\\"install\\":$(ff_lit ${quote(jsonString(item.install))}),\\"outputs\\":{\${ff_outs#,}}}" > ${tmp}`,
65425
65617
  `mv -f ${tmp} ${recordFile("repos", item.name)}`,
65426
65618
  `rm -f ${quotePath(RECORDS_DIR)}/${quote(`${item.name}.json`)}`
65427
65619
  ];
@@ -65450,11 +65642,11 @@ const repos = {
65450
65642
  name: "repos",
65451
65643
  rule: "record",
65452
65644
  dependsOn: ["vp"],
65453
- items: (section) => (section ?? []).map((repo) => ({
65454
- ...repo,
65455
- name: repoName(repo)
65645
+ items: (_section, listed) => listed.map(({ name, def }) => ({
65646
+ ...def,
65647
+ name
65456
65648
  })),
65457
- observe: (items, scope) => observe(items, cloneRoot(scope.config, scope.machine.name)),
65649
+ observe: (items, scope) => observe(items, cloneRoot(scope.config, scope.machine)),
65458
65650
  parse(facts, records) {
65459
65651
  let root;
65460
65652
  const clones = /* @__PURE__ */ new Map();
@@ -65497,9 +65689,9 @@ const repos = {
65497
65689
  records: decoded
65498
65690
  };
65499
65691
  },
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))),
65692
+ resolve: (items, { token }) => forEach$1([...new Map(items.filter((r) => !isPin(r.ref)).map((r) => [branchKey(r), r])).entries()], ([key, repo]) => lsRemote(repo, token).pipe(map$5((result) => [key, result])), { concurrency: 8 }).pipe(map$5((entries) => Object.fromEntries(entries))),
65501
65693
  decide: ({ items, observed, resolved, ctx }) => {
65502
- const root = cloneRoot(ctx.config, ctx.machine.name);
65694
+ const root = cloneRoot(ctx.config, ctx.machine);
65503
65695
  return items.map((repo) => decideRepo(repo, root, observed, resolved, ctx));
65504
65696
  },
65505
65697
  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)] : [])
@@ -65687,28 +65879,14 @@ const PROVIDERS = [
65687
65879
  name: "vp",
65688
65880
  rule: "range",
65689
65881
  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
- });
65882
+ items(section, listed) {
65883
+ const globals = listed.map(({ name, def, path }) => ({
65884
+ name,
65885
+ kind: "global",
65886
+ packages: [name],
65887
+ range: def.range,
65888
+ bumpPath: [...path, "range"]
65889
+ }));
65712
65890
  const defaults = ["node", ...PACKAGE_MANAGERS].flatMap((tool) => {
65713
65891
  const range = section?.[tool];
65714
65892
  return range === void 0 ? [] : [{
@@ -66080,23 +66258,33 @@ const askQuestions = fn("askQuestions")(function* (input) {
66080
66258
  for (const group of groups.values()) {
66081
66259
  const question = group[0]?.question;
66082
66260
  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}`);
66261
+ const bumps = [...Map.groupBy(group.flatMap(({ question: q }) => q.bump === void 0 ? [] : [{
66262
+ ...q.bump,
66263
+ found: q.found
66264
+ }]), (bump) => JSON.stringify(bump.path)).values()].flatMap((list) => {
66265
+ const first = list[0];
66266
+ return first === void 0 ? [] : [{
66267
+ path: first.path,
66268
+ range: first.range,
66269
+ newest: newestOf(list.map((b) => b.found))
66270
+ }];
66271
+ });
66272
+ const where = [...Map.groupBy(group, (a) => a.question.problem)].map(([problem, list]) => `${list.map((a) => `${a.question.found} on ${a.machine}`).join(", ")} ${problem}`).join("; ");
66273
+ yield* input.ledger.note("ask", `${question.subject} ${where}`);
66086
66274
  if (input.dryRun) {
66087
66275
  yield* input.ledger.note("answer", "not asked (dry run)");
66088
66276
  continue;
66089
66277
  }
66090
66278
  const choices = question.choices.map((c) => ({
66091
66279
  value: c.value,
66092
- title: c.value === "bump" && question.bump !== void 0 ? `bump the config to ${bumpedRange(question.bump.range, newest)}` : c.title
66280
+ title: c.value === "bump" && bumps.length > 0 ? `bump the config to ${bumps.map((b) => bumpedRange(b.range, b.newest)).join(" and ")}` : c.title
66093
66281
  }));
66094
66282
  const answer = input.answer(question.kind, question.subject) ?? (input.ask === void 0 ? question.default : yield* input.ask(choices));
66095
66283
  yield* input.ledger.note("answer", choices.find((c) => c.value === answer)?.title ?? answer);
66096
66284
  answered.push({
66097
66285
  question,
66098
66286
  machines: group.map((a) => a.machine),
66099
- newest,
66287
+ bumps,
66100
66288
  answer
66101
66289
  });
66102
66290
  }
@@ -66110,7 +66298,7 @@ var ShipError = class extends Error {
66110
66298
  const unknownMachines = (config, tailnet, only) => {
66111
66299
  const known = new Set(tailnet.fleet.map((m) => m.name));
66112
66300
  const referenced = [
66113
- ...Object.keys(config.machines),
66301
+ ...config.machines,
66114
66302
  ...PROVIDERS.flatMap((p) => machinesNamed(p, config)),
66115
66303
  ...only
66116
66304
  ];
@@ -66119,7 +66307,7 @@ const unknownMachines = (config, tailnet, only) => {
66119
66307
  const unshippable = (config, tailnet, only, source) => {
66120
66308
  const unknown = unknownMachines(config, tailnet, only);
66121
66309
  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));
66310
+ const fleet = new Set(config.machines);
66123
66311
  if (!fleet.has(tailnet.self.name)) return `this Machine (${tailnet.self.name}) is not in machines in ${source}`;
66124
66312
  const outsideFleet = only.filter((name) => !fleet.has(name));
66125
66313
  if (outsideFleet.length > 0) return `not in machines in ${source}: ${outsideFleet.join(", ")}`;
@@ -66167,9 +66355,9 @@ const shipPass = fn("shipPass")(function* (flags) {
66167
66355
  const tailnet = yield* readTailnet();
66168
66356
  const problem = unshippable(config, tailnet, flags.only, path);
66169
66357
  if (problem !== void 0) return yield* fail$1(new ShipError(problem));
66170
- const fleet = new Set(Object.keys(config.machines));
66358
+ const fleet = new Set(config.machines);
66171
66359
  const own = flags.config === void 0 ? tailnet.self.name : void 0;
66172
- const order = Object.keys(config.machines);
66360
+ const order = config.machines;
66173
66361
  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));
66174
66362
  const count = `${targetsList.length} machine${targetsList.length === 1 ? "" : "s"}`;
66175
66363
  yield* log(`${flags.dryRun ? "dry run, " : ""}${count} from ${tailnet.self.name}`);
@@ -66278,14 +66466,10 @@ const shipPass = fn("shipPass")(function* (flags) {
66278
66466
  }
66279
66467
  }
66280
66468
  }
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
- });
66469
+ const targeted = providers.map((provider) => ({
66470
+ provider,
66471
+ items: checked.flatMap(({ machine }) => targetedItems(provider, config, machine))
66472
+ }));
66289
66473
  const lent = targeted.some(({ provider, items }) => provider.name === "repos" && items.length > 0) ? yield* readLentToken() : { token: void 0 };
66290
66474
  if (lent.token === void 0 && lent.reason !== void 0) yield* ledger.note("info", `no gh token (${lent.reason}); private repos will fail`);
66291
66475
  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" }));
@@ -66324,15 +66508,17 @@ const shipPass = fn("shipPass")(function* (flags) {
66324
66508
  dryRun: flags.dryRun
66325
66509
  });
66326
66510
  let progressed = false;
66327
- for (const { question, newest, answer: choice } of answered) {
66511
+ for (const { question, bumps, answer: choice } of answered) {
66328
66512
  const key = `${question.kind}:${question.subject}`;
66329
66513
  progressed ||= !decided.has(key);
66330
66514
  decided.set(key, choice);
66331
- if (choice !== "bump" || question.bump === void 0) continue;
66332
- changing = true;
66333
- const range = yield* bumpConfig(path, question.bump.path, newest);
66334
- progressed ||= range !== question.bump.range;
66335
- cfg = yield* loadConfig(path);
66515
+ if (choice !== "bump") continue;
66516
+ for (const bump of bumps) {
66517
+ changing = true;
66518
+ const range = yield* bumpConfig(path, bump.path, bump.newest);
66519
+ progressed ||= range !== bump.range;
66520
+ }
66521
+ if (bumps.length > 0) cfg = yield* loadConfig(path);
66336
66522
  }
66337
66523
  if (!progressed) break;
66338
66524
  planned = planAll(cfg, answer);