checkly 8.22.0 → 8.23.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 (85) hide show
  1. package/dist/ai-context/skills-command/references/configure-playwright-checks.md +24 -19
  2. package/dist/commands/debug/parse-project.js +3 -0
  3. package/dist/commands/debug/parse-project.js.map +1 -1
  4. package/dist/commands/deploy.js +3 -0
  5. package/dist/commands/deploy.js.map +1 -1
  6. package/dist/commands/pw-test.js +3 -0
  7. package/dist/commands/pw-test.js.map +1 -1
  8. package/dist/commands/test.js +3 -0
  9. package/dist/commands/test.js.map +1 -1
  10. package/dist/config.d.ts +1 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/constructs/playwright-check-bundle.d.ts +5 -5
  13. package/dist/constructs/playwright-check-bundle.js.map +1 -1
  14. package/dist/constructs/project.js +5 -2
  15. package/dist/constructs/project.js.map +1 -1
  16. package/dist/constructs/session.d.ts +2 -1
  17. package/dist/constructs/session.js +2 -1
  18. package/dist/constructs/session.js.map +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/services/check-parser/bundler.d.ts +79 -8
  21. package/dist/services/check-parser/bundler.js +567 -9
  22. package/dist/services/check-parser/bundler.js.map +1 -1
  23. package/dist/services/check-parser/cache-hash.d.ts +141 -17
  24. package/dist/services/check-parser/cache-hash.js +78 -12
  25. package/dist/services/check-parser/cache-hash.js.map +1 -1
  26. package/dist/services/check-parser/faux-package.d.ts +5 -0
  27. package/dist/services/check-parser/faux-package.js +9 -1
  28. package/dist/services/check-parser/faux-package.js.map +1 -1
  29. package/dist/services/check-parser/lockfile-pruner.d.ts +104 -0
  30. package/dist/services/check-parser/lockfile-pruner.js +1107 -0
  31. package/dist/services/check-parser/lockfile-pruner.js.map +1 -0
  32. package/dist/services/check-parser/package-files/package-manager.d.ts +34 -0
  33. package/dist/services/check-parser/package-files/package-manager.js +131 -3
  34. package/dist/services/check-parser/package-files/package-manager.js.map +1 -1
  35. package/dist/services/check-parser/package-files/pnpmfile.d.ts +40 -0
  36. package/dist/services/check-parser/package-files/pnpmfile.js +322 -0
  37. package/dist/services/check-parser/package-files/pnpmfile.js.map +1 -0
  38. package/dist/services/check-parser/package-files/resolver.d.ts +6 -1
  39. package/dist/services/check-parser/package-files/resolver.js +34 -0
  40. package/dist/services/check-parser/package-files/resolver.js.map +1 -1
  41. package/dist/services/check-parser/package-files/workspace.d.ts +16 -1
  42. package/dist/services/check-parser/package-files/workspace.js +17 -2
  43. package/dist/services/check-parser/package-files/workspace.js.map +1 -1
  44. package/dist/services/check-parser/package-prune.d.ts +68 -0
  45. package/dist/services/check-parser/package-prune.js +274 -0
  46. package/dist/services/check-parser/package-prune.js.map +1 -0
  47. package/dist/services/check-parser/parser.js +9 -0
  48. package/dist/services/check-parser/parser.js.map +1 -1
  49. package/dist/services/check-parser/patched-dependencies.d.ts +133 -0
  50. package/dist/services/check-parser/patched-dependencies.js +428 -0
  51. package/dist/services/check-parser/patched-dependencies.js.map +1 -0
  52. package/dist/services/checkly-config-loader.d.ts +164 -10
  53. package/dist/services/checkly-config-loader.js +39 -0
  54. package/dist/services/checkly-config-loader.js.map +1 -1
  55. package/dist/services/embedded-packages/cache.d.ts +8 -4
  56. package/dist/services/embedded-packages/cache.js +27 -6
  57. package/dist/services/embedded-packages/cache.js.map +1 -1
  58. package/dist/services/embedded-packages/diagnostics.d.ts +129 -0
  59. package/dist/services/embedded-packages/diagnostics.js +212 -0
  60. package/dist/services/embedded-packages/diagnostics.js.map +1 -0
  61. package/dist/services/embedded-packages/lockfile-filter.d.ts +20 -0
  62. package/dist/services/embedded-packages/lockfile-filter.js +40 -0
  63. package/dist/services/embedded-packages/lockfile-filter.js.map +1 -0
  64. package/dist/services/embedded-packages/lockfile-packages.d.ts +31 -2
  65. package/dist/services/embedded-packages/lockfile-packages.js +234 -30
  66. package/dist/services/embedded-packages/lockfile-packages.js.map +1 -1
  67. package/dist/services/embedded-packages/materializer.d.ts +19 -5
  68. package/dist/services/embedded-packages/materializer.js +445 -91
  69. package/dist/services/embedded-packages/materializer.js.map +1 -1
  70. package/dist/services/embedded-packages/npmrc.d.ts +163 -21
  71. package/dist/services/embedded-packages/npmrc.js +440 -75
  72. package/dist/services/embedded-packages/npmrc.js.map +1 -1
  73. package/dist/services/embedded-packages/spec.d.ts +89 -17
  74. package/dist/services/embedded-packages/spec.js +199 -30
  75. package/dist/services/embedded-packages/spec.js.map +1 -1
  76. package/dist/services/embedded-packages/url.d.ts +39 -0
  77. package/dist/services/embedded-packages/url.js +60 -0
  78. package/dist/services/embedded-packages/url.js.map +1 -0
  79. package/dist/services/playwright-project-bundler.js +27 -23
  80. package/dist/services/playwright-project-bundler.js.map +1 -1
  81. package/dist/services/runner/registries.d.ts +144 -0
  82. package/dist/services/runner/registries.js +232 -0
  83. package/dist/services/runner/registries.js.map +1 -0
  84. package/oclif.manifest.json +1 -1
  85. package/package.json +5 -5
@@ -1,47 +1,119 @@
1
1
  /**
2
2
  * A parsed `bundle.packages.embed` entry: a package name — or a name
3
3
  * pattern with `*` wildcards — with an optional exact version pin
4
- * (`name` or `name@version`).
4
+ * (`name` or `name@version`), optionally prefixed with `!` to make it an
5
+ * exclusion.
5
6
  */
6
7
  export interface EmbeddedPackageSpec {
7
- /** The raw config entry, kept for error messages. */
8
+ /** The raw config entry, `!` prefix included, kept for error messages. */
8
9
  raw: string;
9
10
  /**
10
11
  * The package name, e.g. `@acme/private-utils` — or, when
11
- * {@link namePattern} is set, the raw name pattern, e.g. `@acme/*`.
12
+ * {@link wildcard} is set, the raw name pattern, e.g. `@acme/*`.
12
13
  */
13
14
  name: string;
14
15
  /** The exact pinned version, if the entry included one. */
15
16
  version?: string;
16
17
  /**
17
- * Present when the name contains `*` wildcards: the compiled matcher.
18
- * Each `*` matches any run of characters except `/`, so a wildcard
19
- * never crosses the scope separator (`@acme/*` matches only packages in
20
- * that scope; a bare `*` matches only unscoped names).
18
+ * True when the name contains `*` wildcards and matches as a pattern
19
+ * rather than by exact comparison. Each single `*` matches any run of
20
+ * characters except `/`, so it never crosses the scope separator
21
+ * (`@acme/*` matches only packages in that scope; a bare `*` matches
22
+ * only unscoped names). A run of two or more stars (`**`) matches any
23
+ * run of characters *including* `/`: `**` alone matches every package,
24
+ * and `**-foo` matches scoped and unscoped names alike. Directly before
25
+ * a `/`, a `**` may also match nothing together with that `/` — the
26
+ * glob habit, so `'**' + '/utils'` (one string; split here only because
27
+ * `*` followed by `/` would end this comment) matches both `utils` and
28
+ * `@acme/utils`.
21
29
  */
22
- namePattern?: RegExp;
30
+ wildcard: boolean;
31
+ /**
32
+ * True when the entry was prefixed with `!`: instead of selecting
33
+ * packages, it removes the ones it matches from what the entries before
34
+ * it selected.
35
+ */
36
+ exclude: boolean;
37
+ }
38
+ /** The parts of a lockfile entry a spec is matched against. */
39
+ export interface PackageRef {
40
+ name: string;
41
+ /**
42
+ * Absent on entries the lockfile records without one, e.g. git, file and
43
+ * URL resolutions and workspace links.
44
+ */
45
+ version?: string;
23
46
  }
24
47
  /**
25
48
  * Whether a spec selects the given package name: exact comparison for
26
49
  * plain specs, pattern match for wildcard specs.
27
50
  */
28
- export declare function specMatchesPackageName(spec: EmbeddedPackageSpec, packageName: string): boolean;
51
+ export declare function specMatchesPackageName(spec: PackageNamePattern, packageName: string): boolean;
52
+ /**
53
+ * Whether an ordered pattern list selects the given name: a matching
54
+ * plain entry selects it, a matching `!` exclusion deselects it, and the
55
+ * last matching entry wins. This is the canonical definition of the
56
+ * order-sensitive exclusion rule both `bundle.packages` options share
57
+ * (`['@acme/*', '!@acme/keep']` selects the scope except `@acme/keep`,
58
+ * while the reverse order selects the whole scope because the exclusion
59
+ * ran before anything was selected); the embed materializer applies the
60
+ * same rule per spec so it can attribute diagnostics to entries.
61
+ */
62
+ export declare function patternsSelectName(patterns: PackageNamePattern[], name: string): boolean;
63
+ /**
64
+ * Whether a spec selects the given package: the name must match, and so
65
+ * must an exact version pin. An entry the lockfile records without a
66
+ * version never satisfies a pin.
67
+ */
68
+ export declare function specMatchesPackage(spec: EmbeddedPackageSpec, entry: PackageRef): boolean;
69
+ /**
70
+ * As {@link specMatchesPackage}, except that a version-less entry matches
71
+ * any pin. Used where such an entry is still worth reporting against a
72
+ * pinned spec: a git resolution or a workspace link may well be the package
73
+ * the user meant, recorded in a form that has no version to compare.
74
+ */
75
+ export declare function specLooselyMatchesPackage(spec: EmbeddedPackageSpec, entry: PackageRef): boolean;
29
76
  export declare class InvalidEmbeddedPackageSpecError extends Error {
30
77
  /** The failure alone, without the `Invalid embedded package '<spec>':` prefix. */
31
78
  readonly reason: string;
32
79
  constructor(spec: string, reason: string);
33
80
  }
34
81
  /**
35
- * Parses a `bundle.packages.embed` entry into a package name and an
36
- * optional exact version pin.
82
+ * Parses a `bundle.packages.embed` entry into a package name, an optional
83
+ * exact version pin and whether the entry excludes rather than selects.
37
84
  *
38
85
  * Accepts `name` (embed every lockfile version of the package) and
39
86
  * `name@version` with an exact semver version. The name may contain `*`
40
- * wildcards (`@acme/*`, `acme-*`, `@acme/*-utils`), each matching any run
41
- * of characters except `/`. Version ranges are rejected: the embedded
42
- * tarball must be the exact artifact the lockfile resolved, so a range has
43
- * nothing meaningful to select against. A leading `v` is stripped, but the
44
- * version is otherwise kept as written (including any build metadata) so
45
- * it compares exactly against lockfile versions.
87
+ * and `**` wildcards (`@acme/*`, `acme-*`, `**-foo`), matching as
88
+ * described on {@link EmbeddedPackageSpec.wildcard}. A `!`
89
+ * prefix (`!@acme/legacy`, `!@acme/*`,
90
+ * `!legacy@2.1.0`) marks the entry as an exclusion, subtracting from what
91
+ * the entries before it selected. Version ranges are rejected: the
92
+ * embedded tarball must be the exact artifact the lockfile resolved, so a
93
+ * range has nothing meaningful to select against. A leading `v` is
94
+ * stripped, but the version is otherwise kept as written (including any
95
+ * build metadata) so it compares exactly against lockfile versions.
46
96
  */
47
97
  export declare function parseEmbeddedPackageSpec(raw: string): EmbeddedPackageSpec;
98
+ /**
99
+ * A parsed package name pattern: a plain package name, or a name pattern
100
+ * with `*` wildcards, optionally prefixed with `!` to make it an
101
+ * exclusion — the name-matching subset of {@link EmbeddedPackageSpec},
102
+ * without the version pin. See the spec's field docs for the wildcard
103
+ * and exclusion semantics.
104
+ */
105
+ export type PackageNamePattern = Pick<EmbeddedPackageSpec, 'name' | 'wildcard' | 'exclude'>;
106
+ export declare class InvalidPackageNamePatternError extends Error {
107
+ constructor(raw: string, reason: string);
108
+ }
109
+ /**
110
+ * Parses a package name pattern: a package name that may contain `*`
111
+ * and `**` wildcards (`@acme/*`, `acme-*`, `**-foo`), matching as
112
+ * described on {@link EmbeddedPackageSpec.wildcard}. A `!` prefix
113
+ * marks the entry as an exclusion, subtracting from what the entries
114
+ * before it selected, exactly as in `bundle.packages.embed`. Unlike
115
+ * {@link parseEmbeddedPackageSpec} there are no `name@version` pins;
116
+ * they are rejected with a pointed message so an embed-style pin fails
117
+ * loudly instead of silently matching nothing.
118
+ */
119
+ export declare function parsePackageNamePattern(raw: string): PackageNamePattern;
@@ -4,26 +4,142 @@ import semver from 'semver';
4
4
  * plain specs, pattern match for wildcard specs.
5
5
  */
6
6
  export function specMatchesPackageName(spec, packageName) {
7
- if (spec.namePattern !== undefined) {
8
- return spec.namePattern.test(packageName);
7
+ if (spec.wildcard) {
8
+ return matchesNamePattern(spec.name, packageName);
9
9
  }
10
10
  return spec.name === packageName;
11
11
  }
12
- function compileNamePattern(name) {
13
- // Splitting on *runs* of `*` treats consecutive stars as one, keeping
14
- // the compiled regex free of adjacent `[^/]*` runs, whose backtracking
15
- // on a mismatch grows catastrophically with the number of stars.
16
- const escaped = name
17
- .split(/\*+/)
18
- .map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
19
- .join('[^/]*');
20
- return new RegExp(`^${escaped}$`);
12
+ /**
13
+ * Whether an ordered pattern list selects the given name: a matching
14
+ * plain entry selects it, a matching `!` exclusion deselects it, and the
15
+ * last matching entry wins. This is the canonical definition of the
16
+ * order-sensitive exclusion rule both `bundle.packages` options share
17
+ * (`['@acme/*', '!@acme/keep']` selects the scope except `@acme/keep`,
18
+ * while the reverse order selects the whole scope because the exclusion
19
+ * ran before anything was selected); the embed materializer applies the
20
+ * same rule per spec so it can attribute diagnostics to entries.
21
+ */
22
+ export function patternsSelectName(patterns, name) {
23
+ let selected = false;
24
+ for (const pattern of patterns) {
25
+ if (specMatchesPackageName(pattern, name)) {
26
+ selected = !pattern.exclude;
27
+ }
28
+ }
29
+ return selected;
30
+ }
31
+ /**
32
+ * Whether a spec selects the given package: the name must match, and so
33
+ * must an exact version pin. An entry the lockfile records without a
34
+ * version never satisfies a pin.
35
+ */
36
+ export function specMatchesPackage(spec, entry) {
37
+ return specMatchesPackageName(spec, entry.name)
38
+ && (spec.version === undefined || entry.version === spec.version);
39
+ }
40
+ /**
41
+ * As {@link specMatchesPackage}, except that a version-less entry matches
42
+ * any pin. Used where such an entry is still worth reporting against a
43
+ * pinned spec: a git resolution or a workspace link may well be the package
44
+ * the user meant, recorded in a form that has no version to compare.
45
+ */
46
+ export function specLooselyMatchesPackage(spec, entry) {
47
+ return specMatchesPackageName(spec, entry.name)
48
+ && (spec.version === undefined || entry.version === undefined || entry.version === spec.version);
49
+ }
50
+ /**
51
+ * Matches a wildcard name pattern against a package name, without a
52
+ * regex: a regex spelling each `*` as `[^/]*` backtracks catastrophically
53
+ * on a mismatch once several stars are separated by literals (measured in
54
+ * the minutes for a dozen stars against a 50-character name), and the
55
+ * pattern comes straight from the user's config. The tokenized pattern —
56
+ * with the wildcard semantics {@link EmbeddedPackageSpec.wildcard}
57
+ * documents — is applied token by token to a boolean row over the name,
58
+ * where entry `j` records whether the tokens so far can match the first
59
+ * `j` characters: a literal shifts the row by one matching character, a
60
+ * star extends every reachable position rightwards one character at a
61
+ * time (refusing `/` unless doubled), and the star-run-plus-`/` pair
62
+ * either leaves the row as it is or lands right past any reachable `/`.
63
+ * No backtracking, and O(pattern length × name length) like the greedy
64
+ * walk it replaced.
65
+ *
66
+ * These semantics are also a wire contract beyond this repository:
67
+ * `runner.registries` routing rules (`services/runner/registries.ts`)
68
+ * ship raw patterns inside the code bundle for Checkly runners to match
69
+ * with identical semantics. A semantic change here changes how deployed
70
+ * routing configs behave and requires a coordinated runner update plus a
71
+ * `REGISTRIES_FILE_VERSION` bump.
72
+ */
73
+ function matchesNamePattern(pattern, packageName) {
74
+ const n = packageName.length;
75
+ let row = new Array(n + 1).fill(false);
76
+ row[0] = true;
77
+ for (const token of tokenizePattern(pattern)) {
78
+ const next = new Array(n + 1).fill(false);
79
+ if (token[0] === '*' && token.endsWith('/')) {
80
+ let reachable = false;
81
+ for (let j = 0; j <= n; j++) {
82
+ reachable = reachable || (j > 0 && row[j - 1]);
83
+ next[j] = row[j] || (j > 0 && reachable && packageName[j - 1] === '/');
84
+ }
85
+ }
86
+ else if (token[0] === '*') {
87
+ for (let j = 0; j <= n; j++) {
88
+ next[j] = row[j] || (j > 0 && next[j - 1] && (token.length > 1 || packageName[j - 1] !== '/'));
89
+ }
90
+ }
91
+ else {
92
+ for (let j = 1; j <= n; j++) {
93
+ next[j] = row[j - 1] && packageName[j - 1] === token;
94
+ }
95
+ }
96
+ row = next;
97
+ }
98
+ return row[n];
21
99
  }
22
100
  // npm's name rules for already-published packages: new publishes must be
23
101
  // lowercase, but plenty of legitimate older packages (JSONStream) contain
24
102
  // uppercase letters, so both cases are accepted. Leading `.` and `_` stay
25
103
  // disallowed, as npm has never permitted them.
26
104
  const PACKAGE_NAME_RE = /^(@[a-zA-Z0-9-~][a-zA-Z0-9-~._]*\/)?[a-zA-Z0-9-~][a-zA-Z0-9-._~]*$/;
105
+ /**
106
+ * One pattern token per match: a maximal star run with a directly
107
+ * following `/` attached, or a single literal character. Shared by the
108
+ * matcher and the name-shape validation so the two agree on what a
109
+ * globstar-plus-`/` pair is.
110
+ */
111
+ function tokenizePattern(pattern) {
112
+ return pattern.match(/\*{2,}\/?|\*|[^*]/g) ?? [];
113
+ }
114
+ /**
115
+ * Whether a wildcard pattern can produce a valid package name. Every star
116
+ * token is replaced with a default probe — `a` for a star run, nothing at
117
+ * all for a `**`-plus-`/` pair, mirroring its zero-segment match — and,
118
+ * since a valid name contains at most one scope (one leading `@` and one
119
+ * `/`), one globstar token at a time may additionally stand in for scope
120
+ * structure: a plain `**` probes as the scope's `@` (`@a`) or its `/`
121
+ * (`a/a`), and a `**`-plus-`/` pair as a segment (`a/`) or a whole scope
122
+ * prefix (`@a/`). The pattern is name-shaped when any substitution
123
+ * satisfies {@link PACKAGE_NAME_RE}. This is a deliberately conservative
124
+ * subset of what the matcher could match — a shape needing two tokens to
125
+ * take scope probes at once is rejected — and single `*` runs are never
126
+ * probed as scope structure, even though a lone star does match a leading
127
+ * `@` in the matcher: scope-crossing spellings must be written with `**`,
128
+ * so `'*' + '/utils'` stays rejected while `'**' + '/utils'` is accepted.
129
+ */
130
+ function isNameShapedPattern(pattern) {
131
+ const tokens = tokenizePattern(pattern);
132
+ const filled = tokens.map(token => token[0] !== '*' ? token : token.endsWith('/') ? '' : 'a');
133
+ if (PACKAGE_NAME_RE.test(filled.join(''))) {
134
+ return true;
135
+ }
136
+ const withProbe = (index, probe) => filled
137
+ .map((token, i) => i === index ? probe : token)
138
+ .join('');
139
+ return tokens.some((token, i) => token[1] === '*'
140
+ && (token.endsWith('/') ? ['a/', '@a/'] : ['@a', 'a/a'])
141
+ .some(probe => PACKAGE_NAME_RE.test(withProbe(i, probe))));
142
+ }
27
143
  export class InvalidEmbeddedPackageSpecError extends Error {
28
144
  /** The failure alone, without the `Invalid embedded package '<spec>':` prefix. */
29
145
  reason;
@@ -34,37 +150,54 @@ export class InvalidEmbeddedPackageSpecError extends Error {
34
150
  }
35
151
  }
36
152
  /**
37
- * Parses a `bundle.packages.embed` entry into a package name and an
38
- * optional exact version pin.
153
+ * Parses a `bundle.packages.embed` entry into a package name, an optional
154
+ * exact version pin and whether the entry excludes rather than selects.
39
155
  *
40
156
  * Accepts `name` (embed every lockfile version of the package) and
41
157
  * `name@version` with an exact semver version. The name may contain `*`
42
- * wildcards (`@acme/*`, `acme-*`, `@acme/*-utils`), each matching any run
43
- * of characters except `/`. Version ranges are rejected: the embedded
44
- * tarball must be the exact artifact the lockfile resolved, so a range has
45
- * nothing meaningful to select against. A leading `v` is stripped, but the
46
- * version is otherwise kept as written (including any build metadata) so
47
- * it compares exactly against lockfile versions.
158
+ * and `**` wildcards (`@acme/*`, `acme-*`, `**-foo`), matching as
159
+ * described on {@link EmbeddedPackageSpec.wildcard}. A `!`
160
+ * prefix (`!@acme/legacy`, `!@acme/*`,
161
+ * `!legacy@2.1.0`) marks the entry as an exclusion, subtracting from what
162
+ * the entries before it selected. Version ranges are rejected: the
163
+ * embedded tarball must be the exact artifact the lockfile resolved, so a
164
+ * range has nothing meaningful to select against. A leading `v` is
165
+ * stripped, but the version is otherwise kept as written (including any
166
+ * build metadata) so it compares exactly against lockfile versions.
48
167
  */
49
168
  export function parseEmbeddedPackageSpec(raw) {
50
169
  if (typeof raw !== 'string' || raw === '') {
51
170
  throw new InvalidEmbeddedPackageSpecError(String(raw), `must be a non-empty string`);
52
171
  }
172
+ // The `!` has to come off before anything else is read: on the raw
173
+ // `!@acme/foo` the version separator below would land on the scope marker
174
+ // at index 1 and parse the entry as name `!` at version `acme/foo`.
175
+ const exclude = raw.startsWith('!');
176
+ const pattern = exclude ? raw.slice(1) : raw;
177
+ if (pattern === '') {
178
+ throw new InvalidEmbeddedPackageSpecError(raw, `must name a package or pattern after '!'`);
179
+ }
53
180
  // A version separator is any `@` past the first character, which keeps the
54
181
  // scope marker of `@scope/name` intact.
55
- const versionSeparator = raw.lastIndexOf('@');
56
- const name = versionSeparator > 0 ? raw.slice(0, versionSeparator) : raw;
57
- const rawVersion = versionSeparator > 0 ? raw.slice(versionSeparator + 1) : undefined;
58
- // A wildcard name must still be name-shaped once every `*` stands in for
59
- // name characters. (`*` itself appears in npm's legacy name charset, but
60
- // no real-world package uses it; here it always means a wildcard.)
182
+ const versionSeparator = pattern.lastIndexOf('@');
183
+ const name = versionSeparator > 0 ? pattern.slice(0, versionSeparator) : pattern;
184
+ const rawVersion = versionSeparator > 0 ? pattern.slice(versionSeparator + 1) : undefined;
185
+ // A wildcard name must still be name-shaped once every star run stands in
186
+ // for name characters. (`*` itself appears in npm's legacy name charset,
187
+ // but no real-world package uses it; here it always means a wildcard.)
61
188
  const wildcard = name.includes('*');
62
- if (!PACKAGE_NAME_RE.test(wildcard ? name.replace(/\*/g, 'a') : name)) {
63
- throw new InvalidEmbeddedPackageSpecError(raw, `'${name}' is not a valid npm package name${wildcard ? ' pattern' : ''}`);
189
+ if (wildcard ? !isNameShapedPattern(name) : !PACKAGE_NAME_RE.test(name)) {
190
+ // A lone `*` next to `/` is the classic near-miss, since only `**`
191
+ // crosses the scope separator; point at the doubled spelling when
192
+ // promoting the star is all it takes to fix the pattern.
193
+ const promoted = name.replace(/(?<=^|[^*])\*(?=\/)/g, '**');
194
+ const hint = promoted !== name && isNameShapedPattern(promoted)
195
+ ? ` (a single '*' never crosses '/'; did you mean '${promoted}'?)`
196
+ : '';
197
+ throw new InvalidEmbeddedPackageSpecError(raw, `'${name}' is not a valid npm package name${wildcard ? ' pattern' : ''}${hint}`);
64
198
  }
65
- const namePattern = wildcard ? compileNamePattern(name) : undefined;
66
199
  if (rawVersion === undefined) {
67
- return { raw, name, namePattern };
200
+ return { raw, name, wildcard, exclude };
68
201
  }
69
202
  // Trim before validating: semver.valid() tolerates surrounding whitespace,
70
203
  // so an untrimmed version would pass validation yet never compare equal to
@@ -74,6 +207,42 @@ export function parseEmbeddedPackageSpec(raw) {
74
207
  if (semver.valid(version) === null) {
75
208
  throw new InvalidEmbeddedPackageSpecError(raw, `'${rawVersion}' is not an exact semver version (use 'name' or 'name@1.2.3')`);
76
209
  }
77
- return { raw, name, version, namePattern };
210
+ return { raw, name, version, wildcard, exclude };
211
+ }
212
+ export class InvalidPackageNamePatternError extends Error {
213
+ constructor(raw, reason) {
214
+ super(`Invalid package name pattern '${raw}': ${reason}`);
215
+ this.name = 'InvalidPackageNamePatternError';
216
+ }
217
+ }
218
+ /**
219
+ * Parses a package name pattern: a package name that may contain `*`
220
+ * and `**` wildcards (`@acme/*`, `acme-*`, `**-foo`), matching as
221
+ * described on {@link EmbeddedPackageSpec.wildcard}. A `!` prefix
222
+ * marks the entry as an exclusion, subtracting from what the entries
223
+ * before it selected, exactly as in `bundle.packages.embed`. Unlike
224
+ * {@link parseEmbeddedPackageSpec} there are no `name@version` pins;
225
+ * they are rejected with a pointed message so an embed-style pin fails
226
+ * loudly instead of silently matching nothing.
227
+ */
228
+ export function parsePackageNamePattern(raw) {
229
+ // The pin rejection must run before the delegation below, which would
230
+ // otherwise accept `name@version` the way embed does. The `!` comes off
231
+ // first so the scope marker of `!@scope/name` at index 1 is not read as
232
+ // a version separator; any `@` past the first character of what remains
233
+ // would be one.
234
+ if (typeof raw === 'string' && raw.replace(/^!/, '').lastIndexOf('@') > 0) {
235
+ throw new InvalidPackageNamePatternError(raw, `'name@version' pins are not supported here`);
236
+ }
237
+ try {
238
+ const { name, wildcard, exclude } = parseEmbeddedPackageSpec(raw);
239
+ return { name, wildcard, exclude };
240
+ }
241
+ catch (err) {
242
+ if (err instanceof InvalidEmbeddedPackageSpecError) {
243
+ throw new InvalidPackageNamePatternError(String(raw), err.reason);
244
+ }
245
+ throw err;
246
+ }
78
247
  }
79
248
  //# sourceMappingURL=spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../../src/services/embedded-packages/spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../../src/services/embedded-packages/spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Parses a URL that something is about to be fetched from, or returns
3
+ * undefined when nothing can be.
4
+ *
5
+ * The scheme has to be one this CLI actually fetches over: `ftp://host/x`
6
+ * parses and has a host, but axios cannot retrieve it, and tarball URLs
7
+ * read out of lockfiles and registry metadata are already held to the same
8
+ * http(s) rule. That also settles the shapes a bare parse lets through,
9
+ * such as `admin:s3cret@nexus.local/x` — the opaque scheme `admin:` with an
10
+ * empty host, and a credential sitting in what looks like a path. The host
11
+ * is checked as well, belt and braces: the WHATWG parser refuses a
12
+ * host-less `https://` today, and this rule means "a request can be made",
13
+ * which needs somewhere to send it.
14
+ *
15
+ * This decides both whether a request can be made and whether the value is
16
+ * safe to echo, which must be the same rule: a URL that reaches the network
17
+ * is one `redactUrl` can rebuild from parts that cannot carry a secret.
18
+ */
19
+ export declare function parseFetchableUrl(url: string): URL | undefined;
20
+ /**
21
+ * The rule `parseFetchableUrl` enforces, as a sentence for a message. Kept
22
+ * beside the check so the two cannot drift into describing different rules.
23
+ */
24
+ export declare const FETCHABLE_URL_REQUIREMENT = "an absolute http or https URL with a host";
25
+ /**
26
+ * Parses a URL that a path will be appended to, or returns undefined when
27
+ * appending would not do what it looks like.
28
+ *
29
+ * Everything `parseFetchableUrl` requires, plus no query and no fragment:
30
+ * those swallow whatever follows them. `https://host/npm/?token=abc` with
31
+ * `bar/-/bar-2.0.0.tgz` appended parses with its pathname still `/npm/` and
32
+ * the whole package path inside the query, so the request would go to the
33
+ * registry root and fail later as something that reads like the registry's
34
+ * fault. Excluding them is what makes "appending a path leaves the host and
35
+ * the path intact" true rather than nearly true.
36
+ */
37
+ export declare function parseComposableUrl(url: string): URL | undefined;
38
+ /** The rule `parseComposableUrl` enforces, as a sentence for a message. */
39
+ export declare const COMPOSABLE_URL_REQUIREMENT = "an absolute http or https URL with a host, carrying no query or fragment";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Parses a URL that something is about to be fetched from, or returns
3
+ * undefined when nothing can be.
4
+ *
5
+ * The scheme has to be one this CLI actually fetches over: `ftp://host/x`
6
+ * parses and has a host, but axios cannot retrieve it, and tarball URLs
7
+ * read out of lockfiles and registry metadata are already held to the same
8
+ * http(s) rule. That also settles the shapes a bare parse lets through,
9
+ * such as `admin:s3cret@nexus.local/x` — the opaque scheme `admin:` with an
10
+ * empty host, and a credential sitting in what looks like a path. The host
11
+ * is checked as well, belt and braces: the WHATWG parser refuses a
12
+ * host-less `https://` today, and this rule means "a request can be made",
13
+ * which needs somewhere to send it.
14
+ *
15
+ * This decides both whether a request can be made and whether the value is
16
+ * safe to echo, which must be the same rule: a URL that reaches the network
17
+ * is one `redactUrl` can rebuild from parts that cannot carry a secret.
18
+ */
19
+ export function parseFetchableUrl(url) {
20
+ let parsed;
21
+ try {
22
+ parsed = new URL(url);
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ if (parsed.host === '' || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
28
+ return undefined;
29
+ }
30
+ return parsed;
31
+ }
32
+ /**
33
+ * The rule `parseFetchableUrl` enforces, as a sentence for a message. Kept
34
+ * beside the check so the two cannot drift into describing different rules.
35
+ */
36
+ export const FETCHABLE_URL_REQUIREMENT = 'an absolute http or https URL with a host';
37
+ /**
38
+ * Parses a URL that a path will be appended to, or returns undefined when
39
+ * appending would not do what it looks like.
40
+ *
41
+ * Everything `parseFetchableUrl` requires, plus no query and no fragment:
42
+ * those swallow whatever follows them. `https://host/npm/?token=abc` with
43
+ * `bar/-/bar-2.0.0.tgz` appended parses with its pathname still `/npm/` and
44
+ * the whole package path inside the query, so the request would go to the
45
+ * registry root and fail later as something that reads like the registry's
46
+ * fault. Excluding them is what makes "appending a path leaves the host and
47
+ * the path intact" true rather than nearly true.
48
+ */
49
+ export function parseComposableUrl(url) {
50
+ // Tested on the raw string, not on `search` and `hash`: a URL ending in a
51
+ // bare `?` or `#` parses with both of those empty, yet still absorbs
52
+ // whatever is appended after the delimiter.
53
+ if (/[?#]/.test(url)) {
54
+ return undefined;
55
+ }
56
+ return parseFetchableUrl(url);
57
+ }
58
+ /** The rule `parseComposableUrl` enforces, as a sentence for a message. */
59
+ export const COMPOSABLE_URL_REQUIREMENT = `${FETCHABLE_URL_REQUIREMENT}, carrying no query or fragment`;
60
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../../src/services/embedded-packages/url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAE,GAAW;IAC5C,IAAI,MAAW,CAAA;IACf,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,CAAC;QACxF,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,2CAA2C,CAAA;AAEpF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAE,GAAW;IAC7C,0EAA0E;IAC1E,qEAAqE;IACrE,4CAA4C;IAC5C,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAA;AAC/B,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,yBAAyB,iCAAiC,CAAA"}
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import { createRequire } from 'node:module';
3
3
  import path from 'node:path';
4
4
  import semver from 'semver';
5
+ import { PNPM_PATCHES_DIR } from './check-parser/patched-dependencies.js';
5
6
  import { detectNearestPackageJson } from './check-parser/package-files/package-manager.js';
6
7
  import { PackageJsonFile } from './check-parser/package-files/package-json-file.js';
7
8
  import { lineage } from './check-parser/package-files/walk.js';
@@ -108,23 +109,10 @@ export class PlaywrightProjectBundler {
108
109
  workspaceMembers: workspaceInfo.members,
109
110
  }));
110
111
  }
111
- // Embedded package tarballs live in the CLI cache, whose on-disk
112
- // location (node_modules/.cache, a per-user dir, or CHECKLY_CACHE_DIR)
113
- // never corresponds to the contract path the runner expects, so they
114
- // carry an explicit archive path instead of relying on the strip
115
- // prefix. The materializer memoizes, so concurrent bundles share one
116
- // download run, and the Bundler dedupes registrations by archive path
117
- // across checks.
118
- const materializer = Session.getEmbeddedPackagesMaterializer();
119
- if (materializer !== undefined) {
120
- for (const tarball of await materializer.materialize()) {
121
- files.push({
122
- filePath: tarball.filePath,
123
- physical: true,
124
- archivePath: tarball.archivePath,
125
- });
126
- }
127
- }
112
+ // Embedded package tarballs are deliberately NOT part of a check's
113
+ // files: the Bundler materializes them once during finalize(), after
114
+ // the bundled lockfile has been pruned, so tarballs the shipped
115
+ // lockfile no longer references are never downloaded.
128
116
  return {
129
117
  browsers: pwConfigParsed.getBrowsers(),
130
118
  playwrightVersion,
@@ -141,12 +129,28 @@ export function normalizeVersion(v) {
141
129
  }
142
130
  export function getAutoIncludes(basePath, globCwd, packageManager, existingIncludes) {
143
131
  const autoIncludes = [];
144
- if (packageManager.name === 'pnpm') {
145
- const patchesDir = path.join(basePath, 'patches');
146
- const alreadyIncluded = existingIncludes.some(p => path.resolve(globCwd, p).startsWith(patchesDir));
147
- if (!alreadyIncluded) {
148
- const patchesPattern = pathToPosix(path.join(path.relative(globCwd, basePath), 'patches', '*.patch'));
149
- autoIncludes.push(patchesPattern);
132
+ // The prefix comparison appends a separator so a sibling directory whose
133
+ // name merely starts with the patches dir (e.g. `patches-archive`) does
134
+ // not suppress the auto-include.
135
+ const includesUnder = (dir) => existingIncludes.some(p => {
136
+ const resolved = path.resolve(globCwd, p);
137
+ return resolved === dir || resolved.startsWith(dir + path.sep);
138
+ });
139
+ // Dependency patches live in a conventional per-manager directory that
140
+ // the remote install (and the lockfile pruner's temp-dir install) needs
141
+ // alongside the manifests: `patches/` for pnpm and bun, `.yarn/patches`
142
+ // for Yarn Berry's patch: protocol. A patch kept at a nonconventional
143
+ // path makes the pruner fail closed with a warning instead.
144
+ const patchesDirByManager = {
145
+ pnpm: [PNPM_PATCHES_DIR],
146
+ bun: [PNPM_PATCHES_DIR],
147
+ yarn: ['.yarn', 'patches'],
148
+ };
149
+ const patchesSegments = patchesDirByManager[packageManager.name];
150
+ if (patchesSegments !== undefined) {
151
+ const patchesDir = path.join(basePath, ...patchesSegments);
152
+ if (!includesUnder(patchesDir)) {
153
+ autoIncludes.push(pathToPosix(path.join(path.relative(globCwd, basePath), ...patchesSegments, '*.patch')));
150
154
  }
151
155
  }
152
156
  return autoIncludes;