@vzn/vx-migrate 0.0.651 → 0.0.653
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 +61 -52
- package/package.json +2 -2
- package/src/migrate-scripts.ts +13 -7
- package/src/migrate-turbo.ts +8 -1
- package/src/migrate.ts +102 -18
- package/src/nx/index.ts +61 -11
- package/src/nx/nx-inputs.ts +48 -24
- package/src/nx/nx-map.ts +204 -20
- package/src/nx/nx-outputs.ts +6 -1
- package/src/nx/nx-upstream.ts +11 -11
- package/src/nx-cache/index.ts +42 -28
- package/src/packages.ts +51 -1
- package/src/remote-refusal.ts +49 -4
- package/src/shared-outputs.ts +86 -25
- package/src/tracked-outputs.ts +59 -20
- package/src/turbo/index.ts +2 -1
- package/src/turbo/turbo-map.ts +165 -29
- package/src/turbo-cache/index.ts +71 -40
- package/src/turbo-cache/oidc.ts +15 -7
- package/src/vite-task/vite-task-map.ts +3 -2
- package/src/workspace-notes.ts +19 -8
- package/src/workspace-plugins.ts +11 -3
package/src/remote-refusal.ts
CHANGED
|
@@ -1,14 +1,57 @@
|
|
|
1
1
|
/** How long the body's first chunk is waited for: a refusal is not held on a body that never comes. */
|
|
2
2
|
const BODY_WAIT_MS = 1_000
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The most of the first chunk read: a token that starts within the line's
|
|
6
|
+
* 200 characters must end within it to be hidden whole (a JWT is ~1 KB).
|
|
7
|
+
*/
|
|
8
|
+
const READ_BYTES = 4096
|
|
9
|
+
|
|
10
|
+
/** Shorter values are not hidden, as core masks none under 6 characters: each would blank common words. */
|
|
11
|
+
const MIN_TOKEN_CHARS = 6
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `text` with each of `tokens` written `***`: a server that quoted a
|
|
15
|
+
* request's Authorization header printed the token to the CI log and
|
|
16
|
+
* cache.db, as core masks only secret-named variables.
|
|
17
|
+
*/
|
|
18
|
+
export function hideTokens(text: string, tokens: Iterable<string | undefined>): string {
|
|
19
|
+
let out = text
|
|
20
|
+
for (const token of tokens)
|
|
21
|
+
if (token !== undefined && token.length >= MIN_TOKEN_CHARS) out = out.replaceAll(token, '***')
|
|
22
|
+
return out
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** `work`, each error it throws with `hide` applied: a thrown fetch error can quote a request's header. */
|
|
26
|
+
export async function hidingErrors<T>(
|
|
27
|
+
work: Promise<T>,
|
|
28
|
+
hide: (text: string) => string,
|
|
29
|
+
): Promise<T> {
|
|
30
|
+
try {
|
|
31
|
+
return await work
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
34
|
+
const hidden = hide(message)
|
|
35
|
+
if (hidden === message) throw err
|
|
36
|
+
// The code is how core groups a failure (a reset connection), and a retry reads it.
|
|
37
|
+
const code = (err as { code?: unknown } | null)?.code
|
|
38
|
+
throw Object.assign(new Error(hidden), code === undefined ? {} : { code })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
4
42
|
/**
|
|
5
43
|
* A remote's refusal, named by its status and its own words: "HTTP 500"
|
|
6
44
|
* alone said nothing, and a batch query Vercel refused with 400 "Too many
|
|
7
45
|
* hashes. Limit is 100." read as no answer and was never seen (main
|
|
8
|
-
* f71b377, 2026-10-23). One line of the body, bounded: it is remote text
|
|
9
|
-
*
|
|
46
|
+
* f71b377, 2026-10-23). One line of the body, bounded: it is remote text,
|
|
47
|
+
* each token `hide` knows written `***` before the cut, so none is left in
|
|
48
|
+
* part. Shared by `turboCache()` and `nxCache()`.
|
|
10
49
|
*/
|
|
11
|
-
export async function refusal(
|
|
50
|
+
export async function refusal(
|
|
51
|
+
res: Response,
|
|
52
|
+
hide: (text: string) => string,
|
|
53
|
+
what = '',
|
|
54
|
+
): Promise<Error> {
|
|
12
55
|
let said = ''
|
|
13
56
|
try {
|
|
14
57
|
const reader = res.body?.getReader()
|
|
@@ -22,7 +65,9 @@ export async function refusal(res: Response, what = ''): Promise<Error> {
|
|
|
22
65
|
]).finally(() => clearTimeout(timer))
|
|
23
66
|
// quiet: read no further; a cancel that fails has nothing left to free
|
|
24
67
|
void reader.cancel().catch(() => undefined)
|
|
25
|
-
said = new TextDecoder().decode(first?.value?.subarray(0,
|
|
68
|
+
said = hide(new TextDecoder().decode(first?.value?.subarray(0, READ_BYTES)))
|
|
69
|
+
.replace(/\s+/g, ' ')
|
|
70
|
+
.trim()
|
|
26
71
|
}
|
|
27
72
|
} catch {
|
|
28
73
|
// quiet: the status alone still names the refusal
|
package/src/shared-outputs.ts
CHANGED
|
@@ -179,8 +179,8 @@ interface Cached {
|
|
|
179
179
|
function cachedOutputs(t: GeneratedTask, index: number): Cached | null {
|
|
180
180
|
const task = t.task
|
|
181
181
|
if (task === null) return null
|
|
182
|
-
const
|
|
183
|
-
const files =
|
|
182
|
+
const outputs = task['outputs'] as { files?: unknown } | undefined
|
|
183
|
+
const files = outputs?.files
|
|
184
184
|
if (!Array.isArray(files)) return null
|
|
185
185
|
// Where a task's outputs might land is its positive globs, as core reads
|
|
186
186
|
// them: a `!` compiled as a glob is true of every other path.
|
|
@@ -267,9 +267,7 @@ export function resolveSharedOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
267
267
|
for (const { k, gb } of pairs) {
|
|
268
268
|
if (k.back.has(gb)) continue
|
|
269
269
|
k.back.add(gb)
|
|
270
|
-
;(tasks[k.index]!.task!['
|
|
271
|
-
`!${gb}`,
|
|
272
|
-
)
|
|
270
|
+
;(tasks[k.index]!.task!['outputs'] as { files: string[] }).files.push(`!${gb}`)
|
|
273
271
|
}
|
|
274
272
|
kept.push(c)
|
|
275
273
|
continue
|
|
@@ -289,17 +287,18 @@ export function resolveSharedOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
289
287
|
sharedFiles.every((g) => isLiteralPattern(g) && TSBUILDINFO.test(g))
|
|
290
288
|
) {
|
|
291
289
|
c.files = c.files.filter((g) => !sharedFiles.includes(g))
|
|
292
|
-
const outputs =
|
|
290
|
+
const outputs = t.task!['outputs'] as { files: string[] }
|
|
293
291
|
outputs.files = outputs.files.filter((g) => !sharedFiles.includes(g))
|
|
294
292
|
kept.push(c)
|
|
295
293
|
continue
|
|
296
294
|
}
|
|
297
295
|
dropped.add(c.index)
|
|
298
296
|
const shared = c.files.find((gb) => clash.files.some((ga) => hits(clash, ga, gb)))!
|
|
299
|
-
|
|
297
|
+
const theirs = clash.files.find((ga) => hits(clash, ga, shared))!
|
|
298
|
+
uncache(t.task!)
|
|
300
299
|
t.todos.push(
|
|
301
|
-
`declares the output ${
|
|
302
|
-
"
|
|
300
|
+
`declares the output ${sharing(shared, theirs, JSON.stringify(clash.name))} — ` +
|
|
301
|
+
"vx cleans a task's outputs before it runs and before a restore, so two " +
|
|
303
302
|
"cached tasks on one path would delete each other's work; this one runs uncached. " +
|
|
304
303
|
'Give it its own output path to cache it.',
|
|
305
304
|
)
|
|
@@ -308,6 +307,13 @@ export function resolveSharedOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
308
307
|
return tasks
|
|
309
308
|
}
|
|
310
309
|
|
|
310
|
+
/** `"<mine>" that <owner> also declares`, or both globs when they only overlap. */
|
|
311
|
+
function sharing(mine: string, theirs: string, owner: string): string {
|
|
312
|
+
return mine === theirs
|
|
313
|
+
? `${JSON.stringify(mine)} that ${owner} also declares`
|
|
314
|
+
: `${JSON.stringify(mine)}, which overlaps the output ${JSON.stringify(theirs)} that ${owner} declares`
|
|
315
|
+
}
|
|
316
|
+
|
|
311
317
|
/**
|
|
312
318
|
* Two globs can match one path only when one's literal prefix is a
|
|
313
319
|
* directory of the other's (`packages/*\/dist/**` and `packages/a/dist/**`),
|
|
@@ -369,10 +375,18 @@ export const isKeyedGroup = (t: { readonly task: Record<string, unknown> | null
|
|
|
369
375
|
* alone, its incomplete key still let a dependant hit stale.
|
|
370
376
|
*/
|
|
371
377
|
export function unkey(task: Record<string, unknown>): void {
|
|
372
|
-
|
|
378
|
+
uncache(task)
|
|
373
379
|
delete task['inputs']
|
|
374
380
|
}
|
|
375
381
|
|
|
382
|
+
/** No cache: `outputs` and `inputs.stability` go with it, as they mean nothing without. */
|
|
383
|
+
export function uncache(task: Record<string, unknown>): void {
|
|
384
|
+
delete task['cache']
|
|
385
|
+
delete task['outputs']
|
|
386
|
+
const inputs = task['inputs'] as Record<string, unknown> | undefined
|
|
387
|
+
if (inputs !== undefined) delete inputs['stability']
|
|
388
|
+
}
|
|
389
|
+
|
|
376
390
|
/** A task's `inputs` block, when it has one with a `files` list. */
|
|
377
391
|
function inputsOf(t: GeneratedTask): { files: string[]; workspaceFiles?: unknown } | undefined {
|
|
378
392
|
const inputs = t.task?.['inputs'] as { files?: unknown } | undefined
|
|
@@ -391,10 +405,17 @@ function inputsOf(t: GeneratedTask): { files: string[]; workspaceFiles?: unknown
|
|
|
391
405
|
* files git tracks, and a build's outputs are not, so the adopted tool
|
|
392
406
|
* never keyed them either. A literal input another task writes cannot be
|
|
393
407
|
* taken back (core refuses a `!` over a literal input), so that task runs
|
|
394
|
-
* uncached with a todo.
|
|
395
|
-
*
|
|
408
|
+
* uncached with a todo. Under `walksGlobs` (Turbo) so does a glob that
|
|
409
|
+
* names the output's place (`gen/**` beside a `gen` writing `gen/**`)
|
|
410
|
+
* when no `dependsOn` path reaches the writer: Turbo walks an explicit
|
|
411
|
+
* glob on disk, and with no edge the writer's key does not reach the
|
|
412
|
+
* reader's either, so taken back it replayed the old output. A glob led
|
|
413
|
+
* by `**` walks the whole package, as the default does, and keeps the
|
|
414
|
+
* `!`. Nx hashes no gitignored file, and its mapper drops an input under
|
|
415
|
+
* an untracked output before this pass. A reader whose `!` hides a
|
|
416
|
+
* committed file is the tracked pass's (`spareTrackedOutputs`).
|
|
396
417
|
*/
|
|
397
|
-
export function excludeSiblingOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
418
|
+
export function excludeSiblingOutputs(tasks: GeneratedTask[], walksGlobs = false): GeneratedTask[] {
|
|
398
419
|
const writers = tasks.flatMap((t, index) => {
|
|
399
420
|
const c = cachedOutputs(t, index)
|
|
400
421
|
return c === null ? [] : [c]
|
|
@@ -421,6 +442,18 @@ export function excludeSiblingOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
421
442
|
)
|
|
422
443
|
return
|
|
423
444
|
}
|
|
445
|
+
const named = walksGlobs
|
|
446
|
+
? read.find((g) => !g.replace(/^(\.\/)+/, '').startsWith('**'))
|
|
447
|
+
: undefined
|
|
448
|
+
if (named !== undefined && !isKeyedGroup(t) && !reaches(tasks, index, w.index)) {
|
|
449
|
+
unkey(t.task!)
|
|
450
|
+
t.todos.push(
|
|
451
|
+
`reads ${JSON.stringify(named)}, which ${JSON.stringify(w.name)} writes and no ` +
|
|
452
|
+
'dependsOn reaches — vx keys a task only on files no other task writes, so it runs ' +
|
|
453
|
+
'uncached; add the edge or read the source instead in a vx.config to cache it',
|
|
454
|
+
)
|
|
455
|
+
return
|
|
456
|
+
}
|
|
424
457
|
inputs.files.push(`!${out}`)
|
|
425
458
|
}
|
|
426
459
|
}
|
|
@@ -428,6 +461,26 @@ export function excludeSiblingOutputs(tasks: GeneratedTask[]): GeneratedTask[] {
|
|
|
428
461
|
return tasks
|
|
429
462
|
}
|
|
430
463
|
|
|
464
|
+
/** Does a same-project `dependsOn` path lead from task `from` to task `to`? */
|
|
465
|
+
function reaches(tasks: readonly GeneratedTask[], from: number, to: number): boolean {
|
|
466
|
+
const byName = new Map(tasks.map((t, i) => [t.name, i]))
|
|
467
|
+
const seen = new Set([from])
|
|
468
|
+
const queue = [from]
|
|
469
|
+
for (let i = queue.shift(); i !== undefined; i = queue.shift()) {
|
|
470
|
+
const deps = tasks[i]!.task?.['dependsOn']
|
|
471
|
+
if (!Array.isArray(deps)) continue
|
|
472
|
+
for (const d of deps) {
|
|
473
|
+
const next = typeof d === 'string' ? byName.get(d) : undefined
|
|
474
|
+
if (next === to) return true
|
|
475
|
+
if (next !== undefined && !seen.has(next)) {
|
|
476
|
+
seen.add(next)
|
|
477
|
+
queue.push(next)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return false
|
|
482
|
+
}
|
|
483
|
+
|
|
431
484
|
/**
|
|
432
485
|
* The same across projects, for workspace outputs: cal.com's shared
|
|
433
486
|
* `post-install` writes `../../node_modules/@prisma/client/**` from every
|
|
@@ -457,11 +510,11 @@ export function resolveSharedWorkspaceOutputs(
|
|
|
457
510
|
for (const p of projects) {
|
|
458
511
|
const rel = relPosix(root, p.dir)
|
|
459
512
|
for (const t of p.tasks) {
|
|
460
|
-
const
|
|
461
|
-
| {
|
|
513
|
+
const outputs = t.task?.['outputs'] as
|
|
514
|
+
| { files?: unknown; workspaceFiles?: unknown }
|
|
462
515
|
| undefined
|
|
463
|
-
const ws = positive(
|
|
464
|
-
const own = positive(
|
|
516
|
+
const ws = positive(outputs?.workspaceFiles)
|
|
517
|
+
const own = positive(outputs?.files).map((g) =>
|
|
465
518
|
rel === '' ? g.replace(/^(\.\/)+/, '') : `${rel}/${g.replace(/^(\.\/)+/, '')}`,
|
|
466
519
|
)
|
|
467
520
|
if (ws.length === 0 && own.length === 0) continue
|
|
@@ -470,11 +523,19 @@ export function resolveSharedWorkspaceOutputs(
|
|
|
470
523
|
a.find((g) => b.some((h) => outputsOverlap(h, g)))
|
|
471
524
|
let clash: (typeof kept)[number] | undefined
|
|
472
525
|
let shared: string | undefined
|
|
526
|
+
let theirs: string | undefined
|
|
473
527
|
for (const i of candidates([...ws, ...own])) {
|
|
474
528
|
const k = kept[i]!
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
529
|
+
const pools: [string[], string[]][] = [
|
|
530
|
+
[ws, [...k.ws, ...k.own]],
|
|
531
|
+
[own, k.project === p.name ? k.ws : [...k.ws, ...k.own]],
|
|
532
|
+
]
|
|
533
|
+
for (const [mine, other] of pools) {
|
|
534
|
+
shared = overlap(mine, other)
|
|
535
|
+
if (shared === undefined) continue
|
|
536
|
+
theirs = other.find((h) => outputsOverlap(h, shared!))
|
|
537
|
+
break
|
|
538
|
+
}
|
|
478
539
|
if (shared !== undefined) {
|
|
479
540
|
clash = k
|
|
480
541
|
break
|
|
@@ -485,9 +546,9 @@ export function resolveSharedWorkspaceOutputs(
|
|
|
485
546
|
kept.push({ id: `${p.name}#${t.name}`, project: p.name, ws, own })
|
|
486
547
|
continue
|
|
487
548
|
}
|
|
488
|
-
|
|
549
|
+
uncache(t.task!)
|
|
489
550
|
t.todos.push(
|
|
490
|
-
`declares the workspace output ${
|
|
551
|
+
`declares the workspace output ${sharing(shared!, theirs!, clash.id)} — ` +
|
|
491
552
|
"vx cleans a task's outputs before it runs and before a restore, so two cached tasks " +
|
|
492
553
|
"on one path would delete each other's work; this one runs uncached. Give it its own " +
|
|
493
554
|
'output path to cache it.',
|
|
@@ -525,9 +586,9 @@ export function excludeWorkspaceOutputs(
|
|
|
525
586
|
for (const p of projects) {
|
|
526
587
|
const rel = relPosix(root, p.dir)
|
|
527
588
|
for (const t of p.tasks) {
|
|
528
|
-
const outputs =
|
|
529
|
-
|
|
530
|
-
|
|
589
|
+
const outputs = t.task?.['outputs'] as
|
|
590
|
+
| { files?: unknown; workspaceFiles?: unknown }
|
|
591
|
+
| undefined
|
|
531
592
|
const globs = [
|
|
532
593
|
...positive(outputs?.workspaceFiles),
|
|
533
594
|
...positive(outputs?.files).map((g) =>
|
package/src/tracked-outputs.ts
CHANGED
|
@@ -72,6 +72,47 @@ export async function gitIgnored(root: string, rels: readonly string[]): Promise
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* What git ignores under the fixed prefixes of the root-relative `globs`:
|
|
77
|
+
* each ignored file, and a directory ignored whole (or holding only
|
|
78
|
+
* ignored files) as one `dir/` entry (`--directory`: `dist/` is never
|
|
79
|
+
* walked). Entries under `node_modules` (the lockfile keys an install) or
|
|
80
|
+
* vx's own `.vx/` are left out. One spawn; empty outside a repo or
|
|
81
|
+
* without git.
|
|
82
|
+
*/
|
|
83
|
+
export async function gitIgnoredUnder(root: string, globs: readonly string[]): Promise<string[]> {
|
|
84
|
+
if (globs.length === 0) return []
|
|
85
|
+
const prefixes = [...new Set(globs.map((g) => literalPrefix(g) || '.'))].sort()
|
|
86
|
+
// A prefix under another adds nothing; `ls-files` takes no pathspec file.
|
|
87
|
+
const kept = prefixes.includes('.')
|
|
88
|
+
? ['.']
|
|
89
|
+
: prefixes.filter((p, i) => !prefixes.slice(0, i).some((q) => p.startsWith(`${q}/`)))
|
|
90
|
+
try {
|
|
91
|
+
const p = Bun.spawn(
|
|
92
|
+
[
|
|
93
|
+
'git',
|
|
94
|
+
'ls-files',
|
|
95
|
+
'-z',
|
|
96
|
+
'--others',
|
|
97
|
+
'--ignored',
|
|
98
|
+
'--exclude-standard',
|
|
99
|
+
'--directory',
|
|
100
|
+
'--no-empty-directory',
|
|
101
|
+
'--',
|
|
102
|
+
...kept,
|
|
103
|
+
],
|
|
104
|
+
{ cwd: root, stdout: 'pipe', stderr: 'ignore' },
|
|
105
|
+
)
|
|
106
|
+
const out = await new Response(p.stdout).text()
|
|
107
|
+
if ((await p.exited) !== 0) return []
|
|
108
|
+
return out
|
|
109
|
+
.split('\0')
|
|
110
|
+
.filter((f) => f !== '' && !f.startsWith('.vx/') && !f.split('/').includes('node_modules'))
|
|
111
|
+
} catch {
|
|
112
|
+
return []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
75
116
|
/**
|
|
76
117
|
* What a project's tracked files are: their extensions (lower case, no
|
|
77
118
|
* dot), directory names at any depth, and top-level entry names.
|
|
@@ -165,18 +206,20 @@ function literalPrefix(glob: string): string {
|
|
|
165
206
|
return out.join('/')
|
|
166
207
|
}
|
|
167
208
|
|
|
168
|
-
/**
|
|
209
|
+
/**
|
|
210
|
+
* The tracked `files` (relative to the same base as `globs`) the positive
|
|
211
|
+
* globs cover, even one the globs' own `!` takes back: a reader's take-back
|
|
212
|
+
* is of the positives alone (`excludeSiblingOutputs`), so its `!*\/**`
|
|
213
|
+
* beside a `*\/**` minus `!src/**` output still hid src.
|
|
214
|
+
*/
|
|
169
215
|
function coveredTracked(globs: readonly string[], files: readonly string[]): string[] {
|
|
170
|
-
const positive = globs.filter((g) => !g.startsWith('!'))
|
|
171
|
-
const taken = globs.filter((g) => g.startsWith('!')).map((g) => g.slice(1))
|
|
172
216
|
const hit = new Set<string>()
|
|
173
|
-
for (const g of
|
|
217
|
+
for (const g of globs) {
|
|
218
|
+
if (g.startsWith('!')) continue
|
|
174
219
|
const prefix = literalPrefix(g)
|
|
175
220
|
for (const f of files) {
|
|
176
221
|
if (prefix !== '' && f !== prefix && !f.startsWith(`${prefix}/`)) continue
|
|
177
|
-
if (
|
|
178
|
-
if (taken.some((t) => outputsOverlap(t, f))) continue
|
|
179
|
-
hit.add(f)
|
|
222
|
+
if (outputsOverlap(g, f)) hit.add(f)
|
|
180
223
|
}
|
|
181
224
|
}
|
|
182
225
|
return [...hit].sort()
|
|
@@ -266,7 +309,7 @@ export function spareTrackedOutputs(
|
|
|
266
309
|
const rel = relPosix(root, p.dir)
|
|
267
310
|
const own = rel === '' ? tracked : filesUnder(tracked, rel).map((f) => f.slice(rel.length + 1))
|
|
268
311
|
for (const t of p.tasks) {
|
|
269
|
-
const outputs =
|
|
312
|
+
const outputs = t.task?.['outputs'] as Outputs | undefined
|
|
270
313
|
if (outputs === undefined) continue
|
|
271
314
|
const hits: [key: 'files' | 'workspaceFiles', hit: string[]][] = []
|
|
272
315
|
for (const [key, base] of [
|
|
@@ -275,20 +318,16 @@ export function spareTrackedOutputs(
|
|
|
275
318
|
] as const) {
|
|
276
319
|
const globs = outputs[key]
|
|
277
320
|
if (!Array.isArray(globs) || globs.length === 0) continue
|
|
278
|
-
|
|
321
|
+
const strings = globs.filter((g): g is string => typeof g === 'string')
|
|
279
322
|
// An output naming one committed file (eslint's
|
|
280
|
-
// `eslint-suppressions.json
|
|
281
|
-
//
|
|
282
|
-
// uncached (hono).
|
|
323
|
+
// `eslint-suppressions.json`, a codegen's `gen.ts`) is also a
|
|
324
|
+
// source: the `!` a reader's inputs got for it goes, or every
|
|
325
|
+
// reader ran uncached (hono). The entry stays: dropped, a task
|
|
326
|
+
// that rewrites the file never saved.
|
|
283
327
|
const rootedOf = (g: string) => (key === 'files' && rel !== '' ? `${rel}/${g}` : g)
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (sources.size > 0) {
|
|
288
|
-
strings = strings.filter((g) => !sources.has(g.startsWith('!') ? g.slice(1) : g))
|
|
289
|
-
outputs[key] = strings
|
|
290
|
-
for (const g of sources) unhide(key === 'files' ? p : null, rootedOf(g))
|
|
291
|
-
}
|
|
328
|
+
for (const g of strings)
|
|
329
|
+
if (isLiteralPattern(g) && trackedSet.has(rootedOf(g)))
|
|
330
|
+
unhide(key === 'files' ? p : null, rootedOf(g))
|
|
292
331
|
const hit = coveredTracked(strings, base)
|
|
293
332
|
if (hit.length > 0) hits.push([key, hit])
|
|
294
333
|
}
|
package/src/turbo/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { refuseUnknownOptions, UserError, type PluginOptionKinds } from '@vzn/vx
|
|
|
18
18
|
import { type AdoptionRun, adoptionPlugin } from '../adoption-plugin.js'
|
|
19
19
|
import type { AdoptionMapping } from '../mapping-cache.js'
|
|
20
20
|
import { collectGaps } from '../plugin-gaps.js'
|
|
21
|
-
import { gitIgnored, trackedKinds } from '../tracked-outputs.js'
|
|
21
|
+
import { gitIgnored, gitIgnoredUnder, trackedKinds } from '../tracked-outputs.js'
|
|
22
22
|
import { packageScripts, relPosix } from '../paths.js'
|
|
23
23
|
import { onceReader, type ReadText, tscBuildInfo } from '../tsc-buildinfo.js'
|
|
24
24
|
import type * as TurboMap from './turbo-map.js'
|
|
@@ -317,6 +317,7 @@ async function mapAll(
|
|
|
317
317
|
...(tracked === null ? {} : { tracked: trackedKinds(tracked) }),
|
|
318
318
|
ownConfig: (rel) => configs.get(rel === '.' ? '' : rel) ?? null,
|
|
319
319
|
ignored: (rels) => gitIgnored(root, rels),
|
|
320
|
+
ignoredUnder: (globs) => gitIgnoredUnder(root, globs),
|
|
320
321
|
buildInfo: (rel, command) => tscBuildInfo(read, path.join(root, rel), command),
|
|
321
322
|
})
|
|
322
323
|
const byName = new Map<string, TurboMappedProject>()
|