@dbx-tools/core 0.9.12 → 0.9.18

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/src/bin.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  } from "node:fs/promises";
19
19
  import { homedir, tmpdir } from "node:os";
20
20
  import { basename, join } from "node:path";
21
+ import { fileURLToPath } from "node:url";
21
22
  import { promisify } from "node:util";
22
23
 
23
24
  import { errorUtils, log } from "@dbx-tools/shared-core";
@@ -81,16 +82,31 @@ export interface BinOptions {
81
82
  versionArgument?: string;
82
83
  /** Version output parser. Defaults to {@link parseVersion}. */
83
84
  versionParser?: BinVersionParser;
85
+ /** Skip `--version` and treat an executable file as acceptable. */
86
+ skipVersionCheck?: boolean;
87
+ }
88
+
89
+ /** Temporary directory created for one `ensure` attempt after the install lock. */
90
+ export interface BinUrlResolveContext {
91
+ readonly tempDir: string;
84
92
  }
85
93
 
86
94
  /** Download source resolved only when the executable is not already installed. */
87
95
  export interface BinSource {
88
96
  url: string;
89
97
  sha256?: string;
98
+ /**
99
+ * Skip `--version` on this candidate. Use when the source already identified
100
+ * the requested version, such as `cargo install --version`.
101
+ */
102
+ trustVersion?: boolean;
90
103
  }
91
104
 
92
105
  /** A download source resolved only when the executable is not already installed. */
93
- export type BinUrl = string | BinSource | (() => string | BinSource | Promise<string | BinSource>);
106
+ export type BinUrl =
107
+ | string
108
+ | BinSource
109
+ | ((context: BinUrlResolveContext) => string | BinSource | Promise<string | BinSource>);
94
110
 
95
111
  function context(name: string, homeDir: string, destination?: BinContext): BinContext {
96
112
  if (!name || basename(name) !== name || name === "." || name === "..") {
@@ -188,6 +204,10 @@ async function isValidBin(path: string, options: BinOptions): Promise<boolean> {
188
204
  logger.debug("binary access check failed", { path, ...access });
189
205
  return false;
190
206
  }
207
+ if (options.skipVersionCheck) {
208
+ logger.debug("binary version check skipped", { path });
209
+ return true;
210
+ }
191
211
  let stdout: string;
192
212
  let stderr: string;
193
213
  try {
@@ -262,6 +282,16 @@ async function selectSingleFile(source: string): Promise<string> {
262
282
  return selected;
263
283
  }
264
284
 
285
+ function fileUrlPath(url: string): string | undefined {
286
+ try {
287
+ const parsed = new URL(url);
288
+ if (parsed.protocol !== "file:") return undefined;
289
+ return fileURLToPath(parsed);
290
+ } catch {
291
+ return undefined;
292
+ }
293
+ }
294
+
265
295
  async function selectedBin(
266
296
  destination: BinContext,
267
297
  source: BinSource,
@@ -269,6 +299,15 @@ async function selectedBin(
269
299
  options: BinOptions,
270
300
  ): Promise<string> {
271
301
  const { url } = source;
302
+ const localPath = fileUrlPath(url);
303
+ if (localPath) {
304
+ const access = await accessContext(localPath);
305
+ if (!access.file) {
306
+ throw new Error(`binary file URL does not exist: ${displayUrl(url)}`);
307
+ }
308
+ logger.debug("using local binary", { from: displayUrl(url), path: localPath });
309
+ return localPath;
310
+ }
272
311
  const name = downloadName(url, basename(destination.path));
273
312
  const downloadPath = join(temp, name);
274
313
  logger.debug("downloading binary", {
@@ -347,22 +386,27 @@ export async function ensure(
347
386
  return destination;
348
387
  }
349
388
 
350
- const resolved = typeof url === "function" ? await url() : url;
351
- const source = typeof resolved === "string" ? { url: resolved } : resolved;
352
- const from = displayUrl(source.url);
353
389
  logger.debug("installing binary", {
354
390
  name,
355
- from,
356
391
  to: destination.path,
357
392
  minVersion: options.minVersion,
358
393
  });
359
394
  const temp = await mkdtemp(join(tmpdir(), `${name}-`));
360
395
  let staged: string | undefined;
361
396
  try {
397
+ const resolved = typeof url === "function" ? await url({ tempDir: temp }) : url;
398
+ const source = typeof resolved === "string" ? { url: resolved } : resolved;
399
+ const from = displayUrl(source.url);
400
+ logger.debug("resolved binary source", { name, from });
362
401
  const selected = await selectedBin(destination, source, temp, options);
363
402
  await chmod(selected, 0o755);
364
- if (!(await isValidBin(selected, options))) {
365
- throw new Error(`selected binary has no acceptable version: ${selected}`);
403
+ const accepted = source.trustVersion ? { ...options, skipVersionCheck: true } : options;
404
+ if (!(await isValidBin(selected, accepted))) {
405
+ throw new Error(
406
+ source.trustVersion
407
+ ? `selected binary is not executable: ${selected}`
408
+ : `selected binary has no acceptable version: ${selected}`,
409
+ );
366
410
  }
367
411
 
368
412
  await mkdir(destination.binDir, { recursive: true });
@@ -371,7 +415,7 @@ export async function ensure(
371
415
  await chmod(staged, 0o755);
372
416
  await rename(staged, destination.path);
373
417
  staged = undefined;
374
- if (!(await isValidBin(destination.path, options))) {
418
+ if (!(await isValidBin(destination.path, accepted))) {
375
419
  throw new Error(`installed binary is invalid after rename: ${destination.path}`);
376
420
  }
377
421
  logger.info("installed binary", {