@prestack/thread 0.1.20 → 0.1.22
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 +193 -0
- package/dist/core/attach.js +21 -0
- package/dist/core/descend.d.ts +2 -0
- package/dist/core/descend.js +18 -3
- package/dist/core/generators/commit.js +55 -3
- package/dist/core/generators/dev.d.ts +1 -0
- package/dist/core/generators/dev.js +68 -0
- package/dist/core/rows.d.ts +5 -0
- package/dist/core/rows.js +45 -0
- package/dist/core/tree.d.ts +1 -0
- package/dist/core/tree.js +7 -1
- package/dist/derivation/generators/build.d.ts +3 -1
- package/dist/derivation/generators/build.js +103 -22
- package/dist/derivation/generators/release.js +74 -26
- package/dist/derivation/release.js +3 -3
- package/dist/integrations/cli.d.ts +3 -9
- package/dist/integrations/cli.js +96 -36
- package/dist/integrations/declared.d.ts +8 -0
- package/dist/integrations/declared.js +22 -0
- package/dist/integrations/pglite.js +13 -3
- package/dist/integrations/target.d.ts +4 -1
- package/dist/integrations/target.js +20 -6
- package/dist/integrity/attach.js +1 -5
- package/dist/integrity/generators/publish.js +2 -1
- package/dist/integrity/slopnators/sl_stale_path.js +10 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +2 -2
- 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 +234 -24
- package/dist/operations/generators/compile.d.ts +21 -0
- package/dist/operations/generators/compile.js +151 -51
- package/dist/operations/generators/permits.d.ts +2 -1
- package/dist/operations/generators/permits.js +52 -47
- 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 -1173
- 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 -459
- 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 -615
- 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 -656
- 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 -232
- 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 -224
- 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,421 +0,0 @@
|
|
|
1
|
-
import { parser } from 'typescript-eslint'
|
|
2
|
-
|
|
3
|
-
import type { Slopnator } from '../slopnator.ts'
|
|
4
|
-
|
|
5
|
-
import { LISTS as LINEAGE, descend, forget as forgetDescent, read, reached as fromThread, threading } from '../../core/descend.ts'
|
|
6
|
-
import { findTree } from '../../core/tree.ts'
|
|
7
|
-
|
|
8
|
-
const TREE_AT = findTree()
|
|
9
|
-
const ROOT = `${TREE_AT.at.split('\\').join('/')}/`
|
|
10
|
-
const TREE = TREE_AT.name
|
|
11
|
-
const THREAD_AT = `${TREE_AT.name}/thread.ts`
|
|
12
|
-
const FOLLOWABLE = /\.(ts|tsx|mts|cts|mjs|cjs|js)$/
|
|
13
|
-
const LISTS: ReadonlySet<string> = new Set(LINEAGE)
|
|
14
|
-
|
|
15
|
-
let tree: Index | undefined
|
|
16
|
-
const parsed = new Map<string, readonly Reach[]>()
|
|
17
|
-
const broke = new Map<string, string>()
|
|
18
|
-
|
|
19
|
-
export interface Reach {
|
|
20
|
-
readonly list?: 'declares' | 'derives' | 'seeds'
|
|
21
|
-
readonly at?: string
|
|
22
|
-
readonly id?: string
|
|
23
|
-
readonly describes?: string
|
|
24
|
-
readonly version?: number
|
|
25
|
-
readonly governs?: readonly string[]
|
|
26
|
-
readonly wears?: readonly string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface Meaning {
|
|
30
|
-
readonly id: string
|
|
31
|
-
readonly describes: string
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface Held {
|
|
35
|
-
readonly type?: string
|
|
36
|
-
readonly kind?: string
|
|
37
|
-
readonly name?: string
|
|
38
|
-
readonly value?: unknown
|
|
39
|
-
readonly key?: Held
|
|
40
|
-
readonly computed?: boolean
|
|
41
|
-
readonly id?: Held
|
|
42
|
-
readonly typeAnnotation?: Held
|
|
43
|
-
readonly typeName?: Held
|
|
44
|
-
readonly elementType?: Held
|
|
45
|
-
readonly properties?: readonly Held[]
|
|
46
|
-
readonly elements?: readonly (Held | null)[]
|
|
47
|
-
readonly declarations?: readonly Held[]
|
|
48
|
-
readonly body?: readonly Held[]
|
|
49
|
-
readonly declaration?: Held
|
|
50
|
-
readonly init?: Held
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const keyOf = (property: Held): string | undefined => {
|
|
54
|
-
if (property.computed === true) return undefined
|
|
55
|
-
const key = property.key
|
|
56
|
-
if (key?.type === 'Identifier') return key.name
|
|
57
|
-
if (key?.type === 'Literal' && typeof key.value === 'string') return key.value
|
|
58
|
-
return undefined
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function annotated(node: Held): string | undefined {
|
|
62
|
-
let held = node.id?.typeAnnotation?.typeAnnotation
|
|
63
|
-
while (held !== undefined && (held.type === 'TSArrayType' || held.type === 'TSTypeOperator')) {
|
|
64
|
-
held = held.elementType ?? held.typeAnnotation
|
|
65
|
-
}
|
|
66
|
-
return held?.typeName?.name
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const here = (filename: string): string => {
|
|
70
|
-
const path = filename.split('\\').join('/')
|
|
71
|
-
return path.startsWith(ROOT) ? path.slice(ROOT.length) : path
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function rows(node: Held, list: Reach['list'], found: Reach[], hoisted: ReadonlyMap<string, Held>): void {
|
|
77
|
-
for (const one of node.elements ?? []) {
|
|
78
|
-
const element = one?.type === 'Identifier' && one.name !== undefined ? hoisted.get(one.name) : one
|
|
79
|
-
if (element === null || element === undefined || element.type !== 'ObjectExpression') continue
|
|
80
|
-
const strings: Record<string, string> = {}
|
|
81
|
-
const said: { at?: string; id?: string; describes?: string; governs?: readonly string[]; wears?: readonly string[] } = {}
|
|
82
|
-
for (const property of element.properties ?? []) {
|
|
83
|
-
const key = keyOf(property)
|
|
84
|
-
const held = property.value as Held | undefined
|
|
85
|
-
if (key !== undefined && held?.type === 'Literal' && typeof held.value === 'string') strings[key] = held.value
|
|
86
|
-
if ((key === 'governs' || key === 'wears') && held?.type === 'ArrayExpression') {
|
|
87
|
-
said[key] = (held.elements ?? [])
|
|
88
|
-
.map((one) => (one?.type === 'Literal' && typeof one.value === 'string' ? one.value : undefined))
|
|
89
|
-
.filter((one): one is string => one !== undefined)
|
|
90
|
-
}
|
|
91
|
-
if (key !== undefined && LISTS.has(key) && held?.type === 'ArrayExpression') rows(held, key as Reach['list'], found, hoisted)
|
|
92
|
-
}
|
|
93
|
-
if (strings['at'] !== undefined) said.at = strings['at']
|
|
94
|
-
if (strings['id'] !== undefined) said.id = strings['id']
|
|
95
|
-
if (strings['describes'] !== undefined) said.describes = strings['describes']
|
|
96
|
-
if (said.at !== undefined || said.id !== undefined) found.push({ list, ...said })
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function reaching(text: string): readonly Reach[] {
|
|
101
|
-
const already = parsed.get(text)
|
|
102
|
-
if (already !== undefined) return already
|
|
103
|
-
|
|
104
|
-
const types = threading()
|
|
105
|
-
const found: Reach[] = []
|
|
106
|
-
const parse = (jsx: boolean): { ast: unknown } =>
|
|
107
|
-
(parser.parseForESLint as (t: string, o: unknown) => { ast: unknown })(text, {
|
|
108
|
-
range: true,
|
|
109
|
-
ecmaVersion: 'latest',
|
|
110
|
-
sourceType: 'module',
|
|
111
|
-
ecmaFeatures: { jsx },
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
const read = ((): { ast: unknown } | undefined => {
|
|
115
|
-
try {
|
|
116
|
-
return parse(true)
|
|
117
|
-
} catch {
|
|
118
|
-
try {
|
|
119
|
-
return parse(false)
|
|
120
|
-
} catch (error) {
|
|
121
|
-
broke.set(text, error instanceof Error ? (error.message.split('\n')[0] ?? error.message) : String(error))
|
|
122
|
-
return undefined
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
})()
|
|
126
|
-
if (read === undefined) {
|
|
127
|
-
parsed.set(text, found)
|
|
128
|
-
return found
|
|
129
|
-
}
|
|
130
|
-
const body = (read.ast as unknown as Held).body ?? []
|
|
131
|
-
const hoisted = new Map<string, Held>()
|
|
132
|
-
for (const statement of body) {
|
|
133
|
-
const held = statement.declaration ?? statement
|
|
134
|
-
if (held.type !== 'VariableDeclaration') continue
|
|
135
|
-
for (const one of held.declarations ?? []) {
|
|
136
|
-
const name = one.id?.name
|
|
137
|
-
if (name !== undefined && one.init?.type === 'ObjectExpression') hoisted.set(name, one.init)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
for (const statement of body) {
|
|
142
|
-
const held = statement.declaration ?? statement
|
|
143
|
-
if (held.type !== 'VariableDeclaration' || held.kind !== 'const') continue
|
|
144
|
-
for (const one of held.declarations ?? []) {
|
|
145
|
-
if (!types.has(annotated(one) ?? '')) continue
|
|
146
|
-
const init = one.init
|
|
147
|
-
for (const node of init?.type === 'ArrayExpression' ? (init.elements ?? []) : [init]) {
|
|
148
|
-
if (node === null || node === undefined || node.type !== 'ObjectExpression') continue
|
|
149
|
-
const said: { id?: string; describes?: string; version?: number } = {}
|
|
150
|
-
for (const property of node.properties ?? []) {
|
|
151
|
-
const key = keyOf(property)
|
|
152
|
-
const value = property.value as Held | undefined
|
|
153
|
-
if ((key === 'id' || key === 'describes') && value?.type === 'Literal' && typeof value.value === 'string') said[key] = value.value
|
|
154
|
-
if (key === 'version' && value?.type === 'Literal' && typeof value.value === 'number') said.version = value.value
|
|
155
|
-
if (key !== undefined && LISTS.has(key) && value?.type === 'ArrayExpression') rows(value, key as Reach['list'], found, hoisted)
|
|
156
|
-
}
|
|
157
|
-
if (said.id !== undefined) found.push({ ...said })
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
parsed.set(text, found)
|
|
163
|
-
return found
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function atsIn(text: string, only?: ReadonlySet<string>): readonly string[] {
|
|
167
|
-
const found: string[] = []
|
|
168
|
-
for (const one of reaching(text)) {
|
|
169
|
-
if (only !== undefined && (one.list === undefined || !only.has(one.list))) continue
|
|
170
|
-
if (one.at !== undefined) found.push(one.at)
|
|
171
|
-
}
|
|
172
|
-
return found
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export const rowsIn = (text: string): readonly Reach[] => reaching(text)
|
|
176
|
-
|
|
177
|
-
const SCHEMA = /^export const ([A-Za-z]+)IslandSchema\b/m
|
|
178
|
-
const DEFINE = /^export function (define[A-Za-z]+)\s*\(/m
|
|
179
|
-
|
|
180
|
-
export type Governed =
|
|
181
|
-
| { readonly kind: 'file'; readonly at: string }
|
|
182
|
-
| { readonly kind: 'folder'; readonly under: string }
|
|
183
|
-
| { readonly kind: 'pattern'; readonly matching: string; readonly under: string }
|
|
184
|
-
|
|
185
|
-
export function governedBy(one: string): Governed {
|
|
186
|
-
if (one.includes('*')) {
|
|
187
|
-
const cut = one.lastIndexOf('/', one.indexOf('*'))
|
|
188
|
-
return { kind: 'pattern', matching: one, under: cut === -1 ? '' : one.slice(0, cut + 1) }
|
|
189
|
-
}
|
|
190
|
-
if (one.endsWith('/')) return { kind: 'folder', under: one }
|
|
191
|
-
return { kind: 'file', at: one }
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export const filesGoverned = (all: readonly string[]): readonly string[] =>
|
|
195
|
-
all.map(governedBy).flatMap((one) => (one.kind === 'file' ? [one.at] : []))
|
|
196
|
-
|
|
197
|
-
export const foldersGoverned = (all: readonly string[]): readonly string[] =>
|
|
198
|
-
all.map(governedBy).flatMap((one) => (one.kind === 'file' ? [] : [one.under]))
|
|
199
|
-
|
|
200
|
-
export function governs(all: readonly string[], path: string): boolean {
|
|
201
|
-
return all.map(governedBy).some((one) => {
|
|
202
|
-
if (one.kind === 'file') return path === one.at
|
|
203
|
-
if (one.kind === 'folder') return path.startsWith(one.under)
|
|
204
|
-
const tail = one.matching.slice(one.under.length).split('*').filter(Boolean)
|
|
205
|
-
const rest = path.startsWith(one.under) ? path.slice(one.under.length) : undefined
|
|
206
|
-
return rest !== undefined && tail.every((part) => rest.includes(part))
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export interface Declared {
|
|
211
|
-
readonly id: string
|
|
212
|
-
readonly at: string
|
|
213
|
-
readonly describes: string
|
|
214
|
-
readonly governs: readonly string[]
|
|
215
|
-
readonly define?: string
|
|
216
|
-
readonly tree?: string
|
|
217
|
-
readonly member?: string
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export const canonical = (at: string): string => at.replace(/^threads\//, `${TREE}/`)
|
|
221
|
-
|
|
222
|
-
export function islandsDeclared(): readonly Declared[] {
|
|
223
|
-
const found: Declared[] = []
|
|
224
|
-
const seen = new Set<string>()
|
|
225
|
-
const all: Reach[] = []
|
|
226
|
-
|
|
227
|
-
for (const at of fromThread()) {
|
|
228
|
-
const held = read(at)
|
|
229
|
-
if (held === undefined) continue
|
|
230
|
-
if (unreadable(at, held) !== undefined) continue
|
|
231
|
-
all.push(...reaching(held))
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
for (const row of all) {
|
|
235
|
-
if (row.id === undefined || row.at === undefined) continue
|
|
236
|
-
const at = canonical(row.at)
|
|
237
|
-
if (seen.has(at)) continue
|
|
238
|
-
const source = read(at)
|
|
239
|
-
if (source === undefined || !SCHEMA.test(source)) continue
|
|
240
|
-
seen.add(at)
|
|
241
|
-
|
|
242
|
-
const tree = `${TREE}/${row.id}.ts`
|
|
243
|
-
const named = all.find((one) => one.at !== undefined && canonical(one.at) === tree)?.id
|
|
244
|
-
|
|
245
|
-
found.push({
|
|
246
|
-
id: row.id,
|
|
247
|
-
at,
|
|
248
|
-
describes: row.describes ?? '',
|
|
249
|
-
governs: row.governs ?? [],
|
|
250
|
-
define: DEFINE.exec(source)?.[1],
|
|
251
|
-
...(named === undefined ? {} : { tree, member: named }),
|
|
252
|
-
})
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return found
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export const declaresIsland = (text: string): string | undefined => SCHEMA.exec(text)?.[1]
|
|
259
|
-
|
|
260
|
-
export function meaningsIn(text: string): readonly Meaning[] {
|
|
261
|
-
const found: Meaning[] = []
|
|
262
|
-
for (const one of reaching(text)) {
|
|
263
|
-
if (one.id === undefined) continue
|
|
264
|
-
found.push({ id: one.id, describes: one.describes ?? '' })
|
|
265
|
-
}
|
|
266
|
-
return found
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export interface Index {
|
|
270
|
-
readonly reached: ReadonlySet<string>
|
|
271
|
-
readonly declared: ReadonlyMap<string, number>
|
|
272
|
-
readonly meanings: ReadonlyMap<string, ReadonlySet<string>>
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
function descended(): Index {
|
|
277
|
-
const pass = descend()
|
|
278
|
-
const reached = new Set<string>([THREAD_AT, ...pass.threaded.keys()])
|
|
279
|
-
const declared = new Map<string, number>()
|
|
280
|
-
for (const [at, held] of pass.threaded) declared.set(at, held.filter((one) => one.list === 'declares').length)
|
|
281
|
-
const meanings = new Map<string, ReadonlySet<string>>()
|
|
282
|
-
for (const [word, held] of pass.coined) meanings.set(word, new Set(held.said))
|
|
283
|
-
return { reached, declared, meanings }
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export function index(): Index {
|
|
287
|
-
tree ??= descended()
|
|
288
|
-
return tree
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function forget(): void {
|
|
292
|
-
parsed.clear()
|
|
293
|
-
broke.clear()
|
|
294
|
-
tree = undefined
|
|
295
|
-
forgetDescent()
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export const threaded = (): ReadonlySet<string> => index().reached
|
|
299
|
-
|
|
300
|
-
export const parses = (path: string): boolean => FOLLOWABLE.test(path)
|
|
301
|
-
|
|
302
|
-
export function unreadable(path: string, text: string): string | undefined {
|
|
303
|
-
if (!parses(path)) return undefined
|
|
304
|
-
atsIn(text)
|
|
305
|
-
const said = broke.get(text)
|
|
306
|
-
if (said === undefined) return undefined
|
|
307
|
-
return `this file cannot be parsed, so no law that reads its structure measured anything on it — ${said}`
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
export const names = (path: string, text: string): readonly string[] => (parses(path) ? atsIn(text) : [])
|
|
311
|
-
|
|
312
|
-
export const slopnator: Slopnator = {
|
|
313
|
-
holds: (_node, at) => {
|
|
314
|
-
const path = here(at.filename)
|
|
315
|
-
if (path === THREAD_AT) return false
|
|
316
|
-
const why = unreadable(path, at.text)
|
|
317
|
-
if (why !== undefined) return why
|
|
318
|
-
const pass = descend()
|
|
319
|
-
if (pass.threaded.has(path)) return false
|
|
320
|
-
if (names(path, at.text).length > 0) return false
|
|
321
|
-
const by = pass.mentioned.get(path) ?? []
|
|
322
|
-
const cut = by.filter((one) => one !== THREAD_AT && !pass.threaded.has(one))
|
|
323
|
-
if (cut.length > 0) {
|
|
324
|
-
return `broken thread — ${cut.join(' and ')} names ${path}, and nothing declares ${cut.length === 1 ? 'it' : 'them'}, so the descent from ${THREAD_AT} never arrives`
|
|
325
|
-
}
|
|
326
|
-
if (by.length > 0) {
|
|
327
|
-
return `broken thread — ${by.join(' and ')} names ${path} and is itself declared, and the descent still never arrived, so the row was read and never walked`
|
|
328
|
-
}
|
|
329
|
-
return `nothing that traces back to ${TREE}/ names ${path}, so the descent from ${THREAD_AT} never reaches it`
|
|
330
|
-
},
|
|
331
|
-
proves: {
|
|
332
|
-
wrong: [
|
|
333
|
-
'export const ORPHAN = 2',
|
|
334
|
-
'export const LOOSE = { id: 2 }',
|
|
335
|
-
{
|
|
336
|
-
files: [
|
|
337
|
-
{ at: 'threads/_names_it.ts', source: "export const OWNER = {\n declares: [\n {\n at: 'scripts/_probe_bare.ts',\n },\n ],\n}\n" },
|
|
338
|
-
{ at: 'scripts/_probe_bare.ts', source: 'export const CHILD = 1\n' },
|
|
339
|
-
],
|
|
340
|
-
at: 'scripts/_probe_bare.ts',
|
|
341
|
-
},
|
|
342
|
-
{
|
|
343
|
-
files: [
|
|
344
|
-
{
|
|
345
|
-
at: 'threads/_names_it.ts',
|
|
346
|
-
source:
|
|
347
|
-
"export const REAL: Threaded = {\n declares: [\n {\n at: 'threads/_names_it.ts',\n },\n ],\n}\n\nexport const BARE = {\n declares: [\n {\n at: 'scripts/_probe_after.ts',\n },\n ],\n}\n",
|
|
348
|
-
},
|
|
349
|
-
{ at: 'scripts/_probe_after.ts', source: 'export const CHILD = 1\n' },
|
|
350
|
-
],
|
|
351
|
-
at: 'scripts/_probe_after.ts',
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
files: [
|
|
355
|
-
{ at: 'threads/_names_it.ts', source: "export let LOOSE = {\n declares: [\n {\n at: 'scripts/_probe_let.ts',\n },\n ],\n}\n" },
|
|
356
|
-
{ at: 'scripts/_probe_let.ts', source: 'export const CHILD = 1\n' },
|
|
357
|
-
],
|
|
358
|
-
at: 'scripts/_probe_let.ts',
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
files: [
|
|
362
|
-
{ at: 'threads/_names_it.ts', source: "export var LOOSE = {\n declares: [\n {\n at: 'scripts/_probe_var.ts',\n },\n ],\n}\n" },
|
|
363
|
-
{ at: 'scripts/_probe_var.ts', source: 'export const CHILD = 1\n' },
|
|
364
|
-
],
|
|
365
|
-
at: 'scripts/_probe_var.ts',
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
files: [
|
|
369
|
-
{ at: 'threads/_names_it.ts', source: "export default {\n declares: [\n {\n at: 'scripts/_probe_default.ts',\n },\n ],\n}\n" },
|
|
370
|
-
{ at: 'scripts/_probe_default.ts', source: 'export const CHILD = 1\n' },
|
|
371
|
-
],
|
|
372
|
-
at: 'scripts/_probe_default.ts',
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
files: [
|
|
376
|
-
{ at: 'threads/_names_it.ts', source: "export function held() {\n return {\n declares: [\n {\n at: 'scripts/_probe_fn.ts',\n },\n ],\n }\n}\n" },
|
|
377
|
-
{ at: 'scripts/_probe_fn.ts', source: 'export const CHILD = 1\n' },
|
|
378
|
-
],
|
|
379
|
-
at: 'scripts/_probe_fn.ts',
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
files: [
|
|
383
|
-
{ at: 'threads/_names_it.ts', source: "export class Held {\n held = {\n declares: [\n {\n at: 'scripts/_probe_class.ts',\n },\n ],\n }\n}\n" },
|
|
384
|
-
{ at: 'scripts/_probe_class.ts', source: 'export const CHILD = 1\n' },
|
|
385
|
-
],
|
|
386
|
-
at: 'scripts/_probe_class.ts',
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
files: [
|
|
390
|
-
{
|
|
391
|
-
at: 'threads/_names_it.ts',
|
|
392
|
-
source: "interface Held { declares: unknown[] }\nexport const FAKE: Held = {\n declares: [\n {\n at: 'scripts/_probe_fake.ts',\n },\n ],\n}\n",
|
|
393
|
-
},
|
|
394
|
-
{ at: 'scripts/_probe_fake.ts', source: 'export const CHILD = 1\n' },
|
|
395
|
-
],
|
|
396
|
-
at: 'scripts/_probe_fake.ts',
|
|
397
|
-
},
|
|
398
|
-
],
|
|
399
|
-
right: [
|
|
400
|
-
{
|
|
401
|
-
files: [
|
|
402
|
-
{ at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
|
|
403
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_names_it.ts',\n },\n ],\n}\n" },
|
|
404
|
-
{ at: 'threads/_names_it.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OWNER: Threaded = {\n declares: [\n {\n at: 'scripts/_probe_child.ts',\n },\n ],\n}\n" },
|
|
405
|
-
{ at: 'scripts/_probe_child.ts', source: 'export const CHILD = 1\n' },
|
|
406
|
-
],
|
|
407
|
-
at: 'scripts/_probe_child.ts',
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
files: [
|
|
411
|
-
{ at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
|
|
412
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_names_it.ts',\n },\n ],\n}\n" },
|
|
413
|
-
{ at: 'threads/_names_it.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const OWNER: Threaded = {\n declares: [\n {\n at: 'scripts/_probe_parent.ts',\n },\n ],\n}\n" },
|
|
414
|
-
{ at: 'scripts/_probe_parent.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const MID: Threaded = {\n declares: [\n {\n at: 'scripts/_probe_leaf.ts',\n },\n ],\n}\n" },
|
|
415
|
-
{ at: 'scripts/_probe_leaf.ts', source: 'export const LEAF = 1\n' },
|
|
416
|
-
],
|
|
417
|
-
at: 'scripts/_probe_leaf.ts',
|
|
418
|
-
},
|
|
419
|
-
],
|
|
420
|
-
},
|
|
421
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync } from '../../integrations/disk.ts'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { Slopnator } from '../slopnator.ts'
|
|
5
|
-
import { named as packageOf } from '../../core/scope.ts'
|
|
6
|
-
|
|
7
|
-
const SKIP = new Set(['node_modules', '.next', '.git', 'dist', '_generated'])
|
|
8
|
-
|
|
9
|
-
const CODE = /\.(tsx?|jsx?)$/
|
|
10
|
-
|
|
11
|
-
const TYPED = /readonly\s+([A-Za-z][A-Za-z0-9]*)\??\s*:\s*MessageKey\b/g
|
|
12
|
-
|
|
13
|
-
function walk(at: string, found: string[] = []): readonly string[] {
|
|
14
|
-
let entries: readonly { name: string; isDirectory: () => boolean }[]
|
|
15
|
-
try {
|
|
16
|
-
entries = readdirSync(at, { withFileTypes: true })
|
|
17
|
-
} catch {
|
|
18
|
-
return found
|
|
19
|
-
}
|
|
20
|
-
for (const entry of entries) {
|
|
21
|
-
if (SKIP.has(entry.name)) continue
|
|
22
|
-
const path = join(at, entry.name)
|
|
23
|
-
if (entry.isDirectory()) walk(path, found)
|
|
24
|
-
else if (CODE.test(entry.name)) found.push(path)
|
|
25
|
-
}
|
|
26
|
-
return found
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function keyProps(root: string): readonly string[] {
|
|
30
|
-
const found = new Set<string>()
|
|
31
|
-
const home = join(root, 'node_modules', packageOf('ui'))
|
|
32
|
-
if (!existsSync(home)) return []
|
|
33
|
-
for (const file of walk(home)) {
|
|
34
|
-
for (const one of readFileSync(file, 'utf8').matchAll(TYPED)) found.add(one[1] ?? '')
|
|
35
|
-
}
|
|
36
|
-
return [...found].sort()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function languagesIn(root: string): { readonly held: readonly string[]; readonly source: string | undefined } {
|
|
40
|
-
const at = join(root, '_threads', 'localizations.ts')
|
|
41
|
-
if (!existsSync(at)) return { held: [], source: undefined }
|
|
42
|
-
const text = readFileSync(at, 'utf8')
|
|
43
|
-
const held: string[] = []
|
|
44
|
-
let source: string | undefined
|
|
45
|
-
for (const one of text.matchAll(/id:\s*(?:SOURCE_ROW|'([^']+)')\s*,[\s\S]{0,200}?is:\s*'([^']*)'/g)) {
|
|
46
|
-
const id = one[1]
|
|
47
|
-
if (id === undefined || id === 'source') {
|
|
48
|
-
source = one[2]
|
|
49
|
-
continue
|
|
50
|
-
}
|
|
51
|
-
held.push(id)
|
|
52
|
-
}
|
|
53
|
-
return { held, source }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export const slopnator: Slopnator = {
|
|
57
|
-
checks: (root) => {
|
|
58
|
-
const { held } = languagesIn(root)
|
|
59
|
-
if (held.length === 0) return []
|
|
60
|
-
|
|
61
|
-
const props = keyProps(root)
|
|
62
|
-
if (props.length === 0) return []
|
|
63
|
-
|
|
64
|
-
const named = new RegExp(`\\b(?:${props.join('|')})\\s*=\\s*"([^"]+)"`, 'g')
|
|
65
|
-
|
|
66
|
-
const used = new Map<string, string>()
|
|
67
|
-
for (const file of walk(root)) {
|
|
68
|
-
const text = readFileSync(file, 'utf8')
|
|
69
|
-
for (const one of text.matchAll(named)) {
|
|
70
|
-
const key = one[1] ?? ''
|
|
71
|
-
if (!used.has(key)) used.set(key, file.slice(root.length).replace(/^\/+/, ''))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (used.size === 0) return []
|
|
75
|
-
|
|
76
|
-
const findings: string[] = []
|
|
77
|
-
for (const language of held) {
|
|
78
|
-
const at = join(root, 'i18n', `${language}.json`)
|
|
79
|
-
if (!existsSync(at)) {
|
|
80
|
-
findings.push(
|
|
81
|
-
`i18n/${language}.json is not there and \`${language}\` is a declared language, so every key this ` +
|
|
82
|
-
'interface names goes unanswered in it — run `thread i18n`',
|
|
83
|
-
)
|
|
84
|
-
continue
|
|
85
|
-
}
|
|
86
|
-
let wording: Readonly<Record<string, unknown>>
|
|
87
|
-
try {
|
|
88
|
-
wording = JSON.parse(readFileSync(at, 'utf8')) as Readonly<Record<string, unknown>>
|
|
89
|
-
} catch {
|
|
90
|
-
findings.push(
|
|
91
|
-
`i18n/${language}.json will not parse, so what this app says in \`${language}\` cannot be known — ` +
|
|
92
|
-
'a reader that cannot read is not a reader that found nothing',
|
|
93
|
-
)
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
for (const [key, where] of [...used].sort()) {
|
|
97
|
-
const said = wording[key]
|
|
98
|
-
if (typeof said === 'string' && said !== '') continue
|
|
99
|
-
findings.push(
|
|
100
|
-
`\`${key}\` is named by ${where} and says nothing in \`${language}\` — an untranslated key is a ` +
|
|
101
|
-
'blank on the screen, and it is an error rather than a word that comes out in the wrong language',
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return findings
|
|
106
|
-
},
|
|
107
|
-
proves: {
|
|
108
|
-
wrong: [
|
|
109
|
-
{
|
|
110
|
-
files: [
|
|
111
|
-
{ at: '_threads/localizations.ts', source: "export const LOCALIZATION = {\n holds: [\n { id: 'source', is: 'en' },\n { id: 'en', is: 'English' },\n ],\n}\n" },
|
|
112
|
-
{ at: `node_modules/${packageOf('ui')}/probe.ts`, source: 'export interface P {\n readonly label: MessageKey\n}\n' },
|
|
113
|
-
{ at: 'compositions/cmp_probe.tsx', source: 'export const P = () => <Button label="probe.blank" />\n' },
|
|
114
|
-
{ at: 'i18n/en.json', source: '{\n "probe.blank": ""\n}\n' },
|
|
115
|
-
],
|
|
116
|
-
at: 'compositions/cmp_probe.tsx',
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
files: [
|
|
120
|
-
{ at: '_threads/localizations.ts', source: "export const LOCALIZATION = {\n holds: [\n { id: 'source', is: 'en' },\n { id: 'en', is: 'English' },\n ],\n}\n" },
|
|
121
|
-
{ at: `node_modules/${packageOf('ui')}/probe.ts`, source: 'export interface P {\n readonly label: MessageKey\n}\n' },
|
|
122
|
-
{ at: 'compositions/cmp_probe.tsx', source: 'export const P = () => <Button label="probe.missing" />\n' },
|
|
123
|
-
{ at: 'i18n/en.json', source: '{\n "probe.other": "Something"\n}\n' },
|
|
124
|
-
],
|
|
125
|
-
at: 'compositions/cmp_probe.tsx',
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
right: [
|
|
129
|
-
{
|
|
130
|
-
files: [
|
|
131
|
-
{ at: '_threads/localizations.ts', source: "export const LOCALIZATION = {\n holds: [\n { id: 'source', is: 'en' },\n { id: 'en', is: 'English' },\n ],\n}\n" },
|
|
132
|
-
{ at: `node_modules/${packageOf('ui')}/probe.ts`, source: 'export interface P {\n readonly label: MessageKey\n}\n' },
|
|
133
|
-
{ at: 'compositions/cmp_probe.tsx', source: 'export const P = () => <Button label="probe.answered" />\n' },
|
|
134
|
-
{ at: 'i18n/en.json', source: '{\n "probe.answered": "Answered"\n}\n' },
|
|
135
|
-
],
|
|
136
|
-
at: 'compositions/cmp_probe.tsx',
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
},
|
|
140
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import type { Slopnator } from '../slopnator.ts'
|
|
2
|
-
|
|
3
|
-
import { descend, read, reached } from '../../core/descend.ts'
|
|
4
|
-
import { rowsIn } from './sl_unthreaded.ts'
|
|
5
|
-
|
|
6
|
-
interface Held {
|
|
7
|
-
readonly type?: string
|
|
8
|
-
readonly name?: string
|
|
9
|
-
readonly value?: unknown
|
|
10
|
-
readonly computed?: boolean
|
|
11
|
-
readonly object?: Held
|
|
12
|
-
readonly property?: Held
|
|
13
|
-
readonly init?: Held
|
|
14
|
-
readonly id?: Held
|
|
15
|
-
readonly declaration?: Held
|
|
16
|
-
readonly declarations?: readonly Held[]
|
|
17
|
-
readonly left?: Held
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const SECRETS = 'secrets'
|
|
21
|
-
|
|
22
|
-
function wears(): readonly string[] {
|
|
23
|
-
const found = new Set<string>()
|
|
24
|
-
|
|
25
|
-
descend()
|
|
26
|
-
|
|
27
|
-
for (const at of reached()) {
|
|
28
|
-
const text = read(at)
|
|
29
|
-
if (text === undefined) continue
|
|
30
|
-
|
|
31
|
-
let waiting: string[] = []
|
|
32
|
-
for (const one of rowsIn(text)) {
|
|
33
|
-
if (one.id === undefined) continue
|
|
34
|
-
if (one.list === 'declares') {
|
|
35
|
-
waiting.push(one.id)
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
38
|
-
if (one.list !== undefined) continue
|
|
39
|
-
if (one.id === SECRETS) {
|
|
40
|
-
for (const id of waiting) {
|
|
41
|
-
const cut = id.indexOf('_')
|
|
42
|
-
if (cut > 0) found.add(id.slice(0, cut + 1))
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
waiting = []
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return [...found]
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const HOLDS = 'process'
|
|
53
|
-
|
|
54
|
-
const ENV = 'env'
|
|
55
|
-
|
|
56
|
-
const isEnv = (one: Held | undefined): boolean =>
|
|
57
|
-
one?.type === 'MemberExpression' &&
|
|
58
|
-
one.object?.type === 'Identifier' &&
|
|
59
|
-
one.object.name === HOLDS &&
|
|
60
|
-
one.property?.type === 'Identifier' &&
|
|
61
|
-
one.property.name === ENV
|
|
62
|
-
|
|
63
|
-
function stated(node: Held | undefined, ancestors: readonly unknown[]): string | undefined {
|
|
64
|
-
if (node === undefined) return undefined
|
|
65
|
-
if (node.type === 'Literal' && typeof node.value === 'string') return node.value
|
|
66
|
-
if (node.type !== 'Identifier' || node.name === undefined) return undefined
|
|
67
|
-
for (const one of ancestors) {
|
|
68
|
-
const body = (one as { readonly body?: unknown }).body
|
|
69
|
-
for (const each of Array.isArray(body) ? (body as readonly unknown[]) : []) {
|
|
70
|
-
const line = each as Held
|
|
71
|
-
const held = line.type === 'ExportNamedDeclaration' ? line.declaration : line
|
|
72
|
-
if (held?.type !== 'VariableDeclaration') continue
|
|
73
|
-
for (const said of held.declarations ?? []) {
|
|
74
|
-
if (said.id?.type !== 'Identifier' || said.id.name !== node.name) continue
|
|
75
|
-
return said.init?.type === 'Literal' && typeof said.init.value === 'string' ? said.init.value : undefined
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return undefined
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export const slopnator: Slopnator = {
|
|
83
|
-
on: ['MemberExpression'],
|
|
84
|
-
holds: (node, at) => {
|
|
85
|
-
const held = node as Held
|
|
86
|
-
if (!isEnv(held.object)) return false
|
|
87
|
-
|
|
88
|
-
const up = at.ancestors()
|
|
89
|
-
const parent = up[up.length - 1] as Held | undefined
|
|
90
|
-
if (parent?.type === 'AssignmentExpression' && parent.left === held) return false
|
|
91
|
-
|
|
92
|
-
const name = held.computed === true ? stated(held.property, up) : held.property?.name
|
|
93
|
-
|
|
94
|
-
if (name === undefined) {
|
|
95
|
-
return (
|
|
96
|
-
'this takes a value out of the environment under a name nobody reading the file can see, so it can name ' +
|
|
97
|
-
'any secret this app declares. A declared value is opened by the vault, which refuses a name no row states ' +
|
|
98
|
-
'and a value nothing sealed, and neither of those refusals can happen here'
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const wearing = wears().find((one) => name.toLowerCase().startsWith(one))
|
|
103
|
-
if (wearing === undefined) return false
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
name +
|
|
107
|
-
' wears the prefix ' +
|
|
108
|
-
wearing +
|
|
109
|
-
', so it names a secret, and this takes it from the environment instead of the vault — past the check that a ' +
|
|
110
|
-
'row declares it, and past the sealed store the value was written to'
|
|
111
|
-
)
|
|
112
|
-
},
|
|
113
|
-
proves: {
|
|
114
|
-
wrong: [
|
|
115
|
-
'const key = process.env[named.toUpperCase()]',
|
|
116
|
-
{
|
|
117
|
-
files: [
|
|
118
|
-
{ at: 'thread/core/thread.ts', source: "export interface Threaded {\n readonly id?: string\n readonly at?: string\n readonly describes?: string\n readonly declares?: readonly Threaded[]\n readonly derives?: readonly Threaded[]\n readonly seeds?: readonly Threaded[]\n}\n" },
|
|
119
|
-
{ at: 'threads/thread.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const THREAD: Threaded = {\n declares: [\n {\n at: 'threads/_probe_secrets.ts',\n },\n ],\n}\n" },
|
|
120
|
-
{ at: 'threads/_probe_secrets.ts', source: "import type { Threaded } from '../thread/core/thread.ts'\n\nexport const SECRETS: Threaded = {\n id: 'secrets',\n declares: [\n {\n id: 'sec_model',\n at: 'threads/_probe_secrets.ts',\n },\n ],\n}\n" },
|
|
121
|
-
{ at: 'probe.tsx', source: "const key = process.env['SEC_MODEL']\n" },
|
|
122
|
-
],
|
|
123
|
-
at: 'probe.tsx',
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
right: [
|
|
127
|
-
"const mode = process.env['NODE_ENV']",
|
|
128
|
-
"const DEPTH = 'THREAD_DEPTH'\n\nconst depth = Number(process.env[DEPTH] ?? '0')",
|
|
129
|
-
"const held = opened(['sec_model'])",
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
}
|