@gjsify/rolldown-plugin-gjsify 0.45.0 → 0.47.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/lib/index.d.ts CHANGED
@@ -26,9 +26,9 @@ export { platformResolvePlugin, nativescriptSuffixChain, desktopSuffixChain, des
26
26
  export type { PlatformResolvePluginOptions, NativescriptPlatform } from './plugins/platform-resolve.js';
27
27
  export { rnRouteManifestPlugin, renderRouteManifest, walkRoutes, RouteManifestError, RN_ROUTES_MODULE_ID, MAX_ROUTE_DEPTH, } from './plugins/rn-route-manifest.js';
28
28
  export type { RnRouteManifestOptions, FoundRoute } from './plugins/rn-route-manifest.js';
29
- export { reactNativeAliasPlugin, classifyReactNativeSpecifier, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, } from './plugins/react-native-alias.js';
30
- export type { ReactNativeSpecifierKind } from './plugins/react-native-alias.js';
31
- export { reactNativeSupportGatePlugin, loadSupportTable, findSupportViolations, formatSupportViolations, formatOpaqueReference, formatUnreadableModule, SUPPORT_TABLE_SUBPATH, WATCHED_SPECIFIERS, ReactNativeUnsupportedImportError, SupportTableUnreadableError, } from './plugins/react-native-gate.js';
29
+ export { reactNativeAliasPlugin, classifyReactNativeSpecifier, couldBeSurfaceSpecifier, loadLayer, FALLBACK_SURFACES, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, SURFACE_MENTION, SURFACE_NAME_PREFIXES, } from './plugins/react-native-alias.js';
30
+ export type { AliasedSurface, LayerReader, ReactNativeAliasOptions, ReactNativeSpecifierKind, } from './plugins/react-native-alias.js';
31
+ export { reactNativeSupportGatePlugin, loadSupportTable, findSupportViolations, formatSupportViolations, formatOpaqueReference, formatUnreadableModule, SUPPORT_TABLE_SUBPATH, WATCHED_SPECIFIERS, watchedSpecifiers, ReactNativeUnsupportedImportError, SupportTableUnreadableError, } from './plugins/react-native-gate.js';
32
32
  export type { ReactNativeSupportGateOptions, SupportTableReader, SupportTableResolver, SupportViolation, } from './plugins/react-native-gate.js';
33
33
  export { scanNamedImports, ImportScanParseError } from './utils/scan-named-imports.js';
34
34
  export type { NamedImport, OpaqueReference, ScanResult } from './utils/scan-named-imports.js';
package/lib/index.js CHANGED
@@ -16,8 +16,8 @@ export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier,
16
16
  export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
17
17
  export { platformResolvePlugin, nativescriptSuffixChain, desktopSuffixChain, desktopOsSuffix, DESKTOP_OS_SUFFIXES, DESKTOP_REFUSED_SUFFIXES, PlatformVariantExternalError, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
18
18
  export { rnRouteManifestPlugin, renderRouteManifest, walkRoutes, RouteManifestError, RN_ROUTES_MODULE_ID, MAX_ROUTE_DEPTH, } from './plugins/rn-route-manifest.js';
19
- export { reactNativeAliasPlugin, classifyReactNativeSpecifier, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, } from './plugins/react-native-alias.js';
20
- export { reactNativeSupportGatePlugin, loadSupportTable, findSupportViolations, formatSupportViolations, formatOpaqueReference, formatUnreadableModule, SUPPORT_TABLE_SUBPATH, WATCHED_SPECIFIERS, ReactNativeUnsupportedImportError, SupportTableUnreadableError, } from './plugins/react-native-gate.js';
19
+ export { reactNativeAliasPlugin, classifyReactNativeSpecifier, couldBeSurfaceSpecifier, loadLayer, FALLBACK_SURFACES, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, SURFACE_MENTION, SURFACE_NAME_PREFIXES, } from './plugins/react-native-alias.js';
20
+ export { reactNativeSupportGatePlugin, loadSupportTable, findSupportViolations, formatSupportViolations, formatOpaqueReference, formatUnreadableModule, SUPPORT_TABLE_SUBPATH, WATCHED_SPECIFIERS, watchedSpecifiers, ReactNativeUnsupportedImportError, SupportTableUnreadableError, } from './plugins/react-native-gate.js';
21
21
  export { scanNamedImports, ImportScanParseError } from './utils/scan-named-imports.js';
22
22
  export * from './plugin.js';
23
23
  import { gjsifyPlugin } from './plugin.js';
@@ -37,6 +37,21 @@ export function gjsImportsEmptyPlugin(options = {}) {
37
37
  const emptyGirs = options.emptyGirs ?? true;
38
38
  // When `@girs/*` is carved out, only `gi://*` reaches the empty redirect.
39
39
  const matcher = emptyGirs ? /^(@girs\/|gi:\/\/)/ : /^gi:\/\//;
40
+ // …with one subpath excluded on EVERY target, carve-out or not.
41
+ //
42
+ // `@girs/<ns>/vocabulary` is generated DATA — property names, signal names,
43
+ // enum nicks, versions (ADR 0029). It imports no `gi://`, binds no library and
44
+ // loads under plain Node; the reason it sits in `@girs` is that ts-for-gir
45
+ // generates it, not that it needs GJS. Emptying it produces
46
+ // `[MISSING_EXPORT] "PROVENANCE" is not exported by "\0gjsify-empty-gjs-import"`
47
+ // at bundle time, which reads like a missing export rather than a substituted
48
+ // module.
49
+ //
50
+ // The same over-broad assumption lived in `GIRS_VALUE_RE`
51
+ // (manifest-conformance), where it made gtk-host's honest `node: "polyfill"`
52
+ // read as runtime drift. Two tools, one rule: the package scope says who
53
+ // generated a file, the subpath says what it needs.
54
+ const isVocabulary = (source) => /^@girs\/[^/]+\/vocabulary$/.test(source);
40
55
  return {
41
56
  name: 'gjsify-gjs-imports-empty',
42
57
  resolveId: {
@@ -48,7 +63,7 @@ export function gjsImportsEmptyPlugin(options = {}) {
48
63
  // matched specifiers; let everything else (including `@girs/*`
49
64
  // when `emptyGirs` is false) fall through to the default
50
65
  // resolver chain.
51
- if (!matcher.test(source))
66
+ if (!matcher.test(source) || isVocabulary(source))
52
67
  return null;
53
68
  return { id: GJSIMPORTS_VIRTUAL_ID };
54
69
  },
@@ -3,15 +3,43 @@ import type { Plugin } from 'rolldown';
3
3
  export declare const REACT_NATIVE_ALIAS_TARGET = "@gjsify/react-native";
4
4
  /** The specifier an unmodified React Native application writes. */
5
5
  export declare const REACT_NATIVE_SPECIFIER = "react-native";
6
+ /**
7
+ * One npm package the layer answers for, and the specifier that answers it.
8
+ *
9
+ * ADR 0036 § 2: `react-native` and `expo-router` are the first two rows of a registry
10
+ * rather than two special cases, because a real React Native application imports
11
+ * sixteen more package names and every one of them used to fail at MODULE RESOLUTION —
12
+ * the bundler said npm could not find it, which names npm rather than this layer.
13
+ *
14
+ * READ FROM THE LAYER, never listed here. `@gjsify/react-native/support-table` exports
15
+ * the registry, the gate already imports that subpath to read the table, and a second
16
+ * list in the bundler plugin is the drift this whole arrangement exists to remove: the
17
+ * plugin would then alias a surface whose table it cannot see, or refuse one the layer
18
+ * has grown.
19
+ */
20
+ export interface AliasedSurface {
21
+ readonly module: string;
22
+ readonly target: string;
23
+ }
6
24
  /** What a specifier is, as far as this alias is concerned. */
7
25
  export type ReactNativeSpecifierKind =
8
- /** Exactly `react-native` — the package root, which is the mirrored surface. */
26
+ /** Exactly a declared surface's own npm name — the mirrored export surface. */
9
27
  {
10
28
  readonly kind: 'root';
29
+ readonly target: string;
11
30
  }
12
- /** `react-native/<subpath>` — internals, which are not mirrored. */
31
+ /**
32
+ * `<a declared surface>/<subpath>` — internals, which are not mirrored.
33
+ *
34
+ * The MODULE and the TARGET come along because the refusal names them: `react-native`
35
+ * is no longer the only surface with a subpath layout below it, and
36
+ * `@expo/vector-icons/Ionicons` and `expo-router/entry` are both ordinary things a
37
+ * real application writes.
38
+ */
13
39
  | {
14
40
  readonly kind: 'subpath';
41
+ readonly module: string;
42
+ readonly target: string;
15
43
  readonly subpath: string;
16
44
  }
17
45
  /** Anything else, including `@gjsify/react-native` itself. */
@@ -29,7 +57,7 @@ export type ReactNativeSpecifierKind =
29
57
  * onto the mirror would replace a package that legitimately does not work with
30
58
  * one that silently does the wrong thing.
31
59
  */
32
- export declare function classifyReactNativeSpecifier(source: string): ReactNativeSpecifierKind;
60
+ export declare function classifyReactNativeSpecifier(source: string, surfaces?: readonly AliasedSurface[]): ReactNativeSpecifierKind;
33
61
  /**
34
62
  * Thrown for `react-native/<subpath>` — an import of React Native's internals.
35
63
  *
@@ -43,21 +71,96 @@ export declare function classifyReactNativeSpecifier(source: string): ReactNativ
43
71
  export declare class ReactNativeDeepImportError extends Error {
44
72
  readonly specifier: string;
45
73
  readonly importer: string;
74
+ readonly module: string;
75
+ readonly target: string;
46
76
  readonly name = "ReactNativeDeepImportError";
47
- constructor(specifier: string, importer: string);
77
+ constructor(specifier: string, importer: string, module?: string, target?: string);
48
78
  }
49
79
  /** Thrown when the alias target itself cannot be resolved. */
50
80
  export declare class ReactNativeAliasTargetMissingError extends Error {
81
+ readonly specifier: string;
82
+ readonly target: string;
51
83
  readonly importer: string;
52
84
  readonly name = "ReactNativeAliasTargetMissingError";
53
- constructor(importer: string);
85
+ constructor(specifier: string, target: string, importer: string);
86
+ }
87
+ /** The subpath that carries the registry and the table in an installed layer. */
88
+ export declare const SUPPORT_TABLE_SUBPATH = "@gjsify/react-native/support-table";
89
+ /** The slice of the layer both plugins need, structurally so a spec can supply a fixture. */
90
+ export interface LayerReader {
91
+ /** Every declared surface, in lookup order. */
92
+ readonly SURFACES: readonly AliasedSurface[];
93
+ /** May a build import this name from this module? */
94
+ isImportable(name: string, module?: string): boolean;
95
+ /** The one sentence a build error and a runtime throw both print. */
96
+ explainUnsupported(name: string, module?: string): string;
54
97
  }
98
+ /** The resolver slice the loader needs, so it is testable without a bundler. */
99
+ export interface SupportTableResolver {
100
+ resolve(specifier: string, importer: string): Promise<{
101
+ id: string;
102
+ external?: boolean | string;
103
+ } | null>;
104
+ }
105
+ /** Thrown when a plugin is on but the layer it must read is not reachable. */
106
+ export declare class SupportTableUnreadableError extends Error {
107
+ readonly name = "SupportTableUnreadableError";
108
+ constructor(detail: string, cause?: unknown);
109
+ }
110
+ /**
111
+ * Resolve and import the layer's support-table subpath.
112
+ *
113
+ * `importer` is a module from the project being built, so the resolve lands in the
114
+ * PROJECT's dependency tree rather than the CLI's.
115
+ */
116
+ export declare function loadLayer(resolver: SupportTableResolver, importer: string, load?: (href: string) => Promise<unknown>): Promise<LayerReader>;
55
117
  /**
56
- * Rewrite the bare `react-native` specifier onto `@gjsify/react-native`.
118
+ * The one row that is known WITHOUT reading the layer.
119
+ *
120
+ * Used when the layer cannot be read at all, and that is a deliberate degradation
121
+ * rather than a default. `gjsify build --dialect react-native` is an opt-in, so a
122
+ * consumer who asked for it and has no layer installed meant `react-native` — the one
123
+ * specifier whose name is a fact rather than a lookup — and that import gets the
124
+ * named "target does not resolve" error it always got. Every other surface then
125
+ * simply fails to resolve the way it did before ADR 0036, which is no worse than
126
+ * before and much better than every `resolveId` in the build throwing.
127
+ */
128
+ export declare const FALLBACK_SURFACES: readonly AliasedSurface[];
129
+ /**
130
+ * Rewrite a declared surface's specifier onto the subpath that answers it.
57
131
  *
58
132
  * `order: 'pre'` so the claim lands before the externals policy and the
59
133
  * substitution-table alias layer get a look — the same position
60
134
  * `platformResolvePlugin` holds, and for the same reason: a redirect that runs
61
135
  * after `externalsPlugin` would find the specifier already externalised.
62
136
  */
63
- export declare function reactNativeAliasPlugin(): Plugin;
137
+ export interface ReactNativeAliasOptions {
138
+ /** The layer, when the caller already has it. Left unset in a real build. */
139
+ layer?: LayerReader;
140
+ }
141
+ export declare function reactNativeAliasPlugin(options?: ReactNativeAliasOptions): Plugin;
142
+ /**
143
+ * The prefixes every declared surface's npm name starts with.
144
+ *
145
+ * A PREFILTER AND NOT A DECISION: the registry decides, and these exist only so the
146
+ * two hot paths — one `resolveId` per specifier in the build, one `transform` per
147
+ * module — can say "certainly not a surface" without resolving or parsing anything.
148
+ * `react-native-alias.spec.ts` asserts every row of the LAYER's own registry starts
149
+ * with one of them, so a surface named outside this list fails a test rather than
150
+ * being silently skipped by the alias and the gate together.
151
+ *
152
+ * `@gjsify/react-native` is here because a gjsify-native application writes the target
153
+ * directly, and the gate watches both spellings of every surface.
154
+ */
155
+ export declare const SURFACE_NAME_PREFIXES: readonly string[];
156
+ /** Could this specifier be a declared surface? Cheap, and deliberately over-inclusive. */
157
+ export declare const couldBeSurfaceSpecifier: (specifier: string) => boolean;
158
+ /**
159
+ * Does this SOURCE mention a specifier that could be a surface?
160
+ *
161
+ * Anchored on the opening QUOTE, which is the whole point: a bare `expo-` test over
162
+ * source text would match the word `export`, so the fragment has to sit where a module
163
+ * specifier sits. Over-inclusive by design (a string literal in a comment matches);
164
+ * under-inclusive never, which is the direction that matters for a gate.
165
+ */
166
+ export declare const SURFACE_MENTION: RegExp;
@@ -29,6 +29,7 @@
29
29
  // unresolvable target is a named error rather than a silent external — the
30
30
  // substitution IS the promise, so its failure is the one thing that must not
31
31
  // exit 0.
32
+ import { pathToFileURL } from 'node:url';
32
33
  /** The package whose export surface mirrors React Native's (ADR 0032 § 2). */
33
34
  export const REACT_NATIVE_ALIAS_TARGET = '@gjsify/react-native';
34
35
  /** The specifier an unmodified React Native application writes. */
@@ -44,11 +45,36 @@ export const REACT_NATIVE_SPECIFIER = 'react-native';
44
45
  * onto the mirror would replace a package that legitimately does not work with
45
46
  * one that silently does the wrong thing.
46
47
  */
47
- export function classifyReactNativeSpecifier(source) {
48
- if (source === REACT_NATIVE_SPECIFIER)
49
- return { kind: 'root' };
50
- if (source.startsWith(`${REACT_NATIVE_SPECIFIER}/`)) {
51
- return { kind: 'subpath', subpath: source.slice(REACT_NATIVE_SPECIFIER.length + 1) };
48
+ export function classifyReactNativeSpecifier(source, surfaces = [{ module: REACT_NATIVE_SPECIFIER, target: REACT_NATIVE_ALIAS_TARGET }]) {
49
+ for (const surface of surfaces) {
50
+ if (source === surface.module)
51
+ return { kind: 'root', target: surface.target };
52
+ }
53
+ // A DEEP IMPORT is only a deep import of a surface this layer answers for. The
54
+ // exact-match loop above runs FIRST for the same reason it always did:
55
+ // `react-native-web`, `react-native-reanimated` and `react-native-svg` all start
56
+ // with the same eleven characters and none of them is `react-native`, so the
57
+ // prefix test has to come after every exact name — including the surface names
58
+ // `react-native-safe-area-context` and `react-native-gesture-handler`, which a
59
+ // prefix test on `react-native/` would not catch but a careless `react-native-`
60
+ // one would.
61
+ //
62
+ // EVERY SURFACE, not only `react-native`, and that gap was reachable from ordinary
63
+ // code: `@expo/vector-icons/Ionicons` is the spelling `@expo/vector-icons`' own
64
+ // documentation uses, and `expo-router/entry` is what an Expo application's
65
+ // `package.json#main` points at. Both classified as `other` before this, so the
66
+ // alias returned null and the build failed at MODULE RESOLUTION — npm's "cannot
67
+ // find package", which is the exact failure ADR 0036 exists to replace with a
68
+ // sentence.
69
+ for (const surface of surfaces) {
70
+ if (source.startsWith(`${surface.module}/`)) {
71
+ return {
72
+ kind: 'subpath',
73
+ module: surface.module,
74
+ target: surface.target,
75
+ subpath: source.slice(surface.module.length + 1),
76
+ };
77
+ }
52
78
  }
53
79
  return { kind: 'other' };
54
80
  }
@@ -65,64 +91,216 @@ export function classifyReactNativeSpecifier(source) {
65
91
  export class ReactNativeDeepImportError extends Error {
66
92
  specifier;
67
93
  importer;
94
+ module;
95
+ target;
68
96
  name = 'ReactNativeDeepImportError';
69
- constructor(specifier, importer) {
70
- super(`gjsify react-native alias: "${specifier}" (imported by ${importer}) reaches into React ` +
71
- `Native's internals. The alias to ${REACT_NATIVE_ALIAS_TARGET} covers the PACKAGE ROOT, ` +
72
- `because that is the export surface ADR 0032 § 2 mirrors; the subpath layout below it ` +
73
- `belongs to Metro and the native bridge and has no counterpart here. Import the name from ` +
74
- `"${REACT_NATIVE_SPECIFIER}" instead, or from "${REACT_NATIVE_ALIAS_TARGET}" directly if ` +
75
- `it is a gjsify-only addition.`);
97
+ constructor(specifier, importer, module = REACT_NATIVE_SPECIFIER, target = REACT_NATIVE_ALIAS_TARGET) {
98
+ super(`gjsify react-native alias: "${specifier}" (imported by ${importer}) reaches into ` +
99
+ `"${module}"'s internals. The alias to ${target} covers the PACKAGE ROOT, because that is ` +
100
+ `the export surface ADR 0032 § 2 mirrors and ADR 0036 § 1 gives one subpath each; the ` +
101
+ `module layout below it has no counterpart here` +
102
+ (module === REACT_NATIVE_SPECIFIER
103
+ ? ` — react-native's own exists because of Metro and the native bridge`
104
+ : '') +
105
+ `. Import the name from "${module}" instead, or from "${target}" directly if it is a ` +
106
+ `gjsify-only addition.`);
76
107
  this.specifier = specifier;
77
108
  this.importer = importer;
109
+ this.module = module;
110
+ this.target = target;
78
111
  }
79
112
  }
80
113
  /** Thrown when the alias target itself cannot be resolved. */
81
114
  export class ReactNativeAliasTargetMissingError extends Error {
115
+ specifier;
116
+ target;
82
117
  importer;
83
118
  name = 'ReactNativeAliasTargetMissingError';
84
- constructor(importer) {
85
- super(`gjsify react-native alias: "${REACT_NATIVE_SPECIFIER}" (imported by ${importer}) is aliased ` +
86
- `to ${REACT_NATIVE_ALIAS_TARGET}, which does not resolve from this project. Install it, or ` +
87
- `drop the react-native opt-in. NOT falling back to the real react-native on purpose: it is ` +
88
- `written for a React Native runtime, so the bundle would build and then fail in the window ` +
89
- `on a NativeModules bridge that does not exist here.`);
119
+ constructor(specifier, target, importer) {
120
+ super(`gjsify react-native alias: "${specifier}" (imported by ${importer}) is aliased ` +
121
+ `to ${target}, which does not resolve from this project. Install ${REACT_NATIVE_ALIAS_TARGET} ` +
122
+ `(and build it, if it is a workspace link), or drop the react-native opt-in. NOT falling back ` +
123
+ `to the real package on purpose: it is written for a React Native runtime, so the bundle ` +
124
+ `would build and then fail in the window on a native module that does not exist here.`);
125
+ this.specifier = specifier;
126
+ this.target = target;
90
127
  this.importer = importer;
91
128
  }
92
129
  }
130
+ // --- reading the layer (ADR 0036 § 2) ----------------------------------------
131
+ //
132
+ // BOTH PLUGINS READ THE LAYER, NEVER A COPY. `@gjsify/react-native/support-table` is
133
+ // a published subpath exporting the surface registry plus `isImportable` and
134
+ // `explainUnsupported`, and it is resolved through the BUNDLER's own resolver from a
135
+ // module of the project being built — so the answer describes the layer this bundle
136
+ // will actually contain, and a version skew between plugin and layer cannot make
137
+ // either lie. Reading the SOURCE the way `scripts/check-rn-surface.mjs` does is not an
138
+ // option here: `files` ships `lib` and not `src`, so a consumer's `node_modules` has
139
+ // no `support-table.ts` to parse.
140
+ //
141
+ // AND IT IS NOT A DEPENDENCY, deliberately. `@gjsify/rolldown-plugin-gjsify` is tier 1
142
+ // and `@gjsify/react-native` is tier 3; a tier-1 package may not depend on a higher
143
+ // tier (`scripts/audit-runtimes.mjs`).
144
+ /** The subpath that carries the registry and the table in an installed layer. */
145
+ export const SUPPORT_TABLE_SUBPATH = `${REACT_NATIVE_ALIAS_TARGET}/support-table`;
146
+ /** Thrown when a plugin is on but the layer it must read is not reachable. */
147
+ export class SupportTableUnreadableError extends Error {
148
+ name = 'SupportTableUnreadableError';
149
+ constructor(detail, cause) {
150
+ super(`gjsify react-native gate: cannot read ${SUPPORT_TABLE_SUBPATH} — ${detail}. The gate has no ` +
151
+ `second source to fall back on, and ADR 0032 § 8 is explicit that a hand-maintained table ` +
152
+ `beside it would be the second truth this repository has already collected several times. ` +
153
+ `Install ${REACT_NATIVE_ALIAS_TARGET} (and build it, if it is a workspace link), or drop ` +
154
+ `the react-native opt-in.`, cause === undefined ? undefined : { cause });
155
+ }
156
+ }
157
+ /**
158
+ * Resolve and import the layer's support-table subpath.
159
+ *
160
+ * `importer` is a module from the project being built, so the resolve lands in the
161
+ * PROJECT's dependency tree rather than the CLI's.
162
+ */
163
+ export async function loadLayer(resolver, importer, load = (href) => import(/* @vite-ignore */ href)) {
164
+ let resolved;
165
+ try {
166
+ resolved = await resolver.resolve(SUPPORT_TABLE_SUBPATH, importer);
167
+ }
168
+ catch (cause) {
169
+ throw new SupportTableUnreadableError(`the resolver threw for ${importer}`, cause);
170
+ }
171
+ if (!resolved)
172
+ throw new SupportTableUnreadableError(`it does not resolve from ${importer}`);
173
+ if (resolved.external) {
174
+ throw new SupportTableUnreadableError(`it resolved as EXTERNAL from ${importer}, so there is no file to read`);
175
+ }
176
+ let mod;
177
+ try {
178
+ // A file path, not the bare specifier: GJS's ESM loader does not follow
179
+ // `package.json#exports`, so the bare form works on Node and fails under the
180
+ // GJS engine — the same limitation the CLI's by-name plugin loader documents.
181
+ // The resolver already did the exports-map hop.
182
+ mod = await load(pathToFileURL(resolved.id).href);
183
+ }
184
+ catch (cause) {
185
+ throw new SupportTableUnreadableError(`importing ${resolved.id} failed`, cause);
186
+ }
187
+ const candidate = mod;
188
+ if (typeof candidate.isImportable !== 'function' ||
189
+ typeof candidate.explainUnsupported !== 'function' ||
190
+ !Array.isArray(candidate.SURFACES)) {
191
+ throw new SupportTableUnreadableError(`${resolved.id} does not export SURFACES + isImportable + explainUnsupported. ` +
192
+ `That is a version skew between the bundler plugin and the layer, not a missing install`);
193
+ }
194
+ return {
195
+ SURFACES: candidate.SURFACES,
196
+ isImportable: candidate.isImportable,
197
+ explainUnsupported: candidate.explainUnsupported,
198
+ };
199
+ }
93
200
  /**
94
- * Rewrite the bare `react-native` specifier onto `@gjsify/react-native`.
201
+ * The one row that is known WITHOUT reading the layer.
95
202
  *
96
- * `order: 'pre'` so the claim lands before the externals policy and the
97
- * substitution-table alias layer get a look — the same position
98
- * `platformResolvePlugin` holds, and for the same reason: a redirect that runs
99
- * after `externalsPlugin` would find the specifier already externalised.
203
+ * Used when the layer cannot be read at all, and that is a deliberate degradation
204
+ * rather than a default. `gjsify build --dialect react-native` is an opt-in, so a
205
+ * consumer who asked for it and has no layer installed meant `react-native` — the one
206
+ * specifier whose name is a fact rather than a lookup — and that import gets the
207
+ * named "target does not resolve" error it always got. Every other surface then
208
+ * simply fails to resolve the way it did before ADR 0036, which is no worse than
209
+ * before and much better than every `resolveId` in the build throwing.
100
210
  */
101
- export function reactNativeAliasPlugin() {
211
+ export const FALLBACK_SURFACES = [
212
+ { module: REACT_NATIVE_SPECIFIER, target: REACT_NATIVE_ALIAS_TARGET },
213
+ ];
214
+ export function reactNativeAliasPlugin(options = {}) {
215
+ let surfaces = options.layer?.SURFACES ?? null;
216
+ let unreadable = false;
217
+ /**
218
+ * The registry, read once.
219
+ *
220
+ * A FAILURE IS CACHED TOO. Without that, a project without the layer installed
221
+ * would retry the resolve for every specifier in the build and throw from every
222
+ * one — including `node:fs`, which has nothing to do with this plugin.
223
+ * {@link FALLBACK_SURFACES} says what the degradation is and why it is the honest
224
+ * one.
225
+ */
226
+ const registry = async (context, importer) => {
227
+ if (surfaces !== null)
228
+ return surfaces;
229
+ if (unreadable)
230
+ return FALLBACK_SURFACES;
231
+ try {
232
+ surfaces = (await loadLayer(context, importer)).SURFACES;
233
+ return surfaces;
234
+ }
235
+ catch {
236
+ unreadable = true;
237
+ return FALLBACK_SURFACES;
238
+ }
239
+ };
102
240
  return {
103
241
  name: 'gjsify-react-native-alias',
104
242
  resolveId: {
105
243
  order: 'pre',
106
244
  async handler(source, importer, extraOptions) {
107
- const classified = classifyReactNativeSpecifier(source);
108
- if (classified.kind === 'other')
245
+ // A CHEAP PREFILTER BEFORE THE REGISTRY, because this hook runs for
246
+ // every specifier in the build and the registry needs a resolve.
247
+ // {@link SURFACE_NAME_PREFIXES} says why the list is a prefilter and
248
+ // what holds it against the layer's own rows.
249
+ if (!couldBeSurfaceSpecifier(source))
109
250
  return null;
110
251
  // An ENTRY has no importer to name; report the specifier itself
111
252
  // rather than the string "undefined".
112
253
  const from = importer ?? source;
113
- if (classified.kind === 'subpath')
114
- throw new ReactNativeDeepImportError(source, from);
254
+ const classified = classifyReactNativeSpecifier(source, await registry(this, importer ?? source));
255
+ if (classified.kind === 'other')
256
+ return null;
257
+ if (classified.kind === 'subpath') {
258
+ throw new ReactNativeDeepImportError(source, from, classified.module, classified.target);
259
+ }
115
260
  // `skipSelf: true` keeps the nested resolve out of this hook. The
116
- // target is a plain package name, so the normal chain (workspace
117
- // link, node_modules, the exports map) answers it.
118
- const resolved = await this.resolve(REACT_NATIVE_ALIAS_TARGET, importer, {
261
+ // target is a plain package name plus a subpath, so the normal chain
262
+ // (workspace link, node_modules, the exports map) answers it.
263
+ const resolved = await this.resolve(classified.target, importer, {
119
264
  skipSelf: true,
120
265
  ...(extraOptions?.kind ? { kind: extraOptions.kind } : {}),
121
266
  });
122
- if (!resolved || resolved.external)
123
- throw new ReactNativeAliasTargetMissingError(from);
267
+ if (!resolved || resolved.external) {
268
+ throw new ReactNativeAliasTargetMissingError(source, classified.target, from);
269
+ }
124
270
  return resolved;
125
271
  },
126
272
  },
127
273
  };
128
274
  }
275
+ /**
276
+ * The prefixes every declared surface's npm name starts with.
277
+ *
278
+ * A PREFILTER AND NOT A DECISION: the registry decides, and these exist only so the
279
+ * two hot paths — one `resolveId` per specifier in the build, one `transform` per
280
+ * module — can say "certainly not a surface" without resolving or parsing anything.
281
+ * `react-native-alias.spec.ts` asserts every row of the LAYER's own registry starts
282
+ * with one of them, so a surface named outside this list fails a test rather than
283
+ * being silently skipped by the alias and the gate together.
284
+ *
285
+ * `@gjsify/react-native` is here because a gjsify-native application writes the target
286
+ * directly, and the gate watches both spellings of every surface.
287
+ */
288
+ export const SURFACE_NAME_PREFIXES = [
289
+ 'react-native',
290
+ 'expo-',
291
+ '@expo/',
292
+ '@react-native-',
293
+ 'nativewind',
294
+ REACT_NATIVE_ALIAS_TARGET,
295
+ ];
296
+ /** Could this specifier be a declared surface? Cheap, and deliberately over-inclusive. */
297
+ export const couldBeSurfaceSpecifier = (specifier) => SURFACE_NAME_PREFIXES.some((prefix) => specifier.startsWith(prefix));
298
+ /**
299
+ * Does this SOURCE mention a specifier that could be a surface?
300
+ *
301
+ * Anchored on the opening QUOTE, which is the whole point: a bare `expo-` test over
302
+ * source text would match the word `export`, so the fragment has to sit where a module
303
+ * specifier sits. Over-inclusive by design (a string literal in a comment matches);
304
+ * under-inclusive never, which is the direction that matters for a gate.
305
+ */
306
+ export const SURFACE_MENTION = new RegExp(`['"\`](?:${SURFACE_NAME_PREFIXES.map((prefix) => prefix.replace(/[/@.]/g, '\\$&')).join('|')})`);
@@ -1,36 +1,40 @@
1
1
  import type { Plugin } from 'rolldown';
2
+ import { type AliasedSurface, type LayerReader } from './react-native-alias.js';
2
3
  import { ImportScanParseError, type NamedImport, type OpaqueReference } from '../utils/scan-named-imports.js';
3
4
  /**
4
- * The two functions the gate needs off the support table.
5
+ * What the gate needs off the layer, and it is now three things rather than two.
5
6
  *
6
- * Structural, so a spec can pass a three-row fixture and so the plugin never
7
- * types itself against a tier-3 package it must not import.
7
+ * `SURFACES` is the addition ADR 0036 makes: the gate has to know WHICH specifiers to
8
+ * watch, and that list is the layer's rather than the plugin's. A structural type, so
9
+ * a spec can pass a three-row fixture and so the plugin never types itself against a
10
+ * tier-3 package it must not import.
8
11
  */
9
- export interface SupportTableReader {
10
- /**
11
- * May a build import this name from the layer?
12
- *
13
- * TWO POPULATIONS, and the layer composes them — not this plugin. A React Native
14
- * name is `supported` or `partial` in the support table; a name the layer ADDS
15
- * (`configureStyle` and the rest of ADR 0032 § 3's token hooks) cannot be in that
16
- * table at all, because `check-rn-surface.mjs` holds its key set EQUAL to
17
- * react-native's own exports. A gate that asked only the first question refused
18
- * the package's own documented API. A name in NEITHER is still false.
19
- */
20
- isImportable(name: string): boolean;
21
- /** The one sentence the build error and the runtime throw both print. */
22
- explainUnsupported(name: string): string;
23
- }
24
- /** The subpath that carries the table in an installed `@gjsify/react-native`. */
25
- export declare const SUPPORT_TABLE_SUBPATH = "@gjsify/react-native/support-table";
12
+ export type SupportTableReader = LayerReader;
13
+ export { SUPPORT_TABLE_SUBPATH, SupportTableUnreadableError } from './react-native-alias.js';
14
+ export type { SupportTableResolver } from './react-native-alias.js';
15
+ /**
16
+ * `loadLayer` under its historical name.
17
+ *
18
+ * Kept because it is a published export of `@gjsify/rolldown-plugin-gjsify`, and
19
+ * renaming a published function to say "surfaces" as well as "table" would be a
20
+ * breaking change for nothing — the loader reads one module either way.
21
+ */
22
+ export { loadLayer as loadSupportTable } from './react-native-alias.js';
23
+ /**
24
+ * Every specifier the gate watches, from the layer's own registry.
25
+ *
26
+ * BOTH SPELLINGS OF EACH SURFACE. A ported application writes `expo-status-bar` (the
27
+ * alias rewrites it, and the rewrite happens in `resolveId` — the SOURCE text still
28
+ * says `expo-status-bar` when this hook reads it). A gjsify-native application, this
29
+ * repository's own showcase included, writes `@gjsify/react-native/expo-status-bar`.
30
+ * The gate is about the SURFACE, not about which name reached it.
31
+ */
32
+ export declare const watchedSpecifiers: (surfaces: readonly AliasedSurface[]) => readonly string[];
26
33
  /**
27
- * Both spellings of the layer are watched.
34
+ * The two specifiers that are known WITHOUT reading the layer.
28
35
  *
29
- * A ported application writes `react-native` (the alias rewrites it, and the
30
- * rewrite happens in `resolveId` — the SOURCE text still says `react-native`
31
- * when this hook reads it). A gjsify-native application, this repository's own
32
- * showcase included, writes `@gjsify/react-native`. The gate is about the
33
- * SURFACE, not about which name reached it.
36
+ * Kept as a published constant because it was one, and it is now the FALLBACK rather
37
+ * than the whole list: `watchedSpecifiers(layer.SURFACES)` is what a real build uses.
34
38
  */
35
39
  export declare const WATCHED_SPECIFIERS: readonly string[];
36
40
  /** One refused import, with everything the message needs. */
@@ -49,11 +53,6 @@ export declare class ReactNativeUnsupportedImportError extends Error {
49
53
  readonly name = "ReactNativeUnsupportedImportError";
50
54
  constructor(id: string, violations: readonly SupportViolation[]);
51
55
  }
52
- /** Thrown when the gate is on but the table it must read is not reachable. */
53
- export declare class SupportTableUnreadableError extends Error {
54
- readonly name = "SupportTableUnreadableError";
55
- constructor(detail: string, cause?: unknown);
56
- }
57
56
  /**
58
57
  * All violations for one module as one message.
59
58
  *
@@ -81,21 +80,6 @@ export declare function formatOpaqueReference(id: string, ref: OpaqueReference):
81
80
  * loud, per file, rather than degrade quietly.
82
81
  */
83
82
  export declare function formatUnreadableModule(error: ImportScanParseError): string;
84
- /** The resolver slice this plugin needs, so `loadSupportTable` is testable. */
85
- export interface SupportTableResolver {
86
- resolve(specifier: string, importer: string): Promise<{
87
- id: string;
88
- external?: boolean | string;
89
- } | null>;
90
- }
91
- /**
92
- * Resolve and import the support table.
93
- *
94
- * `importer` is a module from the project being built, so the resolve lands in
95
- * the PROJECT's dependency tree rather than the CLI's — the gate must describe
96
- * the layer this bundle will actually contain.
97
- */
98
- export declare function loadSupportTable(resolver: SupportTableResolver, importer: string, load?: (href: string) => Promise<unknown>): Promise<SupportTableReader>;
99
83
  export interface ReactNativeSupportGateOptions {
100
84
  /**
101
85
  * The table, when the caller already has it. Left unset in a real build,
@@ -32,22 +32,35 @@
32
32
  // gate then states exactly what the build's own copy of the layer supports, so a
33
33
  // version skew between plugin and layer cannot make the gate lie in either
34
34
  // direction.
35
- import { pathToFileURL } from 'node:url';
36
- import { REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER } from './react-native-alias.js';
35
+ import { FALLBACK_SURFACES, SURFACE_MENTION, loadLayer, } from './react-native-alias.js';
37
36
  import { REWRITE_FILTER } from './rewrite-node-modules-paths.js';
38
37
  import { ImportScanParseError, scanNamedImports, } from '../utils/scan-named-imports.js';
39
- /** The subpath that carries the table in an installed `@gjsify/react-native`. */
40
- export const SUPPORT_TABLE_SUBPATH = `${REACT_NATIVE_ALIAS_TARGET}/support-table`;
38
+ export { SUPPORT_TABLE_SUBPATH, SupportTableUnreadableError } from './react-native-alias.js';
41
39
  /**
42
- * Both spellings of the layer are watched.
40
+ * `loadLayer` under its historical name.
43
41
  *
44
- * A ported application writes `react-native` (the alias rewrites it, and the
45
- * rewrite happens in `resolveId` — the SOURCE text still says `react-native`
46
- * when this hook reads it). A gjsify-native application, this repository's own
47
- * showcase included, writes `@gjsify/react-native`. The gate is about the
48
- * SURFACE, not about which name reached it.
42
+ * Kept because it is a published export of `@gjsify/rolldown-plugin-gjsify`, and
43
+ * renaming a published function to say "surfaces" as well as "table" would be a
44
+ * breaking change for nothing — the loader reads one module either way.
49
45
  */
50
- export const WATCHED_SPECIFIERS = [REACT_NATIVE_SPECIFIER, REACT_NATIVE_ALIAS_TARGET];
46
+ export { loadLayer as loadSupportTable } from './react-native-alias.js';
47
+ /**
48
+ * Every specifier the gate watches, from the layer's own registry.
49
+ *
50
+ * BOTH SPELLINGS OF EACH SURFACE. A ported application writes `expo-status-bar` (the
51
+ * alias rewrites it, and the rewrite happens in `resolveId` — the SOURCE text still
52
+ * says `expo-status-bar` when this hook reads it). A gjsify-native application, this
53
+ * repository's own showcase included, writes `@gjsify/react-native/expo-status-bar`.
54
+ * The gate is about the SURFACE, not about which name reached it.
55
+ */
56
+ export const watchedSpecifiers = (surfaces) => surfaces.flatMap((surface) => [surface.module, surface.target]);
57
+ /**
58
+ * The two specifiers that are known WITHOUT reading the layer.
59
+ *
60
+ * Kept as a published constant because it was one, and it is now the FALLBACK rather
61
+ * than the whole list: `watchedSpecifiers(layer.SURFACES)` is what a real build uses.
62
+ */
63
+ export const WATCHED_SPECIFIERS = watchedSpecifiers(FALLBACK_SURFACES);
51
64
  /** Thrown for every refused import in one module, in one message. */
52
65
  export class ReactNativeUnsupportedImportError extends Error {
53
66
  id;
@@ -59,17 +72,6 @@ export class ReactNativeUnsupportedImportError extends Error {
59
72
  this.violations = violations;
60
73
  }
61
74
  }
62
- /** Thrown when the gate is on but the table it must read is not reachable. */
63
- export class SupportTableUnreadableError extends Error {
64
- name = 'SupportTableUnreadableError';
65
- constructor(detail, cause) {
66
- super(`gjsify react-native gate: cannot read ${SUPPORT_TABLE_SUBPATH} — ${detail}. The gate has no ` +
67
- `second source to fall back on, and ADR 0032 § 8 is explicit that a hand-maintained table ` +
68
- `beside it would be the second truth this repository has already collected several times. ` +
69
- `Install ${REACT_NATIVE_ALIAS_TARGET} (and build it, if it is a workspace link), or drop ` +
70
- `the react-native opt-in.`, cause === undefined ? undefined : { cause });
71
- }
72
- }
73
75
  /**
74
76
  * All violations for one module as one message.
75
77
  *
@@ -79,7 +81,7 @@ export class SupportTableUnreadableError extends Error {
79
81
  * run.
80
82
  */
81
83
  export function formatSupportViolations(id, violations) {
82
- const lines = violations.map((v) => ` ${id}:${v.line}:${v.column} ${v.name}\n ${v.reason}`);
84
+ const lines = violations.map((v) => ` ${id}:${v.line}:${v.column} ${v.name} (from "${v.specifier}")\n ${v.reason}`);
83
85
  return (`gjsify react-native gate: ${violations.length} import(s) this layer does not answer for:\n` +
84
86
  `${lines.join('\n')}\n` +
85
87
  `Each sentence above is the support table's own (ADR 0032 § 8), so it is the same reason the ` +
@@ -90,7 +92,13 @@ export function formatSupportViolations(id, violations) {
90
92
  export function findSupportViolations(named, table) {
91
93
  const out = [];
92
94
  for (const entry of named) {
93
- if (table.isImportable(entry.name))
95
+ // THE SPECIFIER IS PASSED, and after ADR 0036 it is load-bearing rather than
96
+ // extra context: `StatusBar` is a `react-native` export AND the whole of
97
+ // `expo-status-bar`, `Image` is `react-native`'s and `expo-image`'s. Without
98
+ // the module the lookup answers from whichever surface the registry lists
99
+ // first, so `import { Image } from 'expo-image'` would report react-native's
100
+ // "is available" and the build would go green on a name nothing implements.
101
+ if (table.isImportable(entry.name, entry.specifier))
94
102
  continue;
95
103
  out.push({
96
104
  name: entry.name,
@@ -100,7 +108,7 @@ export function findSupportViolations(named, table) {
100
108
  // The table's sentence, never a rephrasing of it: `explainUnsupported`
101
109
  // exists so the build error and the runtime throw cannot describe the
102
110
  // same gap differently.
103
- reason: table.explainUnsupported(entry.name),
111
+ reason: table.explainUnsupported(entry.name, entry.specifier),
104
112
  });
105
113
  }
106
114
  return out;
@@ -137,44 +145,6 @@ export function formatUnreadableModule(error) {
137
145
  return (`${error.message} The build continues: the runtime refusal reads the same table (ADR 0032 § 8) and ` +
138
146
  `still covers this module, but it fires when the code path runs rather than now.`);
139
147
  }
140
- /**
141
- * Resolve and import the support table.
142
- *
143
- * `importer` is a module from the project being built, so the resolve lands in
144
- * the PROJECT's dependency tree rather than the CLI's — the gate must describe
145
- * the layer this bundle will actually contain.
146
- */
147
- export async function loadSupportTable(resolver, importer, load = (href) => import(/* @vite-ignore */ href)) {
148
- let resolved;
149
- try {
150
- resolved = await resolver.resolve(SUPPORT_TABLE_SUBPATH, importer);
151
- }
152
- catch (cause) {
153
- throw new SupportTableUnreadableError(`the resolver threw for ${importer}`, cause);
154
- }
155
- if (!resolved)
156
- throw new SupportTableUnreadableError(`it does not resolve from ${importer}`);
157
- if (resolved.external) {
158
- throw new SupportTableUnreadableError(`it resolved as EXTERNAL from ${importer}, so there is no file to read`);
159
- }
160
- let mod;
161
- try {
162
- // A file path, not the bare specifier: GJS's ESM loader does not follow
163
- // `package.json#exports`, so the bare form works on Node and fails under
164
- // the GJS engine — the same limitation the CLI's by-name plugin loader
165
- // documents. The resolver already did the exports-map hop.
166
- mod = await load(pathToFileURL(resolved.id).href);
167
- }
168
- catch (cause) {
169
- throw new SupportTableUnreadableError(`importing ${resolved.id} failed`, cause);
170
- }
171
- const candidate = mod;
172
- if (typeof candidate.isImportable !== 'function' || typeof candidate.explainUnsupported !== 'function') {
173
- throw new SupportTableUnreadableError(`${resolved.id} does not export isImportable + explainUnsupported. ` +
174
- `That is a version skew between the bundler plugin and the layer, not a missing install`);
175
- }
176
- return { isImportable: candidate.isImportable, explainUnsupported: candidate.explainUnsupported };
177
- }
178
148
  /**
179
149
  * Fail the build on an import of a React Native name whose status is not
180
150
  * `supported` or `partial`.
@@ -201,20 +171,27 @@ export function reactNativeSupportGatePlugin(options = {}) {
201
171
  // a data URL, all of which acorn would reject as invalid JS.
202
172
  if (!REWRITE_FILTER.test(id))
203
173
  return null;
204
- // Text prefilter before the parse, the same cheap gate
205
- // `shouldInline` uses: both watched specifiers contain
206
- // `react-native`, so one `includes` clears every module that has
207
- // nothing to do with this layer.
208
- if (!code.includes(REACT_NATIVE_SPECIFIER))
174
+ // Text prefilter before the parse AND before the layer is read.
175
+ // ADR 0036 turned two watched specifiers into eighteen surfaces with
176
+ // two spellings each, and the list of those is the LAYER's — so the
177
+ // gate cannot know what to scan for until it has read it, and reading
178
+ // it on every module would make the one module that mentions a surface
179
+ // in a comment pay for the whole build. `SURFACE_MENTION` is anchored
180
+ // on the opening quote of a module specifier, which is why a bare
181
+ // `expo-` test is not enough: the word `export` contains it.
182
+ if (!SURFACE_MENTION.test(code))
209
183
  return null;
210
184
  // The ONE catch in this file, with a real throw path and a
211
185
  // stated reason: `scanNamedImports` throws on syntax the pinned
212
186
  // acorn-typescript cannot read, and the policy for that lives
213
187
  // here rather than in the pure function — see
214
188
  // {@link formatUnreadableModule}.
189
+ // The layer FIRST, because its registry is what says which specifiers
190
+ // to scan for. The load is once per build and cached on the closure.
191
+ table ??= await loadLayer(this, id);
215
192
  let scanned;
216
193
  try {
217
- scanned = scanNamedImports(code, id, WATCHED_SPECIFIERS);
194
+ scanned = scanNamedImports(code, id, watchedSpecifiers(table.SURFACES));
218
195
  }
219
196
  catch (error) {
220
197
  if (!(error instanceof ImportScanParseError))
@@ -225,7 +202,6 @@ export function reactNativeSupportGatePlugin(options = {}) {
225
202
  const { named, opaque } = scanned;
226
203
  if (named.length === 0 && opaque.length === 0)
227
204
  return null;
228
- table ??= await loadSupportTable(this, id);
229
205
  const violations = findSupportViolations(named, table);
230
206
  if (violations.length > 0)
231
207
  throw new ReactNativeUnsupportedImportError(id, violations);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/rolldown-plugin-gjsify",
3
- "version": "0.45.0",
3
+ "version": "0.47.0",
4
4
  "description": "Rolldown / Rollup / Vite plugin orchestrator for GJS, Node, and Browser targets",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -60,11 +60,11 @@
60
60
  ],
61
61
  "license": "MIT",
62
62
  "dependencies": {
63
- "@gjsify/console": "^0.45.0",
64
- "@gjsify/resolve-npm": "^0.45.0",
65
- "@gjsify/rolldown-plugin-deepkit": "^0.45.0",
66
- "@gjsify/rolldown-plugin-pnp": "^0.45.0",
67
- "@gjsify/vite-plugin-blueprint": "^0.45.0",
63
+ "@gjsify/console": "^0.47.0",
64
+ "@gjsify/resolve-npm": "^0.47.0",
65
+ "@gjsify/rolldown-plugin-deepkit": "^0.47.0",
66
+ "@gjsify/rolldown-plugin-pnp": "^0.47.0",
67
+ "@gjsify/vite-plugin-blueprint": "^0.47.0",
68
68
  "@rollup/pluginutils": "^5.4.0",
69
69
  "acorn": "^8.17.0",
70
70
  "acorn-typescript": "^1.4.13",
@@ -74,7 +74,7 @@
74
74
  "sass": "^1.101.0"
75
75
  },
76
76
  "peerDependencies": {
77
- "@gjsify/lightningcss-native": "^0.45.0",
77
+ "@gjsify/lightningcss-native": "^0.47.0",
78
78
  "rolldown": "^1.1.4"
79
79
  },
80
80
  "peerDependenciesMeta": {
@@ -86,8 +86,8 @@
86
86
  }
87
87
  },
88
88
  "devDependencies": {
89
- "@gjsify/cli": "^0.45.0",
90
- "@gjsify/unit": "^0.45.0",
89
+ "@gjsify/cli": "^0.47.0",
90
+ "@gjsify/unit": "^0.47.0",
91
91
  "@types/node": "^25.9.2",
92
92
  "rolldown": "^1.1.4",
93
93
  "typescript": "^6.0.3"