@nodegx/export 0.0.0-stage → 0.2.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/README.md +59 -2
- package/dist/analyze/appState.d.ts +296 -0
- package/dist/analyze/plan.d.ts +3757 -0
- package/dist/analyze/querySettings.d.ts +75 -0
- package/dist/analyze/reach.d.ts +41 -0
- package/dist/analyze/scriptUses.d.ts +29 -0
- package/dist/catalog.d.ts +129 -0
- package/dist/cli/args.d.ts +90 -0
- package/dist/cli/exitCodes.d.ts +125 -0
- package/dist/cli/run.d.ts +30 -0
- package/dist/cli.inputs.json +174 -0
- package/dist/cli.mjs +53746 -0
- package/dist/cli.mjs.map +7 -0
- package/dist/emit/animateLib.d.ts +43 -0
- package/dist/emit/component.d.ts +144 -0
- package/dist/emit/dateLib.d.ts +45 -0
- package/dist/emit/emitApp.d.ts +38 -0
- package/dist/emit/idLib.d.ts +68 -0
- package/dist/emit/kits.d.ts +85 -0
- package/dist/emit/mediaLib.d.ts +75 -0
- package/dist/emit/naming.d.ts +76 -0
- package/dist/emit/parseLib.d.ts +106 -0
- package/dist/emit/preflight.d.ts +174 -0
- package/dist/emit/readme.d.ts +51 -0
- package/dist/emit/report.d.ts +309 -0
- package/dist/emit/scaffold.d.ts +67 -0
- package/dist/emit/statesLib.d.ts +52 -0
- package/dist/emit/style.d.ts +165 -0
- package/dist/emit/utilLib.d.ts +78 -0
- package/dist/errorMessage.d.ts +28 -0
- package/dist/index.cjs +52848 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.mjs +52769 -0
- package/dist/index.mjs.map +7 -0
- package/dist/ir/types.d.ts +567 -0
- package/dist/ledger.cjs +971 -0
- package/dist/ledger.cjs.map +7 -0
- package/dist/ledger.d.ts +75 -0
- package/dist/ledger.mjs +948 -0
- package/dist/ledger.mjs.map +7 -0
- package/dist/node-catalog.json +65333 -0
- package/dist/parse/kitSource.d.ts +135 -0
- package/dist/parse/parseModules.d.ts +42 -0
- package/dist/parse/parseProject.d.ts +13 -0
- package/dist/scriptFiles.d.ts +30 -0
- package/dist/structurePorts.d.ts +70 -0
- package/dist/write/writeExport.d.ts +67 -0
- package/package.json +53 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/lib/animate.ts` — the animation pair's frame engine, emitted into the app (EXP-011 §49).
|
|
3
|
+
*
|
|
4
|
+
* The same shape as {@link ./timerLib.ts}: constant text, shipped only where a component calls into
|
|
5
|
+
* it, a fifth module rather than a section of `timer.ts` because an app with a countdown should
|
|
6
|
+
* not ship a tween engine. `states.ts` ({@link ./statesLib.ts}) imports this one.
|
|
7
|
+
*
|
|
8
|
+
* It is a transcription of four things the interpreter runs:
|
|
9
|
+
*
|
|
10
|
+
* - `noodl-runtime/src/timerscheduler.ts` — one timer with repeat count 1: `start()` queues it, it
|
|
11
|
+
* **joins at the end of the next frame** with `_start = frameTime + delay` (a zero delay plays
|
|
12
|
+
* `onStart` + `onRunning(0)` in that frame), each later frame at or after `_start` computes
|
|
13
|
+
* `t = (now − _start) / duration` (`1` when the duration is 0), calls `onRunning(min(t, 1))`, and
|
|
14
|
+
* at `t ≥ 1` calls `onFinish`. `stop()` fires nothing; `start()` on a running timer stops it first.
|
|
15
|
+
* - `noodl-viewer-react/src/easecurves.ts` — start/end baked into every curve, so callers pass the
|
|
16
|
+
* endpoints. The four names the picker offers, with `easeIn`/`easeOut`/`easeInOut` aliased onto
|
|
17
|
+
* the cubic variants exactly as that file does.
|
|
18
|
+
* - `bezier-easing` 1.1.1 — the solver behind a States transition's `curve: [x1, y1, x2, y2]`,
|
|
19
|
+
* transcribed with its constants (four Newton iterations, the eleven-sample table, the
|
|
20
|
+
* subdivision fallback) so the two worlds put the same value on the same frame.
|
|
21
|
+
* - `noodl-viewer-react/src/nodes/std-library/animate-to-value.ts` — the first target is adopted
|
|
22
|
+
* outright, a target equal to the current end is ignored, anything else tweens from wherever the
|
|
23
|
+
* value is; booleans are 1/0 and a NaN is ignored; `At Target Value` fires on finish only.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ **One liberty, recorded.** The frame clock is `requestAnimationFrame`'s timestamp here and the
|
|
26
|
+
* viewer's frame callback there; both are monotonic milliseconds advanced by the host once per
|
|
27
|
+
* frame, and nothing an author can wire observes which.
|
|
28
|
+
*/
|
|
29
|
+
/** Where the module lands in the exported app. */
|
|
30
|
+
export declare const ANIMATE_LIB_PATH = "src/lib/animate.ts";
|
|
31
|
+
/** The exported helpers a component imports. Sorted — the import list is sorted too. */
|
|
32
|
+
export declare const ANIMATE_HELPERS: readonly ["useAnimatedValue"];
|
|
33
|
+
/** The easing names the node's enum offers — `easecurves.ts`'s aliases, in the picker's order. */
|
|
34
|
+
export declare const ANIMATE_EASE_NAMES: readonly ["easeOut", "easeIn", "linear", "easeInOut"];
|
|
35
|
+
export type AnimateEaseName = (typeof ANIMATE_EASE_NAMES)[number];
|
|
36
|
+
/**
|
|
37
|
+
* The module's source.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ A plain string rather than a template literal, for `dateLib.ts`'s stated reason: the emitted
|
|
40
|
+
* body needs no interpolation, and keeping it out of an interpolation context means a future edit
|
|
41
|
+
* cannot accidentally interpolate the generator's own scope into the exported app.
|
|
42
|
+
*/
|
|
43
|
+
export declare function animateLibSource(): string;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The visual-node generator (EXP-002 step 4): one component file + one CSS module per component,
|
|
3
|
+
* from a ComponentPlan. The target shapes are hand-written in EXP-002-TARGET-OUTPUT.md §1–§2 and
|
|
4
|
+
* the golden tests hold this emitter to them:
|
|
5
|
+
*
|
|
6
|
+
* - Component Inputs → a typed optional-props interface; wires from it become JSX interpolations
|
|
7
|
+
* (one input feeding two sinks is two interpolations, not a variable).
|
|
8
|
+
* - One CSS-module class per visual node (style.ts owns the split); identical style sets merge
|
|
9
|
+
* and naming.ts owns every name.
|
|
10
|
+
* - A For Each whose effective mapping is the static identity `map({...})` literal becomes
|
|
11
|
+
* `items.map()` with per-prop spreading and `key={item.id}`.
|
|
12
|
+
* - A DbCollection2 consumed by a repeater becomes plain `useState` + `useEffect` over a typed
|
|
13
|
+
* api stub — no library, no magic.
|
|
14
|
+
* - `RouterNavigate` resolves at generation time to `navigate('/url')` on the wired element.
|
|
15
|
+
* - Unwired inputs stay native and uncontrolled; a control earns state only when a wire demands
|
|
16
|
+
* it. Exporting the app's behaviour includes exporting its gaps.
|
|
17
|
+
*/
|
|
18
|
+
import { CatalogIndex } from '../catalog';
|
|
19
|
+
import { ComponentPlan, ProjectPlan } from '../analyze/plan';
|
|
20
|
+
import { ExportIR, NodeIR } from '../ir/types';
|
|
21
|
+
import { KitBinding } from './kits';
|
|
22
|
+
import { MediaHelper } from './mediaLib';
|
|
23
|
+
/**
|
|
24
|
+
* The old Look-preset markers (P100-UPG001-VARIANT). No runtime, viewer or exporter reads them (UPG-001 §3.4), so a
|
|
25
|
+
* node that still carries one has lost nothing, and the report must not say it was dropped. The editor's own list is
|
|
26
|
+
* `PRESET_MARKERS` in `noodl-editor/…/models/Looks/looks.ts`. This package may not import the editor (HLS-001 AC2),
|
|
27
|
+
* so `tests/p100-upg001-variant.test.ts` holds the two copies equal.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PRESET_MARKERS: readonly ["_variant", "_size"];
|
|
30
|
+
/**
|
|
31
|
+
* EXP-015. The element an author asked a node to render as, validated, with the sentence to report
|
|
32
|
+
* when the answer is "not that one".
|
|
33
|
+
*
|
|
34
|
+
* The port is real, shipped and documented as mattering — `Text.tsx` renders `props.as || 'div'`
|
|
35
|
+
* and `Group.tsx` destructures `const { as = 'div' }` — and until this function existed the
|
|
36
|
+
* exporter never read it. One landing-page template set it 61 times and the export lost all 61,
|
|
37
|
+
* shipping a site with no heading of any level and no landmark of any kind.
|
|
38
|
+
*
|
|
39
|
+
* 🔴 **Validated against the catalog's own enum for this node type, never a list written here.**
|
|
40
|
+
* `Text` offers `div h1…h6 p span`; `Group` offers `div section article aside nav header footer
|
|
41
|
+
* main span`; the two lists differ and both are the editor's own dropdown. A hand-written mirror
|
|
42
|
+
* drifts the moment somebody adds `<figure>` to one of them — `a-second-copy-of-a-palette-drifts-
|
|
43
|
+
* silently` — so the catalog artifact is the authority and this function only reads it.
|
|
44
|
+
*
|
|
45
|
+
* Three ways to be told no — in this order, each reported rather than silently ignored, because a
|
|
46
|
+
* silently ignored authored value is the defect this exists to fix:
|
|
47
|
+
*
|
|
48
|
+
* 1. **The value is a void element**, which cannot take children. Checked FIRST and without
|
|
49
|
+
* consulting the catalog, so that it is a real outer gate rather than dead code behind the enum
|
|
50
|
+
* test: neither shipped enum contains one today, and the whole point of the guard is the day one
|
|
51
|
+
* does. Its absence is `<img className={…}>Sign up</img>` in somebody's repo.
|
|
52
|
+
* 2. **The catalog does not describe an `as` port on this type at all.** Only `Text` and `Group`
|
|
53
|
+
* carry one, so this is a parameter written by hand or by a tool onto a node whose port set has
|
|
54
|
+
* no such port — the export has no authority to invent an element for it.
|
|
55
|
+
* 3. **The value is outside the port's enum.** The editor cannot produce one; a hand-authored
|
|
56
|
+
* project or a future catalog change can.
|
|
57
|
+
*/
|
|
58
|
+
export declare function authoredTag(node: NodeIR, fallback: string, catalog: CatalogIndex): {
|
|
59
|
+
tag: string;
|
|
60
|
+
refusal?: {
|
|
61
|
+
nodeId: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export interface EmittedComponent {
|
|
66
|
+
/** { "src/pages/Landing.tsx": …, "src/pages/Landing.module.css": … } */
|
|
67
|
+
files: Record<string, string>;
|
|
68
|
+
notes: string[];
|
|
69
|
+
/**
|
|
70
|
+
* `src/lib/date.ts` helpers this component imports (EXP-011 Tier 1.3), or an empty set.
|
|
71
|
+
*
|
|
72
|
+
* 🔴 Reported from **here** rather than derived from the plan, and the difference is the
|
|
73
|
+
* dead-module trap one construct over: `resolveExpr` runs speculatively, so a plan-side flag
|
|
74
|
+
* would be set by a date read a later pass then dropped, and the app would ship a `lib/date.ts`
|
|
75
|
+
* no line imports. This set is filled by the same walkers that write the import line, so the
|
|
76
|
+
* module exists exactly when something names it.
|
|
77
|
+
*/
|
|
78
|
+
dateHelpers: Set<string>;
|
|
79
|
+
/** The same, for `src/lib/util.ts` (EXP-011 Tier 2.7) — same rationale, same filling walkers. */
|
|
80
|
+
utilHelpers: Set<string>;
|
|
81
|
+
/** EXP-011 §37 — which of `src/lib/id.ts`'s helpers this component calls. */
|
|
82
|
+
idHelpers: Set<string>;
|
|
83
|
+
/** `src/lib/timer.ts` verbs this component calls (EXP-011 §39). */
|
|
84
|
+
timerHelpers: Set<string>;
|
|
85
|
+
/** EXP-011 §86. Whether this component runs a Function whose signals have chains (`src/lib/functionRun.ts`). */
|
|
86
|
+
functionRunLib: boolean;
|
|
87
|
+
/** EXP-020. `src/lib/noodl.ts`, earned by a printed Function that reads a backend member of the Noodl API. */
|
|
88
|
+
noodlLib: boolean;
|
|
89
|
+
/** EXP-020 slice 2: `src/lib/componentScope.ts`, the `Component` a Function's body reads. */
|
|
90
|
+
componentScopeLib: boolean;
|
|
91
|
+
/** EXP-011 §49. Whether this component imports `src/lib/animate.ts` / `src/lib/states.ts`. */
|
|
92
|
+
animateLib: boolean;
|
|
93
|
+
statesLib: boolean;
|
|
94
|
+
/** EXP-011 §52. Whether this component imports `src/lib/script.ts`. */
|
|
95
|
+
scriptLib: boolean;
|
|
96
|
+
/** P109 ISL-005. Whether this component imports the script-file registry (`src/lib/projectScriptFiles.ts`). */
|
|
97
|
+
projectScripts: boolean;
|
|
98
|
+
/** EXP-011 §53. `src/lib/runTasks.ts` is owed when any component keeps a Run Tasks node. */
|
|
99
|
+
runTasksLib: boolean;
|
|
100
|
+
/** EXP-011 §54. `src/lib/errors.ts` is owed when this component keeps a boundary or raises on the channel. */
|
|
101
|
+
errorsLib: boolean;
|
|
102
|
+
/** EXP-011 §56. `src/lib/filterRecords.ts` is owed when this component prints a Filter Records read. */
|
|
103
|
+
recordFilterLib: boolean;
|
|
104
|
+
/** EXP-011 §58. `src/lib/streaming.ts` is owed when this component keeps a streaming node. */
|
|
105
|
+
streamingLib: boolean;
|
|
106
|
+
/** EXP-011 §64. `src/lib/sse.ts` is owed when this component keeps a Server-Sent Events node. */
|
|
107
|
+
sseLib: boolean;
|
|
108
|
+
/** EXP-011 §65. `src/lib/websocket.ts` is owed when this component keeps a WebSocket node. */
|
|
109
|
+
websocketLib: boolean;
|
|
110
|
+
/** EXP-011 §66. `src/lib/realtime.ts` is owed when this component keeps a Subscribe To Changes node. */
|
|
111
|
+
realtimeLib: boolean;
|
|
112
|
+
/** GAM-013. `src/lib/repeat.ts` is owed when this component keeps a Repeat node. */
|
|
113
|
+
repeatLib: boolean;
|
|
114
|
+
/** EXP-011 §77. `src/lib/actions.ts` is owed when this component keeps an Action Dispatcher or an Action Handler. */
|
|
115
|
+
actionsLib: boolean;
|
|
116
|
+
/** EXP-011 §78. `src/lib/history.ts` is owed when this component keeps a State History, an Undo / Redo, a State Snapshot or an Optimistic Update. */
|
|
117
|
+
historyLib: boolean;
|
|
118
|
+
/** EXP-011 §79. `src/lib/signIn.ts` is owed when this component keeps a Sign In With. */
|
|
119
|
+
signInLib: boolean;
|
|
120
|
+
/** EXP-011 §59. `src/lib/crypto.ts` verbs this component calls; `src/lib/screen.ts` is owed when a viewport hook prints. */
|
|
121
|
+
cryptoHelpers: Set<string>;
|
|
122
|
+
screenLib: boolean;
|
|
123
|
+
/** EXP-011 §60. `src/lib/componentObject.ts` is owed when a record hook, a parent hook or a provider printed. */
|
|
124
|
+
componentObjectLib: boolean;
|
|
125
|
+
/** EXP-011 §63. Whether this component imports `src/lib/drag.ts`. */
|
|
126
|
+
dragLib: boolean;
|
|
127
|
+
/** EXP-011 §61. `src/lib/pageStack.ts` is owed when this component renders a stack, pushes, pops, or is pushed. */
|
|
128
|
+
pageStackLib: boolean;
|
|
129
|
+
/** HLT-014 — a popup slot rendered, so `src/lib/popupDialog.ts` is owed. */
|
|
130
|
+
popupDialogLib: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* EXP-014 §14.5. Which of `src/lib/media.ts`'s helpers this component prints a WIRED src / srcSet /
|
|
133
|
+
* poster through. Filled by `contentAttrs` at the point the attribute is printed — the same
|
|
134
|
+
* dead-module rule as `dateHelpers`: the module ships exactly when a line names it.
|
|
135
|
+
*/
|
|
136
|
+
mediaHelpers: Set<MediaHelper>;
|
|
137
|
+
}
|
|
138
|
+
export declare function emitComponent(plan: ComponentPlan, project: ProjectPlan, ir: ExportIR, catalog: CatalogIndex,
|
|
139
|
+
/**
|
|
140
|
+
* EXP-010. Node type → the generated wrapper the pages render it through. Empty when the project
|
|
141
|
+
* has no kit nodes; a type that is *missing* from a non-empty map is a node whose kit failed to
|
|
142
|
+
* load, and it emits a named marker rather than nothing.
|
|
143
|
+
*/
|
|
144
|
+
kitBindings?: Map<string, KitBinding>): EmittedComponent | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/lib/date.ts` — the date family's arithmetic, emitted into the app (EXP-011 Tier 1.3).
|
|
3
|
+
*
|
|
4
|
+
* Unlike `src/api/http.ts`, which is generated per node from a configuration, this module is the
|
|
5
|
+
* **same text in every project**: the five pure date nodes are one function each, and what varies
|
|
6
|
+
* between two projects using them is only the arguments at the call site. So it is a constant
|
|
7
|
+
* here rather than a builder, and it ships only where something calls into it.
|
|
8
|
+
*
|
|
9
|
+
* It is a transcription of two runtime files, and the transcription is the whole job:
|
|
10
|
+
*
|
|
11
|
+
* - `noodl-runtime/src/nodes/std-library/date/datemath.ts` — `toDate`, `addToDate`,
|
|
12
|
+
* `differenceBetween`, `truncateTo`, `isoWeek`. The clamping rule and the whole-calendar-step
|
|
13
|
+
* rule live there and are reproduced exactly, comments included, because both are answers the
|
|
14
|
+
* author has already seen in the interpreter.
|
|
15
|
+
* - `noodl-runtime/src/nodes/std-library/datetostring.ts` — `_format` and `fieldsInZone`.
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **`Date To String` reads its input through `toDate`, like the other four, since P107-D14.**
|
|
18
|
+
* It predates `datemath.ts` and used to read a millisecond **number** unconverted, so `getDate()`
|
|
19
|
+
* threw and the catch blanked it, while the Timezone branch rendered it. ISL-032 (`844f8569b`,
|
|
20
|
+
* ruled "One rule, like the other date nodes") moved the runtime to `toDate`. This module kept the
|
|
21
|
+
* old read for eight days, unseen, because the spec that grades it had been given a `require` stub
|
|
22
|
+
* that blanked the runtime's side too (P18-S102-DATEHARNESS).
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ **These functions are pure; the nodes are not.** A node holds its last answer, and when its
|
|
25
|
+
* Date input goes from a value to *unset* the interpreter abstains (`_recompute` returns early on
|
|
26
|
+
* `inputSupplied === false`) and keeps the previous answer, where this returns `undefined`. The
|
|
27
|
+
* divergence is one transition wide, it only exists for a wired input that can deliver empty, and
|
|
28
|
+
* the planner reports it where the wire can actually do that. Note that the interpreter is
|
|
29
|
+
* already inconsistent with itself here: an *invalid* date clears the answer, an *absent* one
|
|
30
|
+
* does not — and the pure form matches the invalid-date branch.
|
|
31
|
+
*/
|
|
32
|
+
/** Where the module lands in the exported app. */
|
|
33
|
+
export declare const DATE_LIB_PATH = "src/lib/date.ts";
|
|
34
|
+
/** The exported helpers, one per translated date node. Sorted — the import list is sorted too. */
|
|
35
|
+
export declare const DATE_HELPERS: readonly ["dateAdd", "dateCompare", "dateDifference", "datePart", "dateToString"];
|
|
36
|
+
export type DateHelper = (typeof DATE_HELPERS)[number];
|
|
37
|
+
/**
|
|
38
|
+
* The module's source.
|
|
39
|
+
*
|
|
40
|
+
* ⚠️ Written as a plain string rather than a template literal: the emitted body contains `${`
|
|
41
|
+
* nowhere by design (the runtime's one template literal is a `throw` this transcription does not
|
|
42
|
+
* reproduce — see `addToDate` below), and keeping it out of an interpolation context means a
|
|
43
|
+
* future edit cannot accidentally interpolate the generator's own scope into the exported app.
|
|
44
|
+
*/
|
|
45
|
+
export declare function dateLibSource(): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whole-app emission: the scaffold, then the visual generator's component/page files replacing
|
|
3
|
+
* the scaffold's placeholders, then the app-state modules (stores/events — step 5) and the
|
|
4
|
+
* typed api stubs the pages consume. The dependency list stays computed from the output
|
|
5
|
+
* (TARGET-OUTPUT §3): @nodegx/core joins package.json exactly when a generated file imports it.
|
|
6
|
+
*/
|
|
7
|
+
import { Catalog } from '../catalog';
|
|
8
|
+
import { ExportIR } from '../ir/types';
|
|
9
|
+
import { EmittedCopy } from './kits';
|
|
10
|
+
import { ExportReportData } from './report';
|
|
11
|
+
export interface EmittedApp {
|
|
12
|
+
/** Path → content, sorted by path (D-rules). */
|
|
13
|
+
files: Record<string, string>;
|
|
14
|
+
/**
|
|
15
|
+
* Files that travel byte-for-byte rather than being generated (EXP-010 AC5) — every
|
|
16
|
+
* `noodl_modules` file, sorted by destination.
|
|
17
|
+
*
|
|
18
|
+
* 🔴 **A separate channel because `files` is `Record<string, string>` and a font is not a
|
|
19
|
+
* string.** `noodl_modules/inter/` ships four `.ttf` files and `lucide-icons/` a `.woff2`;
|
|
20
|
+
* reading one into a UTF-8 string to put it in `files` would corrupt it silently, and the
|
|
21
|
+
* failure would render as blank glyphs rather than as an error. Emission stays pure — this is a
|
|
22
|
+
* list of paths, not a filesystem operation — and the caller that writes `files` to disk copies
|
|
23
|
+
* these alongside.
|
|
24
|
+
*/
|
|
25
|
+
copies: EmittedCopy[];
|
|
26
|
+
/** EXP-004's feed: everything analysis or emission dropped or deferred, per component. */
|
|
27
|
+
notes: string[];
|
|
28
|
+
/**
|
|
29
|
+
* The same facts, grouped by the scope the pushing code already knew (EXP-004).
|
|
30
|
+
*
|
|
31
|
+
* 🔴 **Built beside `notes`, never derived from it.** `notes` is a flat list of sentences and
|
|
32
|
+
* three dozen suites assert on its wording; recovering "which component is this about" by
|
|
33
|
+
* reading those sentences back would make every reword a re-grouping. Both channels are filled
|
|
34
|
+
* at the same push site, where `plan` is in hand and scope is a fact rather than a guess.
|
|
35
|
+
*/
|
|
36
|
+
report: ExportReportData;
|
|
37
|
+
}
|
|
38
|
+
export declare function emitApp(ir: ExportIR, catalog: Catalog): EmittedApp;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/lib/id.ts` — the two id generators, emitted into the app (EXP-011 Tier 2.7).
|
|
3
|
+
*
|
|
4
|
+
* The same shape as {@link ./dateLib.ts} and {@link ./utilLib.ts}: constant text, shipped only
|
|
5
|
+
* where something calls into it. A **separate** module rather than a section of `util.ts`, on
|
|
6
|
+
* that file's own stated rule — a project that formats a string should not ship a CSPRNG.
|
|
7
|
+
*
|
|
8
|
+
* It is a transcription of three runtime files, and the two nodes it serves are **not** two
|
|
9
|
+
* spellings of one thing. `uuid.ts`'s own header says so, and the export has to keep them apart:
|
|
10
|
+
*
|
|
11
|
+
* | | `Unique Id` | `UUID` |
|
|
12
|
+
* |---|---|---|
|
|
13
|
+
* | shape | 10 chars, alphanumeric | 36 chars, RFC 4122 v4 |
|
|
14
|
+
* | source | `Math.random()` (`model.ts` `_randomString`) | `crypto.randomUUID` / `crypto.getRandomValues` |
|
|
15
|
+
* | can it fail | **no** — and the node has no Failure port | **yes**, and it has `failure` and `Error` |
|
|
16
|
+
*
|
|
17
|
+
* - `noodl-runtime/src/model.ts` — `Model.guid()` is `_randomString(10)`, and the character set
|
|
18
|
+
* and the `(1 + Math.random()) * 0x10000` index are transcribed rather than modernised. An
|
|
19
|
+
* `id.length === 10` assertion would pass on a rewrite that drew from a different alphabet, and
|
|
20
|
+
* these ids end up in urls and as React keys in apps that already shipped.
|
|
21
|
+
* - `noodl-runtime/src/nodes/std-library/crypto/encoding.ts` — `randomUuid`, its `randomBytes`
|
|
22
|
+
* dependency and `bytesToHex`. 🔴 **The `getRandomValues` throw is transcribed verbatim,
|
|
23
|
+
* message included**, because it is the *only* thing `UUID`'s Failure arm can ever report and
|
|
24
|
+
* the emitted app's Error output prints it.
|
|
25
|
+
* - `noodl-runtime/src/nodes/std-library/crypto/uuid.ts` — the node's own `try`/`catch`, which is
|
|
26
|
+
* what {@link tryRandomUuid} and `initialUuid` are. The node never lets the throw escape.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ **`randomBytes`'s 65536-byte chunk loop is deliberately not transcribed.** It exists so a
|
|
29
|
+
* large request does not exceed `getRandomValues`'s per-call quota; this call asks for **16**
|
|
30
|
+
* bytes, one loop iteration, and reproducing the loop would be transcribing a constraint that
|
|
31
|
+
* cannot bind here. Everything the loop *decides* for 16 bytes is what the single call does.
|
|
32
|
+
*
|
|
33
|
+
* 🔴 **`tryRandomUuid` returns a discriminated union rather than `{ uuid?, error? }`.** The
|
|
34
|
+
* emitted action branches on it and then reads `.uuid` in the success arm; an optional-property
|
|
35
|
+
* pair does not narrow, so that read would be `string | undefined` and every sink downstream
|
|
36
|
+
* would have to guard a value that is always present. The union is what makes the emitted app
|
|
37
|
+
* typecheck without a `!`.
|
|
38
|
+
*/
|
|
39
|
+
/** Where the module lands in the exported app. */
|
|
40
|
+
export declare const ID_LIB_PATH = "src/lib/id.ts";
|
|
41
|
+
/**
|
|
42
|
+
* The exported helpers. Sorted — the import list is sorted too.
|
|
43
|
+
*
|
|
44
|
+
* `randomUuid` is deliberately **not** exported from the emitted module: it throws, and the two
|
|
45
|
+
* nodes that use it both catch. Exporting it would offer the exported app a door the interpreter
|
|
46
|
+
* does not have.
|
|
47
|
+
*/
|
|
48
|
+
export declare const ID_HELPERS: readonly ["initialUuid", "randomId", "tryRandomUuid"];
|
|
49
|
+
export type IdHelper = (typeof ID_HELPERS)[number];
|
|
50
|
+
/**
|
|
51
|
+
* Which of the two generators a node calls, and what each earns.
|
|
52
|
+
*
|
|
53
|
+
* 🔴 Must agree with `ID_NODES` in `plan.ts`, which reads this table rather than restating it.
|
|
54
|
+
* The `boot` helper seeds the state row and the `call` helper is what the `New` action invokes;
|
|
55
|
+
* they differ for `UUID` because `initialize` swallows the failure and `_generate` reports it.
|
|
56
|
+
*/
|
|
57
|
+
export declare const ID_HELPERS_BY_FN: Record<'randomId' | 'randomUuid', {
|
|
58
|
+
boot: IdHelper;
|
|
59
|
+
call: IdHelper;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* The module's source.
|
|
63
|
+
*
|
|
64
|
+
* ⚠️ A plain string array rather than a template literal, for `dateLib.ts`'s stated reason: the
|
|
65
|
+
* emitted body needs no interpolation, and keeping it out of an interpolation context means a
|
|
66
|
+
* future edit cannot accidentally interpolate the generator's own scope into the exported app.
|
|
67
|
+
*/
|
|
68
|
+
export declare function idLibSource(): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom node kits in the exported app — EXP-010's emit half, Route B.
|
|
3
|
+
*
|
|
4
|
+
* ## What this emits, and the honest cost
|
|
5
|
+
*
|
|
6
|
+
* Route B ships the kit's own `index.js` verbatim and runs it behind a small shim implementing the
|
|
7
|
+
* four things a kit node's code reaches for: `props`, `_internal`, `flagOutputDirty` and
|
|
8
|
+
* `sendSignalOnOutput`. That is **correct by construction** — the exported app runs the same code
|
|
9
|
+
* the editor and the deploy run, so a kit node cannot render differently in the three places — and
|
|
10
|
+
* it is the only route that works for every existing kit on the day it lands.
|
|
11
|
+
*
|
|
12
|
+
* 🔴 **It also puts a slice of the Noodl node model into an export that has spent thirty sessions
|
|
13
|
+
* keeping one out** (`EXP-002-TARGET-OUTPUT.md`'s headline: zero `@nodegx/core` imports). That
|
|
14
|
+
* cost is real, it is confined to `src/kits/runtime.tsx`, and it is stated in the emitted file
|
|
15
|
+
* itself rather than left for a reader to discover. Route A — a generated wrapper per node kind
|
|
16
|
+
* with no shim at all — is the phase's exit criterion and needs a kit-format convention phase 69
|
|
17
|
+
* owns (EXP-010 §6). This is the floor it will be diffed against.
|
|
18
|
+
*
|
|
19
|
+
* ## What a developer reads
|
|
20
|
+
*
|
|
21
|
+
* The call site is ordinary React: `<CashflowPill day={12} onDropped={…} />`. The generated
|
|
22
|
+
* wrapper per node type is a dozen typed lines. Only `runtime.tsx` knows what a Noodl node is.
|
|
23
|
+
*
|
|
24
|
+
* ## The file layout, and why it splits
|
|
25
|
+
*
|
|
26
|
+
* - `src/kits/runtime.tsx` — the shim, the registry, and `window.React` / `window.Noodl`.
|
|
27
|
+
* - `src/kits/modules/<dir>/<main>` — each kit's script, byte-identical, **bundled** so it is
|
|
28
|
+
* subject to the app's own build rather than served loose.
|
|
29
|
+
* - `public/noodl_modules/<dir>/…` — everything else in every module folder, **verbatim**, because
|
|
30
|
+
* a stylesheet's `url()` references are invisible to the manifest and only survive a copy that
|
|
31
|
+
* changes nothing (AC5).
|
|
32
|
+
*
|
|
33
|
+
* A kit's script is the one file that goes to `src/` rather than `public/`: it has to be a module
|
|
34
|
+
* the bundle imports so its evaluation order relative to the globals is guaranteed. Every other
|
|
35
|
+
* file has exactly one home.
|
|
36
|
+
*/
|
|
37
|
+
import { ExportIR, KitNodeIR, KitPortIR } from '../ir/types';
|
|
38
|
+
/** A file to copy byte-for-byte, rather than generate. Both paths are relative to their root. */
|
|
39
|
+
export interface EmittedCopy {
|
|
40
|
+
/** Project-relative source, e.g. `noodl_modules/inter/Inter-Bold.ttf`. */
|
|
41
|
+
from: string;
|
|
42
|
+
/** Output-relative destination, e.g. `public/noodl_modules/inter/Inter-Bold.ttf`. */
|
|
43
|
+
to: string;
|
|
44
|
+
}
|
|
45
|
+
/** One custom node type as the emitted app sees it. */
|
|
46
|
+
export interface KitBinding {
|
|
47
|
+
/** The graph's node type — `nodegx.cashflow.Pill`. */
|
|
48
|
+
type: string;
|
|
49
|
+
/** The React symbol the pages import — `Pill`, or `CashflowPill` when `Pill` collides. */
|
|
50
|
+
symbol: string;
|
|
51
|
+
/** `src/kits/CashflowKit` — the module pages import `symbol` from. */
|
|
52
|
+
modulePath: string;
|
|
53
|
+
def: KitNodeIR;
|
|
54
|
+
/** Input port name → the wrapper's prop name. Identity unless the name is not an identifier. */
|
|
55
|
+
propOf: Map<string, string>;
|
|
56
|
+
/** Signal output port name → the wrapper's callback prop (`dropped` → `onDropped`). */
|
|
57
|
+
signalPropOf: Map<string, string>;
|
|
58
|
+
/** Value output port name → the wrapper's callback prop (`liveDay` → `onLiveDayChanged`). */
|
|
59
|
+
valuePropOf: Map<string, string>;
|
|
60
|
+
}
|
|
61
|
+
export interface EmittedKits {
|
|
62
|
+
files: Record<string, string>;
|
|
63
|
+
copies: EmittedCopy[];
|
|
64
|
+
/** Node type → how to render it. Empty when the project has no usable kit node. */
|
|
65
|
+
bindings: Map<string, KitBinding>;
|
|
66
|
+
notes: string[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The kit bridge for a project.
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ **Every module contributes copies; only kits that loaded contribute code.** A kit that threw
|
|
72
|
+
* still has its folder shipped and its failure named — deleting the folder because the export
|
|
73
|
+
* could not read it would take an author's own work out of their repo on the strength of this
|
|
74
|
+
* pipeline's opinion.
|
|
75
|
+
*/
|
|
76
|
+
export declare function emitKits(ir: ExportIR, usedTypes: Set<string>): EmittedKits;
|
|
77
|
+
/**
|
|
78
|
+
* A kit port's declared type as TypeScript.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ **`unknown` where the kit says nothing**, never `any`: a port with no declared type is one
|
|
81
|
+
* the exported app's typecheck genuinely cannot help with, and `any` would quietly claim it can.
|
|
82
|
+
* A units-typed port is `number | string` because the runtime turns `24` into `"24px"` before the
|
|
83
|
+
* component sees it, and the graph may equally set `var(--space-4)`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function tsTypeOf(port: KitPortIR): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/lib/media.ts` — a media URL port as the browser must be handed it, emitted into the app
|
|
3
|
+
* (EXP-014 §14.5, the `<img src>` residual).
|
|
4
|
+
*
|
|
5
|
+
* A transcription of two runtime functions, kept as two functions so each can be cited:
|
|
6
|
+
*
|
|
7
|
+
* - `absoluteUrl` is `noodl-runtime/src/utils.ts` `getAbsoluteUrl`: a LOCAL path takes the base
|
|
8
|
+
* URL in front; a root-absolute path, a `scheme://` URL and a `data:` URI pass through, and so
|
|
9
|
+
* does an empty string. The runtime's base is `Noodl.baseUrl || '/'`; the exported app is served
|
|
10
|
+
* from its root, so the base is `/` here. `String(value)` first, as there — a Cloud File is an
|
|
11
|
+
* object with a custom `toString()`, and the cast is what makes one usable as a source.
|
|
12
|
+
* - `mediaSrc` is `noodl-viewer-react/src/nodes/visual/media-source.ts` `resolveMediaSource`, the
|
|
13
|
+
* gate the Image and Video nodes put in FRONT of `getAbsoluteUrl`: `null`, `undefined` and `''`
|
|
14
|
+
* answer `undefined`, which React renders as no attribute at all. `src=""` resolves to the
|
|
15
|
+
* document and refetches the page; `/undefined` requests a file called undefined — both are
|
|
16
|
+
* measured in that file's own table, and clearing the attribute is the only spelling that
|
|
17
|
+
* produces no request.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 **Why this module exists at all.** The emitted app routes with a `BrowserRouter`, so the
|
|
20
|
+
* DOCUMENT sits at the route's depth and a relative `src` resolves against it. The parameter holds
|
|
21
|
+
* a project-relative path (`noodl_modules/starter-imagery/portrait.svg`) and the asset travels
|
|
22
|
+
* through the `copies` channel to `public/<same path>`, served from the site root. On a one-segment
|
|
23
|
+
* route (`/business`) the relative form resolves to `/noodl_modules/…` and works; on `/gallery/team`
|
|
24
|
+
* it resolves to `/gallery/noodl_modules/…` and 404s. The viewer never sees this because its Image
|
|
25
|
+
* node runs every source through `getAbsoluteUrl` at the port; the export printed the parameter
|
|
26
|
+
* verbatim, and every route the shipped templates emit is one segment deep, which is why three
|
|
27
|
+
* drives read zero broken images. `cssUrl` in `style.ts` closes the same gap for a CSS `url()`,
|
|
28
|
+
* for a different reason (a stylesheet resolves against ITSELF).
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ **Recorded divergence: `srcset` is resolved candidate by candidate here, and the viewer sets
|
|
31
|
+
* it verbatim** (`image.ts`, an `inputProps` entry with `propPath: 'dom'` and no setter). Under the
|
|
32
|
+
* viewer's default hash routing the document is always `/`, so verbatim works there; under path
|
|
33
|
+
* routing the viewer carries the same latent 404 in `srcset` that this closes for `src`. The
|
|
34
|
+
* export's router is a path router with no hash mode, so verbatim here would be a picture that
|
|
35
|
+
* breaks one route-depth away — the residual this module was written to close. The viewer-side
|
|
36
|
+
* gap is registered in EXP-014, owner NONE, not reproduced.
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ The icon node is the one caller that takes `absoluteUrl` DIRECTLY: the runtime's
|
|
39
|
+
* `iconImageSource` setter (`node-shared-port-definitions.ts`) calls `getAbsoluteUrl` with no
|
|
40
|
+
* empty gate in front, so an empty icon source stays `src=""` there and stays `src=""` here. That is
|
|
41
|
+
* the runtime's own unrepaired edge (NDA-012 fixed Image and Video only) — transcribed, not fixed.
|
|
42
|
+
*/
|
|
43
|
+
/** Where the module lands in the exported app. */
|
|
44
|
+
export declare const MEDIA_LIB_PATH = "src/lib/media.ts";
|
|
45
|
+
/** The helpers the module exports; a wired attribute names the one it prints through. */
|
|
46
|
+
export type MediaHelper = 'mediaSrc' | 'mediaSrcSet';
|
|
47
|
+
/**
|
|
48
|
+
* The DOM attributes that carry a media URL, and the helper each resolves through. Keyed by the
|
|
49
|
+
* ATTRIBUTE because that is the DOM's meaning, the same key `ATTR_SINK` in `component.ts` uses —
|
|
50
|
+
* `src` on an `<img>` and on a `<video>` are the same channel.
|
|
51
|
+
*/
|
|
52
|
+
export declare const MEDIA_ATTRS: Record<string, {
|
|
53
|
+
helper: MediaHelper;
|
|
54
|
+
resolve: (value: unknown) => string | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
/** `getAbsoluteUrl`, with the base the exported app is served from. */
|
|
57
|
+
export declare function absoluteUrl(value: unknown): string;
|
|
58
|
+
/** `resolveMediaSource`: an empty port clears the attribute; anything else is made absolute. */
|
|
59
|
+
export declare function mediaSrc(value: unknown): string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* A `srcset` with every candidate URL resolved and every descriptor kept. The tokeniser is the
|
|
62
|
+
* HTML parser's own: a candidate URL is a run of non-whitespace with trailing commas stripped (a
|
|
63
|
+
* comma INSIDE a `data:` URI survives, as it does in the browser), and its descriptors run to the
|
|
64
|
+
* next comma. A candidate that resolves to nothing is dropped; a list with none left clears.
|
|
65
|
+
*/
|
|
66
|
+
export declare function mediaSrcSet(value: unknown): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* The module's source — the three functions above, as the app receives them.
|
|
69
|
+
*
|
|
70
|
+
* 🔴 A second copy of the rule, and it is graded against the first: `tests/the-picture-path.test.ts`
|
|
71
|
+
* transpiles this text and runs it beside the package's own functions over one table, so the two
|
|
72
|
+
* cannot drift apart without a row going red. A plain string array rather than a template literal,
|
|
73
|
+
* for `dateLib.ts`'s stated reason.
|
|
74
|
+
*/
|
|
75
|
+
export declare function mediaLibSource(): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class and symbol naming (EXP-002 step 4, rules from EXP-002-TARGET-OUTPUT.md §1–§2):
|
|
3
|
+
*
|
|
4
|
+
* - Class names come from the author's node ids, camelCased. Editor dedup suffixes (`card-3`,
|
|
5
|
+
* `body-2`) are stripped when no other class in the component claims the base name — v2 ids
|
|
6
|
+
* are author-chosen and semantic, so this is authoring intent carrying through.
|
|
7
|
+
* - Nodes with byte-identical style declaration sets merge into one class. The merged name is
|
|
8
|
+
* the ids' longest common camelCase-word suffix when one exists (`nameLabel`/`emailLabel`… →
|
|
9
|
+
* `label`); failing that their longest common word prefix (`fieldName`/`fieldEmail`… →
|
|
10
|
+
* `field`, the target's own example); failing both, the alphabetically first id. Determinism
|
|
11
|
+
* needs a stated tiebreak, so it is stated here.
|
|
12
|
+
* - Machine-generated ids (UUIDs) carry no intent: those nodes name from their authored label,
|
|
13
|
+
* or fall back to their style role.
|
|
14
|
+
* - Collisions resolve first-wins in tree order with numeric suffixes (D5).
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Whether a bindable-looking identifier is in fact reserved (EXP-011 §45). The state allocator
|
|
18
|
+
* asks it: a checkbox labelled "Private" minted `const [private, setPrivate]`, which is a syntax
|
|
19
|
+
* error in a strict module — the fixture's own typecheck found it, and the sanitiser above had
|
|
20
|
+
* the list all along.
|
|
21
|
+
*/
|
|
22
|
+
export declare const isReservedWord: (name: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* One port name → the identifier it prints as, before per-component collision resolution.
|
|
25
|
+
* A name that is already a bindable identifier is returned untouched — the emitted interface is
|
|
26
|
+
* the author's vocabulary wherever it legally can be.
|
|
27
|
+
*/
|
|
28
|
+
export declare function propIdentifier(name: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Port name → emitted prop identifier, for every declared input prop of one component.
|
|
31
|
+
*
|
|
32
|
+
* Two passes, and the order is the point: a port that is *already* an identifier keeps its name,
|
|
33
|
+
* so a sanitised sibling yields to it rather than stealing it. The generated callback props
|
|
34
|
+
* (`onXChanged`, `onClose`) share the destructuring, so they reserve first.
|
|
35
|
+
*/
|
|
36
|
+
export declare function propIdentifiers(plan: {
|
|
37
|
+
props: ReadonlyArray<{
|
|
38
|
+
name: string;
|
|
39
|
+
}>;
|
|
40
|
+
outputProps: ReadonlyArray<{
|
|
41
|
+
prop: string;
|
|
42
|
+
}>;
|
|
43
|
+
liftedOutputProps: ReadonlyArray<{
|
|
44
|
+
prop: string;
|
|
45
|
+
}>;
|
|
46
|
+
closesPopup?: boolean;
|
|
47
|
+
}): Map<string, string>;
|
|
48
|
+
export interface ClassCandidate {
|
|
49
|
+
/** Node ids sharing one merged class, in tree (pre-order) encounter order. */
|
|
50
|
+
nodeIds: string[];
|
|
51
|
+
/** Fallback when every id is machine-generated: authored label of the first node, if any. */
|
|
52
|
+
label?: string;
|
|
53
|
+
/** Style role of the first node — the last-resort name (`page`, `text`, `group`). */
|
|
54
|
+
role: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Assigns one class name per candidate group, in the given order (first-wins for collisions).
|
|
58
|
+
* Returns names parallel to the input array.
|
|
59
|
+
*/
|
|
60
|
+
export declare function assignClassNames(groups: ClassCandidate[]): string[];
|
|
61
|
+
/**
|
|
62
|
+
* Splits one identical-style group into merge subgroups. Byte-identical declarations alone are
|
|
63
|
+
* not intent: `sectionHead` can collide with the field wrappers by accident. Ids merge only when
|
|
64
|
+
* they share naming vocabulary — a common first camel word (`fieldName`/`fieldEmail` → `field`)
|
|
65
|
+
* or a common last one (`nameLabel`/`emailLabel` → `label`). The partition (by-first vs by-last)
|
|
66
|
+
* that yields fewer classes wins; ties prefer the suffix. Ids sharing neither keep their own
|
|
67
|
+
* class — duplicate CSS is honest, a stolen name is not. Machine-generated ids never merge.
|
|
68
|
+
*/
|
|
69
|
+
export declare function partitionMergeGroup(nodeIds: string[]): string[][];
|
|
70
|
+
/** `card-3` → `card`: the editor's id-collision counter, not authoring intent. */
|
|
71
|
+
export declare function stripDedupSuffix(id: string): string;
|
|
72
|
+
export declare function camelCase(text: string): string;
|
|
73
|
+
export declare function pascalCase(text: string): string;
|
|
74
|
+
export declare function lowerFirst(text: string): string;
|
|
75
|
+
/** UUID-shaped ids carry no authoring intent. */
|
|
76
|
+
export declare function isMachineId(id: string): boolean;
|