@prestack/thread-ui 0.0.1 → 0.1.0
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 +177 -0
- package/dist/ui/animations/anm_appear.ts +13 -0
- package/dist/ui/animations/anm_blur_in.ts +19 -0
- package/dist/ui/animations/anm_blur_out.ts +18 -0
- package/dist/ui/animations/anm_dismiss.ts +18 -0
- package/dist/ui/animations/anm_drop.ts +19 -0
- package/dist/ui/animations/anm_fall.ts +18 -0
- package/dist/ui/animations/anm_flash.ts +18 -0
- package/dist/ui/animations/anm_glow.ts +16 -0
- package/dist/ui/animations/anm_lift.ts +16 -0
- package/dist/ui/animations/anm_nudge.ts +16 -0
- package/dist/ui/animations/anm_pop_in.ts +23 -0
- package/dist/ui/animations/anm_pop_out.ts +22 -0
- package/dist/ui/animations/anm_press.ts +12 -0
- package/dist/ui/animations/anm_pulse.ts +13 -0
- package/dist/ui/animations/anm_rise.ts +19 -0
- package/dist/ui/animations/anm_shake.ts +13 -0
- package/dist/ui/animations/anm_slide_in_left.ts +19 -0
- package/dist/ui/animations/anm_slide_in_right.ts +19 -0
- package/dist/ui/animations/anm_slide_out_left.ts +18 -0
- package/dist/ui/animations/anm_slide_out_right.ts +18 -0
- package/dist/ui/animations/anm_spin.ts +12 -0
- package/dist/ui/animations/anm_swell.ts +16 -0
- package/dist/ui/animations/anm_wobble.ts +24 -0
- package/dist/ui/animations/anm_zoom_in.ts +19 -0
- package/dist/ui/animations/anm_zoom_out.ts +18 -0
- package/dist/ui/animations.js +2 -3
- package/dist/ui/animations.ts +316 -0
- package/dist/ui/attach.js +13 -10
- package/dist/ui/attach.ts +607 -0
- package/dist/ui/charts.tsx +74 -0
- package/dist/ui/checked.d.ts +2 -2
- package/dist/ui/checked.ts +32 -0
- package/dist/ui/collector.ts +140 -0
- package/dist/ui/compositions.d.ts +11 -2
- package/dist/ui/compositions.js +283 -59
- package/dist/ui/compositions.ts +633 -0
- package/dist/ui/consent.ts +55 -0
- package/dist/ui/content.tsx +39 -0
- package/dist/ui/contrast.ts +27 -0
- package/dist/ui/controls.tsx +30 -0
- package/dist/ui/disclosure.tsx +22 -0
- package/dist/ui/dismiss-layer.ts +87 -0
- package/dist/ui/door.ts +140 -0
- package/dist/ui/drag.ts +316 -0
- package/dist/ui/elements/accordion.js +2 -2
- package/dist/ui/elements/accordion.tsx +47 -0
- package/dist/ui/elements/accordion.works.ts +66 -0
- package/dist/ui/elements/aspect-ratio.js +1 -1
- package/dist/ui/elements/aspect-ratio.tsx +30 -0
- package/dist/ui/elements/aspect-ratio.works.ts +5 -0
- package/dist/ui/elements/avatar.js +1 -1
- package/dist/ui/elements/avatar.tsx +33 -0
- package/dist/ui/elements/avatar.works.ts +16 -0
- package/dist/ui/elements/badge.js +1 -1
- package/dist/ui/elements/badge.tsx +32 -0
- package/dist/ui/elements/badge.works.ts +9 -0
- package/dist/ui/elements/bars.js +2 -2
- package/dist/ui/elements/bars.tsx +49 -0
- package/dist/ui/elements/bars.works.ts +29 -0
- package/dist/ui/elements/box.js +3 -2
- package/dist/ui/elements/box.tsx +35 -0
- package/dist/ui/elements/breadcrumb.js +2 -2
- package/dist/ui/elements/breadcrumb.tsx +37 -0
- package/dist/ui/elements/breadcrumb.works.ts +24 -0
- package/dist/ui/elements/button.js +1 -1
- package/dist/ui/elements/button.tsx +45 -0
- package/dist/ui/elements/button.works.tsx +52 -0
- package/dist/ui/elements/column.js +1 -1
- package/dist/ui/elements/column.tsx +28 -0
- package/dist/ui/elements/consent.js +2 -2
- package/dist/ui/elements/consent.tsx +70 -0
- package/dist/ui/elements/consent.works.tsx +132 -0
- package/dist/ui/elements/donut.js +2 -2
- package/dist/ui/elements/donut.tsx +44 -0
- package/dist/ui/elements/donut.works.ts +42 -0
- package/dist/ui/elements/each.js +1 -1
- package/dist/ui/elements/each.tsx +33 -0
- package/dist/ui/elements/each.works.tsx +82 -0
- package/dist/ui/elements/empty.js +1 -1
- package/dist/ui/elements/empty.tsx +32 -0
- package/dist/ui/elements/field.js +1 -1
- package/dist/ui/elements/field.tsx +44 -0
- package/dist/ui/elements/field.works.ts +65 -0
- package/dist/ui/elements/funnel.js +2 -2
- package/dist/ui/elements/funnel.tsx +49 -0
- package/dist/ui/elements/funnel.works.ts +37 -0
- package/dist/ui/elements/gauge.js +1 -1
- package/dist/ui/elements/gauge.tsx +39 -0
- package/dist/ui/elements/gauge.works.ts +42 -0
- package/dist/ui/elements/glyph.js +1 -1
- package/dist/ui/elements/glyph.tsx +29 -0
- package/dist/ui/elements/glyph.works.tsx +42 -0
- package/dist/ui/elements/grid.js +1 -1
- package/dist/ui/elements/grid.tsx +28 -0
- package/dist/ui/elements/heatmap.js +2 -2
- package/dist/ui/elements/heatmap.tsx +47 -0
- package/dist/ui/elements/heatmap.works.ts +24 -0
- package/dist/ui/elements/hit-box.js +1 -1
- package/dist/ui/elements/hit-box.tsx +46 -0
- package/dist/ui/elements/hit-box.works.tsx +576 -0
- package/dist/ui/elements/image.js +1 -1
- package/dist/ui/elements/image.tsx +28 -0
- package/dist/ui/elements/line-chart.js +2 -2
- package/dist/ui/elements/line-chart.tsx +44 -0
- package/dist/ui/elements/line-chart.works.ts +41 -0
- package/dist/ui/elements/link.js +1 -1
- package/dist/ui/elements/link.tsx +37 -0
- package/dist/ui/elements/link.works.ts +25 -0
- package/dist/ui/elements/media.js +1 -1
- package/dist/ui/elements/media.tsx +56 -0
- package/dist/ui/elements/menu.js +2 -2
- package/dist/ui/elements/menu.tsx +50 -0
- package/dist/ui/elements/menu.works.tsx +153 -0
- package/dist/ui/elements/money.js +1 -1
- package/dist/ui/elements/money.tsx +28 -0
- package/dist/ui/elements/money.works.ts +9 -0
- package/dist/ui/elements/pagination.js +2 -2
- package/dist/ui/elements/pagination.tsx +38 -0
- package/dist/ui/elements/pagination.works.ts +20 -0
- package/dist/ui/elements/progress.js +1 -1
- package/dist/ui/elements/progress.tsx +33 -0
- package/dist/ui/elements/progress.works.ts +8 -0
- package/dist/ui/elements/radio-group.js +2 -2
- package/dist/ui/elements/radio-group.tsx +49 -0
- package/dist/ui/elements/radio-group.works.ts +33 -0
- package/dist/ui/elements/row.js +1 -1
- package/dist/ui/elements/row.tsx +28 -0
- package/dist/ui/elements/scatter.js +1 -1
- package/dist/ui/elements/scatter.tsx +41 -0
- package/dist/ui/elements/scatter.works.ts +38 -0
- package/dist/ui/elements/select.js +2 -2
- package/dist/ui/elements/select.tsx +42 -0
- package/dist/ui/elements/select.works.ts +5 -0
- package/dist/ui/elements/separator.js +1 -1
- package/dist/ui/elements/separator.tsx +30 -0
- package/dist/ui/elements/skeleton.js +1 -1
- package/dist/ui/elements/skeleton.tsx +30 -0
- package/dist/ui/elements/slider.js +1 -1
- package/dist/ui/elements/slider.tsx +33 -0
- package/dist/ui/elements/slider.works.ts +5 -0
- package/dist/ui/elements/sparkline.js +1 -1
- package/dist/ui/elements/sparkline.tsx +36 -0
- package/dist/ui/elements/sparkline.works.ts +32 -0
- package/dist/ui/elements/spinner.js +1 -1
- package/dist/ui/elements/spinner.tsx +32 -0
- package/dist/ui/elements/stack.js +1 -1
- package/dist/ui/elements/stack.tsx +28 -0
- package/dist/ui/elements/stat.js +1 -1
- package/dist/ui/elements/stat.tsx +35 -0
- package/dist/ui/elements/stat.works.ts +14 -0
- package/dist/ui/elements/surface.js +1 -1
- package/dist/ui/elements/surface.tsx +35 -0
- package/dist/ui/elements/surface.works.ts +9 -0
- package/dist/ui/elements/table.js +2 -2
- package/dist/ui/elements/table.tsx +61 -0
- package/dist/ui/elements/table.works.ts +30 -0
- package/dist/ui/elements/tabs.js +2 -2
- package/dist/ui/elements/tabs.tsx +47 -0
- package/dist/ui/elements/tabs.works.ts +84 -0
- package/dist/ui/elements/text.js +1 -1
- package/dist/ui/elements/text.tsx +32 -0
- package/dist/ui/elements/textarea.js +1 -1
- package/dist/ui/elements/textarea.tsx +37 -0
- package/dist/ui/elements/textarea.works.ts +5 -0
- package/dist/ui/elements/time.js +1 -1
- package/dist/ui/elements/time.tsx +34 -0
- package/dist/ui/elements/time.works.ts +25 -0
- package/dist/ui/elements/toggle.js +1 -1
- package/dist/ui/elements/toggle.tsx +33 -0
- package/dist/ui/elements/toggle.works.ts +5 -0
- package/dist/ui/elements/tooltip.js +1 -1
- package/dist/ui/elements/tooltip.tsx +39 -0
- package/dist/ui/elements/tooltip.works.ts +77 -0
- package/dist/ui/elements/value.js +1 -1
- package/dist/ui/elements/value.tsx +30 -0
- package/dist/ui/elements/value.works.ts +9 -0
- package/dist/ui/elements.ts +127 -0
- package/dist/ui/focus-scope.ts +121 -0
- package/dist/ui/fonts.ts +355 -0
- package/dist/ui/generators/dev.js +2 -1
- package/dist/ui/generators/dev.ts +80 -0
- package/dist/ui/generators/elements.d.ts +1 -11
- package/dist/ui/generators/elements.js +44 -14
- package/dist/ui/generators/elements.ts +794 -0
- package/dist/ui/generators/i18n.js +2 -3
- package/dist/ui/generators/i18n.ts +300 -0
- package/dist/ui/generators/icons.js +3 -12
- package/dist/ui/generators/icons.ts +214 -0
- package/dist/ui/generators/react.js +13 -5
- package/dist/ui/generators/react.ts +269 -0
- package/dist/ui/generators/routes.d.ts +1 -0
- package/dist/ui/generators/routes.js +99 -83
- package/dist/ui/generators/routes.ts +743 -0
- package/dist/ui/generators/show.js +4 -12
- package/dist/ui/generators/show.ts +260 -0
- package/dist/ui/generators/styles.js +3 -4
- package/dist/ui/generators/styles.ts +669 -0
- package/dist/ui/generators/variants.js +2 -3
- package/dist/ui/generators/variants.ts +204 -0
- package/dist/ui/i18n/catalogs/en.ts +191 -0
- package/dist/ui/i18n/format.ts +221 -0
- package/dist/ui/i18n/locales.ts +33 -0
- package/dist/ui/i18n/parse.ts +315 -0
- package/dist/ui/i18n/translator.ts +31 -0
- package/dist/ui/i18n/types.ts +96 -0
- package/dist/ui/icons.ts +331 -0
- package/dist/ui/islands/map.ts +58 -0
- package/dist/ui/localizations.ts +75 -0
- package/dist/ui/material.ts +236 -0
- package/dist/ui/materials/mat_bordered.ts +13 -0
- package/dist/ui/materials/mat_ceramic.ts +13 -0
- package/dist/ui/materials/mat_clay.ts +13 -0
- package/dist/ui/materials/mat_elevated.ts +24 -0
- package/dist/ui/materials/mat_flat.ts +34 -0
- package/dist/ui/materials/mat_glass.ts +20 -0
- package/dist/ui/materials/mat_lit.ts +13 -0
- package/dist/ui/materials/mat_moulded.ts +11 -0
- package/dist/ui/materials.js +2 -3
- package/dist/ui/materials.ts +205 -0
- package/dist/ui/modes.ts +44 -0
- package/dist/ui/navigation.tsx +32 -0
- package/dist/ui/positioning.ts +90 -0
- package/dist/ui/presented.tsx +119 -0
- package/dist/ui/provider.d.ts +2 -0
- package/dist/ui/provider.js +7 -1
- package/dist/ui/provider.tsx +182 -0
- package/dist/ui/render-loop.ts +133 -0
- package/dist/ui/roving-focus.ts +161 -0
- package/dist/ui/screens.js +22 -22
- package/dist/ui/screens.ts +141 -0
- package/dist/ui/scroll-lock.ts +28 -0
- package/dist/ui/series.ts +97 -0
- package/dist/ui/signals.ts +386 -0
- package/dist/ui/styleguide.ts +231 -0
- package/dist/ui/styleguides.ts +374 -0
- package/dist/ui/themes.ts +22 -0
- package/dist/ui/token.js +1 -1
- package/dist/ui/token.ts +78 -0
- package/dist/ui/tokens/shadow.ts +25 -0
- package/dist/ui/tokens.js +1 -2
- package/dist/ui/tokens.ts +234 -0
- package/dist/ui/validators.ts +232 -0
- package/dist/ui/variant.d.ts +1 -0
- package/dist/ui/variant.js +3 -0
- package/dist/ui/variant.ts +352 -0
- package/dist/ui/variants/var_avatar-round.ts +14 -0
- package/dist/ui/variants/var_bubble-me.ts +14 -0
- package/dist/ui/variants/var_bubble-them.ts +13 -0
- package/dist/ui/variants/var_button-border.ts +76 -0
- package/dist/ui/variants/var_button-cta.ts +76 -0
- package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
- package/dist/ui/variants/var_button-destructive.ts +76 -0
- package/dist/ui/variants/var_button-fill.ts +76 -0
- package/dist/ui/variants/var_button-ghost.ts +72 -0
- package/dist/ui/variants/var_button-glass.ts +55 -0
- package/dist/ui/variants/var_button-normal.ts +76 -0
- package/dist/ui/variants/var_button-quiet.ts +74 -0
- package/dist/ui/variants/var_card.ts +17 -0
- package/dist/ui/variants/var_chip.ts +13 -0
- package/dist/ui/variants/var_choice.ts +15 -0
- package/dist/ui/variants/var_framed.ts +10 -0
- package/dist/ui/variants/var_glyph-plain.ts +11 -0
- package/dist/ui/variants/var_input.ts +43 -0
- package/dist/ui/variants/var_link-inline.ts +29 -0
- package/dist/ui/variants/var_list-row.ts +34 -0
- package/dist/ui/variants/var_rule.ts +10 -0
- package/dist/ui/variants/var_skeleton-block.ts +12 -0
- package/dist/ui/variants/var_spinner-turning.ts +12 -0
- package/dist/ui/variants/var_stat-tile.ts +13 -0
- package/dist/ui/variants/var_tab-bar.ts +15 -0
- package/dist/ui/variants/var_tap-target.ts +25 -0
- package/dist/ui/variants.ts +64 -0
- package/dist/ui/wrappers.d.ts +4 -1
- package/dist/ui/wrappers.js +60 -20
- package/dist/ui/wrappers.tsx +426 -0
- package/package.json +8 -2
- package/dist/ui/makers/message.d.ts +0 -1
- package/dist/ui/makers/message.js +0 -230
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readFileSync as bytesAt } from 'node:fs'
|
|
3
|
+
import { existsSync, readFileSync, statSync } from '@prestack/thread/core/files'
|
|
4
|
+
import { cwd } from 'node:process'
|
|
5
|
+
import { dirname, join, resolve } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import ts from 'typescript'
|
|
8
|
+
|
|
9
|
+
import { idOf } from '../checked.ts'
|
|
10
|
+
import { classOf } from '../variant.ts'
|
|
11
|
+
|
|
12
|
+
const OWNED = /\.(t|j)sx$/
|
|
13
|
+
|
|
14
|
+
const VARIANT_MEMBER = /^[a-z]/
|
|
15
|
+
|
|
16
|
+
interface Edit {
|
|
17
|
+
readonly from: number
|
|
18
|
+
readonly to: number
|
|
19
|
+
readonly text: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Arranging {
|
|
23
|
+
readonly from: number
|
|
24
|
+
readonly to: number
|
|
25
|
+
readonly own: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ARRANGERS = new Set(['composition', 'screen'])
|
|
29
|
+
|
|
30
|
+
const HOST = /^[a-z]/
|
|
31
|
+
|
|
32
|
+
const MAKES = 'element'
|
|
33
|
+
|
|
34
|
+
const ONE_WORD = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/
|
|
35
|
+
|
|
36
|
+
function prefixed(called: string): string {
|
|
37
|
+
return called === 'screen' ? 's-' : 'c-'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function quoted(node: ts.Node): string | undefined {
|
|
41
|
+
return ts.isStringLiteral(node) ? node.text : undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function joined(held: readonly string[]): string {
|
|
45
|
+
return held.filter((one) => one !== '').join(' ')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function arranging(held: readonly Arranging[], at: number): string | undefined {
|
|
49
|
+
let found: Arranging | undefined
|
|
50
|
+
for (const one of held) {
|
|
51
|
+
if (at < one.from || at >= one.to) continue
|
|
52
|
+
if (found === undefined || one.from > found.from) found = one
|
|
53
|
+
}
|
|
54
|
+
return found?.own
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function within(held: readonly { readonly from: number; readonly to: number }[], at: number): boolean {
|
|
58
|
+
return held.some((one) => at >= one.from && at < one.to)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function tagOf(node: ts.JsxOpeningElement | ts.JsxSelfClosingElement | ts.JsxClosingElement): ts.JsxTagNameExpression {
|
|
62
|
+
return node.tagName
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function attributes(node: ts.JsxOpeningElement | ts.JsxSelfClosingElement): ts.JsxAttributes {
|
|
66
|
+
return node.attributes
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface Held {
|
|
70
|
+
readonly at?: ts.JsxAttribute
|
|
71
|
+
readonly className?: ts.JsxAttribute
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function named(held: ts.JsxAttributes): Held {
|
|
75
|
+
let at: ts.JsxAttribute | undefined
|
|
76
|
+
let className: ts.JsxAttribute | undefined
|
|
77
|
+
for (const one of held.properties) {
|
|
78
|
+
if (!ts.isJsxAttribute(one) || !ts.isIdentifier(one.name)) continue
|
|
79
|
+
if (one.name.text === 'at') at = one
|
|
80
|
+
if (one.name.text === 'className') className = one
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...(at === undefined ? {} : { at }),
|
|
84
|
+
...(className === undefined ? {} : { className }),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wearing(held: ts.JsxAttribute | undefined, added: string, text: string): string {
|
|
89
|
+
if (held?.initializer === undefined) return `className="${added}"`
|
|
90
|
+
const value = held.initializer
|
|
91
|
+
if (ts.isStringLiteral(value)) return `className="${joined([value.text, added])}"`
|
|
92
|
+
const inner = text.slice(value.getStart() + 1, value.getEnd() - 1)
|
|
93
|
+
return `className={\`\${${inner}} ${added}\`}`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function compiled(text: string, at: string): { readonly text?: string; readonly because?: string } {
|
|
97
|
+
const file = ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
98
|
+
|
|
99
|
+
const arranges: Arranging[] = []
|
|
100
|
+
const edits: Edit[] = []
|
|
101
|
+
let because: string | undefined
|
|
102
|
+
const writing: { readonly from: number; readonly to: number }[] = []
|
|
103
|
+
|
|
104
|
+
const calls = (node: ts.Node): void => {
|
|
105
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === MAKES) {
|
|
106
|
+
writing.push({ from: node.getStart(), to: node.getEnd() })
|
|
107
|
+
}
|
|
108
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && ARRANGERS.has(node.expression.text)) {
|
|
109
|
+
const first = node.arguments[0]
|
|
110
|
+
const said = first === undefined ? undefined : quoted(first)
|
|
111
|
+
if (first !== undefined && said !== undefined) {
|
|
112
|
+
if (!ONE_WORD.test(said)) {
|
|
113
|
+
because =
|
|
114
|
+
`${at} names a ${node.expression.text} "${said}", and that name becomes the class everything inside ` +
|
|
115
|
+
'it is arranged against. This one is not a single word, so it would become two classes and neither ' +
|
|
116
|
+
'would be the one the stylesheet writes'
|
|
117
|
+
}
|
|
118
|
+
const own = `${prefixed(node.expression.text)}${idOf(said)}`
|
|
119
|
+
arranges.push({ from: node.getStart(), to: node.getEnd(), own })
|
|
120
|
+
edits.push({ from: first.getStart(), to: first.getEnd(), text: `'${own}'` })
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
ts.forEachChild(node, calls)
|
|
124
|
+
}
|
|
125
|
+
calls(file)
|
|
126
|
+
|
|
127
|
+
const tags = (node: ts.Node): void => {
|
|
128
|
+
if (ts.isJsxClosingElement(node)) {
|
|
129
|
+
const tag = tagOf(node)
|
|
130
|
+
if (ts.isPropertyAccessExpression(tag) && VARIANT_MEMBER.test(tag.name.text)) {
|
|
131
|
+
edits.push({ from: tag.getStart(), to: tag.getEnd(), text: tag.expression.getText() })
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
136
|
+
const tag = tagOf(node)
|
|
137
|
+
const held = named(attributes(node))
|
|
138
|
+
const worn: string[] = []
|
|
139
|
+
|
|
140
|
+
if (ts.isIdentifier(tag) && HOST.test(tag.text) && !within(writing, node.getStart())) {
|
|
141
|
+
because =
|
|
142
|
+
`${at} writes <${tag.text}>, which is a host element: it wears no declared style, sits in no ` +
|
|
143
|
+
'catalogue, and the style guide, a rebrand and every overlay read past it. Declare the element ' +
|
|
144
|
+
'and wear its look, or use the declared groupings where a host element would have gone'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (held.className !== undefined && !within(writing, node.getStart())) {
|
|
148
|
+
because =
|
|
149
|
+
`${at} writes a \`className\` by hand, which is CSS written in place: it names no style guide ` +
|
|
150
|
+
'row, wears no declared variant, and the style guide, a rebrand and every overlay read past ' +
|
|
151
|
+
'it. Declare a variant and wear it — `Box.card` — or name a place with `at`, which the build ' +
|
|
152
|
+
'turns into the class the arrangement already declares'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (ts.isPropertyAccessExpression(tag) && VARIANT_MEMBER.test(tag.name.text)) {
|
|
156
|
+
worn.push(classOf(idOf(tag.name.text)))
|
|
157
|
+
edits.push({ from: tag.getStart(), to: tag.getEnd(), text: tag.expression.getText() })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (held.at !== undefined) {
|
|
161
|
+
const place = held.at.initializer === undefined ? undefined : quoted(held.at.initializer)
|
|
162
|
+
if (place === undefined) {
|
|
163
|
+
because = `${at} gives a place that is not written out where it is used. A place is a class the build joins, so it has to be a plain word here`
|
|
164
|
+
} else {
|
|
165
|
+
const own = arranging(arranges, node.getStart())
|
|
166
|
+
if (own === undefined) {
|
|
167
|
+
because =
|
|
168
|
+
`${at} names the place "${place}" outside any composition or screen, and a place is declared by ` +
|
|
169
|
+
'whatever arranges it. Nothing arranges this one, so the name points at nothing'
|
|
170
|
+
} else {
|
|
171
|
+
worn.push(`${own}-${place}`)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
edits.push({ from: held.at.getFullStart(), to: held.at.getEnd(), text: '' })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (worn.length > 0) {
|
|
178
|
+
const wrote = wearing(held.className, joined(worn), text)
|
|
179
|
+
if (held.className === undefined) edits.push({ from: tag.getEnd(), to: tag.getEnd(), text: ` ${wrote}` })
|
|
180
|
+
else edits.push({ from: held.className.getStart(), to: held.className.getEnd(), text: wrote })
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
ts.forEachChild(node, tags)
|
|
185
|
+
}
|
|
186
|
+
tags(file)
|
|
187
|
+
|
|
188
|
+
if (because !== undefined) return { because }
|
|
189
|
+
if (edits.length === 0) return { text }
|
|
190
|
+
|
|
191
|
+
let made = text
|
|
192
|
+
for (const one of [...edits].sort((a, b) => b.from - a.from)) {
|
|
193
|
+
made = made.slice(0, one.from) + one.text + made.slice(one.to)
|
|
194
|
+
}
|
|
195
|
+
return { text: made }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface Loading {
|
|
199
|
+
readonly onLoad: (
|
|
200
|
+
opts: { filter: RegExp },
|
|
201
|
+
run: (args: { path: string }) => { contents?: string; loader?: string; errors?: readonly { text: string }[] } | undefined,
|
|
202
|
+
) => void
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let checked: string | null | undefined
|
|
206
|
+
let against: string | undefined
|
|
207
|
+
|
|
208
|
+
function treeAt(from: string): string | undefined {
|
|
209
|
+
for (const start of [dirname(resolve(from)), resolve(cwd())]) {
|
|
210
|
+
let root = start
|
|
211
|
+
while (root !== dirname(root)) {
|
|
212
|
+
if (existsSync(join(root, '.thread', 'gate.json')) || existsSync(join(root, '_threads'))) return root
|
|
213
|
+
root = dirname(root)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function ungated(from: string): string | null {
|
|
220
|
+
const root = treeAt(from)
|
|
221
|
+
const at = root === undefined ? '' : join(root, '.thread', 'gate.json')
|
|
222
|
+
|
|
223
|
+
const stamp = at !== '' && existsSync(at) ? `${String(statSync(at).mtimeMs)}:${String(statSync(at).size)}` : `none:${from}`
|
|
224
|
+
if (checked !== undefined && against === stamp) return checked
|
|
225
|
+
against = stamp
|
|
226
|
+
checked = null
|
|
227
|
+
|
|
228
|
+
if (at === '' || !existsSync(at)) {
|
|
229
|
+
checked =
|
|
230
|
+
'this tree has no passing gate, so nothing in it may compile. Run `thread gate` — or the ' +
|
|
231
|
+
'`dev`, `build` and `start` scripts, which run it for you. A gate that has never run is not a ' +
|
|
232
|
+
'gate that passed, and the file you are compiling was never measured. Compiling ' +
|
|
233
|
+
`\`${from}\`, it looked for a gate receipt from \`${dirname(resolve(from))}\` and from ` +
|
|
234
|
+
`\`${resolve(cwd())}\` upwards${at === '' ? ', and neither reached a tree' : `, and found none at ${at}`}`
|
|
235
|
+
return checked
|
|
236
|
+
}
|
|
237
|
+
const held = JSON.parse(readFileSync(at, 'utf8')) as { of?: Record<string, string> }
|
|
238
|
+
for (const [named, was] of Object.entries(held.of ?? {})) {
|
|
239
|
+
const now = existsSync(join(dirname(dirname(at)), named))
|
|
240
|
+
? createHash('sha256').update(bytesAt(join(dirname(dirname(at)), named))).digest('hex').slice(0, 16)
|
|
241
|
+
: 'gone'
|
|
242
|
+
if (now === was) continue
|
|
243
|
+
checked = `${named} has changed since the gate last passed, so what this tree declares and what it was measured as no longer agree. Run \`thread gate\``
|
|
244
|
+
return checked
|
|
245
|
+
}
|
|
246
|
+
return checked
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default function loader(this: { readonly resourcePath?: string } | void, source: string): string {
|
|
250
|
+
const at = typeof this?.resourcePath === 'string' ? this.resourcePath : ''
|
|
251
|
+
const skipped = at === '' ? null : ungated(at)
|
|
252
|
+
if (skipped !== null) throw new Error(`thread: ${skipped}.`)
|
|
253
|
+
const made = compiled(source, at)
|
|
254
|
+
|
|
255
|
+
if (made.because !== undefined) throw new Error(made.because)
|
|
256
|
+
return made.text ?? source
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export const plugin: { readonly name: string; readonly setup: (build: Loading) => void } = {
|
|
260
|
+
name: 'thread-react',
|
|
261
|
+
setup(build) {
|
|
262
|
+
build.onLoad({ filter: OWNED }, (args) => {
|
|
263
|
+
const text = readFileSync(args.path, 'utf8')
|
|
264
|
+
const made = compiled(text, args.path)
|
|
265
|
+
if (made.because !== undefined) return { errors: [{ text: made.because }] }
|
|
266
|
+
return { contents: made.text ?? text, loader: 'tsx' }
|
|
267
|
+
})
|
|
268
|
+
},
|
|
269
|
+
}
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
10
1
|
import { dirname, join, relative } from 'node:path';
|
|
11
2
|
import { argv, stderr, stdout } from 'node:process';
|
|
12
|
-
import { pathToFileURL } from 'node:url';
|
|
13
3
|
import ts from 'typescript';
|
|
4
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from "@prestack/thread/core/files";
|
|
14
5
|
import { exit, invoked } from "@prestack/thread/core/invoked";
|
|
6
|
+
import { valuesIn } from "@prestack/thread/core/rows";
|
|
15
7
|
import { named as packageOf } from "@prestack/thread/core/scope";
|
|
16
8
|
import { findTree, inTree } from "@prestack/thread/core/tree";
|
|
17
|
-
import {
|
|
9
|
+
import { PERMITS_AT } from "@prestack/thread/operations/access";
|
|
10
|
+
import { COMPILED, compiledAt, nameOf as compositionOf } from "../compositions.js";
|
|
18
11
|
import { PRESENTATIONS } from "../door.js";
|
|
19
12
|
import { PAGE, entryAmong, nameOf, pageAt, presentedIn, routeFor } from "../screens.js";
|
|
20
13
|
import { keyProps } from "./i18n.js";
|
|
@@ -24,29 +17,38 @@ const DECLARED = `src/_generated/seams.${'ts'}`;
|
|
|
24
17
|
const CLIENT = `app/_generated/dispatching.${'ts'}`;
|
|
25
18
|
const DOOR = `app/_generated/door.${'tsx'}`;
|
|
26
19
|
const MAP = `app/_generated/map.${'ts'}`;
|
|
27
|
-
const PROVIDER = `${
|
|
28
|
-
const GRAMMAR = `${
|
|
20
|
+
const PROVIDER = `${COMPILED}/ui/provider.tsx`;
|
|
21
|
+
const GRAMMAR = `${COMPILED}/ui/islands/map.ts`;
|
|
29
22
|
const MOVES = 'navigateTo';
|
|
23
|
+
const sheetAt = (at) => {
|
|
24
|
+
if (!existsSync(at))
|
|
25
|
+
return {};
|
|
26
|
+
return valuesIn(readFileSync(at));
|
|
27
|
+
};
|
|
30
28
|
function door(knowing, wording, dispatching, moving, presenting) {
|
|
31
29
|
const opening = presenting.length > 0;
|
|
32
|
-
const props = `asking={ASKING}${knowing ? ' actor={actor}' : ''}${dispatching ? ' dispatch={dispatchTo}' : ''}` +
|
|
30
|
+
const props = `asking={ASKING}${knowing ? ' actor={actor} permits={gate}' : ''}${dispatching ? ' dispatch={dispatchTo}' : ''}` +
|
|
33
31
|
`${moving ? ' navigateTo={navigateTo}' : ''}${wording ? ' locale={SOURCE} wording={WORDING}' : ''}`;
|
|
34
32
|
return [
|
|
35
33
|
"'use client'",
|
|
36
34
|
'',
|
|
37
35
|
'/* GENERATED by thread — do not edit. It is derived from what this app declares. */',
|
|
38
36
|
'',
|
|
39
|
-
...(moving
|
|
40
|
-
? [
|
|
37
|
+
...(moving || knowing
|
|
38
|
+
? [
|
|
39
|
+
`import { useCallback, ${moving && opening ? 'useState, ' : ''}type ReactNode } from 'react'`,
|
|
40
|
+
...(moving ? ["import { useRouter } from 'next/navigation'"] : []),
|
|
41
|
+
]
|
|
41
42
|
: ["import type { ReactNode } from 'react'"]),
|
|
42
43
|
'',
|
|
43
|
-
`import { ThreadUi } from '${PROVIDER}'`,
|
|
44
|
+
`import { ThreadUi } from '${reaching(DOOR, PROVIDER)}'`,
|
|
44
45
|
'',
|
|
45
46
|
"import { ASKING } from './declared.ts'",
|
|
47
|
+
...(knowing ? [`import { permits } from '${reaching(DOOR, PERMITS_AT)}'`] : []),
|
|
46
48
|
...(dispatching ? ["import { dispatchTo } from './dispatching.ts'"] : []),
|
|
47
49
|
...(moving ? ["import { MAP } from './map.ts'"] : []),
|
|
48
50
|
...(wording ? ["import { SOURCE, WORDING } from './wording.ts'"] : []),
|
|
49
|
-
...(opening ? ['', ...presenting.map((one) => `import { ${one.name} } from '${reaching(DOOR, one.at)}'`)] : []),
|
|
51
|
+
...(opening ? ['', ...presenting.map((one) => `import { ${one.name} } from '${reaching(DOOR, compiledAt(one.at))}'`)] : []),
|
|
50
52
|
'',
|
|
51
53
|
'export function Door({',
|
|
52
54
|
...(knowing
|
|
@@ -59,6 +61,14 @@ function door(knowing, wording, dispatching, moving, presenting) {
|
|
|
59
61
|
: []),
|
|
60
62
|
' readonly children: ReactNode',
|
|
61
63
|
'}) {',
|
|
64
|
+
...(knowing
|
|
65
|
+
? [
|
|
66
|
+
' const gate = useCallback(',
|
|
67
|
+
' (action: string): boolean => (actor === undefined ? false : permits(actor, action, null).allow),',
|
|
68
|
+
' [actor],',
|
|
69
|
+
' )',
|
|
70
|
+
]
|
|
71
|
+
: []),
|
|
62
72
|
...(moving
|
|
63
73
|
? [
|
|
64
74
|
' const router = useRouter()',
|
|
@@ -115,6 +125,21 @@ const TYPED = /screen\(\s*'[^']*'\s*,\s*\{[^}]*route:\s*'([^']*)'/;
|
|
|
115
125
|
export function typedRoute(source) {
|
|
116
126
|
return TYPED.exec(source)?.[1];
|
|
117
127
|
}
|
|
128
|
+
const SHOWING = /screen\(\s*'[^']*'\s*,\s*\{[^}]*views:\s*\[([^\]]*)\]/;
|
|
129
|
+
export function viewsIn(source) {
|
|
130
|
+
const held = SHOWING.exec(source)?.[1];
|
|
131
|
+
if (held === undefined)
|
|
132
|
+
return [];
|
|
133
|
+
return [...held.matchAll(QUOTED)].map(([, single, double]) => single ?? double ?? '').filter((one) => one !== '');
|
|
134
|
+
}
|
|
135
|
+
const VIEW = /^vw_[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
|
|
136
|
+
const OPERATION = /operation:\s*"([^"]+)"/g;
|
|
137
|
+
function reachable(at) {
|
|
138
|
+
const held = join(at, CLIENT);
|
|
139
|
+
if (!existsSync(held))
|
|
140
|
+
return [];
|
|
141
|
+
return [...readFileSync(held, 'utf8').matchAll(OPERATION)].map(([, one]) => one ?? '');
|
|
142
|
+
}
|
|
118
143
|
function written(node) {
|
|
119
144
|
if (node === undefined)
|
|
120
145
|
return undefined;
|
|
@@ -243,7 +268,7 @@ function mapModule(map) {
|
|
|
243
268
|
return [
|
|
244
269
|
'/* GENERATED by thread — do not edit. It is derived from the screens, and from every navigateTo they and their compositions hold. */',
|
|
245
270
|
'',
|
|
246
|
-
`import type { MapIsland } from '${GRAMMAR}'`,
|
|
271
|
+
`import type { MapIsland } from '${reaching(MAP, GRAMMAR)}'`,
|
|
247
272
|
'',
|
|
248
273
|
'export const MAP: MapIsland = {',
|
|
249
274
|
' specVersion: 1,',
|
|
@@ -275,7 +300,7 @@ function reaching(file, at) {
|
|
|
275
300
|
return `${up}${at.replace(/\.tsx?$/, '')}`;
|
|
276
301
|
}
|
|
277
302
|
function page(name, at, route) {
|
|
278
|
-
const from = reaching(pageAt(route), at);
|
|
303
|
+
const from = reaching(pageAt(route), compiledAt(at));
|
|
279
304
|
return [
|
|
280
305
|
'/* GENERATED by thread — do not edit. It follows from the screen\'s name and its place among the pages. */',
|
|
281
306
|
'',
|
|
@@ -287,32 +312,47 @@ function page(name, at, route) {
|
|
|
287
312
|
'',
|
|
288
313
|
].join('\n');
|
|
289
314
|
}
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
return [
|
|
293
|
-
"throw new Error('thread: the compiler that checks this tree cannot be found, so nothing here " +
|
|
294
|
-
"can be compiled. Reinstall the framework packages. A check that cannot run is not a check " +
|
|
295
|
-
"that passed.')",
|
|
296
|
-
'',
|
|
297
|
-
];
|
|
298
|
-
}
|
|
315
|
+
function gate(typed) {
|
|
316
|
+
const said = typed ? ' as { of?: Record<string, string> }' : '';
|
|
299
317
|
return [
|
|
300
|
-
"import {
|
|
318
|
+
"import { createHash } from 'node:crypto'",
|
|
319
|
+
"import { existsSync, readFileSync, statSync } from 'node:fs'",
|
|
320
|
+
"import { join } from 'node:path'",
|
|
301
321
|
'',
|
|
302
|
-
"const
|
|
303
|
-
'if (
|
|
322
|
+
"const receipt = join(process.cwd(), '.thread', 'gate.json')",
|
|
323
|
+
'if (!existsSync(receipt)) {',
|
|
324
|
+
' throw new Error(',
|
|
325
|
+
" 'thread: this tree has no passing gate, so nothing in it may compile. Run `thread gate` — or the ' +",
|
|
326
|
+
" 'dev, build and start scripts, which run it for you. A gate that has never run is not a gate that passed.',",
|
|
327
|
+
' )',
|
|
328
|
+
'}',
|
|
329
|
+
"const passed = Symbol.for('thread.gate')",
|
|
330
|
+
"const stamp = String(statSync(receipt).mtimeMs) + ':' + String(statSync(receipt).size)",
|
|
331
|
+
'if (Reflect.get(globalThis, passed) !== stamp) {',
|
|
332
|
+
` const measured = JSON.parse(readFileSync(receipt, 'utf8'))${said}`,
|
|
333
|
+
' for (const [named, was] of Object.entries(measured.of ?? {})) {',
|
|
334
|
+
' const at = join(process.cwd(), named)',
|
|
335
|
+
" const now = existsSync(at) ? createHash('sha256').update(readFileSync(at)).digest('hex').slice(0, 16) : 'gone'",
|
|
336
|
+
' if (now === was) continue',
|
|
337
|
+
' throw new Error(',
|
|
338
|
+
" 'thread: ' + named + ' has changed since the gate last passed, so what this tree declares and what ' +",
|
|
339
|
+
" 'it was measured as no longer agree. Run `thread gate`',",
|
|
340
|
+
' )',
|
|
341
|
+
' }',
|
|
342
|
+
' Reflect.set(globalThis, passed, stamp)',
|
|
343
|
+
'}',
|
|
304
344
|
'',
|
|
305
345
|
];
|
|
306
346
|
}
|
|
307
|
-
function layout(wording, icons, source,
|
|
347
|
+
function layout(wording, icons, source, knowing, doored) {
|
|
308
348
|
return [
|
|
309
349
|
'/* GENERATED by thread — do not edit. It is derived from what the other generators produce. */',
|
|
310
350
|
'',
|
|
311
351
|
'/// <reference types="next/types/global" />',
|
|
312
352
|
'',
|
|
313
|
-
...
|
|
353
|
+
...gate(true),
|
|
314
354
|
"import type { ReactNode } from 'react'",
|
|
315
|
-
...(doored ? ["import { Door } from './_generated/door.tsx'"] : [`import { ThreadUi } from '${PROVIDER}'`]),
|
|
355
|
+
...(doored ? ["import { Door } from './_generated/door.tsx'"] : [`import { ThreadUi } from '${reaching(LAYOUT, PROVIDER)}'`]),
|
|
316
356
|
...(doored ? [] : ["import { ASKING } from './_generated/declared.ts'"]),
|
|
317
357
|
...(knowing
|
|
318
358
|
? ["import { headers } from 'next/headers'", `import { actor } from '../${DECLARED}'`]
|
|
@@ -365,14 +405,6 @@ function rootFor(at) {
|
|
|
365
405
|
const common = shared.join('/');
|
|
366
406
|
return relative(here, common) || '.';
|
|
367
407
|
}
|
|
368
|
-
function loaderFor(at) {
|
|
369
|
-
try {
|
|
370
|
-
return realpathSync(join(at, 'node_modules', packageOf('ui'), 'dist', 'generators', 'react.js'));
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
return undefined;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
408
|
const DRIVER = /export\s+const\s+driver\s*:[^=\n]*=\s*\[([^\]]*)\]/;
|
|
377
409
|
const QUOTED = /'([^']+)'|"([^"]+)"/g;
|
|
378
410
|
const RELATIVE = /(?:^|[\s;{(])import\s+(?!type\s)(?:[\w*{][^'"]*from\s+)?['"](\.[^'"]+)['"]/g;
|
|
@@ -414,45 +446,14 @@ function externalFor(at) {
|
|
|
414
446
|
}
|
|
415
447
|
return [...found].sort();
|
|
416
448
|
}
|
|
417
|
-
function
|
|
418
|
-
try {
|
|
419
|
-
const held = JSON.parse(readFileSync(join(dirname(loader), '..', '..', 'package.json'), 'utf8'));
|
|
420
|
-
return held.version ?? 'unknown';
|
|
421
|
-
}
|
|
422
|
-
catch {
|
|
423
|
-
return 'unknown';
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
function config(root, loader, external) {
|
|
449
|
+
function config(root, external) {
|
|
427
450
|
const turbopack = [
|
|
428
451
|
...(root === undefined ? [] : [` root: join(import.meta.dirname, ${JSON.stringify(root)}),`]),
|
|
429
|
-
...(loader === undefined
|
|
430
|
-
? []
|
|
431
|
-
: [
|
|
432
|
-
' rules: {',
|
|
433
|
-
` '**/*.tsx': { loaders: [{ loader: ${JSON.stringify(loader)}, options: ${JSON.stringify({ built: builtAs(loader) })} }] },`,
|
|
434
|
-
' },',
|
|
435
|
-
]),
|
|
436
452
|
];
|
|
437
|
-
const gate = loader === undefined
|
|
438
|
-
? [
|
|
439
|
-
"throw new Error('thread: the compiler that checks this tree cannot be found, so nothing here " +
|
|
440
|
-
"can be compiled. Reinstall the framework packages. A check that cannot run is not a check " +
|
|
441
|
-
"that passed.')",
|
|
442
|
-
'',
|
|
443
|
-
]
|
|
444
|
-
: [
|
|
445
|
-
`import { ungated } from ${JSON.stringify(loader)}`,
|
|
446
|
-
'',
|
|
447
|
-
'const why = ungated(import.meta.filename)',
|
|
448
|
-
'if (why !== null) throw new Error(`thread: ${why}`)',
|
|
449
|
-
'',
|
|
450
|
-
];
|
|
451
453
|
return [
|
|
452
454
|
'/* GENERATED by thread — do not edit. It is derived from where the packages resolve. */',
|
|
453
455
|
'',
|
|
454
|
-
...(
|
|
455
|
-
...gate,
|
|
456
|
+
...gate(false),
|
|
456
457
|
'export default {',
|
|
457
458
|
...(turbopack.length === 0 ? [] : [' turbopack: {', ...turbopack, ' },']),
|
|
458
459
|
...(external.length === 0
|
|
@@ -471,14 +472,14 @@ async function run() {
|
|
|
471
472
|
stdout.write(`routes: this tree has no ${relative(at, screensAt)}, so it declares no screen and there is no map, page or layout to write or check.\n`);
|
|
472
473
|
return 0;
|
|
473
474
|
}
|
|
474
|
-
const sheet = (
|
|
475
|
+
const sheet = valuesIn(readFileSync(screensAt));
|
|
475
476
|
if (sheet.SCREEN === undefined) {
|
|
476
477
|
stderr.write('routes: _threads/screens.ts exports no `SCREEN`, so nothing declares a screen here.\n');
|
|
477
478
|
return 1;
|
|
478
479
|
}
|
|
479
|
-
const held = (
|
|
480
|
+
const held = sheetAt(inTree(tree, `localizations.${'ts'}`));
|
|
480
481
|
const source = held.LOCALIZATION?.holds?.find((one) => one.id === 'source')?.is ?? 'en';
|
|
481
|
-
const placed = (
|
|
482
|
+
const placed = sheetAt(inTree(tree, `compositions.${'ts'}`));
|
|
482
483
|
const compositions = placed.COMPOSITION?.declares ?? [];
|
|
483
484
|
const rows = sheet.SCREEN.declares ?? [];
|
|
484
485
|
const texts = new Map();
|
|
@@ -495,6 +496,7 @@ async function run() {
|
|
|
495
496
|
const made = [];
|
|
496
497
|
const routes = new Map();
|
|
497
498
|
const screens = [];
|
|
499
|
+
const reaches = reachable(at);
|
|
498
500
|
for (const row of rows) {
|
|
499
501
|
const text = texts.get(row.id) ?? '';
|
|
500
502
|
const typed = typedRoute(text);
|
|
@@ -514,12 +516,27 @@ async function run() {
|
|
|
514
516
|
}
|
|
515
517
|
const route = shownAs === PAGE ? routeFor(row.id, row.id === entry) : undefined;
|
|
516
518
|
routes.set(name, route);
|
|
519
|
+
const shows = viewsIn(text);
|
|
520
|
+
for (const one of shows) {
|
|
521
|
+
if (!VIEW.test(one)) {
|
|
522
|
+
stderr.write(`routes: ${row.at} shows \`${one}\`, and that is not the name of a view. A view is ` +
|
|
523
|
+
'`vw_` and then lower case words joined by underscores. A screen names a declared read, ' +
|
|
524
|
+
'and never states what the read does.\n');
|
|
525
|
+
return 1;
|
|
526
|
+
}
|
|
527
|
+
if (!reaches.includes(one)) {
|
|
528
|
+
stderr.write(`routes: ${row.at} shows \`${one}\`, and this app reaches no operation by that name. It reaches ` +
|
|
529
|
+
`${reaches.length === 0 ? 'nothing yet — \`thread capabilities\` writes that list, and the settle runs it before this' : reaches.join(', ')}. Declare the view with ` +
|
|
530
|
+
'`thread create operation`, or name one of those.\n');
|
|
531
|
+
return 1;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
517
534
|
screens.push({
|
|
518
535
|
id: name,
|
|
519
536
|
nameKey: `screen.${row.id}`,
|
|
520
537
|
...(route === undefined ? {} : { path: route }),
|
|
521
538
|
entry: row.id === entry,
|
|
522
|
-
shows: [],
|
|
539
|
+
shows: [...shows],
|
|
523
540
|
});
|
|
524
541
|
if (route === undefined)
|
|
525
542
|
continue;
|
|
@@ -541,7 +558,7 @@ async function run() {
|
|
|
541
558
|
}
|
|
542
559
|
const map = { specVersion: 1, screens, links };
|
|
543
560
|
made.push({ path: MAP, text: mapModule(map) });
|
|
544
|
-
made.push({ path: CONFIG, text: config(rootFor(at),
|
|
561
|
+
made.push({ path: CONFIG, text: config(rootFor(at), externalFor(at)) });
|
|
545
562
|
const dispatching = existsSync(join(at, CLIENT));
|
|
546
563
|
const moving = rows.length > 0;
|
|
547
564
|
const doored = dispatching || moving;
|
|
@@ -552,7 +569,7 @@ async function run() {
|
|
|
552
569
|
made.push({ path: DOOR, text: door(existsSync(join(at, DECLARED)), existsSync(join(at, WORDING)), dispatching, moving, presenting) });
|
|
553
570
|
made.push({
|
|
554
571
|
path: LAYOUT,
|
|
555
|
-
text: layout(existsSync(join(at, WORDING)), existsSync(join(at, 'app/_generated/icons.ts')), source,
|
|
572
|
+
text: layout(existsSync(join(at, WORDING)), existsSync(join(at, 'app/_generated/icons.ts')), source, existsSync(join(at, DECLARED)), doored),
|
|
556
573
|
});
|
|
557
574
|
const paged = map.screens.filter((one) => one.path !== undefined).length;
|
|
558
575
|
const counted = `${String(map.screens.length)} screen(s), ${String(paged)} route(s), ${String(map.links.length)} link(s)`;
|
|
@@ -584,7 +601,6 @@ async function run() {
|
|
|
584
601
|
return 0;
|
|
585
602
|
}
|
|
586
603
|
for (const one of made) {
|
|
587
|
-
mkdirSync(dirname(join(at, one.path)), { recursive: true });
|
|
588
604
|
writeFileSync(join(at, one.path), one.text, 'utf8');
|
|
589
605
|
}
|
|
590
606
|
stdout.write(`routes: the map — ${counted}\n`);
|