@askrjs/cli 0.0.7 → 0.0.9

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.
@@ -0,0 +1,95 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import Config from "@npmcli/config";
4
+ import npmDefinitions from "@npmcli/config/lib/definitions/index.js";
5
+ import registryFetch from "npm-registry-fetch";
6
+ //#region src/update/registry.ts
7
+ function executionEnvironment(env) {
8
+ if (env === process.env) return { ...process.env };
9
+ const inherited = { ...process.env };
10
+ for (const name of Object.keys(inherited)) if (/^npm_config_/i.test(name)) delete inherited[name];
11
+ return {
12
+ ...inherited,
13
+ ...env
14
+ };
15
+ }
16
+ async function loadNpmConfiguration(root, env = process.env) {
17
+ const effectiveEnvironment = executionEnvironment(env);
18
+ const { definitions, flatten, shorthands } = npmDefinitions;
19
+ const configuration = new Config({
20
+ npmPath: path.dirname(fileURLToPath(import.meta.resolve("@npmcli/config/package.json"))),
21
+ definitions,
22
+ flatten,
23
+ shorthands,
24
+ argv: [process.execPath, "askr"],
25
+ cwd: root,
26
+ env: effectiveEnvironment,
27
+ execPath: process.execPath,
28
+ warn: false
29
+ });
30
+ await configuration.load();
31
+ configuration.validate();
32
+ return {
33
+ cwd: root,
34
+ env: effectiveEnvironment,
35
+ options: { ...configuration.flat }
36
+ };
37
+ }
38
+ function isRecord(value) {
39
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
40
+ }
41
+ function isPackument(value) {
42
+ return isRecord(value) && isRecord(value["dist-tags"]) && isRecord(value.versions);
43
+ }
44
+ async function fetchPackage(packageName, configuration, _specifications) {
45
+ return registryFetch.json(packageName.replace("/", "%2f"), {
46
+ ...configuration.options,
47
+ spec: packageName,
48
+ headers: {
49
+ ...isRecord(configuration.options.headers) ? configuration.options.headers : {},
50
+ accept: "application/vnd.npm.install-v1+json"
51
+ }
52
+ });
53
+ }
54
+ function sanitizeRegistryError(error) {
55
+ const value = error && typeof error === "object" ? error : {};
56
+ if (value.statusCode === 404 || value.code === "E404") return "package was not found in the selected registry";
57
+ if (value.statusCode === 401 || value.statusCode === 403 || value.code === "E401" || value.code === "E403") return "registry authentication or authorization failed";
58
+ const code = typeof value.code === "string" ? value.code.toUpperCase() : "";
59
+ if ([
60
+ "ETIMEDOUT",
61
+ "ETIMEOUT",
62
+ "FETCH_ERROR",
63
+ "ECONNRESET"
64
+ ].includes(code)) return "registry request timed out or was interrupted";
65
+ if (/^[A-Z][A-Z0-9_]{1,30}$/.test(code)) return `registry request failed (${code})`;
66
+ return "registry request failed";
67
+ }
68
+ async function fetchPackuments(packageNames, configuration, options = {}) {
69
+ const names = [...new Set(packageNames)].sort((left, right) => left.localeCompare(right));
70
+ const packuments = /* @__PURE__ */ new Map();
71
+ const failures = /* @__PURE__ */ new Map();
72
+ const viewPackage = options.viewPackage ?? fetchPackage;
73
+ const configuredSockets = Number(configuration.options.maxSockets ?? 15);
74
+ const concurrency = Math.max(1, Math.min(names.length, Number.isFinite(configuredSockets) ? configuredSockets : 15));
75
+ let nextIndex = 0;
76
+ const worker = async () => {
77
+ while (nextIndex < names.length) {
78
+ const packageName = names[nextIndex++];
79
+ try {
80
+ const result = await viewPackage(packageName, configuration, options.requirements?.specifications?.get(packageName) ?? []);
81
+ if (isPackument(result)) packuments.set(packageName, result);
82
+ else failures.set(packageName, "registry returned malformed package metadata");
83
+ } catch (error) {
84
+ failures.set(packageName, sanitizeRegistryError(error));
85
+ }
86
+ }
87
+ };
88
+ await Promise.all(Array.from({ length: concurrency }, worker));
89
+ return {
90
+ packuments,
91
+ failures
92
+ };
93
+ }
94
+ //#endregion
95
+ export { fetchPackuments, loadNpmConfiguration };
@@ -34,7 +34,7 @@ Use this when the app renders outside the browser or produces static output. The
34
34
  ## Copy This Shape
35
35
 
36
36
  ```ts
37
- registerRoutes(() => {
37
+ const registry = createRouteRegistry(() => {
38
38
  page("/docs/{slug}", DocsPage, {
39
39
  entries: async () => [{ slug: "getting-started" }, { slug: "routing" }],
40
40
  });
@@ -303,7 +303,6 @@ const REVIEW_PROMPTS = [
303
303
  re("local Sidebar", String.raw`export\s+(?:default\s+)?function\s+Sidebar\b`)
304
304
  ]),
305
305
  requireAny("Keeps Askr-native route or state primitives in use.", [
306
- re("registerRoutes", String.raw`\bregisterRoutes\s*\(`),
307
306
  re("createRouteRegistry", String.raw`\bcreateRouteRegistry\s*\(`),
308
307
  re("state()", String.raw`\bstate\s*\(`),
309
308
  re("resource()", String.raw`\bresource\s*\(`)
package/dist/skills.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import { n as runSkillsCli, r as syncBundledSkills, t as installBundledSkills } from "./skills-B7CbWur9.js";
2
+ import { n as runSkillsCli, r as syncBundledSkills, t as installBundledSkills } from "./skills-C2KzfTl9.js";
3
3
  export { installBundledSkills, runSkillsCli, syncBundledSkills };
@@ -106,12 +106,11 @@ function rewriteXRange(shape, target) {
106
106
  if (count === 1) return String(parsed.major);
107
107
  return `${parsed.major}.${parsed.minor}`;
108
108
  }
109
- function rewriteAtomic(shape, target, forcedBreaking) {
109
+ function rewriteAtomic(shape, target, _forcedBreaking) {
110
110
  if (shape.kind === "exact") return target;
111
111
  if (shape.kind === "operator") return `${shape.operator}${target}`;
112
112
  if (shape.kind === "x") return rewriteXRange(shape, target);
113
- const boundary = nextBreakingBoundary(target);
114
- return forcedBreaking ? `>=${target} <${boundary}` : `${shape.lower} <${boundary}`;
113
+ return `>=${target} <${nextBreakingBoundary(target)}`;
115
114
  }
116
115
  function rewriteRange(shape, target, forcedBreaking) {
117
116
  if (shape.kind !== "union") return rewriteAtomic(shape, target, forcedBreaking);
@@ -122,4 +121,51 @@ function rewriteRange(shape, target, forcedBreaking) {
122
121
  return rewritten.join(" || ");
123
122
  }
124
123
  //#endregion
125
- export { isBreakingChange as n, rewriteRange as r, analyzeRange as t };
124
+ //#region src/update/specification.ts
125
+ const WINDOWS_PATH = /^[A-Za-z]:[\\/]/;
126
+ const HOSTED_GIT = /^(?:bitbucket|gist|github|gitlab):/i;
127
+ const GIT_URL = /^(?:git(?:\+[^:]+)?:|git@|ssh:)/i;
128
+ const GIT_SHORTHAND = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:#.*)?$/;
129
+ const REGISTRY_TAG = /^[A-Za-z][A-Za-z0-9._-]*$/;
130
+ const SCOPED_PATH = /^@[^/]+\/[^/]+$/;
131
+ function parseDependencySpecification(specification) {
132
+ const rawSpec = specification.trim();
133
+ if (!rawSpec) return {
134
+ type: "unsupported",
135
+ rawSpec
136
+ };
137
+ if (/^npm:/i.test(rawSpec)) return {
138
+ type: "alias",
139
+ rawSpec
140
+ };
141
+ if (/^(?:file|link|workspace):/i.test(rawSpec) || rawSpec.startsWith("./") || rawSpec.startsWith("../") || rawSpec.startsWith("/") || rawSpec.startsWith("~/") || /\.(?:tar\.gz|tgz)$/i.test(rawSpec) || WINDOWS_PATH.test(rawSpec) || /^[A-Za-z]:/.test(rawSpec) || SCOPED_PATH.test(rawSpec)) return {
142
+ type: "file",
143
+ rawSpec
144
+ };
145
+ if (HOSTED_GIT.test(rawSpec) || GIT_URL.test(rawSpec) || GIT_SHORTHAND.test(rawSpec)) return {
146
+ type: "git",
147
+ rawSpec
148
+ };
149
+ if (/^https?:/i.test(rawSpec) || /^[a-z][a-z0-9+.-]*:\/\//i.test(rawSpec)) return {
150
+ type: "remote",
151
+ rawSpec
152
+ };
153
+ if (semver.valid(rawSpec)) return {
154
+ type: "version",
155
+ rawSpec
156
+ };
157
+ if (semver.validRange(rawSpec)) return {
158
+ type: "range",
159
+ rawSpec
160
+ };
161
+ if (REGISTRY_TAG.test(rawSpec)) return {
162
+ type: "tag",
163
+ rawSpec
164
+ };
165
+ return {
166
+ type: "unsupported",
167
+ rawSpec
168
+ };
169
+ }
170
+ //#endregion
171
+ export { rewriteRange as i, analyzeRange as n, isBreakingChange as r, parseDependencySpecification as t };
package/dist/ssg.d.ts CHANGED
@@ -38,7 +38,6 @@ interface SsgDeps {
38
38
  existsSync?: typeof existsSync;
39
39
  importConfig?: (filePath: string) => Promise<unknown>;
40
40
  createStaticGen?: (options: {
41
- routes?: unknown[];
42
41
  registry?: unknown;
43
42
  outputDir: string;
44
43
  seed?: unknown;
package/dist/ssg.js CHANGED
@@ -458,9 +458,8 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
458
458
  }
459
459
  const configModule = imported;
460
460
  const candidate = configModule.default ?? configModule.staticConfig ?? configModule;
461
- const hasRoutes = Array.isArray(candidate.routes);
462
- if (hasRoutes === (candidate.registry !== void 0)) {
463
- io.error("Error: Config must provide exactly one route source: routes or registry");
461
+ if (!(candidate.registry !== void 0) || Object.prototype.hasOwnProperty.call(candidate, "routes")) {
462
+ io.error("Error: Config must provide a route registry and no raw routes array");
464
463
  return 1;
465
464
  }
466
465
  const config = candidate;
@@ -468,7 +467,7 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
468
467
  io.error("Error: Config must provide siteUrl to generate sitemap.xml, or set sitemap: false");
469
468
  return 1;
470
469
  }
471
- io.log(hasRoutes ? `Generating ${config.routes?.length ?? 0} routes...` : "Generating registered routes...");
470
+ io.log("Generating registered routes...");
472
471
  const createStaticGen = typeof resolvedDeps.createStaticGen === "function" ? resolvedDeps.createStaticGen : await loadCreateStaticGen();
473
472
  cliStagingDir = await createSiblingStage(resolvedOutputDir, "askr-ssg");
474
473
  if (parsed.incremental && !parsed.forceFull && await pathExists(resolvedOutputDir)) await fs$1.cp(resolvedOutputDir, cliStagingDir, {
@@ -477,7 +476,7 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
477
476
  });
478
477
  const generationOutputDir = cliStagingDir;
479
478
  const ssg = createStaticGen({
480
- ...hasRoutes ? { routes: config.routes } : { registry: config.registry },
479
+ registry: config.registry,
481
480
  outputDir: generationOutputDir,
482
481
  seed: config.seed,
483
482
  dataOverrides: config.dataOverrides,
@@ -7,7 +7,18 @@ import { telemetry } from "../telemetry";
7
7
  import { createActionHandlers } from "./action-registry";
8
8
  import type { AppDependencies } from "./dependencies";
9
9
 
10
+ function csrfSecret(): string {
11
+ const secret = process.env.CSRF_SECRET;
12
+ if (process.env.NODE_ENV !== "production") return secret ?? "development-only-secret";
13
+ if (!secret || secret.length < 32 || new Set(secret).size < 12) {
14
+ throw new Error("Production requires a strong CSRF_SECRET (at least 32 varied characters).");
15
+ }
16
+ return secret;
17
+ }
18
+
10
19
  export function createApp(deps: AppDependencies) {
20
+ const secret = csrfSecret();
21
+ const development = process.env.NODE_ENV !== "production";
11
22
  return createAskrApp({
12
23
  name: "{{appName}}",
13
24
  version: "1.0.0",
@@ -37,26 +48,33 @@ export function createApp(deps: AppDependencies) {
37
48
  .summary("Create a message")
38
49
  .tags("Messages")
39
50
  .use(
40
- csrf({ secret: process.env.CSRF_SECRET ?? "development-only-secret" }),
41
- rateLimit({ store: deps.rateLimits, limit: 30, windowMs: 60_000 }),
51
+ csrf({ secret }),
52
+ rateLimit({
53
+ store: deps.rateLimits,
54
+ limit: 30,
55
+ windowMs: 60_000,
56
+ key: (ctx) => `message:${ctx.auth.session?.id ?? "anonymous"}`,
57
+ }),
42
58
  )
43
59
  .created(Message)
44
60
  .badRequest()
45
61
  .unprocessableEntity()
46
62
  .tooManyRequests();
47
- api
48
- .post("/session", (ctx) => {
49
- const response = ctx.redirect("/", 303);
50
- return ctx.setCookie(response, "askr-session", "demo-session", {
51
- httpOnly: true,
52
- sameSite: "lax",
53
- secure: ctx.url.protocol === "https:",
54
- path: "/",
55
- });
56
- })
57
- .operationId("createSession")
58
- .summary("Create a demo session")
59
- .seeOther();
63
+ if (development) {
64
+ api
65
+ .post("/session", (ctx) => {
66
+ const response = ctx.redirect("/", 303);
67
+ return ctx.setCookie(response, "askr-session", "demo-session", {
68
+ httpOnly: true,
69
+ sameSite: "lax",
70
+ secure: ctx.url.protocol === "https:",
71
+ path: "/",
72
+ });
73
+ })
74
+ .operationId("createSession")
75
+ .summary("Create a development-only demo session")
76
+ .seeOther();
77
+ }
60
78
  },
61
79
  },
62
80
  auth: {
@@ -68,7 +86,7 @@ export function createApp(deps: AppDependencies) {
68
86
  },
69
87
  actions: {
70
88
  handlers: createActionHandlers(),
71
- csrf: { secret: process.env.CSRF_SECRET ?? "development-only-secret" },
89
+ csrf: { secret },
72
90
  },
73
91
  middleware: [requestId(), securityHeaders()],
74
92
  telemetry,
@@ -29,11 +29,16 @@ export function createDependencies(): AppDependencies {
29
29
  const counters = new Map<string, { count: number; reset: number }>();
30
30
  return {
31
31
  sessions: {
32
- get: async (id) => (id === "demo-session" ? { id, subject: "demo-user" } : null),
32
+ get: async (id) =>
33
+ process.env.NODE_ENV !== "production" && id === "demo-session"
34
+ ? { id, subject: "demo-user" }
35
+ : null,
33
36
  },
34
37
  principals: {
35
38
  get: async (subject) =>
36
- subject === "demo-user" ? { id: subject, subject, permissions: ["messages:create"] } : null,
39
+ process.env.NODE_ENV !== "production" && subject === "demo-user"
40
+ ? { id: subject, subject, permissions: ["messages:create"] }
41
+ : null,
37
42
  },
38
43
  actions: { record: async () => undefined },
39
44
  rateLimits: {
@@ -38,7 +38,7 @@ code lives in `src/adapters`.
38
38
 
39
39
  ```tsx
40
40
  // src/pages/_routes.tsx
41
- import { fallback, group, registerRoutes } from '@askrjs/askr/router';
41
+ import { createRouteRegistry, fallback, group } from '@askrjs/askr/router';
42
42
  import RootLayout from './_layout';
43
43
  import NotFoundPage from './not-found';
44
44
  import AuthLayout from './auth/_layout';
@@ -48,7 +48,7 @@ import { registerAppRoutes } from './app/_routes';
48
48
  import PublicLayout from './public/_layout';
49
49
  import { registerPublicRoutes } from './public/_routes';
50
50
 
51
- registerRoutes(() => {
51
+ export const pageRegistry = createRouteRegistry(() => {
52
52
  group({ layout: RootLayout }, () => {
53
53
  group({ layout: PublicLayout }, () => {
54
54
  registerPublicRoutes();
@@ -1,10 +1,9 @@
1
1
  import { createSPA } from '@askrjs/askr/boot';
2
- import { getManifest } from '@askrjs/askr/router';
2
+ import { pageRegistry } from './pages/_routes';
3
3
 
4
4
  import './styles.css';
5
- import './pages/_routes';
6
5
 
7
6
  await createSPA({
8
7
  root: document.getElementById('app')!,
9
- manifest: getManifest(),
8
+ registry: pageRegistry,
10
9
  });
@@ -1,4 +1,4 @@
1
- import { fallback, group, registerRoutes } from '@askrjs/askr/router';
1
+ import { createRouteRegistry, fallback, group } from '@askrjs/askr/router';
2
2
  import RootLayout from './_layout';
3
3
  import AuthLayout from './auth/_layout';
4
4
  import { registerAuthRoutes } from './auth/_routes';
@@ -8,7 +8,7 @@ import NotFoundPage from './not-found';
8
8
  import { registerPublicRoutes } from './public/_routes';
9
9
  import PublicLayout from './public/_layout';
10
10
 
11
- registerRoutes(() => {
11
+ export const pageRegistry = createRouteRegistry(() => {
12
12
  group({ layout: RootLayout }, () => {
13
13
  group({ layout: PublicLayout }, () => {
14
14
  registerPublicRoutes();
package/dist/update.js CHANGED
@@ -193,7 +193,7 @@ function collectEdits(decisions) {
193
193
  }] : []));
194
194
  }
195
195
  async function defaultRegistry(root, packageNames, requirements) {
196
- const { fetchPackuments, loadNpmConfiguration } = await import("./registry-D2APi6x0.js");
196
+ const { fetchPackuments, loadNpmConfiguration } = await import("./registry-BVliEbUv.js");
197
197
  return fetchPackuments(packageNames, await loadNpmConfiguration(root), { requirements });
198
198
  }
199
199
  async function runDependencyCli(command, args, io = console, runtime = {}) {
@@ -217,7 +217,7 @@ async function runDependencyCli(command, args, io = console, runtime = {}) {
217
217
  let root = null;
218
218
  let selectedWorkspaces = [];
219
219
  try {
220
- const [{ discoverProject }, { planUpdates }] = await Promise.all([import("./discovery-Djq8TxJu.js"), import("./planner-ZAX9SFjY.js")]);
220
+ const [{ discoverProject }, { planUpdates }] = await Promise.all([import("./discovery-BX-lnFRK.js"), import("./planner-BDfYDKnI.js")]);
221
221
  const project = await discoverProject({
222
222
  cwd: parsed.cwd,
223
223
  packagePatterns: parsed.packagePatterns,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askrjs/cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Unified CLI for the Askr platform",
5
5
  "homepage": "https://github.com/askrjs/askr-cli#readme",
6
6
  "bugs": {
@@ -44,18 +44,22 @@
44
44
  "test:publint": "publint",
45
45
  "pack:check": "npm pack --ignore-scripts --dry-run --json",
46
46
  "test:templates": "node scripts/verify-packed-templates.mjs",
47
+ "bench": "npm run build --silent && node --import tsx benchmarks/cli.mjs --gate",
48
+ "bench:json": "npm run build --silent && node --import tsx benchmarks/cli.mjs --gate --json",
47
49
  "check": "npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run test:publint && npm run pack:check",
48
50
  "prepack": "npm run build",
49
51
  "prepublishOnly": "npm run check && npm run test:templates"
50
52
  },
51
53
  "dependencies": {
54
+ "@npmcli/config": "^10.12.0",
52
55
  "js-yaml": "^5.2.1",
53
56
  "minimatch": "^10.2.5",
57
+ "npm-registry-fetch": "^19.1.1",
54
58
  "semver": "^7.8.5",
55
59
  "tsx": "^4.23.1"
56
60
  },
57
61
  "devDependencies": {
58
- "@askrjs/askr": ">=0.0.53 <0.1.0",
62
+ "@askrjs/askr": "^0.0.66",
59
63
  "@askrjs/charts": ">=0.1.0 <0.2.0",
60
64
  "@askrjs/logos": ">=0.0.3 <0.1.0",
61
65
  "@askrjs/lucide": ">=0.0.3 <0.1.0",
@@ -1,228 +0,0 @@
1
- import { t as parseDependencySpecification } from "./specification-DXnDOC-0.js";
2
- import path from "node:path";
3
- import { existsSync } from "node:fs";
4
- import { execFile } from "node:child_process";
5
- import semver from "semver";
6
- //#region src/update/registry.ts
7
- function installedNpmCli(executable) {
8
- const directories = /* @__PURE__ */ new Set([path.dirname(process.execPath)]);
9
- if (executable && path.isAbsolute(executable)) directories.add(path.dirname(executable));
10
- for (const directory of directories) for (const candidate of [path.join(directory, "node_modules", "npm", "bin", "npm-cli.js"), path.resolve(directory, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")]) if (existsSync(candidate)) return candidate;
11
- return null;
12
- }
13
- function npmInvocation(env) {
14
- const executable = env.npm_execpath;
15
- const executableName = executable ? path.basename(executable).toLowerCase() : "";
16
- const isNpmExecutable = executableName === "npm" || executableName === "npm.cmd" || /^npm(?:-cli)?\.(?:cjs|js|mjs)$/.test(executableName);
17
- if (executable && isNpmExecutable) {
18
- const extension = path.extname(executable).toLowerCase();
19
- if ([
20
- ".cjs",
21
- ".js",
22
- ".mjs"
23
- ].includes(extension)) return {
24
- executable: process.execPath,
25
- prefix: [executable]
26
- };
27
- const npmCli = installedNpmCli(executable);
28
- return npmCli ? {
29
- executable: process.execPath,
30
- prefix: [npmCli]
31
- } : {
32
- executable,
33
- prefix: []
34
- };
35
- }
36
- const npmCli = installedNpmCli();
37
- if (npmCli) return {
38
- executable: process.execPath,
39
- prefix: [npmCli]
40
- };
41
- return {
42
- executable: process.platform === "win32" ? "npm.cmd" : "npm",
43
- prefix: []
44
- };
45
- }
46
- function executionEnvironment(env) {
47
- if (env === process.env) return { ...process.env };
48
- const inherited = { ...process.env };
49
- for (const name of Object.keys(inherited)) if (/^npm_config_/i.test(name)) delete inherited[name];
50
- return {
51
- ...inherited,
52
- ...env
53
- };
54
- }
55
- async function loadNpmConfiguration(root, env = process.env) {
56
- const effectiveEnvironment = executionEnvironment(env);
57
- return {
58
- cwd: root,
59
- env: effectiveEnvironment,
60
- invocation: npmInvocation(effectiveEnvironment)
61
- };
62
- }
63
- function npmError(error, stderr) {
64
- const details = `${String(error.code ?? "")}\n${stderr}`;
65
- const safe = /* @__PURE__ */ new Error("npm registry lookup failed");
66
- safe.code = (details.match(/\bE(?:401|403|404|CONNRESET|TIMEDOUT|TIMEOUT)\b/i)?.[0])?.toUpperCase() ?? (typeof error.code === "string" ? error.code : "");
67
- if (safe.code === "E401") safe.statusCode = 401;
68
- if (safe.code === "E403") safe.statusCode = 403;
69
- if (safe.code === "E404") safe.statusCode = 404;
70
- return safe;
71
- }
72
- function executeNpmJson(args, configuration, preferOnline) {
73
- const [command, ...positionals] = args;
74
- return new Promise((resolve, reject) => {
75
- execFile(configuration.invocation.executable, [
76
- ...configuration.invocation.prefix,
77
- command,
78
- "--json",
79
- preferOnline ? "--prefer-online" : "--prefer-offline",
80
- "--offline=false",
81
- "--loglevel=error",
82
- "--update-notifier=false",
83
- "--",
84
- ...positionals
85
- ], {
86
- cwd: configuration.cwd,
87
- encoding: "utf8",
88
- env: configuration.env,
89
- maxBuffer: 64 * 1024 * 1024,
90
- windowsHide: true
91
- }, (error, stdout, stderr) => {
92
- if (error) {
93
- reject(npmError(error, stderr));
94
- return;
95
- }
96
- try {
97
- resolve(JSON.parse(stdout));
98
- } catch {
99
- reject(/* @__PURE__ */ new Error("npm registry returned malformed JSON"));
100
- }
101
- });
102
- });
103
- }
104
- function isRecord(value) {
105
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
106
- }
107
- function basePackument(value) {
108
- if (!isRecord(value) || !Array.isArray(value.versions) || !isRecord(value["dist-tags"])) return null;
109
- const versions = value.versions.filter((version) => typeof version === "string");
110
- if (versions.length === 0) return null;
111
- return {
112
- "dist-tags": value["dist-tags"],
113
- versions: Object.fromEntries(versions.map((version) => [version, { version }]))
114
- };
115
- }
116
- function selectedVersions(packument, specifications) {
117
- const versions = Object.keys(packument.versions ?? {}).filter((version) => semver.valid(version) !== null);
118
- const selected = new Set(Object.values(packument["dist-tags"] ?? {}).filter((version) => typeof version === "string" && semver.valid(version) !== null && versions.includes(version)));
119
- for (const specification of specifications) {
120
- const parsed = parseDependencySpecification(specification);
121
- if (parsed.type === "tag") {
122
- const tagged = packument["dist-tags"]?.[parsed.rawSpec];
123
- if (typeof tagged === "string" && versions.includes(tagged)) selected.add(tagged);
124
- } else if (parsed.type === "version") {
125
- if (versions.includes(parsed.rawSpec)) selected.add(parsed.rawSpec);
126
- } else if (parsed.type === "range") {
127
- const matching = semver.maxSatisfying(versions, parsed.rawSpec);
128
- if (matching) selected.add(matching);
129
- }
130
- }
131
- return [...selected].sort(semver.compare);
132
- }
133
- function candidateVersions(packument, specifications) {
134
- const versions = Object.keys(packument.versions ?? {}).filter((version) => semver.valid(version) !== null);
135
- const selected = selectedVersions(packument, specifications);
136
- const lowerBounds = specifications.flatMap((specification) => {
137
- const parsed = parseDependencySpecification(specification);
138
- if (parsed.type === "version") return semver.valid(parsed.rawSpec) ? [parsed.rawSpec] : [];
139
- if (parsed.type === "range") {
140
- const current = semver.maxSatisfying(versions, parsed.rawSpec);
141
- return current ? [current] : [];
142
- }
143
- return [];
144
- });
145
- if (lowerBounds.length === 0 || selected.length === 0) return selected;
146
- const floor = lowerBounds.sort(semver.compare)[0];
147
- const ceiling = selected.sort(semver.compare)[selected.length - 1];
148
- return versions.filter((version) => semver.gte(version, floor) && semver.lte(version, ceiling));
149
- }
150
- function mergeVersionMetadata(packument, value, selected) {
151
- const entries = Array.isArray(value) ? value : [value];
152
- const merged = /* @__PURE__ */ new Set();
153
- for (const entry of entries) {
154
- if (typeof entry === "string") {
155
- if (packument.versions?.[entry]) merged.add(entry);
156
- continue;
157
- }
158
- if (!isRecord(entry) || typeof entry.version !== "string") continue;
159
- const metadata = packument.versions?.[entry.version];
160
- if (!isRecord(metadata)) continue;
161
- if (isRecord(entry.peerDependencies)) metadata.peerDependencies = entry.peerDependencies;
162
- if (isRecord(entry.peerDependenciesMeta)) metadata.peerDependenciesMeta = entry.peerDependenciesMeta;
163
- merged.add(entry.version);
164
- }
165
- return selected.every((version) => merged.has(version));
166
- }
167
- async function executeNpmView(packageName, configuration, specifications) {
168
- const packument = basePackument(await executeNpmJson([
169
- "view",
170
- packageName,
171
- "versions",
172
- "dist-tags"
173
- ], configuration, true));
174
- if (!packument) return null;
175
- const versions = candidateVersions(packument, specifications);
176
- if (versions.length === 0) return packument;
177
- return mergeVersionMetadata(packument, await executeNpmJson([
178
- "view",
179
- `${packageName}@${versions.join(" || ")}`,
180
- "version",
181
- "peerDependencies",
182
- "peerDependenciesMeta"
183
- ], configuration, false), versions) ? packument : null;
184
- }
185
- function isPackument(value) {
186
- return isRecord(value) && isRecord(value["dist-tags"]) && isRecord(value.versions);
187
- }
188
- function sanitizeRegistryError(error) {
189
- const value = error && typeof error === "object" ? error : {};
190
- if (value.statusCode === 404 || value.code === "E404") return "package was not found in the selected registry";
191
- if (value.statusCode === 401 || value.statusCode === 403 || value.code === "E401" || value.code === "E403") return "registry authentication or authorization failed";
192
- const code = typeof value.code === "string" ? value.code.toUpperCase() : "";
193
- if ([
194
- "ETIMEDOUT",
195
- "ETIMEOUT",
196
- "FETCH_ERROR",
197
- "ECONNRESET"
198
- ].includes(code)) return "registry request timed out or was interrupted";
199
- if (/^[A-Z][A-Z0-9_]{1,30}$/.test(code)) return `registry request failed (${code})`;
200
- return "registry request failed";
201
- }
202
- async function fetchPackuments(packageNames, configuration, options = {}) {
203
- const names = [...new Set(packageNames)].sort((left, right) => left.localeCompare(right));
204
- const packuments = /* @__PURE__ */ new Map();
205
- const failures = /* @__PURE__ */ new Map();
206
- const viewPackage = options.viewPackage ?? executeNpmView;
207
- let nextIndex = 0;
208
- const worker = async () => {
209
- while (nextIndex < names.length) {
210
- const packageName = names[nextIndex];
211
- nextIndex += 1;
212
- try {
213
- const result = await viewPackage(packageName, configuration, options.requirements?.specifications?.get(packageName) ?? []);
214
- if (isPackument(result)) packuments.set(packageName, result);
215
- else failures.set(packageName, "registry returned malformed package metadata");
216
- } catch (error) {
217
- failures.set(packageName, sanitizeRegistryError(error));
218
- }
219
- }
220
- };
221
- await Promise.all(Array.from({ length: Math.min(8, names.length) }, worker));
222
- return {
223
- packuments,
224
- failures
225
- };
226
- }
227
- //#endregion
228
- export { fetchPackuments, loadNpmConfiguration };