@vzn/vx 0.0.636 → 0.0.637
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -11
- package/docs/caching.md +213 -207
- package/docs/cli.md +699 -655
- package/docs/execution.md +67 -75
- package/docs/features.md +44 -40
- package/docs/patterns.md +26 -25
- package/docs/schema.md +376 -198
- package/docs/security.md +76 -39
- package/launcher.sh +43 -0
- package/package.json +5 -5
- package/schemas/cache.json +41 -19
- package/schemas/info.json +23 -11
- package/schemas/last.json +48 -2
- package/schemas/lock.json +1 -1
- package/schemas/plan.json +2 -2
- package/schemas/show.json +5 -3
- package/schemas/why.json +15 -1
- package/skills/vx/SKILL.md +16 -12
- package/src/bin.ts +17 -2
- package/src/cache/archive.ts +11 -0
- package/src/cache/cache.ts +105 -53
- package/src/cache/git-inputs.ts +277 -63
- package/src/cache/index.ts +7 -1
- package/src/cache/inputs.ts +125 -35
- package/src/cache/key-fold.ts +8 -7
- package/src/cache/layer.ts +8 -17
- package/src/cache/layered-cache.ts +16 -102
- package/src/cache/policy.ts +25 -29
- package/src/cache/run-history.ts +36 -14
- package/src/cache/schema.ts +21 -23
- package/src/cache/scoped-cache.ts +224 -0
- package/src/cli/cache.ts +150 -28
- package/src/cli/completions.ts +2 -2
- package/src/cli/core-alias.ts +1 -1
- package/src/cli/foreign-flags.ts +13 -12
- package/src/cli/help.ts +94 -63
- package/src/cli/index.ts +42 -13
- package/src/cli/info.ts +38 -23
- package/src/cli/init.ts +115 -406
- package/src/cli/last.ts +145 -4
- package/src/cli/lock.ts +333 -111
- package/src/cli/plan-format.ts +2 -2
- package/src/cli/plugin-commands.ts +5 -9
- package/src/cli/plugin-templates.ts +10 -10
- package/src/cli/run.ts +103 -149
- package/src/cli/select.ts +33 -39
- package/src/cli/show.ts +17 -19
- package/src/cli/task-verb.ts +2 -9
- package/src/cli/upgrade.ts +402 -145
- package/src/cli/watch-cycle.ts +9 -2
- package/src/cli/watch-filter.ts +33 -60
- package/src/cli/watch-fs.ts +29 -16
- package/src/cli/watch-judge.ts +12 -3
- package/src/cli/watch-set.ts +16 -42
- package/src/cli/watch.ts +188 -215
- package/src/cli/why.ts +28 -17
- package/src/cli/workspace-config.ts +31 -68
- package/src/config.ts +169 -70
- package/src/exec/env.ts +1 -31
- package/src/exec/executor.ts +1 -5
- package/src/exec/kill-tree.ts +2 -1
- package/src/exec/sandbox-runtime.ts +12 -1
- package/src/exec/sandbox-violations.ts +9 -1
- package/src/graph/dependency-spec.ts +1 -1
- package/src/graph/index.ts +1 -0
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +19 -7
- package/src/graph/task-graph.ts +44 -20
- package/src/handoff.ts +169 -0
- package/src/index.ts +24 -57
- package/src/orchestrator/affected-tasks.ts +7 -5
- package/src/orchestrator/command-windows.ts +44 -0
- package/src/orchestrator/deferred-outputs.ts +12 -10
- package/src/orchestrator/doctor.ts +98 -110
- package/src/orchestrator/download-policy.ts +3 -3
- package/src/orchestrator/events.ts +19 -3
- package/src/orchestrator/excluded-keys.ts +0 -2
- package/src/orchestrator/execute-task.ts +158 -63
- package/src/orchestrator/fingerprint-watch.ts +18 -15
- package/src/orchestrator/forecast.ts +10 -2
- package/src/orchestrator/framed-output.ts +8 -3
- package/src/orchestrator/graph-stage.ts +5 -5
- package/src/orchestrator/history.ts +70 -30
- package/src/orchestrator/hit-restore.ts +15 -3
- package/src/orchestrator/index.ts +18 -4
- package/src/orchestrator/local-shortcircuit.ts +2 -1
- package/src/orchestrator/lockfile-claim.ts +79 -20
- package/src/orchestrator/logger.ts +71 -39
- package/src/orchestrator/metrics.ts +71 -6
- package/src/orchestrator/miss-save.ts +3 -0
- package/src/orchestrator/options.ts +42 -29
- package/src/orchestrator/output-host.ts +72 -0
- package/src/orchestrator/plan.ts +6 -8
- package/src/orchestrator/plugin-host.ts +87 -63
- package/src/orchestrator/plugin.ts +94 -60
- package/src/orchestrator/prepare.ts +113 -72
- package/src/orchestrator/projects.ts +102 -99
- package/src/orchestrator/remote-prefetch.ts +2 -1
- package/src/orchestrator/run-context.ts +25 -6
- package/src/orchestrator/run-failures.ts +17 -8
- package/src/orchestrator/run-lock.ts +1 -1
- package/src/orchestrator/run-report.ts +9 -9
- package/src/orchestrator/run-settings.ts +48 -0
- package/src/orchestrator/run.ts +121 -95
- package/src/orchestrator/sandbox-fix.ts +39 -20
- package/src/orchestrator/sandbox-request.ts +84 -13
- package/src/orchestrator/signals.ts +1 -1
- package/src/orchestrator/stable-keys.ts +13 -11
- package/src/orchestrator/status-line.ts +48 -6
- package/src/orchestrator/summary.ts +46 -62
- package/src/orchestrator/task-hash.ts +12 -83
- package/src/orchestrator/telemetry.ts +3 -3
- package/src/settings.ts +417 -0
- package/src/util/errors.ts +1 -1
- package/src/util/essential-env.ts +31 -0
- package/src/util/hide-env.ts +107 -0
- package/src/util/index.ts +8 -2
- package/src/util/paths.ts +10 -0
- package/src/util/settle.ts +49 -30
- package/src/util/size.ts +23 -4
- package/src/util/timing.ts +3 -2
- package/src/util/ulid.ts +1 -1
- package/src/util/verbs.ts +0 -15
- package/src/workspace/affected.ts +77 -222
- package/src/workspace/config-eval.ts +15 -20
- package/src/workspace/config-imports.ts +78 -3
- package/src/workspace/config-schema.ts +209 -268
- package/src/workspace/dependency-rules.ts +57 -0
- package/src/workspace/filter.ts +2 -2
- package/src/workspace/fingerprint.ts +11 -121
- package/src/workspace/index.ts +20 -27
- package/src/workspace/load-reads.ts +4 -4
- package/src/workspace/lockfile.ts +96 -140
- package/src/workspace/migration.ts +242 -146
- package/src/workspace/package-graph.ts +30 -216
- package/src/workspace/project-loader.ts +80 -285
- package/src/workspace/repo-id.ts +2 -1
- package/src/workspace/workspace.ts +362 -831
- package/src/cache/config-evals.ts +0 -130
- package/src/workspace/config-cache.ts +0 -661
- package/src/workspace/migrate-scripts.ts +0 -1121
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
1
|
import type { ProjectMeta } from './workspace.js'
|
|
3
2
|
|
|
4
3
|
export interface PackageGraph {
|
|
5
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Some `discover` entry carried `deps`: a project with none is a leaf.
|
|
6
|
+
* False, no plugin supplies edges and a `^task` is refused.
|
|
7
|
+
*/
|
|
8
|
+
readonly edgeSource: boolean
|
|
9
|
+
/** Projects the project builds AFTER (sorted): a `discover` plugin's `deps` and the task edges. */
|
|
6
10
|
directDeps: (name: string) => string[]
|
|
7
|
-
/** All
|
|
11
|
+
/** All projects a change can reach the project through: order edges and a plugin's `reach`. */
|
|
8
12
|
transitiveDeps: (name: string) => string[]
|
|
9
|
-
/** All
|
|
13
|
+
/** All projects a change here can reach (the reverse). */
|
|
10
14
|
transitiveDependents: (name: string) => string[]
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* `taskEdges`: project → the projects its tasks name in a cross-project
|
|
15
19
|
* `dependsOn` (`e2e` → `app` from `dependsOn: ['app#build']`). A dependent
|
|
16
|
-
*
|
|
20
|
+
* no plugin edge names but the task graph does; without it
|
|
17
21
|
* `--filter '...app'` never selected `e2e`, and a CI that runs "what
|
|
18
22
|
* changed and everything depending on it" silently left it out
|
|
19
23
|
* (2026-09-10). The `^task` walk still reads `directDeps`, which carries
|
|
@@ -24,112 +28,37 @@ export function buildPackageGraph(
|
|
|
24
28
|
taskEdges?: ReadonlyMap<string, readonly string[]>,
|
|
25
29
|
): PackageGraph {
|
|
26
30
|
const byName = new Map<string, ProjectMeta>()
|
|
27
|
-
const
|
|
28
|
-
for (const p of projects) {
|
|
29
|
-
byName.set(p.name, p)
|
|
30
|
-
byDir.set(path.resolve(p.dir), p)
|
|
31
|
-
}
|
|
32
|
-
const linked = linkedTarget(byName, byDir)
|
|
31
|
+
for (const p of projects) byName.set(p.name, p)
|
|
33
32
|
|
|
34
|
-
// Two adjacencies. ORDER (`directDeps`, the `^task` walk) is
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// and `--affected` read) is ORDER plus every workspace peer.
|
|
39
|
-
//
|
|
40
|
-
// A peer on a workspace sibling is an import that resolves to that
|
|
41
|
-
// sibling's build: every package manager links or hoists it (pnpm's
|
|
42
|
-
// `linkWorkspacePackages`, the hoisted root of bun/npm/yarn), and
|
|
43
|
-
// TanStack/router's `router-devtools-core` peers on `router-core` and
|
|
44
|
-
// type-checks against its `dist` — Nx orders `^build` on the peer,
|
|
45
|
-
// vx built the devtools first and failed (2026-09-11). But peers are
|
|
46
|
-
// the one bucket that routinely cycles (medusa's test-utils peers on
|
|
47
|
-
// medusa, which dev-depends on it through analytics; Turbo, which
|
|
48
|
-
// reads no peers, runs it; an unconditional order edge made `^build`
|
|
49
|
-
// a task cycle, 2026-09-11), so a peer edge that would close a cycle
|
|
50
|
-
// through the order graph is reach only: the consumer above provides
|
|
51
|
-
// that peer.
|
|
52
|
-
//
|
|
53
|
-
// Which edge of a MUTUAL two-peer cycle stays is stable across runs,
|
|
54
|
-
// and the PACKAGE sort below is what makes it so: the first package
|
|
55
|
-
// in name order keeps its edge, whatever order the projects arrive
|
|
56
|
-
// in. The per-package peer sort provides none of that — measured
|
|
57
|
-
// (item 571), both manifest orders give identical graphs in every
|
|
58
|
-
// arrangement tried, because `reaches(peer, p)` walks edges INTO `p`
|
|
59
|
-
// and adding an edge OUT of `p` cannot change it. It stays as cheap
|
|
60
|
-
// insurance against a future rule that does depend on it; it is not
|
|
61
|
-
// what the stability rests on.
|
|
33
|
+
// Two adjacencies. ORDER (`directDeps`, the `^task` walk) is the task
|
|
34
|
+
// edges and a `discover` plugin's `deps`. REACH (the transitive closures
|
|
35
|
+
// `--filter pkg...` and `--affected` read) is ORDER plus a plugin's
|
|
36
|
+
// `reach`, which never orders a build.
|
|
62
37
|
const directDeps = new Map<string, string[]>()
|
|
63
38
|
const order = new Map<string, Set<string>>()
|
|
64
|
-
const
|
|
39
|
+
const reachOnly = new Map<string, readonly string[]>()
|
|
65
40
|
const sorted = [...projects].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
41
|
+
let edgeSource = false
|
|
66
42
|
for (const p of sorted) {
|
|
67
43
|
const own = new Set<string>()
|
|
68
44
|
for (const name of taskEdges?.get(p.name) ?? []) {
|
|
69
45
|
if (name !== p.name && byName.has(name)) own.add(name)
|
|
70
46
|
}
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const add = (key: string, spec: unknown, installed: boolean, into: Set<string>): void => {
|
|
75
|
-
if (typeof spec !== 'string') return
|
|
76
|
-
const target = linked(key, fromCatalog(p, key, spec.trim()), p.dir, installed)
|
|
77
|
-
if (target !== undefined && target !== p.name) into.add(target)
|
|
78
|
-
}
|
|
79
|
-
// Which entry is installed when a key sits in more than one field,
|
|
80
|
-
// measured with bun 1.4, npm 10, yarn 1 and pnpm 12 (2026-09-24): bun
|
|
81
|
-
// and npm take devDependencies, then optionalDependencies, then
|
|
82
|
-
// dependencies; yarn and pnpm take optionalDependencies, then
|
|
83
|
-
// dependencies, then devDependencies. vx cannot tell which manager
|
|
84
|
-
// installed the tree, so either winner linking is an edge: a spare
|
|
85
|
-
// edge costs order, a missing one a stale hit. A dev or optional
|
|
86
|
-
// entry wins under one order or the other; a prod entry loses to an
|
|
87
|
-
// optional one under both.
|
|
88
|
-
for (const key in dev) add(key, dev[key], true, own)
|
|
89
|
-
for (const key in optional) add(key, optional[key], true, own)
|
|
90
|
-
for (const key in prod) if (!Object.hasOwn(optional, key)) add(key, prod[key], true, own)
|
|
91
|
-
order.set(p.name, own)
|
|
92
|
-
// A peer is not installed by the package that declares it: when an
|
|
93
|
-
// installed entry names the same key, that entry is what resolves
|
|
94
|
-
// (turborepo#12640: a registry `buffer@^6` dev dependency beside a
|
|
95
|
-
// `workspace:*` peer on it installs the registry copy in all four).
|
|
96
|
-
// Alone, a peer on a sibling resolves to the sibling whatever its
|
|
97
|
-
// range, since bun and yarn hoist the workspace copy (npm refuses an
|
|
98
|
-
// unmet one; pnpm 12 fetched it from the registry).
|
|
99
|
-
const peer = new Set<string>()
|
|
100
|
-
const peerDeps = bucket(p.packageJson.peerDependencies)
|
|
101
|
-
for (const key in peerDeps) {
|
|
102
|
-
if (!Object.hasOwn(optional, key) && !Object.hasOwn(prod, key) && !Object.hasOwn(dev, key)) {
|
|
103
|
-
add(key, peerDeps[key], false, peer)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
peers.set(p.name, peer)
|
|
107
|
-
}
|
|
108
|
-
// `to` reaches `from` through the order edges so far ⇒ from → to
|
|
109
|
-
// would close a cycle.
|
|
110
|
-
const reaches = (start: string, goal: string): boolean => {
|
|
111
|
-
const seen = new Set<string>([start])
|
|
112
|
-
const stack = [start]
|
|
113
|
-
while (stack.length > 0) {
|
|
114
|
-
for (const next of order.get(stack.pop()!) ?? []) {
|
|
115
|
-
if (next === goal) return true
|
|
116
|
-
if (!seen.has(next)) {
|
|
117
|
-
seen.add(next)
|
|
118
|
-
stack.push(next)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
47
|
+
if (p.deps !== undefined) edgeSource = true
|
|
48
|
+
for (const name of p.deps ?? []) {
|
|
49
|
+
if (name !== p.name && byName.has(name)) own.add(name)
|
|
121
50
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (!own.has(peer) && !reaches(peer, p.name)) own.add(peer)
|
|
51
|
+
if (p.reach !== undefined) {
|
|
52
|
+
reachOnly.set(
|
|
53
|
+
p.name,
|
|
54
|
+
p.reach.filter((name) => name !== p.name && byName.has(name)),
|
|
55
|
+
)
|
|
128
56
|
}
|
|
57
|
+
order.set(p.name, own)
|
|
58
|
+
directDeps.set(p.name, [...own].sort())
|
|
129
59
|
}
|
|
130
|
-
for (const p of sorted) directDeps.set(p.name, [...order.get(p.name)!].sort())
|
|
131
60
|
|
|
132
|
-
// REACH and its reverse (who
|
|
61
|
+
// REACH and its reverse (who has an edge to X) are read only
|
|
133
62
|
// by the transitive questions below, which an unscoped run never asks:
|
|
134
63
|
// built on the first one, as the closures are.
|
|
135
64
|
const reachDeps = new Map<string, string[]>()
|
|
@@ -140,7 +69,7 @@ export function buildPackageGraph(
|
|
|
140
69
|
if (reach === undefined) {
|
|
141
70
|
const own = order.get(name)
|
|
142
71
|
if (own === undefined) return []
|
|
143
|
-
reach = [...new Set([...own, ...
|
|
72
|
+
reach = [...new Set([...own, ...(reachOnly.get(name) ?? [])])].sort()
|
|
144
73
|
reachDeps.set(name, reach)
|
|
145
74
|
}
|
|
146
75
|
return reach
|
|
@@ -151,7 +80,7 @@ export function buildPackageGraph(
|
|
|
151
80
|
reachBuilt = true
|
|
152
81
|
for (const p of sorted) {
|
|
153
82
|
const own = order.get(p.name)!
|
|
154
|
-
reachDeps.set(p.name, [...new Set([...own, ...
|
|
83
|
+
reachDeps.set(p.name, [...new Set([...own, ...(reachOnly.get(p.name) ?? [])])].sort())
|
|
155
84
|
}
|
|
156
85
|
for (const [name, deps] of reachDeps) {
|
|
157
86
|
for (const d of deps) {
|
|
@@ -294,124 +223,9 @@ export function buildPackageGraph(
|
|
|
294
223
|
}
|
|
295
224
|
|
|
296
225
|
return {
|
|
226
|
+
edgeSource,
|
|
297
227
|
directDeps: (name) => directDeps.get(name) ?? [],
|
|
298
228
|
transitiveDeps: makeAccessor(reachDeps, searchDeps),
|
|
299
229
|
transitiveDependents: makeAccessor(directDependents),
|
|
300
230
|
}
|
|
301
231
|
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* The spec a `catalog:` / `catalog:<name>` entry stands for in the
|
|
305
|
-
* workspace's catalog, so it is an edge by the rule its range or protocol
|
|
306
|
-
* meets (zenstack's `nuxt: "catalog:"` is `4.3.1`, which the workspace's
|
|
307
|
-
* versionless `nuxt` sample does not satisfy). A catalog the graph was not
|
|
308
|
-
* given or that lacks the key leaves the spec, which keeps its edge.
|
|
309
|
-
*/
|
|
310
|
-
function fromCatalog(p: ProjectMeta, key: string, spec: string): string {
|
|
311
|
-
if (p.catalogs === undefined || !spec.startsWith('catalog:')) return spec
|
|
312
|
-
const resolved = p.catalogs.get(spec.slice('catalog:'.length).trim() || 'default')?.[key]
|
|
313
|
-
return typeof resolved === 'string' && !resolved.startsWith('catalog:') ? resolved.trim() : spec
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function bucket(field: unknown): Readonly<Record<string, unknown>> {
|
|
317
|
-
return typeof field === 'object' && field !== null ? (field as Record<string, unknown>) : {}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// The ranges a manifest carries, in the part of npm's grammar (node-semver)
|
|
321
|
-
// where `Bun.semver.satisfies` answers as npm does: `||`-joined sets, each
|
|
322
|
-
// a hyphen range, one comparator, or two or more `<`/`>` bounds, over
|
|
323
|
-
// versions whose wildcards trail (`1.x`, `1.2.*`). Bun 1.4.2 answers true
|
|
324
|
-
// for text that is no range at all (`latest`, `npm:foo@1`, `github:a/b`,
|
|
325
|
-
// `../x`), ORs bare comparators npm ANDs (`1 2`), and reads `>x` and a
|
|
326
|
-
// leading wildcard (`x.3.1`) its own way; a spec outside this grammar
|
|
327
|
-
// counts as unmet. One corner inside it still differs (item 831): a
|
|
328
|
-
// prerelease at a `<` bound over a partial (`<3.x`, `<3.1`) or at a hyphen
|
|
329
|
-
// range's partial end, beside a lower bound naming that prerelease's own
|
|
330
|
-
// version — `3.0.0-beta` against `>=3.0.0-alpha <3.x` — Bun admits and npm
|
|
331
|
-
// does not. bun installs by the first answer and the others by the second,
|
|
332
|
-
// so neither is right everywhere; the graph takes Bun's. The playground's
|
|
333
|
-
// port (packages/vx-docs, src/playground/shim/semver.ts) is held to Bun
|
|
334
|
-
// over this grammar.
|
|
335
|
-
const W = String.raw`[xX*]`
|
|
336
|
-
const PRE = String.raw`(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`
|
|
337
|
-
const NUMERIC = String.raw`[vV]?\d+(?:\.${W}(?:\.${W})?|\.\d+(?:\.${W}|\.\d+${PRE})?)?`
|
|
338
|
-
const SINGLE = String.raw`(?:(?:[<>]=?|~>?|\^)\s*${NUMERIC}|=?\s*(?:${NUMERIC}|${W}(?:\.${W}){0,2}))`
|
|
339
|
-
const BOUND = String.raw`[<>]=?\s*${NUMERIC}`
|
|
340
|
-
const SET = String.raw`(?:${NUMERIC}\s+-\s+${NUMERIC}|${BOUND}(?:\s+${BOUND})+|${SINGLE})`
|
|
341
|
-
export const SEMVER_RANGE = new RegExp(String.raw`^(?:${SET}(?:\s*\|\|\s*${SET})*)?$`)
|
|
342
|
-
/** `<name>[@<range>]` after `npm:` or `workspace:`; the name may be scoped. */
|
|
343
|
-
const ALIAS = /^(@[^@/]+\/[^@/]+|[^@/.][^@/]*)(?:@(.*))?$/
|
|
344
|
-
const PATH_PROTOCOLS = ['file:', 'link:', 'portal:'] as const
|
|
345
|
-
const MAY_POINT_ELSEWHERE = /^(?:workspace:|npm:|file:|link:|portal:|\.\.?\/|\/)/
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* The workspace package a manifest entry resolves to, or undefined when
|
|
349
|
-
* the package manager installs it from elsewhere. `installed` is false
|
|
350
|
-
* for a peer, which the hoisted workspace copy under its key provides
|
|
351
|
-
* whatever the spec. The rule and its measurements are
|
|
352
|
-
* docs/modules/package-graph.md § Which entries are edges.
|
|
353
|
-
*/
|
|
354
|
-
function linkedTarget(
|
|
355
|
-
byName: ReadonlyMap<string, ProjectMeta>,
|
|
356
|
-
byDir: ReadonlyMap<string, ProjectMeta>,
|
|
357
|
-
): (key: string, spec: string, fromDir: string, installed: boolean) => string | undefined {
|
|
358
|
-
// A monorepo repeats a few ranges and versions thousands of times, and
|
|
359
|
-
// the grammar test and `Bun.semver` cost about a microsecond a call:
|
|
360
|
-
// unmemoised they tripled the graph build at 1000 projects × 30 deps.
|
|
361
|
-
const isRange = new Map<string, boolean>()
|
|
362
|
-
const range = (spec: string): boolean => {
|
|
363
|
-
let ok = isRange.get(spec)
|
|
364
|
-
if (ok === undefined) isRange.set(spec, (ok = SEMVER_RANGE.test(spec)))
|
|
365
|
-
return ok
|
|
366
|
-
}
|
|
367
|
-
const satisfiedBy = new Map<string, Map<string, boolean>>()
|
|
368
|
-
// A dist-tag (`latest`) is no range, and bun, npm, yarn and pnpm all
|
|
369
|
-
// installed it from the registry beside a matching workspace package.
|
|
370
|
-
const satisfies = (p: ProjectMeta, spec: string): boolean => {
|
|
371
|
-
if (spec === '*' || spec === '') return true
|
|
372
|
-
const version = p.packageJson.version
|
|
373
|
-
if (typeof version !== 'string' || !range(spec)) return false
|
|
374
|
-
let memo = satisfiedBy.get(version)
|
|
375
|
-
if (memo === undefined) satisfiedBy.set(version, (memo = new Map()))
|
|
376
|
-
let ok = memo.get(spec)
|
|
377
|
-
if (ok === undefined) memo.set(spec, (ok = Bun.semver.satisfies(version, spec)))
|
|
378
|
-
return ok
|
|
379
|
-
}
|
|
380
|
-
const named = (name: string, spec: string): string | undefined => {
|
|
381
|
-
const p = byName.get(name)
|
|
382
|
-
return p !== undefined && satisfies(p, spec) ? name : undefined
|
|
383
|
-
}
|
|
384
|
-
const atPath = (fromDir: string, rel: string): string | undefined =>
|
|
385
|
-
byDir.get(path.resolve(fromDir, rel))?.name
|
|
386
|
-
return (key, spec, fromDir, installed) => {
|
|
387
|
-
if (spec === 'workspace:*') return named(key, '*')
|
|
388
|
-
// Only a protocol or a path points a key at another package; every
|
|
389
|
-
// other spec (a range, a tag, a URL) is decided by the key.
|
|
390
|
-
if (!MAY_POINT_ELSEWHERE.test(spec)) {
|
|
391
|
-
const local = byName.get(key)
|
|
392
|
-
if (local === undefined) return undefined
|
|
393
|
-
// A catalog entry its workspace's catalog did not resolve keeps the edge.
|
|
394
|
-
if (!installed || spec.startsWith('catalog:')) return key
|
|
395
|
-
return satisfies(local, spec) ? key : undefined
|
|
396
|
-
}
|
|
397
|
-
if (spec.startsWith('workspace:')) {
|
|
398
|
-
const rest = spec.slice('workspace:'.length)
|
|
399
|
-
if (rest === '^' || rest === '~') return named(key, '*')
|
|
400
|
-
if (range(rest)) return named(key, rest)
|
|
401
|
-
const alias = ALIAS.exec(rest)
|
|
402
|
-
if (alias !== null) return named(alias[1]!, alias[2] ?? '*')
|
|
403
|
-
return atPath(fromDir, rest)
|
|
404
|
-
}
|
|
405
|
-
if (spec.startsWith('npm:')) {
|
|
406
|
-
// bun links a satisfied alias; npm and yarn fetch it from the
|
|
407
|
-
// registry. The edge is the spare one, as above.
|
|
408
|
-
const alias = ALIAS.exec(spec.slice('npm:'.length))
|
|
409
|
-
return alias === null ? undefined : named(alias[1]!, alias[2] ?? '*')
|
|
410
|
-
}
|
|
411
|
-
for (const protocol of PATH_PROTOCOLS) {
|
|
412
|
-
if (spec.startsWith(protocol)) return atPath(fromDir, spec.slice(protocol.length))
|
|
413
|
-
}
|
|
414
|
-
// A bare `./`, `../` or `/` path is a directory to bun, npm and pnpm.
|
|
415
|
-
return atPath(fromDir, spec)
|
|
416
|
-
}
|
|
417
|
-
}
|