@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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `src/lib/parse.ts` — `Parse CSV`, `To CSV`, `Parse XML` and `Parse Feed`, emitted into the app (EXP-011 §75).
|
|
3
|
+
*
|
|
4
|
+
* The same shape as {@link ./utilLib.ts}: constant text, shipped only where a component calls into it, and a
|
|
5
|
+
* **separate** module from `util.ts` on that file's own rule (a project that formats a string should not ship
|
|
6
|
+
* a CSV tokeniser, and an XML parser even less). The four are `util-call`s in the plan — each node is a
|
|
7
|
+
* function of its inputs and holds nothing else — with one difference from the three small utilities:
|
|
8
|
+
* every one of them has SEVERAL value outputs, so the call carries the output port's name as its trailing
|
|
9
|
+
* literal argument (`DATE_NODES`' selector, one family over) and the helper answers that port. A one-entry
|
|
10
|
+
* memo inside each helper keeps a page that reads three outputs of one node to one parse.
|
|
11
|
+
*
|
|
12
|
+
* A transcription of three runtime modules and four node files:
|
|
13
|
+
*
|
|
14
|
+
* - `noodl-runtime/src/csv.ts` — `stripBOM`, the contiguity-checking `tokenise`, `parseCSV` (the
|
|
15
|
+
* unterminated-quote sentence verbatim), `rowsToRecords`, `unionOfKeys`, `quoteCell`, `toCSV`, `rowsToCSV`.
|
|
16
|
+
* - `data/parsecsv.ts` / `data/tocsv.ts` — the abstain (`textSupplied` / `itemsSupplied`: nothing arrived ⇒
|
|
17
|
+
* no Items, Count 0, no Error — Failure Contract §2), the setters' fallbacks (`delimiter || ','`,
|
|
18
|
+
* `!!hasHeader`, a `stringlist` Columns split on commas and trimmed), rows-of-cells written cell for cell.
|
|
19
|
+
* - `noodl-runtime/src/xml.ts` — `scanForHostileConstructs` (the two refusals, sentences verbatim) and
|
|
20
|
+
* `parseXML` over **`fast-xml-parser` at the interpreter's own pin, 4.5.7**, with the interpreter's own
|
|
21
|
+
* options. 🔴 The one third-party dependency an emitted lib has ever declared: FED-001 R1 took the library
|
|
22
|
+
* over a hand-rolled scanner for the runtime (entity expansion is a denial-of-service hole, not a wrong
|
|
23
|
+
* answer), and a transcription of an XML parser would be a second parser that drifts — so
|
|
24
|
+
* {@link PARSE_DEPENDENCIES} is added to the exported `package.json` exactly when a component calls
|
|
25
|
+
* `parseXml` or `parseFeed`.
|
|
26
|
+
* - `noodl-runtime/src/feed.ts` — the whole module: RSS/Atom/RDF detection (RDF first, its `<item>` a
|
|
27
|
+
* SIBLING of `<channel>`), the readers, `toISODate`'s retry, the Atom link family, images, enclosures,
|
|
28
|
+
* tags, authors, the identity ladder and FNV-1a `hashId`. 🔴 **The ladder's separator is a NUL byte.**
|
|
29
|
+
* `feed.ts:365` holds a literal U+0000 between the quotes, which every editor draws as a space; the
|
|
30
|
+
* transcription writes `'\u0000'` because FED-002's unique index keys on the id and a space would mint a
|
|
31
|
+
* different id for every item on the hash rung. Found by the §A differential, not by reading.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ **The one divergence, and where it is reported.** On a FAILED parse the interpreter keeps the previous
|
|
34
|
+
* successful answer on its value outputs beside the Error (`items` is "replaced only on a successful
|
|
35
|
+
* parse"); a pure call has no previous answer, so Items is absent and Count is 0 beside the Error. The
|
|
36
|
+
* planner files a note at every parse node whose text arrives by wire — a literal cannot have a previous
|
|
37
|
+
* parse. On a FIRST failure the two agree exactly.
|
|
38
|
+
*
|
|
39
|
+
* `Changed` and `Failure` are recomputation pulses and defer with the date family's sentence
|
|
40
|
+
* ({@link PARSE_SIGNAL_SENTENCES}); a wire off one is not a chain the export can hang anything on.
|
|
41
|
+
*/
|
|
42
|
+
/** Where the module lands in the exported app. */
|
|
43
|
+
export declare const PARSE_LIB_PATH = "src/lib/parse.ts";
|
|
44
|
+
/** The exported helpers, one per node. Sorted — the import list is sorted too. */
|
|
45
|
+
export declare const PARSE_HELPERS: readonly ["parseCsv", "parseFeed", "parseXml", "toCsv"];
|
|
46
|
+
export type ParseHelper = (typeof PARSE_HELPERS)[number];
|
|
47
|
+
export declare function isParseHelper(fn: string): fn is ParseHelper;
|
|
48
|
+
/**
|
|
49
|
+
* The npm dependency the exported app takes on when XML is parsed — the interpreter's own pin
|
|
50
|
+
* (`noodl-runtime/package.json`, `xml.ts`'s module comment measured 4.5.7 at 230 KB unpacked with one dep
|
|
51
|
+
* against 5.x's 1.3 MB with six). Exact, not a caret, so the exported app and the interpreter read one parser.
|
|
52
|
+
*/
|
|
53
|
+
export declare const PARSE_DEPENDENCIES: Readonly<Record<string, string>>;
|
|
54
|
+
/** Which helpers need {@link PARSE_DEPENDENCIES}. `parseCsv` and `toCsv` need nothing. */
|
|
55
|
+
export declare const PARSE_HELPERS_NEEDING_DEPENDENCY: ReadonlySet<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Per output port: the static TypeScript type the helper's overload answers (`exprTsType`'s vocabulary —
|
|
58
|
+
* `unknown` wherever the value can be `undefined`, for `dateToString`'s reason: a single-placeholder String
|
|
59
|
+
* Format collapses a `string`-typed expression to the bare value, and that would print the word
|
|
60
|
+
* "undefined" where the interpreter substitutes `''`), and whether it can be `undefined` at all
|
|
61
|
+
* (`maybeUndefinedExpr`'s table, one family over).
|
|
62
|
+
*
|
|
63
|
+
* 🔴 Must agree with the overloads in {@link parseLibSource}. `items` is `any[]` on both CSV and Feed —
|
|
64
|
+
* the CSV rows' shape depends on Has Header, and the feed rows are typed inside the module only, so the
|
|
65
|
+
* emitted component never has to name a type it does not import. {@link FEED_ITEM_FIELDS} is what lets a
|
|
66
|
+
* repeater over a feed drop a mapped field the rows do not carry, by name.
|
|
67
|
+
*/
|
|
68
|
+
export declare const PARSE_OUTPUTS: Readonly<Record<ParseHelper, Readonly<Record<string, {
|
|
69
|
+
tsType: string;
|
|
70
|
+
maybeUndefined: boolean;
|
|
71
|
+
}>>>>;
|
|
72
|
+
/** The fields every feed item carries (`FeedItem` in the emitted module; `parsefeed.ts`'s Items description). */
|
|
73
|
+
export declare const FEED_ITEM_FIELDS: readonly string[];
|
|
74
|
+
/**
|
|
75
|
+
* The selector a `util-call` into this module carries: its trailing literal argument. `undefined` for a
|
|
76
|
+
* call that is not a parse helper, or whose last argument is not a string literal — neither can be built by
|
|
77
|
+
* the planner, so a lookup that answers `undefined` is a programming error upstream, not an authored shape.
|
|
78
|
+
*/
|
|
79
|
+
export declare function parseSelectorOf(expr: {
|
|
80
|
+
fn: string;
|
|
81
|
+
args: ReadonlyArray<{
|
|
82
|
+
kind: string;
|
|
83
|
+
value?: unknown;
|
|
84
|
+
}>;
|
|
85
|
+
}): string | undefined;
|
|
86
|
+
/** `exprTsType` for a parse call — `unknown` for a selector the table does not know, never a throw. */
|
|
87
|
+
export declare function parseOutputTsType(fn: ParseHelper, selector: string | undefined): string;
|
|
88
|
+
/** `maybeUndefinedExpr` for a parse call — `true` for a selector the table does not know: the guarded answer. */
|
|
89
|
+
export declare function parseOutputMayBeUndefined(fn: ParseHelper, selector: string | undefined): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The deferral sentence for a signal output consumed as a wire — the date family's `DATE_SIGNAL_OUTPUTS`,
|
|
92
|
+
* one family over, and for the same reason: `Changed` and `Failure` announce a recomputation, and a
|
|
93
|
+
* recomputation in the emitted component is a render, not an event a chain can hang off. `Failure` is a
|
|
94
|
+
* value this slice renders as an absent one (the Error output) rather than a pulse it can fire.
|
|
95
|
+
*/
|
|
96
|
+
export declare const PARSE_SIGNAL_SENTENCES: Readonly<Record<string, string>>;
|
|
97
|
+
/**
|
|
98
|
+
* The module's source.
|
|
99
|
+
*
|
|
100
|
+
* ⚠️ A plain string array rather than a template literal, for `dateLib.ts`'s stated reason: the emitted body
|
|
101
|
+
* needs no interpolation, and keeping it out of an interpolation context means a future edit cannot
|
|
102
|
+
* accidentally interpolate the generator's own scope into the exported app. Generated from a real `.ts`
|
|
103
|
+
* file that was typechecked under the exported app's own strict options and graded against the runtime
|
|
104
|
+
* modules by `tests/the-parsers.test.ts` §A; edit the strings and the test grades the edit.
|
|
105
|
+
*/
|
|
106
|
+
export declare function parseLibSource(): string;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pre-flight summary — EXP-004's *before you export* surface.
|
|
3
|
+
*
|
|
4
|
+
* ## What this is for
|
|
5
|
+
*
|
|
6
|
+
* `EXPORT-REPORT.md` (see {@link module:emit/report}) is the account an author reads *after* the
|
|
7
|
+
* export, inside the app they now own. EXP-004 asks for one more moment: **before** they pick a
|
|
8
|
+
* directory and write sixty files, tell them what they are about to get, so the decision to
|
|
9
|
+
* proceed is made with accurate expectations rather than with the word "export".
|
|
10
|
+
*
|
|
11
|
+
* Same facts, earlier moment, and much shorter — this is a thing someone reads in a dialog and
|
|
12
|
+
* then clicks a button, not a document they study.
|
|
13
|
+
*
|
|
14
|
+
* ## 🔴 It is exact, and it is deliberately not an estimate
|
|
15
|
+
*
|
|
16
|
+
* EXP-004's own wording is *"pre-flight **estimate**"*, and its testing plan asks that the
|
|
17
|
+
* estimate "match the actual outcome within a reasonable margin". **That premise does not survive
|
|
18
|
+
* contact with this pipeline, and the honest thing is to say so rather than to build a
|
|
19
|
+
* deliberately worse predictor so that a margin exists to measure.**
|
|
20
|
+
*
|
|
21
|
+
* `emitApp` is pure: it reads nothing, writes nothing, and returns the files as strings. Writing
|
|
22
|
+
* them to disk is the caller's separate act (`write/writeExport.ts`, which both front doors call).
|
|
23
|
+
* So the cost of knowing the real answer is the cost of running the generator — about a tenth of a
|
|
24
|
+
* second on every project in the corpus — and there is no reason whatever to approximate a number
|
|
25
|
+
* you can simply compute.
|
|
26
|
+
* A margin here would be a defect, not a tolerance.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ **The cheap alternative was measured before this was written, because it is the one a future
|
|
29
|
+
* session will reach for.** The obvious "estimate" is to stop after `planProject` and skip
|
|
30
|
+
* emission — that is where most refusals are decided (§20.1), and it looks like the same answer
|
|
31
|
+
* for less work. Over the seven fixtures it is **not** the same answer: the plan layer files 28
|
|
32
|
+
* refusals where the export files 32. The missing four are all decided during emission — three
|
|
33
|
+
* `parameter … has no style/content mapping` on `puppy-test-3` and one retired kit parameter on
|
|
34
|
+
* `kits` — and they are invisible to any analysis that stops before the generator runs.
|
|
35
|
+
*
|
|
36
|
+
* 🔴 **That divergence happens to be invisible at component level today, and that is a fact about
|
|
37
|
+
* the corpus rather than a property of the layers.** No fixture currently has a component whose
|
|
38
|
+
* *only* refusals are emit-layer ones, so a plan-layer pre-flight would still sort every component
|
|
39
|
+
* onto the correct side of "clean". Nothing makes that hold: a component with one unmapped
|
|
40
|
+
* `padding` and no dropped wires would be called clean and export dirty. The control pair in
|
|
41
|
+
* `tests/preflight.test.ts` asserts the two layers **disagree** on the totals, which is what keeps
|
|
42
|
+
* this paragraph honest — a cheap-layer rewrite reddens a row instead of quietly under-reporting.
|
|
43
|
+
*
|
|
44
|
+
* ## The framing rules are `report.ts`'s, and they are the actual work
|
|
45
|
+
*
|
|
46
|
+
* - **Lead with what worked.** Counts and whole components first; a project with nothing to report
|
|
47
|
+
* never renders a list of problems, because there are none to render.
|
|
48
|
+
* - **Be specific, not statistical.** No percentages and no confidence score. Components are named
|
|
49
|
+
* and their refusals counted.
|
|
50
|
+
* - **Never let unverified code look verified.** Nothing has been run here either — less so, in
|
|
51
|
+
* fact, since at this point nothing has even been written.
|
|
52
|
+
*
|
|
53
|
+
* @module emit/preflight
|
|
54
|
+
*/
|
|
55
|
+
import { Catalog } from '../catalog';
|
|
56
|
+
import { ExportIR, RefusedNode } from '../ir/types';
|
|
57
|
+
import { EmittedApp } from './emitApp';
|
|
58
|
+
import { BackendMode, ExportCascade } from './report';
|
|
59
|
+
/** A component that will emit a file, but with refusals recorded against it. */
|
|
60
|
+
export interface PreflightAttention {
|
|
61
|
+
path: string;
|
|
62
|
+
file: string;
|
|
63
|
+
/** How many nodes, wires or parameters the export will leave out of this component. */
|
|
64
|
+
refusals: number;
|
|
65
|
+
unreachable: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* EXP-013 AC2 — the refused nodes themselves, by type and label, with the root that starves each
|
|
68
|
+
* silenced one. The same rows `EXPORT-REPORT.md` prints under the component.
|
|
69
|
+
*/
|
|
70
|
+
nodes: RefusedNode[];
|
|
71
|
+
}
|
|
72
|
+
/** A component that will emit no file at all. */
|
|
73
|
+
export interface PreflightNoFile {
|
|
74
|
+
path: string;
|
|
75
|
+
reason: string;
|
|
76
|
+
/**
|
|
77
|
+
* `scaffolded` is done — the app shell covers it. `deferred` is not. They are opposite news and
|
|
78
|
+
* the discriminator is carried from the two sites in `plan.ts` that know, never read back out of
|
|
79
|
+
* the sentence.
|
|
80
|
+
*/
|
|
81
|
+
kind: 'scaffolded' | 'deferred';
|
|
82
|
+
/** EXP-013 AC2. The nodes the plan refused in a component that emits no file. */
|
|
83
|
+
nodes: RefusedNode[];
|
|
84
|
+
}
|
|
85
|
+
export interface PreflightSummary {
|
|
86
|
+
projectName: string;
|
|
87
|
+
/**
|
|
88
|
+
* EXP-013 AC3 — the refusals split into the nodes the export has no rule for and the nodes only
|
|
89
|
+
* refused because of them, with the roots named. `cascade.silenced + cascade.unsilenced` is the
|
|
90
|
+
* number of refused nodes; {@link refusals} below is the number of *lines* (nodes, wires,
|
|
91
|
+
* parameters, whole components) and stays the length of the report's list.
|
|
92
|
+
*/
|
|
93
|
+
cascade: ExportCascade;
|
|
94
|
+
/**
|
|
95
|
+
* EXP-013 AC4 — the plain sentence that leads when a cascade costs a pathway (a backend verb, a
|
|
96
|
+
* navigation, an `On App Error`), or `null` when the loss is nodes rather than a pathway. The
|
|
97
|
+
* modal's button changes with it.
|
|
98
|
+
*/
|
|
99
|
+
verdict: string | null;
|
|
100
|
+
/**
|
|
101
|
+
* Files the export will generate, `EXPORT-REPORT.md` included — the number the author will see
|
|
102
|
+
* in the folder afterwards.
|
|
103
|
+
*/
|
|
104
|
+
generatedFiles: number;
|
|
105
|
+
/**
|
|
106
|
+
* Assets that travel byte-for-byte beside them: a kit's script, an icon set's `.woff2`,
|
|
107
|
+
* Inter's four `.ttf` (EXP-010 AC5).
|
|
108
|
+
*
|
|
109
|
+
* 🔴 **Counted separately and never folded into {@link generatedFiles}, because they are a
|
|
110
|
+
* separate channel that has already been dropped once.** `emitApp` returns `files` and `copies`
|
|
111
|
+
* as two lists, and §19.6 records the one shipped runner writing only the first — an author's
|
|
112
|
+
* fonts silently absent from their repo, every gate green. A pre-flight that promised
|
|
113
|
+
* "33 files" and delivered 45 would be describing the same defect from the other end.
|
|
114
|
+
*/
|
|
115
|
+
copiedAssets: number;
|
|
116
|
+
/** Pages and components that will emit a file. */
|
|
117
|
+
pages: number;
|
|
118
|
+
components: number;
|
|
119
|
+
/** Paths of the components that will translate with nothing left over, in emission order. */
|
|
120
|
+
whole: string[];
|
|
121
|
+
/** The rest, worst first. */
|
|
122
|
+
attention: PreflightAttention[];
|
|
123
|
+
noFile: PreflightNoFile[];
|
|
124
|
+
/**
|
|
125
|
+
* Every item the export will list as not translated: each component-level refusal, each
|
|
126
|
+
* component that emits **no file at all**, each kit that contributes no nodes, and each line
|
|
127
|
+
* about the project as a whole. This is the length of the list `EXPORT-REPORT.md` will print.
|
|
128
|
+
*
|
|
129
|
+
* 🔴 **A component that emits no file is in this number, and the first draft left it out.** It
|
|
130
|
+
* counted only the refusals recorded *inside* generated components, so `puppy-test-3` announced
|
|
131
|
+
* "18" and then printed nineteen bullets — the extra being `Components/BenchLogicProbe`, which
|
|
132
|
+
* is not a node dropped from a file but a whole component with no file. That is the largest
|
|
133
|
+
* thing an export can leave out, and it was the one thing the headline number could not see.
|
|
134
|
+
*/
|
|
135
|
+
refusals: number;
|
|
136
|
+
/** Components with no generated file that are *not* handled by the app shell — counted above. */
|
|
137
|
+
noFileDeferred: number;
|
|
138
|
+
backend: BackendMode;
|
|
139
|
+
backendEndpoint: string | null;
|
|
140
|
+
/** `src/api/http.ts` — real generated calls to the addresses the author typed, never stubs. */
|
|
141
|
+
httpModule: boolean;
|
|
142
|
+
/** EXP-011 §41. `src/api/functions.ts` — one function per `Cloud Function` node. */
|
|
143
|
+
functionsModule: boolean;
|
|
144
|
+
/** Kits that will ship their files but contribute none of their nodes. */
|
|
145
|
+
moduleFailures: number;
|
|
146
|
+
/** Refusals about the project rather than any one component (cloud functions, chiefly). */
|
|
147
|
+
projectRefusals: number;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The summary, from an export that has already been generated in memory.
|
|
151
|
+
*
|
|
152
|
+
* 🔴 **Everything here is read off `EmittedApp`, and nothing is recomputed.** The pre-flight and
|
|
153
|
+
* the report are two renderings of one set of facts; the moment this function starts deciding for
|
|
154
|
+
* itself whether a component counts as whole, the surface an author reads *before* exporting and
|
|
155
|
+
* the one they read *after* can disagree about the same project — which is the failure EXP-004's
|
|
156
|
+
* risk table calls "the report drifting out of sync with what the generators actually do",
|
|
157
|
+
* arriving through a door the risk table did not think to name.
|
|
158
|
+
*/
|
|
159
|
+
export declare function summarizePreflight(app: EmittedApp): PreflightSummary;
|
|
160
|
+
/**
|
|
161
|
+
* Generate the app in memory and summarise it, writing nothing.
|
|
162
|
+
*
|
|
163
|
+
* This is the whole of the "pre" in pre-flight: the export runs, and its output is discarded
|
|
164
|
+
* rather than committed to a directory the author has not yet chosen.
|
|
165
|
+
*/
|
|
166
|
+
export declare function preflight(ir: ExportIR, catalog: Catalog): PreflightSummary;
|
|
167
|
+
/**
|
|
168
|
+
* The pre-flight as text an author reads and then decides on.
|
|
169
|
+
*
|
|
170
|
+
* Pure, and deliberately much shorter than `renderReport` — this is read standing up. It names
|
|
171
|
+
* what will be built, names every component that will come out incomplete, and says plainly that
|
|
172
|
+
* the full reasons travel with the app rather than repeating them here.
|
|
173
|
+
*/
|
|
174
|
+
export declare function renderPreflight(s: PreflightSummary): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `README.md` — the front door of the exported repository (EXP-004).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is a second file and not a section of the report
|
|
5
|
+
*
|
|
6
|
+
* They are reached by different routes and answer different questions.
|
|
7
|
+
*
|
|
8
|
+
* `EXPORT-REPORT.md` is the **record**: every node, wire and parameter the export refused, in the
|
|
9
|
+
* exporter's own words. It is what the `TODO(export)` markers in the code point at, and it is
|
|
10
|
+
* complete by construction — a reader arrives at it already looking for one specific thing.
|
|
11
|
+
*
|
|
12
|
+
* This file is what someone opens **first**, usually without having been sent anywhere. It has to
|
|
13
|
+
* answer *what is this*, *how do I run it*, *what is missing*, *what do I do about it* and *can I
|
|
14
|
+
* edit it* — and then get out of the way. It states no refusal individually; it says how many
|
|
15
|
+
* there are and sends the reader next door.
|
|
16
|
+
*
|
|
17
|
+
* ## 🔴 It is emitted for every export, and it did not used to be
|
|
18
|
+
*
|
|
19
|
+
* Until EXP-004 the README was written inside `apiModules`, under `backend !== undefined &&
|
|
20
|
+
* hasApi` — so it arrived only for a project with a NodeGX backend *and* something that queried
|
|
21
|
+
* it. Six of the seven corpus fixtures got no README at all, which is the file a developer opens
|
|
22
|
+
* first being absent from the export in the ordinary case. It is now emitted beside the report,
|
|
23
|
+
* from the same data, unconditionally. The backend section is what became conditional.
|
|
24
|
+
*
|
|
25
|
+
* ## What it shares with the report, and what it must not
|
|
26
|
+
*
|
|
27
|
+
* The ordered next steps are **the same list**, from {@link nextSteps} — one decision, two
|
|
28
|
+
* readers, the pattern `backendMode` already establishes here. What it does not share is the
|
|
29
|
+
* itemised refusals: two full copies of that list would be two things to keep in step, and the
|
|
30
|
+
* one that drifted would be the one nobody regenerated.
|
|
31
|
+
*
|
|
32
|
+
* 🔴 **No clock, no absolute path, no random id**, for the reason `report.ts` gives at length: the
|
|
33
|
+
* generators are byte-stable, and a date line would turn every golden in the package into noise.
|
|
34
|
+
*
|
|
35
|
+
* @module emit/readme
|
|
36
|
+
*/
|
|
37
|
+
import { ExportReportData } from './report';
|
|
38
|
+
export declare const README_PATH = "README.md";
|
|
39
|
+
/** Just the two fields the README prints; the IR record carries more, and none of it belongs here. */
|
|
40
|
+
export interface ReadmeBackend {
|
|
41
|
+
endpoint: string;
|
|
42
|
+
appId: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The README, as Markdown.
|
|
46
|
+
*
|
|
47
|
+
* Pure. `backend` is read only when {@link backendMode} says `connected` — a project can declare
|
|
48
|
+
* cloud services and never query them, and an env-var section for a `src/api/client.ts` that was
|
|
49
|
+
* never emitted would be instructions for configuring nothing.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderReadme(data: ExportReportData, backend: ReadmeBackend | null): string;
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `EXPORT-REPORT.md` — the report the exported app carries with it (EXP-004).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this file exists
|
|
5
|
+
*
|
|
6
|
+
* Two generated `TODO(export)` markers have ended with *"See the export report"* since EXP-002,
|
|
7
|
+
* and there was no report. Everything the export dropped or refused went to `EmittedApp.notes`,
|
|
8
|
+
* which the runner printed to **stderr** — so an author who ran the export and opened
|
|
9
|
+
* the repo saw markers pointing at a document that did not exist, and a scrollback they no longer
|
|
10
|
+
* had. This is that document, written into the app beside the code it is about.
|
|
11
|
+
*
|
|
12
|
+
* ## The framing rules, which are the actual work
|
|
13
|
+
*
|
|
14
|
+
* EXP-004's own words, and each one is visible in the output below:
|
|
15
|
+
*
|
|
16
|
+
* - **Lead with what worked.** A report opening on a list of problems reads as failure even when
|
|
17
|
+
* the export went well. The counts and the whole-component list come first, and a project with
|
|
18
|
+
* nothing to report never renders an "attention" section at all.
|
|
19
|
+
* - **Be specific, not statistical.** No percentages and no confidence score. Every refusal is a
|
|
20
|
+
* named component, a named node or wire, and the exporter's own sentence for why.
|
|
21
|
+
* - **Never let unverified code look verified.** ⚠️ This is the one that constrains wording
|
|
22
|
+
* hardest, and the honest statement today is *narrower* than EXP-004 anticipated: EXP-003's AI
|
|
23
|
+
* translation and trace verification do not exist yet, so nothing in a generated app has been
|
|
24
|
+
* run, replayed or compared against anything. The report says that in those words. When
|
|
25
|
+
* EXP-003 lands, this section gains a verdict per translation — it must not gain the word
|
|
26
|
+
* "verified" before then.
|
|
27
|
+
*
|
|
28
|
+
* ## 🔴 No timestamp, and that is not an oversight
|
|
29
|
+
*
|
|
30
|
+
* The generators are deterministic (EXP-002-DETERMINISTIC-GENERATORS): the same project emits the
|
|
31
|
+
* same bytes, which is what makes the goldens a regression net and what lets a user diff two
|
|
32
|
+
* exports. A date line would change every byte-comparison in the suite and turn "nothing changed"
|
|
33
|
+
* into noise. The report carries no clock, no random id and no absolute path.
|
|
34
|
+
*
|
|
35
|
+
* ## Where the structure comes from
|
|
36
|
+
*
|
|
37
|
+
* 🔴 **Grouping is by what the caller already knew, never by reading the prose back.** `emitApp`
|
|
38
|
+
* pushes a component's notes inside the loop that owns `plan`, so scope is a fact at the push
|
|
39
|
+
* site; `skipKind` discriminates the router shell from a logic-only component because the two
|
|
40
|
+
* sentences would otherwise have to be told apart by matching their words. A report that grouped
|
|
41
|
+
* by parsing `notes` would mis-file the first time anyone reworded a refusal — and rewording
|
|
42
|
+
* refusals is the ordinary business of this package.
|
|
43
|
+
*
|
|
44
|
+
* @module emit/report
|
|
45
|
+
*/
|
|
46
|
+
import { RefusedNode } from '../ir/types';
|
|
47
|
+
export declare const REPORT_PATH = "EXPORT-REPORT.md";
|
|
48
|
+
/** One component, as the report talks about it. */
|
|
49
|
+
export interface ReportComponent {
|
|
50
|
+
/** "Pages/Home", "Components/PuppyCard". */
|
|
51
|
+
path: string;
|
|
52
|
+
role: 'page' | 'component';
|
|
53
|
+
/** The file it emitted, or `null` when it emitted none. */
|
|
54
|
+
file: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Everything dropped or deferred inside it, in emission order, with the exporter's own
|
|
57
|
+
* reason. Already stripped of the `"<path>: "` prefix the emitter writes onto each one.
|
|
58
|
+
*/
|
|
59
|
+
notes: string[];
|
|
60
|
+
/**
|
|
61
|
+
* No route reaches this component. Reported, never acted on — it changes how the notes above
|
|
62
|
+
* read (a refusal in unreachable code is not a gap in the export's reach), so it is stated
|
|
63
|
+
* beside them rather than filtering them out.
|
|
64
|
+
*/
|
|
65
|
+
unreachable: boolean;
|
|
66
|
+
/** Set when nothing was emitted for this component at all. */
|
|
67
|
+
skipped?: {
|
|
68
|
+
kind: 'scaffolded' | 'deferred';
|
|
69
|
+
reason: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Authored scripts this export did not translate, on a component that emitted no file
|
|
73
|
+
* (EXP-011 §27.6).
|
|
74
|
+
*
|
|
75
|
+
* 🔴 **Only ever populated when {@link file} is `null`.** A component with a module carries the
|
|
76
|
+
* identical record as a code comment beside the node, and printing it here as well would make
|
|
77
|
+
* the report a second copy of something the repo already holds — the shape that is a duplicate
|
|
78
|
+
* before it is a check. `emitApp` fills this in its skip branch and nowhere else, so which of
|
|
79
|
+
* the two carriers holds a script is decided by whether a file exists, not by a filter.
|
|
80
|
+
*/
|
|
81
|
+
preservedScripts?: Array<{
|
|
82
|
+
nodeId: string;
|
|
83
|
+
typeName: string;
|
|
84
|
+
label?: string;
|
|
85
|
+
source: string;
|
|
86
|
+
}>;
|
|
87
|
+
/**
|
|
88
|
+
* EXP-013. The nodes the plan refused in this component, each with the refused node that starves
|
|
89
|
+
* it when the refusal is a cascade. Optional so a report built by hand before EXP-013 still
|
|
90
|
+
* types; `emitApp` always sets it.
|
|
91
|
+
*/
|
|
92
|
+
refusals?: RefusedNode[];
|
|
93
|
+
}
|
|
94
|
+
export interface ExportReportData {
|
|
95
|
+
projectName: string;
|
|
96
|
+
/** Every emitted file, so the report can count without being handed the contents. */
|
|
97
|
+
files: string[];
|
|
98
|
+
components: ReportComponent[];
|
|
99
|
+
/** `noodl_modules` folders that could not contribute their nodes; their files still ship. */
|
|
100
|
+
modules: string[];
|
|
101
|
+
/** Lines that are about the project rather than any one component. */
|
|
102
|
+
project: string[];
|
|
103
|
+
/** The backend endpoint the api modules talk to, or `null` when the project declares none. */
|
|
104
|
+
backendEndpoint: string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the project asks anything of a backend — a collection query, a mutation, or a session
|
|
107
|
+
* call.
|
|
108
|
+
*
|
|
109
|
+
* 🔴 **Not "were any api files emitted".** `src/api/http.ts` is emitted for an `HTTP Request`,
|
|
110
|
+
* which talks to whatever address the author typed and is real code in a project with no backend
|
|
111
|
+
* at all. Keying the stub sentence on "any api file" told a project with a working fetch and no
|
|
112
|
+
* backend that its reads answer empty — see {@link httpModule}.
|
|
113
|
+
*/
|
|
114
|
+
usesBackend: boolean;
|
|
115
|
+
/** Whether `src/api/http.ts` was emitted — generated `HTTP Request` calls, never stubs. */
|
|
116
|
+
httpModule: boolean;
|
|
117
|
+
/** EXP-011 §41. Whether `src/api/functions.ts` was emitted — one function per `Cloud Function` node. Optional so a report built by hand before §41 still types. */
|
|
118
|
+
functionsModule?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* HLS-003 — stored parameters this export read the editor's way rather than the file's.
|
|
121
|
+
*
|
|
122
|
+
* Absent when the project has no node that exercises the seam, which is the common case and is
|
|
123
|
+
* why this is optional rather than a zeroed record. 🔴 **Absent means "nothing here exercises
|
|
124
|
+
* it", never "the seam is closed"** — a project with no wired control signal on one of the
|
|
125
|
+
* fifteen NDA-017 families reads absent however open the seam is.
|
|
126
|
+
*/
|
|
127
|
+
settled?: {
|
|
128
|
+
parameters: number;
|
|
129
|
+
nodes: number;
|
|
130
|
+
/** Component paths, sorted — where an author would go to look. */
|
|
131
|
+
components: string[];
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* STY-004 AC6 — the named styles this export resolved into CSS.
|
|
135
|
+
*
|
|
136
|
+
* 🔴 **Silence was the defect.** A variant and a text style on an ordinary leaf node were both
|
|
137
|
+
* dropped, and `variant` appeared **0 times in a 1,059-line report** — so the author had no
|
|
138
|
+
* signal at all, in a report that itemises dropped wires. A link that IS carried must therefore
|
|
139
|
+
* say so out loud; an export that prints nothing here is claiming the project has no named
|
|
140
|
+
* styles, which is a checkable claim rather than an absence.
|
|
141
|
+
*
|
|
142
|
+
* Absent when the project declares no styles — every fixture in this package and all seven
|
|
143
|
+
* shipped templates.
|
|
144
|
+
*/
|
|
145
|
+
styles?: StylesReport;
|
|
146
|
+
}
|
|
147
|
+
/** STY-004. What {@link ExportReportData.styles} counts. Every field is "resolved into CSS", not "present in the file". */
|
|
148
|
+
export interface StylesReport {
|
|
149
|
+
/** `Look name (NodeType)` → how many nodes wore it. Sorted by name. */
|
|
150
|
+
looks: Array<{
|
|
151
|
+
name: string;
|
|
152
|
+
typename: string;
|
|
153
|
+
nodes: number;
|
|
154
|
+
}>;
|
|
155
|
+
/** Text style name → how many nodes resolved it. Sorted. */
|
|
156
|
+
textStyles: Array<{
|
|
157
|
+
name: string;
|
|
158
|
+
nodes: number;
|
|
159
|
+
}>;
|
|
160
|
+
/** Colour style names the project defines, sorted. Resolution is per parameter and not counted here. */
|
|
161
|
+
colors: string[];
|
|
162
|
+
/**
|
|
163
|
+
* Looks that carry `stateParameters` — hover, pressed, disabled.
|
|
164
|
+
*
|
|
165
|
+
* 🔴 **Carried in the file, NOT drawn by this export.** Naming them is the honest half of AC6:
|
|
166
|
+
* the states half of the Look model is a later task, and an author whose Look defines a hover
|
|
167
|
+
* needs to be told it did not ship rather than discover it in a browser.
|
|
168
|
+
*/
|
|
169
|
+
looksWithUnemittedStates: string[];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Which of the three states the generated `src/api/` is in.
|
|
173
|
+
*
|
|
174
|
+
* 🔴 **One decision, two readers.** The report prints a sentence from it and the pre-flight
|
|
175
|
+
* summary (EXP-004's before-you-export surface) prints a different one, and the discriminator
|
|
176
|
+
* itself — *does the project ask anything of a backend, and did it name one* — must not exist
|
|
177
|
+
* twice. A second copy would be right until the day `usesBackend` grew a third case, and the two
|
|
178
|
+
* surfaces would then disagree about the same project in the same export.
|
|
179
|
+
*/
|
|
180
|
+
export type BackendMode = 'connected' | 'stubbed' | 'absent';
|
|
181
|
+
export declare function backendMode(data: Pick<ExportReportData, 'usesBackend' | 'backendEndpoint'>): BackendMode;
|
|
182
|
+
/** `"Pages/Home: wire x dropped"` → `"wire x dropped"`, when the prefix is this component's. */
|
|
183
|
+
export declare function stripScope(path: string, note: string): string;
|
|
184
|
+
/**
|
|
185
|
+
* The ordered steps as Markdown, shared by the report and the README so the two cannot format the
|
|
186
|
+
* same facts differently. Three-space continuation indent is what keeps the detail and locations
|
|
187
|
+
* inside their numbered item rather than starting a new block after it.
|
|
188
|
+
*/
|
|
189
|
+
/** A refused node the way a person names it: their label first, the picker's type name beside it. */
|
|
190
|
+
export declare function describeNode(node: Pick<RefusedNode, 'displayName' | 'label' | 'nodeId'>): string;
|
|
191
|
+
/**
|
|
192
|
+
* The reason as a person reads it. The planner's catch-all is `logic node (<typeName>)` — the
|
|
193
|
+
* exporter talking to itself, and the sentence `sweepUnreportedDeferrals` already refuses to
|
|
194
|
+
* stutter back at the author. Every other reason is a gate's own sentence and is kept verbatim.
|
|
195
|
+
*/
|
|
196
|
+
export declare function plainReason(node: Pick<RefusedNode, 'type' | 'reason'>): string;
|
|
197
|
+
/** One root refusal and everything it silences, across the whole export. */
|
|
198
|
+
export interface CascadeRoot {
|
|
199
|
+
/** The component the root sits in. */
|
|
200
|
+
path: string;
|
|
201
|
+
node: RefusedNode;
|
|
202
|
+
/** Every node whose `causedBy` resolves to this root, in report order. */
|
|
203
|
+
silences: Array<{
|
|
204
|
+
path: string;
|
|
205
|
+
node: RefusedNode;
|
|
206
|
+
}>;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* EXP-013 AC3/AC4 — the refusals of an export, split into the ones the export has no rule for
|
|
210
|
+
* and the ones that are only refused because of them.
|
|
211
|
+
*/
|
|
212
|
+
export interface ExportCascade {
|
|
213
|
+
/** Refused nodes with no `causedBy`, that at least one other node names as its cause. Worst first. */
|
|
214
|
+
roots: CascadeRoot[];
|
|
215
|
+
/** Refused nodes with no `causedBy` — the roots above plus every independent refusal. */
|
|
216
|
+
unsilenced: number;
|
|
217
|
+
/** Refused nodes with a `causedBy` — the sum of every root's `silences`. */
|
|
218
|
+
silenced: number;
|
|
219
|
+
/**
|
|
220
|
+
* Nodes whose loss is a pathway rather than a feature (see `isPathwayType`) and that are lost to
|
|
221
|
+
* a cascade — as a silenced node, or as a root that is itself a pathway (an `On App Error` the
|
|
222
|
+
* export has no rule for takes the error pathway with it whether or not anything hangs off it).
|
|
223
|
+
*/
|
|
224
|
+
pathway: Array<{
|
|
225
|
+
path: string;
|
|
226
|
+
node: RefusedNode;
|
|
227
|
+
}>;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The cascade, from the report data and nothing else.
|
|
231
|
+
*
|
|
232
|
+
* 🔴 **`silenced` + `unsilenced` is the number of refused nodes, and `silenced` is the sum of the
|
|
233
|
+
* roots' lists — asserted rather than assumed** (`tests/cascade.test.ts`), because the headline the
|
|
234
|
+
* modal prints is *"N the export has no rule for, M more silenced by them"*, and the cheap mistake
|
|
235
|
+
* is to count a silenced node on both sides. A node with two roots is listed under both and counted
|
|
236
|
+
* once.
|
|
237
|
+
*/
|
|
238
|
+
export declare function cascadeOf(data: Pick<ExportReportData, 'components'>): ExportCascade;
|
|
239
|
+
/**
|
|
240
|
+
* EXP-013 AC4 — the sentence that leads when a cascade costs a pathway, or `null`.
|
|
241
|
+
*
|
|
242
|
+
* Plain, and the same words on the modal and in the report: *this export would be missing a
|
|
243
|
+
* pathway, not a node*. It names the roots, because the roots are what a person can act on.
|
|
244
|
+
*/
|
|
245
|
+
export declare function pathwayVerdict(cascade: ExportCascade): string | null;
|
|
246
|
+
/** The lines under a component that name the nodes the plan refused, roots and their cascades first. */
|
|
247
|
+
export declare function refusedNodeLines(refusals: RefusedNode[] | undefined, indent?: string): string[];
|
|
248
|
+
export declare function renderSteps(steps: NextStep[]): string[];
|
|
249
|
+
/**
|
|
250
|
+
* The report, as Markdown.
|
|
251
|
+
*
|
|
252
|
+
* Pure — it is handed everything it says and reads nothing, so the suite can drive it on data
|
|
253
|
+
* that no fixture on disk produces (a project with no gaps at all, which is the section order's
|
|
254
|
+
* hardest case and the one a corpus is least likely to contain).
|
|
255
|
+
*/
|
|
256
|
+
export declare function renderReport(data: ExportReportData): string;
|
|
257
|
+
/**
|
|
258
|
+
* One thing the author has to do, and where.
|
|
259
|
+
*
|
|
260
|
+
* `where` is *locations*, not always files: a component the export refused entirely has no file to
|
|
261
|
+
* name, so the component's own path is what a reader can act on. The renderers do not know the
|
|
262
|
+
* difference and do not need to — both print it as a code span.
|
|
263
|
+
*/
|
|
264
|
+
export interface NextStep {
|
|
265
|
+
/** Imperative, one sentence, ending in a full stop. */
|
|
266
|
+
title: string;
|
|
267
|
+
/** What is broken until it is done. This is the sentence that justifies the position. */
|
|
268
|
+
detail: string;
|
|
269
|
+
/** Files or component paths, in emission order. May be empty. */
|
|
270
|
+
where: string[];
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* EXP-004's *"actionable next steps ordered by priority"*, computed once and rendered twice.
|
|
274
|
+
*
|
|
275
|
+
* ## 🔴 The ordering criterion, written down so it can be argued with
|
|
276
|
+
*
|
|
277
|
+
* **How much of the running app is missing without it, most first.** Not difficulty, not the
|
|
278
|
+
* number of refusals, and deliberately not the order the report prints its sections in — the
|
|
279
|
+
* report is organised for *looking something up*, and this list is organised for *doing the work*.
|
|
280
|
+
*
|
|
281
|
+
* 1. **Components with no file at all.** The most absent thing the export produces: a route can
|
|
282
|
+
* lead to a component that was never written.
|
|
283
|
+
* 2. **A stubbed `src/api/`.** Every read answers empty and every write throws, across the whole
|
|
284
|
+
* app rather than one screen.
|
|
285
|
+
* 3. **Refusals in components a route reaches.** Screens that render, missing behaviour.
|
|
286
|
+
* 4. **Refusals in components no route reaches.** The same work, but nothing runs it today — which
|
|
287
|
+
* is the whole of why it sits below 3 rather than beside it.
|
|
288
|
+
* 5. **Kits that shipped files but contributed no nodes.**
|
|
289
|
+
* 6. **Anything the export said about the project as a whole.**
|
|
290
|
+
* 7. **Test what was generated.** Always present, always last, and the only step that is not about
|
|
291
|
+
* something missing.
|
|
292
|
+
*
|
|
293
|
+
* ⚠️ **1 above 2 is a judgement and not a measurement.** A missing component takes out one screen;
|
|
294
|
+
* a stubbed backend takes out every data path in the app. They are ranked the way they are because
|
|
295
|
+
* "nothing was emitted" is strictly more absent than "a stub exists, and it names the node it came
|
|
296
|
+
* from" — but an author whose app is one page and six queries would reasonably reverse them. Both
|
|
297
|
+
* lines say what they cost, so the reading does not depend on the rank alone.
|
|
298
|
+
*
|
|
299
|
+
* ## What is *not* in the order
|
|
300
|
+
*
|
|
301
|
+
* Within a group, components stay in **emission order** — the order they appear in the report's
|
|
302
|
+
* own sections, so a reader can scan straight down from a step to its detail. Sorting them by
|
|
303
|
+
* refusal count was considered and rejected: it would break that correspondence to express a
|
|
304
|
+
* ranking that the counts printed beside them already express.
|
|
305
|
+
*
|
|
306
|
+
* Pure, and driven directly by the suite for the four shapes the corpus does not contain
|
|
307
|
+
* (a stubbed backend, project-wide notes, a clean export, and every group at once).
|
|
308
|
+
*/
|
|
309
|
+
export declare function nextSteps(data: ExportReportData): NextStep[];
|