@nodegx/export 0.0.0-stage → 0.2.6

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.
Files changed (53) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +76 -2
  3. package/dist/analyze/appState.d.ts +303 -0
  4. package/dist/analyze/plan.d.ts +3904 -0
  5. package/dist/analyze/querySettings.d.ts +75 -0
  6. package/dist/analyze/reach.d.ts +41 -0
  7. package/dist/analyze/scriptUses.d.ts +29 -0
  8. package/dist/catalog.d.ts +129 -0
  9. package/dist/cli/args.d.ts +100 -0
  10. package/dist/cli/exitCodes.d.ts +125 -0
  11. package/dist/cli/run.d.ts +30 -0
  12. package/dist/cli.inputs.json +203 -0
  13. package/dist/cli.mjs +62547 -0
  14. package/dist/cli.mjs.map +7 -0
  15. package/dist/emit/animateLib.d.ts +43 -0
  16. package/dist/emit/component.d.ts +166 -0
  17. package/dist/emit/dateLib.d.ts +45 -0
  18. package/dist/emit/emitApp.d.ts +38 -0
  19. package/dist/emit/idLib.d.ts +68 -0
  20. package/dist/emit/kits.d.ts +91 -0
  21. package/dist/emit/mediaLib.d.ts +75 -0
  22. package/dist/emit/naming.d.ts +76 -0
  23. package/dist/emit/parseLib.d.ts +108 -0
  24. package/dist/emit/preflight.d.ts +174 -0
  25. package/dist/emit/readme.d.ts +51 -0
  26. package/dist/emit/report.d.ts +309 -0
  27. package/dist/emit/scaffold.d.ts +83 -0
  28. package/dist/emit/statesLib.d.ts +52 -0
  29. package/dist/emit/style.d.ts +181 -0
  30. package/dist/emit/utilLib.d.ts +78 -0
  31. package/dist/errorMessage.d.ts +28 -0
  32. package/dist/index.cjs +61649 -0
  33. package/dist/index.cjs.map +7 -0
  34. package/dist/index.d.ts +37 -0
  35. package/dist/index.mjs +61570 -0
  36. package/dist/index.mjs.map +7 -0
  37. package/dist/ir/types.d.ts +567 -0
  38. package/dist/ledger.cjs +971 -0
  39. package/dist/ledger.cjs.map +7 -0
  40. package/dist/ledger.d.ts +75 -0
  41. package/dist/ledger.mjs +948 -0
  42. package/dist/ledger.mjs.map +7 -0
  43. package/dist/node-catalog.json +65370 -0
  44. package/dist/parse/kitSource.d.ts +135 -0
  45. package/dist/parse/parseModules.d.ts +42 -0
  46. package/dist/parse/parseProject.d.ts +13 -0
  47. package/dist/react19/VERSION +2 -0
  48. package/dist/react19/react-dom.production.min.js +51 -0
  49. package/dist/react19/react.production.min.js +21 -0
  50. package/dist/scriptFiles.d.ts +30 -0
  51. package/dist/structurePorts.d.ts +70 -0
  52. package/dist/write/writeExport.d.ts +67 -0
  53. package/package.json +53 -4
@@ -0,0 +1,75 @@
1
+ /**
2
+ * P18-S115-QUERYFILTER (EXP-011 §82). What a Query Records asks the backend for: its `where`, its `order`, its `limit`
3
+ * and its `skip`, as the running app sends them.
4
+ *
5
+ * Before this the exported query was `query('<Class>')` with no parameters, whatever the node's Filter, Sort and Limit
6
+ * said, so a filtered list held every record in the class, and nothing said so. The probe of EXP-008 never saw it: the
7
+ * catalog gives Query Records no static inputs, so it had nothing to vary.
8
+ *
9
+ * The filter is translated by the runtime's own translator, not a copy of it: the calls `QueryUtils.convertVisualFilter`
10
+ * makes (`visualQueryToNeutral` for the QueryEditor shape, `savedFilterToNeutral` with `dropUnresolvedConnected` for
11
+ * the builder's, then `toParseWhere` for the NodeGX backend), run here at export time. Every value this slice accepts
12
+ * is known when the project is exported, so the `where` is a constant: the one the runtime computes at its first fetch.
13
+ * The rest of the node's settings follow `dbcollectionnode2.ts` line for line (`getStorageFilter`, `getStorageLimit`,
14
+ * `getStorageSkip`, `convertVisualSorting`).
15
+ *
16
+ * P18-S115-QUERYPARAMS. A filter value read from a wired port is known only while the app runs, so its `where` is built
17
+ * there (`WiredWhere`): the same translator runs here once for every combination of those values being set or unset,
18
+ * with a marker standing for each set value, and the app picks the combination and puts each value where its marker
19
+ * sits. An unset value drops its condition, as `dropUnresolvedConnected` and the QueryEditor's own rule do. A marker
20
+ * must land whole under a plain comparison (`$eq`, `$ne`, `$lt`, `$gt`, `$lte`, `$gte`), where the translator passes
21
+ * the value through `parseValue` and nothing else; anywhere else (a lowered regex, an `$in` list, a relation) it is
22
+ * refused by name.
23
+ *
24
+ * What it cannot reproduce it refuses by name, before any request is made:
25
+ * - a wired filter value whose Run On Value Change box is unticked while nothing else runs the query (with Fetch wired,
26
+ * P18-S116-QUERYFETCH, the value waits for Fetch, which is exported; there the boxes must agree), that more than one
27
+ * wire feeds, that also holds a typed-in value, or that lands anywhere but a plain comparison; more than three of them;
28
+ * - the Javascript filter (a script the runtime runs on every fetch);
29
+ * - a Search term (the backend's full-text path, BAK-008);
30
+ * - a Filter, Sort, Use Limit, Limit or Skip input that is itself wired;
31
+ * - a relation filter whose class the runtime reads off a loaded record (`resolveRelatedClasses`), and a "points to"
32
+ * condition, whose target class the export's schema snapshot does not carry;
33
+ * - a filter the translator itself refuses: the runtime answers it with Failure and never queries (`setError`).
34
+ */
35
+ import type { CollectionIR, ComponentIR, NodeIR } from '../ir/types';
36
+ /** What the exported query sends beside `_method: 'GET'`; each key only where the runtime sends it. */
37
+ export interface QueryParamsPlan {
38
+ where?: unknown;
39
+ /** P18-S115-QUERYPARAMS. In place of `where` when the Filter reads a wired value: the `where` is built in the app. */
40
+ wired?: WiredWhere;
41
+ sort?: string[];
42
+ limit?: number;
43
+ skip?: number;
44
+ }
45
+ /** Where a wired value goes in a `where`: the parameter's name, and whether its column is a Date (`parseValue`'s fork). */
46
+ export interface WiredValueMark {
47
+ [WIRED_VALUE]: string;
48
+ date: boolean;
49
+ }
50
+ export declare const WIRED_VALUE = "@nodegx/wired-value";
51
+ /**
52
+ * P18-S115-QUERYPARAMS. The runtime translator's `where` for every combination of the wired values: `cases[mask]`, where
53
+ * bit `i` of `mask` is set when `params[i]` holds a value (not `undefined`). `undefined` is no `where` at all. Each value
54
+ * sits as a `WiredValueMark`.
55
+ */
56
+ export interface WiredWhere {
57
+ /** The wired parameters' names (the `qp-` port without its prefix), in the order the filter first reads them. */
58
+ params: string[];
59
+ cases: Array<Record<string, unknown> | undefined>;
60
+ }
61
+ /** Three wired values are eight translations; the corpus reads at most two. */
62
+ export declare const MAX_WIRED_PARAMS = 3;
63
+ export type QuerySettings = {
64
+ params: QueryParamsPlan;
65
+ } | {
66
+ refuse: string;
67
+ };
68
+ export declare function querySettingsOf(node: NodeIR, component: ComponentIR, collections: readonly CollectionIR[]): QuerySettings;
69
+ /** True for a `WiredValueMark`. */
70
+ export declare const isWiredValueMark: (value: unknown) => value is WiredValueMark;
71
+ /**
72
+ * One case's `where` as TypeScript: the translator's document printed as JSON (its `$eq` keys need the quotes), each
73
+ * value at its mark printed by `valueCode`.
74
+ */
75
+ export declare function wiredWhereCode(where: Record<string, unknown>, valueCode: (name: string, date: boolean) => string): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Which components a running app can actually reach (EXP-002-RECORD-VERBS-TARGET §20).
3
+ *
4
+ * The export emits every component in the project, and the coverage ledger counts every node in
5
+ * every one of them. That is the right thing to emit and the wrong thing to *report*, because a
6
+ * component no route can reach renders in no running app: its deferrals are not work the export
7
+ * owes anybody, and its translated nodes are not reach the export has earned.
8
+ *
9
+ * Measured over the 40-project corpus, session 31: **904 of 4,441 nodes (20.4%) sit in components
10
+ * nothing reaches, and they hold 432 of the 666 deferrals (64.9%)**. The largest single block is a
11
+ * downloaded filter kit copied into eight projects and never placed on a page — 79 nodes each,
12
+ * the whole of the "row identity" wall three handoffs in a row put at the top of the list.
13
+ *
14
+ * 🔴 A COMPONENT IS NAMED BY ITS LEGACY NAME, WHICH IS NOT ITS FOLDER. `components/__page__/Home`
15
+ * is named `/#__page__/Home`, and that is the form a Router's `routes` are written in.
16
+ * `parseProject` already carries it (`meta.path ?? meta.name`), so this module joins on
17
+ * `/${component.path}` and never on a path it reconstructs. The first draft of the instrument that
18
+ * found all this derived names from directories, and nine projects' routes read as unresolvable —
19
+ * a shipped routing defect that does not exist. `ProjectImporter.toLegacyName` is the authority
20
+ * and `lessonprojectcontext.ts:19-29` records the same trap being paid for once already.
21
+ *
22
+ * 🔴 THE WALK REFUSES TO ANSWER RATHER THAN GUESS. Two shapes make reachability unknowable, and
23
+ * both return `unreachable: []` with the reason named instead of a confident empty-app answer:
24
+ * a project with no resolvable route has no root to walk from, and a *reachable* For Each with
25
+ * `templateType: 'dynamic'` picks its row component from row data at runtime, so any component
26
+ * could be the one it instantiates. In this corpus every dynamic For Each sits inside the unplaced
27
+ * kit — it is not reachable, so it cannot rescue anything — but the guard is what makes the claim
28
+ * safe rather than lucky. [[assert-an-absence-with-a-known-firing-signal-beside-it]]
29
+ */
30
+ import { ExportIR } from '../ir/types';
31
+ export interface Reachability {
32
+ /**
33
+ * Legacy names ("/Filters/Range") no route reaches, in `ir.components` order (D1). Empty when
34
+ * every component is reachable *and* when no claim can be made — read `inconclusive` to tell
35
+ * those apart; they are opposite facts with an identical shape.
36
+ */
37
+ unreachable: string[];
38
+ /** Why the walk declined to answer, or null when it did. */
39
+ inconclusive: string | null;
40
+ }
41
+ export declare function componentReachability(ir: ExportIR): Reachability;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * P109 ISL-005 AC7 — can a Function's `uses` be carried into the export, and if not, why not.
3
+ *
4
+ * A Function that lists script files re-hosts as any other (EXP-003 §4), with one more name in its
5
+ * scope: `Scripts`, holding each listed file's `exports` (the runtime's `simplejavascript.ts`
6
+ * `scriptsFor`). The files themselves are emitted once, under `src/project-scripts/`, never pasted
7
+ * into the wrapper (ISL-005 §7: a copy at save time is not sharing).
8
+ *
9
+ * Each refusal below is one the runtime makes too, or one the re-host makes of a body — never a
10
+ * new rule. A Function this refuses is deferred by name, so the export's report says which file
11
+ * and why, instead of emitting a wrapper whose `Scripts` is unbound (the red this closed:
12
+ * `TS2304 Cannot find name 'Scripts'` on every page that used a file).
13
+ */
14
+ import { ScriptFileIR } from '../ir/types';
15
+ import { ScriptPath } from '../scriptFiles';
16
+ /** A listed file, by the name the body reaches it by (`Scripts.<alias>`). */
17
+ export interface ScriptUsePlan {
18
+ alias: string;
19
+ path: ScriptPath;
20
+ }
21
+ /**
22
+ * `{ uses }` — the files to bind, in the order listed (empty: the Function lists none).
23
+ * `{ defer }` — the reason, worded to follow "the script " in the report.
24
+ */
25
+ export declare function scriptUsesPlan(usesValue: unknown, files: ScriptFileIR[] | undefined): {
26
+ uses: ScriptUsePlan[];
27
+ } | {
28
+ defer: string;
29
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * A minimal typed view over packages/noodl-types/src/node-catalog.json — only the fields the
3
+ * export pipeline reads. The catalog artifact is the authority; this file must never restate
4
+ * catalog *content* (the Rise lesson from EXP-007: every value is read from the thing it
5
+ * describes).
6
+ */
7
+ export interface CatalogPortType {
8
+ name?: string;
9
+ codeeditor?: string;
10
+ /** EXP-015. Present on `name: 'enum'` ports — the editor's own dropdown, read by `enumValues`. */
11
+ enums?: Array<{
12
+ label?: string;
13
+ value: unknown;
14
+ }>;
15
+ }
16
+ export interface CatalogPort {
17
+ name: string;
18
+ plug?: 'input' | 'output';
19
+ isSignal?: boolean;
20
+ type?: string | CatalogPortType;
21
+ default?: unknown;
22
+ }
23
+ export interface CatalogNode {
24
+ typeName: string;
25
+ displayName?: string;
26
+ category?: string;
27
+ isVisual?: boolean;
28
+ /** null for fully-static port sets; otherwise describes how the real set is determined. */
29
+ dynamicPorts?: {
30
+ mechanisms?: string[];
31
+ } | null;
32
+ inputs?: CatalogPort[];
33
+ outputs?: CatalogPort[];
34
+ }
35
+ export interface Catalog {
36
+ catalogFormatVersion: string;
37
+ nodes: CatalogNode[];
38
+ }
39
+ /**
40
+ * Nodes whose output port set only execution reveals (outputs are discovered through a proxy at
41
+ * runtime). These parse to portKnowledge 'unknown' and are EXP-003's territory. Expression is
42
+ * deliberately not here: its dynamic ports are *inputs*, statically derivable from the
43
+ * expression's free variables, and its outputs are static.
44
+ */
45
+ export declare const EXECUTION_REVEALED_TYPES: ReadonlySet<string>;
46
+ export declare class CatalogIndex {
47
+ readonly catalogFormatVersion: string;
48
+ private readonly byTypeName;
49
+ constructor(catalog: Catalog);
50
+ get(typeName: string): CatalogNode | undefined;
51
+ isVisual(typeName: string): boolean;
52
+ /**
53
+ * The catalog-declared default of an input port, or undefined when the catalog does not know
54
+ * one. The interpreter falls back to these same defaults, so style extraction reads them here
55
+ * rather than restating catalog content in code (the Rise lesson).
56
+ */
57
+ inputDefault(typeName: string, portName: string): unknown;
58
+ /**
59
+ * EXP-015. The values an enum-typed input port offers, in catalog order, or `undefined` where the
60
+ * catalog does not describe the port as an enum.
61
+ *
62
+ * 🔴 **This exists so that nothing hand-writes a second copy of an enum.** The `as` (Tag) port
63
+ * offers a different element list on `Text` than on `Group`, both lists are the editor's own
64
+ * dropdown, and a hand-written mirror of either drifts the moment somebody adds `<figure>` — the
65
+ * `a-second-copy-of-a-palette-drifts-silently` shape. The catalog artifact is the authority.
66
+ */
67
+ enumValues(typeName: string, portName: string): string[] | undefined;
68
+ /**
69
+ * STY-004. The declared type name of an input port (`color`, `textStyle`, `string`, …), or
70
+ * undefined where the catalog does not know the port.
71
+ *
72
+ * 🔴 **This exists so colour-style resolution asks rather than guesses.** A project colour style
73
+ * is referenced by bare name (`resolveColor(v) = colors[v] ?? v`, `noodl-viewer-react/src/
74
+ * styles.ts:122`), so a resolver with no port types would have to try every string parameter
75
+ * against the colour dictionary — and would rewrite a `fontFamily` or a `cssClassName` that
76
+ * happened to share a style's name. The catalog declares 118 colour ports and 12 textStyle
77
+ * ports; asking it costs one lookup and cannot collide.
78
+ */
79
+ inputTypeName(typeName: string, portName: string): string | undefined;
80
+ /**
81
+ * The kind of a catalog-declared port, by name. Returns undefined when the catalog does not
82
+ * know the port (dynamic ports, unknown types) — callers decide what that means; this function
83
+ * never guesses.
84
+ */
85
+ portKind(typeName: string, portName: string, plug: 'input' | 'output'): 'value' | 'signal' | undefined;
86
+ }
87
+ export declare function isSignalPort(port: {
88
+ isSignal?: boolean;
89
+ type?: string | CatalogPortType;
90
+ }): boolean;
91
+ export declare function portTypeName(type: string | CatalogPortType | undefined): string | undefined;
92
+ export declare function isCodeEditorType(type: string | CatalogPortType | undefined): boolean;
93
+ /**
94
+ * A codeeditor port whose language is **JavaScript**.
95
+ *
96
+ * 🔴 `isCodeEditorType` is not this question, and the gap between them is most of the catalog.
97
+ * Of the 36 node types carrying a codeeditor input, 29 carry `styleCss` (`codeeditor: 'text'`),
98
+ * one is CSS Definition's `style` (`'css'`), and Static Data's `json`/`csv` are data. Exactly
99
+ * seven ports are JavaScript — Function's `functionScript`, Expression's `expression`,
100
+ * Map Collection's `mapScript`, For Each's `templateScript`, Script's `code` and REST's two.
101
+ * The catalog names the language, so this set is *derived* rather than listed here (EXP-007's
102
+ * rule: never restate catalog content).
103
+ *
104
+ * This is the predicate `NodeIR.sourceText` needs: its contract promises author-written **code**,
105
+ * and selecting on `isCodeEditorType` fills it with a Text node's CSS instead.
106
+ */
107
+ export declare function isJavaScriptCodeEditorType(type: string | CatalogPortType | undefined): boolean;
108
+ /**
109
+ * The one place the node catalog is read from disk — HLS-001 AC4.
110
+ *
111
+ * Before this, every caller computed its own path to `packages/noodl-types/src/node-catalog.json`:
112
+ * two production call sites and twenty-five test files, each with its own `path.join(__dirname,
113
+ * '..', '..', ...)`. That is fine while everything lives in one checkout and impossible the moment
114
+ * the package is installed from a registry, because the catalog is in a different package that a
115
+ * consumer does not have.
116
+ *
117
+ * So the catalog now **travels with the package**: `build.mjs` copies it into `dist/` at build
118
+ * time. It is still authored in exactly one place (`@noodl/types`) and never edited here — a copy
119
+ * that is regenerated by the build cannot drift, which a copy that is maintained by hand would.
120
+ *
121
+ * ⚠️ Two locations are tried, and this is the honest reason: running from `src/` in this repo there
122
+ * is no `dist/`, and running from an installed package there is no `@noodl/types`. Both are real
123
+ * and neither is a fallback for a mistake. If both miss, the error names both, because a catalog
124
+ * that silently resolved to nothing would make every node in the project look unknown rather than
125
+ * make the export fail.
126
+ */
127
+ export declare function catalogPath(): string;
128
+ /** The catalog, parsed. Every caller in this package and its consumers goes through here. */
129
+ export declare function loadCatalog(): Catalog;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * HLS-002 — argument parsing, separated from the command so that the parse is a function of a
3
+ * string array and nothing else.
4
+ *
5
+ * `scripts/emit-app.ts` decided its mode with `argv[0] === '--preflight'` and destructured the
6
+ * rest positionally. That is fine for a script one person runs by hand and wrong for a binary:
7
+ * `nodegx export --dry-run ./app` and `nodegx export ./app --dry-run` are the same request, and a
8
+ * positional read gets the second one wrong silently — it would treat `--dry-run` as the output
9
+ * directory and write an app into a folder called `--dry-run`.
10
+ */
11
+ /** What the arguments asked for. Exactly one shape per outcome; no partially-filled results. */
12
+ export type ParsedArgs = {
13
+ kind: 'help';
14
+ } | {
15
+ kind: 'version';
16
+ } | {
17
+ kind: 'usage';
18
+ problem: string;
19
+ } | {
20
+ kind: 'export';
21
+ projectDir: string;
22
+ /** `null` in `--dry-run` mode, where there is no output folder by design. */
23
+ outDir: string | null;
24
+ dryRun: boolean;
25
+ /** Write into a folder that already holds something. */
26
+ force: boolean;
27
+ /** EXP-022. What the export writes: React source (the default), or a SwiftUI package with an Xcode app around it. */
28
+ target: ExportTarget;
29
+ /**
30
+ * HTX-006. One component and what it needs, into a folder another page's own code shares (`--target html` only):
31
+ * its name as the project names it, or `null` for the whole app.
32
+ */
33
+ component: string | null;
34
+ } | {
35
+ kind: 'serve';
36
+ /** A folder of built files. Not a project — see `serve/staticServer.ts`. */
37
+ dir: string;
38
+ port: number;
39
+ /**
40
+ * 🔴 An explicit decision to leave loopback. `--host` sets this as well as the address,
41
+ * because somebody who types an address has decided; `resolveAccess` refuses to infer the
42
+ * same thing from a `host` that arrived any other way, which is the asymmetry that keeps a
43
+ * forwarded option from re-opening the port by accident.
44
+ */
45
+ share: boolean;
46
+ host: string | null;
47
+ /** `null` mints one. A caller supplies it so a pipeline can know it before the server runs. */
48
+ token: string | null;
49
+ } | {
50
+ kind: 'deploy';
51
+ projectDir: string;
52
+ /** A folder to write the site into. Never optional — a deploy with nowhere to go is nothing. */
53
+ outDir: string;
54
+ /** Write into a folder that already holds something. */
55
+ force: boolean;
56
+ /**
57
+ * The path the site will be served from, spliced into `index.html`'s `<base>`. `null` means
58
+ * `/`, which is right for a site at the root of a domain and wrong for one in a subfolder —
59
+ * where every asset URL comes out absolute and 404s. The editor's dialog asks for it; this is
60
+ * the same question with nobody to ask.
61
+ */
62
+ baseUrl: string | null;
63
+ /**
64
+ * EXP-017 — publish a DEVELOPMENT build of the NodeGX viewer knowingly.
65
+ *
66
+ * Without it a development engine is refused (exit 11) before anything is written. The flag
67
+ * is spelled out rather than shortened because its whole job is to be read: what it permits
68
+ * is uploading the viewer's own source, as a 9.88 MB inline source map, to a public host.
69
+ */
70
+ allowDevelopmentEngine: boolean;
71
+ } | {
72
+ kind: 'live';
73
+ /** A full URL — the site as somebody would open it, redirects and all. */
74
+ url: string;
75
+ /**
76
+ * A deploy folder to compare what is live against. `null` asks only "what is live", which is
77
+ * the half of the question that has no local half and is answerable with no local anything.
78
+ */
79
+ against: string | null;
80
+ } | {
81
+ kind: 'render';
82
+ projectDir: string;
83
+ /** `null` grades without keeping anything — a CI gate that wants no artefacts. */
84
+ outDir: string | null;
85
+ /**
86
+ * Passed to the harness verbatim, deliberately. The vocabulary (`desktop,phone`,
87
+ * `1280x900`) belongs to `parseViewports` in the harness, and restating it here would be a
88
+ * second copy of a list that already has one reader — with the CLI's copy going stale the
89
+ * first time a viewport is named.
90
+ */
91
+ viewports: string | null;
92
+ scale: number | null;
93
+ };
94
+ /** EXP-022, P134. The export's targets: one IR, one plan, three printers. */
95
+ export declare const EXPORT_TARGETS: readonly ["react", "swift", "html"];
96
+ export type ExportTarget = (typeof EXPORT_TARGETS)[number];
97
+ export declare function parseArgs(argv: readonly string[]): ParsedArgs;
98
+ /** EXP-017 — named here and in `noodl-preview/src/deploy.ts`; `args.test.ts` asserts they match. */
99
+ export declare const ALLOW_DEV_ENGINE_FLAG = "--allow-development-engine";
100
+ export declare const USAGE = "nodegx \u2014 the NodeGX command line\n\n nodegx export <project> <output> Export a project as a React app\n nodegx export --target swift <project> <output>\n Export it as a SwiftUI app (a Swift package and an Xcode project)\n nodegx export --target html <project> <output>\n Export it as plain HTML, CSS and JavaScript: no npm, no build step\n nodegx export --target html --component <name> <project> <output>\n Export one component and what it needs, to mount in a page of your own\n nodegx export --dry-run <project> Print what the export would produce, and write nothing\n nodegx deploy <project> <output> Write a ready-to-host site you can upload as it is\n nodegx serve <folder> Serve a built site over HTTP, on this machine only\n nodegx render <project> Render every routed page and say which ones did not\n nodegx live <url> Read a site that is being served and say which build it is\n\nexport or deploy?\n export writes React SOURCE. You run npm install and npm run build, and you own the code.\n deploy writes a FINISHED SITE \u2014 index.html, your app, and the NodeGX runtime that runs it.\n Upload the folder and it works. Nothing to build, and no source to edit.\n\nOptions\n --dry-run, --preflight Print the pre-flight summary and exit without writing anything.\n Exits 4 if anything would be left out, so a pipeline can gate on it.\n --target <react|swift|html>\n export: React source (the default), a SwiftUI package with an Xcode app around it,\n or plain files any static server serves as they are.\n --component <name> export --target html: one component (by its name or path in the project) and what\n it places, into a folder your own page shares. No index.html, no router. It lists\n what it wrote in nodegx-export.json: a re-export replaces and removes only those\n files, and refuses, writing nothing, if one of them was changed by hand.\n --out-dir <folder> export: the output folder, given by name instead of second.\n --force Write into an output folder that already holds something. Files with\n the same names are overwritten; nothing else is touched.\n --base-url <path> deploy: the path the site is served from, when it is not the root of a\n domain (`--base-url /app/`). Without it every asset URL is absolute.\n --allow-development-engine\n deploy: publish a DEVELOPMENT build of the NodeGX viewer. That build\n carries a ~9.9 MB inline source map \u2014 the viewer's own source \u2014 which\n a deploy copies verbatim onto your host. Without this flag such a\n build is refused (exit 11) and nothing is written.\n --port <n> serve: the port to listen on (default 8575).\n --share serve: also accept connections from other machines on this network.\n Prints a URL containing a token; without the token nothing is served.\n --host <addr> serve: the address to bind when sharing. Implies --share.\n --token <t> serve: use this token instead of minting one, so a pipeline can know\n it in advance.\n --against <folder> live: the deploy folder to compare the served site with. Without it\n the command reports what is live and compares it with nothing.\n --out-dir <dir> render: keep the images, one per page per viewport, as\n <page>-<viewport>.png. Without it nothing is written and nothing is\n captured \u2014 the run still grades every page.\n --viewports <list> render: \"desktop,phone\" or \"1280x900,390x844\" (default desktop,phone).\n --scale <n> render: screenshot scale (default 0.5).\n --help, -h This.\n --version, -v The exporter version.\n\nExit codes\n 0 the export ran (or --dry-run found nothing left out)\n 1 the arguments do not name a command\n 2 the project could not be read\n 3 the output folder was refused\n 4 --dry-run: something will not translate\n 5 a write failed part-way\n 6 serve: the folder is not a built site, or the port could not be bound\n 7 render: a routed page did not render \u2014 it is named on stderr\n 8 render/deploy: this installation has no render harness or deploy engine (see below)\n 9 deploy: the site that was written would render nothing \u2014 it is not fit to upload\n 10 live: the served site is not the build you compared it against, or is not a NodeGX site\n 11 deploy: the NodeGX viewer build here is a DEVELOPMENT build \u2014 nothing was written\n\n\u26A0\uFE0F `nodegx serve` listens on 127.0.0.1 unless you pass --share or --host. That is a decision, not\na default that something else can change: nothing but those two flags reaches another interface.\n\n\u26A0\uFE0F `nodegx deploy` needs the NodeGX deploy engine \u2014 the editor's model engine and the ~15 MB\nNodeGX interpreter \u2014 which is NOT part of this package. It works from a NodeGX checkout with the\nengine built, or set NODEGX_DEPLOY_CLI to one. `nodegx export` needs none of it.\n\n\u26A0\uFE0F `nodegx deploy` copies whichever NodeGX viewer build is on disk, and every run says which one\nit picked up. On a checkout where `npm run dev` has run, that is the DEVELOPMENT build: 14 MB, of\nwhich ~9.9 MB is an inline source map of the viewer's source. A deploy copies it verbatim, so\nuploading the folder publishes that source. Such a build is refused before anything is written;\n`npm run build:editor:_viewer` replaces it with the production one.\n\n\u26A0\uFE0F `nodegx deploy` publishes your project folder as well as your app. Everything beside\nnodegx.project.json ships unless a default rule or your .noodlignore excludes it \u2014 the run says\nwhat it left out, and reading that line is how \".env was excluded\" and \"my logo went missing\" stay\ndifferent things.\n\n\u26A0\uFE0F `nodegx deploy` into a folder it deployed to before is an UPDATE: it rewrites the site and\nremoves the files its own previous deploy left behind, and touches nothing else in the folder. A\nfolder holding anything else is refused unless you pass --force, and with --force nothing is swept\n\u2014 this command will not delete from a folder it has no record of writing.\n\n\u26A0\uFE0F `nodegx live` is the only reading here taken from a machine that is not this one. Everything a\ndeploy knows, it learned from a folder on this disk; between that folder and the app somebody loads\nthere is an upload, a cache and a host. It follows redirects and says so when it did.\n\n\u26A0\uFE0F `nodegx render` needs the NodeGX render harness \u2014 a browser and the viewer bundle \u2014 which is\nNOT part of this package. It works from a NodeGX checkout, or set NODEGX_RENDER_CLI to one. Every\nother command works without it.\n\n\u26A0\uFE0F `nodegx render` photographs URLS, not components. A route that redirects \u2014 an admin page that\nsends a logged-out visitor to a login page \u2014 is photographed as what that URL renders, which is the\nlogin page. It is a true picture of the route and not of the component you named.\n\n\u26A0\uFE0F `nodegx render` proves pixels, not reachability. It clicks nothing, so a page whose only button\nsits under an invisible overlay photographs perfectly and exits 0.\n\n\u26A0\uFE0F This reads the project from disk. If the editor has it open with unsaved changes, save first.";
@@ -0,0 +1,125 @@
1
+ /**
2
+ * HLS-002 — the exit codes `nodegx` answers with, and the reason they are a table rather than
3
+ * `0` and `1`.
4
+ *
5
+ * 🔴 **This whole phase is about removing the human from the loop.** In a terminal, a wrong exit
6
+ * code is a cosmetic complaint: the person reads the sentence above it. In a GitHub Action there
7
+ * is no person, the sentence scrolls past in a log nobody opens, and the only thing the pipeline
8
+ * can branch on is this number. A CLI that returns `1` for "you pointed me at a folder inside the
9
+ * project" and `1` for "this project is in the legacy format" has told the pipeline nothing.
10
+ *
11
+ * Each code below is produced by exactly one cause and is asserted against that cause in
12
+ * `tests/hls002-cli.test.ts` — a spec per row, provoked for real, never by inspecting a branch.
13
+ */
14
+ export declare const EXIT: {
15
+ /** The export ran, or `--dry-run` found nothing that will not translate. */
16
+ readonly ok: 0;
17
+ /** The arguments do not name a command this binary has. Nothing was read. */
18
+ readonly usage: 1;
19
+ /**
20
+ * The project could not be read: the folder is not there, it holds a legacy `project.json`
21
+ * rather than `nodegx.project.json`, or parsing threw. Distinct from {@link EXIT.target}
22
+ * because "your project is the wrong format" and "your output folder is the wrong place" are
23
+ * different things to fix and a pipeline should be able to say which happened.
24
+ */
25
+ readonly project: 2;
26
+ /** The output folder was refused — inside the project, a file, or not empty without `--force`. */
27
+ readonly target: 3;
28
+ /**
29
+ * `--dry-run` only: the export would leave something out. Not an error — the pre-flight ran and
30
+ * printed — but a check mode that always exits 0 is not a check, and the thing a pipeline wants
31
+ * to gate on is precisely "did anything fail to translate this time".
32
+ *
33
+ * ⚠️ A real export with refusals still exits {@link EXIT.ok}: it succeeded, and the refusals are
34
+ * in `EXPORT-REPORT.md` where the author reads them. Failing the build on them is `--dry-run`'s
35
+ * job, deliberately, so that a project with a known deferral can still ship.
36
+ */
37
+ readonly refusals: 4;
38
+ /**
39
+ * A write failed part-way. The folder now holds part of an app, which builds nothing — so this
40
+ * is louder than a failure before any write, and says so.
41
+ */
42
+ readonly write: 5;
43
+ /**
44
+ * HLS-006 — `serve` could not start: the folder is not a built site, or the port is taken.
45
+ * HLS-014 widened it to `live`, where the same sentence — *nothing is answering* — is a URL that
46
+ * refused the connection, timed out, or came back non-200. It
47
+ * is its own code rather than {@link EXIT.target} because a pipeline that gets this one has a
48
+ * *running* problem (a port already in use, most often) rather than a wrong argument, and the
49
+ * two want different retries.
50
+ */
51
+ readonly serve: 6;
52
+ /**
53
+ * HLS-007 — `render` only: a routed page did not render. The router registers it and the sweep
54
+ * has no row for it, or it was reached and came back blank, or its measurement errored, or the
55
+ * image that was asked for was never written.
56
+ *
57
+ * 🔴 Its own code, and not {@link EXIT.refusals}, because the two mean opposite things to a
58
+ * pipeline. A refusal is *the exporter knows it cannot translate this and said so*; this is
59
+ * *the app was built, the page is routed, and there is nothing on it* — which nothing upstream
60
+ * of the browser can see. The harness itself exits 0 whatever it finds (a finding is
61
+ * `render_report`'s product, not a crash), so a pipeline gating on the harness's status gates on
62
+ * nothing at all. This code is the whole reason the command exists rather than a shell alias.
63
+ */
64
+ readonly render: 7;
65
+ /**
66
+ * `render` and `deploy`: there is no harness or engine here. Distinct from every code above
67
+ * because it is a statement about **this installation**, not about the project or the arguments:
68
+ * nothing the caller changes about either will fix it, and a pipeline that retries is wasting
69
+ * its time. See `renderHarness.ts` and `deployEngine.ts` for why the published package carries
70
+ * neither.
71
+ *
72
+ * 🔴 HLS-015 widened this from "render only" to both commands rather than minting a ninth code
73
+ * for the same sentence. The two refusals differ in what is missing and are identical in what a
74
+ * pipeline should do about it, and an exit code exists to be branched on — a second number
75
+ * meaning "stop retrying, this box cannot do that" would be a distinction with no consumer.
76
+ */
77
+ readonly harness: 8;
78
+ /**
79
+ * HLS-015 — `deploy` only: the folder that was written would render nothing. Not one deployed
80
+ * component carries a root node, or the component the app starts at does not.
81
+ *
82
+ * 🔴 Its own code, and the reason it exists is register row **C67**: this failure is invisible to
83
+ * every reading upstream of the browser. The deploy resolves, writes all eight files, reports
84
+ * the same copy count as a good run, and keeps **93 of 93 connections** — the field that empties
85
+ * is `roots`. A pipeline that gates on `deployToFolder` resolving gates on nothing, and the
86
+ * person gets a blank page under a success message. Distinct from {@link EXIT.render} because
87
+ * that one is measured by a browser and this one is measured by reading the artefact, so they
88
+ * are available at different moments and a pipeline may well have only one of them.
89
+ */
90
+ readonly deploy: 9;
91
+ /**
92
+ * HLS-014 — `live` only: the URL answered, and what it is serving is not the build it was
93
+ * compared against (or is not a NodeGX deploy at all, or names an export the server does not
94
+ * hold).
95
+ *
96
+ * 🔴 Its own code because it is the only one in this table that is a statement about **a
97
+ * machine that is not this one**. Every other failure here is fixed by changing an argument, a
98
+ * project or an installation; this one is fixed by uploading, or by waiting for a cache, or by
99
+ * discovering that the deploy went somewhere else — and a pipeline that treats it like
100
+ * {@link EXIT.deploy} would refuse to ship a build that is perfectly good.
101
+ *
102
+ * ⚠️ Distinct from {@link EXIT.serve}, which stays "nothing answered". A 404 from the host and a
103
+ * 200 serving last week's app are opposite situations: one is a deploy that has not landed, the
104
+ * other is a deploy that has landed somewhere nobody is looking.
105
+ */
106
+ readonly stale: 10;
107
+ /**
108
+ * EXP-017 — `deploy` only: the NodeGX viewer build this installation would publish is a
109
+ * **development** build, and nothing was written.
110
+ *
111
+ * 🔴 Its own code, and not {@link EXIT.harness}, because the two are opposite statements about
112
+ * the same installation. `harness` means *there is no engine here, stop retrying* — nothing the
113
+ * caller does to the project or the arguments will change it. This one means *the engine is
114
+ * here and it is the wrong build of itself*, and it is fixed by one command
115
+ * (`npm run build:editor:_viewer`) that a pipeline can be taught to run. A pipeline that read
116
+ * this as 8 would give up on a box that is one build away from deploying.
117
+ *
118
+ * 🔴 And not {@link EXIT.deploy}, which is *the site that WAS written renders nothing*. This is
119
+ * a refusal taken before any byte is written, about a site that would have rendered perfectly —
120
+ * and carried 9.88 MB of the NodeGX viewer's own source to a public host. The two differ in
121
+ * what is on disk afterwards, which is the one thing a pipeline's next step depends on.
122
+ */
123
+ readonly engine: 11;
124
+ };
125
+ export type ExitCode = (typeof EXIT)[keyof typeof EXIT];
@@ -0,0 +1,30 @@
1
+ import { ExitCode } from './exitCodes';
2
+ /** Where the command's words go. Injected so a spec can read them without a subprocess. */
3
+ export interface CliIO {
4
+ out(text: string): void;
5
+ err(text: string): void;
6
+ }
7
+ export declare const PROJECT_FILE = "nodegx.project.json";
8
+ /**
9
+ * The newest modification time among the files the export actually reads, or `null` if the walk
10
+ * found nothing. Deliberately the *read set* — the project file and the component tree — rather
11
+ * than the whole folder: a `node_modules` restore or a git checkout of a README would otherwise
12
+ * report a save that never happened.
13
+ */
14
+ export declare function lastWritten(projectDir: string): Date | null;
15
+ /**
16
+ * Whether this folder is a project this exporter can read, phrased as the sentence the person
17
+ * needs rather than as the exception `parseProject` would throw (`ENOENT: no such file or
18
+ * directory, open '…/nodegx.project.json'`, which names a file they have never heard of).
19
+ */
20
+ export declare function readableProject(projectDir: string): {
21
+ ok: true;
22
+ } | {
23
+ ok: false;
24
+ reason: string;
25
+ };
26
+ /**
27
+ * Runs one invocation and returns its exit code. Never throws for a cause the person can fix:
28
+ * every refusal is a sentence and a code.
29
+ */
30
+ export declare function runCli(argv: readonly string[], io: CliIO): ExitCode;