@youdie006/prodex 0.40.17 → 0.40.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/prodex.mjs ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "./dist/cli.js";
3
+
4
+ runCli(process.argv.slice(2))
5
+ .then((code) => {
6
+ if (code !== 0) process.exitCode = code;
7
+ })
8
+ .catch((error) => {
9
+ console.error(error instanceof Error ? error.message : String(error));
10
+ process.exitCode = 1;
11
+ });
@@ -0,0 +1,71 @@
1
+ import { execFile } from "node:child_process";
2
+ import { realpathSync, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+
6
+ const execFileAsync = promisify(execFile);
7
+
8
+ export function resolveNpmCliPath(options = {}) {
9
+ const env = normalizeNpmEnvironment(options.env ?? process.env);
10
+ const execPath = options.execPath ?? process.execPath;
11
+ const cwd = options.cwd ?? process.cwd();
12
+ const candidates = [];
13
+ const npmExecPath = envValue(env, "npm_execpath");
14
+ if (npmExecPath && !isCompetingPackageManagerCli(npmExecPath)) candidates.push(path.resolve(cwd, npmExecPath));
15
+
16
+ const pathValue = envValue(env, "PATH");
17
+ for (const entry of pathValue?.split(path.delimiter) ?? []) {
18
+ if (!entry) continue;
19
+ for (const commandName of process.platform === "win32" ? ["npm.cmd", "npm.exe", "npm"] : ["npm"]) {
20
+ const commandPath = path.join(entry, commandName);
21
+ const resolvedCommand = existingJavaScriptFile(commandPath);
22
+ if (resolvedCommand) candidates.push(resolvedCommand);
23
+ }
24
+ candidates.push(path.join(entry, "node_modules", "npm", "bin", "npm-cli.js"));
25
+ candidates.push(path.resolve(entry, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
26
+ }
27
+
28
+ const nodeDir = path.dirname(execPath);
29
+ candidates.push(path.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js"));
30
+ candidates.push(path.resolve(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
31
+
32
+ for (const candidate of candidates) {
33
+ const resolved = existingJavaScriptFile(candidate);
34
+ if (resolved) return resolved;
35
+ }
36
+ throw new Error(`Could not locate npm's JavaScript CLI for ${execPath}`);
37
+ }
38
+
39
+ export function execNpm(args, options = {}) {
40
+ const env = normalizeNpmEnvironment(options.env ?? process.env);
41
+ const npmCliPath = resolveNpmCliPath({ cwd: options.cwd, env, execPath: process.execPath });
42
+ return execFileAsync(process.execPath, [npmCliPath, ...args], { ...options, env, encoding: "utf8" });
43
+ }
44
+
45
+ export function normalizeNpmEnvironment(env, platform = process.platform) {
46
+ if (platform !== "win32") return env;
47
+ // Node otherwise sorts duplicate Windows names and can keep the inherited
48
+ // spelling instead of an explicit override appended by the caller.
49
+ const entries = new Map();
50
+ for (const [name, value] of Object.entries(env)) entries.set(name.toLowerCase(), [name, value]);
51
+ return Object.fromEntries(entries.values());
52
+ }
53
+
54
+ function envValue(env, name) {
55
+ if (typeof env[name] === "string") return env[name];
56
+ const entry = Object.entries(env).find(([key, value]) => key.toLowerCase() === name.toLowerCase() && typeof value === "string");
57
+ return entry?.[1];
58
+ }
59
+
60
+ function isCompetingPackageManagerCli(candidate) {
61
+ return /^(?:pnpm|pnpx|yarn|yarnpkg)(?:\.(?:cjs|mjs|js))?$/i.test(path.basename(candidate));
62
+ }
63
+
64
+ function existingJavaScriptFile(candidate) {
65
+ try {
66
+ const resolved = realpathSync(candidate);
67
+ return /\.(?:cjs|mjs|js)$/i.test(resolved) && statSync(resolved).isFile() ? resolved : undefined;
68
+ } catch {
69
+ return undefined;
70
+ }
71
+ }
@@ -4,6 +4,7 @@ import { lstat, readFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { promisify } from "node:util";
7
+ import { execNpm } from "./npm-command.mjs";
7
8
 
8
9
  const execFileAsync = promisify(execFile);
9
10
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -159,7 +160,7 @@ async function runFullReleaseVerification(rootDir) {
159
160
  ];
160
161
  for (const [command, commandArgs] of checks) {
161
162
  const key = [command, ...commandArgs].join(" ");
162
- await run(commandForPlatform(command), commandArgs, rootDir, CHECK_TIMEOUT_MS[key] ?? CHECK_TIMEOUT_MS.default);
163
+ await run(command, commandArgs, rootDir, CHECK_TIMEOUT_MS[key] ?? CHECK_TIMEOUT_MS.default);
163
164
  }
164
165
  }
165
166
 
@@ -205,7 +206,7 @@ async function assertPackedFileModes(rootDir, packageJson) {
205
206
  async function readPackedFiles(rootDir) {
206
207
  let stdout;
207
208
  try {
208
- ({ stdout } = await execFileAsync(commandForPlatform("npm"), ["pack", "--json", "--dry-run", "--ignore-scripts"], {
209
+ ({ stdout } = await execNpm(["pack", "--json", "--dry-run", "--ignore-scripts"], {
209
210
  cwd: rootDir,
210
211
  timeout: 120_000,
211
212
  maxBuffer: 20 * 1024 * 1024
@@ -282,6 +283,9 @@ function findNonExecutableBinPackedFiles(files, packageJson) {
282
283
  }
283
284
 
284
285
  async function findNonExecutableBinSourceFiles(rootDir, packageJson) {
286
+ // Windows ACLs do not map to POSIX execute bits. npm still reports package
287
+ // bin entries with canonical executable modes in the packed file metadata.
288
+ if (process.platform === "win32") return [];
285
289
  const invalid = [];
286
290
  for (const packagePath of packageBinPaths(packageJson)) {
287
291
  const filePath = path.join(rootDir, packagePath);
@@ -349,11 +353,18 @@ async function run(command, commandArgs, cwd, timeoutMs = 300_000) {
349
353
  const commandLine = [command, ...commandArgs].join(" ");
350
354
  console.log(`release_check: ${commandLine}`);
351
355
  try {
352
- await execFileAsync(command, commandArgs, {
353
- cwd,
354
- timeout: timeoutMs,
355
- maxBuffer: 20 * 1024 * 1024
356
- });
356
+ const execute = command === "npm"
357
+ ? execNpm(commandArgs, {
358
+ cwd,
359
+ timeout: timeoutMs,
360
+ maxBuffer: 20 * 1024 * 1024
361
+ })
362
+ : execFileAsync(command === "node" ? process.execPath : command, commandArgs, {
363
+ cwd,
364
+ timeout: timeoutMs,
365
+ maxBuffer: 20 * 1024 * 1024
366
+ });
367
+ await execute;
357
368
  } catch (error) {
358
369
  // Surface the real failure: the one-line detail used to show the FIRST
359
370
  // stderr line, which npm noise ("npm warn ...") could occupy while the
@@ -376,10 +387,6 @@ function printCapturedOutputTail(error, commandLine) {
376
387
  }
377
388
  }
378
389
 
379
- function commandForPlatform(command) {
380
- return process.platform === "win32" && command === "npm" ? "npm.cmd" : command;
381
- }
382
-
383
390
  function parseArgs(values) {
384
391
  const parsed = {
385
392
  root: undefined,
@@ -5,10 +5,10 @@ import { tmpdir } from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { promisify } from "node:util";
8
+ import { execNpm } from "./npm-command.mjs";
8
9
 
9
10
  const execFileAsync = promisify(execFile);
10
11
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
- const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
12
12
 
13
13
  try {
14
14
  const args = parseArgs(process.argv.slice(2));
@@ -71,7 +71,7 @@ async function ensureDistBuiltForSource(root) {
71
71
  const tsconfigPath = path.join(root, "tsconfig.json");
72
72
  const srcDir = path.join(root, "src");
73
73
  if (!(await pathExists(tsconfigPath)) || !(await pathExists(srcDir))) return false;
74
- await execFileAsync(npmCommand, ["run", "build"], { cwd: root, maxBuffer: 20 * 1024 * 1024 });
74
+ await execNpm(["run", "build"], { cwd: root, maxBuffer: 20 * 1024 * 1024 });
75
75
  return true;
76
76
  }
77
77
 
@@ -211,7 +211,11 @@ async function run(command, commandArgs, cwd) {
211
211
 
212
212
  async function runNpmPack(commandArgs, cwd, label) {
213
213
  try {
214
- return await run(npmCommand, commandArgs, cwd);
214
+ return await execNpm(commandArgs, {
215
+ cwd,
216
+ timeout: 120_000,
217
+ maxBuffer: 20 * 1024 * 1024
218
+ });
215
219
  } catch (error) {
216
220
  throw new Error(`${label} failed: ${commandFailureDetail(error)}`);
217
221
  }
@@ -340,7 +344,12 @@ function errorMessage(error) {
340
344
  }
341
345
 
342
346
  function shellQuote(value) {
343
- return /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
347
+ const shellSafe = /^[A-Za-z0-9_./:@=-]+$/.test(value);
348
+ if (shellSafe && (process.platform !== "win32" || !value.startsWith("@"))) return value;
349
+ const escaped = process.platform === "win32"
350
+ ? value.replaceAll("'", "''")
351
+ : value.replaceAll("'", "'\\''");
352
+ return `'${escaped}'`;
344
353
  }
345
354
 
346
355
  async function readReleaseGitStatus(root) {