@vzn/vx 0.0.636 → 0.0.638
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 +246 -213
- package/docs/cli.md +791 -705
- package/docs/execution.md +68 -76
- package/docs/features.md +47 -42
- package/docs/patterns.md +26 -25
- package/docs/schema.md +392 -205
- 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 +4 -4
- package/schemas/show.json +5 -3
- package/schemas/why.json +15 -1
- package/skills/vx/SKILL.md +28 -13
- package/src/bin.ts +30 -9
- package/src/cache/archive.ts +11 -0
- package/src/cache/cache.ts +124 -53
- package/src/cache/chained-cache.ts +4 -0
- package/src/cache/git-inputs.ts +277 -63
- package/src/cache/index.ts +15 -1
- package/src/cache/inputs.ts +102 -36
- package/src/cache/key-fold.ts +8 -7
- package/src/cache/layer.ts +15 -18
- package/src/cache/layered-cache.ts +20 -102
- package/src/cache/plugin-cache.ts +248 -0
- package/src/cache/policy.ts +32 -27
- package/src/cache/run-history.ts +104 -14
- package/src/cache/run-store.ts +77 -0
- package/src/cache/schema.ts +58 -31
- package/src/cache/scoped-cache.ts +237 -0
- package/src/cli/cache.ts +173 -34
- package/src/cli/completions.ts +11 -7
- package/src/cli/core-alias.ts +1 -1
- package/src/cli/foreign-flags.ts +13 -12
- package/src/cli/help.ts +110 -70
- package/src/cli/index.ts +67 -24
- package/src/cli/info.ts +43 -27
- package/src/cli/init.ts +115 -407
- package/src/cli/last.ts +157 -9
- package/src/cli/lock.ts +356 -122
- package/src/cli/plan-format.ts +7 -5
- package/src/cli/plugin-commands.ts +81 -14
- package/src/cli/plugin-templates.ts +11 -11
- package/src/cli/run.ts +116 -160
- package/src/cli/select.ts +50 -47
- package/src/cli/show.ts +83 -59
- package/src/cli/task-verb.ts +6 -13
- package/src/cli/upgrade.ts +415 -149
- package/src/cli/watch-cycle.ts +9 -2
- package/src/cli/watch-filter.ts +39 -61
- package/src/cli/watch-fs.ts +29 -16
- package/src/cli/watch-judge.ts +18 -4
- package/src/cli/watch-set.ts +16 -42
- package/src/cli/watch.ts +227 -230
- package/src/cli/why.ts +38 -18
- package/src/cli/workspace-config.ts +33 -70
- 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 +27 -7
- package/src/graph/task-graph.ts +76 -24
- package/src/handoff.ts +169 -0
- package/src/index.ts +35 -58
- package/src/orchestrator/affected-tasks.ts +15 -8
- 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 +147 -69
- package/src/orchestrator/failure-mode.ts +19 -10
- package/src/orchestrator/fingerprint-watch.ts +18 -15
- package/src/orchestrator/forecast.ts +10 -2
- package/src/orchestrator/framed-output.ts +9 -5
- package/src/orchestrator/graph-stage.ts +5 -5
- package/src/orchestrator/history.ts +126 -30
- package/src/orchestrator/hit-restore.ts +15 -3
- package/src/orchestrator/index.ts +20 -5
- package/src/orchestrator/local-shortcircuit.ts +2 -1
- package/src/orchestrator/lockfile-claim.ts +79 -20
- package/src/orchestrator/logger.ts +80 -41
- package/src/orchestrator/metrics.ts +78 -9
- 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 +100 -66
- package/src/orchestrator/plugin.ts +173 -67
- package/src/orchestrator/prepare.ts +163 -78
- package/src/orchestrator/projects.ts +105 -99
- package/src/orchestrator/remote-prefetch.ts +11 -6
- 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-records.ts +1 -1
- package/src/orchestrator/run-report.ts +9 -9
- package/src/orchestrator/run-settings.ts +48 -0
- package/src/orchestrator/run.ts +244 -119
- 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 +49 -7
- package/src/orchestrator/summary.ts +115 -106
- package/src/orchestrator/task-hash.ts +12 -83
- package/src/orchestrator/telemetry.ts +4 -4
- package/src/settings.ts +428 -0
- package/src/{orchestrator → util}/colors.ts +5 -0
- package/src/util/config-frame.ts +31 -2
- package/src/util/errors.ts +33 -1
- package/src/util/essential-env.ts +31 -0
- package/src/util/hide-env.ts +107 -0
- package/src/util/index.ts +16 -3
- package/src/util/paths.ts +10 -0
- package/src/util/refusal-notice.ts +111 -0
- package/src/util/report.ts +31 -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/ui.ts +186 -0
- package/src/util/ulid.ts +1 -1
- package/src/util/verbs.ts +0 -15
- package/src/workspace/affected.ts +84 -232
- 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 +65 -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 +307 -215
- 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 +363 -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,41 +1,43 @@
|
|
|
1
|
-
// vx
|
|
1
|
+
// .vx/projects.lock.json — frozen resolved-config lockfile.
|
|
2
2
|
//
|
|
3
|
-
// `vx lock`
|
|
4
|
-
// environment and freezes the resolved objects
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// docs/design/config-lock-2026-06.md.
|
|
3
|
+
// `vx lock` finds every project's vx.config.*, evaluates each in the
|
|
4
|
+
// CURRENT environment and freezes the resolved objects, by directory, into
|
|
5
|
+
// `.vx/projects.lock.json` at the workspace root. Every run loads configs from
|
|
6
|
+
// it and never evaluates one (owner, 2026-10-14); only `vx lock` writes
|
|
7
|
+
// it. See docs/design/config-lock-2026-06.md.
|
|
9
8
|
//
|
|
10
9
|
// Verification asymmetry (deliberate):
|
|
11
|
-
// -
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// that file hashes cannot see.
|
|
10
|
+
// - runs TRUST the lock: no staleness check, no schema validation,
|
|
11
|
+
// eval-free.
|
|
12
|
+
// - `vx lock --check` AUDITS it: full re-evaluation + deep equality.
|
|
15
13
|
|
|
14
|
+
import { readFileSync } from 'node:fs'
|
|
16
15
|
import { rename, rm } from 'node:fs/promises'
|
|
17
16
|
import path from 'node:path'
|
|
18
17
|
import type { ProjectConfig } from '../config.js'
|
|
19
18
|
import { relPosix, UserError, xxh3 } from '../util/index.js'
|
|
20
19
|
import { VERSION } from '../version.js'
|
|
21
|
-
import { CONFIG_EVAL_VERSION, type ConfigEvalStore } from './config-cache.js'
|
|
22
20
|
import { validateProjectConfig } from './config-schema.js'
|
|
21
|
+
import { PROJECT_CONFIG_FILENAMES } from './workspace.js'
|
|
23
22
|
|
|
24
|
-
export const LOCKFILE_NAME = 'vx
|
|
25
|
-
export const LOCKFILE_VERSION =
|
|
23
|
+
export const LOCKFILE_NAME = '.vx/projects.lock.json'
|
|
24
|
+
export const LOCKFILE_VERSION = 3
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/** The resolved (post-evaluation) config object, JSON-normalized. */
|
|
26
|
+
/** One project as the lock holds it. */
|
|
27
|
+
export interface LockedProject {
|
|
28
|
+
/** The config file's name in the directory (`vx.config.ts`). */
|
|
29
|
+
file: string
|
|
30
|
+
/** Its resolved (post-evaluation) config, JSON-normalized. */
|
|
33
31
|
config: ProjectConfig
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
export interface Lockfile {
|
|
37
35
|
version: number
|
|
38
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Root-relative POSIX directory (`.` for the root) → its project. Keyed
|
|
38
|
+
* by directory, so a name a plugin gives never invalidates the lock.
|
|
39
|
+
*/
|
|
40
|
+
projects: Record<string, LockedProject>
|
|
39
41
|
/** Written by `writeLockfile` from the bytes; never read off the object. */
|
|
40
42
|
validated?: string
|
|
41
43
|
}
|
|
@@ -44,12 +46,38 @@ export function lockfilePath(root: string): string {
|
|
|
44
46
|
return path.join(root, LOCKFILE_NAME)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
/** What a
|
|
48
|
-
export const
|
|
49
|
-
"--frozen requires vx-lock.json at the workspace root — run 'vx lock' and commit it"
|
|
49
|
+
/** What a run says when a crash came from a hand-broken lock entry (M, 2026-10-14). */
|
|
50
|
+
export const LOCK_INVALID = `${LOCKFILE_NAME} is invalid, run \`vx lock\``
|
|
50
51
|
|
|
51
52
|
/**
|
|
52
|
-
*
|
|
53
|
+
* Whether the lock holds an entry `vx lock` could not have written. Runs
|
|
54
|
+
* validate no entry, so a hand-broken one crashed deep in a run with a
|
|
55
|
+
* raw TypeError; asked only on that crash's path, so a run pays nothing.
|
|
56
|
+
*/
|
|
57
|
+
export function lockEntryBroken(root: string): boolean {
|
|
58
|
+
let projects: unknown
|
|
59
|
+
try {
|
|
60
|
+
projects = (JSON.parse(readFileSync(lockfilePath(root), 'utf8')) as { projects?: unknown })
|
|
61
|
+
.projects
|
|
62
|
+
} catch {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
if (typeof projects !== 'object' || projects === null) return false
|
|
66
|
+
for (const [dir, entry] of Object.entries(projects)) {
|
|
67
|
+
try {
|
|
68
|
+
validateProjectConfig((entry as LockedProject).config, `${LOCKFILE_NAME}: "${dir}"`, null)
|
|
69
|
+
} catch {
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** What a run says when there is no lock to read. */
|
|
77
|
+
export const NO_LOCK = 'no lock: run `vx lock`'
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Read and shape-validate `.vx/projects.lock.json`. Returns `null` when no lock
|
|
53
81
|
* exists (the common case — the feature is opt-in). The lock is a
|
|
54
82
|
* user-editable file, so this is a system boundary: malformed content
|
|
55
83
|
* throws a `UserError` instead of crashing deeper in the run.
|
|
@@ -84,68 +112,57 @@ export async function readLockfile(root: string): Promise<Lockfile | null> {
|
|
|
84
112
|
}
|
|
85
113
|
// Same trap, and this one is worse than a bad message: an ARRAY of entries is
|
|
86
114
|
// ACCEPTED, because `Object.entries` happily walks it — yielding a project
|
|
87
|
-
//
|
|
88
|
-
// lock that declares none of them.
|
|
115
|
+
// at directory "0".
|
|
89
116
|
if (typeof lock.projects !== 'object' || lock.projects === null || Array.isArray(lock.projects)) {
|
|
90
117
|
throw new UserError(`${LOCKFILE_NAME}: \`projects\` must be an object — re-run \`vx lock\``)
|
|
91
118
|
}
|
|
92
|
-
for (const [
|
|
93
|
-
const
|
|
119
|
+
for (const [dir, entry] of Object.entries(lock.projects)) {
|
|
120
|
+
const { file, config } = (entry ?? {}) as { file?: unknown; config?: unknown }
|
|
94
121
|
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
typeof
|
|
99
|
-
|
|
122
|
+
typeof entry !== 'object' ||
|
|
123
|
+
entry === null ||
|
|
124
|
+
Array.isArray(entry) ||
|
|
125
|
+
typeof file !== 'string' ||
|
|
126
|
+
!PROJECT_CONFIG_FILENAMES.includes(file) ||
|
|
127
|
+
typeof config !== 'object' ||
|
|
128
|
+
config === null ||
|
|
129
|
+
Array.isArray(config)
|
|
100
130
|
) {
|
|
101
|
-
throw new UserError(`${LOCKFILE_NAME}: entry for "${
|
|
131
|
+
throw new UserError(`${LOCKFILE_NAME}: entry for "${dir}" is malformed — re-run \`vx lock\``)
|
|
102
132
|
}
|
|
103
133
|
}
|
|
104
|
-
lockDigests.set(parsed, xxh3(text).toString(16))
|
|
105
|
-
const stamped = VALIDATED_LINE.exec(text)
|
|
106
|
-
if (stamped !== null && stamped[1] === validatedDigest(text.slice(0, stamped.index) + '\n}')) {
|
|
107
|
-
selfValidated.add(parsed)
|
|
108
|
-
}
|
|
109
134
|
return parsed as Lockfile
|
|
110
135
|
}
|
|
111
136
|
|
|
112
|
-
/** The digest of the bytes each lock `readLockfile` returned was parsed from. */
|
|
113
|
-
const lockDigests = new WeakMap<object, string>()
|
|
114
|
-
|
|
115
137
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* is left out and does not churn a committed lock), as the last field. A lock
|
|
120
|
-
* whose digest matches is validated already, and a cold `--frozen` run
|
|
121
|
-
* skips the ~30 ms of validating 1,090 entries before its first task. An
|
|
122
|
-
* edit, a merge, or another vx version leaves it unmatched, and the
|
|
123
|
-
* entries are validated as before.
|
|
138
|
+
* `vx lock` evaluates and validates every config, then writes a digest of
|
|
139
|
+
* the bytes it wrote, under vx's version, as the last field: the lock's
|
|
140
|
+
* record of which vx validated it. No reader consults it.
|
|
124
141
|
*/
|
|
125
|
-
const VALIDATED_LINE = /,\n {2}"validated": "([0-9a-f]+)"\n\}\n?$/
|
|
126
|
-
const selfValidated = new WeakSet<object>()
|
|
127
|
-
|
|
128
142
|
function validatedDigest(body: string): string {
|
|
129
|
-
return xxh3(`vx-lock-valid-
|
|
143
|
+
return xxh3(`vx-lock-valid-v3\0${VERSION}\0${body}`).toString(16)
|
|
130
144
|
}
|
|
131
145
|
|
|
132
146
|
export async function writeLockfile(root: string, lock: Lockfile): Promise<void> {
|
|
133
147
|
// Written beside its name and renamed over it: `Bun.write` truncates in
|
|
134
|
-
// place, so a
|
|
148
|
+
// place, so a run, or `vx watch` woken by the write
|
|
135
149
|
// itself (item 971), could read an empty or half-written lock and fail
|
|
136
150
|
// on "not valid JSON" (item 972).
|
|
137
151
|
const file = lockfilePath(root)
|
|
138
152
|
const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`
|
|
139
153
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
// Minified: the file only vx reads, and it loads faster than pretty
|
|
155
|
+
// JSON (measured, 10k projects: 15.6 ms against 22.5 ms). `vx.lock.json`
|
|
156
|
+
// beside each config is the copy for people.
|
|
157
|
+
const body = JSON.stringify({ version: lock.version, projects: lock.projects })
|
|
158
|
+
await Bun.write(tmp, `${body.slice(0, -1)},"validated":"${validatedDigest(body)}"}\n`)
|
|
142
159
|
await rename(tmp, file)
|
|
143
160
|
} catch (err) {
|
|
144
161
|
// Best effort: where the write was refused the temp cannot be named
|
|
145
162
|
// either (ENOTDIR), and that must not replace the refusal itself.
|
|
146
163
|
await rm(tmp, { force: true }).catch(() => {})
|
|
147
164
|
// The refusal names the file the user knows: a read-only checkout read
|
|
148
|
-
// `open '
|
|
165
|
+
// `open '…/.vx/projects.lock.json.tmp-…'` where it had read the lock's own name.
|
|
149
166
|
const e = err as NodeJS.ErrnoException
|
|
150
167
|
if (typeof e.message === 'string') e.message = e.message.split(tmp).join(file)
|
|
151
168
|
if (e.path === tmp) e.path = file
|
|
@@ -155,93 +172,32 @@ export async function writeLockfile(root: string, lock: Lockfile): Promise<void>
|
|
|
155
172
|
|
|
156
173
|
/**
|
|
157
174
|
* Run-time config load from the lock: return the FROZEN resolved config
|
|
158
|
-
* for each project, in order. No evaluation happens
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* back to evaluation would break frozen-env semantics.
|
|
164
|
-
*
|
|
165
|
-
* `store` remembers which entries of which lock bytes this validator
|
|
166
|
-
* accepted, beside the config evaluations and keyed like them (vx's and
|
|
167
|
-
* Bun's versions): validating 1,000 entries was ~30 ms of every warm
|
|
168
|
-
* `--frozen` run, for a verdict that cannot change until the bytes or vx
|
|
169
|
-
* do.
|
|
175
|
+
* for each project, in order. No evaluation happens, and no staleness
|
|
176
|
+
* check or schema validation either: the lock's contract is "what runs is
|
|
177
|
+
* what was locked", `vx lock` validated every entry it wrote, and
|
|
178
|
+
* `vx lock --check` re-evaluates them all. An unlocked project is a hard
|
|
179
|
+
* error; a run never evaluates.
|
|
170
180
|
*/
|
|
171
|
-
export
|
|
181
|
+
export function frozenProjectConfigs(
|
|
172
182
|
lock: Lockfile,
|
|
173
|
-
metas: ReadonlyArray<{ name: string; configPath: string }>,
|
|
183
|
+
metas: ReadonlyArray<{ name: string; dir: string; configPath: string }>,
|
|
174
184
|
root: string,
|
|
175
|
-
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
const rel = relPosix(root, meta.configPath)
|
|
185
|
+
): ProjectConfig[] {
|
|
186
|
+
return metas.map((meta) => {
|
|
187
|
+
const dir = lockKey(root, meta.dir)
|
|
179
188
|
// Own entries only: `constructor` read Object's, "locked" at undefined (D-152).
|
|
180
|
-
const entry = Object.hasOwn(lock.projects,
|
|
181
|
-
if (entry
|
|
182
|
-
// A renamed or moved config: "has no entry for a" named an entry the
|
|
183
|
-
// lock holds, under its old path (D-88).
|
|
184
|
-
throw new UserError(
|
|
185
|
-
`${LOCKFILE_NAME} locks "${meta.name}" at ${entry.configPath}, but its config is ${rel} now — ` +
|
|
186
|
-
`run \`vx lock\` to refresh, or delete ${LOCKFILE_NAME}`,
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
if (!entry) {
|
|
189
|
+
const entry = Object.hasOwn(lock.projects, dir) ? lock.projects[dir] : undefined
|
|
190
|
+
if (entry === undefined) {
|
|
190
191
|
throw new UserError(
|
|
191
|
-
`${LOCKFILE_NAME} has no entry for "${meta.name}" (${
|
|
192
|
-
|
|
192
|
+
`${LOCKFILE_NAME} has no entry for "${meta.name}" (${relPosix(root, meta.configPath)}) — ` +
|
|
193
|
+
'run `vx lock`',
|
|
193
194
|
)
|
|
194
195
|
}
|
|
195
|
-
return entry
|
|
196
|
+
return entry.config
|
|
196
197
|
})
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// in the file solely for --check's fast file-changed reporting.
|
|
203
|
-
// The lock is hand-editable; the stored config crosses the same
|
|
204
|
-
// boundary a freshly evaluated one does.
|
|
205
|
-
const digest = store === undefined ? undefined : lockDigests.get(lock)
|
|
206
|
-
const prefix =
|
|
207
|
-
digest === undefined
|
|
208
|
-
? undefined
|
|
209
|
-
: `vx-lock-valid-v${CONFIG_EVAL_VERSION}\0${VERSION}\0${Bun.version}\0${digest}`
|
|
210
|
-
// One row for a lock whose every entry was accepted: a warm `--all` run
|
|
211
|
-
// asked for 1,090 per-project rows in one `IN` query, ~10 ms of its
|
|
212
|
-
// config load, where the eval-cache path it replaces is cheaper (X-181).
|
|
213
|
-
// A scoped run still validates, and remembers, only what it loads.
|
|
214
|
-
if (
|
|
215
|
-
selfValidated.has(lock) ||
|
|
216
|
-
(prefix !== undefined && store !== undefined && store.getConfigEval(prefix) !== null)
|
|
217
|
-
) {
|
|
218
|
-
return entries.map((e) => e.config)
|
|
219
|
-
}
|
|
220
|
-
const keys = prefix === undefined ? undefined : metas.map((m) => `${prefix}\0${m.name}`)
|
|
221
|
-
const known =
|
|
222
|
-
keys === undefined || store === undefined
|
|
223
|
-
? new Map<string, string>()
|
|
224
|
-
: store.getConfigEvals !== undefined
|
|
225
|
-
? store.getConfigEvals(keys)
|
|
226
|
-
: new Map(keys.flatMap((k) => (store.getConfigEval(k) === null ? [] : [[k, '1'] as const])))
|
|
227
|
-
const accepted: Array<readonly [string, string]> = []
|
|
228
|
-
for (let i = 0; i < entries.length; i++) {
|
|
229
|
-
const key = keys?.[i]
|
|
230
|
-
if (key !== undefined && known.has(key)) continue
|
|
231
|
-
validateProjectConfig(entries[i]!.config, `${LOCKFILE_NAME} (${metas[i]!.name})`)
|
|
232
|
-
if (key !== undefined) accepted.push([key, '1'])
|
|
233
|
-
}
|
|
234
|
-
// Every meta found its entry above, so as many distinct names as the
|
|
235
|
-
// lock holds is every entry of it.
|
|
236
|
-
if (
|
|
237
|
-
prefix !== undefined &&
|
|
238
|
-
new Set(metas.map((m) => m.name)).size === Object.keys(lock.projects).length
|
|
239
|
-
) {
|
|
240
|
-
accepted.push([prefix, '1'])
|
|
241
|
-
}
|
|
242
|
-
if (store !== undefined && accepted.length > 0) {
|
|
243
|
-
if (store.putConfigEvals !== undefined) store.putConfigEvals(accepted)
|
|
244
|
-
else for (const [k, v] of accepted) store.putConfigEval(k, v)
|
|
245
|
-
}
|
|
246
|
-
return entries.map((e) => e.config)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A project directory as the lock keys it: root-relative POSIX, `.` for the root. */
|
|
201
|
+
export function lockKey(root: string, dir: string): string {
|
|
202
|
+
return relPosix(root, dir) || '.'
|
|
247
203
|
}
|