@gjermundgaraba/upup 0.3.0 → 0.4.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.
package/dist/main.mjs CHANGED
@@ -20,7 +20,7 @@ import { BACKGROUND_CONTEXT, withAbortSignal } from "@earendil-works/chord/conte
20
20
  import { Type } from "@earendil-works/pi-ai";
21
21
  import { GenerationTask, Harness, ToolTask, createRegistry, defineDoc, defineExtension, defineTool, hook, section, watchEvents } from "@earendil-works/pi-durable";
22
22
  import { openNodeSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/node";
23
- import { ChildProcess, ChildProcessSpawner } from "effect/process";
23
+ import { ChildProcess, ChildProcessSpawner, CommandLine } from "effect/process";
24
24
  import { Worker } from "node:worker_threads";
25
25
  import semver from "semver";
26
26
  import tarStream from "tar-stream";
@@ -41,8 +41,9 @@ import { XMLParser } from "fast-xml-parser";
41
41
  import { isSea } from "node:sea";
42
42
  import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
43
43
  import * as Action from "@gjermundgaraba/effect-actions/Action";
44
+ import { setup, types } from "xstate/fsm";
44
45
  // package.json
45
- var version$1 = "0.3.0";
46
+ var version$1 = "0.4.0";
46
47
  // src/ai/Setup.ts
47
48
  const Thinking = Schema.Literals([
48
49
  "off",
@@ -283,7 +284,7 @@ const ordered = (left, right) => Number(right.signedIn) - Number(left.signedIn)
283
284
  var AiFailed = class extends Schema.TaggedError()("AiFailed", { issue: Schema.String }) {};
284
285
  var Ai = class extends Context.Service()("upup/Ai") {};
285
286
  const failed$2 = (cause) => new AiFailed({ issue: cause instanceof Error ? cause.message : String(cause) });
286
- const make$14 = (models, credentials = store()) => Effect.gen(function* () {
287
+ const make$15 = (models, credentials = store()) => Effect.gen(function* () {
287
288
  const settings = yield* Settings;
288
289
  const catalogue = Effect.promise(async () => {
289
290
  const providers = models.getProviders();
@@ -328,7 +329,7 @@ const make$14 = (models, credentials = store()) => Effect.gen(function* () {
328
329
  });
329
330
  const layer$7 = Layer.effect(Ai, Effect.suspend(() => {
330
331
  const credentials = store();
331
- return make$14(builtinModels({ credentials }), credentials);
332
+ return make$15(builtinModels({ credentials }), credentials);
332
333
  }));
333
334
  // src/process/lock.ts
334
335
  var LockFailed = class extends Schema.TaggedError()("LockFailed", {
@@ -519,7 +520,7 @@ var CommandFailed = class extends Schema.TaggedError()("CommandFailed", {
519
520
  output: Schema.String
520
521
  }) {};
521
522
  var Exec = class extends Context.Service()("upup/Exec") {};
522
- const commandLine = (invocation) => [invocation.command, ...invocation.args].join(" ");
523
+ const commandLine = (invocation) => CommandLine.display([invocation.command, ...invocation.args]);
523
524
  const requireSuccess = (run) => (invocation) => run(invocation).pipe(Effect.filterOrFail((output) => output.exitCode === 0, (output) => new CommandFailed({
524
525
  command: commandLine(invocation),
525
526
  cwd: invocation.cwd,
@@ -550,7 +551,7 @@ const layer$6 = Layer.effect(Exec, Effect.gen(function* () {
550
551
  }, Effect.scoped, (effect, invocation) => Effect.mapError(effect, (cause) => new CommandFailed({
551
552
  command: commandLine(invocation),
552
553
  cwd: invocation.cwd,
553
- output: cause.message
554
+ output: Predicate.isError(cause.cause) ? cause.cause.message : cause.reason.description ?? cause.reason._tag
554
555
  })));
555
556
  return Exec.of({
556
557
  run,
@@ -595,14 +596,14 @@ const Installed = Schema.Struct({
595
596
  formulae: Schema.Array(Formula$2),
596
597
  casks: Schema.Array(Cask$1)
597
598
  });
598
- const OutdatedEntry$1 = Schema.Struct({
599
+ const OutdatedEntry = Schema.Struct({
599
600
  name: Schema.String,
600
601
  current_version: Schema.String,
601
602
  pinned: Schema.Boolean
602
603
  });
603
- const Outdated$2 = Schema.Struct({
604
- formulae: Schema.Array(OutdatedEntry$1),
605
- casks: Schema.Array(OutdatedEntry$1)
604
+ const Outdated$1 = Schema.Struct({
605
+ formulae: Schema.Array(OutdatedEntry),
606
+ casks: Schema.Array(OutdatedEntry)
606
607
  });
607
608
  const brew$1 = (args, refreshMetadata = false) => ({
608
609
  command: "brew",
@@ -653,7 +654,7 @@ const packages = (installed, outdated) => {
653
654
  });
654
655
  return [...formulae, ...casks];
655
656
  };
656
- const make$13 = Effect.gen(function* () {
657
+ const make$14 = Effect.gen(function* () {
657
658
  const exec = yield* Exec;
658
659
  const json = (schema, invocation) => exec.stdout(invocation).pipe(Effect.flatMap(decodeJson(schema, commandLine(invocation))));
659
660
  const list = Effect.gen(function* () {
@@ -662,7 +663,7 @@ const make$13 = Effect.gen(function* () {
662
663
  "--json=v2",
663
664
  "--installed"
664
665
  ]));
665
- const outdated = yield* json(Outdated$2, brew$1(["outdated", "--json=v2"], true));
666
+ const outdated = yield* json(Outdated$1, brew$1(["outdated", "--json=v2"], true));
666
667
  return {
667
668
  packages: packages(installed, outdated),
668
669
  notices: []
@@ -695,7 +696,7 @@ const make$13 = Effect.gen(function* () {
695
696
  exitCode: output.exitCode,
696
697
  output: output.stderr
697
698
  });
698
- const outdated = yield* decodeJson(Outdated$2, commandLine(invocation))(output.stdout);
699
+ const outdated = yield* decodeJson(Outdated$1, commandLine(invocation))(output.stdout);
699
700
  return [...outdated.formulae, ...outdated.casks].find((entry) => entry.name === pkg.name)?.current_version;
700
701
  });
701
702
  return {
@@ -797,7 +798,7 @@ const landingKey = (landing) => sha256(JSON.stringify([
797
798
  ]));
798
799
  const unauditedReasons = {
799
800
  app: "an app update",
800
- runtime: "a runtime version the project pins",
801
+ runtime: "a Node.js runtime version",
801
802
  os: "an operating-system update"
802
803
  };
803
804
  const unauditable = (pkg) => {
@@ -2268,7 +2269,7 @@ var LookupFailed = class extends Schema.TaggedError()("LookupFailed", {
2268
2269
  url: Schema.String,
2269
2270
  issue: Schema.String
2270
2271
  }) {};
2271
- const Release$2 = Schema.fromJsonString(Schema.Struct({
2272
+ const Release$3 = Schema.fromJsonString(Schema.Struct({
2272
2273
  vers: Schema.String,
2273
2274
  yanked: Schema.Boolean
2274
2275
  }));
@@ -2285,7 +2286,7 @@ const newestRelease = Effect.fn("CratesIndex.newestRelease")(function* (client,
2285
2286
  issue: error.message
2286
2287
  });
2287
2288
  const text = yield* HttpClient.filterStatusOk(client).get(url).pipe(Effect.flatMap((response) => response.text), Effect.mapError(failed));
2288
- const releases = yield* Effect.forEach(text.split("\n").filter((line) => line.trim().length > 0), (line) => Schema.decodeEffect(Release$2)(line).pipe(Effect.mapError(failed)));
2289
+ const releases = yield* Effect.forEach(text.split("\n").filter((line) => line.trim().length > 0), (line) => Schema.decodeEffect(Release$3)(line).pipe(Effect.mapError(failed)));
2289
2290
  return newestOffer(current, releases.filter((release) => !release.yanked).map((release) => release.vers));
2290
2291
  });
2291
2292
  // src/registry/Metadata.ts
@@ -6447,7 +6448,7 @@ const githubActionPreparer = (repositoryOf, options = {}) => Effect.fn("githubAc
6447
6448
  const prepareGitHubAction = githubActionPreparer(githubRepository);
6448
6449
  // src/audit/pypi.ts
6449
6450
  const optional = (schema) => Schema.optionalKey(Schema.NullOr(schema));
6450
- const Release$1 = Schema.Struct({ urls: optional(Schema.Array(Schema.Struct({
6451
+ const Release$2 = Schema.Struct({ urls: optional(Schema.Array(Schema.Struct({
6451
6452
  filename: optional(Schema.String),
6452
6453
  url: optional(Schema.String),
6453
6454
  packagetype: optional(Schema.String),
@@ -6538,7 +6539,7 @@ const artifactPlan = ([file, reason]) => {
6538
6539
  };
6539
6540
  const resolve$1 = Effect.fn("pypi.resolve")(function* (client, name, version, pythonVersion) {
6540
6541
  const source = releaseUrl(name, version);
6541
- const files = ((yield* getJson(client, source, Release$1).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6542
+ const files = ((yield* getJson(client, source, Release$2).pipe(Effect.mapError((error) => new EvidenceUnavailable({
6542
6543
  source,
6543
6544
  issue: error.issue
6544
6545
  })))).urls ?? []).map((file) => ({
@@ -7748,7 +7749,7 @@ const toPackage$1 = ({ uses, resolved, source }) => {
7748
7749
  };
7749
7750
  };
7750
7751
  const publicTags = (url) => tags(url, { timeout: lsRemoteTimeout }).pipe(Effect.mapError((error) => error.issue), Effect.result);
7751
- const make$12 = (root, origin, readPublic = publicTags) => Effect.gen(function* () {
7752
+ const make$13 = (root, origin, readPublic = publicTags) => Effect.gen(function* () {
7752
7753
  const exec = yield* Exec;
7753
7754
  const fs = yield* FileSystem.FileSystem;
7754
7755
  const read = (file) => readText(file).pipe(Effect.provideService(FileSystem.FileSystem, fs));
@@ -7884,7 +7885,7 @@ const discover$4 = Effect.fn("GitHubActions.discover")(function* (dir) {
7884
7885
  cwd: root
7885
7886
  }).pipe(Effect.option);
7886
7887
  const host = Option.isSome(origin) ? hostOf(origin.value.trim()) : void 0;
7887
- return [yield* make$12(root, host === "github.com" ? void 0 : host)];
7888
+ return [yield* make$13(root, host === "github.com" ? void 0 : host)];
7888
7889
  });
7889
7890
  // src/process/on-path.ts
7890
7891
  const executable = (file) => Effect.promise(async () => await access(file, constants.X_OK).then(() => true, () => false));
@@ -7898,7 +7899,7 @@ const asRoot = (invocation) => [
7898
7899
  ];
7899
7900
  const enablesTouchId = (pam) => pam.split("\n").some((line) => /^\s*auth\s+\S+\s+pam_tid\.so\b/u.test(line));
7900
7901
  const pamFiles = ["/etc/pam.d/sudo_local", "/etc/pam.d/sudo"];
7901
- const make$11 = (interactive) => Effect.gen(function* () {
7902
+ const make$12 = (interactive) => Effect.gen(function* () {
7902
7903
  const exec = yield* Exec;
7903
7904
  const fs = yield* FileSystem.FileSystem;
7904
7905
  const asking = yield* SubscriptionRef.make(void 0);
@@ -7957,8 +7958,8 @@ const make$11 = (interactive) => Effect.gen(function* () {
7957
7958
  answer
7958
7959
  });
7959
7960
  });
7960
- const layer$4 = Layer.effect(Sudo, make$11(true));
7961
- const layerNoPrompt = Layer.effect(Sudo, make$11(false));
7961
+ const layer$4 = Layer.effect(Sudo, make$12(true));
7962
+ const layerNoPrompt = Layer.effect(Sudo, make$12(false));
7962
7963
  // src/apps/app-store.ts
7963
7964
  const countryOf = (locale) => (/[@;]rg=(?<region>[a-z]{2})/iu.exec(locale)?.groups?.["region"] ?? /^[a-z]{2,3}[_-](?<region>[a-z]{2})\b/iu.exec(locale)?.groups?.["region"] ?? "us").toLowerCase();
7964
7965
  const Lookup = Schema.Struct({ results: Schema.Array(Schema.Struct({
@@ -8750,7 +8751,7 @@ const locate$3 = Effect.gen(function* () {
8750
8751
  return Option.isNone(manifest) || !(yield* fs.exists(zip).pipe(Effect.orElseSucceed(() => false))) ? Option.none() : yield* decode(manifest.value, zip);
8751
8752
  });
8752
8753
  // src/apps/swap.ts
8753
- const tools = {
8754
+ const tools$2 = {
8754
8755
  ps: "/bin/ps",
8755
8756
  ditto: "/usr/bin/ditto",
8756
8757
  mv: "/bin/mv",
@@ -9242,7 +9243,7 @@ const makeWith = (options) => Effect.gen(function* () {
9242
9243
  return found;
9243
9244
  });
9244
9245
  const queue = yield* Ref.make([]);
9245
- const using = options.tools ?? tools;
9246
+ const using = options.tools ?? tools$2;
9246
9247
  const helper = options.sparkle === void 0 ? locate$3.pipe(Effect.provideService(FileSystem.FileSystem, fs)) : Effect.succeed(options.sparkle);
9247
9248
  const sparkleOf = (pkg, install, build) => {
9248
9249
  const refused = (reason) => new InstallRefused({
@@ -9442,7 +9443,7 @@ const makeWith = (options) => Effect.gen(function* () {
9442
9443
  })
9443
9444
  };
9444
9445
  });
9445
- const make$10 = onPath("brew").pipe(Effect.flatMap((brewInstalled) => makeWith({
9446
+ const make$11 = onPath("brew").pipe(Effect.flatMap((brewInstalled) => makeWith({
9446
9447
  roots: ["/Applications", path.join(homedir(), "Applications")],
9447
9448
  brew: brewInstalled
9448
9449
  })));
@@ -9468,7 +9469,7 @@ const parseList = (text) => text.split("\n").flatMap((line) => {
9468
9469
  fromCratesIo: source === void 0
9469
9470
  }];
9470
9471
  });
9471
- const make$9 = Effect.gen(function* () {
9472
+ const make$10 = Effect.gen(function* () {
9472
9473
  const exec = yield* Exec;
9473
9474
  const client = yield* HttpClient.HttpClient;
9474
9475
  const installed = exec.stdout(cargo$1(["install", "--list"])).pipe(Effect.map(parseList));
@@ -9544,18 +9545,30 @@ const locateAll = Effect.fn("locateAll")(function* (dir, nested, manifest, locat
9544
9545
  const own = yield* locate(dir);
9545
9546
  const below = yield* Effect.forEach(nested, (from) => Effect.map(Effect.result(locate(from)), (result) => [from, result]), { concurrency: 8 });
9546
9547
  const found = /* @__PURE__ */ new Map();
9548
+ const claimed = /* @__PURE__ */ new Map();
9547
9549
  const failed = [];
9548
- const add = (located) => {
9549
- if (Option.isSome(located) && !found.has(located.value.root)) found.set(located.value.root, located.value);
9550
+ const add = (from, located) => {
9551
+ if (Option.isNone(located)) return;
9552
+ const { root } = located.value;
9553
+ if (!found.has(root)) found.set(root, located.value);
9554
+ claimed.set(root, [...claimed.get(root) ?? [], from]);
9550
9555
  };
9551
- add(own);
9556
+ add(dir, own);
9552
9557
  const ownRoot = Option.map(own, (located) => located.root);
9553
9558
  const outside = (located) => Option.isSome(located) && placeOf$1(dir, located.value.root) === "" && !Option.contains(ownRoot, located.value.root);
9559
+ const failures = below.flatMap(([from, result]) => Result.isFailure(result) ? [{
9560
+ from,
9561
+ reason: describeError(result.failure)
9562
+ }] : []);
9563
+ const repeated = (from, reason) => failures.some((other) => other.reason === reason && from.startsWith(`${other.from}${path.sep}`));
9554
9564
  for (const [from, result] of below) if (Result.isSuccess(result)) {
9555
- if (!outside(result.success)) add(result.success);
9556
- } else failed.push(failing(path.join(path.relative(dir, from), manifest), result.failure));
9565
+ if (!outside(result.success)) add(from, result.success);
9566
+ } else if (!repeated(from, describeError(result.failure))) failed.push(failing(path.join(path.relative(dir, from), manifest), result.failure));
9557
9567
  return {
9558
- found: [...found.values()],
9568
+ found: [...found.values()].map((located) => ({
9569
+ ...located,
9570
+ claimed: claimed.get(located.root) ?? []
9571
+ })),
9559
9572
  failed
9560
9573
  };
9561
9574
  });
@@ -9642,10 +9655,11 @@ const entriesOf = (manifest, parsed) => {
9642
9655
  for (const [name, spec] of Object.entries(parsed.workspace?.dependencies ?? {})) add(["workspace", "dependencies"], name, spec);
9643
9656
  return entries;
9644
9657
  };
9645
- const cargo = (root, args) => ({
9658
+ const cargo = (cwd, args) => ({
9646
9659
  command: "cargo",
9647
9660
  args,
9648
- cwd: root
9661
+ cwd,
9662
+ env: { RUSTUP_AUTO_INSTALL: "0" }
9649
9663
  });
9650
9664
  const parseManifest = (file, text) => Effect.try({
9651
9665
  try: () => getStaticTOMLValue(parseTOML(text)),
@@ -9670,7 +9684,7 @@ const resolvedByMember = (root, meta) => {
9670
9684
  return [path.relative(root, pkg.manifest_path), deps];
9671
9685
  }));
9672
9686
  };
9673
- const make$8 = (root, place = "") => Effect.gen(function* () {
9687
+ const make$9 = (root, place = "") => Effect.gen(function* () {
9674
9688
  const providerName = placed("cargo", place);
9675
9689
  const exec = yield* Exec;
9676
9690
  const fs = yield* FileSystem.FileSystem;
@@ -9683,6 +9697,7 @@ const make$8 = (root, place = "") => Effect.gen(function* () {
9683
9697
  "metadata",
9684
9698
  "--format-version",
9685
9699
  "1",
9700
+ "--all-features",
9686
9701
  "--locked"
9687
9702
  ]);
9688
9703
  return yield* exec.stdout(invocation).pipe(Effect.flatMap(decodeJson(Metadata, commandLine(invocation))));
@@ -9731,6 +9746,11 @@ const make$8 = (root, place = "") => Effect.gen(function* () {
9731
9746
  };
9732
9747
  });
9733
9748
  const list = Effect.gen(function* () {
9749
+ const lockfile = path.join(root, "Cargo.lock");
9750
+ if (!(yield* fs.exists(lockfile).pipe(Effect.mapError(fileFailed("stat", lockfile))))) return {
9751
+ packages: [],
9752
+ notices: [{ message: "not listed: no Cargo.lock yet" }]
9753
+ };
9734
9754
  const { declared, notices } = yield* declarations;
9735
9755
  return {
9736
9756
  packages: yield* Effect.forEach(declared, ({ pkg }) => newestRelease(client, pkg.artifact, pkg.current).pipe(Effect.map((target) => {
@@ -9812,21 +9832,17 @@ const locate$2 = Effect.fn("CargoWorkspace.locate")(function* (dir) {
9812
9832
  const exec = yield* Exec;
9813
9833
  const manifest = yield* findUp(dir, "Cargo.toml");
9814
9834
  if (Option.isNone(manifest)) return Option.none();
9815
- const root = yield* exec.stdout({
9816
- command: "cargo",
9817
- args: [
9818
- "locate-project",
9819
- "--workspace",
9820
- "--message-format",
9821
- "plain"
9822
- ],
9823
- cwd: manifest.value
9824
- });
9835
+ const root = yield* exec.stdout(cargo(manifest.value, [
9836
+ "locate-project",
9837
+ "--workspace",
9838
+ "--message-format",
9839
+ "plain"
9840
+ ]));
9825
9841
  return Option.some({ root: path.dirname(root.trim()) });
9826
9842
  });
9827
9843
  const discover$3 = Effect.fn("CargoWorkspace.discover")(function* (dir, nested = []) {
9828
9844
  const { found, failed } = yield* locateAll(dir, nested, "Cargo.toml", locate$2);
9829
- return [...failed, ...yield* Effect.forEach(found, ({ root }) => make$8(root, placeOf$1(dir, root)))];
9845
+ return [...failed, ...yield* Effect.forEach(found, ({ root }) => make$9(root, placeOf$1(dir, root)))];
9830
9846
  });
9831
9847
  // src/registry/origin.ts
9832
9848
  const trailing = (url) => url.endsWith("/") ? url : `${url}/`;
@@ -9971,14 +9987,15 @@ const fromBuildInfo = (info, file) => {
9971
9987
  current: main.Version
9972
9988
  }) : Option.none();
9973
9989
  };
9974
- const make$7 = Effect.gen(function* () {
9990
+ const make$8 = Effect.gen(function* () {
9975
9991
  const exec = yield* Exec;
9976
9992
  const fs = yield* FileSystem.FileSystem;
9977
- const goEnv = yield* Effect.cached(exec.stdout(go([
9993
+ const envInvocation = go([
9978
9994
  "env",
9979
9995
  "-json",
9980
9996
  ...envKeys
9981
- ])).pipe(Effect.flatMap(decodeGoJson(GoEnv, `go env -json ${envKeys.join(" ")}`))));
9997
+ ]);
9998
+ const goEnv = yield* Effect.cached(exec.stdout(envInvocation).pipe(Effect.flatMap(decodeGoJson(GoEnv, commandLine(envInvocation)))));
9982
9999
  const binDir = goEnv.pipe(Effect.map((env) => env.GOBIN || path.join(env.GOPATH.split(":")[0] ?? env.GOPATH, "bin")));
9983
10000
  const inspect = Effect.fn("GoInstall.inspect")(function* (dir, file) {
9984
10001
  const invocation = go([
@@ -10066,6 +10083,7 @@ const make$7 = Effect.gen(function* () {
10066
10083
  });
10067
10084
  // src/go/GoModule.ts
10068
10085
  const ModFile = Schema.Struct({
10086
+ Module: Schema.optionalKey(Schema.Struct({ Path: Schema.String })),
10069
10087
  Require: Schema.optionalKey(Schema.Array(Schema.Struct({
10070
10088
  Path: Schema.String,
10071
10089
  Version: Schema.String,
@@ -10079,7 +10097,10 @@ const Selected = Schema.Struct({
10079
10097
  Update: Schema.optionalKey(Schema.Struct({ Version: Schema.String })),
10080
10098
  Error: Schema.optionalKey(Schema.Struct({ Err: Schema.String }))
10081
10099
  });
10082
- const WorkFile = Schema.Struct({ Use: Schema.optionalKey(Schema.Array(Schema.Struct({ DiskPath: Schema.String }))) });
10100
+ const WorkFile = Schema.Struct({
10101
+ Use: Schema.optionalKey(Schema.Array(Schema.Struct({ DiskPath: Schema.String }))),
10102
+ Replace: Schema.optionalKey(Schema.Array(Schema.Struct({ Old: Schema.Struct({ Path: Schema.String }) })))
10103
+ });
10083
10104
  const goEnv = {
10084
10105
  GOWORK: "off",
10085
10106
  GIT_TERMINAL_PROMPT: "0"
@@ -10089,26 +10110,30 @@ const GoProxyEnv = Schema.Struct({
10089
10110
  GOPRIVATE: Schema.optionalKey(Schema.String),
10090
10111
  GONOPROXY: Schema.optionalKey(Schema.String)
10091
10112
  });
10092
- const directRequirements = (mod) => {
10113
+ const directRequirements = (mod, local) => {
10093
10114
  const replaced = new Set((mod.Replace ?? []).map((replace) => replace.Old.Path));
10094
- return (mod.Require ?? []).filter((requirement) => requirement.Indirect !== true && !replaced.has(requirement.Path));
10115
+ return (mod.Require ?? []).filter((requirement) => requirement.Indirect !== true && !replaced.has(requirement.Path) && !local.has(requirement.Path));
10095
10116
  };
10096
- const make$6 = (root, moduleDir, place = "") => Effect.gen(function* () {
10117
+ const goIn = (cwd, args) => ({
10118
+ command: "go",
10119
+ args,
10120
+ cwd,
10121
+ env: goEnv
10122
+ });
10123
+ const readModFile = (exec, cwd) => {
10124
+ const edit = goIn(cwd, [
10125
+ "mod",
10126
+ "edit",
10127
+ "-json"
10128
+ ]);
10129
+ return exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(ModFile, commandLine(edit))));
10130
+ };
10131
+ const make$7 = (root, moduleDir, place = "", local = /* @__PURE__ */ new Set()) => Effect.gen(function* () {
10097
10132
  const exec = yield* Exec;
10098
10133
  const manifest = path.join(place, path.relative(root, path.join(moduleDir, "go.mod")));
10099
- const go = (args) => ({
10100
- command: "go",
10101
- args,
10102
- cwd: moduleDir,
10103
- env: goEnv
10104
- });
10134
+ const go = (args) => goIn(moduleDir, args);
10105
10135
  const requirements = Effect.gen(function* () {
10106
- const edit = go([
10107
- "mod",
10108
- "edit",
10109
- "-json"
10110
- ]);
10111
- return directRequirements(yield* exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(ModFile, commandLine(edit)))));
10136
+ return directRequirements(yield* readModFile(exec, moduleDir), local);
10112
10137
  });
10113
10138
  const selected = Effect.fn("GoModule.selected")(function* (paths, withUpdates) {
10114
10139
  if (paths.length === 0) return /* @__PURE__ */ new Map();
@@ -10211,36 +10236,56 @@ const discover$2 = Effect.fn("GoModule.discover")(function* (dir, nested = []) {
10211
10236
  const exec = yield* Exec;
10212
10237
  const { found, failed } = yield* locateAll(dir, nested, "go.mod", locate$1);
10213
10238
  const failedWorks = [];
10214
- const modules = yield* Effect.forEach(found, ({ root, work }) => Effect.gen(function* () {
10215
- if (!work) return [{
10216
- root,
10217
- moduleDir: root
10218
- }];
10219
- const edit = {
10220
- command: "go",
10221
- args: [
10222
- "work",
10223
- "edit",
10224
- "-json"
10225
- ],
10226
- cwd: root
10227
- };
10228
- const file = yield* exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(WorkFile, commandLine(edit))), Effect.result);
10239
+ const listed = /* @__PURE__ */ new Set();
10240
+ const modules = yield* Effect.forEach(found, ({ root, work, claimed }) => Effect.gen(function* () {
10241
+ if (!work) {
10242
+ listed.add(root);
10243
+ return [{
10244
+ root,
10245
+ moduleDir: root,
10246
+ local: /* @__PURE__ */ new Set()
10247
+ }];
10248
+ }
10229
10249
  const place = placeOf$1(dir, root);
10230
- if (Result.isSuccess(file)) return (file.success.Use ?? []).map((use) => ({
10231
- root,
10232
- moduleDir: path.resolve(root, use.DiskPath)
10233
- })).filter(({ moduleDir }) => place === "" || placeOf$1(dir, moduleDir) !== "");
10234
- if (place === "") return yield* file.failure;
10235
- failedWorks.push(failing(path.join(place, "go.work"), file.failure));
10250
+ const scanned = yield* Effect.gen(function* () {
10251
+ const edit = {
10252
+ command: "go",
10253
+ args: [
10254
+ "work",
10255
+ "edit",
10256
+ "-json"
10257
+ ],
10258
+ cwd: root
10259
+ };
10260
+ const file = yield* exec.stdout(edit).pipe(Effect.flatMap(decodeGoJson(WorkFile, commandLine(edit))));
10261
+ const used = (file.Use ?? []).map((use) => path.resolve(root, use.DiskPath));
10262
+ for (const moduleDir of used) listed.add(moduleDir);
10263
+ const paths = yield* Effect.forEach(used, (moduleDir) => Effect.gen(function* () {
10264
+ const mod = yield* readModFile(exec, moduleDir);
10265
+ if (mod.Module === void 0) return yield* new OutputMalformed({
10266
+ command: "go mod edit -json",
10267
+ issue: `no module path in ${path.join(moduleDir, "go.mod")}`
10268
+ });
10269
+ return mod.Module.Path;
10270
+ }), { concurrency: 8 });
10271
+ const local = /* @__PURE__ */ new Set([...paths, ...(file.Replace ?? []).map((replace) => replace.Old.Path)]);
10272
+ return used.map((moduleDir) => ({
10273
+ root,
10274
+ moduleDir,
10275
+ local
10276
+ })).filter(({ moduleDir }) => place === "" || placeOf$1(dir, moduleDir) !== "");
10277
+ }).pipe(Effect.result);
10278
+ if (Result.isSuccess(scanned)) return scanned.success;
10279
+ for (const moduleDir of claimed) listed.add(moduleDir);
10280
+ failedWorks.push(failing(path.join(place, "go.work"), scanned.failure));
10236
10281
  return [];
10237
10282
  }));
10238
- const listed = new Set(modules.flat().map(({ moduleDir }) => moduleDir));
10239
10283
  const outside = nested.flatMap((moduleDir) => listed.has(moduleDir) ? [] : [{
10240
10284
  root: moduleDir,
10241
- moduleDir
10285
+ moduleDir,
10286
+ local: /* @__PURE__ */ new Set()
10242
10287
  }]);
10243
- const providers = yield* Effect.forEach([...modules.flat(), ...outside], ({ root, moduleDir }) => make$6(root, moduleDir, placeOf$1(dir, root)));
10288
+ const providers = yield* Effect.forEach([...modules.flat(), ...outside], ({ root, moduleDir, local }) => make$7(root, moduleDir, placeOf$1(dir, root), local));
10244
10289
  return [
10245
10290
  ...failed,
10246
10291
  ...failedWorks,
@@ -10249,7 +10294,7 @@ const discover$2 = Effect.fn("GoModule.discover")(function* (dir, nested = []) {
10249
10294
  });
10250
10295
  // src/inventory/ProviderSource.ts
10251
10296
  var ProviderSource = class extends Context.Service()("upup/ProviderSource") {};
10252
- // src/node/prerelease-line.ts
10297
+ // src/node/release-history.ts
10253
10298
  const minutesPerDay = 1440;
10254
10299
  const firstPnpmMajorWithDefaultReleaseAge = 11;
10255
10300
  const gateSettings = {
@@ -10287,16 +10332,18 @@ const exempt = (rules, name, version) => {
10287
10332
  };
10288
10333
  const isPrerelease = (version) => semver.prerelease(version) !== null;
10289
10334
  const lineOf = (version) => (semver.prerelease(version) ?? []).map((part) => Predicate.isNumber(part) ? "#" : part).join(".");
10290
- const prereleaseSuccessor = (name, current, history, gate, now) => {
10335
+ const releaseSuccessor = (name, current, history, gate, now) => {
10291
10336
  const line = lineOf(current);
10292
10337
  const following = isPrerelease(current);
10293
10338
  const pinned = Date.parse(history.time[current] ?? "");
10294
10339
  if (following && Number.isNaN(pinned)) return Result.fail(`${name} ${current} has no publish date, so upup offers it nothing`);
10295
10340
  const releasedBeforePrereleasePin = (published) => following && !(published > pinned);
10341
+ const latest = semver.valid(history.latest ?? "");
10342
+ const beyondLatest = (candidate) => latest !== null && semver.gt(candidate, latest);
10296
10343
  let newest;
10297
10344
  for (const candidate of history.versions) {
10298
10345
  if (semver.valid(candidate) === null || !semver.gt(candidate, current)) continue;
10299
- if (isPrerelease(candidate) && lineOf(candidate) !== line) continue;
10346
+ if (isPrerelease(candidate) ? lineOf(candidate) !== line : beyondLatest(candidate)) continue;
10300
10347
  const published = Date.parse(history.time[candidate] ?? "");
10301
10348
  if (Number.isNaN(published) || releasedBeforePrereleasePin(published)) continue;
10302
10349
  if (gate.minutes > 0 && now - published < gate.minutes * 6e4 && !exempt(gate.exclude, name, candidate)) continue;
@@ -10304,35 +10351,64 @@ const prereleaseSuccessor = (name, current, history, gate, now) => {
10304
10351
  }
10305
10352
  return Result.succeed(newest);
10306
10353
  };
10307
- const Viewed = Schema.Struct({
10354
+ const ViewedRelease = Schema.Struct({
10308
10355
  versions: Schema.Union([Schema.String, Schema.Array(Schema.String)]),
10309
- time: Schema.Record(Schema.String, Schema.String)
10310
- });
10311
- const prereleaseOffers = Effect.fn("prereleaseOffers")(function* (pins, manager, run, config) {
10356
+ time: Schema.Record(Schema.String, Schema.String),
10357
+ "dist-tags": Schema.optionalKey(Schema.Struct({ latest: Schema.optionalKey(Schema.String) }))
10358
+ });
10359
+ const Viewed = Schema.Union([ViewedRelease, Schema.NonEmptyArray(ViewedRelease)]);
10360
+ const ViewFailed = Schema.Struct({ error: Schema.Struct({
10361
+ code: Schema.optionalKey(Schema.String),
10362
+ summary: Schema.optionalKey(Schema.String),
10363
+ message: Schema.optionalKey(Schema.String)
10364
+ }) });
10365
+ const releaseOffers = Effect.fn("releaseOffers")(function* (pins, manager, run, config) {
10312
10366
  const exec = yield* Exec;
10313
10367
  const now = yield* Clock.currentTimeMillis;
10314
- const read = (artifact) => {
10368
+ const read = Effect.fn("releaseOffers.read")(function* (artifact) {
10315
10369
  const view = run([
10316
10370
  "view",
10317
10371
  artifact,
10318
10372
  "versions",
10319
10373
  "time",
10374
+ "dist-tags",
10320
10375
  "--json"
10321
10376
  ]);
10322
- return exec.stdout(view).pipe(Effect.flatMap(decodeJson(Viewed, commandLine(view))), Effect.map((viewed) => ({
10377
+ const output = yield* exec.run(view);
10378
+ if (output.exitCode !== 0) {
10379
+ const reported = Option.getOrUndefined(Schema.decodeOption(Schema.fromJsonString(ViewFailed))(output.stdout))?.error;
10380
+ if (reported?.code?.includes("404") === true) return [artifact, Result.fail(`${artifact} isn't on the registry, or your credentials can't see it, so upup offers it nothing`)];
10381
+ return yield* new CommandFailed({
10382
+ command: commandLine(view),
10383
+ cwd: view.cwd,
10384
+ exitCode: output.exitCode,
10385
+ output: reported?.summary ?? reported?.message ?? output.stderr
10386
+ });
10387
+ }
10388
+ const decoded = yield* decodeJson(Viewed, commandLine(view))(output.stdout);
10389
+ const viewed = "versions" in decoded ? decoded : decoded[0];
10390
+ const latest = viewed["dist-tags"]?.latest;
10391
+ const history = {
10323
10392
  versions: [viewed.versions].flat(),
10324
- time: viewed.time
10325
- })), Effect.result, Effect.map((history) => [artifact, history]));
10326
- };
10327
- const followed = (pin) => isPrerelease(pin.current) || pin.history === true;
10328
- const artifacts = new Set(pins.flatMap((pin) => followed(pin) ? [pin.artifact] : []));
10329
- const histories = new Map(yield* Effect.forEach(artifacts, read, { concurrency: 4 }));
10330
- const version = manager === "pnpm" && artifacts.size > 0 ? yield* exec.stdout(run(["--version"])) : "";
10393
+ time: viewed.time,
10394
+ ...latest === void 0 ? {} : { latest }
10395
+ };
10396
+ return [artifact, Result.succeed(history)];
10397
+ });
10398
+ const artifacts = new Set(pins.map((pin) => pin.artifact));
10399
+ const reads = yield* Effect.result(Effect.forEach(artifacts, read, { concurrency: 8 }));
10400
+ const histories = new Map(Result.isSuccess(reads) ? reads.success : []);
10401
+ const notices = Result.isFailure(reads) ? [{ message: `release histories could not be read, so upup offers nothing: ${describeError(reads.failure)}` }] : [];
10402
+ const version = manager === "pnpm" && histories.size > 0 ? yield* exec.stdout(run(["--version"])) : "";
10331
10403
  const gate = releaseGate(manager, version.trim(), config);
10332
- return (pin) => {
10333
- const history = followed(pin) ? histories.get(pin.artifact) : void 0;
10334
- if (history === void 0) return;
10335
- return Result.isFailure(history) ? Result.fail(`${pin.artifact}'s release history could not be read, so upup offers it nothing: ${describeError(history.failure)}`) : prereleaseSuccessor(pin.artifact, pin.current, history.success, gate, now);
10404
+ const offer = (pin) => {
10405
+ const history = histories.get(pin.artifact);
10406
+ if (history === void 0) return Result.succeed(void 0);
10407
+ return Result.flatMap(history, (found) => releaseSuccessor(pin.artifact, pin.current, found, gate, now));
10408
+ };
10409
+ return {
10410
+ offer,
10411
+ notices
10336
10412
  };
10337
10413
  });
10338
10414
  // src/node/script-policy.ts
@@ -10388,7 +10464,7 @@ const Root = Schema.Struct({
10388
10464
  dependencies: Schema.optionalKey(Schema.Record(Schema.String, Dependency))
10389
10465
  });
10390
10466
  const Listed$1 = Schema.Union([Root, Schema.Array(Root)]);
10391
- const Outdated$1 = Schema.Record(Schema.String, Schema.Struct({ latest: Schema.optionalKey(Schema.String) }));
10467
+ const Outdated = Schema.Record(Schema.String, Schema.Struct({ latest: Schema.optionalKey(Schema.String) }));
10392
10468
  const parseListed = (provider, listed) => {
10393
10469
  const roots = Array.isArray(listed) ? listed : [listed];
10394
10470
  const pkg = (name, version, runtime) => ({
@@ -10422,7 +10498,7 @@ const makeGlobal = (spec) => Effect.gen(function* () {
10422
10498
  const latest = Effect.gen(function* () {
10423
10499
  const outdated = invocation(spec.outdated);
10424
10500
  const output = yield* exec.run(outdated);
10425
- const parsed = yield* decodeJson(Outdated$1, commandLine(outdated))(output.stdout).pipe(Effect.catchTag("OutputMalformed", (error) => output.exitCode === 0 ? Effect.fail(error) : Effect.fail(new CommandFailed({
10501
+ const parsed = yield* decodeJson(Outdated, commandLine(outdated))(output.stdout).pipe(Effect.catchTag("OutputMalformed", (error) => output.exitCode === 0 ? Effect.fail(error) : Effect.fail(new CommandFailed({
10426
10502
  command: commandLine(outdated),
10427
10503
  cwd: outdated.cwd,
10428
10504
  exitCode: output.exitCode,
@@ -10436,20 +10512,20 @@ const makeGlobal = (spec) => Effect.gen(function* () {
10436
10512
  latest,
10437
10513
  readNpmConfig(spec.config, "/").pipe(Effect.provideService(Exec, exec))
10438
10514
  ], { concurrency: 3 });
10439
- const offers = yield* prereleaseOffers(packages, spec.config, (args) => ({
10515
+ const { offer, notices: unread } = yield* releaseOffers(packages.filter((pkg) => isPrerelease(pkg.current)), spec.config, (args) => ({
10440
10516
  command: spec.config,
10441
10517
  args,
10442
10518
  cwd: "/"
10443
10519
  }), config).pipe(Effect.provideService(Exec, exec));
10444
- const notices = [];
10520
+ const notices = [...unread];
10445
10521
  return {
10446
10522
  packages: packages.map(withNpmOrigin(npmRegistries(config))).map((pkg) => {
10447
- const line = offers(pkg);
10448
- if (line !== void 0 && Result.isFailure(line)) notices.push({
10523
+ const line = offer(pkg);
10524
+ if (Result.isFailure(line)) notices.push({
10449
10525
  rowKey: rowKey(pkg),
10450
10526
  message: line.failure
10451
10527
  });
10452
- const target = line === void 0 ? targets.get(pkg.name) : Result.getOrUndefined(line);
10528
+ const target = isPrerelease(pkg.current) ? Result.getOrUndefined(line) : targets.get(pkg.name);
10453
10529
  return target !== void 0 && isNewerStable(pkg.current, target) ? withTarget(pkg, target) : pkg;
10454
10530
  }),
10455
10531
  notices
@@ -10671,7 +10747,7 @@ const scan = Effect.fn("NodePins.scan")(function* (root, dir, nested) {
10671
10747
  const files = yield* Effect.forEach(versioned.toSorted(), (file) => Effect.map(read(file), (text) => filePin(file, text)));
10672
10748
  return [...steps, ...files.flat()];
10673
10749
  });
10674
- const make$5 = (root, dir = root, nested = []) => Effect.gen(function* () {
10750
+ const make$6 = (root, dir = root, nested = []) => Effect.gen(function* () {
10675
10751
  const fs = yield* FileSystem.FileSystem;
10676
10752
  const client = yield* HttpClient.HttpClient;
10677
10753
  const context = yield* Effect.context();
@@ -10742,7 +10818,7 @@ const discover$1 = Effect.fn("NodePins.discover")(function* (dir, nested = []) {
10742
10818
  if (Option.isNone(repository)) return [];
10743
10819
  const pins = yield* scan(repository.value, dir, nested).pipe(Effect.result);
10744
10820
  if (Result.isSuccess(pins) && pins.success.length === 0) return [];
10745
- return [yield* make$5(repository.value, dir, nested)];
10821
+ return [yield* make$6(repository.value, dir, nested)];
10746
10822
  });
10747
10823
  // src/node/selector.ts
10748
10824
  const split = (name, spec) => {
@@ -10794,20 +10870,21 @@ const Manifest = Schema.Struct({
10794
10870
  optionalDependencies: Schema.optionalKey(Specs)
10795
10871
  });
10796
10872
  const Resolved = Schema.Record(Schema.String, Schema.Struct({ version: Schema.String }));
10797
- const PnpmLock = Schema.Struct({ importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({
10798
- dependencies: Schema.optionalKey(Resolved),
10799
- devDependencies: Schema.optionalKey(Resolved),
10800
- optionalDependencies: Schema.optionalKey(Resolved)
10801
- }))) });
10873
+ const PnpmLock = Schema.Struct({
10874
+ overrides: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
10875
+ importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({
10876
+ dependencies: Schema.optionalKey(Resolved),
10877
+ devDependencies: Schema.optionalKey(Resolved),
10878
+ optionalDependencies: Schema.optionalKey(Resolved)
10879
+ })))
10880
+ });
10802
10881
  const PnpmManagerLock = Schema.Struct({ importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ packageManagerDependencies: Schema.optionalKey(Resolved) }))) });
10803
10882
  const NpmLock = Schema.Struct({ packages: Schema.Record(Schema.String, Schema.Struct({ version: Schema.optionalKey(Schema.String) })) });
10804
10883
  const Workspace = Schema.Struct({
10805
10884
  catalog: Schema.optionalKey(Specs),
10806
- catalogs: Schema.optionalKey(Schema.Record(Schema.String, Specs))
10885
+ catalogs: Schema.optionalKey(Schema.Record(Schema.String, Specs)),
10886
+ overrides: Schema.optionalKey(Specs)
10807
10887
  });
10808
- const OutdatedEntry = Schema.Struct({ latest: Schema.optionalKey(Schema.String) });
10809
- const Outdated = Schema.Record(Schema.String, Schema.Union([OutdatedEntry, Schema.Array(OutdatedEntry)]));
10810
- const NpmError = Schema.Struct({ error: Schema.Struct({ summary: Schema.String }) });
10811
10888
  const PackageJsonFields = Schema.Struct({
10812
10889
  workspaces: Schema.optionalKey(Schema.Unknown),
10813
10890
  packageManager: Schema.optionalKey(Schema.String),
@@ -10830,25 +10907,22 @@ const managerEntry = "the package manager's declaration is removed by hand, in p
10830
10907
  const resolvedVersion = (version) => version?.split("(")[0];
10831
10908
  const readJson = (schema, file) => readText(file).pipe(Effect.flatMap((text) => decodeFile(schema, file)(parse$1(text))));
10832
10909
  const exact = (version) => semver.valid(version) === version ? version : void 0;
10910
+ const overriddenName = (key) => {
10911
+ const target = key.split(">").at(-1) ?? key;
10912
+ const at = target.indexOf("@", target.startsWith("@") ? 1 : 0);
10913
+ return at === -1 ? target : target.slice(0, at);
10914
+ };
10833
10915
  const corepackHashed = (name, algorithm) => {
10834
10916
  if (algorithm === void 0) return {};
10835
10917
  if (algorithm === "sha512") return { hashed: true };
10836
10918
  return { held: `${name}'s packageManager entry carries a ${algorithm} hash, which upup can work out only for sha512; move it by hand` };
10837
10919
  };
10838
- const pinOf = ({ pkg, manager: own }) => ({
10839
- artifact: pkg.artifact,
10840
- current: pkg.current,
10841
- ...own === true ? { history: true } : {}
10842
- });
10843
10920
  const editDeclaration = (text, manifest, entry, next) => {
10844
10921
  if (next === void 0 && entry.pkg.unremovable !== void 0) return Result.fail(entry.pkg.unremovable);
10845
10922
  if (manifest.endsWith(".yaml") && next !== void 0) return editYamlScalar(text, entry.path, next);
10846
10923
  return editJson(text, entry.path, next);
10847
10924
  };
10848
- const withoutDependencyKindMarker = (key) => key.replace(/ \([a-z]+\)$/u, "");
10849
- const withoutLockedVersionSuffix = (key) => key.replace(/(?<=.)@[^@/ ]+$/u, "");
10850
- const outdatedName = (key) => withoutLockedVersionSuffix(withoutDependencyKindMarker(key));
10851
- const make$4 = (root, manager, place = "") => Effect.gen(function* () {
10925
+ const make$5 = (root, manager, place = "", claimed = []) => Effect.gen(function* () {
10852
10926
  const providerName = placed(manager, place);
10853
10927
  const exec = yield* Exec;
10854
10928
  const fs = yield* FileSystem.FileSystem;
@@ -10929,6 +11003,9 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
10929
11003
  onNone: () => []
10930
11004
  })
10931
11005
  });
11006
+ const lockedDirs = new Set(members.map((member) => path.normalize(member)));
11007
+ const unlocked = claimed.filter((dir) => !lockedDirs.has(path.normalize(dir))).toSorted((a, b) => a.localeCompare(b));
11008
+ if (unlocked.length > 0) notices.push({ message: `not listed: ${unlocked.join(", ")} (not in ${lockfile})` });
10932
11009
  const catalogLocked = /* @__PURE__ */ new Map();
10933
11010
  for (const member of members.toSorted((a, b) => a.localeCompare(b))) {
10934
11011
  const memberDir = member === "." ? "" : member;
@@ -10963,8 +11040,25 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
10963
11040
  ["catalogs", catalog],
10964
11041
  entries
10965
11042
  ])];
11043
+ const overridden = /* @__PURE__ */ new Map();
11044
+ for (const [key, spec] of Object.entries(workspace.overrides ?? {})) {
11045
+ if (!spec.startsWith("catalog:")) continue;
11046
+ const name = overriddenName(key);
11047
+ const locked = pnpm.value.overrides?.[key];
11048
+ const selector = locked === void 0 ? void 0 : parseSelector(name, locked);
11049
+ if (selector === void 0 || exact(selector.range) === void 0) continue;
11050
+ const id = `${spec.slice(8) || "default"}:${name}`;
11051
+ const versions = overridden.get(id) ?? /* @__PURE__ */ new Set();
11052
+ versions.add(selector.artifact === name ? selector.range : `${selector.artifact}@${selector.range}`);
11053
+ overridden.set(id, versions);
11054
+ }
10966
11055
  for (const [catalog, catalogPath, entries] of catalogs) for (const [name, spec] of Object.entries(entries)) {
10967
- const resolved = catalogLocked.get(catalog)?.get(name);
11056
+ const overrides = overridden.get(`${catalog}:${name}`);
11057
+ if (overrides !== void 0 && overrides.size > 1) {
11058
+ notices.push({ message: `${catalog} catalog entry ${name} has conflicting locked overrides; run pnpm install` });
11059
+ continue;
11060
+ }
11061
+ const resolved = catalogLocked.get(catalog)?.get(name) ?? overrides?.values().next().value;
10968
11062
  if (resolved === void 0) continue;
10969
11063
  add("pnpm-workspace.yaml", catalogPath.join("."), [...catalogPath, name], name, spec, resolved);
10970
11064
  }
@@ -11001,53 +11095,20 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
11001
11095
  notices
11002
11096
  };
11003
11097
  }).pipe(Effect.provideService(FileSystem.FileSystem, fs));
11004
- const latest = Effect.gen(function* () {
11005
- const workspaces = manager === "npm" && (yield* readJson(PackageJsonFields, path.join(root, "package.json"))).workspaces !== void 0;
11006
- const outdated = run(manager === "pnpm" ? [
11007
- "outdated",
11008
- "-r",
11009
- "--format",
11010
- "json"
11011
- ] : [
11012
- "outdated",
11013
- "--json",
11014
- ...workspaces ? ["--workspaces", "--include-workspace-root"] : []
11015
- ]);
11016
- const output = yield* exec.run(outdated);
11017
- const reported = Schema.decodeOption(Schema.fromJsonString(NpmError))(output.stdout);
11018
- if (output.exitCode !== 0 && (output.stdout.trim() === "" || Option.isSome(reported))) return yield* new CommandFailed({
11019
- command: commandLine(outdated),
11020
- cwd: root,
11021
- exitCode: output.exitCode,
11022
- output: Option.isSome(reported) ? reported.value.error.summary : output.stderr
11023
- });
11024
- const parsed = yield* decodeJson(Outdated, commandLine(outdated))(output.stdout || "{}");
11025
- const targets = /* @__PURE__ */ new Map();
11026
- for (const [key, entries] of Object.entries(parsed)) {
11027
- const name = outdatedName(key);
11028
- const newest = [entries].flat().find((entry) => entry.latest !== void 0)?.latest;
11029
- if (newest !== void 0 && !targets.has(name)) targets.set(name, newest);
11030
- }
11031
- return targets;
11032
- }).pipe(Effect.provideService(FileSystem.FileSystem, fs));
11033
11098
  const list = Effect.gen(function* () {
11034
- const [{ declared, notices }, targets, config] = yield* Effect.all([
11035
- declarations,
11036
- latest,
11037
- readNpmConfig(manager, root).pipe(Effect.provideService(Exec, exec))
11038
- ], { concurrency: 3 });
11099
+ const [{ declared, notices }, config] = yield* Effect.all([declarations, readNpmConfig(manager, root).pipe(Effect.provideService(Exec, exec))], { concurrency: 2 });
11039
11100
  const origin = withNpmOrigin(npmRegistries(config));
11040
- const offers = yield* prereleaseOffers(declared.flatMap((entry) => entry.held === true ? [] : [pinOf(entry)]), manager, run, config).pipe(Effect.provideService(Exec, exec));
11101
+ const { offer, notices: unread } = yield* releaseOffers(declared.flatMap((entry) => entry.held === true ? [] : [entry.pkg]), manager, run, config).pipe(Effect.provideService(Exec, exec));
11041
11102
  return {
11042
11103
  packages: declared.map((entry) => {
11043
11104
  const pkg = origin(entry.pkg);
11044
11105
  if (entry.held === true) return pkg;
11045
- const line = offers(pinOf(entry));
11046
- if (line !== void 0 && Result.isFailure(line)) notices.push({
11106
+ const line = offer(entry.pkg);
11107
+ if (Result.isFailure(line)) notices.push({
11047
11108
  rowKey: rowKey(pkg),
11048
11109
  message: line.failure
11049
11110
  });
11050
- const target = line === void 0 ? targets.get(pkg.name) ?? targets.get(pkg.artifact) : Result.getOrUndefined(line);
11111
+ const target = Result.getOrUndefined(line);
11051
11112
  if (target === void 0 || !isNewerStable(pkg.current, target)) return pkg;
11052
11113
  const selector = parseSelector(pkg.name, pkg.declaration?.selector ?? "");
11053
11114
  if (selector === void 0 || moveTo(selector, target) === void 0) {
@@ -11059,7 +11120,7 @@ const make$4 = (root, manager, place = "") => Effect.gen(function* () {
11059
11120
  }
11060
11121
  return withTarget(pkg, target);
11061
11122
  }),
11062
- notices
11123
+ notices: [...unread, ...notices]
11063
11124
  };
11064
11125
  }).pipe(Effect.withSpan(`NodeProject.list`));
11065
11126
  const find = (pkg) => declarations.pipe(Effect.map(({ declared }) => declared.find((entry) => entry.pkg.id === pkg.id)));
@@ -11230,7 +11291,7 @@ const discover = Effect.fn("NodeProject.discover")(function* (dir, nested = [])
11230
11291
  const { found, failed } = yield* locateAll(dir, nested, "package.json", locate);
11231
11292
  const providers = [...failed];
11232
11293
  const unlisted = [];
11233
- for (const { root, manager } of found) {
11294
+ for (const { root, manager, claimed } of found) {
11234
11295
  const place = placeOf$1(dir, root);
11235
11296
  if (!isManager(manager)) {
11236
11297
  if (place === "") providers.push(failing(manager, new Unsupported({ reason: `${path.relative(dir, root) || "This"} project uses ${manager}, which upup does not support` })));
@@ -11240,11 +11301,144 @@ const discover = Effect.fn("NodeProject.discover")(function* (dir, nested = [])
11240
11301
  const lockfile = path.join(root, lockfiles[manager]);
11241
11302
  const installed = yield* fs.exists(lockfile).pipe(Effect.mapError(fileFailed("stat", lockfile)));
11242
11303
  if (place !== "" && !installed) unlisted.push(`${place} (no ${lockfiles[manager]} yet)`);
11243
- else providers.push(yield* make$4(root, manager, place));
11304
+ else {
11305
+ const manifests = yield* Effect.filter(claimed, (from) => fs.exists(path.join(from, "package.json")).pipe(Effect.mapError(fileFailed("stat", from))));
11306
+ providers.push(yield* make$5(root, manager, place, manifests.map((from) => path.relative(root, from))));
11307
+ }
11244
11308
  }
11245
11309
  if (unlisted.length > 0) providers.push(notListed(unlisted));
11246
11310
  return providers;
11247
11311
  });
11312
+ // src/node/VpEnv.ts
11313
+ const vp$1 = (args) => ({
11314
+ command: "vp",
11315
+ args: ["env", ...args],
11316
+ cwd: "/"
11317
+ });
11318
+ const defaults = vp$1(["default"]);
11319
+ const tools$1 = /* @__PURE__ */ new Set([
11320
+ "node",
11321
+ "npm",
11322
+ "pnpm",
11323
+ "yarn",
11324
+ "bun"
11325
+ ]);
11326
+ const Release$1 = Schema.Struct({
11327
+ version: Schema.String,
11328
+ latest: Schema.Boolean
11329
+ });
11330
+ const NodeReleases = Schema.Struct({ node: Schema.Array(Release$1) });
11331
+ const ManagerReleases = Schema.Struct({ package_managers: Schema.Record(Schema.String, Schema.Array(Release$1)) });
11332
+ const malformed$1 = (command, issue) => new OutputMalformed({
11333
+ command: commandLine(command),
11334
+ issue
11335
+ });
11336
+ const readDefaults = Effect.fn("VpEnv.readDefaults")(function* (output) {
11337
+ const entries = [];
11338
+ const notices = [];
11339
+ let recognized = false;
11340
+ for (const line of output.split("\n").map((text) => text.trim())) {
11341
+ if (line.startsWith("No default Node.js version configured.") || line === "Package manager defaults: not configured") recognized = true;
11342
+ if (!line.startsWith("Default ")) continue;
11343
+ const fields = /^Default (?<name>.+) version: (?<selector>\S+)$/u.exec(line)?.groups;
11344
+ const label = fields?.["name"];
11345
+ const selector = fields?.["selector"];
11346
+ if (label === void 0 || selector === void 0) return yield* malformed$1(defaults, `unrecognized default: ${line}`);
11347
+ recognized = true;
11348
+ const name = label === "Node.js" ? "node" : label;
11349
+ if (tools$1.has(name)) entries.push({
11350
+ name,
11351
+ selector
11352
+ });
11353
+ else notices.push({ message: `Vite+ default ${name} is not supported` });
11354
+ }
11355
+ if (!recognized) return yield* malformed$1(defaults, "no global defaults found");
11356
+ if (entries.length === 0) return {
11357
+ packages: [],
11358
+ notices
11359
+ };
11360
+ const manifest = /^\s+Set via: (?<path>.+)$/mu.exec(output)?.groups?.["path"];
11361
+ if (manifest === void 0) return yield* malformed$1(defaults, "no configuration path found");
11362
+ return {
11363
+ packages: entries.map(({ name, selector }) => {
11364
+ const version = semver.parse(selector);
11365
+ return {
11366
+ provider: "vp-env",
11367
+ id: name,
11368
+ name,
11369
+ ecosystem: name === "node" ? "node" : "npm",
11370
+ artifact: name === "yarn" && version !== null && version.major >= 2 ? "@yarnpkg/cli-dist" : name,
11371
+ current: version?.version ?? selector,
11372
+ declaration: {
11373
+ manifest,
11374
+ section: `${name} default`,
11375
+ selector
11376
+ },
11377
+ description: version === null ? `Managed automatically by Vite+ (${selector})` : "Vite+ global default",
11378
+ unremovable: "Vite+ defaults are cleared with vp env default --unset"
11379
+ };
11380
+ }),
11381
+ notices
11382
+ };
11383
+ });
11384
+ const make$4 = Effect.gen(function* () {
11385
+ const exec = yield* Exec;
11386
+ const configured = exec.stdout(defaults).pipe(Effect.flatMap(readDefaults));
11387
+ const offer = Effect.fn("VpEnv.offer")(function* (pkg) {
11388
+ const current = semver.parse(pkg.current);
11389
+ if (current === null) return;
11390
+ const withinMajor = pkg.id === "node" || pkg.id === "yarn" && current.major === 1;
11391
+ const command = vp$1([
11392
+ "list-remote",
11393
+ pkg.id,
11394
+ ...withinMajor ? [String(current.major)] : [],
11395
+ "--json"
11396
+ ]);
11397
+ const output = yield* exec.stdout(command);
11398
+ const releases = pkg.id === "node" ? (yield* decodeJson(NodeReleases, commandLine(command))(output)).node : (yield* decodeJson(ManagerReleases, commandLine(command))(output)).package_managers[pkg.id];
11399
+ if (releases === void 0 || releases.length === 0) return yield* malformed$1(command, `no releases found for ${pkg.name}`);
11400
+ return newestOffer(pkg.current, releases.filter(({ version, latest }) => (withinMajor || latest) && semver.prerelease(version) === null).map(({ version }) => version));
11401
+ });
11402
+ const list = Effect.gen(function* () {
11403
+ const found = yield* configured;
11404
+ const notices = [...found.notices];
11405
+ return {
11406
+ packages: yield* Effect.forEach(found.packages, (pkg) => Effect.gen(function* () {
11407
+ const target = yield* Effect.result(offer(pkg));
11408
+ if (Result.isFailure(target)) {
11409
+ notices.push({
11410
+ rowKey: rowKey(pkg),
11411
+ message: describeError(target.failure)
11412
+ });
11413
+ return pkg;
11414
+ }
11415
+ return target.success === void 0 ? pkg : withTarget(pkg, target.success);
11416
+ })),
11417
+ notices
11418
+ };
11419
+ }).pipe(Effect.withSpan("VpEnv.list"));
11420
+ const observe = Effect.fn("VpEnv.observe")(function* (pkg) {
11421
+ return Option.fromUndefinedOr((yield* configured).packages.find((candidate) => candidate.id === pkg.id));
11422
+ });
11423
+ const unchanged = Effect.fn("VpEnv.unchanged")(function* (pkg) {
11424
+ const current = yield* observe(pkg);
11425
+ if (Option.isNone(current) || current.value.current !== pkg.current || current.value.declaration?.selector !== pkg.declaration?.selector) return yield* new InstallRefused({
11426
+ package: pkg.name,
11427
+ reason: "the Vite+ default changed; refresh before updating"
11428
+ });
11429
+ });
11430
+ return {
11431
+ name: "vp-env",
11432
+ mode: "system",
11433
+ list,
11434
+ observe,
11435
+ update: Effect.fn("VpEnv.update")(function* (pkg, target) {
11436
+ yield* exec.stdout(vp$1(["install", `${pkg.id}@${target}`]));
11437
+ yield* unchanged(pkg);
11438
+ yield* exec.stdout(vp$1(["default", `${pkg.id}@${target}`]));
11439
+ })
11440
+ };
11441
+ });
11248
11442
  // src/node/VpSelf.ts
11249
11443
  const vp = (args) => ({
11250
11444
  command: "vp",
@@ -11272,6 +11466,10 @@ const make$3 = Effect.gen(function* () {
11272
11466
  mode: "system",
11273
11467
  list: Effect.gen(function* () {
11274
11468
  const output = yield* exec.stdout(check$1);
11469
+ if (output.includes("Homebrew manages this installation.")) return {
11470
+ packages: [],
11471
+ notices: []
11472
+ };
11275
11473
  const { latest, current } = /found vite-plus@(?<latest>\S+) \(current: (?<current>\S+)\)/u.exec(output)?.groups ?? {};
11276
11474
  if (latest === void 0 || current === void 0) return yield* malformed(check$1);
11277
11475
  const pkg = vitePlus(current);
@@ -11615,14 +11813,15 @@ const make = Effect.gen(function* () {
11615
11813
  const system$1 = [
11616
11814
  ["softwareupdate", make$1],
11617
11815
  ["apt-get", make$2],
11618
- ["brew", make$13],
11619
- ["codesign", make$10],
11816
+ ["brew", make$14],
11817
+ ["codesign", make$11],
11620
11818
  ["npm", makeNpm],
11621
11819
  ["pnpm", makePnpm],
11622
11820
  ["vp", makeVp],
11623
11821
  ["vp", make$3],
11624
- ["cargo", make$9],
11625
- ["go", make$7],
11822
+ ["vp", make$4],
11823
+ ["cargo", make$10],
11824
+ ["go", make$8],
11626
11825
  ["uv", make]
11627
11826
  ];
11628
11827
  const available = (entries) => Effect.filter([...new Set(entries.map(([command]) => command))], onPath).pipe(Effect.map((found) => entries.filter(([command]) => found.includes(command))));
@@ -12491,18 +12690,17 @@ const runProvider = Effect.fn("Engine.runProvider")(function* (provider, targets
12491
12690
  key: rowKey(target.pkg),
12492
12691
  outcome
12493
12692
  }))));
12494
- const checked = [];
12693
+ const applied = [];
12495
12694
  for (const target of targets) {
12496
12695
  if (yield* stopped) {
12497
12696
  yield* finish(target, Outcome.NotStarted());
12498
12697
  continue;
12499
12698
  }
12500
- const result = yield* check(target, emit);
12501
- if (Result.isFailure(result)) yield* finish(target, result.failure);
12502
- else checked.push([target, result.success]);
12503
- }
12504
- const applied = [];
12505
- for (const [target, attempt] of checked) {
12699
+ const checked = yield* check(target, emit);
12700
+ if (Result.isFailure(checked)) {
12701
+ yield* finish(target, checked.failure);
12702
+ continue;
12703
+ }
12506
12704
  if (yield* stopped) {
12507
12705
  yield* finish(target, Outcome.NotStarted());
12508
12706
  continue;
@@ -12511,7 +12709,7 @@ const runProvider = Effect.fn("Engine.runProvider")(function* (provider, targets
12511
12709
  key: rowKey(target.pkg),
12512
12710
  phase: "applying"
12513
12711
  }));
12514
- const result = yield* Effect.result(withDecisions(attempt));
12712
+ const result = yield* Effect.result(withDecisions(checked.success));
12515
12713
  if (Result.isFailure(result)) yield* finish(target, failedAs(result.failure, false));
12516
12714
  else if (provider.reconcile === void 0) yield* finish(target, yield* verify(target, emit));
12517
12715
  else applied.push(target);
@@ -12656,15 +12854,236 @@ const updateTools = (home) => Action.implement(UpdateActions, Effect.gen(functio
12656
12854
  }));
12657
12855
  // src/mcp.ts
12658
12856
  const instructions = "upup reads the tools installed on this machine and the registry dependencies of a project, audits offered updates with the user's chosen model, and applies or removes them. Start with list_packages; every other tool takes its package keys. Before updating, look at package_changes or run audit_packages and poll audit_reports until no Audit is running; operating-system updates (ecosystem macos or apt) can't be audited and have no changes to read; app updates (ecosystem sparkle, electron, appstore or app) can't be audited either, and package_changes gives their release notes when the app's feed has any; ask the user to quit an open app before updating it; the Node.js versions a project pins (ecosystem node) can't be audited either and are edited in place. update_packages and remove_packages change the user's machine or project and cannot be undone by upup: confirm them with the user, and ask before passing allowScripts.";
12659
- const serve = (home) => ActionMcp.runStdio([
12857
+ const tools = (home) => [
12660
12858
  inventoryTools(home),
12661
12859
  auditTools(home),
12662
12860
  updateTools(home)
12663
- ], {
12861
+ ];
12862
+ const serverInfo = {
12664
12863
  name: "upup",
12665
12864
  version: version$1,
12666
12865
  instructions
12866
+ };
12867
+ const serve = (home) => ActionMcp.runStdio(tools(home), serverInfo);
12868
+ // src/session/operation.ts
12869
+ const machine$1 = setup({
12870
+ schemas: { events: {
12871
+ Run: types(),
12872
+ Stop: types(),
12873
+ Ask: types(),
12874
+ Answer: types(),
12875
+ Finish: types()
12876
+ } },
12877
+ states: {
12878
+ Idle: { schemas: { context: types() } },
12879
+ Running: { schemas: { context: types() } },
12880
+ Asking: { schemas: { context: types() } }
12881
+ }
12882
+ }).createFSM({
12883
+ initial: "Idle",
12884
+ context: {
12885
+ stopping: false,
12886
+ decision: void 0
12887
+ },
12888
+ states: {
12889
+ Idle: { on: { Run: { target: "Running" } } },
12890
+ Running: { on: {
12891
+ Stop: { context: { stopping: true } },
12892
+ Ask: ({ event }) => ({
12893
+ target: "Asking",
12894
+ context: { decision: event.decision }
12895
+ }),
12896
+ Finish: {
12897
+ target: "Idle",
12898
+ context: { stopping: false }
12899
+ }
12900
+ } },
12901
+ Asking: { on: {
12902
+ Stop: { context: { stopping: true } },
12903
+ Answer: {
12904
+ target: "Running",
12905
+ context: { decision: void 0 }
12906
+ },
12907
+ Finish: {
12908
+ target: "Idle",
12909
+ context: {
12910
+ stopping: false,
12911
+ decision: void 0
12912
+ }
12913
+ }
12914
+ } }
12915
+ }
12916
+ });
12917
+ const step$1 = (snapshot, event) => {
12918
+ const [next] = machine$1.transition(snapshot, event);
12919
+ return [next, next !== snapshot];
12920
+ };
12921
+ // src/session/sign-in.ts
12922
+ const machine = setup({
12923
+ schemas: { events: {
12924
+ SaveKey: types(),
12925
+ Start: types(),
12926
+ Noticed: types(),
12927
+ Prompt: types(),
12928
+ Answer: types(),
12929
+ Succeed: types(),
12930
+ Fail: types(),
12931
+ Close: types()
12932
+ } },
12933
+ states: {
12934
+ Idle: { schemas: { context: types() } },
12935
+ SavingKey: { schemas: { context: types() } },
12936
+ Working: { schemas: { context: types() } },
12937
+ Prompted: { schemas: { context: types() } },
12938
+ Failed: { schemas: { context: types() } },
12939
+ SignedIn: { schemas: { context: types() } }
12940
+ }
12941
+ }).createFSM({
12942
+ initial: "Idle",
12943
+ context: {
12944
+ attempt: 0,
12945
+ provider: void 0,
12946
+ notices: void 0,
12947
+ prompt: void 0,
12948
+ issue: void 0
12949
+ },
12950
+ states: {
12951
+ Idle: { on: {
12952
+ SaveKey: ({ context, event }) => ({
12953
+ target: "SavingKey",
12954
+ context: {
12955
+ attempt: context.attempt + 1,
12956
+ provider: event.provider
12957
+ }
12958
+ }),
12959
+ Start: ({ context, event }) => ({
12960
+ target: "Working",
12961
+ context: {
12962
+ attempt: context.attempt + 1,
12963
+ provider: event.provider,
12964
+ notices: []
12965
+ }
12966
+ })
12967
+ } },
12968
+ SavingKey: { on: {
12969
+ Succeed: ({ context, event }) => event.attempt === context.attempt ? { target: "SignedIn" } : void 0,
12970
+ Fail: ({ context, event }) => event.attempt === context.attempt ? {
12971
+ target: "Failed",
12972
+ context: { issue: event.issue }
12973
+ } : void 0,
12974
+ Close: {
12975
+ target: "Idle",
12976
+ context: { provider: void 0 }
12977
+ }
12978
+ } },
12979
+ Working: { on: {
12980
+ Noticed: ({ context, event }) => event.attempt === context.attempt ? { context: { notices: [...context.notices, event.notice] } } : void 0,
12981
+ Prompt: ({ context, event }) => event.attempt === context.attempt ? {
12982
+ target: "Prompted",
12983
+ context: { prompt: event.prompt }
12984
+ } : void 0,
12985
+ Succeed: ({ context, event }) => event.attempt === context.attempt ? {
12986
+ target: "SignedIn",
12987
+ context: { notices: void 0 }
12988
+ } : void 0,
12989
+ Fail: ({ context, event }) => event.attempt === context.attempt ? {
12990
+ target: "Failed",
12991
+ context: {
12992
+ notices: void 0,
12993
+ issue: event.issue
12994
+ }
12995
+ } : void 0,
12996
+ Close: {
12997
+ target: "Idle",
12998
+ context: {
12999
+ provider: void 0,
13000
+ notices: void 0
13001
+ }
13002
+ }
13003
+ } },
13004
+ Prompted: { on: {
13005
+ Noticed: ({ context, event }) => event.attempt === context.attempt ? { context: { notices: [...context.notices, event.notice] } } : void 0,
13006
+ Answer: ({ context, event }) => event.attempt === context.attempt ? {
13007
+ target: "Working",
13008
+ context: { prompt: void 0 }
13009
+ } : void 0,
13010
+ Succeed: ({ context, event }) => event.attempt === context.attempt ? {
13011
+ target: "SignedIn",
13012
+ context: {
13013
+ notices: void 0,
13014
+ prompt: void 0
13015
+ }
13016
+ } : void 0,
13017
+ Fail: ({ context, event }) => event.attempt === context.attempt ? {
13018
+ target: "Failed",
13019
+ context: {
13020
+ notices: void 0,
13021
+ prompt: void 0,
13022
+ issue: event.issue
13023
+ }
13024
+ } : void 0,
13025
+ Close: {
13026
+ target: "Idle",
13027
+ context: {
13028
+ provider: void 0,
13029
+ notices: void 0,
13030
+ prompt: void 0
13031
+ }
13032
+ }
13033
+ } },
13034
+ Failed: { on: {
13035
+ SaveKey: ({ context, event }) => ({
13036
+ target: "SavingKey",
13037
+ context: {
13038
+ attempt: context.attempt + 1,
13039
+ provider: event.provider,
13040
+ issue: void 0
13041
+ }
13042
+ }),
13043
+ Start: ({ context, event }) => ({
13044
+ target: "Working",
13045
+ context: {
13046
+ attempt: context.attempt + 1,
13047
+ provider: event.provider,
13048
+ notices: [],
13049
+ issue: void 0
13050
+ }
13051
+ }),
13052
+ Close: {
13053
+ target: "Idle",
13054
+ context: {
13055
+ provider: void 0,
13056
+ issue: void 0
13057
+ }
13058
+ }
13059
+ } },
13060
+ SignedIn: { on: {
13061
+ SaveKey: ({ context, event }) => ({
13062
+ target: "SavingKey",
13063
+ context: {
13064
+ attempt: context.attempt + 1,
13065
+ provider: event.provider
13066
+ }
13067
+ }),
13068
+ Start: ({ context, event }) => ({
13069
+ target: "Working",
13070
+ context: {
13071
+ attempt: context.attempt + 1,
13072
+ provider: event.provider,
13073
+ notices: []
13074
+ }
13075
+ }),
13076
+ Close: {
13077
+ target: "Idle",
13078
+ context: { provider: void 0 }
13079
+ }
13080
+ } }
13081
+ }
12667
13082
  });
13083
+ const step = (snapshot, event) => {
13084
+ const [next] = machine.transition(snapshot, event);
13085
+ return [next, next !== snapshot];
13086
+ };
12668
13087
  // src/session/Session.ts
12669
13088
  const aboutKey = (pkg) => `${rowKey(pkg)}@${pkg.current}..${pkg.target ?? ""}`;
12670
13089
  const auditKey = (pkg, mode) => {
@@ -12677,13 +13096,14 @@ const questionOf = (state) => {
12677
13096
  _tag: "Password",
12678
13097
  request: state.password
12679
13098
  };
12680
- if (state.decision?._tag === "ScriptsBlocked") return {
13099
+ if (state.operation.value !== "Asking") return;
13100
+ const { decision } = state.operation.context;
13101
+ return decision._tag === "ScriptsBlocked" ? {
12681
13102
  _tag: "Scripts",
12682
- blocked: state.decision
12683
- };
12684
- if (state.decision?._tag === "AppRunning") return {
13103
+ blocked: decision
13104
+ } : {
12685
13105
  _tag: "Quit",
12686
- running: state.decision
13106
+ running: decision
12687
13107
  };
12688
13108
  };
12689
13109
  const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
@@ -12694,14 +13114,12 @@ const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
12694
13114
  discoveryError: void 0,
12695
13115
  providers: [],
12696
13116
  selected: [],
12697
- running: false,
12698
- stopping: false,
13117
+ operation: machine$1.initialState,
12699
13118
  activity: {},
12700
13119
  outcomes: {},
12701
13120
  actions: {},
12702
13121
  installed: {},
12703
13122
  removed: {},
12704
- decision: void 0,
12705
13123
  password: void 0,
12706
13124
  about: {},
12707
13125
  audits,
@@ -12711,7 +13129,7 @@ const initial = (mode, scope, preferences, audits = {}, settingsIssue) => ({
12711
13129
  preferences,
12712
13130
  settingsIssue,
12713
13131
  catalogue: void 0,
12714
- signIn: void 0
13132
+ signIn: machine.initialState
12715
13133
  });
12716
13134
  const issueOf = (error) => `${error.file} cannot be read (${error.issue}); upup keeps audit first on and changes no settings until it is fixed or deleted`;
12717
13135
  const without = (record, key) => Object.fromEntries(Object.entries(record).filter(([entry]) => entry !== key));
@@ -12822,9 +13240,15 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
12822
13240
  const providers = yield* Ref.make([]);
12823
13241
  const loading = yield* FiberHandle.make();
12824
13242
  const operation = yield* FiberHandle.make();
12825
- const stopRequested = yield* Ref.make(false);
12826
13243
  const pendingDecision = yield* Ref.make(Option.none());
12827
13244
  const update = (f) => SubscriptionRef.update(state, f);
13245
+ const send = (event) => SubscriptionRef.modify(state, (current) => {
13246
+ const [next, handled] = step$1(current.operation, event);
13247
+ return [handled, handled ? {
13248
+ ...current,
13249
+ operation: next
13250
+ } : current];
13251
+ });
12828
13252
  const setProvider = (name, f) => update((current) => ({
12829
13253
  ...current,
12830
13254
  providers: current.providers.map((entry) => entry.name === name ? f(entry) : entry)
@@ -12995,11 +13419,12 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
12995
13419
  return {
12996
13420
  ...initial(mode, scope, current.preferences, current.audits, current.settingsIssue),
12997
13421
  ...places.get(`${mode}:${scope}`),
12998
- catalogue: current.catalogue
13422
+ catalogue: current.catalogue,
13423
+ signIn: current.signIn
12999
13424
  };
13000
13425
  });
13001
13426
  const open = Effect.fn("Session.open")(function* (mode, scope) {
13002
- if ((yield* SubscriptionRef.get(state)).running) return;
13427
+ if ((yield* SubscriptionRef.get(state)).operation.value !== "Idle") return;
13003
13428
  yield* FiberHandle.clear(loading);
13004
13429
  yield* reset(mode, scope);
13005
13430
  yield* FiberHandle.run(loading, discover(mode, scope));
@@ -13016,7 +13441,7 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13016
13441
  });
13017
13442
  const refresh = Effect.gen(function* () {
13018
13443
  const current = yield* SubscriptionRef.get(state);
13019
- if (current.running || current.discovering) return;
13444
+ if (current.operation.value !== "Idle" || current.discovering) return;
13020
13445
  yield* FiberHandle.run(loading, discover(current.mode, current.scope));
13021
13446
  });
13022
13447
  const toggle = Effect.fn("Session.toggle")(function* (key) {
@@ -13038,16 +13463,11 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13038
13463
  const ask = (decision) => Effect.gen(function* () {
13039
13464
  const answer = yield* Deferred.make();
13040
13465
  yield* Ref.set(pendingDecision, Option.some(answer));
13041
- yield* update((current) => ({
13042
- ...current,
13466
+ yield* send({
13467
+ type: "Ask",
13043
13468
  decision
13044
- }));
13045
- const allowed = yield* Deferred.await(answer);
13046
- yield* update((current) => ({
13047
- ...current,
13048
- decision: void 0
13049
- }));
13050
- return allowed;
13469
+ });
13470
+ return yield* Deferred.await(answer);
13051
13471
  });
13052
13472
  const decisions = Decisions.of({
13053
13473
  asks: true,
@@ -13056,16 +13476,9 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13056
13476
  });
13057
13477
  const run = Effect.fn("Session.run")(function* (action, keys, accepted = []) {
13058
13478
  const current = yield* SubscriptionRef.get(state);
13059
- if (current.running) return;
13060
13479
  const targets = targetsFor(current, yield* Ref.get(providers), action, keys);
13061
- if (targets.length === 0) return;
13062
- yield* Ref.set(stopRequested, false);
13480
+ if (targets.length === 0 || !(yield* send({ type: "Run" }))) return;
13063
13481
  const vetting = action === "update" && current.preferences.auditFirst;
13064
- yield* update((latest) => ({
13065
- ...latest,
13066
- running: true,
13067
- stopping: false
13068
- }));
13069
13482
  const touched = [...new Set(targets.map((target) => target.provider))];
13070
13483
  const confirmed = targets.filter((target) => accepted.includes(rowKey(target.pkg)));
13071
13484
  const chosen = vetting ? vet(targets.filter((target) => !accepted.includes(rowKey(target.pkg))), current.mode).pipe(Effect.flatMap(({ approved, refused }) => update((latest) => ({
@@ -13085,7 +13498,7 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13085
13498
  ...latest.activity,
13086
13499
  ...Object.fromEntries(approved.map((target) => [rowKey(target.pkg), "waiting"]))
13087
13500
  }
13088
- }))), Effect.flatMap((approved) => run$1(approved, Ref.get(stopRequested)).pipe(Stream.runForEach((event) => update((latest) => event._tag === "Progress" ? {
13501
+ }))), Effect.flatMap((approved) => run$1(approved, SubscriptionRef.get(state).pipe(Effect.map((latest) => latest.operation.context.stopping))).pipe(Stream.runForEach((event) => update((latest) => event._tag === "Progress" ? {
13089
13502
  ...latest,
13090
13503
  activity: {
13091
13504
  ...latest.activity,
@@ -13107,21 +13520,14 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13107
13520
  selected: event.outcome._tag === "Completed" ? latest.selected.filter((key) => key !== event.key) : latest.selected
13108
13521
  })), Effect.provideService(Decisions, decisions), Effect.provideService(UpdateLog, scoped(updateLog, placeOf(current))), Effect.andThen(collect$2(touched)), Effect.andThen(recallAll))), Effect.ensuring(Ref.set(pendingDecision, Option.none()).pipe(Effect.andThen(update((latest) => ({
13109
13522
  ...latest,
13110
- running: false,
13111
- stopping: false,
13112
- activity: {},
13113
- decision: void 0
13523
+ operation: step$1(latest.operation, { type: "Finish" })[0],
13524
+ activity: {}
13114
13525
  })))))));
13115
13526
  });
13116
13527
  const answerPassword = (password) => sudo.answer(password);
13117
- const stop = Effect.gen(function* () {
13118
- yield* Ref.set(stopRequested, true);
13119
- yield* update((current) => current.running ? {
13120
- ...current,
13121
- stopping: true
13122
- } : current);
13123
- });
13528
+ const stop = Effect.asVoid(send({ type: "Stop" }));
13124
13529
  const answer = Effect.fn("Session.answer")(function* (allowed) {
13530
+ if (!(yield* send({ type: "Answer" }))) return;
13125
13531
  const pending = yield* Ref.getAndSet(pendingDecision, Option.none());
13126
13532
  if (Option.isSome(pending)) yield* Deferred.succeed(pending.value, allowed);
13127
13533
  });
@@ -13178,73 +13584,86 @@ const layer = Layer.effect(Session, Effect.gen(function* () {
13178
13584
  ...stored,
13179
13585
  auditFirst: on
13180
13586
  }));
13181
- const setSignIn = (signIn) => update((current) => ({
13182
- ...current,
13183
- signIn
13184
- }));
13185
- const saveKey = (provider, key) => setSignIn({
13186
- _tag: "Working",
13187
- provider,
13188
- notices: []
13189
- }).pipe(Effect.andThen(ai.setApiKey(provider, key)), Effect.matchEffect({
13190
- onSuccess: () => setSignIn(void 0).pipe(Effect.andThen(loadCatalogue)),
13191
- onFailure: (error) => setSignIn({
13192
- _tag: "Failed",
13193
- provider,
13587
+ const sendSignIn = (event) => SubscriptionRef.modify(state, (current) => {
13588
+ const stepped = step(current.signIn, event);
13589
+ const [next, handled] = stepped;
13590
+ return [stepped, handled ? {
13591
+ ...current,
13592
+ signIn: next
13593
+ } : current];
13594
+ });
13595
+ const takeAnswer = (taken) => Ref.modify(pendingAnswer, (pending) => Option.isSome(pending) && taken(pending.value.attempt) ? [pending, Option.none()] : [Option.none(), pending]);
13596
+ const finish = (attempt) => Effect.matchEffect({
13597
+ onSuccess: () => sendSignIn({
13598
+ type: "Succeed",
13599
+ attempt
13600
+ }).pipe(Effect.andThen(loadCatalogue)),
13601
+ onFailure: (error) => sendSignIn({
13602
+ type: "Fail",
13603
+ attempt,
13194
13604
  issue: error.issue
13195
13605
  })
13196
- }));
13606
+ });
13607
+ const saveKey = (provider, key) => Effect.gen(function* () {
13608
+ const [saving, handled] = yield* sendSignIn({
13609
+ type: "SaveKey",
13610
+ provider
13611
+ });
13612
+ if (handled) yield* ai.setApiKey(provider, key).pipe(finish(saving.context.attempt));
13613
+ });
13197
13614
  const signIn = (provider) => Effect.gen(function* () {
13198
- yield* setSignIn({
13199
- _tag: "Working",
13200
- provider,
13201
- notices: []
13615
+ const [working, handled] = yield* sendSignIn({
13616
+ type: "Start",
13617
+ provider
13202
13618
  });
13619
+ if (!handled) return;
13620
+ const { attempt } = working.context;
13203
13621
  const context = yield* Effect.context();
13204
- const working = (change) => update((current) => current.signIn?._tag === "Working" && current.signIn.provider === provider ? {
13205
- ...current,
13206
- signIn: change(current.signIn)
13207
- } : current);
13208
13622
  yield* FiberHandle.run(signing, ai.login(provider, {
13209
13623
  notify: (event) => {
13210
- const notice = noticeOf(event);
13211
- Effect.runForkWith(context)(working((current) => ({
13212
- ...current,
13213
- notices: [...current.notices, notice]
13214
- })));
13624
+ Effect.runForkWith(context)(sendSignIn({
13625
+ type: "Noticed",
13626
+ attempt,
13627
+ notice: noticeOf(event)
13628
+ }));
13215
13629
  },
13216
13630
  prompt: async (prompt) => await Effect.runPromiseWith(context)(Effect.gen(function* () {
13217
13631
  const reply = yield* Deferred.make();
13218
- yield* Ref.set(pendingAnswer, Option.some(reply));
13219
- yield* working((current) => ({
13220
- ...current,
13632
+ const replaced = yield* Ref.modify(pendingAnswer, (pending) => Option.isSome(pending) && pending.value.attempt >= attempt ? [void 0, pending] : [pending, Option.some({
13633
+ attempt,
13634
+ reply
13635
+ })]);
13636
+ if (replaced === void 0) return yield* Effect.interrupt;
13637
+ if (Option.isSome(replaced)) yield* Deferred.interrupt(replaced.value.reply);
13638
+ const [, asked] = yield* sendSignIn({
13639
+ type: "Prompt",
13640
+ attempt,
13221
13641
  prompt
13222
- }));
13642
+ });
13643
+ if (!asked) {
13644
+ yield* takeAnswer((stored) => stored === attempt);
13645
+ return yield* Effect.interrupt;
13646
+ }
13223
13647
  return yield* Deferred.await(reply);
13224
13648
  }))
13225
- }).pipe(Effect.matchEffect({
13226
- onSuccess: () => setSignIn(void 0).pipe(Effect.andThen(loadCatalogue)),
13227
- onFailure: (error) => setSignIn({
13228
- _tag: "Failed",
13229
- provider,
13230
- issue: error.issue
13231
- })
13232
- })));
13649
+ }).pipe(finish(attempt)));
13233
13650
  });
13234
13651
  const answerSignIn = (reply) => Effect.gen(function* () {
13235
- const pending = yield* Ref.getAndSet(pendingAnswer, Option.none());
13236
- if (Option.isNone(pending)) return;
13237
- yield* update((current) => current.signIn?._tag === "Working" ? {
13238
- ...current,
13239
- signIn: {
13240
- _tag: "Working",
13241
- provider: current.signIn.provider,
13242
- notices: current.signIn.notices
13243
- }
13244
- } : current);
13245
- yield* Deferred.succeed(pending.value, reply);
13652
+ const { attempt } = (yield* SubscriptionRef.get(state)).signIn.context;
13653
+ const [, handled] = yield* sendSignIn({
13654
+ type: "Answer",
13655
+ attempt
13656
+ });
13657
+ if (!handled) return;
13658
+ const pending = yield* takeAnswer((stored) => stored === attempt);
13659
+ if (Option.isSome(pending)) yield* Deferred.succeed(pending.value.reply, reply);
13660
+ });
13661
+ const closeSignIn = Effect.gen(function* () {
13662
+ const [closed] = yield* sendSignIn({ type: "Close" });
13663
+ yield* FiberHandle.clear(signing);
13664
+ const pending = yield* takeAnswer((stored) => stored <= closed.context.attempt);
13665
+ if (Option.isSome(pending)) yield* Deferred.interrupt(pending.value.reply);
13246
13666
  });
13247
- const closeSignIn = FiberHandle.clear(signing).pipe(Effect.andThen(Ref.set(pendingAnswer, Option.none())), Effect.andThen(setSignIn(void 0)));
13248
13667
  const awaitIdle = FiberHandle.awaitEmpty(operation);
13249
13668
  return Session.of({
13250
13669
  changes: SubscriptionRef.changes(state),
@@ -13284,7 +13703,7 @@ const silenceNodeWarningsWhileTuiDraws = () => {
13284
13703
  };
13285
13704
  const loadTui = Effect.suspend(() => {
13286
13705
  silenceNodeWarningsWhileTuiDraws();
13287
- return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.12 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-BiTtZ1EF.mjs"));
13706
+ return process.getBuiltinModule("node:ffi") === void 0 ? Effect.die(`upup's TUI needs Node 26.13 or newer; this is Node ${process.versions.node}`) : Effect.promise(async () => await import("./Tui-DV_6wbrQ.mjs"));
13288
13707
  });
13289
13708
  const open$1 = (begin, projectDir) => Effect.gen(function* () {
13290
13709
  const { launch } = yield* loadTui;