checkly 8.21.0 → 8.22.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.
Files changed (56) hide show
  1. package/dist/ai-context/skills-command/references/configure-playwright-checks.md +2 -1
  2. package/dist/commands/debug/parse-project.js +2 -0
  3. package/dist/commands/debug/parse-project.js.map +1 -1
  4. package/dist/commands/deploy.js +2 -0
  5. package/dist/commands/deploy.js.map +1 -1
  6. package/dist/commands/pw-test.js +2 -0
  7. package/dist/commands/pw-test.js.map +1 -1
  8. package/dist/commands/test.js +2 -0
  9. package/dist/commands/test.js.map +1 -1
  10. package/dist/commands/validate.js +1 -0
  11. package/dist/commands/validate.js.map +1 -1
  12. package/dist/constructs/project.d.ts +1 -0
  13. package/dist/constructs/project.js +101 -2
  14. package/dist/constructs/project.js.map +1 -1
  15. package/dist/constructs/session.d.ts +9 -0
  16. package/dist/constructs/session.js +25 -0
  17. package/dist/constructs/session.js.map +1 -1
  18. package/dist/rest/errors.d.ts +7 -0
  19. package/dist/rest/errors.js +13 -0
  20. package/dist/rest/errors.js.map +1 -1
  21. package/dist/services/check-parser/bundler.d.ts +30 -1
  22. package/dist/services/check-parser/bundler.js +98 -10
  23. package/dist/services/check-parser/bundler.js.map +1 -1
  24. package/dist/services/check-parser/cache-hash.d.ts +36 -3
  25. package/dist/services/check-parser/cache-hash.js +30 -17
  26. package/dist/services/check-parser/cache-hash.js.map +1 -1
  27. package/dist/services/checkly-config-loader.d.ts +34 -1
  28. package/dist/services/checkly-config-loader.js +37 -0
  29. package/dist/services/checkly-config-loader.js.map +1 -1
  30. package/dist/services/config.js +3 -1
  31. package/dist/services/config.js.map +1 -1
  32. package/dist/services/embedded-packages/cache.d.ts +48 -0
  33. package/dist/services/embedded-packages/cache.js +171 -0
  34. package/dist/services/embedded-packages/cache.js.map +1 -0
  35. package/dist/services/embedded-packages/integrity.d.ts +28 -0
  36. package/dist/services/embedded-packages/integrity.js +58 -0
  37. package/dist/services/embedded-packages/integrity.js.map +1 -0
  38. package/dist/services/embedded-packages/lockfile-packages.d.ts +48 -0
  39. package/dist/services/embedded-packages/lockfile-packages.js +227 -0
  40. package/dist/services/embedded-packages/lockfile-packages.js.map +1 -0
  41. package/dist/services/embedded-packages/materializer.d.ts +88 -0
  42. package/dist/services/embedded-packages/materializer.js +314 -0
  43. package/dist/services/embedded-packages/materializer.js.map +1 -0
  44. package/dist/services/embedded-packages/npmrc.d.ts +56 -0
  45. package/dist/services/embedded-packages/npmrc.js +174 -0
  46. package/dist/services/embedded-packages/npmrc.js.map +1 -0
  47. package/dist/services/embedded-packages/spec.d.ts +47 -0
  48. package/dist/services/embedded-packages/spec.js +79 -0
  49. package/dist/services/embedded-packages/spec.js.map +1 -0
  50. package/dist/services/playwright-project-bundler.js +17 -0
  51. package/dist/services/playwright-project-bundler.js.map +1 -1
  52. package/dist/services/project-parser.d.ts +1 -0
  53. package/dist/services/project-parser.js +5 -1
  54. package/dist/services/project-parser.js.map +1 -1
  55. package/oclif.manifest.json +156 -156
  56. package/package.json +7 -7
@@ -0,0 +1,227 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import JSON5 from 'json5';
5
+ import semver from 'semver';
6
+ export class UnsupportedLockfileError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'UnsupportedLockfileError';
10
+ }
11
+ }
12
+ /**
13
+ * Enumerates every package entry in a lockfile, classified into embeddable
14
+ * registry packages and excluded (git/file/link/integrity-less) entries.
15
+ * Supports `pnpm-lock.yaml` (v6/v9) and `package-lock.json` (v2/v3).
16
+ */
17
+ export async function loadLockfilePackages(lockfilePath) {
18
+ const basename = path.basename(lockfilePath);
19
+ const content = await fs.readFile(lockfilePath, 'utf8');
20
+ switch (basename) {
21
+ case 'pnpm-lock.yaml':
22
+ return parsePnpmLockfilePackages(content);
23
+ case 'package-lock.json':
24
+ return parseNpmLockfilePackages(content);
25
+ default:
26
+ throw new UnsupportedLockfileError(`Embedded packages are not supported for '${basename}' lockfiles yet.`
27
+ + ` Only pnpm (pnpm-lock.yaml) and npm (package-lock.json) are currently supported.`);
28
+ }
29
+ }
30
+ /**
31
+ * Strips a pnpm peer-dependency suffix (`(react@18.2.0)`) from a package
32
+ * key. The v9 `packages` section doesn't use them (they live in
33
+ * `snapshots`), but v6 keys do.
34
+ */
35
+ function stripPeerSuffix(key) {
36
+ const cut = key.indexOf('(');
37
+ return cut === -1 ? key : key.slice(0, cut);
38
+ }
39
+ export function parsePnpmLockfilePackages(content) {
40
+ const data = parseYaml(content);
41
+ // The version can arrive as a number: pnpm writes `lockfileVersion: '9.0'`
42
+ // quoted, but a YAML re-serializer (merge tooling, formatters) may drop
43
+ // the quotes, turning it into the number 9.
44
+ const lockfileVersion = String(data?.lockfileVersion ?? '');
45
+ const lockfileMajor = Number.parseInt(lockfileVersion, 10);
46
+ if (lockfileMajor !== 6 && lockfileMajor !== 9) {
47
+ throw new UnsupportedLockfileError(`Embedded packages require pnpm lockfile version 6 or 9`
48
+ + ` (found '${lockfileVersion || 'unknown'}'). Regenerate the lockfile with a supported`
49
+ + ` pnpm version, or update the Checkly CLI if the lockfile is newer.`);
50
+ }
51
+ const result = { registry: [], excluded: [] };
52
+ // Workspace-linked packages never appear in the `packages` section — only
53
+ // as `link:` dependencies under `importers`. Record them so a user listing
54
+ // their own workspace package gets a precise "cannot be embedded" error
55
+ // instead of a "not found, check the spelling" one.
56
+ const importers = data?.importers;
57
+ if (typeof importers === 'object' && importers !== null) {
58
+ const linkedNames = new Set();
59
+ for (const importer of Object.values(importers)) {
60
+ for (const group of ['dependencies', 'devDependencies', 'optionalDependencies']) {
61
+ for (const [name, dep] of Object.entries(importer?.[group] ?? {})) {
62
+ const version = typeof dep === 'string' ? dep : dep?.version;
63
+ if (typeof version === 'string' && version.startsWith('link:') && !linkedNames.has(name)) {
64
+ linkedNames.add(name);
65
+ // Same distinction as npm's `link: true` entries: a link whose
66
+ // target escapes the workspace is not part of the project the
67
+ // bundle carries.
68
+ const target = version.slice('link:'.length);
69
+ const escapesWorkspace = target === '..' || target.startsWith('../') || path.isAbsolute(target);
70
+ result.excluded.push({
71
+ name,
72
+ reason: escapesWorkspace
73
+ ? `'${name}' is a local directory link outside the workspace, which cannot be embedded`
74
+ + ` as a registry tarball`
75
+ : `'${name}' is a workspace package, which cannot be embedded as a registry tarball`,
76
+ kind: escapesWorkspace ? 'unfetchable' : 'workspace',
77
+ });
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ const packages = data?.packages;
84
+ if (typeof packages !== 'object' || packages === null) {
85
+ return result;
86
+ }
87
+ const seen = new Set();
88
+ for (const [rawKey, rawEntry] of Object.entries(packages)) {
89
+ // v6 keys have a leading slash (`/name@1.2.3`), v9 keys do not.
90
+ const key = stripPeerSuffix(rawKey.startsWith('/') ? rawKey.slice(1) : rawKey);
91
+ // The name/ref separator is the first `@` past the name. Searching from
92
+ // the front (after the scope, when present) keeps the name intact when
93
+ // the ref itself contains `@`, as git refs do
94
+ // (`foo@git+ssh://git@github.com/...`).
95
+ const searchFrom = key.startsWith('@') ? key.indexOf('/') + 1 : 1;
96
+ const separator = searchFrom > 0 ? key.indexOf('@', searchFrom) : -1;
97
+ if (separator <= 0) {
98
+ continue;
99
+ }
100
+ const name = key.slice(0, separator);
101
+ const ref = key.slice(separator + 1);
102
+ if (seen.has(`${name}@${ref}`)) {
103
+ continue;
104
+ }
105
+ seen.add(`${name}@${ref}`);
106
+ // Validate with semver but keep the ref as written: semver.valid()
107
+ // normalizes away build metadata (`1.0.0+sha.abc` → `1.0.0`), which
108
+ // would break both version-pin matching and the derived tarball URL.
109
+ const version = semver.valid(ref) !== null ? ref : null;
110
+ if (version === null) {
111
+ result.excluded.push({
112
+ name,
113
+ reason: `'${name}@${ref}' resolves to a git, file or URL dependency,`
114
+ + ` which cannot be embedded as a registry tarball`,
115
+ kind: 'unfetchable',
116
+ });
117
+ continue;
118
+ }
119
+ const resolution = rawEntry?.resolution;
120
+ const integrity = resolution?.integrity;
121
+ if (typeof integrity !== 'string' || integrity === '') {
122
+ result.excluded.push({
123
+ name,
124
+ version,
125
+ reason: `the lockfile records no integrity hash for '${name}@${version}',`
126
+ + ` which is required to embed it`,
127
+ kind: 'unfetchable',
128
+ });
129
+ continue;
130
+ }
131
+ const tarball = resolution?.tarball;
132
+ result.registry.push({
133
+ name,
134
+ version,
135
+ integrity,
136
+ // Only absolute http(s) URLs are usable for downloading; anything
137
+ // else falls back to the registry-derived URL.
138
+ tarballUrl: typeof tarball === 'string' && /^https?:/.test(tarball) ? tarball : undefined,
139
+ });
140
+ }
141
+ return result;
142
+ }
143
+ export function parseNpmLockfilePackages(content) {
144
+ const data = JSON5.parse(content);
145
+ const lockfileVersion = data?.lockfileVersion;
146
+ if (lockfileVersion !== 2 && lockfileVersion !== 3) {
147
+ throw new UnsupportedLockfileError(`Embedded packages require npm lockfile version 2 or 3`
148
+ + ` (found '${lockfileVersion ?? 'unknown'}'). Update npm and regenerate the lockfile.`);
149
+ }
150
+ const packages = data?.packages;
151
+ const result = { registry: [], excluded: [] };
152
+ if (typeof packages !== 'object' || packages === null) {
153
+ return result;
154
+ }
155
+ const seen = new Set();
156
+ for (const [key, entry] of Object.entries(packages)) {
157
+ const lastNodeModules = key.lastIndexOf('node_modules/');
158
+ if (lastNodeModules === -1) {
159
+ // The workspace root ('') and workspace member paths are not
160
+ // installable registry artifacts.
161
+ continue;
162
+ }
163
+ // Aliased installs record the real package name in the entry; the key
164
+ // segment is the alias.
165
+ const name = typeof entry?.name === 'string'
166
+ ? entry.name
167
+ : key.slice(lastNodeModules + 'node_modules/'.length);
168
+ if (entry?.link === true) {
169
+ // `link: true` covers both workspace members and `file:` directory
170
+ // dependencies. A link whose target escapes the workspace is not
171
+ // part of the project the bundle carries, so a wildcard must not
172
+ // skip it silently.
173
+ const target = typeof entry?.resolved === 'string' ? entry.resolved : '';
174
+ const escapesWorkspace = target === '..' || target.startsWith('../') || path.isAbsolute(target);
175
+ result.excluded.push({
176
+ name: key.slice(lastNodeModules + 'node_modules/'.length),
177
+ reason: escapesWorkspace
178
+ ? `'${key}' is a local directory link outside the workspace, which cannot be embedded`
179
+ + ` as a registry tarball`
180
+ : `'${key}' is a workspace link, which cannot be embedded as a registry tarball`,
181
+ kind: escapesWorkspace ? 'unfetchable' : 'workspace',
182
+ });
183
+ continue;
184
+ }
185
+ // As above: validate with semver but keep the version as recorded.
186
+ const version = typeof entry?.version === 'string' && semver.valid(entry.version) !== null
187
+ ? entry.version
188
+ : null;
189
+ const resolved = typeof entry?.resolved === 'string' ? entry.resolved : undefined;
190
+ if (version === null || (resolved !== undefined && !/^https?:/.test(resolved))) {
191
+ result.excluded.push({
192
+ name,
193
+ version: version ?? undefined,
194
+ reason: `'${key}' resolves to a git, file or URL dependency,`
195
+ + ` which cannot be embedded as a registry tarball`,
196
+ kind: 'unfetchable',
197
+ });
198
+ continue;
199
+ }
200
+ if (seen.has(`${name}@${version}`)) {
201
+ continue;
202
+ }
203
+ const integrity = entry?.integrity;
204
+ if (typeof integrity !== 'string' || integrity === '') {
205
+ // Deliberately not marked as seen: an integrity-less copy (typically
206
+ // a nested bundled dependency) must not shadow a proper registry
207
+ // entry of the same name@version appearing later in the map.
208
+ result.excluded.push({
209
+ name,
210
+ version,
211
+ reason: `the lockfile records no integrity hash for '${name}@${version}'`
212
+ + ` (typically a bundled dependency), which is required to embed it`,
213
+ kind: 'unfetchable',
214
+ });
215
+ continue;
216
+ }
217
+ seen.add(`${name}@${version}`);
218
+ result.registry.push({
219
+ name,
220
+ version,
221
+ integrity,
222
+ tarballUrl: resolved,
223
+ });
224
+ }
225
+ return result;
226
+ }
227
+ //# sourceMappingURL=lockfile-packages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lockfile-packages.js","sourceRoot":"","sources":["../../../src/services/embedded-packages/lockfile-packages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { LockfileRegistryPackage } from './lockfile-packages.js';
2
+ /**
3
+ * The directory inside the code bundle where embedded package tarballs
4
+ * live. This path is a contract with Checkly runners: tarballs found there
5
+ * are served through a local registry during the bundle's install step.
6
+ */
7
+ export declare const EMBEDDED_PACKAGES_ARCHIVE_DIR = ".checkly/embedded-packages";
8
+ export interface EmbeddedPackagesIssue {
9
+ type: 'invalid-spec' | 'missing-lockfile' | 'unsupported-lockfile' | 'spec-not-found' | 'spec-version-not-found' | 'spec-not-embeddable';
10
+ /** The offending `bundle.packages.embed` entry, when tied to one. */
11
+ spec?: string;
12
+ /** Standalone sentence describing the issue, usable on its own. */
13
+ message: string;
14
+ /**
15
+ * Entry-scoped detail for grouped diagnostics, phrased to follow the
16
+ * entry under a per-type heading — e.g. the versions the lockfile does
17
+ * have, or the reasons the matches cannot be embedded.
18
+ */
19
+ detail?: string;
20
+ }
21
+ /**
22
+ * One tarball selected for embedding, resolved from the lockfile.
23
+ */
24
+ export interface PlannedTarball extends LockfileRegistryPackage {
25
+ /** Archive filename, e.g. `@acme+foo@1.2.3.tgz` (scope slash → `+`). */
26
+ archiveFilename: string;
27
+ }
28
+ export interface EmbeddedPackagesPlan {
29
+ tarballs: PlannedTarball[];
30
+ issues: EmbeddedPackagesIssue[];
31
+ /**
32
+ * Non-fatal problems worth surfacing (e.g. a spec also matching
33
+ * dependencies that cannot be embedded). Reported through the
34
+ * diagnostics channel during project validation.
35
+ */
36
+ warnings: string[];
37
+ /**
38
+ * The lockfile the specs were resolved against, for diagnostics that
39
+ * name it once instead of once per issue. Absent when resolution never
40
+ * happened — no lockfile found, or an unsupported/unparsable one (the
41
+ * corresponding lockfile issue explains it).
42
+ */
43
+ lockfilePath?: string;
44
+ }
45
+ /**
46
+ * A planned tarball that has been sourced into the CLI cache and is ready
47
+ * to be added to the code bundle.
48
+ */
49
+ export interface MaterializedTarball extends PlannedTarball {
50
+ /** Absolute path of the verified tarball in the CLI cache. */
51
+ filePath: string;
52
+ /** Bundle-root-relative archive path (POSIX). */
53
+ archivePath: string;
54
+ }
55
+ export declare class EmbeddedPackageError extends Error {
56
+ constructor(message: string, options?: ErrorOptions);
57
+ }
58
+ export interface EmbeddedPackagesMaterializerOptions {
59
+ /** Raw `bundle.packages.embed` entries. */
60
+ specs: string[];
61
+ /** Absolute path of the workspace root lockfile, when one exists. */
62
+ lockfilePath?: string;
63
+ /** Workspace root directory, used to locate the root `.npmrc`. */
64
+ workspaceRoot?: string;
65
+ /**
66
+ * The directory the Checkly project lives in (a workspace member in a
67
+ * monorepo), whose `.npmrc` takes precedence over the workspace root's.
68
+ */
69
+ contextDir?: string;
70
+ env?: NodeJS.ProcessEnv;
71
+ homedir?: string;
72
+ }
73
+ /**
74
+ * Resolves the configured `bundle.packages.embed` specs against the
75
+ * workspace lockfile (plan) and sources the selected tarballs into the CLI
76
+ * cache (materialize), through a chain of CLI cache → npm cacache →
77
+ * registry download, always verified against the lockfile integrity.
78
+ *
79
+ * Both stages memoize their in-flight promise: multiple Playwright checks
80
+ * bundle concurrently, and validation and bundling share one instance per
81
+ * parsed project, so the work runs exactly once.
82
+ */
83
+ export declare class EmbeddedPackagesMaterializer {
84
+ #private;
85
+ constructor(options: EmbeddedPackagesMaterializerOptions);
86
+ plan(): Promise<EmbeddedPackagesPlan>;
87
+ materialize(): Promise<MaterializedTarball[]>;
88
+ }
@@ -0,0 +1,314 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import axios from 'axios';
5
+ import Debug from 'debug';
6
+ import PQueue from 'p-queue';
7
+ import { assignProxy } from '../proxy.js';
8
+ import { TarballCache, lookupNpmCacache } from './cache.js';
9
+ import { verifyIntegrity } from './integrity.js';
10
+ import { UnsupportedLockfileError, loadLockfilePackages, } from './lockfile-packages.js';
11
+ import { defaultNpmrcPaths, loadNpmrcConfig, resolveAuthHeader, resolveRegistryUrl } from './npmrc.js';
12
+ import { InvalidEmbeddedPackageSpecError, parseEmbeddedPackageSpec, specMatchesPackageName, } from './spec.js';
13
+ const debug = Debug('checkly:cli:services:embedded-packages');
14
+ /**
15
+ * The directory inside the code bundle where embedded package tarballs
16
+ * live. This path is a contract with Checkly runners: tarballs found there
17
+ * are served through a local registry during the bundle's install step.
18
+ */
19
+ export const EMBEDDED_PACKAGES_ARCHIVE_DIR = '.checkly/embedded-packages';
20
+ export class EmbeddedPackageError extends Error {
21
+ constructor(message, options) {
22
+ super(message, options);
23
+ this.name = 'EmbeddedPackageError';
24
+ }
25
+ }
26
+ const DOWNLOAD_CONCURRENCY = 5;
27
+ const DOWNLOAD_TIMEOUT_MS = 120_000;
28
+ const MAX_TARBALL_BYTES = 1024 * 1024 * 1024;
29
+ /**
30
+ * Joins up to 8 items, appending `<overflow>N more` for the rest — the
31
+ * uniform truncation for user-facing lists of packages, versions and
32
+ * reasons.
33
+ */
34
+ function capList(items, separator, overflow) {
35
+ const shown = items.slice(0, 8).join(separator);
36
+ return items.length > 8 ? `${shown}${overflow}${items.length - 8} more` : shown;
37
+ }
38
+ /**
39
+ * Removes userinfo credentials from a URL so it can be safely included in
40
+ * error messages and logs (a registry URL may embed a token).
41
+ */
42
+ function redactUrl(url) {
43
+ try {
44
+ const parsed = new URL(url);
45
+ parsed.username = '';
46
+ parsed.password = '';
47
+ return parsed.toString();
48
+ }
49
+ catch {
50
+ // Not parseable as a URL (e.g. a scheme-less registry entry) — strip
51
+ // anything that looks like a userinfo segment before displaying it.
52
+ return url.replace(/(^|\/\/)[^/@\s]+@/, '$1');
53
+ }
54
+ }
55
+ /**
56
+ * Resolves the configured `bundle.packages.embed` specs against the
57
+ * workspace lockfile (plan) and sources the selected tarballs into the CLI
58
+ * cache (materialize), through a chain of CLI cache → npm cacache →
59
+ * registry download, always verified against the lockfile integrity.
60
+ *
61
+ * Both stages memoize their in-flight promise: multiple Playwright checks
62
+ * bundle concurrently, and validation and bundling share one instance per
63
+ * parsed project, so the work runs exactly once.
64
+ */
65
+ export class EmbeddedPackagesMaterializer {
66
+ #options;
67
+ #cache;
68
+ #env;
69
+ #homedir;
70
+ #plan;
71
+ #materialized;
72
+ constructor(options) {
73
+ this.#options = options;
74
+ this.#env = options.env ?? process.env;
75
+ this.#homedir = options.homedir ?? os.homedir();
76
+ this.#cache = TarballCache.default(this.#env, this.#projectRoot, process.platform, this.#homedir);
77
+ }
78
+ get #projectRoot() {
79
+ const { workspaceRoot, lockfilePath } = this.#options;
80
+ return workspaceRoot ?? (lockfilePath !== undefined ? path.dirname(lockfilePath) : undefined);
81
+ }
82
+ plan() {
83
+ this.#plan ??= this.#createPlan();
84
+ return this.#plan;
85
+ }
86
+ materialize() {
87
+ this.#materialized ??= this.#materializeAll();
88
+ return this.#materialized;
89
+ }
90
+ async #createPlan() {
91
+ const issues = [];
92
+ const warnings = [];
93
+ const specs = [];
94
+ for (const raw of this.#options.specs) {
95
+ try {
96
+ specs.push(parseEmbeddedPackageSpec(raw));
97
+ }
98
+ catch (err) {
99
+ issues.push({
100
+ type: 'invalid-spec',
101
+ spec: String(raw),
102
+ message: err.message,
103
+ detail: err instanceof InvalidEmbeddedPackageSpecError ? err.reason : err.message,
104
+ });
105
+ }
106
+ }
107
+ const { lockfilePath } = this.#options;
108
+ if (lockfilePath === undefined) {
109
+ issues.push({
110
+ type: 'missing-lockfile',
111
+ message: `Embedded packages require a lockfile to resolve package versions and`
112
+ + ` integrity hashes, but no lockfile was found for the project.`,
113
+ });
114
+ return { tarballs: [], issues, warnings };
115
+ }
116
+ let packages;
117
+ try {
118
+ packages = await loadLockfilePackages(lockfilePath);
119
+ }
120
+ catch (err) {
121
+ // Any failure to read or parse the lockfile (missing file, merge
122
+ // conflict markers, unknown format) becomes a diagnostic naming the
123
+ // lockfile instead of an unhandled exception aborting the command.
124
+ const message = err instanceof UnsupportedLockfileError
125
+ ? err.message
126
+ : `Failed to read or parse the lockfile ('${lockfilePath}'): ${err.message}`;
127
+ // No lockfilePath in the result: the specs were never resolved
128
+ // against the lockfile, so diagnostics must not credit it.
129
+ issues.push({ type: 'unsupported-lockfile', message });
130
+ return { tarballs: [], issues, warnings };
131
+ }
132
+ debug('lockfile %s: %d registry entries, %d excluded entries', lockfilePath, packages.registry.length, packages.excluded.length);
133
+ // Excluded entries that share a name@version with a proper registry
134
+ // entry are shadowed duplicates (npm nests integrity-less bundled
135
+ // copies): the artifact IS embeddable through its registry entry, so
136
+ // they must not trigger not-embeddable errors or skip warnings.
137
+ const registryKeys = new Set(packages.registry.map(entry => `${entry.name}@${entry.version}`));
138
+ const relevantExcluded = packages.excluded.filter(entry => entry.version === undefined || !registryKeys.has(`${entry.name}@${entry.version}`));
139
+ const tarballs = new Map();
140
+ for (const spec of specs) {
141
+ const nameMatches = packages.registry.filter(entry => specMatchesPackageName(spec, entry.name));
142
+ const candidates = nameMatches
143
+ .filter(entry => spec.version === undefined || entry.version === spec.version);
144
+ const nameExcluded = relevantExcluded.filter(entry => specMatchesPackageName(spec, entry.name));
145
+ const looseExcluded = nameExcluded.filter(entry => spec.version === undefined || entry.version === undefined || entry.version === spec.version);
146
+ if (candidates.length === 0) {
147
+ // Excluded entries matching the exact pin (or any entry, when
148
+ // unpinned) carry the most actionable reason and win; a version
149
+ // pin that filtered out real registry matches is blamed next.
150
+ // Version-less excluded entries (e.g. workspace links) are a last
151
+ // resort, so a pinned spec is never blamed on one while a better
152
+ // explanation exists.
153
+ const strictExcluded = nameExcluded.filter(entry => spec.version === undefined || entry.version === spec.version);
154
+ const excludedMatches = strictExcluded.length > 0
155
+ ? strictExcluded
156
+ : nameMatches.length === 0 ? looseExcluded : [];
157
+ if (excludedMatches.length > 0) {
158
+ const reasons = capList([...new Set(excludedMatches.map(entry => entry.reason))], '; ', '; and ');
159
+ issues.push({
160
+ type: 'spec-not-embeddable',
161
+ spec: spec.raw,
162
+ message: `Embedded package '${spec.raw}' cannot be embedded: ${reasons}.`,
163
+ detail: reasons,
164
+ });
165
+ }
166
+ else if (nameMatches.length > 0) {
167
+ const versions = capList([...new Set(nameMatches.map(entry => entry.version))], ', ', ' and ');
168
+ issues.push({
169
+ type: 'spec-version-not-found',
170
+ spec: spec.raw,
171
+ message: `Embedded package '${spec.raw}' matches package name(s) in the lockfile`
172
+ + ` ('${lockfilePath}'), but none of them at version ${spec.version}`
173
+ + ` (lockfile has: ${versions}).`,
174
+ detail: `lockfile has: ${versions}`,
175
+ });
176
+ }
177
+ else {
178
+ const hint = spec.namePattern !== undefined
179
+ ? `pattern matches its name${spec.version !== undefined ? ' and the version is spelled correctly' : ''}`
180
+ : `name ${spec.version !== undefined ? 'and version are' : 'is'} spelled correctly`;
181
+ issues.push({
182
+ type: 'spec-not-found',
183
+ spec: spec.raw,
184
+ message: `Embedded package '${spec.raw}' does not match any package in the lockfile`
185
+ + ` ('${lockfilePath}'). Make sure the package is installed and the ${hint}.`,
186
+ });
187
+ }
188
+ continue;
189
+ }
190
+ // When the spec also reaches entries it cannot embed, that is not
191
+ // the hard error a fully-unresolvable spec gets. Workspace members
192
+ // (part of the project itself) are skipped silently; git/file/URL
193
+ // and integrity-less matches cannot be embedded but may still be
194
+ // needed at install time, so skipping them is said out loud.
195
+ const workspace = looseExcluded.filter(entry => entry.kind === 'workspace');
196
+ if (workspace.length > 0) {
197
+ debug('spec %s: %d workspace matches skipped: %j', spec.raw, workspace.length, workspace.map(entry => entry.name));
198
+ }
199
+ const unfetchable = looseExcluded.filter(entry => entry.kind === 'unfetchable');
200
+ if (unfetchable.length > 0) {
201
+ const names = [...new Set(unfetchable.map(entry => entry.name))];
202
+ warnings.push(`Embedded package '${spec.raw}' also matches ${names.length} package(s) that cannot`
203
+ + ` be embedded as registry tarballs and were skipped: ${capList(names, ', ', ' and ')}.`
204
+ + ` The runner must be able to fetch these itself.`);
205
+ }
206
+ if (spec.namePattern !== undefined) {
207
+ // Wildcards select invisibly, but only the debug log says what they
208
+ // selected. Selections that need attention surface louder: a
209
+ // pattern matching nothing is a fatal validation issue, and matches
210
+ // that cannot be embedded produce a warning diagnostic.
211
+ debug('pattern %s matched %d package(s): %j', spec.raw, candidates.length, candidates.map(entry => `${entry.name}@${entry.version}`));
212
+ }
213
+ for (const entry of candidates) {
214
+ tarballs.set(`${entry.name}@${entry.version}`, {
215
+ ...entry,
216
+ archiveFilename: `${entry.name.replace(/\//g, '+')}@${entry.version}.tgz`,
217
+ });
218
+ }
219
+ }
220
+ debug('plan: %d tarballs, %d issues, %d warnings', tarballs.size, issues.length, warnings.length);
221
+ return {
222
+ tarballs: [...tarballs.values()].sort((a, b) => a.archiveFilename.localeCompare(b.archiveFilename)),
223
+ issues,
224
+ warnings,
225
+ lockfilePath,
226
+ };
227
+ }
228
+ async #materializeAll() {
229
+ const { tarballs, issues } = await this.plan();
230
+ // Commands validate before bundling and exit on fatal diagnostics, so
231
+ // this is a defensive backstop for direct/programmatic use.
232
+ if (issues.length > 0) {
233
+ throw new EmbeddedPackageError(`Cannot embed packages due to configuration issues:\n\n`
234
+ + issues.map(issue => ` ${issue.message}`).join('\n'));
235
+ }
236
+ if (tarballs.length === 0) {
237
+ return [];
238
+ }
239
+ // Safe to assert: a missing lockfile is a plan issue, and issues abort
240
+ // above.
241
+ const npmrcConfig = await loadNpmrcConfig(defaultNpmrcPaths(this.#projectRoot, this.#homedir, this.#options.contextDir), this.#env);
242
+ const queue = new PQueue({ concurrency: DOWNLOAD_CONCURRENCY });
243
+ const results = await queue.addAll(tarballs.map(tarball => async () => {
244
+ const filePath = await this.#obtainTarball(tarball, npmrcConfig);
245
+ return {
246
+ ...tarball,
247
+ filePath,
248
+ archivePath: `${EMBEDDED_PACKAGES_ARCHIVE_DIR}/${tarball.archiveFilename}`,
249
+ };
250
+ }));
251
+ return results;
252
+ }
253
+ async #obtainTarball(tarball, npmrcConfig) {
254
+ const cached = await this.#cache.get(tarball.integrity);
255
+ if (cached !== undefined) {
256
+ debug('%s@%s: CLI cache hit', tarball.name, tarball.version);
257
+ return cached;
258
+ }
259
+ const fromNpmCacache = await lookupNpmCacache(tarball.integrity, this.#env, process.platform, this.#homedir);
260
+ if (fromNpmCacache !== undefined) {
261
+ debug('%s@%s: npm cache hit', tarball.name, tarball.version);
262
+ return await this.#cache.put(tarball.integrity, fromNpmCacache);
263
+ }
264
+ const url = tarball.tarballUrl ?? this.#deriveTarballUrl(tarball, npmrcConfig);
265
+ if (!URL.canParse(url)) {
266
+ throw new EmbeddedPackageError(`The tarball URL for embedded package '${tarball.name}@${tarball.version}'`
267
+ + ` is not a valid URL: '${redactUrl(url)}'. Check the 'registry' configuration`
268
+ + ` in your .npmrc (it must be an absolute URL including the protocol).`);
269
+ }
270
+ debug('%s@%s: downloading from %s', tarball.name, tarball.version, redactUrl(url));
271
+ const content = await this.#download(tarball, url, npmrcConfig);
272
+ if (!verifyIntegrity(content, tarball.integrity)) {
273
+ throw new EmbeddedPackageError(`The tarball downloaded for embedded package '${tarball.name}@${tarball.version}'`
274
+ + ` from '${redactUrl(url)}' does not match the integrity hash recorded in the lockfile`
275
+ + ` ('${tarball.integrity}'). The registry may be serving a different artifact`
276
+ + ` than the one the lockfile was created against.`);
277
+ }
278
+ return await this.#cache.put(tarball.integrity, content);
279
+ }
280
+ #deriveTarballUrl(tarball, npmrcConfig) {
281
+ const registryUrl = resolveRegistryUrl(npmrcConfig, tarball.name, this.#env);
282
+ const basename = tarball.name.split('/').pop();
283
+ return `${registryUrl}${tarball.name}/-/${basename}-${tarball.version}.tgz`;
284
+ }
285
+ async #download(tarball, url, npmrcConfig) {
286
+ const authHeader = resolveAuthHeader(npmrcConfig, url, this.#env);
287
+ try {
288
+ const response = await axios.get(url, assignProxy(url, {
289
+ responseType: 'arraybuffer',
290
+ headers: {
291
+ // Ask for the raw artifact: a registry or proxy that labels the
292
+ // already-gzipped tarball with `Content-Encoding: gzip` would
293
+ // otherwise make axios gunzip it, breaking integrity verification
294
+ // with a misleading "different artifact" error.
295
+ 'accept-encoding': 'identity',
296
+ ...(authHeader !== undefined ? { authorization: authHeader } : {}),
297
+ },
298
+ timeout: DOWNLOAD_TIMEOUT_MS,
299
+ maxContentLength: MAX_TARBALL_BYTES,
300
+ }));
301
+ return Buffer.from(response.data);
302
+ }
303
+ catch (err) {
304
+ const status = err?.response?.status;
305
+ const statusHint = status !== undefined ? ` (HTTP ${status})` : '';
306
+ const authHint = status === 401 || status === 403
307
+ ? ` Check that your .npmrc contains valid credentials for this registry.`
308
+ : '';
309
+ throw new EmbeddedPackageError(`Failed to download embedded package '${tarball.name}@${tarball.version}'`
310
+ + ` from '${redactUrl(url)}'${statusHint}.${authHint}`, { cause: err });
311
+ }
312
+ }
313
+ }
314
+ //# sourceMappingURL=materializer.js.map