@prestack/thread-ui 0.0.1 → 0.0.2
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/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.ts +317 -0
- package/dist/ui/attach.js +13 -4
- package/dist/ui/attach.ts +613 -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 +10 -1
- package/dist/ui/compositions.js +262 -23
- package/dist/ui/compositions.ts +640 -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.ts +79 -0
- package/dist/ui/generators/elements.d.ts +1 -11
- package/dist/ui/generators/elements.js +43 -13
- package/dist/ui/generators/elements.ts +794 -0
- package/dist/ui/generators/i18n.ts +301 -0
- package/dist/ui/generators/icons.ts +214 -0
- package/dist/ui/generators/react.js +10 -3
- package/dist/ui/generators/react.ts +268 -0
- package/dist/ui/generators/routes.d.ts +1 -0
- package/dist/ui/generators/routes.js +89 -69
- package/dist/ui/generators/routes.ts +739 -0
- package/dist/ui/generators/show.ts +259 -0
- package/dist/ui/generators/styles.js +1 -1
- package/dist/ui/generators/styles.ts +670 -0
- package/dist/ui/generators/variants.ts +205 -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/makers/message.ts +292 -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.ts +206 -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 +17 -8
- package/dist/ui/screens.ts +142 -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.ts +235 -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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
3
|
+
import { cwd } from 'node:process'
|
|
4
|
+
import { dirname, join, resolve } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import ts from 'typescript'
|
|
7
|
+
|
|
8
|
+
import { idOf } from '../checked.ts'
|
|
9
|
+
import { classOf } from '../variant.ts'
|
|
10
|
+
|
|
11
|
+
const OWNED = /\.(t|j)sx$/
|
|
12
|
+
|
|
13
|
+
const VARIANT_MEMBER = /^[a-z]/
|
|
14
|
+
|
|
15
|
+
interface Edit {
|
|
16
|
+
readonly from: number
|
|
17
|
+
readonly to: number
|
|
18
|
+
readonly text: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Arranging {
|
|
22
|
+
readonly from: number
|
|
23
|
+
readonly to: number
|
|
24
|
+
readonly own: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const ARRANGERS = new Set(['composition', 'screen'])
|
|
28
|
+
|
|
29
|
+
const HOST = /^[a-z]/
|
|
30
|
+
|
|
31
|
+
const MAKES = 'element'
|
|
32
|
+
|
|
33
|
+
const ONE_WORD = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/
|
|
34
|
+
|
|
35
|
+
function prefixed(called: string): string {
|
|
36
|
+
return called === 'screen' ? 's-' : 'c-'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function quoted(node: ts.Node): string | undefined {
|
|
40
|
+
return ts.isStringLiteral(node) ? node.text : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function joined(held: readonly string[]): string {
|
|
44
|
+
return held.filter((one) => one !== '').join(' ')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function arranging(held: readonly Arranging[], at: number): string | undefined {
|
|
48
|
+
let found: Arranging | undefined
|
|
49
|
+
for (const one of held) {
|
|
50
|
+
if (at < one.from || at >= one.to) continue
|
|
51
|
+
if (found === undefined || one.from > found.from) found = one
|
|
52
|
+
}
|
|
53
|
+
return found?.own
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function within(held: readonly { readonly from: number; readonly to: number }[], at: number): boolean {
|
|
57
|
+
return held.some((one) => at >= one.from && at < one.to)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function tagOf(node: ts.JsxOpeningElement | ts.JsxSelfClosingElement | ts.JsxClosingElement): ts.JsxTagNameExpression {
|
|
61
|
+
return node.tagName
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function attributes(node: ts.JsxOpeningElement | ts.JsxSelfClosingElement): ts.JsxAttributes {
|
|
65
|
+
return node.attributes
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface Held {
|
|
69
|
+
readonly at?: ts.JsxAttribute
|
|
70
|
+
readonly className?: ts.JsxAttribute
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function named(held: ts.JsxAttributes): Held {
|
|
74
|
+
let at: ts.JsxAttribute | undefined
|
|
75
|
+
let className: ts.JsxAttribute | undefined
|
|
76
|
+
for (const one of held.properties) {
|
|
77
|
+
if (!ts.isJsxAttribute(one) || !ts.isIdentifier(one.name)) continue
|
|
78
|
+
if (one.name.text === 'at') at = one
|
|
79
|
+
if (one.name.text === 'className') className = one
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
...(at === undefined ? {} : { at }),
|
|
83
|
+
...(className === undefined ? {} : { className }),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function wearing(held: ts.JsxAttribute | undefined, added: string, text: string): string {
|
|
88
|
+
if (held?.initializer === undefined) return `className="${added}"`
|
|
89
|
+
const value = held.initializer
|
|
90
|
+
if (ts.isStringLiteral(value)) return `className="${joined([value.text, added])}"`
|
|
91
|
+
const inner = text.slice(value.getStart() + 1, value.getEnd() - 1)
|
|
92
|
+
return `className={\`\${${inner}} ${added}\`}`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function compiled(text: string, at: string): { readonly text?: string; readonly because?: string } {
|
|
96
|
+
const file = ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
97
|
+
|
|
98
|
+
const arranges: Arranging[] = []
|
|
99
|
+
const edits: Edit[] = []
|
|
100
|
+
let because: string | undefined
|
|
101
|
+
const writing: { readonly from: number; readonly to: number }[] = []
|
|
102
|
+
|
|
103
|
+
const calls = (node: ts.Node): void => {
|
|
104
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === MAKES) {
|
|
105
|
+
writing.push({ from: node.getStart(), to: node.getEnd() })
|
|
106
|
+
}
|
|
107
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && ARRANGERS.has(node.expression.text)) {
|
|
108
|
+
const first = node.arguments[0]
|
|
109
|
+
const said = first === undefined ? undefined : quoted(first)
|
|
110
|
+
if (first !== undefined && said !== undefined) {
|
|
111
|
+
if (!ONE_WORD.test(said)) {
|
|
112
|
+
because =
|
|
113
|
+
`${at} names a ${node.expression.text} "${said}", and that name becomes the class everything inside ` +
|
|
114
|
+
'it is arranged against. This one is not a single word, so it would become two classes and neither ' +
|
|
115
|
+
'would be the one the stylesheet writes'
|
|
116
|
+
}
|
|
117
|
+
const own = `${prefixed(node.expression.text)}${idOf(said)}`
|
|
118
|
+
arranges.push({ from: node.getStart(), to: node.getEnd(), own })
|
|
119
|
+
edits.push({ from: first.getStart(), to: first.getEnd(), text: `'${own}'` })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
ts.forEachChild(node, calls)
|
|
123
|
+
}
|
|
124
|
+
calls(file)
|
|
125
|
+
|
|
126
|
+
const tags = (node: ts.Node): void => {
|
|
127
|
+
if (ts.isJsxClosingElement(node)) {
|
|
128
|
+
const tag = tagOf(node)
|
|
129
|
+
if (ts.isPropertyAccessExpression(tag) && VARIANT_MEMBER.test(tag.name.text)) {
|
|
130
|
+
edits.push({ from: tag.getStart(), to: tag.getEnd(), text: tag.expression.getText() })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
135
|
+
const tag = tagOf(node)
|
|
136
|
+
const held = named(attributes(node))
|
|
137
|
+
const worn: string[] = []
|
|
138
|
+
|
|
139
|
+
if (ts.isIdentifier(tag) && HOST.test(tag.text) && !within(writing, node.getStart())) {
|
|
140
|
+
because =
|
|
141
|
+
`${at} writes <${tag.text}>, which is a host element: it wears no declared style, sits in no ` +
|
|
142
|
+
'catalogue, and the style guide, a rebrand and every overlay read past it. Declare the element ' +
|
|
143
|
+
'and wear its look, or use the declared groupings where a host element would have gone'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (held.className !== undefined && !within(writing, node.getStart())) {
|
|
147
|
+
because =
|
|
148
|
+
`${at} writes a \`className\` by hand, which is CSS written in place: it names no style guide ` +
|
|
149
|
+
'row, wears no declared variant, and the style guide, a rebrand and every overlay read past ' +
|
|
150
|
+
'it. Declare a variant and wear it — `Box.card` — or name a place with `at`, which the build ' +
|
|
151
|
+
'turns into the class the arrangement already declares'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (ts.isPropertyAccessExpression(tag) && VARIANT_MEMBER.test(tag.name.text)) {
|
|
155
|
+
worn.push(classOf(idOf(tag.name.text)))
|
|
156
|
+
edits.push({ from: tag.getStart(), to: tag.getEnd(), text: tag.expression.getText() })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (held.at !== undefined) {
|
|
160
|
+
const place = held.at.initializer === undefined ? undefined : quoted(held.at.initializer)
|
|
161
|
+
if (place === undefined) {
|
|
162
|
+
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`
|
|
163
|
+
} else {
|
|
164
|
+
const own = arranging(arranges, node.getStart())
|
|
165
|
+
if (own === undefined) {
|
|
166
|
+
because =
|
|
167
|
+
`${at} names the place "${place}" outside any composition or screen, and a place is declared by ` +
|
|
168
|
+
'whatever arranges it. Nothing arranges this one, so the name points at nothing'
|
|
169
|
+
} else {
|
|
170
|
+
worn.push(`${own}-${place}`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
edits.push({ from: held.at.getFullStart(), to: held.at.getEnd(), text: '' })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (worn.length > 0) {
|
|
177
|
+
const wrote = wearing(held.className, joined(worn), text)
|
|
178
|
+
if (held.className === undefined) edits.push({ from: tag.getEnd(), to: tag.getEnd(), text: ` ${wrote}` })
|
|
179
|
+
else edits.push({ from: held.className.getStart(), to: held.className.getEnd(), text: wrote })
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
ts.forEachChild(node, tags)
|
|
184
|
+
}
|
|
185
|
+
tags(file)
|
|
186
|
+
|
|
187
|
+
if (because !== undefined) return { because }
|
|
188
|
+
if (edits.length === 0) return { text }
|
|
189
|
+
|
|
190
|
+
let made = text
|
|
191
|
+
for (const one of [...edits].sort((a, b) => b.from - a.from)) {
|
|
192
|
+
made = made.slice(0, one.from) + one.text + made.slice(one.to)
|
|
193
|
+
}
|
|
194
|
+
return { text: made }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface Loading {
|
|
198
|
+
readonly onLoad: (
|
|
199
|
+
opts: { filter: RegExp },
|
|
200
|
+
run: (args: { path: string }) => { contents?: string; loader?: string; errors?: readonly { text: string }[] } | undefined,
|
|
201
|
+
) => void
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let checked: string | null | undefined
|
|
205
|
+
let against: string | undefined
|
|
206
|
+
|
|
207
|
+
function treeAt(from: string): string | undefined {
|
|
208
|
+
for (const start of [dirname(resolve(from)), resolve(cwd())]) {
|
|
209
|
+
let root = start
|
|
210
|
+
while (root !== dirname(root)) {
|
|
211
|
+
if (existsSync(join(root, '.thread', 'gate.json')) || existsSync(join(root, '_threads'))) return root
|
|
212
|
+
root = dirname(root)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return undefined
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function ungated(from: string): string | null {
|
|
219
|
+
const root = treeAt(from)
|
|
220
|
+
const at = root === undefined ? '' : join(root, '.thread', 'gate.json')
|
|
221
|
+
|
|
222
|
+
const stamp = at !== '' && existsSync(at) ? `${String(statSync(at).mtimeMs)}:${String(statSync(at).size)}` : `none:${from}`
|
|
223
|
+
if (checked !== undefined && against === stamp) return checked
|
|
224
|
+
against = stamp
|
|
225
|
+
checked = null
|
|
226
|
+
|
|
227
|
+
if (at === '' || !existsSync(at)) {
|
|
228
|
+
checked =
|
|
229
|
+
'this tree has no passing gate, so nothing in it may compile. Run `thread gate` — or the ' +
|
|
230
|
+
'`dev`, `build` and `start` scripts, which run it for you. A gate that has never run is not a ' +
|
|
231
|
+
'gate that passed, and the file you are compiling was never measured. Compiling ' +
|
|
232
|
+
`\`${from}\`, it looked for a gate receipt from \`${dirname(resolve(from))}\` and from ` +
|
|
233
|
+
`\`${resolve(cwd())}\` upwards${at === '' ? ', and neither reached a tree' : `, and found none at ${at}`}`
|
|
234
|
+
return checked
|
|
235
|
+
}
|
|
236
|
+
const held = JSON.parse(readFileSync(at, 'utf8')) as { of?: Record<string, string> }
|
|
237
|
+
for (const [named, was] of Object.entries(held.of ?? {})) {
|
|
238
|
+
const now = existsSync(join(dirname(dirname(at)), named))
|
|
239
|
+
? createHash('sha256').update(readFileSync(join(dirname(dirname(at)), named))).digest('hex').slice(0, 16)
|
|
240
|
+
: 'gone'
|
|
241
|
+
if (now === was) continue
|
|
242
|
+
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\``
|
|
243
|
+
return checked
|
|
244
|
+
}
|
|
245
|
+
return checked
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default function loader(this: { readonly resourcePath?: string } | void, source: string): string {
|
|
249
|
+
const at = typeof this?.resourcePath === 'string' ? this.resourcePath : ''
|
|
250
|
+
const skipped = at === '' ? null : ungated(at)
|
|
251
|
+
if (skipped !== null) throw new Error(`thread: ${skipped}.`)
|
|
252
|
+
const made = compiled(source, at)
|
|
253
|
+
|
|
254
|
+
if (made.because !== undefined) throw new Error(made.because)
|
|
255
|
+
return made.text ?? source
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const plugin: { readonly name: string; readonly setup: (build: Loading) => void } = {
|
|
259
|
+
name: 'thread-react',
|
|
260
|
+
setup(build) {
|
|
261
|
+
build.onLoad({ filter: OWNED }, (args) => {
|
|
262
|
+
const text = readFileSync(args.path, 'utf8')
|
|
263
|
+
const made = compiled(text, args.path)
|
|
264
|
+
if (made.because !== undefined) return { errors: [{ text: made.because }] }
|
|
265
|
+
return { contents: made.text ?? text, loader: 'tsx' }
|
|
266
|
+
})
|
|
267
|
+
},
|
|
268
|
+
}
|
|
@@ -14,7 +14,8 @@ import ts from 'typescript';
|
|
|
14
14
|
import { exit, invoked } from "@prestack/thread/core/invoked";
|
|
15
15
|
import { named as packageOf } from "@prestack/thread/core/scope";
|
|
16
16
|
import { findTree, inTree } from "@prestack/thread/core/tree";
|
|
17
|
-
import {
|
|
17
|
+
import { PERMITS_AT } from "@prestack/thread/operations/access";
|
|
18
|
+
import { COMPILED, compiledAt, nameOf as compositionOf } from "../compositions.js";
|
|
18
19
|
import { PRESENTATIONS } from "../door.js";
|
|
19
20
|
import { PAGE, entryAmong, nameOf, pageAt, presentedIn, routeFor } from "../screens.js";
|
|
20
21
|
import { keyProps } from "./i18n.js";
|
|
@@ -24,29 +25,33 @@ const DECLARED = `src/_generated/seams.${'ts'}`;
|
|
|
24
25
|
const CLIENT = `app/_generated/dispatching.${'ts'}`;
|
|
25
26
|
const DOOR = `app/_generated/door.${'tsx'}`;
|
|
26
27
|
const MAP = `app/_generated/map.${'ts'}`;
|
|
27
|
-
const PROVIDER = `${
|
|
28
|
-
const GRAMMAR = `${
|
|
28
|
+
const PROVIDER = `${COMPILED}/ui/provider.tsx`;
|
|
29
|
+
const GRAMMAR = `${COMPILED}/ui/islands/map.ts`;
|
|
29
30
|
const MOVES = 'navigateTo';
|
|
30
31
|
function door(knowing, wording, dispatching, moving, presenting) {
|
|
31
32
|
const opening = presenting.length > 0;
|
|
32
|
-
const props = `asking={ASKING}${knowing ? ' actor={actor}' : ''}${dispatching ? ' dispatch={dispatchTo}' : ''}` +
|
|
33
|
+
const props = `asking={ASKING}${knowing ? ' actor={actor} permits={gate}' : ''}${dispatching ? ' dispatch={dispatchTo}' : ''}` +
|
|
33
34
|
`${moving ? ' navigateTo={navigateTo}' : ''}${wording ? ' locale={SOURCE} wording={WORDING}' : ''}`;
|
|
34
35
|
return [
|
|
35
36
|
"'use client'",
|
|
36
37
|
'',
|
|
37
38
|
'/* GENERATED by thread — do not edit. It is derived from what this app declares. */',
|
|
38
39
|
'',
|
|
39
|
-
...(moving
|
|
40
|
-
? [
|
|
40
|
+
...(moving || knowing
|
|
41
|
+
? [
|
|
42
|
+
`import { useCallback, ${moving && opening ? 'useState, ' : ''}type ReactNode } from 'react'`,
|
|
43
|
+
...(moving ? ["import { useRouter } from 'next/navigation'"] : []),
|
|
44
|
+
]
|
|
41
45
|
: ["import type { ReactNode } from 'react'"]),
|
|
42
46
|
'',
|
|
43
|
-
`import { ThreadUi } from '${PROVIDER}'`,
|
|
47
|
+
`import { ThreadUi } from '${reaching(DOOR, PROVIDER)}'`,
|
|
44
48
|
'',
|
|
45
49
|
"import { ASKING } from './declared.ts'",
|
|
50
|
+
...(knowing ? [`import { permits } from '${reaching(DOOR, PERMITS_AT)}'`] : []),
|
|
46
51
|
...(dispatching ? ["import { dispatchTo } from './dispatching.ts'"] : []),
|
|
47
52
|
...(moving ? ["import { MAP } from './map.ts'"] : []),
|
|
48
53
|
...(wording ? ["import { SOURCE, WORDING } from './wording.ts'"] : []),
|
|
49
|
-
...(opening ? ['', ...presenting.map((one) => `import { ${one.name} } from '${reaching(DOOR, one.at)}'`)] : []),
|
|
54
|
+
...(opening ? ['', ...presenting.map((one) => `import { ${one.name} } from '${reaching(DOOR, compiledAt(one.at))}'`)] : []),
|
|
50
55
|
'',
|
|
51
56
|
'export function Door({',
|
|
52
57
|
...(knowing
|
|
@@ -59,6 +64,14 @@ function door(knowing, wording, dispatching, moving, presenting) {
|
|
|
59
64
|
: []),
|
|
60
65
|
' readonly children: ReactNode',
|
|
61
66
|
'}) {',
|
|
67
|
+
...(knowing
|
|
68
|
+
? [
|
|
69
|
+
' const gate = useCallback(',
|
|
70
|
+
' (action: string): boolean => (actor === undefined ? false : permits(actor, action, null).allow),',
|
|
71
|
+
' [actor],',
|
|
72
|
+
' )',
|
|
73
|
+
]
|
|
74
|
+
: []),
|
|
62
75
|
...(moving
|
|
63
76
|
? [
|
|
64
77
|
' const router = useRouter()',
|
|
@@ -115,6 +128,21 @@ const TYPED = /screen\(\s*'[^']*'\s*,\s*\{[^}]*route:\s*'([^']*)'/;
|
|
|
115
128
|
export function typedRoute(source) {
|
|
116
129
|
return TYPED.exec(source)?.[1];
|
|
117
130
|
}
|
|
131
|
+
const SHOWING = /screen\(\s*'[^']*'\s*,\s*\{[^}]*views:\s*\[([^\]]*)\]/;
|
|
132
|
+
export function viewsIn(source) {
|
|
133
|
+
const held = SHOWING.exec(source)?.[1];
|
|
134
|
+
if (held === undefined)
|
|
135
|
+
return [];
|
|
136
|
+
return [...held.matchAll(QUOTED)].map(([, single, double]) => single ?? double ?? '').filter((one) => one !== '');
|
|
137
|
+
}
|
|
138
|
+
const VIEW = /^vw_[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
|
|
139
|
+
const OPERATION = /operation:\s*"([^"]+)"/g;
|
|
140
|
+
function reachable(at) {
|
|
141
|
+
const held = join(at, CLIENT);
|
|
142
|
+
if (!existsSync(held))
|
|
143
|
+
return [];
|
|
144
|
+
return [...readFileSync(held, 'utf8').matchAll(OPERATION)].map(([, one]) => one ?? '');
|
|
145
|
+
}
|
|
118
146
|
function written(node) {
|
|
119
147
|
if (node === undefined)
|
|
120
148
|
return undefined;
|
|
@@ -243,7 +271,7 @@ function mapModule(map) {
|
|
|
243
271
|
return [
|
|
244
272
|
'/* GENERATED by thread — do not edit. It is derived from the screens, and from every navigateTo they and their compositions hold. */',
|
|
245
273
|
'',
|
|
246
|
-
`import type { MapIsland } from '${GRAMMAR}'`,
|
|
274
|
+
`import type { MapIsland } from '${reaching(MAP, GRAMMAR)}'`,
|
|
247
275
|
'',
|
|
248
276
|
'export const MAP: MapIsland = {',
|
|
249
277
|
' specVersion: 1,',
|
|
@@ -275,7 +303,7 @@ function reaching(file, at) {
|
|
|
275
303
|
return `${up}${at.replace(/\.tsx?$/, '')}`;
|
|
276
304
|
}
|
|
277
305
|
function page(name, at, route) {
|
|
278
|
-
const from = reaching(pageAt(route), at);
|
|
306
|
+
const from = reaching(pageAt(route), compiledAt(at));
|
|
279
307
|
return [
|
|
280
308
|
'/* GENERATED by thread — do not edit. It follows from the screen\'s name and its place among the pages. */',
|
|
281
309
|
'',
|
|
@@ -287,32 +315,47 @@ function page(name, at, route) {
|
|
|
287
315
|
'',
|
|
288
316
|
].join('\n');
|
|
289
317
|
}
|
|
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
|
-
}
|
|
318
|
+
function gate(typed) {
|
|
319
|
+
const said = typed ? ' as { of?: Record<string, string> }' : '';
|
|
299
320
|
return [
|
|
300
|
-
"import {
|
|
321
|
+
"import { createHash } from 'node:crypto'",
|
|
322
|
+
"import { existsSync, readFileSync, statSync } from 'node:fs'",
|
|
323
|
+
"import { join } from 'node:path'",
|
|
301
324
|
'',
|
|
302
|
-
"const
|
|
303
|
-
'if (
|
|
325
|
+
"const receipt = join(process.cwd(), '.thread', 'gate.json')",
|
|
326
|
+
'if (!existsSync(receipt)) {',
|
|
327
|
+
' throw new Error(',
|
|
328
|
+
" 'thread: this tree has no passing gate, so nothing in it may compile. Run `thread gate` — or the ' +",
|
|
329
|
+
" 'dev, build and start scripts, which run it for you. A gate that has never run is not a gate that passed.',",
|
|
330
|
+
' )',
|
|
331
|
+
'}',
|
|
332
|
+
"const passed = Symbol.for('thread.gate')",
|
|
333
|
+
"const stamp = String(statSync(receipt).mtimeMs) + ':' + String(statSync(receipt).size)",
|
|
334
|
+
'if (Reflect.get(globalThis, passed) !== stamp) {',
|
|
335
|
+
` const measured = JSON.parse(readFileSync(receipt, 'utf8'))${said}`,
|
|
336
|
+
' for (const [named, was] of Object.entries(measured.of ?? {})) {',
|
|
337
|
+
' const at = join(process.cwd(), named)',
|
|
338
|
+
" const now = existsSync(at) ? createHash('sha256').update(readFileSync(at)).digest('hex').slice(0, 16) : 'gone'",
|
|
339
|
+
' if (now === was) continue',
|
|
340
|
+
' throw new Error(',
|
|
341
|
+
" 'thread: ' + named + ' has changed since the gate last passed, so what this tree declares and what ' +",
|
|
342
|
+
" 'it was measured as no longer agree. Run `thread gate`',",
|
|
343
|
+
' )',
|
|
344
|
+
' }',
|
|
345
|
+
' Reflect.set(globalThis, passed, stamp)',
|
|
346
|
+
'}',
|
|
304
347
|
'',
|
|
305
348
|
];
|
|
306
349
|
}
|
|
307
|
-
function layout(wording, icons, source,
|
|
350
|
+
function layout(wording, icons, source, knowing, doored) {
|
|
308
351
|
return [
|
|
309
352
|
'/* GENERATED by thread — do not edit. It is derived from what the other generators produce. */',
|
|
310
353
|
'',
|
|
311
354
|
'/// <reference types="next/types/global" />',
|
|
312
355
|
'',
|
|
313
|
-
...
|
|
356
|
+
...gate(true),
|
|
314
357
|
"import type { ReactNode } from 'react'",
|
|
315
|
-
...(doored ? ["import { Door } from './_generated/door.tsx'"] : [`import { ThreadUi } from '${PROVIDER}'`]),
|
|
358
|
+
...(doored ? ["import { Door } from './_generated/door.tsx'"] : [`import { ThreadUi } from '${reaching(LAYOUT, PROVIDER)}'`]),
|
|
316
359
|
...(doored ? [] : ["import { ASKING } from './_generated/declared.ts'"]),
|
|
317
360
|
...(knowing
|
|
318
361
|
? ["import { headers } from 'next/headers'", `import { actor } from '../${DECLARED}'`]
|
|
@@ -365,14 +408,6 @@ function rootFor(at) {
|
|
|
365
408
|
const common = shared.join('/');
|
|
366
409
|
return relative(here, common) || '.';
|
|
367
410
|
}
|
|
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
411
|
const DRIVER = /export\s+const\s+driver\s*:[^=\n]*=\s*\[([^\]]*)\]/;
|
|
377
412
|
const QUOTED = /'([^']+)'|"([^"]+)"/g;
|
|
378
413
|
const RELATIVE = /(?:^|[\s;{(])import\s+(?!type\s)(?:[\w*{][^'"]*from\s+)?['"](\.[^'"]+)['"]/g;
|
|
@@ -414,45 +449,14 @@ function externalFor(at) {
|
|
|
414
449
|
}
|
|
415
450
|
return [...found].sort();
|
|
416
451
|
}
|
|
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) {
|
|
452
|
+
function config(root, external) {
|
|
427
453
|
const turbopack = [
|
|
428
454
|
...(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
455
|
];
|
|
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
456
|
return [
|
|
452
457
|
'/* GENERATED by thread — do not edit. It is derived from where the packages resolve. */',
|
|
453
458
|
'',
|
|
454
|
-
...(
|
|
455
|
-
...gate,
|
|
459
|
+
...gate(false),
|
|
456
460
|
'export default {',
|
|
457
461
|
...(turbopack.length === 0 ? [] : [' turbopack: {', ...turbopack, ' },']),
|
|
458
462
|
...(external.length === 0
|
|
@@ -495,6 +499,7 @@ async function run() {
|
|
|
495
499
|
const made = [];
|
|
496
500
|
const routes = new Map();
|
|
497
501
|
const screens = [];
|
|
502
|
+
const reaches = reachable(at);
|
|
498
503
|
for (const row of rows) {
|
|
499
504
|
const text = texts.get(row.id) ?? '';
|
|
500
505
|
const typed = typedRoute(text);
|
|
@@ -514,12 +519,27 @@ async function run() {
|
|
|
514
519
|
}
|
|
515
520
|
const route = shownAs === PAGE ? routeFor(row.id, row.id === entry) : undefined;
|
|
516
521
|
routes.set(name, route);
|
|
522
|
+
const shows = viewsIn(text);
|
|
523
|
+
for (const one of shows) {
|
|
524
|
+
if (!VIEW.test(one)) {
|
|
525
|
+
stderr.write(`routes: ${row.at} shows \`${one}\`, and that is not the name of a view. A view is ` +
|
|
526
|
+
'`vw_` and then lower case words joined by underscores. A screen names a declared read, ' +
|
|
527
|
+
'and never states what the read does.\n');
|
|
528
|
+
return 1;
|
|
529
|
+
}
|
|
530
|
+
if (!reaches.includes(one)) {
|
|
531
|
+
stderr.write(`routes: ${row.at} shows \`${one}\`, and this app reaches no operation by that name. It reaches ` +
|
|
532
|
+
`${reaches.length === 0 ? 'nothing yet — \`thread capabilities\` writes that list, and the settle runs it before this' : reaches.join(', ')}. Declare the view with ` +
|
|
533
|
+
'`thread create operation`, or name one of those.\n');
|
|
534
|
+
return 1;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
517
537
|
screens.push({
|
|
518
538
|
id: name,
|
|
519
539
|
nameKey: `screen.${row.id}`,
|
|
520
540
|
...(route === undefined ? {} : { path: route }),
|
|
521
541
|
entry: row.id === entry,
|
|
522
|
-
shows: [],
|
|
542
|
+
shows: [...shows],
|
|
523
543
|
});
|
|
524
544
|
if (route === undefined)
|
|
525
545
|
continue;
|
|
@@ -541,7 +561,7 @@ async function run() {
|
|
|
541
561
|
}
|
|
542
562
|
const map = { specVersion: 1, screens, links };
|
|
543
563
|
made.push({ path: MAP, text: mapModule(map) });
|
|
544
|
-
made.push({ path: CONFIG, text: config(rootFor(at),
|
|
564
|
+
made.push({ path: CONFIG, text: config(rootFor(at), externalFor(at)) });
|
|
545
565
|
const dispatching = existsSync(join(at, CLIENT));
|
|
546
566
|
const moving = rows.length > 0;
|
|
547
567
|
const doored = dispatching || moving;
|
|
@@ -552,7 +572,7 @@ async function run() {
|
|
|
552
572
|
made.push({ path: DOOR, text: door(existsSync(join(at, DECLARED)), existsSync(join(at, WORDING)), dispatching, moving, presenting) });
|
|
553
573
|
made.push({
|
|
554
574
|
path: LAYOUT,
|
|
555
|
-
text: layout(existsSync(join(at, WORDING)), existsSync(join(at, 'app/_generated/icons.ts')), source,
|
|
575
|
+
text: layout(existsSync(join(at, WORDING)), existsSync(join(at, 'app/_generated/icons.ts')), source, existsSync(join(at, DECLARED)), doored),
|
|
556
576
|
});
|
|
557
577
|
const paged = map.screens.filter((one) => one.path !== undefined).length;
|
|
558
578
|
const counted = `${String(map.screens.length)} screen(s), ${String(paged)} route(s), ${String(map.links.length)} link(s)`;
|