@reactra/vite-plugin 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/esbuild-prescan.d.ts +31 -0
- package/dist/esbuild-prescan.d.ts.map +1 -0
- package/dist/esbuild-prescan.js +75 -0
- package/dist/esbuild-prescan.js.map +1 -0
- package/dist/extract.d.ts +128 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +155 -0
- package/dist/extract.js.map +1 -0
- package/dist/fingerprint.d.ts +28 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +68 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/graph-cache.d.ts +23 -0
- package/dist/graph-cache.d.ts.map +1 -0
- package/dist/graph-cache.js +52 -0
- package/dist/graph-cache.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +379 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +57 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +147 -0
- package/dist/manifest.js.map +1 -0
- package/dist/resource-names.d.ts +49 -0
- package/dist/resource-names.d.ts.map +1 -0
- package/dist/resource-names.js +128 -0
- package/dist/resource-names.js.map +1 -0
- package/dist/sidecar-writer.d.ts +60 -0
- package/dist/sidecar-writer.d.ts.map +1 -0
- package/dist/sidecar-writer.js +114 -0
- package/dist/sidecar-writer.js.map +1 -0
- package/dist/walker.d.ts +332 -0
- package/dist/walker.d.ts.map +1 -0
- package/dist/walker.js +1070 -0
- package/dist/walker.js.map +1 -0
- package/package.json +39 -0
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// @reactra/vite-plugin — manifest regeneration
|
|
2
|
+
//
|
|
3
|
+
// Owner: reactra-compiler-spec.md §7.1 (vite plugin file-watcher hook) +
|
|
4
|
+
// reactra-router-spec.md §5.1 (manifest format)
|
|
5
|
+
//
|
|
6
|
+
// Phase 1 #5b: a thin wrapper around the walker that
|
|
7
|
+
// (1) scans `<projectRoot>/src/pages/`,
|
|
8
|
+
// (2) renders the manifest text via `emitRouteManifest`, and
|
|
9
|
+
// (3) writes it to `<projectRoot>/src/routeManifest.generated.ts` IFF
|
|
10
|
+
// the rendered text differs from the current on-disk content.
|
|
11
|
+
//
|
|
12
|
+
// The change check matters: writing on every watcher fire would cascade
|
|
13
|
+
// into an HMR full-reload even when the page set didn't actually change.
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { createGraphCache } from "./graph-cache.js";
|
|
17
|
+
import { chainsForRoutes, collectServiceInjectionDiagnostics, emitRouteManifest, emitRouterMiddleware, scanMiddleware, scanPages, scanServices, scanStores, } from "./walker.js";
|
|
18
|
+
/**
|
|
19
|
+
* The on-disk location of the generated manifest, relative to the project
|
|
20
|
+
* root. Exported for tests + so the vite plugin can `import` from a stable
|
|
21
|
+
* path in the demo / app config.
|
|
22
|
+
*/
|
|
23
|
+
export const MANIFEST_RELATIVE_PATH = "src/routeManifest.generated.ts";
|
|
24
|
+
/**
|
|
25
|
+
* The on-disk location of the generated middleware chains module (Wave 3,
|
|
26
|
+
* Stage 2). Sits next to the route manifest in `src/` so importers can
|
|
27
|
+
* pull `import { middlewareChains } from "./router-middleware.generated.ts"`
|
|
28
|
+
* with the same relative-path convention. Spec §6.4 names this file.
|
|
29
|
+
*/
|
|
30
|
+
export const ROUTER_MIDDLEWARE_RELATIVE_PATH = "src/router-middleware.generated.ts";
|
|
31
|
+
/**
|
|
32
|
+
* Scan + emit + write-if-changed. Idempotent — calling twice with no
|
|
33
|
+
* page-tree change is a no-op for the filesystem.
|
|
34
|
+
*
|
|
35
|
+
* The walker tolerates a missing `src/pages/` directory (returns []); we
|
|
36
|
+
* still write the empty manifest so importers don't ImportError before
|
|
37
|
+
* any pages exist. Mirrors the placeholder a fresh-clone commits.
|
|
38
|
+
*/
|
|
39
|
+
export const regenerateRouteManifest = (projectRoot, cache = createGraphCache()) => {
|
|
40
|
+
// A3: ONE graph cache per regen — every container-reading scanner below
|
|
41
|
+
// (scanPages/scanStores/scanServices/collectServiceInjectionDiagnostics)
|
|
42
|
+
// shares it, so each src/ file compiles at most once per regen instead of
|
|
43
|
+
// 4–5×. scanMiddleware stays OFF the cache (it reads plain-TS via lex()).
|
|
44
|
+
// The watcher passes its own per-event cache so resource-names shares it too.
|
|
45
|
+
//
|
|
46
|
+
// B6: scanPages is wrapped to add a legible context frame, but it RETHROWS
|
|
47
|
+
// (unlike scanStores/scanServices, which degrade to []). Routes are the
|
|
48
|
+
// PRIMARY manifest output — degrading to empty would silently blank the app.
|
|
49
|
+
// The missing-`src/pages/` case already returns [] from INSIDE the walker
|
|
50
|
+
// (the top-level readdirSync try/catch), so only genuine fs/parse errors
|
|
51
|
+
// reach this catch.
|
|
52
|
+
let routes;
|
|
53
|
+
try {
|
|
54
|
+
routes = scanPages(projectRoot, cache);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Routes are the PRIMARY manifest output — unlike scanStores/scanServices
|
|
58
|
+
// (which degrade to []), empty routes = a silently-blank app. Fail loud,
|
|
59
|
+
// but add a legible context frame + spec ref so the raw error isn't alone.
|
|
60
|
+
throw new Error(`[@reactra/vite-plugin] scanPages failed for "${projectRoot}/src/pages" — ` +
|
|
61
|
+
`the route manifest cannot be regenerated. Routes are the primary manifest ` +
|
|
62
|
+
`output and are NOT degraded to empty (that would silently blank the app). ` +
|
|
63
|
+
`Fix the page tree (fs access or a malformed page component) and save again. ` +
|
|
64
|
+
`(reactra-router-spec.md §5.1)`, { cause: err });
|
|
65
|
+
}
|
|
66
|
+
// Day 20 / `#5c`: defensive try/catch around scanStores — it walks
|
|
67
|
+
// src/ and reads each candidate file. fs errors (permission, etc.)
|
|
68
|
+
// shouldn't crash the manifest regen; emit STORES as empty on
|
|
69
|
+
// failure and let the next regen recover.
|
|
70
|
+
let stores = [];
|
|
71
|
+
try {
|
|
72
|
+
stores = scanStores(projectRoot, cache);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error("[@reactra/vite-plugin] scanStores failed (STORES omitted from manifest):", err);
|
|
76
|
+
}
|
|
77
|
+
// Phase 3: defensive try/catch around scanServices — same pattern as scanStores.
|
|
78
|
+
let services = [];
|
|
79
|
+
try {
|
|
80
|
+
services = scanServices(projectRoot, cache);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error("[@reactra/vite-plugin] scanServices failed (SERVICES omitted from manifest):", err);
|
|
84
|
+
}
|
|
85
|
+
// Phase 3: service injection diagnostics — warn-only, no error code minted yet
|
|
86
|
+
// (deferred to Phase 6). Run after scanServices so we know the declared set.
|
|
87
|
+
try {
|
|
88
|
+
const diags = collectServiceInjectionDiagnostics(projectRoot, cache);
|
|
89
|
+
for (const d of diags) {
|
|
90
|
+
console.warn(`[@reactra/vite-plugin] inject service "${d.service}" in ${d.file} references an undeclared service — ` +
|
|
91
|
+
`add \`export service ${d.service} { ... }\` under src/ or list it in configureServices({ services }). ` +
|
|
92
|
+
`(reactra-service-spec.md §2)`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Diagnostic errors must never crash the manifest regen — swallow silently.
|
|
97
|
+
}
|
|
98
|
+
// Wave 3 / Stage 2 — same defensive pattern for the middleware walker.
|
|
99
|
+
let middleware = [];
|
|
100
|
+
try {
|
|
101
|
+
middleware = scanMiddleware(projectRoot);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error("[@reactra/vite-plugin] scanMiddleware failed (router-middleware.generated.ts emitted empty):", err);
|
|
105
|
+
}
|
|
106
|
+
const content = emitRouteManifest(routes, stores, services);
|
|
107
|
+
const path = join(projectRoot, MANIFEST_RELATIVE_PATH);
|
|
108
|
+
let prior = "";
|
|
109
|
+
if (existsSync(path)) {
|
|
110
|
+
try {
|
|
111
|
+
prior = readFileSync(path, "utf8");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// unreadable file — treat as missing so we overwrite cleanly.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
let wrote = false;
|
|
118
|
+
if (prior !== content) {
|
|
119
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
120
|
+
writeFileSync(path, content, "utf8");
|
|
121
|
+
wrote = true;
|
|
122
|
+
}
|
|
123
|
+
// Companion `router-middleware.generated.ts` — same change-check
|
|
124
|
+
// semantics so an unrelated touch doesn't churn HMR. Always emitted
|
|
125
|
+
// (even when empty) so importers don't break before any
|
|
126
|
+
// `_middleware.ts` exists.
|
|
127
|
+
const chains = chainsForRoutes(routes, middleware);
|
|
128
|
+
const mwContent = emitRouterMiddleware(middleware, chains);
|
|
129
|
+
const mwPath = join(projectRoot, ROUTER_MIDDLEWARE_RELATIVE_PATH);
|
|
130
|
+
let mwPrior = "";
|
|
131
|
+
if (existsSync(mwPath)) {
|
|
132
|
+
try {
|
|
133
|
+
mwPrior = readFileSync(mwPath, "utf8");
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// unreadable → overwrite cleanly.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
let wroteMiddleware = false;
|
|
140
|
+
if (mwPrior !== mwContent) {
|
|
141
|
+
mkdirSync(dirname(mwPath), { recursive: true });
|
|
142
|
+
writeFileSync(mwPath, mwContent, "utf8");
|
|
143
|
+
wroteMiddleware = true;
|
|
144
|
+
}
|
|
145
|
+
return { wrote, routes, stores, services, content, path, middleware, wroteMiddleware };
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type GraphCache } from "./graph-cache.ts";
|
|
2
|
+
/**
|
|
3
|
+
* On-disk location of the generated ResourceName declaration, relative to the
|
|
4
|
+
* project root. Sits under `.reactra/` (gitignored, not committed) so it never
|
|
5
|
+
* collides with the `src/`-located route manifest.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RESOURCE_NAMES_RELATIVE_PATH = ".reactra/resource-names.d.ts";
|
|
8
|
+
/**
|
|
9
|
+
* Walk `<projectRoot>/src/` and collect every `resource X(...)` name declared
|
|
10
|
+
* in any DSL file, deduped into a sorted array. Cold-start safe: reads each
|
|
11
|
+
* candidate file and runs `compileToGraph` (preprocess → parse → extract,
|
|
12
|
+
* stopping before codegen — no emission). A file that fails to compile is
|
|
13
|
+
* skipped (a half-typed file mid-edit must not crash the dev server).
|
|
14
|
+
*
|
|
15
|
+
* Names are sorted with `localeCompare` so the emitted union is byte-stable
|
|
16
|
+
* across runs (C5 determinism).
|
|
17
|
+
*
|
|
18
|
+
* Tolerates a missing `src/` directory (returns []).
|
|
19
|
+
*/
|
|
20
|
+
export declare const collectResourceNames: (projectRoot: string, cache?: GraphCache) => string[];
|
|
21
|
+
/**
|
|
22
|
+
* Render the `.reactra/resource-names.d.ts` module text from a list of names.
|
|
23
|
+
*
|
|
24
|
+
* Empty project → `type ResourceName = string` (NEVER an empty union / `never`,
|
|
25
|
+
* which would make every `invalidate` entry a type error and break the build
|
|
26
|
+
* before any resource exists). A non-empty project emits the sorted union.
|
|
27
|
+
*
|
|
28
|
+
* Pure: same input → byte-identical output (C5).
|
|
29
|
+
*/
|
|
30
|
+
export declare const emitResourceNames: (names: readonly string[]) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Result of a {@link regenerateResourceNames} call. `wrote: true` means the
|
|
33
|
+
* file changed and new bytes were written; `wrote: false` means the content
|
|
34
|
+
* was already up-to-date and the file was left untouched (mirrors the manifest
|
|
35
|
+
* change-check so an unrelated edit doesn't churn HMR).
|
|
36
|
+
*/
|
|
37
|
+
export interface ResourceNamesResult {
|
|
38
|
+
readonly wrote: boolean;
|
|
39
|
+
readonly names: readonly string[];
|
|
40
|
+
readonly content: string;
|
|
41
|
+
readonly path: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Collect → render → write-if-changed. Idempotent: a second call over an
|
|
45
|
+
* unchanged `src/` is a filesystem no-op. The change-check matters — writing
|
|
46
|
+
* on every watcher fire would cascade into needless TS re-checks.
|
|
47
|
+
*/
|
|
48
|
+
export declare const regenerateResourceNames: (projectRoot: string, cache?: GraphCache) => ResourceNamesResult;
|
|
49
|
+
//# sourceMappingURL=resource-names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-names.d.ts","sourceRoot":"","sources":["../src/resource-names.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAEpE;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAA;AAI1E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAC/B,aAAa,MAAM,EACnB,QAAO,UAA+B,KACrC,MAAM,EAkCR,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,SAAS,MAAM,EAAE,KAAG,MAe5D,CAAA;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,aAAa,MAAM,EACnB,QAAO,UAA+B,KACrC,mBAmBF,CAAA"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// @reactra/vite-plugin — project-wide ResourceName union generator
|
|
2
|
+
//
|
|
3
|
+
// Owner: reactra-resource-spec.md §7 (typed `invalidate`) +
|
|
4
|
+
// reactra-compiler-spec.md §7.1 (vite plugin file-watcher hook).
|
|
5
|
+
//
|
|
6
|
+
// framework-review QW-5 / C1-tier-1: surface the set of resource names declared
|
|
7
|
+
// across the WHOLE project to the TS layer, so `useMutation(..., { invalidate:
|
|
8
|
+
// [...] })` autocompletes + type-errors on a typo. The mechanism is a generated
|
|
9
|
+
// `.reactra/resource-names.d.ts` that narrows `@reactra/resource`'s
|
|
10
|
+
// `ResourceName` alias via TS declaration merging.
|
|
11
|
+
//
|
|
12
|
+
// READ-ONLY over the source graph (C3): this module only READS resource names
|
|
13
|
+
// from `compileToGraph(source).graph` — it never alters emitted code. It writes
|
|
14
|
+
// exactly one generated `.d.ts` file and nothing else.
|
|
15
|
+
//
|
|
16
|
+
// Project-wide (NOT per-file): invalidation is cross-file (a mutation in one
|
|
17
|
+
// page invalidates a resource declared in another), so the union must be the
|
|
18
|
+
// union of ALL declared names. We piggyback on the same `src/` walk the manifest
|
|
19
|
+
// regen already performs.
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { createGraphCache } from "./graph-cache.js";
|
|
23
|
+
/**
|
|
24
|
+
* On-disk location of the generated ResourceName declaration, relative to the
|
|
25
|
+
* project root. Sits under `.reactra/` (gitignored, not committed) so it never
|
|
26
|
+
* collides with the `src/`-located route manifest.
|
|
27
|
+
*/
|
|
28
|
+
export const RESOURCE_NAMES_RELATIVE_PATH = ".reactra/resource-names.d.ts";
|
|
29
|
+
const SUPPORTED_EXT_RE = /\.(?:tsx|jsx|ts|js)$/;
|
|
30
|
+
/**
|
|
31
|
+
* Walk `<projectRoot>/src/` and collect every `resource X(...)` name declared
|
|
32
|
+
* in any DSL file, deduped into a sorted array. Cold-start safe: reads each
|
|
33
|
+
* candidate file and runs `compileToGraph` (preprocess → parse → extract,
|
|
34
|
+
* stopping before codegen — no emission). A file that fails to compile is
|
|
35
|
+
* skipped (a half-typed file mid-edit must not crash the dev server).
|
|
36
|
+
*
|
|
37
|
+
* Names are sorted with `localeCompare` so the emitted union is byte-stable
|
|
38
|
+
* across runs (C5 determinism).
|
|
39
|
+
*
|
|
40
|
+
* Tolerates a missing `src/` directory (returns []).
|
|
41
|
+
*/
|
|
42
|
+
export const collectResourceNames = (projectRoot, cache = createGraphCache()) => {
|
|
43
|
+
const srcRoot = join(projectRoot, "src");
|
|
44
|
+
const names = new Set();
|
|
45
|
+
const walk = (dir) => {
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = readdirSync(dir);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const full = join(dir, entry);
|
|
55
|
+
const stat = statSync(full);
|
|
56
|
+
if (stat.isDirectory()) {
|
|
57
|
+
walk(full);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!stat.isFile())
|
|
61
|
+
continue;
|
|
62
|
+
if (!SUPPORTED_EXT_RE.test(entry))
|
|
63
|
+
continue;
|
|
64
|
+
// A3: one cached compile per file (shared with the manifest scanners when
|
|
65
|
+
// the watcher threads the same per-event cache). Returns [] on read/
|
|
66
|
+
// compile failure — a half-typed file mid-edit is skipped, next regen
|
|
67
|
+
// recovers.
|
|
68
|
+
for (const container of cache.containersFor(full)) {
|
|
69
|
+
for (const resource of container.resources) {
|
|
70
|
+
names.add(resource.name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
walk(srcRoot);
|
|
76
|
+
return [...names].sort((a, b) => a.localeCompare(b));
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Render the `.reactra/resource-names.d.ts` module text from a list of names.
|
|
80
|
+
*
|
|
81
|
+
* Empty project → `type ResourceName = string` (NEVER an empty union / `never`,
|
|
82
|
+
* which would make every `invalidate` entry a type error and break the build
|
|
83
|
+
* before any resource exists). A non-empty project emits the sorted union.
|
|
84
|
+
*
|
|
85
|
+
* Pure: same input → byte-identical output (C5).
|
|
86
|
+
*/
|
|
87
|
+
export const emitResourceNames = (names) => {
|
|
88
|
+
const union = names.length === 0
|
|
89
|
+
? "string"
|
|
90
|
+
: names.map((n) => JSON.stringify(n)).join(" | ");
|
|
91
|
+
return [
|
|
92
|
+
"// GENERATED by @reactra/vite-plugin — do not edit.",
|
|
93
|
+
"// Narrows @reactra/resource's ResourceName to this project's resource set",
|
|
94
|
+
"// (framework-review QW-5 / C1). Regenerated on every src/ change; degrades",
|
|
95
|
+
"// to `string` when the project declares no resources.",
|
|
96
|
+
'declare module "@reactra/resource" {',
|
|
97
|
+
` type ResourceName = ${union}`,
|
|
98
|
+
"}",
|
|
99
|
+
"",
|
|
100
|
+
].join("\n");
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Collect → render → write-if-changed. Idempotent: a second call over an
|
|
104
|
+
* unchanged `src/` is a filesystem no-op. The change-check matters — writing
|
|
105
|
+
* on every watcher fire would cascade into needless TS re-checks.
|
|
106
|
+
*/
|
|
107
|
+
export const regenerateResourceNames = (projectRoot, cache = createGraphCache()) => {
|
|
108
|
+
const names = collectResourceNames(projectRoot, cache);
|
|
109
|
+
const content = emitResourceNames(names);
|
|
110
|
+
const path = join(projectRoot, RESOURCE_NAMES_RELATIVE_PATH);
|
|
111
|
+
let prior = "";
|
|
112
|
+
if (existsSync(path)) {
|
|
113
|
+
try {
|
|
114
|
+
prior = readFileSync(path, "utf8");
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Unreadable → treat as missing so we overwrite cleanly.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let wrote = false;
|
|
121
|
+
if (prior !== content) {
|
|
122
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
123
|
+
writeFileSync(path, content, "utf8");
|
|
124
|
+
wrote = true;
|
|
125
|
+
}
|
|
126
|
+
return { wrote, names, content, path };
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=resource-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-names.js","sourceRoot":"","sources":["../src/resource-names.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ReactraSidecar } from "@reactra/babel-plugin";
|
|
2
|
+
/**
|
|
3
|
+
* Result of one sidecar-write attempt. `wrote` is false when the on-disk
|
|
4
|
+
* file already matched (modulo `generated`) — useful for HMR diagnostics
|
|
5
|
+
* and idempotence assertions in tests.
|
|
6
|
+
*/
|
|
7
|
+
export interface WriteSidecarResult {
|
|
8
|
+
readonly wrote: boolean;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Write `sidecar` to its Compiler §6 location and return the result.
|
|
13
|
+
* Re-uses the existing on-disk `generated` timestamp when every other
|
|
14
|
+
* field is unchanged, so a no-op dev-server transform doesn't churn the
|
|
15
|
+
* file's mtime (which would otherwise re-trigger any external watcher).
|
|
16
|
+
*/
|
|
17
|
+
export declare const writeSidecar: (projectRoot: string, sourcePath: string, sidecar: ReactraSidecar) => WriteSidecarResult;
|
|
18
|
+
/**
|
|
19
|
+
* Result of one `deleteSidecar` attempt. `deleted` is true when an
|
|
20
|
+
* on-disk file was removed; false when there was nothing to remove
|
|
21
|
+
* (which is the idempotent no-op case the caller can rely on).
|
|
22
|
+
*/
|
|
23
|
+
export interface DeleteSidecarResult {
|
|
24
|
+
readonly deleted: boolean;
|
|
25
|
+
readonly path: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Remove the sidecar for `sourcePath`. Used by the Vite plugin's
|
|
29
|
+
* `transform` hook (Day 18 / `#7c`) when a recompile produces a sidecar with
|
|
30
|
+
* no stores+services — e.g. a route store was moved out to its own file. This
|
|
31
|
+
* keeps the on-disk sidecar set accurate for the tooling that consumes it
|
|
32
|
+
* (devtools/LSP — Compiler §6). (Backlog 1a deleted the codegen sidecar
|
|
33
|
+
* reader, so a stale sidecar no longer causes a duplicate-store build error;
|
|
34
|
+
* GC is now hygiene for the tooling metadata, not correctness for codegen.)
|
|
35
|
+
*
|
|
36
|
+
* Best-effort: ENOENT (nothing to delete) returns `deleted: false`; any
|
|
37
|
+
* other error logs to stderr but doesn't throw (matches `writeSidecar`'s
|
|
38
|
+
* "never break the transform" stance).
|
|
39
|
+
*/
|
|
40
|
+
export declare const deleteSidecar: (projectRoot: string, sourcePath: string) => DeleteSidecarResult;
|
|
41
|
+
/**
|
|
42
|
+
* True when the sidecar carries nothing any consumer reads — no stores,
|
|
43
|
+
* no services, AND no components. Used by the Vite plugin to decide
|
|
44
|
+
* between `writeSidecar` (keep the sidecar; consumers want the data)
|
|
45
|
+
* and `deleteSidecar` (truly nothing to keep; clearing prevents stale
|
|
46
|
+
* store-name conflicts AND keeps the sidecar directory tidy).
|
|
47
|
+
*
|
|
48
|
+
* Day 23 / `#7b` note: tightened from "stores+services empty" →
|
|
49
|
+
* "components+stores+services empty." The earlier predicate over-
|
|
50
|
+
* deleted component-only sidecars (e.g. pure-component pages after
|
|
51
|
+
* the Day-18 split), losing the per-component metadata that the
|
|
52
|
+
* Day-23 view-tree walker now populates into `components[].awaitBlocks`.
|
|
53
|
+
* Component-only sidecars are exactly what devtools / LSP will read.
|
|
54
|
+
*
|
|
55
|
+
* The store-name conflict GC still works: when a file's store moves out,
|
|
56
|
+
* the file's recompile produces `stores: []` and `writeSidecar`
|
|
57
|
+
* overwrites the old listing — no need to delete the whole sidecar.
|
|
58
|
+
*/
|
|
59
|
+
export declare const isEmptyForIndex: (sidecar: ReactraSidecar) => boolean;
|
|
60
|
+
//# sourceMappingURL=sidecar-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar-writer.d.ts","sourceRoot":"","sources":["../src/sidecar-writer.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3E;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GACvB,aAAa,MAAM,EACnB,YAAY,MAAM,EAClB,SAAS,cAAc,KACtB,kBAiBF,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,GACxB,aAAa,MAAM,EACnB,YAAY,MAAM,KACjB,mBAUF,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,cAAc,KAAG,OAGzB,CAAA"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// On-disk sidecar writer — Compiler §6 (`.reactra.json`).
|
|
2
|
+
//
|
|
3
|
+
// Every Reactra source that the vite plugin compiles also gets a
|
|
4
|
+
// per-file metadata sidecar dropped under
|
|
5
|
+
// `<projectRoot>/node_modules/.reactra/sidecars/<rel-src>.reactra.json`.
|
|
6
|
+
// Future passes (cross-file `use storeX` resolution, devtools, LSP, replay)
|
|
7
|
+
// read these manifests; the writer scope here only emits.
|
|
8
|
+
//
|
|
9
|
+
// `writeSidecar` is best-effort: failures (read-only FS, permissions) log
|
|
10
|
+
// to stderr but never break the transform — Reactra source still compiles
|
|
11
|
+
// to React 19 even if the sidecar can't be written.
|
|
12
|
+
//
|
|
13
|
+
// Day 18 / `#7c` — sidecar GC on container move:
|
|
14
|
+
// `deleteSidecar(projectRoot, sourcePath)` removes the on-disk file when
|
|
15
|
+
// a recompile produces a `FileGraph` with no stores+services (the two
|
|
16
|
+
// kinds the cross-file index keys on). Without this, moving a store
|
|
17
|
+
// declaration to another file leaves a stale sidecar listing the same
|
|
18
|
+
// name → `loadSidecarIndex` throws duplicate-store-across-files. The
|
|
19
|
+
// Vite plugin's `transform` hook chooses between write and delete based
|
|
20
|
+
// on `isEmptyForIndex(sidecar)`.
|
|
21
|
+
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname } from "node:path";
|
|
23
|
+
import { sidecarPathFor } from "@reactra/babel-plugin";
|
|
24
|
+
/**
|
|
25
|
+
* Write `sidecar` to its Compiler §6 location and return the result.
|
|
26
|
+
* Re-uses the existing on-disk `generated` timestamp when every other
|
|
27
|
+
* field is unchanged, so a no-op dev-server transform doesn't churn the
|
|
28
|
+
* file's mtime (which would otherwise re-trigger any external watcher).
|
|
29
|
+
*/
|
|
30
|
+
export const writeSidecar = (projectRoot, sourcePath, sidecar) => {
|
|
31
|
+
const path = sidecarPathFor(projectRoot, sourcePath);
|
|
32
|
+
const next = JSON.stringify(sidecar, null, 2) + "\n";
|
|
33
|
+
let existing = null;
|
|
34
|
+
try {
|
|
35
|
+
existing = readFileSync(path, "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// File doesn't exist yet — first write.
|
|
39
|
+
}
|
|
40
|
+
if (existing !== null && contentMatches(existing, next)) {
|
|
41
|
+
return { wrote: false, path };
|
|
42
|
+
}
|
|
43
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
44
|
+
writeFileSync(path, next, "utf8");
|
|
45
|
+
return { wrote: true, path };
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Remove the sidecar for `sourcePath`. Used by the Vite plugin's
|
|
49
|
+
* `transform` hook (Day 18 / `#7c`) when a recompile produces a sidecar with
|
|
50
|
+
* no stores+services — e.g. a route store was moved out to its own file. This
|
|
51
|
+
* keeps the on-disk sidecar set accurate for the tooling that consumes it
|
|
52
|
+
* (devtools/LSP — Compiler §6). (Backlog 1a deleted the codegen sidecar
|
|
53
|
+
* reader, so a stale sidecar no longer causes a duplicate-store build error;
|
|
54
|
+
* GC is now hygiene for the tooling metadata, not correctness for codegen.)
|
|
55
|
+
*
|
|
56
|
+
* Best-effort: ENOENT (nothing to delete) returns `deleted: false`; any
|
|
57
|
+
* other error logs to stderr but doesn't throw (matches `writeSidecar`'s
|
|
58
|
+
* "never break the transform" stance).
|
|
59
|
+
*/
|
|
60
|
+
export const deleteSidecar = (projectRoot, sourcePath) => {
|
|
61
|
+
const path = sidecarPathFor(projectRoot, sourcePath);
|
|
62
|
+
try {
|
|
63
|
+
unlinkSync(path);
|
|
64
|
+
return { deleted: true, path };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (isEnoent(err))
|
|
68
|
+
return { deleted: false, path };
|
|
69
|
+
console.error("[@reactra/vite-plugin] sidecar delete failed:", err);
|
|
70
|
+
return { deleted: false, path };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* True when the sidecar carries nothing any consumer reads — no stores,
|
|
75
|
+
* no services, AND no components. Used by the Vite plugin to decide
|
|
76
|
+
* between `writeSidecar` (keep the sidecar; consumers want the data)
|
|
77
|
+
* and `deleteSidecar` (truly nothing to keep; clearing prevents stale
|
|
78
|
+
* store-name conflicts AND keeps the sidecar directory tidy).
|
|
79
|
+
*
|
|
80
|
+
* Day 23 / `#7b` note: tightened from "stores+services empty" →
|
|
81
|
+
* "components+stores+services empty." The earlier predicate over-
|
|
82
|
+
* deleted component-only sidecars (e.g. pure-component pages after
|
|
83
|
+
* the Day-18 split), losing the per-component metadata that the
|
|
84
|
+
* Day-23 view-tree walker now populates into `components[].awaitBlocks`.
|
|
85
|
+
* Component-only sidecars are exactly what devtools / LSP will read.
|
|
86
|
+
*
|
|
87
|
+
* The store-name conflict GC still works: when a file's store moves out,
|
|
88
|
+
* the file's recompile produces `stores: []` and `writeSidecar`
|
|
89
|
+
* overwrites the old listing — no need to delete the whole sidecar.
|
|
90
|
+
*/
|
|
91
|
+
export const isEmptyForIndex = (sidecar) => sidecar.stores.length === 0 &&
|
|
92
|
+
sidecar.services.length === 0 &&
|
|
93
|
+
sidecar.components.length === 0;
|
|
94
|
+
// Node's fs errors carry `code: "ENOENT"`; this narrows the unknown
|
|
95
|
+
// without pulling in `NodeJS.ErrnoException` from `@types/node`.
|
|
96
|
+
const isEnoent = (e) => typeof e === "object" && e != null && e.code === "ENOENT";
|
|
97
|
+
// Compare two serialised sidecar bodies for equality ignoring `generated`.
|
|
98
|
+
// JSON.parse → strip → JSON.stringify both sides; on any parse failure
|
|
99
|
+
// fall back to raw string equality (safe default that just rewrites).
|
|
100
|
+
const contentMatches = (existing, next) => {
|
|
101
|
+
try {
|
|
102
|
+
const a = JSON.parse(existing);
|
|
103
|
+
const b = JSON.parse(next);
|
|
104
|
+
const stripGenerated = (o) => {
|
|
105
|
+
const { generated: _g, ...rest } = o;
|
|
106
|
+
return rest;
|
|
107
|
+
};
|
|
108
|
+
return JSON.stringify(stripGenerated(a)) === JSON.stringify(stripGenerated(b));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return existing === next;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=sidecar-writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sidecar-writer.js","sourceRoot":"","sources":["../src/sidecar-writer.ts"],"names":[],"mappings":"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"}
|