@prestack/thread 0.1.3 → 0.1.5
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 +563 -0
- package/dist/core/actor.d.ts +1 -1
- package/dist/core/actor.js +5 -4
- package/dist/core/actor.ts +5 -4
- package/dist/core/attach.d.ts +0 -7
- package/dist/core/attach.js +38 -67
- package/dist/core/attach.ts +34 -80
- package/dist/core/chain.js +13 -17
- package/dist/core/chain.ts +11 -15
- package/dist/core/condition.d.ts +4 -4
- package/dist/core/condition.js +2 -2
- package/dist/core/descend.d.ts +4 -2
- package/dist/core/descend.js +91 -23
- package/dist/core/descend.ts +90 -21
- package/dist/core/errors/catalogue.d.ts +52 -0
- package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
- package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
- package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
- package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
- package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
- package/dist/core/errors/namespaces.d.ts +3 -0
- package/dist/core/errors/namespaces.js +15 -0
- package/dist/core/errors/namespaces.ts +19 -0
- package/dist/core/finding.js +2 -2
- package/dist/core/finding.ts +1 -2
- package/dist/core/init.d.ts +1 -1
- package/dist/core/init.js +51 -5
- package/dist/core/init.ts +58 -5
- package/dist/core/invoked.d.ts +1 -1
- package/dist/core/invoked.js +3 -1
- package/dist/core/invoked.ts +5 -2
- package/dist/core/round.js +36 -22
- package/dist/core/round.ts +26 -9
- package/dist/core/rows.d.ts +4 -0
- package/dist/core/rows.js +31 -2
- package/dist/core/rows.ts +33 -1
- package/dist/core/scope.d.ts +1 -0
- package/dist/core/scope.js +1 -0
- package/dist/core/scope.ts +2 -0
- package/dist/core/thread.d.ts +3 -3
- package/dist/core/tree.d.ts +4 -1
- package/dist/core/tree.js +26 -6
- package/dist/core/tree.ts +29 -6
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +3 -1
- package/dist/core/types.ts +2 -1
- package/dist/derivation/attach.js +3 -2
- package/dist/derivation/attach.ts +2 -1
- package/dist/derivation/generators/build.js +6 -6
- package/dist/derivation/generators/build.ts +1 -1
- package/dist/derivation/generators/check.js +1 -1
- package/dist/derivation/generators/determinism.js +1 -1
- package/dist/derivation/generators/release.js +60 -47
- package/dist/derivation/generators/release.ts +66 -43
- package/dist/derivation/release.d.ts +7 -3
- package/dist/derivation/release.js +14 -1
- package/dist/derivation/release.ts +22 -2
- package/dist/integrations/attach.d.ts +4 -0
- package/dist/integrations/attach.js +37 -56
- package/dist/integrations/attach.ts +54 -60
- package/dist/integrations/cli.d.ts +37 -0
- package/dist/integrations/cli.js +440 -0
- package/dist/integrations/cli.ts +543 -0
- package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
- package/dist/integrations/disk.js +435 -0
- package/dist/integrations/disk.ts +465 -0
- package/dist/integrations/generators/read.js +6 -6
- package/dist/integrations/generators/read.ts +4 -4
- package/dist/integrations/generators/vault.js +10 -11
- package/dist/integrations/generators/vault.ts +4 -5
- package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
- package/dist/integrations/https.js +170 -0
- package/dist/integrations/https.ts +216 -0
- package/dist/integrations/pglite.d.ts +26 -0
- package/dist/integrations/pglite.js +143 -0
- package/dist/integrations/pglite.ts +206 -0
- package/dist/integrations/secret.d.ts +8 -2
- package/dist/integrations/secret.js +3 -5
- package/dist/integrations/secret.ts +10 -6
- package/dist/integrations/target.d.ts +155 -14
- package/dist/integrations/target.js +171 -50
- package/dist/integrations/target.ts +377 -61
- package/dist/integrations/vault.d.ts +1 -0
- package/dist/integrations/vault.js +20 -5
- package/dist/integrations/vault.ts +17 -5
- package/dist/integrity/attach.js +8 -77
- package/dist/integrity/attach.ts +7 -76
- package/dist/integrity/check-threads.js +5 -5
- package/dist/integrity/check-threads.ts +2 -2
- package/dist/integrity/constraint.d.ts +0 -1
- package/dist/integrity/constraint.js +267 -366
- package/dist/integrity/constraint.ts +267 -375
- package/dist/integrity/generators/compiler.js +14 -17
- package/dist/integrity/generators/compiler.ts +10 -15
- package/dist/integrity/generators/gate.js +15 -15
- package/dist/integrity/generators/gate.ts +10 -10
- package/dist/integrity/generators/issues.js +7 -7
- package/dist/integrity/generators/publish.js +39 -57
- package/dist/integrity/generators/publish.ts +44 -55
- package/dist/integrity/generators/trial.d.ts +3 -3
- package/dist/integrity/generators/trial.js +133 -165
- package/dist/integrity/generators/trial.ts +145 -169
- package/dist/integrity/lint.js +9 -13
- package/dist/integrity/lint.ts +7 -10
- package/dist/integrity/lsp/bin.js +5 -4
- package/dist/integrity/lsp/bin.ts +3 -1
- package/dist/integrity/lsp/diagnostics.js +3 -3
- package/dist/integrity/lsp/diagnostics.ts +1 -1
- package/dist/integrity/lsp/lint.js +19 -30
- package/dist/integrity/lsp/lint.ts +16 -32
- package/dist/integrity/lsp/server.js +3 -3
- package/dist/integrity/slopnator.d.ts +3 -13
- package/dist/integrity/slopnator.js +59 -38
- package/dist/integrity/slopnator.ts +54 -38
- package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
- package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
- package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
- package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
- package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
- package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
- package/dist/integrity/slopnators/sl_inert_island.js +10 -8
- package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
- package/dist/integrity/slopnators/sl_missing_row.js +6 -51
- package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
- package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
- package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
- package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
- package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
- package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
- package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
- package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
- package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
- package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
- package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
- package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
- package/dist/integrity/slopnators/sl_stale_path.js +5 -22
- package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
- package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
- package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
- package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
- package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
- package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
- package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
- package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
- package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
- package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
- package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
- package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
- package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
- package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
- package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
- package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
- package/dist/integrity/slopnators/sl_underived_route.js +61 -0
- package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
- package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
- package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
- package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
- package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
- package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
- package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
- package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
- package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
- package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
- package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
- package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
- package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
- package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
- package/dist/integrity/slopnators/sl_unversioned.js +6 -4
- package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
- package/dist/integrity/sweep.d.ts +1 -0
- package/dist/integrity/sweep.js +105 -42
- package/dist/integrity/sweep.ts +115 -42
- package/dist/knowledge/attach.js +4 -3
- package/dist/knowledge/attach.ts +3 -2
- package/dist/knowledge/generators/docsgen.js +8 -6
- package/dist/knowledge/generators/docsgen.ts +4 -2
- package/dist/knowledge/generators/glossary.js +4 -4
- package/dist/knowledge/generators/glossary.ts +1 -1
- package/dist/operations/access.d.ts +23 -0
- package/dist/operations/access.js +28 -1
- package/dist/operations/access.ts +43 -0
- package/dist/operations/attach.js +33 -113
- package/dist/operations/attach.ts +51 -117
- package/dist/operations/capabilities.d.ts +14 -5
- package/dist/operations/capabilities.js +13 -4
- package/dist/operations/capabilities.ts +27 -8
- package/dist/operations/dispatch.d.ts +8 -2
- package/dist/operations/dispatch.js +128 -185
- package/dist/operations/dispatch.ts +138 -191
- package/dist/operations/error-results.d.ts +1 -1
- package/dist/operations/error-results.js +2 -2
- package/dist/operations/error-results.ts +2 -2
- package/dist/operations/generators/capability.d.ts +11 -1
- package/dist/operations/generators/capability.js +481 -224
- package/dist/operations/generators/capability.ts +562 -233
- package/dist/operations/generators/compile.js +66 -39
- package/dist/operations/generators/compile.ts +78 -39
- package/dist/operations/generators/permits.js +11 -12
- package/dist/operations/generators/permits.ts +3 -4
- package/dist/operations/generators/seed.d.ts +1 -0
- package/dist/operations/generators/seed.js +19 -17
- package/dist/operations/generators/seed.ts +22 -14
- package/dist/operations/generators/trial.d.ts +6 -8
- package/dist/operations/generators/trial.js +200 -70
- package/dist/operations/generators/trial.ts +230 -93
- package/dist/operations/nodes.d.ts +15 -16
- package/dist/operations/nodes.ts +16 -17
- package/dist/operations/operations.d.ts +40 -4
- package/dist/operations/operations.js +1 -0
- package/dist/operations/operations.ts +61 -4
- package/dist/operations/result.d.ts +2 -2
- package/dist/operations/result.ts +2 -2
- package/package.json +1 -4
- package/dist/core/answers.d.ts +0 -171
- package/dist/core/answers.js +0 -789
- package/dist/core/answers.ts +0 -999
- package/dist/core/client.d.ts +0 -63
- package/dist/core/client.js +0 -249
- package/dist/core/client.ts +0 -289
- package/dist/core/files.js +0 -171
- package/dist/core/files.ts +0 -222
- package/dist/integrations/module.d.ts +0 -6
- package/dist/integrations/module.js +0 -153
- package/dist/integrations/module.ts +0 -191
- package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
- package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
- package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
- package/dist/operations/cost.d.ts +0 -14
- package/dist/operations/cost.js +0 -37
- package/dist/operations/cost.ts +0 -58
- package/dist/operations/errors/errors.d.ts +0 -52
- package/dist/operations/http.js +0 -124
- package/dist/operations/http.ts +0 -169
- package/dist/operations/memory.d.ts +0 -104
- package/dist/operations/memory.js +0 -365
- package/dist/operations/memory.ts +0 -470
- package/dist/operations/metering.d.ts +0 -55
- package/dist/operations/metering.js +0 -247
- package/dist/operations/metering.ts +0 -328
- package/dist/operations/queue-policy.d.ts +0 -3
- package/dist/operations/queue-policy.js +0 -8
- package/dist/operations/queue-policy.ts +0 -10
- package/dist/operations/seams.d.ts +0 -239
- package/dist/operations/seams.js +0 -45
- package/dist/operations/seams.ts +0 -364
- package/dist/operations/store.d.ts +0 -14
- package/dist/operations/store.js +0 -1
- package/dist/operations/store.ts +0 -29
- /package/dist/{operations → core}/errors/classes.d.ts +0 -0
- /package/dist/{operations → core}/errors/classes.js +0 -0
- /package/dist/{operations → core}/errors/classes.ts +0 -0
- /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
- /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
package/dist/core/answers.ts
DELETED
|
@@ -1,999 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from 'node:crypto'
|
|
2
|
-
import { existsSync, readFileSync, rmSync, writeFileSync } from './files.ts'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { dirname, join, relative } from 'node:path'
|
|
5
|
-
import { argv, env, exit, stderr, stdin, stdout } from 'node:process'
|
|
6
|
-
import { createInterface } from 'node:readline'
|
|
7
|
-
import { pathToFileURL } from 'node:url'
|
|
8
|
-
import { CONSTRAINTS } from '../integrity/constraint.ts'
|
|
9
|
-
import type { Row } from './descend.ts'
|
|
10
|
-
import { LISTS, descend, listFields, listShape, shapeFields, shapesNamed, type Field } from './descend.ts'
|
|
11
|
-
import { quoted, valuesIn } from './rows.ts'
|
|
12
|
-
import type { Tree } from './tree.ts'
|
|
13
|
-
|
|
14
|
-
export const RECEIPT = '--receipt'
|
|
15
|
-
|
|
16
|
-
export const ENDS = '.'
|
|
17
|
-
|
|
18
|
-
export const FROM = '--from'
|
|
19
|
-
|
|
20
|
-
export const CHAINED = '--chained'
|
|
21
|
-
|
|
22
|
-
export const BODY = 'body'
|
|
23
|
-
|
|
24
|
-
export const WRITES = 'writes'
|
|
25
|
-
|
|
26
|
-
export const asFlag = (key: string): string => `--${key}`
|
|
27
|
-
|
|
28
|
-
export function refuse(because: string): never {
|
|
29
|
-
stderr.write(`create: ${because}\n`)
|
|
30
|
-
exit(1)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function slug(one: string): string {
|
|
34
|
-
return one
|
|
35
|
-
.trim()
|
|
36
|
-
.toLowerCase()
|
|
37
|
-
.replace(/[^a-z0-9]+/g, '_')
|
|
38
|
-
.replace(/^_+|_+$/g, '')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function within(root: string, path: string): string {
|
|
42
|
-
return relative(root, path).split('\\').join('/')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const NAMED = /^([a-z][A-Za-z0-9_.-]*):[ \t]?(.*)$/
|
|
46
|
-
|
|
47
|
-
export function parsed(text: string): readonly { readonly at: number; readonly key: string; readonly value: string }[] {
|
|
48
|
-
const lines = text.split('\n')
|
|
49
|
-
const found: { at: number; key: string; value: string }[] = []
|
|
50
|
-
for (let at = 0; at < lines.length; at += 1) {
|
|
51
|
-
const line = lines[at] ?? ''
|
|
52
|
-
if (line.trim() === '' || line.trimStart().startsWith('#')) continue
|
|
53
|
-
const held = NAMED.exec(line)
|
|
54
|
-
if (held === null) {
|
|
55
|
-
refuse(
|
|
56
|
-
`line ${String(at + 1)} is neither blank nor an answer: ${JSON.stringify(line.slice(0, 60))}. ` +
|
|
57
|
-
'Every answer is written `name: value`, and a body is written under `body:` and ended by a line ' +
|
|
58
|
-
`holding only ${ENDS}.`,
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
const key = held[1] ?? ''
|
|
62
|
-
if (key !== 'body') {
|
|
63
|
-
found.push({ at: at + 1, key, value: (held[2] ?? '').trim() })
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const body: string[] = []
|
|
68
|
-
const beside = held[2] ?? ''
|
|
69
|
-
if (beside.trim() !== '') body.push(beside)
|
|
70
|
-
let shut = false
|
|
71
|
-
for (at += 1; at < lines.length; at += 1) {
|
|
72
|
-
if ((lines[at] ?? '').trim() === ENDS) {
|
|
73
|
-
shut = true
|
|
74
|
-
break
|
|
75
|
-
}
|
|
76
|
-
body.push(lines[at] ?? '')
|
|
77
|
-
}
|
|
78
|
-
if (!shut) {
|
|
79
|
-
refuse(
|
|
80
|
-
`the \`body:\` opened on line ${String(found.length === 0 ? 1 : at)} is never closed — a body ends at a ` +
|
|
81
|
-
`line holding only ${ENDS}, and this one runs to the end of the input.`,
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
found.push({ at: at + 1, key, value: body.join('\n').trim() })
|
|
85
|
-
}
|
|
86
|
-
return found
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function scoped(
|
|
90
|
-
given: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
91
|
-
opens: string,
|
|
92
|
-
): {
|
|
93
|
-
readonly own: readonly { readonly at: number; readonly key: string; readonly value: string }[]
|
|
94
|
-
readonly each: readonly (readonly { readonly at: number; readonly key: string; readonly value: string }[])[]
|
|
95
|
-
} {
|
|
96
|
-
const own: { at: number; key: string; value: string }[] = []
|
|
97
|
-
const each: { at: number; key: string; value: string }[][] = []
|
|
98
|
-
const lists = new Set<string>(LISTS)
|
|
99
|
-
let mine = false
|
|
100
|
-
for (const one of given) {
|
|
101
|
-
if (one.key === opens) {
|
|
102
|
-
each.push([one])
|
|
103
|
-
mine = true
|
|
104
|
-
continue
|
|
105
|
-
}
|
|
106
|
-
if (lists.has(one.key)) {
|
|
107
|
-
mine = false
|
|
108
|
-
continue
|
|
109
|
-
}
|
|
110
|
-
if (mine) (each[each.length - 1] ?? []).push(one)
|
|
111
|
-
else if (each.length === 0) own.push(one)
|
|
112
|
-
}
|
|
113
|
-
return { own, each }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const locate = (one: { readonly at: number; readonly key: string }): string =>
|
|
117
|
-
one.at > 0 ? `line ${String(one.at)}` : `\`--${one.key}\``
|
|
118
|
-
|
|
119
|
-
export interface Asked {
|
|
120
|
-
readonly at: string
|
|
121
|
-
readonly guide: string
|
|
122
|
-
readonly asks?: string
|
|
123
|
-
readonly optional?: boolean
|
|
124
|
-
readonly list?: boolean
|
|
125
|
-
readonly map?: boolean
|
|
126
|
-
readonly boolean?: boolean
|
|
127
|
-
readonly worded?: boolean
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export interface Said {
|
|
131
|
-
readonly guide: string
|
|
132
|
-
readonly asks?: string
|
|
133
|
-
readonly optional?: boolean
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export const named = (all: readonly Asked[]): readonly string[] => all.map((one) => one.at)
|
|
137
|
-
|
|
138
|
-
export const prompted = (one: Asked): string =>
|
|
139
|
-
one.asks ?? `${one.at} — ${one.guide}${one.optional === true ? ', or blank' : ''}: `
|
|
140
|
-
|
|
141
|
-
export type Given = { readonly at: number; readonly key: string; readonly value: string }
|
|
142
|
-
|
|
143
|
-
export interface Child {
|
|
144
|
-
readonly id: string
|
|
145
|
-
readonly at: string
|
|
146
|
-
readonly describes: string
|
|
147
|
-
readonly by?: string
|
|
148
|
-
readonly runs?: string
|
|
149
|
-
readonly body?: string
|
|
150
|
-
readonly writes?: string
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export interface Stated {
|
|
154
|
-
readonly field: string
|
|
155
|
-
readonly said: string
|
|
156
|
-
readonly list: boolean
|
|
157
|
-
readonly map?: boolean
|
|
158
|
-
readonly boolean?: boolean
|
|
159
|
-
readonly worded?: boolean
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export const mapped = (one: { readonly map?: boolean; readonly worded?: boolean }, said: string): boolean =>
|
|
163
|
-
one.map === true && !(one.worded === true && !said.includes('='))
|
|
164
|
-
|
|
165
|
-
export const CARRIES: readonly string[] = ['cites', 'requires', 'takes', 'by', 'runs']
|
|
166
|
-
|
|
167
|
-
export function ownAnswers(all: readonly Given[]): readonly Given[] {
|
|
168
|
-
const opens = new Set<string>(LISTS)
|
|
169
|
-
const at = all.findIndex((one) => opens.has(one.key))
|
|
170
|
-
return at === -1 ? all : all.slice(0, at)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export async function stating(
|
|
174
|
-
asks: readonly Asked[],
|
|
175
|
-
all: readonly Given[],
|
|
176
|
-
piped: boolean,
|
|
177
|
-
ask: (name: string, asks: string) => Promise<string>,
|
|
178
|
-
line: () => Promise<string | undefined>,
|
|
179
|
-
settled: readonly string[],
|
|
180
|
-
): Promise<{ readonly stated: readonly Stated[]; readonly kids: ReadonlyMap<string, readonly Child[]> }> {
|
|
181
|
-
const own = ownAnswers(all)
|
|
182
|
-
const skip = new Set<string>([...settled, ...LISTS])
|
|
183
|
-
const stated: Stated[] = []
|
|
184
|
-
for (const one of asks) {
|
|
185
|
-
if (skip.has(one.at)) continue
|
|
186
|
-
const given = (piped ? (answer(own, one.at) ?? '') : await ask(one.at, prompted(one))).trim()
|
|
187
|
-
if (given === '') continue
|
|
188
|
-
stated.push({ field: one.at, said: given, list: one.list === true, map: one.map === true, boolean: one.boolean === true, worded: one.worded === true })
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (piped) return { stated, kids: childrenIn(all) }
|
|
192
|
-
|
|
193
|
-
const kids = new Map<string, readonly Child[]>()
|
|
194
|
-
for (const list of LISTS) {
|
|
195
|
-
if (!asks.some((one) => one.at === list)) continue
|
|
196
|
-
const found = await children(list, all, ask, line)
|
|
197
|
-
if (found.length > 0) kids.set(list, found)
|
|
198
|
-
}
|
|
199
|
-
return { stated, kids }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function arrayValue(pad: string, key: string, said: string, found: readonly Child[] | undefined): string {
|
|
203
|
-
if (found === undefined || found.length === 0) return asList(said)
|
|
204
|
-
const lines = rowLines(pad, [], new Map([[key, found]]))
|
|
205
|
-
return ['[', ...lines.slice(1, -1), `${pad}]`].join('\n')
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const BARE = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
209
|
-
|
|
210
|
-
const NUMBER = /^-?\d+(?:\.\d+)?$/
|
|
211
|
-
|
|
212
|
-
const nested = (held: Readonly<Record<string, unknown>>): string => {
|
|
213
|
-
const pairs = Object.entries(held).map(([key, value]) => {
|
|
214
|
-
const name = BARE.test(key) ? key : quoted(key)
|
|
215
|
-
if (typeof value === 'object' && value !== null) return `${name}: ${nested(value as Readonly<Record<string, unknown>>)}`
|
|
216
|
-
const said = String(value)
|
|
217
|
-
return `${name}: ${NUMBER.test(said) ? said : quoted(said)}`
|
|
218
|
-
})
|
|
219
|
-
return pairs.length === 0 ? '{}' : `{ ${pairs.join(', ')} }`
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export const asMap = (said: string): string => {
|
|
223
|
-
const held: Record<string, unknown> = {}
|
|
224
|
-
for (const one of said.split(',').map((each) => each.trim()).filter((each) => each !== '')) {
|
|
225
|
-
const at = one.indexOf('=')
|
|
226
|
-
if (at < 1) {
|
|
227
|
-
refuse(
|
|
228
|
-
`\`${one}\` is one entry of a name-and-value field, and it states no value. Write each one as ` +
|
|
229
|
-
'`name=value`, separated by commas — `core=*` says that package at any version, and a dot in the name ' +
|
|
230
|
-
'nests it: `dark.background=colour-surface`. Nothing was written.',
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
const path = one.slice(0, at).trim().split('.')
|
|
234
|
-
const last = path[path.length - 1] ?? ''
|
|
235
|
-
let into = held
|
|
236
|
-
for (const step of path.slice(0, -1)) {
|
|
237
|
-
const next = into[step]
|
|
238
|
-
if (next !== undefined && (typeof next !== 'object' || next === null)) {
|
|
239
|
-
refuse(`\`${one}\` nests under \`${step}\`, and \`${step}\` is already given a value of its own. One or the other. Nothing was written.`)
|
|
240
|
-
}
|
|
241
|
-
if (next === undefined) into[step] = {}
|
|
242
|
-
into = into[step] as Record<string, unknown>
|
|
243
|
-
}
|
|
244
|
-
if (typeof into[last] === 'object' && into[last] !== null) {
|
|
245
|
-
refuse(`\`${one}\` gives \`${last}\` a value, and entries already nest under it. One or the other. Nothing was written.`)
|
|
246
|
-
}
|
|
247
|
-
into[last] = one.slice(at + 1).trim()
|
|
248
|
-
}
|
|
249
|
-
return nested(held)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function gathered(all: readonly Given[], fields: readonly Field[]): readonly Given[] {
|
|
253
|
-
const maps = new Set(fields.filter((one) => one.map).map((one) => one.name))
|
|
254
|
-
const entries = new Map<string, { readonly at: number; readonly parts: string[] }>()
|
|
255
|
-
for (const one of all) {
|
|
256
|
-
const dot = one.key.indexOf('.')
|
|
257
|
-
if (dot === -1 || !maps.has(one.key.slice(0, dot))) continue
|
|
258
|
-
const head = one.key.slice(0, dot)
|
|
259
|
-
const held = entries.get(head) ?? { at: one.at, parts: [] }
|
|
260
|
-
held.parts.push(`${one.key.slice(dot + 1)}=${one.value}`)
|
|
261
|
-
entries.set(head, held)
|
|
262
|
-
}
|
|
263
|
-
const written = new Set<string>()
|
|
264
|
-
const out: Given[] = []
|
|
265
|
-
for (const one of all) {
|
|
266
|
-
const dot = one.key.indexOf('.')
|
|
267
|
-
const head = dot === -1 ? one.key : one.key.slice(0, dot)
|
|
268
|
-
if (dot === -1 || !entries.has(head)) {
|
|
269
|
-
out.push(one)
|
|
270
|
-
continue
|
|
271
|
-
}
|
|
272
|
-
if (written.has(head)) continue
|
|
273
|
-
written.add(head)
|
|
274
|
-
const held = entries.get(head)
|
|
275
|
-
out.push({ at: held?.at ?? one.at, key: head, value: (held?.parts ?? []).join(', ') })
|
|
276
|
-
}
|
|
277
|
-
return out
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const asList = (said: string): string =>
|
|
281
|
-
`[${said
|
|
282
|
-
.split(',')
|
|
283
|
-
.map((one) => one.trim())
|
|
284
|
-
.filter((one) => one !== '')
|
|
285
|
-
.map(quoted)
|
|
286
|
-
.join(', ')}]`
|
|
287
|
-
|
|
288
|
-
export const asBoolean = (said: string): string => {
|
|
289
|
-
const word = said.trim().toLowerCase()
|
|
290
|
-
if (word === 'yes' || word === 'y' || word === 'true') return 'true'
|
|
291
|
-
if (word === 'no' || word === 'n' || word === 'false') return 'false'
|
|
292
|
-
refuse(`\`${said}\` is not an answer to a yes-or-no field. Say yes or no. Nothing was written.`)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export function rowLines(
|
|
296
|
-
pad: string,
|
|
297
|
-
stated: readonly Stated[],
|
|
298
|
-
kids: ReadonlyMap<string, readonly Child[]>,
|
|
299
|
-
): readonly string[] {
|
|
300
|
-
const kid = (one: Child): readonly string[] => [
|
|
301
|
-
`${pad} {`,
|
|
302
|
-
`${pad} id: ${quoted(one.id)},`,
|
|
303
|
-
`${pad} at: ${quoted(one.at)},`,
|
|
304
|
-
`${pad} describes: ${quoted(one.describes)},`,
|
|
305
|
-
...(one.by === undefined || one.by === '' ? [] : [`${pad} by: ${quoted(one.by)},`]),
|
|
306
|
-
...(one.runs === undefined || one.runs === '' ? [] : [`${pad} runs: ${quoted(one.runs)},`]),
|
|
307
|
-
`${pad} version: 1,`,
|
|
308
|
-
`${pad} },`,
|
|
309
|
-
]
|
|
310
|
-
return [
|
|
311
|
-
...stated.map(
|
|
312
|
-
(one) =>
|
|
313
|
-
`${pad}${one.field}: ${
|
|
314
|
-
mapped(one, one.said) ? asMap(one.said) : one.list ? asList(one.said) : one.boolean === true ? asBoolean(one.said) : quoted(one.said)
|
|
315
|
-
},`,
|
|
316
|
-
),
|
|
317
|
-
...[...kids].flatMap(([list, found]) => [`${pad}${list}: [`, ...found.flatMap(kid), `${pad}],`]),
|
|
318
|
-
]
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function withChildren(row: string, lines: readonly string[]): string {
|
|
322
|
-
if (lines.length === 0) return row
|
|
323
|
-
const all = row.split('\n')
|
|
324
|
-
let at = all.length - 1
|
|
325
|
-
while (at >= 0 && (all[at] ?? '').trim() !== '},' && (all[at] ?? '').trim() !== '}') at -= 1
|
|
326
|
-
if (at < 0) {
|
|
327
|
-
refuse(
|
|
328
|
-
'the row just written has no closing brace on a line of its own, so what it declares has nowhere to go. ' +
|
|
329
|
-
'Nothing more was written, because adding the row and dropping its children quietly is the one outcome ' +
|
|
330
|
-
'nobody could see.',
|
|
331
|
-
)
|
|
332
|
-
}
|
|
333
|
-
return [...all.slice(0, at), ...lines, ...all.slice(at)].join('\n')
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export function childrenIn(all: readonly Given[]): ReadonlyMap<string, readonly Child[]> {
|
|
337
|
-
const found = new Map<string, readonly Child[]>()
|
|
338
|
-
for (const list of LISTS) {
|
|
339
|
-
const each: Child[] = []
|
|
340
|
-
for (const group of scoped(all, list).each) {
|
|
341
|
-
const id = (answer(group, list) ?? '').trim()
|
|
342
|
-
if (id === '') continue
|
|
343
|
-
const at = (answer(group, 'at') ?? '').trim()
|
|
344
|
-
const describes = (answer(group, 'describes') ?? '').trim()
|
|
345
|
-
if (at === '' || describes === '') {
|
|
346
|
-
refuse(
|
|
347
|
-
`\`${list} ${id}\` names no ${at === '' ? 'file' : 'meaning'}. Every row says which file it is about and ` +
|
|
348
|
-
'what that file is, and one missing either is a row the next person cannot act on. Nothing more was written.',
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
const body = (answer(group, BODY) ?? '').trim()
|
|
352
|
-
const writes = (answer(group, WRITES) ?? '').trim()
|
|
353
|
-
each.push({
|
|
354
|
-
id,
|
|
355
|
-
at,
|
|
356
|
-
describes,
|
|
357
|
-
...(writes === '' ? {} : { writes }),
|
|
358
|
-
...(list === 'declares'
|
|
359
|
-
? {}
|
|
360
|
-
: { by: (answer(group, 'by') ?? '').trim(), runs: (answer(group, 'runs') ?? '').trim() }),
|
|
361
|
-
...(body === '' ? {} : { body }),
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
if (each.length > 0) found.set(list, each)
|
|
365
|
-
}
|
|
366
|
-
return found
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
export const BODIED: readonly string[] = ['declares', 'seeds']
|
|
370
|
-
|
|
371
|
-
export const WRITTEN: readonly string[] = ['derives', 'seeds']
|
|
372
|
-
|
|
373
|
-
export function seeding(kids: ReadonlyMap<string, readonly Child[]>, root: string): readonly string[] {
|
|
374
|
-
const said: string[] = []
|
|
375
|
-
for (const list of BODIED) {
|
|
376
|
-
for (const one of kids.get(list) ?? []) {
|
|
377
|
-
const into = join(root, one.at)
|
|
378
|
-
const there = existsSync(into)
|
|
379
|
-
|
|
380
|
-
if (one.body === undefined || one.body === '') {
|
|
381
|
-
said.push(
|
|
382
|
-
there
|
|
383
|
-
? `${one.at} was already on disk, and the row now names it.`
|
|
384
|
-
: `${one.at} is declared and nothing is in it yet — write it with ` +
|
|
385
|
-
`\`${TYPED} edit ${one.at} --from <path>\`.`,
|
|
386
|
-
)
|
|
387
|
-
continue
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (there) {
|
|
391
|
-
said.push(
|
|
392
|
-
`${one.at} was already on disk, so the body you wrote was NOT put over it. Nothing was lost — ` +
|
|
393
|
-
`\`${TYPED} read ${one.at}\` prints what is there.`,
|
|
394
|
-
)
|
|
395
|
-
continue
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
writeFileSync(into, one.body.endsWith('\n') ? one.body : `${one.body}\n`, 'utf8')
|
|
399
|
-
said.push(`wrote ${one.at} with the body you gave it.`)
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return said
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const ASKED: Readonly<Record<string, string>> = {
|
|
406
|
-
declares: 'name each file a person writes that this one owns',
|
|
407
|
-
derives: 'name each file a command writes from this one, and the command that writes it',
|
|
408
|
-
seeds: 'name each file a command shapes here',
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
export async function children(
|
|
412
|
-
list: string,
|
|
413
|
-
all: readonly Given[],
|
|
414
|
-
ask: (name: string, asks: string) => Promise<string>,
|
|
415
|
-
line: () => Promise<string | undefined>,
|
|
416
|
-
): Promise<readonly Child[]> {
|
|
417
|
-
const written = WRITTEN.includes(list)
|
|
418
|
-
const out: Child[] = []
|
|
419
|
-
|
|
420
|
-
const owed = (id: string, at: string, describes: string): void => {
|
|
421
|
-
if (at !== '' && describes !== '') return
|
|
422
|
-
refuse(
|
|
423
|
-
`\`${list} ${id}\` names no ${at === '' ? 'file' : 'meaning'}. Every row says which file it is about and what ` +
|
|
424
|
-
'that file is, and one missing either is a row the next person cannot act on. Nothing more was written.',
|
|
425
|
-
)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const bodies = [
|
|
429
|
-
...(written ? [`${asFlag(WRITES)} is the generator's own body — the code that writes the template`] : []),
|
|
430
|
-
...(BODIED.includes(list) ? [`${asFlag(BODY)} is the body of the file itself`] : []),
|
|
431
|
-
]
|
|
432
|
-
stdout.write(
|
|
433
|
-
`\n ${list} — ${ASKED[list] ?? ''}. ` +
|
|
434
|
-
(bodies.length === 0
|
|
435
|
-
? ''
|
|
436
|
-
: `Each one takes ${bodies.length === 1 ? 'one body' : `${String(bodies.length)} bodies, and they are never the same text`}: ` +
|
|
437
|
-
`${bodies.join('; ')}. `) +
|
|
438
|
-
'A blank id ends the list.\n',
|
|
439
|
-
)
|
|
440
|
-
for (;;) {
|
|
441
|
-
const id = (await ask(list, ` ${list} — id, or blank to stop: `)).trim()
|
|
442
|
-
if (id === '') break
|
|
443
|
-
const at = (await ask('at', ' at — the file this names: ')).trim()
|
|
444
|
-
const describes = (await ask('describes', ' describes — what that file is, to somebody who has not opened it: ')).trim()
|
|
445
|
-
owed(id, at, describes)
|
|
446
|
-
const by = written ? (await ask('by', ' by — the command that writes it: ')).trim() : undefined
|
|
447
|
-
const runs = written ? (await ask('runs', ' runs — the command that catches it drifting: ')).trim() : undefined
|
|
448
|
-
const writes = written
|
|
449
|
-
? await bodied(`the generator behind \`${by ?? 'that command'}\` — this is ${asFlag(WRITES)}`, '', false, all, line)
|
|
450
|
-
: undefined
|
|
451
|
-
const body = BODIED.includes(list)
|
|
452
|
-
? await bodied(`${at} itself — this is ${asFlag(BODY)}`, '', false, all, line)
|
|
453
|
-
: undefined
|
|
454
|
-
out.push({
|
|
455
|
-
id,
|
|
456
|
-
at,
|
|
457
|
-
describes,
|
|
458
|
-
...(written ? { by, runs } : {}),
|
|
459
|
-
...(writes === undefined ? {} : { writes }),
|
|
460
|
-
...(body === undefined ? {} : { body }),
|
|
461
|
-
})
|
|
462
|
-
}
|
|
463
|
-
return out
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
export async function bodied(
|
|
467
|
-
what: string,
|
|
468
|
-
shows: string,
|
|
469
|
-
piped: boolean,
|
|
470
|
-
all: readonly Given[],
|
|
471
|
-
line: () => Promise<string | undefined>,
|
|
472
|
-
): Promise<string | undefined> {
|
|
473
|
-
const from = argv[argv.indexOf(FROM) + 1]
|
|
474
|
-
if (argv.includes(FROM) && from !== undefined && !from.startsWith('-')) {
|
|
475
|
-
try {
|
|
476
|
-
return refuse(`${FROM} seeds the body out of a file, and a file is not a receipt: nothing here checked it and no descent reached it. Give the body with ${asFlag(BODY)} on the command. Nothing was written.`)
|
|
477
|
-
} catch {
|
|
478
|
-
refuse(
|
|
479
|
-
`\`${FROM} ${from}\` names a file nothing could read, so there is no body to seed and nothing was ` +
|
|
480
|
-
`written. Write it first, then run this again — or drop ${FROM} and type the body when it is asked for.`,
|
|
481
|
-
)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
if (piped) {
|
|
485
|
-
const said = answer(all, BODY)
|
|
486
|
-
return said === undefined || said.trim() === '' ? undefined : said
|
|
487
|
-
}
|
|
488
|
-
if (shows.trim() === '') stdout.write(` body — write what goes in ${what}.\n`)
|
|
489
|
-
else {
|
|
490
|
-
stdout.write(` body — what a ${what} is handed looks like this:\n`)
|
|
491
|
-
for (const one of shows.split('\n')) stdout.write(` ${one}\n`)
|
|
492
|
-
}
|
|
493
|
-
stdout.write(
|
|
494
|
-
` write yours, then a line holding only ${ENDS}. A first line of ${ENDS} leaves it unwritten, and\n` +
|
|
495
|
-
` \`${TYPED} trials\` reports it unproven until somebody writes it.\n` +
|
|
496
|
-
` Or give ${FROM} <path> instead and the file at that path is what gets seeded, which is the one\n` +
|
|
497
|
-
' form that needs no terminal.\n',
|
|
498
|
-
)
|
|
499
|
-
const held: string[] = []
|
|
500
|
-
for (;;) {
|
|
501
|
-
const next = await line()
|
|
502
|
-
if (next === undefined || next.trim() === ENDS) break
|
|
503
|
-
held.push(next)
|
|
504
|
-
}
|
|
505
|
-
const written = held.join('\n').trim()
|
|
506
|
-
return written === '' ? undefined : written
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
export function only(
|
|
510
|
-
given: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
511
|
-
allowed: readonly string[],
|
|
512
|
-
where: string,
|
|
513
|
-
): void {
|
|
514
|
-
unasked(ownAnswers(given), allowed, where)
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
export function unasked(
|
|
518
|
-
given: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
519
|
-
allowed: readonly string[],
|
|
520
|
-
where: string,
|
|
521
|
-
): void {
|
|
522
|
-
for (const one of given) {
|
|
523
|
-
if (CARRIES.includes(one.key) || one.key === WRITES) continue
|
|
524
|
-
if (allowed.includes(one.key)) continue
|
|
525
|
-
refuse(
|
|
526
|
-
`${locate(one)} answers \`${one.key}\`, and ${where} takes ${allowed.map((each) => `\`${each}\``).join(', ')}. ` +
|
|
527
|
-
'Nothing was written.',
|
|
528
|
-
)
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
export function answer(
|
|
533
|
-
given: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
534
|
-
key: string,
|
|
535
|
-
): string | undefined {
|
|
536
|
-
const found = given.filter((one) => one.key === key)
|
|
537
|
-
if (found.length > 1) {
|
|
538
|
-
refuse(
|
|
539
|
-
`\`${key}\` is answered ${String(found.length)} times: ${found.map((one) => locate(one)).join(', ')}. ` +
|
|
540
|
-
'Nothing was written.',
|
|
541
|
-
)
|
|
542
|
-
}
|
|
543
|
-
const held = found[0]?.value
|
|
544
|
-
return held === undefined ? undefined : guarded(key, held)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export function accepted(
|
|
548
|
-
given: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
549
|
-
): string {
|
|
550
|
-
if (given.length === 0) return 'nothing was accepted before it'
|
|
551
|
-
return `accepted so far: ${given.map((one) => `${one.key} (${locate(one)})`).join(', ')}`
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
export function needed(
|
|
555
|
-
given: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
556
|
-
key: string,
|
|
557
|
-
where: string,
|
|
558
|
-
): string {
|
|
559
|
-
const held = answer(given, key)
|
|
560
|
-
if (held === undefined || held === '') {
|
|
561
|
-
refuse(`${where} states no \`${key}\`, and it is not optional. ${accepted(given)}. Nothing was written.`)
|
|
562
|
-
}
|
|
563
|
-
return held
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
export function wrapped(text: string, pad: string): readonly string[] {
|
|
567
|
-
const lines: string[] = []
|
|
568
|
-
let held = ''
|
|
569
|
-
for (const word of text.split(/\s+/).filter((one) => one !== '')) {
|
|
570
|
-
if (held !== '' && `${held} ${word}`.length > 92) {
|
|
571
|
-
lines.push(`${pad}${held}`)
|
|
572
|
-
held = word
|
|
573
|
-
} else held = held === '' ? word : `${held} ${word}`
|
|
574
|
-
}
|
|
575
|
-
if (held !== '') lines.push(`${pad}${held}`)
|
|
576
|
-
return lines
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
export function marked(parts: readonly string[]): string {
|
|
580
|
-
return createHash('sha256').update(parts.join('\n')).digest('hex').slice(0, 6)
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
export interface Told {
|
|
584
|
-
readonly on: string
|
|
585
|
-
readonly said: string
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
export interface Declared {
|
|
589
|
-
readonly at: string
|
|
590
|
-
readonly list: string
|
|
591
|
-
readonly shape: string | undefined
|
|
592
|
-
readonly fields: readonly Field[]
|
|
593
|
-
readonly describes: string
|
|
594
|
-
readonly holds: number
|
|
595
|
-
readonly cites: readonly Told[]
|
|
596
|
-
readonly top: { readonly id: string; readonly describes: string } | undefined
|
|
597
|
-
readonly files?: string
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const citesOf = (held: unknown): readonly Told[] =>
|
|
601
|
-
Array.isArray(held)
|
|
602
|
-
? held.filter(
|
|
603
|
-
(one): one is Told =>
|
|
604
|
-
one !== null && typeof one === 'object' && typeof (one as Told).on === 'string' && typeof (one as Told).said === 'string',
|
|
605
|
-
)
|
|
606
|
-
: []
|
|
607
|
-
|
|
608
|
-
const NAMED_SHAPE = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
609
|
-
|
|
610
|
-
const rowLike = (one: unknown): one is Row =>
|
|
611
|
-
one !== null && typeof one === 'object' && typeof (one as Row).id === 'string' && typeof (one as Row).describes === 'string'
|
|
612
|
-
|
|
613
|
-
const kinded = (shape: string): string =>
|
|
614
|
-
shape
|
|
615
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
616
|
-
.toLowerCase()
|
|
617
|
-
|
|
618
|
-
const oneOf = (list: string): string => {
|
|
619
|
-
const word = list.toLowerCase().split('_').join('-')
|
|
620
|
-
return word.endsWith('s') ? word.slice(0, -1) : word
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
export async function declared(tree: Tree): Promise<ReadonlyMap<string, Declared>> {
|
|
624
|
-
const found = new Map<string, Declared>()
|
|
625
|
-
const walked = new Set<string>()
|
|
626
|
-
|
|
627
|
-
const says = new Map<string, string>()
|
|
628
|
-
|
|
629
|
-
const parents = new Map<string, Row & { readonly cites?: unknown }>()
|
|
630
|
-
|
|
631
|
-
const sheetsBy = new Map<string, Set<string>>()
|
|
632
|
-
|
|
633
|
-
const meant = (shape: string | undefined, sheetAt: string): string => {
|
|
634
|
-
if (shape === undefined) return ''
|
|
635
|
-
try {
|
|
636
|
-
const named = shapesNamed()
|
|
637
|
-
for (const at of named.get(shape) ?? []) {
|
|
638
|
-
if (at === sheetAt) continue
|
|
639
|
-
let others = 0
|
|
640
|
-
for (const [, files] of named) if (files.includes(at)) others += 1
|
|
641
|
-
if (others !== 1) continue
|
|
642
|
-
const told = says.get(at)
|
|
643
|
-
if (told !== undefined && told !== '') return told
|
|
644
|
-
}
|
|
645
|
-
} catch {
|
|
646
|
-
return ''
|
|
647
|
-
}
|
|
648
|
-
return ''
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const sheet = (one: unknown): string | undefined => {
|
|
652
|
-
const at = (one as Row | undefined)?.at
|
|
653
|
-
return typeof at === 'string' && at.startsWith(`${tree.name}/`) && at.endsWith('.ts') ? at : undefined
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const walk = async (at: string, top: Declared['top']): Promise<void> => {
|
|
657
|
-
if (walked.has(at)) return
|
|
658
|
-
walked.add(at)
|
|
659
|
-
|
|
660
|
-
let read: Record<string, unknown>
|
|
661
|
-
try {
|
|
662
|
-
read = valuesIn(readFileSync(join(tree.at, at))) as Record<string, unknown>
|
|
663
|
-
} catch {
|
|
664
|
-
return
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
for (const [name, value] of Object.entries(read)) {
|
|
668
|
-
const node = value as
|
|
669
|
-
| { readonly id?: unknown; readonly describes?: unknown; readonly cites?: unknown; readonly declares?: unknown; readonly holds?: unknown; readonly files?: unknown }
|
|
670
|
-
| undefined
|
|
671
|
-
const lists: readonly { readonly list: string; readonly rows: readonly unknown[] }[] = Array.isArray(value)
|
|
672
|
-
? [{ list: name, rows: value }]
|
|
673
|
-
: (['holds', 'declares'] as const).flatMap((list) => {
|
|
674
|
-
const rows = node?.[list]
|
|
675
|
-
return Array.isArray(rows) ? [{ list, rows }] : []
|
|
676
|
-
})
|
|
677
|
-
const rowed = lists.filter((one) => one.rows.every(rowLike))
|
|
678
|
-
const first = rowed[0]
|
|
679
|
-
if (first === undefined) continue
|
|
680
|
-
|
|
681
|
-
if (Array.isArray(value)) {
|
|
682
|
-
const noun = oneOf(name)
|
|
683
|
-
sheetsBy.set(name, new Set([...(sheetsBy.get(name) ?? []), at]))
|
|
684
|
-
if (!found.has(noun)) {
|
|
685
|
-
found.set(noun, {
|
|
686
|
-
at,
|
|
687
|
-
list: name,
|
|
688
|
-
shape: listShape(at, name),
|
|
689
|
-
fields: listFields(at, name),
|
|
690
|
-
describes: '',
|
|
691
|
-
holds: first.rows.length,
|
|
692
|
-
cites: [],
|
|
693
|
-
top,
|
|
694
|
-
})
|
|
695
|
-
}
|
|
696
|
-
} else if (at !== within(tree.at, tree.root)) {
|
|
697
|
-
const files = typeof node?.files === 'string' && node.files !== '' ? node.files : undefined
|
|
698
|
-
const held = files === undefined ? first : (rowed.find((one) => one.list === 'declares') ?? first)
|
|
699
|
-
const kind = listShape(at, held.list)
|
|
700
|
-
const noun =
|
|
701
|
-
kind === undefined || (kind === 'Threaded' && files === undefined)
|
|
702
|
-
? undefined
|
|
703
|
-
: kind !== 'Threaded' && NAMED_SHAPE.test(kind)
|
|
704
|
-
? kinded(kind)
|
|
705
|
-
: typeof node?.id === 'string' && node.id !== ''
|
|
706
|
-
? node.id
|
|
707
|
-
: undefined
|
|
708
|
-
if (typeof noun === 'string' && noun !== '' && !found.has(noun)) {
|
|
709
|
-
found.set(noun, {
|
|
710
|
-
at,
|
|
711
|
-
list: held.list,
|
|
712
|
-
shape: kind,
|
|
713
|
-
fields: listFields(at, held.list),
|
|
714
|
-
describes: typeof node?.describes === 'string' ? node.describes : '',
|
|
715
|
-
holds: held.rows.length,
|
|
716
|
-
cites: citesOf(node?.cites),
|
|
717
|
-
top,
|
|
718
|
-
...(files === undefined ? {} : { files }),
|
|
719
|
-
})
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
for (const one of rowed.flatMap((each) => each.rows)) {
|
|
723
|
-
const where = (one as Row).at
|
|
724
|
-
const told = (one as Row).describes
|
|
725
|
-
if (typeof where === 'string' && typeof told === 'string' && !says.has(where)) says.set(where, told)
|
|
726
|
-
const next = sheet(one)
|
|
727
|
-
if (next === undefined) continue
|
|
728
|
-
const row = one as Row & { readonly cites?: unknown }
|
|
729
|
-
if (!parents.has(next)) parents.set(next, row)
|
|
730
|
-
await walk(next, top ?? { id: typeof row.id === 'string' ? row.id : '', describes: typeof row.describes === 'string' ? row.describes : '' })
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
await walk(within(tree.at, tree.root), undefined)
|
|
736
|
-
|
|
737
|
-
const out = new Map<string, Declared>()
|
|
738
|
-
for (const [noun, one] of found) {
|
|
739
|
-
const parent = parents.get(one.at)
|
|
740
|
-
const said = typeof parent?.describes === 'string' ? parent.describes : ''
|
|
741
|
-
const owned = (sheetsBy.get(one.list)?.size ?? 0) <= 1
|
|
742
|
-
out.set(noun, {
|
|
743
|
-
...one,
|
|
744
|
-
describes: one.describes !== '' ? one.describes : owned && said !== '' ? said : meant(one.shape, one.at),
|
|
745
|
-
cites: one.cites.length > 0 ? one.cites : citesOf(parent?.cites),
|
|
746
|
-
})
|
|
747
|
-
}
|
|
748
|
-
return out
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
export function nouns(all: readonly Row[], verb = 'create'): ReadonlyMap<string, string> {
|
|
752
|
-
const found = new Map<string, string>()
|
|
753
|
-
const opens = `thread ${verb} `
|
|
754
|
-
for (const one of all) {
|
|
755
|
-
const by = one.by
|
|
756
|
-
if (typeof by !== 'string' || !by.startsWith(opens)) continue
|
|
757
|
-
const word = by.slice(opens.length).trim()
|
|
758
|
-
if (word === '' || typeof one.at !== 'string') continue
|
|
759
|
-
const already = found.get(word)
|
|
760
|
-
if (already !== undefined && already !== one.at) {
|
|
761
|
-
refuse(
|
|
762
|
-
`two rows both state \`by: 'thread ${verb} ${word}'\` — ${already} and ${one.at} — so which one answers a ` +
|
|
763
|
-
`${word} is whichever was written first. One noun, one file.`,
|
|
764
|
-
)
|
|
765
|
-
}
|
|
766
|
-
found.set(word, one.at)
|
|
767
|
-
}
|
|
768
|
-
return found
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
export function asking(
|
|
772
|
-
held: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
773
|
-
): {
|
|
774
|
-
readonly piped: boolean
|
|
775
|
-
readonly ask: (
|
|
776
|
-
key: string,
|
|
777
|
-
question: string,
|
|
778
|
-
from?: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
779
|
-
) => Promise<string>
|
|
780
|
-
|
|
781
|
-
readonly line: () => Promise<string | undefined>
|
|
782
|
-
readonly shut: () => void
|
|
783
|
-
} {
|
|
784
|
-
const piped = answered()
|
|
785
|
-
const rl = piped ? undefined : createInterface({ input: stdin, output: stdout })
|
|
786
|
-
const lines = rl?.[Symbol.asyncIterator]()
|
|
787
|
-
return {
|
|
788
|
-
piped,
|
|
789
|
-
ask: async (key, question, from = held) => {
|
|
790
|
-
const already = answer(from, key)
|
|
791
|
-
if (already !== undefined) return guarded(key, already)
|
|
792
|
-
if (piped) return ''
|
|
793
|
-
stdout.write(question)
|
|
794
|
-
const next = await lines?.next()
|
|
795
|
-
return next === undefined || next.done === true ? '' : guarded(key, next.value.trim())
|
|
796
|
-
},
|
|
797
|
-
line: async () => {
|
|
798
|
-
const next = await lines?.next()
|
|
799
|
-
return next === undefined || next.done === true ? undefined : next.value
|
|
800
|
-
},
|
|
801
|
-
shut: () => rl?.close(),
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
const ANSWERS = '--answers'
|
|
806
|
-
|
|
807
|
-
function fromFile(): string | undefined {
|
|
808
|
-
const at = argv.indexOf(ANSWERS)
|
|
809
|
-
if (at === -1) return undefined
|
|
810
|
-
const path = argv[at + 1]
|
|
811
|
-
if (path === undefined || path.startsWith('-')) {
|
|
812
|
-
refuse(`${ANSWERS} needs the path of a file holding the answers, one \`name: value\` to a line.`)
|
|
813
|
-
}
|
|
814
|
-
try {
|
|
815
|
-
return readFileSync(path, 'utf8')
|
|
816
|
-
} catch {
|
|
817
|
-
refuse(`${ANSWERS} names ${path}, and there is no file there to read the answers from.`)
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const OWN = '--own'
|
|
822
|
-
const CHECK = '--check'
|
|
823
|
-
|
|
824
|
-
function flagged(): readonly { readonly at: number; readonly key: string; readonly value: string }[] {
|
|
825
|
-
const found: { at: number; key: string; value: string }[] = []
|
|
826
|
-
for (let at = 2; at < argv.length; at += 1) {
|
|
827
|
-
const one = argv[at] ?? ''
|
|
828
|
-
if (
|
|
829
|
-
!one.startsWith('--') ||
|
|
830
|
-
one === RECEIPT ||
|
|
831
|
-
one === ANSWERS ||
|
|
832
|
-
one === OWN ||
|
|
833
|
-
one === CHECK ||
|
|
834
|
-
one === FROM ||
|
|
835
|
-
one === CHAINED
|
|
836
|
-
)
|
|
837
|
-
continue
|
|
838
|
-
const value = argv[at + 1]
|
|
839
|
-
if (value === undefined || value.startsWith('-')) {
|
|
840
|
-
refuse(`\`${one}\` was given no value — an answer given as a flag is \`${one} value\`, with the value right after it.`)
|
|
841
|
-
}
|
|
842
|
-
found.push({ at: 0, key: one.slice(2), value })
|
|
843
|
-
at += 1
|
|
844
|
-
}
|
|
845
|
-
return found
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
export function answered(): boolean {
|
|
849
|
-
return argv.includes(ANSWERS) || stdin.isTTY !== true
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
export function given(): readonly { readonly at: number; readonly key: string; readonly value: string }[] {
|
|
853
|
-
const flags = flagged()
|
|
854
|
-
const fromFlag = new Set(flags.map((one) => one.key))
|
|
855
|
-
|
|
856
|
-
const held = fromFile()
|
|
857
|
-
if (held !== undefined) return read([...flags, ...parsed(held).filter((one) => !fromFlag.has(one.key))])
|
|
858
|
-
|
|
859
|
-
const text = handed()
|
|
860
|
-
if (text === undefined) return read(flags)
|
|
861
|
-
|
|
862
|
-
return read([...flags, ...parsed(text).filter((one) => !fromFlag.has(one.key))])
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const FROM_END = '-from'
|
|
866
|
-
|
|
867
|
-
function read(
|
|
868
|
-
all: readonly { readonly at: number; readonly key: string; readonly value: string }[],
|
|
869
|
-
): readonly { readonly at: number; readonly key: string; readonly value: string }[] {
|
|
870
|
-
return all.map((one) => {
|
|
871
|
-
if (!one.key.endsWith(FROM_END)) return one
|
|
872
|
-
const key = one.key.slice(0, -FROM_END.length)
|
|
873
|
-
if (key === '') return one
|
|
874
|
-
if (all.some((each) => each.key === key)) {
|
|
875
|
-
refuse(
|
|
876
|
-
`\`${asFlag(key)}\` and \`${asFlag(one.key)}\` both answer ${key}, and they do not have to agree. ` +
|
|
877
|
-
'Give one of them. Nothing was written.',
|
|
878
|
-
)
|
|
879
|
-
}
|
|
880
|
-
try {
|
|
881
|
-
return refuse(`${asFlag(key)}${FROM_END} takes what this writes out of a file, and a file is not a receipt: nothing here checked it and no descent reached it. Give the text on the command with ${asFlag(key)}. Nothing was written.`)
|
|
882
|
-
} catch {
|
|
883
|
-
refuse(
|
|
884
|
-
`\`${asFlag(one.key)} ${one.value}\` names a file nothing could read, so there is no ${key} to write and ` +
|
|
885
|
-
`nothing was written. Write that file first, or give the text itself with ${asFlag(key)}.`,
|
|
886
|
-
)
|
|
887
|
-
}
|
|
888
|
-
})
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
let stood: string | undefined
|
|
892
|
-
let taken = false
|
|
893
|
-
|
|
894
|
-
export function handed(): string | undefined {
|
|
895
|
-
if (taken) return stood
|
|
896
|
-
taken = true
|
|
897
|
-
if (stdin.isTTY === true) return undefined
|
|
898
|
-
try {
|
|
899
|
-
stood = readFileSync(0, 'utf8')
|
|
900
|
-
} catch {
|
|
901
|
-
stood = undefined
|
|
902
|
-
}
|
|
903
|
-
return stood
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
export const TYPED = 'thread'
|
|
907
|
-
|
|
908
|
-
export const CARRIED: readonly { readonly verb: string; readonly typed: string; readonly does: string }[] = [
|
|
909
|
-
{ verb: 'read', typed: `${TYPED} read <row>`, does: 'prints every field a row states' },
|
|
910
|
-
{ verb: 'edit', typed: `${TYPED} edit <row> --<field> <value>`, does: 'changes any field it was declared with' },
|
|
911
|
-
{ verb: 'remove', typed: `${TYPED} edit <row> --remove`, does: 'takes the row out of the tree' },
|
|
912
|
-
]
|
|
913
|
-
|
|
914
|
-
const elsewhere = CARRIED.map((one) => `\`${one.typed}\` ${one.does}`)
|
|
915
|
-
|
|
916
|
-
export function wordsIn(offered: readonly string[]): readonly string[] {
|
|
917
|
-
const values = new Set([argv[argv.indexOf(RECEIPT) + 1], argv[argv.indexOf(ANSWERS) + 1], ...flagged().map((one) => one.value)])
|
|
918
|
-
const words = argv.slice(2).filter((one) => !one.startsWith('-') && !values.has(one))
|
|
919
|
-
const first = words[0]
|
|
920
|
-
if (offered.length === 0 || first === undefined || offered.includes(first)) return words
|
|
921
|
-
const carried = CARRIED.find((one) => one.verb === first)
|
|
922
|
-
if (carried !== undefined) {
|
|
923
|
-
refuse(
|
|
924
|
-
`\`${first}\` is not a word this command answers, because every row in this tree has it and it is ` +
|
|
925
|
-
`answered in one place rather than once per thing. \`${carried.typed}\` ${carried.does}, whatever ` +
|
|
926
|
-
'declared it, and it finds the row by its id or by the mark printed beside it. Nothing was written, ' +
|
|
927
|
-
'and nothing was read.',
|
|
928
|
-
)
|
|
929
|
-
}
|
|
930
|
-
refuse(
|
|
931
|
-
`\`${first}\` is not one of the things this command does. It does: ${offered.join(', ')}. ` +
|
|
932
|
-
`Reading, changing and removing a row are done elsewhere, because they reach every row rather than ` +
|
|
933
|
-
`only this one's: ${elsewhere.join('; ')}. ` +
|
|
934
|
-
'Run it with none of those to see what already exists and exactly what each of them asks for. ' +
|
|
935
|
-
'Nothing was written, and nothing was read.',
|
|
936
|
-
)
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
const ANNOUNCED: readonly { readonly what: string; readonly pattern: RegExp }[] = [
|
|
940
|
-
{ what: 'a Stripe secret key', pattern: /\bsk_(?:live|test)_[A-Za-z0-9]{16,}/ },
|
|
941
|
-
{ what: 'a Stripe restricted key', pattern: /\brk_(?:live|test)_[A-Za-z0-9]{16,}/ },
|
|
942
|
-
{ what: 'a GitHub token', pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}/ },
|
|
943
|
-
{ what: 'a GitHub personal access token', pattern: /\bgithub_pat_[A-Za-z0-9_]{30,}/ },
|
|
944
|
-
{ what: 'an AWS access key id', pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/ },
|
|
945
|
-
{ what: 'a Slack token', pattern: /\bxox[abposr]-[A-Za-z0-9-]{10,}/ },
|
|
946
|
-
{ what: 'an OpenAI key', pattern: /\bsk-[A-Za-z0-9_-]{32,}/ },
|
|
947
|
-
{ what: 'an Anthropic key', pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}/ },
|
|
948
|
-
{ what: 'a Google API key', pattern: /\bAIza[A-Za-z0-9_-]{35}\b/ },
|
|
949
|
-
{ what: 'a JSON web token', pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
|
|
950
|
-
{ what: 'a private key', pattern: /-----BEGIN (?:RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/ },
|
|
951
|
-
{ what: 'a password inside a connection string', pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]{6,}@/ },
|
|
952
|
-
]
|
|
953
|
-
|
|
954
|
-
const ASSIGNED = /\b(?:secret|password|passwd|api_?key|apikey|access_?token|auth_?token|private_?key|client_?secret|credential)s?\b\s*[:=]\s*(['"`])([^'"`\n]{16,})\1/gi
|
|
955
|
-
|
|
956
|
-
const MIXED = /^(?=.*[a-z])(?=.*[A-Z0-9])[A-Za-z0-9_+/=.-]{16,}$/
|
|
957
|
-
|
|
958
|
-
const NOT = [
|
|
959
|
-
/^(?:sec|pub)_[a-z0-9_]+$/,
|
|
960
|
-
/^\$\{/,
|
|
961
|
-
/^(?:x{3,}|X{3,}|\.{3,}|<[^>]+>|change[-_]?me|your[-_]|example|placeholder|redacted|todo|test|dummy|sample|fake|dGhpcyBpcyBub3QgYSBzZWNyZXQ)/i,
|
|
962
|
-
]
|
|
963
|
-
|
|
964
|
-
function guarded(key: string, value: string): string {
|
|
965
|
-
const carried = credentialIn(value)
|
|
966
|
-
if (carried !== undefined) {
|
|
967
|
-
refuse(
|
|
968
|
-
`\`${key}\` holds ${carried}. A credential is never typed into a declaration — not in a describes, not ` +
|
|
969
|
-
'anywhere but a secret. Declare it with `thread create secret`, name it where the thing that needs it ' +
|
|
970
|
-
'states what it requires, and it is resolved through the vault at the moment it is used and never ' +
|
|
971
|
-
'written down. Nothing was written.',
|
|
972
|
-
)
|
|
973
|
-
}
|
|
974
|
-
const address = ADDRESS.exec(value)
|
|
975
|
-
if (address !== null) {
|
|
976
|
-
refuse(
|
|
977
|
-
`\`${key}\` holds the address \`${address[0]}\`. An address is a secret here, not a sentence — the same ` +
|
|
978
|
-
'thing behind another host is another environment, and one written into a declaration is one nothing ' +
|
|
979
|
-
'can point elsewhere. Declare it with `thread create secret` and name it where the thing that needs it ' +
|
|
980
|
-
'states what it requires. Nothing was written.',
|
|
981
|
-
)
|
|
982
|
-
}
|
|
983
|
-
return value
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
const ADDRESS = /\b[a-z][a-z0-9+.-]*:\/\/[^\s'"`)]+/i
|
|
987
|
-
|
|
988
|
-
export function credentialIn(text: string): string | undefined {
|
|
989
|
-
for (const one of ANNOUNCED) {
|
|
990
|
-
if (one.pattern.test(text)) return one.what
|
|
991
|
-
}
|
|
992
|
-
for (const found of text.matchAll(ASSIGNED)) {
|
|
993
|
-
const value = found[2] ?? ''
|
|
994
|
-
if (NOT.some((each) => each.test(value))) continue
|
|
995
|
-
if (!MIXED.test(value)) continue
|
|
996
|
-
return `a ${(found[0].split(/[:=]/)[0] ?? '').trim()} written down as a literal ${String(value.length)} characters long`
|
|
997
|
-
}
|
|
998
|
-
return undefined
|
|
999
|
-
}
|