@n-seiji/nuthatch 0.1.7 → 0.2.1

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.
@@ -2,16 +2,11 @@ import {
2
2
  isRunningAsCompiledBinary,
3
3
  wantsJson,
4
4
  reportFatalError,
5
- firstExecutable,
6
5
  VERSION
7
- } from "./cli-3ya15je0.js";
6
+ } from "./cli-sre95k9x.js";
8
7
  import {
9
- parentDir,
10
- baseName,
11
- absoluteCandidates,
12
- splitPathEnv,
13
8
  messageOf
14
- } from "./cli-qrxgkskr.js";
9
+ } from "./cli-ph0gp56k.js";
15
10
  import {
16
11
  reportResult
17
12
  } from "./cli-dbzx9nzg.js";
@@ -21,43 +16,67 @@ import {
21
16
  EXIT_SAFE_REJECTION,
22
17
  ok,
23
18
  fail
24
- } from "./cli-2bhb9p0x.js";
19
+ } from "./cli-77yanq37.js";
20
+ import {
21
+ parentDir
22
+ } from "./cli-s5qcjcwk.js";
25
23
 
26
- // src/domain/install-roots.ts
27
- var MANAGER_ROOTS = [
28
- {
29
- markers: ["/Cellar/", "/Caskroom/"],
30
- manager: "Homebrew",
31
- update: '"brew upgrade"',
32
- ownsNpmPrefix: true
33
- },
34
- {
35
- markers: ["/nix/store/"],
36
- manager: "Nix",
37
- update: "Nix (the store is read-only)",
38
- ownsNpmPrefix: true
39
- },
40
- {
41
- markers: ["/aquaproj-aqua/", "/aqua/pkgs/"],
42
- manager: "aqua",
43
- update: "aqua",
44
- ownsNpmPrefix: false
45
- },
46
- { markers: ["/.proto/tools/"], manager: "proto", update: "proto", ownsNpmPrefix: false }
24
+ // src/domain/install-method.ts
25
+ var RELEASE_ASSETS = new Map([
26
+ ["darwin-arm64", "hop-darwin-arm64"],
27
+ ["darwin-x64", "hop-darwin-x64"],
28
+ ["linux-x64", "hop-linux-x64"]
29
+ ]);
30
+ var MANAGED_TREES = [
31
+ "/Cellar/",
32
+ "/Caskroom/",
33
+ "/nix/store/",
34
+ "/aquaproj-aqua/",
35
+ "/aqua/pkgs/",
36
+ "/.proto/",
37
+ "/installs/"
47
38
  ];
48
- var reasonWithin = (path, roots) => {
49
- const root = roots.find(({ markers }) => markers.some((marker) => path.includes(marker)));
50
- return root === undefined ? null : `installed by ${root.manager} (${path}); update it with ${root.update}`;
39
+ var MISE_TREE = "/mise/installs/";
40
+ var NODE_MODULES = "/node_modules/";
41
+ var detectInstallMethod = (facts) => {
42
+ const path = facts.compiled ? facts.executablePath : facts.scriptPath;
43
+ if (path === null) {
44
+ return { kind: "other" };
45
+ }
46
+ if (path.includes(MISE_TREE)) {
47
+ return { kind: "mise" };
48
+ }
49
+ if (!facts.compiled) {
50
+ return { kind: path.includes(NODE_MODULES) ? "package-manager" : "other" };
51
+ }
52
+ const assetName = RELEASE_ASSETS.get(`${facts.platform}-${facts.arch}`);
53
+ if (assetName === undefined || MANAGED_TREES.some((tree) => path.includes(tree))) {
54
+ return { kind: "other" };
55
+ }
56
+ return {
57
+ kind: "standalone",
58
+ binaryPath: path,
59
+ installDir: parentDir(path),
60
+ assetName
61
+ };
62
+ };
63
+ var suggestedCommand = (kind) => {
64
+ if (kind === "mise") {
65
+ return ["mise", "upgrade"];
66
+ }
67
+ return kind === "package-manager" ? ["npm", "i", "-g", "@n-seiji/nuthatch@latest"] : null;
68
+ };
69
+ var updateHint = (kind) => {
70
+ const command = suggestedCommand(kind)?.join(" ");
71
+ if (kind === "mise") {
72
+ return `Update with mise: ${command}`;
73
+ }
74
+ return kind === "package-manager" ? `Update with your package manager, e.g. ${command}` : "Update hop with the tool you installed it with.";
51
75
  };
52
- var managerRootReason = (binaryPath) => reasonWithin(binaryPath, MANAGER_ROOTS);
53
- var managerNpmPrefixReason = (prefix) => reasonWithin(prefix, MANAGER_ROOTS.filter(({ ownsNpmPrefix }) => ownsNpmPrefix));
54
76
 
55
77
  // src/domain/self-update.ts
56
- var NPM_PACKAGE = "@n-seiji/nuthatch";
57
- var NPM_PACKAGE_LATEST = `${NPM_PACKAGE}@latest`;
58
78
  var GITHUB_REPO = "n-seiji/nuthatch";
59
79
  var GITHUB_LATEST_RELEASE_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`;
60
- var NPM_LATEST_URL = `https://registry.npmjs.org/${NPM_PACKAGE.replace("/", "%2F")}/latest`;
61
80
  var releaseAssetUrl = (version, assetName) => `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${assetName}`;
62
81
  var VERSION_PATTERN = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$/u;
63
82
  var TAG_PATTERN = /^v(?<version>\d+\.\d+\.\d+)$/u;
@@ -74,10 +93,6 @@ var compareVersions = (a, b) => {
74
93
  const [bMajor, bMinor, bPatch] = parseVersion(b);
75
94
  return aMajor - bMajor || aMinor - bMinor || aPatch - bPatch;
76
95
  };
77
- var requireVersion = (text) => {
78
- parseVersion(text);
79
- return text;
80
- };
81
96
  var versionFromTag = (tag) => {
82
97
  const version = TAG_PATTERN.exec(tag)?.groups?.version;
83
98
  if (version === undefined) {
@@ -96,165 +111,8 @@ var parseChecksumFile = (text, assetName) => {
96
111
  }
97
112
  return { ok: true, digest: digest.toLowerCase() };
98
113
  };
99
- var pathExecutableCandidates = (name, pathEnv, preferredDir = null) => absoluteCandidates([...preferredDir === null ? [] : [preferredDir], ...splitPathEnv(pathEnv)], name);
100
114
  var isWriteDenied = (error) => error instanceof Error && ("code" in error) && (error.code === "EACCES" || error.code === "EPERM" || error.code === "EROFS");
101
115
 
102
- // src/domain/install-script.ts
103
- var NPX_MARKER = "/_npx/";
104
- var BUNX_MARKER = "/bunx-";
105
- var BUN_GLOBAL_MARKER = "/.bun/install/global/node_modules/";
106
- var NPM_GLOBAL_MARKER = `/lib/node_modules/${NPM_PACKAGE}/`;
107
- var NODE_MODULES_MARKER = `/node_modules/${NPM_PACKAGE}/`;
108
- var PNPM_GLOBAL_MARKER = "/pnpm/global/";
109
- var YARN_GLOBAL_MARKER = "/yarn/global/";
110
- var unsupported = (reason) => ({ kind: "unsupported", reason });
111
- var npmGlobalPrefixOf = (scriptPath) => {
112
- const index = scriptPath.lastIndexOf(NPM_GLOBAL_MARKER);
113
- return index > 0 ? scriptPath.slice(0, index) : null;
114
- };
115
- var otherNodeModulesReason = (scriptPath) => {
116
- if (scriptPath.includes(PNPM_GLOBAL_MARKER)) {
117
- return `installed globally with pnpm, which hop does not drive; run "pnpm add -g ${NPM_PACKAGE_LATEST}" yourself`;
118
- }
119
- if (scriptPath.includes(YARN_GLOBAL_MARKER)) {
120
- return `installed globally with yarn, which hop does not drive; run "yarn global add ${NPM_PACKAGE_LATEST}" yourself`;
121
- }
122
- return "installed as a project dependency (a local node_modules); update it in that project";
123
- };
124
- var detectScriptInstall = (scriptPath, npmGlobalPrefix) => {
125
- if (scriptPath.includes(NPX_MARKER)) {
126
- return unsupported("running through npx, which already fetches the published version on every run");
127
- }
128
- if (scriptPath.includes(BUNX_MARKER)) {
129
- return unsupported("running through bunx, which already fetches the published version on every run");
130
- }
131
- if (scriptPath.includes(BUN_GLOBAL_MARKER)) {
132
- return { kind: "bun" };
133
- }
134
- if (npmGlobalPrefix !== null) {
135
- const managedBy = managerNpmPrefixReason(npmGlobalPrefix);
136
- return managedBy === null ? { kind: "npm", prefix: npmGlobalPrefix } : unsupported(managedBy);
137
- }
138
- if (scriptPath.includes(NODE_MODULES_MARKER)) {
139
- return unsupported(otherNodeModulesReason(scriptPath));
140
- }
141
- return unsupported("running from a source checkout; update it with git (git pull)");
142
- };
143
-
144
- // src/domain/mise-tool.ts
145
- var MISE_WALK_LIMIT = 8;
146
- var HOP_BACKEND_PATTERN = /^[a-z0-9_-]+:@?n-seiji\/nuthatch$/iu;
147
- var HOP_TOOL_DIR_PATTERN = /(?:^|-)n-seiji-nuthatch$/iu;
148
- var parseMiseBackendToml = (text) => {
149
- const match = /^\s*full\s*=\s*"(?<tool>[^"]+)"\s*$/mu.exec(text);
150
- return match?.groups?.tool ?? null;
151
- };
152
- var parseLegacyMiseBackend = (text) => {
153
- const lines = text.split(`
154
- `).map((line) => line.trim()).filter((line) => line.length > 0);
155
- return lines.at(-1) ?? null;
156
- };
157
- var withoutToolOptions = (backend) => backend.replace(/\[[^\]]*\]$/u, "");
158
- var isHopBackend = (backend) => HOP_BACKEND_PATTERN.test(backend);
159
- var isHopToolDirName = (name) => HOP_TOOL_DIR_PATTERN.test(name);
160
- var ancestorDirs = (path, limit) => {
161
- const dirs = [];
162
- let dir = parentDir(path);
163
- while (dirs.length < limit && dir !== "/" && dir !== ".") {
164
- dirs.push(dir);
165
- dir = parentDir(dir);
166
- }
167
- return dirs;
168
- };
169
- var miseToolDir = (path) => ancestorDirs(path, MISE_WALK_LIMIT).find((dir) => baseName(parentDir(dir)) === "installs") ?? null;
170
-
171
- // src/domain/install-method.ts
172
- var RELEASE_ASSETS = new Map([
173
- ["darwin-arm64", "hop-darwin-arm64"],
174
- ["darwin-x64", "hop-darwin-x64"],
175
- ["linux-x64", "hop-linux-x64"]
176
- ]);
177
- var releaseAssetName = (platform, arch) => RELEASE_ASSETS.get(`${platform}-${arch}`) ?? null;
178
- var GITHUB_BACKEND_PREFIXES = ["github:", "ubi:", "aqua:"];
179
- var NPM_BACKEND_PREFIX = "npm:";
180
- var unsupported2 = (reason) => ({
181
- kind: "unsupported",
182
- reason
183
- });
184
- var miseChannel = (tool) => {
185
- if (tool.startsWith(NPM_BACKEND_PREFIX)) {
186
- return "npm";
187
- }
188
- return GITHUB_BACKEND_PREFIXES.some((prefix) => tool.startsWith(prefix)) ? "github" : null;
189
- };
190
- var insideVersionManager = (dir, marker) => unsupported2(`installed under a version manager's install dir (${dir}) ${marker}; update it with the tool that installed it`);
191
- var detectUnmarkedToolDir = (dir, compiled) => {
192
- if (isHopToolDirName(baseName(dir))) {
193
- return unsupported2(`installed by mise, but its backend is unknown (no readable marker in ${dir}); run "mise upgrade" yourself`);
194
- }
195
- return compiled ? insideVersionManager(dir, "without a readable mise marker") : null;
196
- };
197
- var detectMise = ({ mise, compiled }) => {
198
- if (mise === null) {
199
- return null;
200
- }
201
- if (mise.backend === null) {
202
- return detectUnmarkedToolDir(mise.dir, compiled);
203
- }
204
- const tool = withoutToolOptions(mise.backend);
205
- if (!isHopBackend(tool)) {
206
- return compiled ? insideVersionManager(mise.dir, `whose marker names "${tool}", not hop`) : null;
207
- }
208
- const channel = miseChannel(tool);
209
- return channel === null ? unsupported2(`mise installed hop through "${tool}", which is neither a GitHub nor an npm backend; run "mise upgrade ${tool}" yourself`) : { kind: "mise", tool, channel };
210
- };
211
- var detectStandalone = (facts) => {
212
- const { executablePath, platform, arch } = facts;
213
- if (executablePath === null) {
214
- return unsupported2("cannot tell where the hop binary is installed");
215
- }
216
- const managedBy = managerRootReason(executablePath);
217
- if (managedBy !== null) {
218
- return unsupported2(managedBy);
219
- }
220
- const assetName = releaseAssetName(platform, arch);
221
- if (assetName === null) {
222
- return unsupported2(`there is no prebuilt binary for ${platform}-${arch}; install with npm instead (npm i -g ${NPM_PACKAGE})`);
223
- }
224
- return {
225
- kind: "standalone",
226
- binaryPath: executablePath,
227
- installDir: parentDir(executablePath),
228
- assetName
229
- };
230
- };
231
- var detectInstallMethod = (facts) => {
232
- const mise = detectMise(facts);
233
- if (mise !== null) {
234
- return mise;
235
- }
236
- return facts.compiled ? detectStandalone(facts) : detectScriptInstall(facts.scriptPath ?? "", facts.npmGlobalPrefix);
237
- };
238
- var latestChannel = (method) => {
239
- if (method.kind === "mise") {
240
- return method.channel;
241
- }
242
- return method.kind === "standalone" ? "github" : "npm";
243
- };
244
- var upgradeCommand = (method) => {
245
- if (method.kind === "mise") {
246
- return ["mise", "upgrade", method.tool];
247
- }
248
- return method.kind === "npm" ? ["npm", "install", "-g", "--prefix", method.prefix, NPM_PACKAGE_LATEST] : ["bun", "add", "-g", NPM_PACKAGE_LATEST];
249
- };
250
- var miseDryRunCommand = (method) => [
251
- "mise",
252
- "upgrade",
253
- "--dry-run-code",
254
- method.tool
255
- ];
256
- var preferredProgramDir = (method) => method.kind === "npm" ? `${method.prefix}/bin` : null;
257
-
258
116
  // src/commands/self-update-attempt.ts
259
117
  var attempt = async (run) => {
260
118
  try {
@@ -264,48 +122,6 @@ var attempt = async (run) => {
264
122
  }
265
123
  };
266
124
 
267
- // src/commands/self-update-delegated.ts
268
- var MISE_WOULD_NOT_UPGRADE = 0;
269
- var runLogged = (port, term, executable, [, ...args]) => {
270
- const command = [executable, ...args];
271
- term.logStderr(`Running: ${command.join(" ")}`);
272
- return attempt(() => port.runCommand(command));
273
- };
274
- var miseNothingToUpgradeWarning = ({ tool }, { current, latest }) => [
275
- `mise has nothing to upgrade for ${tool}, so hop stays at ${current} although ${latest} is the latest release.`,
276
- "Likely causes: the version is pinned in your global mise config, a minimum_release_age setting holds the release back,",
277
- `or ${tool} is not in your global mise config (hop runs mise from your home directory, so a project's mise.toml does not count).`,
278
- `Run "mise upgrade --dry-run ${tool}" from your home directory to see what mise decides.`
279
- ].join(" ");
280
- var runDelegated = async (port, term, method, base) => {
281
- const argv = upgradeCommand(method);
282
- const [program] = argv;
283
- const command = argv.join(" ");
284
- const executable = await port.resolveExecutable(program, preferredProgramDir(method));
285
- if (executable === null) {
286
- return fail(EXIT_GENERAL_ERROR, `Cannot find "${program}" on PATH. Run it yourself: ${command}`);
287
- }
288
- if (method.kind === "mise") {
289
- const question = miseDryRunCommand(method);
290
- const answer = await runLogged(port, term, executable, question);
291
- if (!answer.ok) {
292
- return fail(EXIT_GENERAL_ERROR, `Failed to run ${question.join(" ")}: ${messageOf(answer.error)}`);
293
- }
294
- if (answer.value === MISE_WOULD_NOT_UPGRADE) {
295
- return ok({ data: base, warnings: [miseNothingToUpgradeWarning(method, base)] });
296
- }
297
- }
298
- const run = await runLogged(port, term, executable, argv);
299
- if (!run.ok) {
300
- return fail(EXIT_GENERAL_ERROR, `Failed to run ${command}: ${messageOf(run.error)}`);
301
- }
302
- if (run.value !== 0) {
303
- return fail(EXIT_GENERAL_ERROR, `${command} exited with code ${run.value}.`);
304
- }
305
- term.logStderr(`${command} finished; run "hop --version" to see which version you have now.`);
306
- return ok({ data: { ...base, action: "delegated", command: [...argv] } });
307
- };
308
-
309
125
  // src/commands/self-update-standalone.ts
310
126
  var checksumRejection = (port, assetName, checksumText, bytes) => {
311
127
  const expected = parseChecksumFile(checksumText, assetName);
@@ -349,24 +165,18 @@ var replaceStandalone = async (port, term, method, base) => {
349
165
  };
350
166
 
351
167
  // src/commands/self-update.ts
352
- var fetchLatest = (port, method) => latestChannel(method) === "npm" ? port.latestNpmVersion() : port.latestGithubVersion();
353
- var checkCommand = (method) => method.kind === "standalone" ? null : [...upgradeCommand(method)];
354
- var describeCheck = (method, base) => {
355
- const how = method.kind === "standalone" ? `replaces ${method.binaryPath}` : `runs: ${upgradeCommand(method).join(" ")}`;
356
- return `Update available: ${base.current} -> ${base.latest}. "hop --update" ${how}.`;
357
- };
358
168
  var selfUpdate = async (port, term, options) => {
359
169
  const facts = await attempt(() => port.installFacts());
360
170
  if (!facts.ok) {
361
171
  return fail(EXIT_GENERAL_ERROR, `Cannot update hop: could not tell how it was installed (${messageOf(facts.error)}).`);
362
172
  }
363
173
  const method = detectInstallMethod(facts.value);
364
- if (method.kind === "unsupported") {
365
- return fail(EXIT_GENERAL_ERROR, `Cannot update hop: ${method.reason}.`);
174
+ if (method.kind !== "standalone" && !options.check) {
175
+ return fail(EXIT_GENERAL_ERROR, updateHint(method.kind));
366
176
  }
367
177
  term.logStderr("Checking for the latest release...");
368
178
  const release = await attempt(async () => {
369
- const latest = await fetchLatest(port, method);
179
+ const latest = await port.latestGithubVersion();
370
180
  return {
371
181
  latest,
372
182
  updateAvailable: compareVersions(latest, options.currentVersion) > 0
@@ -387,94 +197,43 @@ var selfUpdate = async (port, term, options) => {
387
197
  term.logStderr(`hop ${base.current} is up to date (latest release: ${base.latest}).`);
388
198
  return ok({ data: base });
389
199
  }
200
+ const available = `Update available: ${base.current} -> ${base.latest}.`;
201
+ if (method.kind !== "standalone") {
202
+ term.logStderr(`${available} ${updateHint(method.kind)}`);
203
+ const command = suggestedCommand(method.kind);
204
+ return ok({
205
+ data: { ...base, command: command === null ? null : [...command] }
206
+ });
207
+ }
390
208
  if (options.check) {
391
- term.logStderr(describeCheck(method, base));
392
- return ok({ data: { ...base, command: checkCommand(method) } });
209
+ term.logStderr(`${available} "hop --update" replaces ${method.binaryPath}.`);
210
+ return ok({ data: base });
393
211
  }
394
- return method.kind === "standalone" ? replaceStandalone(port, term, method, base) : runDelegated(port, term, method, base);
212
+ return replaceStandalone(port, term, method, base);
395
213
  };
396
214
 
397
215
  // src/infra/install-facts.ts
398
- import { readFile, realpath } from "node:fs/promises";
399
- import { join } from "node:path";
400
- var MISE_BACKEND_TOML = ".mise.backend.toml";
401
- var MISE_BACKEND_LEGACY = ".mise.backend";
402
- var realpathOrNull = async (path) => {
216
+ import { realpath } from "node:fs/promises";
217
+ var realpathOrSelf = async (path) => {
403
218
  try {
404
219
  return await realpath(path);
405
220
  } catch {
406
- return null;
407
- }
408
- };
409
- var realpathOrSelf = async (path) => await realpathOrNull(path) ?? path;
410
- var readTextOrNull = async (path) => {
411
- try {
412
- return await readFile(path, "utf8");
413
- } catch {
414
- return null;
415
- }
416
- };
417
- var readMiseBackend = async (dir) => {
418
- const toml = await readTextOrNull(join(dir, MISE_BACKEND_TOML));
419
- const fromToml = toml === null ? null : parseMiseBackendToml(toml);
420
- if (fromToml !== null) {
421
- return fromToml;
422
- }
423
- const legacy = await readTextOrNull(join(dir, MISE_BACKEND_LEGACY));
424
- return legacy === null ? null : parseLegacyMiseBackend(legacy);
425
- };
426
- var findMiseTool = async (path) => {
427
- const dir = path === null ? null : miseToolDir(path);
428
- return dir === null ? null : { dir, backend: await readMiseBackend(dir) };
429
- };
430
- var verifiedNpmPrefix = async (scriptPath) => {
431
- const prefix = scriptPath === null ? null : npmGlobalPrefixOf(scriptPath);
432
- if (prefix === null) {
433
- return null;
221
+ return path;
434
222
  }
435
- return await realpathOrNull(join(prefix, "bin", "hop")) === scriptPath ? prefix : null;
436
223
  };
437
- var readInstallFacts = async (source) => {
438
- const executablePath = source.compiled ? await realpathOrSelf(source.execPath) : null;
439
- const scriptPath = source.compiled || source.argv1 === undefined ? null : await realpathOrSelf(source.argv1);
440
- return {
441
- compiled: source.compiled,
442
- executablePath,
443
- scriptPath,
444
- mise: await findMiseTool(executablePath ?? scriptPath),
445
- npmGlobalPrefix: await verifiedNpmPrefix(scriptPath),
446
- platform: source.platform,
447
- arch: source.arch
448
- };
449
- };
450
-
451
- // src/infra/package-manager.ts
452
- import { spawn } from "node:child_process";
453
- import { homedir } from "node:os";
454
- import { basename } from "node:path";
455
- var STDERR_FD = 2;
456
- var resolveExecutable = (name, pathEnv, preferredDir = null) => firstExecutable(pathExecutableCandidates(name, pathEnv, preferredDir));
457
- var runCommand = (argv) => new Promise((resolve, reject) => {
458
- const [program, ...args] = argv;
459
- const child = spawn(program, args, {
460
- cwd: homedir(),
461
- stdio: ["inherit", STDERR_FD, STDERR_FD]
462
- });
463
- child.once("error", reject);
464
- child.once("close", (code, signal) => {
465
- if (code === null) {
466
- reject(new Error(`${basename(program)} was killed by signal ${signal ?? "unknown"}`));
467
- return;
468
- }
469
- resolve(code);
470
- });
224
+ var readInstallFacts = async (source) => ({
225
+ compiled: source.compiled,
226
+ executablePath: source.compiled ? await realpathOrSelf(source.execPath) : null,
227
+ scriptPath: source.compiled || source.argv1 === undefined ? null : await realpathOrSelf(source.argv1),
228
+ platform: source.platform,
229
+ arch: source.arch
471
230
  });
472
231
 
473
232
  // src/infra/release-binary.ts
474
233
  import { createHash, randomBytes } from "node:crypto";
475
234
  import { constants } from "node:fs";
476
235
  import { access, open, realpath as realpath2, rename, rm } from "node:fs/promises";
477
- import { basename as basename2, dirname, join as join2 } from "node:path";
236
+ import { basename, dirname, join } from "node:path";
478
237
  var EXECUTABLE_MODE = 493;
479
238
  var TEMP_SUFFIX_BYTES = 6;
480
239
  var sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -496,7 +255,7 @@ var writeDurably = async (path, bytes) => {
496
255
  var replaceExecutable = async (path, bytes) => {
497
256
  const target = await realpath2(path);
498
257
  const suffix = randomBytes(TEMP_SUFFIX_BYTES).toString("hex");
499
- const temp = join2(dirname(target), `.${basename2(target)}.update-${suffix}`);
258
+ const temp = join(dirname(target), `.${basename(target)}.update-${suffix}`);
500
259
  try {
501
260
  await writeDurably(temp, bytes);
502
261
  await rename(temp, target);
@@ -737,7 +496,6 @@ var GITHUB_API_ORIGIN = "https://api.github.com/";
737
496
  var HTTP_FORBIDDEN = 403;
738
497
  var HTTP_TOO_MANY_REQUESTS = 429;
739
498
  var GithubLatestSchema = object({ tag_name: string() });
740
- var NpmLatestSchema = object({ version: string() });
741
499
  var DECODER = new TextDecoder;
742
500
  var send = async (fetcher, url, { headers, timeoutMs }) => {
743
501
  try {
@@ -806,14 +564,6 @@ var createReleaseHttp = (userAgent, fetcher = fetch) => ({
806
564
  });
807
565
  return versionFromTag(release.tag_name);
808
566
  },
809
- async latestNpmVersion() {
810
- const latest = await getJson(fetcher, NPM_LATEST_URL, NpmLatestSchema, {
811
- headers: { Accept: "application/json", "User-Agent": userAgent },
812
- timeoutMs: METADATA_TIMEOUT_MS,
813
- maxBytes: METADATA_MAX_BYTES
814
- });
815
- return requireVersion(latest.version);
816
- },
817
567
  async downloadReleaseAsset(version, assetName) {
818
568
  return await getBytes(fetcher, releaseAssetUrl(version, assetName), {
819
569
  headers: { Accept: "application/octet-stream", "User-Agent": userAgent },
@@ -846,9 +596,7 @@ var createSelfUpdatePort = ({
846
596
  ...createReleaseHttp(`hop/${version}`),
847
597
  sha256Hex,
848
598
  assertWritableDir,
849
- replaceExecutable,
850
- resolveExecutable: (name, preferredDir) => resolveExecutable(name, process.env.PATH, preferredDir),
851
- runCommand
599
+ replaceExecutable
852
600
  });
853
601
 
854
602
  // src/cli-update.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  EXIT_SAFE_REJECTION,
3
3
  fail
4
- } from "./cli-2bhb9p0x.js";
4
+ } from "./cli-77yanq37.js";
5
5
 
6
6
  // src/infra/lock.ts
7
7
  import { randomUUID } from "node:crypto";
@@ -1,12 +1,21 @@
1
1
  import {
2
- isWithin,
3
- classifyWorktreePath,
4
2
  parsePorcelain
5
- } from "./cli-g72ra4e5.js";
3
+ } from "./cli-b0tk5wcx.js";
6
4
  import {
7
5
  EXIT_USAGE_ERROR,
8
- fail
9
- } from "./cli-2bhb9p0x.js";
6
+ fail,
7
+ pass,
8
+ reject
9
+ } from "./cli-77yanq37.js";
10
+ import {
11
+ isWithin,
12
+ classifyWorktreePath
13
+ } from "./cli-a56b2ar0.js";
14
+
15
+ // src/domain/branch-name.ts
16
+ var isOptionLikeRef = (name) => name.startsWith("-");
17
+ var optionLikeRefRejection = (label, name) => isOptionLikeRef(name) ? fail(EXIT_USAGE_ERROR, `Refusing ${label} "${name}": names starting with "-" would be parsed as git options.`) : null;
18
+ var refArgumentRejection = (branch, track) => optionLikeRefRejection("branch", branch) ?? (track === undefined ? null : optionLikeRefRejection("--track ref", track));
10
19
 
11
20
  // src/infra/repo.ts
12
21
  import { basename, dirname, join } from "node:path";
@@ -49,9 +58,8 @@ var loadRepoContext = async (git, fs, cwd) => {
49
58
  }));
50
59
  return { rootPath, managedRoot, commonDir, worktrees };
51
60
  };
52
- var nestedWorktreePaths = (worktrees, path) => nestedWorktrees(worktrees, path).map((wt) => wt.path);
53
61
  var nestedWorktrees = (worktrees, path) => worktrees.filter((wt) => wt.path !== path && !wt.prunable && isWithin(path, wt.path));
54
- var isWorktreeDirty = (git, worktrees, path) => git.isDirty(path, nestedWorktreePaths(worktrees, path));
62
+ var isWorktreeDirty = (git, worktrees, path) => git.isDirty(path, nestedWorktrees(worktrees, path).map((wt) => wt.path));
55
63
  var worktreeDirtyState = async (git, fs, worktrees, wt) => {
56
64
  if (wt.bare || wt.prunable || !await fs.exists(wt.path)) {
57
65
  return null;
@@ -66,16 +74,17 @@ var resolveBranchCheckout = async (git, rootPath, branch, explicitTrack) => {
66
74
  const remotes = await git.remotesWithBranch(rootPath, branch);
67
75
  const resolution = resolveTrackingRef(branch, remotes);
68
76
  if (resolution.kind === "ambiguous") {
69
- return {
70
- ok: false,
71
- rejection: fail(EXIT_USAGE_ERROR, `Branch "${branch}" exists on multiple remotes (${remotes.join(", ")}). Use --track to disambiguate.`)
72
- };
77
+ return reject(fail(EXIT_USAGE_ERROR, `Branch "${branch}" exists on multiple remotes (${remotes.join(", ")}). Use --track to disambiguate.`));
73
78
  }
74
79
  if (resolution.kind === "track") {
80
+ const rejection = optionLikeRefRejection("--track ref", resolution.ref);
81
+ if (rejection !== null) {
82
+ return reject(rejection);
83
+ }
75
84
  track = resolution.ref;
76
85
  }
77
86
  }
78
- return { ok: true, options: { createBranch, ...track === undefined ? {} : { track } } };
87
+ return pass({ createBranch, ...track === undefined ? {} : { track } });
79
88
  };
80
89
  var realpathOrRaw = async (fs, path) => {
81
90
  try {
@@ -85,4 +94,4 @@ var realpathOrRaw = async (fs, path) => {
85
94
  }
86
95
  };
87
96
 
88
- export { loadRepoContext, nestedWorktrees, isWorktreeDirty, worktreeDirtyState, resolveBranchCheckout };
97
+ export { isOptionLikeRef, optionLikeRefRejection, refArgumentRejection, loadRepoContext, nestedWorktrees, isWorktreeDirty, worktreeDirtyState, resolveBranchCheckout };