@prestack/thread 0.1.21 → 0.1.23
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/CHANGELOG.md +227 -0
- package/dist/core/attach.js +21 -0
- package/dist/core/descend.js +6 -3
- package/dist/core/generators/commit.js +55 -3
- package/dist/core/generators/dev.d.ts +1 -0
- package/dist/core/generators/dev.js +69 -0
- package/dist/derivation/generators/build.d.ts +3 -1
- package/dist/derivation/generators/build.js +104 -22
- package/dist/derivation/generators/release.js +74 -25
- package/dist/derivation/release.js +3 -3
- package/dist/integrations/cli.d.ts +0 -1
- package/dist/integrations/cli.js +69 -11
- package/dist/integrations/declared.d.ts +2 -0
- package/dist/integrations/declared.js +6 -1
- package/dist/integrations/pglite.js +13 -3
- package/dist/integrations/target.d.ts +3 -0
- package/dist/integrity/attach.js +1 -5
- package/dist/integrity/slopnators/sl_stale_path.js +10 -0
- package/dist/operations/access.js +3 -2
- package/dist/operations/capabilities.d.ts +2 -0
- package/dist/operations/capabilities.js +2 -0
- package/dist/operations/generators/capability.js +237 -24
- package/dist/operations/generators/compile.js +150 -50
- package/dist/operations/generators/seed.d.ts +3 -0
- package/dist/operations/generators/seed.js +25 -3
- package/dist/operations/nodes.d.ts +2 -2
- package/package.json +1 -1
- package/dist/core/actor.ts +0 -21
- package/dist/core/attach.ts +0 -613
- package/dist/core/chain.ts +0 -145
- package/dist/core/condition.ts +0 -227
- package/dist/core/descend.ts +0 -1185
- package/dist/core/errors/catalogue.ts +0 -314
- package/dist/core/errors/classes.ts +0 -51
- package/dist/core/errors/error.ts +0 -152
- package/dist/core/errors/namespaces.ts +0 -19
- package/dist/core/finding.ts +0 -37
- package/dist/core/generators/commit.ts +0 -157
- package/dist/core/init.ts +0 -222
- package/dist/core/invoked.ts +0 -52
- package/dist/core/round.ts +0 -74
- package/dist/core/rows.ts +0 -506
- package/dist/core/scope.ts +0 -20
- package/dist/core/thread.ts +0 -29
- package/dist/core/tree.ts +0 -90
- package/dist/core/types.ts +0 -351
- package/dist/derivation/attach.ts +0 -57
- package/dist/derivation/generators/build.ts +0 -371
- package/dist/derivation/generators/check.ts +0 -118
- package/dist/derivation/generators/determinism.ts +0 -145
- package/dist/derivation/generators/release.ts +0 -236
- package/dist/derivation/release.ts +0 -43
- package/dist/integrations/attach.ts +0 -187
- package/dist/integrations/cli.ts +0 -606
- package/dist/integrations/declared.ts +0 -34
- package/dist/integrations/disk.ts +0 -466
- package/dist/integrations/generators/read.ts +0 -127
- package/dist/integrations/generators/vault.ts +0 -210
- package/dist/integrations/https.ts +0 -216
- package/dist/integrations/pglite.ts +0 -206
- package/dist/integrations/secret.ts +0 -54
- package/dist/integrations/target.ts +0 -672
- package/dist/integrations/vault.ts +0 -148
- package/dist/integrity/attach.ts +0 -150
- package/dist/integrity/check-threads.ts +0 -166
- package/dist/integrity/constraint.ts +0 -1246
- package/dist/integrity/generators/compiler.ts +0 -138
- package/dist/integrity/generators/gate.ts +0 -227
- package/dist/integrity/generators/issues.ts +0 -160
- package/dist/integrity/generators/publish.ts +0 -233
- package/dist/integrity/generators/trial.ts +0 -359
- package/dist/integrity/lint.ts +0 -214
- package/dist/integrity/lsp/bin.ts +0 -44
- package/dist/integrity/lsp/diagnostics.ts +0 -92
- package/dist/integrity/lsp/findings.ts +0 -63
- package/dist/integrity/lsp/lint.ts +0 -124
- package/dist/integrity/lsp/protocol.ts +0 -78
- package/dist/integrity/lsp/server.ts +0 -224
- package/dist/integrity/slopnator.ts +0 -324
- package/dist/integrity/slopnators/sl_broken_lineage.ts +0 -38
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +0 -60
- package/dist/integrity/slopnators/sl_derived_drift.ts +0 -101
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +0 -250
- package/dist/integrity/slopnators/sl_hardcoded_style.ts +0 -55
- package/dist/integrity/slopnators/sl_inert_island.ts +0 -84
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +0 -93
- package/dist/integrity/slopnators/sl_missing_row.ts +0 -140
- package/dist/integrity/slopnators/sl_no_barrel.ts +0 -48
- package/dist/integrity/slopnators/sl_no_child_process.ts +0 -100
- package/dist/integrity/slopnators/sl_no_comment.ts +0 -43
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +0 -276
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +0 -66
- package/dist/integrity/slopnators/sl_no_such_command.ts +0 -81
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +0 -63
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +0 -25
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +0 -70
- package/dist/integrity/slopnators/sl_secret_literal.ts +0 -68
- package/dist/integrity/slopnators/sl_stale_path.ts +0 -178
- package/dist/integrity/slopnators/sl_twice_meant.ts +0 -57
- package/dist/integrity/slopnators/sl_twice_parented.ts +0 -82
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +0 -481
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +0 -113
- package/dist/integrity/slopnators/sl_undeclared_element.ts +0 -44
- package/dist/integrity/slopnators/sl_undeclared_list.ts +0 -358
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -75
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +0 -257
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +0 -152
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +0 -108
- package/dist/integrity/slopnators/sl_underived_route.ts +0 -72
- package/dist/integrity/slopnators/sl_undescribed.ts +0 -65
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +0 -60
- package/dist/integrity/slopnators/sl_unlisted_program.ts +0 -46
- package/dist/integrity/slopnators/sl_unnamed_island.ts +0 -41
- package/dist/integrity/slopnators/sl_unpaired_member.ts +0 -139
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +0 -71
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +0 -110
- package/dist/integrity/slopnators/sl_unthreaded.ts +0 -421
- package/dist/integrity/slopnators/sl_untranslated_key.ts +0 -140
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +0 -132
- package/dist/integrity/slopnators/sl_unversioned.ts +0 -52
- package/dist/integrity/sweep.ts +0 -395
- package/dist/knowledge/attach.ts +0 -327
- package/dist/knowledge/generators/docsgen.ts +0 -186
- package/dist/knowledge/generators/glossary.ts +0 -73
- package/dist/knowledge/glossary.ts +0 -6
- package/dist/operations/access.ts +0 -131
- package/dist/operations/attach.ts +0 -408
- package/dist/operations/capabilities.ts +0 -88
- package/dist/operations/dispatch.ts +0 -294
- package/dist/operations/error-results.ts +0 -31
- package/dist/operations/generators/capability.ts +0 -1054
- package/dist/operations/generators/compile.ts +0 -701
- package/dist/operations/generators/permits.ts +0 -229
- package/dist/operations/generators/seed.ts +0 -359
- package/dist/operations/generators/trial.ts +0 -666
- package/dist/operations/nodes.ts +0 -53
- package/dist/operations/operations.ts +0 -91
- package/dist/operations/result.ts +0 -28
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { readdirSync, readFileSync, writeFileSync } from '../../integrations/disk.ts'
|
|
3
|
-
import { join, resolve } from 'node:path'
|
|
4
|
-
import { argv, stderr, stdout } from 'node:process'
|
|
5
|
-
|
|
6
|
-
import { exit } from '../../core/invoked.ts'
|
|
7
|
-
import { ROOT, THREAD_AT, descend, uncompiled } from '../../core/descend.ts'
|
|
8
|
-
|
|
9
|
-
import { forbids, runChecker, type Finding } from '../../core/finding.ts'
|
|
10
|
-
|
|
11
|
-
function refuse(why: string): never {
|
|
12
|
-
stderr.write(`compiler: ${why}\n`)
|
|
13
|
-
exit(1)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const SOURCE = /\.(?:ts|tsx|mts|cts)$/
|
|
17
|
-
const DECLARING = /\.d\.[cm]?ts$/
|
|
18
|
-
|
|
19
|
-
const INSTALLED = 'node_' + 'modules'
|
|
20
|
-
|
|
21
|
-
const pass = await descend()
|
|
22
|
-
|
|
23
|
-
const stopped = [...pass.broken, ...pass.findings].filter(forbids)[0]
|
|
24
|
-
if (stopped !== undefined) {
|
|
25
|
-
refuse(
|
|
26
|
-
'the descent is broken, so what this tree declares is short — and a short list of declarations ' +
|
|
27
|
-
`leaves out more of the app than it should, which is a compiler that agrees with anything. ${stopped.detail} (${stopped.file ?? ''})`,
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const WORD = 'thread compiler'
|
|
32
|
-
|
|
33
|
-
const SELF = [...pass.threaded.entries()]
|
|
34
|
-
.flatMap(([at, held]) => held.map((one) => ({ at, row: one.row })))
|
|
35
|
-
.filter((one) => (one.row.by ?? '') === WORD)
|
|
36
|
-
.find((one) => !one.at.split('/').includes(INSTALLED))
|
|
37
|
-
|
|
38
|
-
if (SELF === undefined) {
|
|
39
|
-
refuse(
|
|
40
|
-
`no row in this tree names a file of this app with \`by: '${WORD}'\`, so nothing says which config is this app's. ` +
|
|
41
|
-
'Run `thread attach integrity`, which declares it.',
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const AT = SELF.at
|
|
46
|
-
const PATH = resolve(ROOT, AT)
|
|
47
|
-
|
|
48
|
-
const declared = new Set([THREAD_AT, ...pass.threaded.keys()])
|
|
49
|
-
|
|
50
|
-
function scan(rel: string): { readonly declares: boolean; readonly holds: boolean; readonly out: readonly string[] } {
|
|
51
|
-
let entries
|
|
52
|
-
try {
|
|
53
|
-
entries = readdirSync(join(ROOT, rel === '' ? '.' : rel), { withFileTypes: true })
|
|
54
|
-
} catch {
|
|
55
|
-
return { declares: false, holds: false, out: [] }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let declares = false
|
|
59
|
-
let holds = false
|
|
60
|
-
const out: string[] = []
|
|
61
|
-
|
|
62
|
-
for (const entry of [...entries].sort((one, two) => one.name.localeCompare(two.name))) {
|
|
63
|
-
if (entry.name === '.git' || entry.name === INSTALLED) continue
|
|
64
|
-
const at = rel === '' ? entry.name : `${rel}/${entry.name}`
|
|
65
|
-
|
|
66
|
-
if (entry.isDirectory()) {
|
|
67
|
-
const held = scan(at)
|
|
68
|
-
if (held.declares) {
|
|
69
|
-
declares = true
|
|
70
|
-
holds = true
|
|
71
|
-
out.push(...held.out)
|
|
72
|
-
} else if (held.holds) {
|
|
73
|
-
holds = true
|
|
74
|
-
out.push(at)
|
|
75
|
-
}
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (declared.has(at)) {
|
|
80
|
-
declares = true
|
|
81
|
-
holds = true
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
if (!SOURCE.test(entry.name) || DECLARING.test(entry.name)) continue
|
|
85
|
-
holds = true
|
|
86
|
-
out.push(at)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { declares, holds, out }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const held = scan('')
|
|
93
|
-
|
|
94
|
-
if (!held.declares) {
|
|
95
|
-
refuse(
|
|
96
|
-
'no TypeScript file in this tree is declared by anything, so every one of them would be left out ' +
|
|
97
|
-
'and the compiler would pass by reading nothing. Declare something with `thread create` first.',
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const NEXT = '.next'
|
|
102
|
-
|
|
103
|
-
const leave = [...new Set([...held.out, NEXT])].sort()
|
|
104
|
-
|
|
105
|
-
let current: Record<string, unknown>
|
|
106
|
-
try {
|
|
107
|
-
current = JSON.parse(readFileSync(PATH, 'utf8')) as Record<string, unknown>
|
|
108
|
-
} catch (cause) {
|
|
109
|
-
refuse(`${AT} could not be read as JSON, so what it already states cannot be kept — ${String(cause)}`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const wanted = JSON.stringify({ ...current, exclude: leave }, null, 2)
|
|
113
|
-
const has = JSON.stringify(current.exclude ?? []) === JSON.stringify(leave)
|
|
114
|
-
|
|
115
|
-
if (argv.includes('--check')) {
|
|
116
|
-
const findings: Finding[] = []
|
|
117
|
-
if (!has) {
|
|
118
|
-
findings.push({
|
|
119
|
-
file: AT,
|
|
120
|
-
detail: `does not leave out ${String(leave.length)} path(s) nothing in this tree declares, so the compiler is holding this app to code it never wrote`,
|
|
121
|
-
instead:
|
|
122
|
-
'run `thread compiler`. What the compiler reads is derived from what the tree declares — an edit here is a declaration somebody skipped, and a path put here by hand is taken back out on the next settle',
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
findings.push(...uncompiled())
|
|
126
|
-
runChecker('compiler', findings, `${String(leave.length)} path(s) left out, over ${String(pass.declarations)} declaration(s)`)
|
|
127
|
-
exit(findings.length > 0 ? 1 : 0)
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (has) {
|
|
132
|
-
stdout.write(`compiler: ${AT} already leaves out exactly what this tree declares nothing under.\n`)
|
|
133
|
-
exit(0)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
writeFileSync(PATH, `${wanted}\n`)
|
|
137
|
-
stdout.write(`compiler: wrote ${AT} — ${String(leave.length)} path(s) left out, declared by nobody: ${leave.join(', ')}.\n`)
|
|
138
|
-
exit(0)
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { starts } from '../../integrations/cli.ts'
|
|
2
|
-
import { createHash } from 'node:crypto'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { argv, execPath, stderr, stdout } from 'node:process'
|
|
5
|
-
import { descend } from '../../core/descend.ts'
|
|
6
|
-
import { attached, existsSync, here, readdirSync, readFileSync, rmSync, writeFileSync } from '../../integrations/disk.ts'
|
|
7
|
-
import { LOADER, exit } from '../../core/invoked.ts'
|
|
8
|
-
import { valuesIn } from '../../core/rows.ts'
|
|
9
|
-
import { ORGANISATION, ours } from '../../core/scope.ts'
|
|
10
|
-
import { findTree, inTree } from '../../core/tree.ts'
|
|
11
|
-
|
|
12
|
-
const TREE = findTree()
|
|
13
|
-
const ROOT = TREE.at
|
|
14
|
-
|
|
15
|
-
const STEP_TIMEOUT = 600_000
|
|
16
|
-
|
|
17
|
-
const { GATE } = valuesIn(readFileSync(inTree(TREE, 'gate.ts'))) as { GATE: { derives?: unknown; declares?: unknown } }
|
|
18
|
-
const { GENERATORS } = valuesIn(readFileSync(inTree(TREE, 'derivation.ts'))) as { GENERATORS: readonly Record<string, unknown>[] }
|
|
19
|
-
|
|
20
|
-
function refuse(why: string): never {
|
|
21
|
-
stderr.write(`gate: ${why}\n`)
|
|
22
|
-
exit(1)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface Row {
|
|
26
|
-
readonly id?: string
|
|
27
|
-
readonly at?: string
|
|
28
|
-
readonly by?: string
|
|
29
|
-
readonly runs?: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const rowsOf = (edge: unknown): readonly Row[] => (Array.isArray(edge) ? (edge as readonly Row[]) : [])
|
|
33
|
-
|
|
34
|
-
interface Step {
|
|
35
|
-
readonly id: string
|
|
36
|
-
readonly runs: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const walked = await descend()
|
|
40
|
-
|
|
41
|
-
const every: readonly Row[] = [
|
|
42
|
-
...rowsOf(GATE.declares),
|
|
43
|
-
...rowsOf(GENERATORS),
|
|
44
|
-
...[...walked.threaded.values()].flat().map((one) => one.row as Row),
|
|
45
|
-
...[...walked.stated.values()].map((one) => one as Row),
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
function steps(): readonly Step[] {
|
|
49
|
-
const found: Step[] = []
|
|
50
|
-
const claimed = new Set<string>()
|
|
51
|
-
|
|
52
|
-
for (const row of every) {
|
|
53
|
-
const runs = (row.runs ?? '').trim()
|
|
54
|
-
if (runs === '' || claimed.has(runs)) continue
|
|
55
|
-
claimed.add(runs)
|
|
56
|
-
found.push({ id: row.id ?? runs, runs })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (found.length === 0) {
|
|
60
|
-
refuse('no row in this tree names a command to check it, so the gate would pass by running nothing')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return found
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const writers: readonly Row[] = [
|
|
67
|
-
...rowsOf(GATE.declares),
|
|
68
|
-
...rowsOf(GENERATORS),
|
|
69
|
-
...[...walked.threaded.values()].flat().filter((one) => one.list === 'declares' && (one.row as Row).by === undefined).map((one) => one.row as Row),
|
|
70
|
-
...[...walked.stated.values()].map((one) => one as Row),
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
const written = new Map<string, string>()
|
|
74
|
-
|
|
75
|
-
for (const row of writers) {
|
|
76
|
-
const by = (row.by ?? '').trim()
|
|
77
|
-
const at = (row.at ?? '').trim()
|
|
78
|
-
if (by !== '' && at !== '' && !written.has(by)) written.set(by, at)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function ran(runs: string): number {
|
|
82
|
-
const said = runs.trim().split(/\s+/)
|
|
83
|
-
const spent = { at: ROOT, shows: true, within: STEP_TIMEOUT } as const
|
|
84
|
-
|
|
85
|
-
if (said[0] !== 'thread') {
|
|
86
|
-
const other = starts(said[0] ?? '', said.slice(1), spent)
|
|
87
|
-
return other.there ? other.code : 1
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const of = `thread ${said[1] ?? ''}`
|
|
91
|
-
const at = written.get(of)
|
|
92
|
-
if (at === undefined) {
|
|
93
|
-
stderr.write(`gate: nothing in this tree is written by \`${of}\`, so this step names a command that does not exist here\n`)
|
|
94
|
-
return 1
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (attached()) {
|
|
98
|
-
const ordered = here().run(join(ROOT, at), said.slice(2))
|
|
99
|
-
stdout.write(ordered.out)
|
|
100
|
-
stderr.write(ordered.err)
|
|
101
|
-
return ordered.there ? ordered.code : 1
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const spun = starts(execPath, [...LOADER, join(ROOT, at), ...said.slice(2)], spent)
|
|
105
|
-
return spun.there ? spun.code : 1
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function named(flag: string): readonly string[] | undefined {
|
|
109
|
-
const at = argv.indexOf(flag)
|
|
110
|
-
if (at === -1) return undefined
|
|
111
|
-
return (argv[at + 1] ?? '').split(',').map((one) => one.trim()).filter(Boolean)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const all = steps()
|
|
115
|
-
|
|
116
|
-
if (argv.includes('--list')) {
|
|
117
|
-
stdout.write(`gate: ${String(all.length)} step(s) declared\n`)
|
|
118
|
-
for (const step of all) stdout.write(` ${step.id} — ${step.runs}\n`)
|
|
119
|
-
exit(0)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const only = named('--only')
|
|
123
|
-
const skip = named('--skip') ?? []
|
|
124
|
-
const known = new Set(all.map((step) => step.id))
|
|
125
|
-
const unknown = [...(only ?? []), ...skip].filter((id) => !known.has(id))
|
|
126
|
-
|
|
127
|
-
if (unknown.length > 0) {
|
|
128
|
-
stderr.write(`gate: no declared step is called ${unknown.join(', ')} — a filter naming nothing would run a shorter gate and exit 0\n`)
|
|
129
|
-
stderr.write(`gate: the declared steps are ${[...known].join(', ')}\n`)
|
|
130
|
-
exit(1)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const chosen = all.filter((step) => (only === undefined || only.includes(step.id)) && !skip.includes(step.id))
|
|
134
|
-
const dropped = all.filter((step) => !chosen.includes(step))
|
|
135
|
-
|
|
136
|
-
if (chosen.length === 0) {
|
|
137
|
-
stderr.write('gate: the filter selected no steps, and a gate that runs nothing must not exit 0\n')
|
|
138
|
-
exit(1)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const failed: Step[] = []
|
|
142
|
-
const unmeasured: Step[] = []
|
|
143
|
-
let passed = 0
|
|
144
|
-
|
|
145
|
-
for (const [index, step] of chosen.entries()) {
|
|
146
|
-
const at = `step ${String(index + 1)} of ${String(chosen.length)}`
|
|
147
|
-
stdout.write(`\ngate: ${at} — ${step.runs} (${step.id})\n`)
|
|
148
|
-
|
|
149
|
-
if (attached() && step.runs.trim().split(/\s+/)[0] !== 'thread') {
|
|
150
|
-
unmeasured.push(step)
|
|
151
|
-
stdout.write(
|
|
152
|
-
`gate: ${at} — NOT RUN. \`${step.runs}\` is not a thread command, so it reads the disk of the machine this is\n` +
|
|
153
|
-
`running on rather than the tree being answered for. It would report on the wrong files, and a step that\n` +
|
|
154
|
-
`passed against the wrong machine says nothing about this one.\n`,
|
|
155
|
-
)
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const began = Date.now()
|
|
160
|
-
const code = ran(step.runs)
|
|
161
|
-
|
|
162
|
-
stdout.write(`gate: ${at} — exited ${String(code)} in ${String(Date.now() - began)} ms\n`)
|
|
163
|
-
if (code === 0) passed += 1
|
|
164
|
-
else failed.push(step)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
stdout.write(`\ngate: ${String(passed)} of ${String(chosen.length)} steps passed.\n`)
|
|
168
|
-
|
|
169
|
-
if (dropped.length > 0) {
|
|
170
|
-
stdout.write(`gate: PARTIAL RUN — ${String(dropped.length)} of ${String(all.length)} declared step(s) did not run: ${dropped.map((step) => step.id).join(', ')}\n`)
|
|
171
|
-
stdout.write('gate: this is not a gate pass. `thread gate` with no filter runs every step the tree declares.\n')
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function fingerprint(): { readonly of: Record<string, string>; readonly versions: Record<string, string> } {
|
|
175
|
-
const of: Record<string, string> = {}
|
|
176
|
-
const hash = (at: string): void => {
|
|
177
|
-
if (!existsSync(join(ROOT, at))) return
|
|
178
|
-
of[at] = createHash('sha256').update(readFileSync(join(ROOT, at))).digest('hex').slice(0, 16)
|
|
179
|
-
}
|
|
180
|
-
const sheets = join(ROOT, '_threads')
|
|
181
|
-
if (existsSync(sheets)) {
|
|
182
|
-
for (const name of readdirSync(sheets)) if (name.endsWith('.ts')) hash(join('_threads', name))
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const versions: Record<string, string> = {}
|
|
186
|
-
const held = join(ROOT, 'node_modules', ORGANISATION)
|
|
187
|
-
if (existsSync(held)) {
|
|
188
|
-
for (const name of readdirSync(held)) {
|
|
189
|
-
const pkg = `${ORGANISATION}/${name}`
|
|
190
|
-
if (!ours(pkg)) continue
|
|
191
|
-
const at = join(held, name, 'package.json')
|
|
192
|
-
if (!existsSync(at)) continue
|
|
193
|
-
const read = JSON.parse(readFileSync(at, 'utf8')) as { version?: string }
|
|
194
|
-
if (typeof read.version === 'string') versions[pkg] = read.version
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return { of, versions }
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const RECEIPT_AT = join('.thread', 'gate.json')
|
|
201
|
-
const RECEIPT = join(ROOT, RECEIPT_AT)
|
|
202
|
-
|
|
203
|
-
if (unmeasured.length > 0) {
|
|
204
|
-
stdout.write(
|
|
205
|
-
`gate: ${String(unmeasured.length)} step(s) could not be measured here — ${unmeasured.map((step) => step.id).join(', ')}. Each one\n` +
|
|
206
|
-
`runs a tool that is not part of this framework, so it reads whatever disk it was started on. This is not a\n` +
|
|
207
|
-
`gate pass, and no receipt is written for a run that left them unanswered.\n`,
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (failed.length === 0 && dropped.length === 0 && unmeasured.length === 0) {
|
|
212
|
-
writeFileSync(RECEIPT, `${JSON.stringify({ passed: chosen.length, ...fingerprint() }, null, 2)}\n`, 'utf8')
|
|
213
|
-
stdout.write(`gate: wrote ${RECEIPT_AT} — what compiles is checked against this, so a tree that skipped the gate does not build.\n`)
|
|
214
|
-
} else if (existsSync(RECEIPT)) {
|
|
215
|
-
|
|
216
|
-
rmSync(RECEIPT)
|
|
217
|
-
stdout.write(`gate: removed ${RECEIPT_AT} — the tree no longer passes, so nothing may compile against an older run.\n`)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (failed.length === 0) exit(unmeasured.length === 0 ? 0 : 1)
|
|
221
|
-
|
|
222
|
-
stderr.write(
|
|
223
|
-
`gate: FAILED — ${String(failed.length)} of ${String(chosen.length)} step(s). The other ${String(passed)} ran and passed; a failing step stops nothing.\n`,
|
|
224
|
-
)
|
|
225
|
-
for (const step of failed) stderr.write(` - ${step.id} — run it alone with \`${step.runs}\`\n`)
|
|
226
|
-
stderr.write('gate: every step above is its own command. Run the failing one on its own to work on it, and `thread issues` for everything wrong with the tree at once.\n')
|
|
227
|
-
exit(1)
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { argv, stderr, stdout } from 'node:process'
|
|
2
|
-
|
|
3
|
-
import { exit } from '../../core/invoked.ts'
|
|
4
|
-
import { uncompiled } from '../../core/descend.ts'
|
|
5
|
-
|
|
6
|
-
import { THREAD_AT, descend, refusals, trackedFiles, unthreaded, type Finding } from '../check-threads.ts'
|
|
7
|
-
import { CONSTRAINTS } from '../constraint.ts'
|
|
8
|
-
import { ruleMessage } from '../lint.ts'
|
|
9
|
-
import { ROOT, load, rules } from '../slopnator.ts'
|
|
10
|
-
import { sweep, tracked as governed } from '../sweep.ts'
|
|
11
|
-
|
|
12
|
-
const WITHIN = '--in'
|
|
13
|
-
|
|
14
|
-
const CHECK = '--check'
|
|
15
|
-
|
|
16
|
-
const checking = argv.includes(CHECK)
|
|
17
|
-
|
|
18
|
-
const scoped = argv.indexOf(WITHIN)
|
|
19
|
-
const under = scoped === -1 ? undefined : argv[scoped + 1]
|
|
20
|
-
if (scoped !== -1 && (under === undefined || under.startsWith('-'))) {
|
|
21
|
-
stderr.write(`thread issues ${WITHIN} <path> — which directory to report on. The totals still count the whole tree.\n`)
|
|
22
|
-
exit(1)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const inside = (one: Finding): boolean =>
|
|
26
|
-
under === undefined || one.file === undefined || one.file === under || one.file.startsWith(`${under.replace(/\/+$/, '')}/`)
|
|
27
|
-
|
|
28
|
-
const pass = await descend()
|
|
29
|
-
const tracked = trackedFiles()
|
|
30
|
-
const kept = new Set(tracked)
|
|
31
|
-
|
|
32
|
-
const found = refusals(pass, tracked)
|
|
33
|
-
|
|
34
|
-
const loose = new Set(unthreaded(pass, tracked))
|
|
35
|
-
|
|
36
|
-
const governing = [...CONSTRAINTS, ...((await rules()) ?? [])]
|
|
37
|
-
const named = new Map(governing.map((one) => [one.id, one]))
|
|
38
|
-
const ran = sweep(ROOT, governing, await load(ROOT, governing), governed(ROOT))
|
|
39
|
-
|
|
40
|
-
const refused: readonly Finding[] = [
|
|
41
|
-
...ran.findings.map((one): Finding => {
|
|
42
|
-
const rule = named.get(one.rule)
|
|
43
|
-
return {
|
|
44
|
-
detail: `${rule === undefined ? one.rule : ruleMessage(rule)} Found: ${one.found}.`,
|
|
45
|
-
instead: rule?.instead ?? `read what ${one.rule} refuses and stop writing it`,
|
|
46
|
-
...(one.file === '' ? {} : { file: one.file }),
|
|
47
|
-
}
|
|
48
|
-
}),
|
|
49
|
-
...ran.unrun.map((one): Finding => ({
|
|
50
|
-
detail: `${one} DID NOT RUN — no slopnator is on disk for it, so nothing here measured that rule and its absence from this list says nothing about it`,
|
|
51
|
-
instead: 'write the walk the rule names, or delete the rule and its row together — half a rule reads as a whole one from every direction',
|
|
52
|
-
})),
|
|
53
|
-
]
|
|
54
|
-
|
|
55
|
-
const warned = found.filter((one) => one.severity === 'warning')
|
|
56
|
-
const firm = found.filter((one) => one.severity !== 'warning')
|
|
57
|
-
const cut = firm.filter((one) => one.file !== undefined && loose.has(one.file))
|
|
58
|
-
const rest = firm.filter((one) => !(one.file !== undefined && loose.has(one.file)))
|
|
59
|
-
const wrong = uncompiled().filter((one) => one.file === undefined || kept.has(one.file))
|
|
60
|
-
|
|
61
|
-
const where = (one: Finding): string => {
|
|
62
|
-
const line = one.at?.line
|
|
63
|
-
const column = one.at?.column
|
|
64
|
-
return `${one.file ?? '(nowhere named)'}${line === undefined ? '' : `:${String(line)}${column === undefined ? '' : `:${String(column)}`}`}`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const listed = (held: readonly Finding[]): readonly string[] =>
|
|
68
|
-
held.flatMap((one) => [` ${where(one)}`, ` ${one.detail}`, ` fix: ${one.instead}`, ''])
|
|
69
|
-
|
|
70
|
-
const out: string[] = []
|
|
71
|
-
|
|
72
|
-
let shown = 0
|
|
73
|
-
|
|
74
|
-
const group = (title: string, taught: string, held: readonly Finding[]): void => {
|
|
75
|
-
if (held.length === 0) return
|
|
76
|
-
const here = held.filter(inside)
|
|
77
|
-
shown += here.length
|
|
78
|
-
if (here.length === 0) return
|
|
79
|
-
out.push(
|
|
80
|
-
'',
|
|
81
|
-
`${title} — ${String(held.length)}${here.length === held.length ? '' : `, ${String(here.length)} of them under ${String(under)}`}`,
|
|
82
|
-
taught,
|
|
83
|
-
'',
|
|
84
|
-
...listed(here),
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
group(
|
|
89
|
-
'files nothing declares',
|
|
90
|
-
' every file here is named by a row, or it is not part of this app. The descent starts at\n' +
|
|
91
|
-
` ${THREAD_AT} and walks what each row declares; whatever it never arrives at is not wired,\n` +
|
|
92
|
-
' however well it reads. Declare it with `thread create`, or delete it.',
|
|
93
|
-
cut,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
group(
|
|
97
|
-
'files that will not compile',
|
|
98
|
-
' nothing in this tree builds, serves or gates while one of its files does not typecheck.\n' +
|
|
99
|
-
' Read off the same program the descent judged the declarations with.',
|
|
100
|
-
wrong,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
group(
|
|
104
|
-
'declarations this tree refuses',
|
|
105
|
-
' the files are reached and they compile; these are the things the descent will not accept about\n' +
|
|
106
|
-
' how they are declared.',
|
|
107
|
-
rest,
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
group(
|
|
111
|
-
'what the rules refuse',
|
|
112
|
-
' every rule this framework ships and every rule this app added, run over the files each one\n' +
|
|
113
|
-
` governs — ${String(ran.rules)} rule(s) over ${String(ran.swept)} of ${String(ran.tracked)} tracked file(s).\n` +
|
|
114
|
-
' This is what `thread sweep` reports, said here so one read answers the whole question.',
|
|
115
|
-
refused,
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
group(
|
|
119
|
-
'derivations nothing has produced yet',
|
|
120
|
-
' each row here states what a generator would write, and no file is there. A generator that refuses\n' +
|
|
121
|
-
' to write an empty file is doing its job, so the descent says these and does not refuse them, and\n' +
|
|
122
|
-
' neither does this. Run the command the row names in `by` to produce the file.',
|
|
123
|
-
warned,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
const total = cut.length + wrong.length + rest.length + refused.length
|
|
127
|
-
|
|
128
|
-
const saidNotCounted = (): void => {
|
|
129
|
-
if (warned.length === 0) return
|
|
130
|
-
stdout.write(`issues: ${String(warned.length)} derivation(s) nothing has produced yet — listed below, said and not counted.\n`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (total === 0) {
|
|
134
|
-
stdout.write(
|
|
135
|
-
`issues: none — ${String(tracked.length)} tracked file(s), every one of them declared by a row, and all of them compile.\n` +
|
|
136
|
-
'issues: that is what a threaded tree is. Nothing here is written by hand; everything here was declared.\n',
|
|
137
|
-
)
|
|
138
|
-
saidNotCounted()
|
|
139
|
-
if (out.length > 0) stdout.write(`${out.join('\n')}\n`)
|
|
140
|
-
exit(0)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
stdout.write(
|
|
144
|
-
`issues: ${String(total)} — ${String(cut.length)} file(s) nothing declares, ${String(wrong.length)} that will not compile, ` +
|
|
145
|
-
`${String(rest.length)} declared in a way it refuses, ${String(refused.length)} the rules refuse, over ${String(tracked.length)} tracked file(s).\n`,
|
|
146
|
-
)
|
|
147
|
-
saidNotCounted()
|
|
148
|
-
stdout.write(
|
|
149
|
-
under === undefined
|
|
150
|
-
? `issues: listing all ${String(total)} below. \`thread issues ${WITHIN} <path>\` reports on one directory instead.\n`
|
|
151
|
-
: `issues: listing the ${String(shown)} of them under ${under}. The counts above are the whole tree.\n`,
|
|
152
|
-
)
|
|
153
|
-
stdout.write(
|
|
154
|
-
checking
|
|
155
|
-
? `issues: ${CHECK} refuses while any of these stand. Without it this reads and never refuses, so it still answers on a tree that is entirely broken.\n`
|
|
156
|
-
: `issues: this reads and never refuses, so it works on a tree that is entirely broken. Run it with ${CHECK}, which is what the gate runs, to have it refuse instead.\n`,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
stdout.write(`${out.join('\n')}\n`)
|
|
160
|
-
exit(checking ? 1 : 0)
|