@gjsify/rolldown-plugin-gjsify 0.60.0 → 0.61.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.
@@ -31,6 +31,7 @@ import { giRendererPlugin } from '../plugins/gi-renderer.js';
31
31
  import { blueprintTemplateExit } from '../utils/blueprint-template-exit.js';
32
32
  import { cssAsStringPlugin } from '../plugins/css-as-string.js';
33
33
  import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
34
+ import { gschemaPlugin } from '../plugins/gschema.js';
34
35
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
35
36
  export const setupForBrowser = async (input) => {
36
37
  const userExternal = input.userExternal ?? [];
@@ -143,6 +144,7 @@ export const setupForBrowser = async (input) => {
143
144
  // readily as a read, so `window = {…}` would emit `globalThis = {…}` — code that
144
145
  // replaces the whole global object. This transform runs first and leaves the define
145
146
  // nothing to rewrite; the guarded branch stays unreachable on a page either way.
147
+ gschemaPlugin({ mode: 'emit' }),
146
148
  implicitGlobalAssignPlugin(),
147
149
  cssAsStringPlugin(),
148
150
  // `order: 'post'` — see app/gjs.ts. The browser target's whole job is to
package/lib/app/gjs.js CHANGED
@@ -21,6 +21,7 @@ import { nodeModulesPathRewritePlugin, getBundleDirFromOutput } from '../plugins
21
21
  import { processStubPlugin } from '../plugins/process-stub.js';
22
22
  import { cssAsStringPlugin } from '../plugins/css-as-string.js';
23
23
  import { consoleAssignPlugin } from '../plugins/console-assign.js';
24
+ import { gschemaPlugin } from '../plugins/gschema.js';
24
25
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
25
26
  import { shebangPlugin, resolveShebangLine, inputShebangStripPlugin } from '../plugins/shebang.js';
26
27
  import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
@@ -201,6 +202,7 @@ export const setupForGjs = async (input) => {
201
202
  // define in `transform.define` replaces the assignment TARGET, emitting
202
203
  // `globalThis = {…}`, i.e. code that would replace the whole global object if the
203
204
  // branch ever ran. Measured; the plugin header carries the artifact diff.
205
+ gschemaPlugin({ mode: 'ignore' }),
204
206
  implicitGlobalAssignPlugin(),
205
207
  // `gi://Ns?version=X&optional` → a guarded import (ADR 0087), claimed `pre`
206
208
  // so the externals policy never sees the flagged specifier.
@@ -47,6 +47,7 @@ import { giRendererPlugin } from '../plugins/gi-renderer.js';
47
47
  import { blueprintTemplateExit } from '../utils/blueprint-template-exit.js';
48
48
  import { platformResolvePlugin, nativescriptSuffixChain, detectNativescriptPlatform, nativescriptPlatformDefines, } from '../plugins/platform-resolve.js';
49
49
  import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
50
+ import { gschemaPlugin } from '../plugins/gschema.js';
50
51
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
51
52
  // Never bundled: an app works only with the ONE core instance the runtime boots, and what
52
53
  // this target emits is an ENTRY the NS bundler resolves it for. An optional peer absent from
@@ -186,6 +187,7 @@ export const setupForNativescript = async (input) => {
186
187
  // a NativeScript app gates on the absence of `window`, but a WRITE to it is a
187
188
  // `ReferenceError` in a strict module body, not a gate. `globalThis.window = …`
188
189
  // holds the author's intent; the guard and the absence are untouched.
190
+ gschemaPlugin({ mode: 'emit' }),
189
191
  implicitGlobalAssignPlugin(),
190
192
  // NO cssAsStringPlugin — NS ships its own CSS pipeline via
191
193
  // @nativescript/core; .css imports are handled by the consuming
package/lib/app/node.js CHANGED
@@ -11,6 +11,7 @@ import { cssAsStringPlugin } from '../plugins/css-as-string.js';
11
11
  import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
12
12
  import { gjsGiNodePlugin, gjsBuiltinModulesNodePlugin } from '../plugins/gjs-gi-node.js';
13
13
  import { giOptionalPlugin } from '../plugins/gi-optional.js';
14
+ import { gschemaPlugin } from '../plugins/gschema.js';
14
15
  import { implicitGlobalAssignPlugin } from '../plugins/implicit-global-assign.js';
15
16
  import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
16
17
  import { nodeNativeExternalPlugin } from '../plugins/node-native-external.js';
@@ -351,6 +352,7 @@ export const setupForNode = async (input) => {
351
352
  // in a strict module body that write is a `ReferenceError` (map-editor#300).
352
353
  // `globalThis.window = {…}` is the same statement, the guard is untouched, and
353
354
  // nothing defines `window`.
355
+ gschemaPlugin({ mode: 'ignore' }),
354
356
  implicitGlobalAssignPlugin(),
355
357
  // Decides the fate of `@girs/*` before `aliasPlugin` and the default
356
358
  // resolver (same composition order as `app/browser.ts`). `emptyGirs` is
package/lib/index.d.ts CHANGED
@@ -13,6 +13,8 @@ export type { GiSystemProbe } from './plugins/gi-runtime-paths.js';
13
13
  export type { ProcessStubPluginOptions } from './plugins/process-stub.js';
14
14
  export { findImplicitGlobalAssignments, rewriteImplicitGlobalAssignments, implicitGlobalAssignPlugin, IMPLICIT_GLOBAL_ASSIGN_PLUGIN, } from './plugins/implicit-global-assign.js';
15
15
  export type { ImplicitGlobalAssignment } from './plugins/implicit-global-assign.js';
16
+ export { gschemaPlugin, parseGschema, GSCHEMA_PLUGIN } from './plugins/gschema.js';
17
+ export type { GschemaOut, GschemaKey, GschemaPluginOptions } from './plugins/gschema.js';
16
18
  export { cssAsStringPlugin } from './plugins/css-as-string.js';
17
19
  export { textLoaderPlugin } from './plugins/text-loader.js';
18
20
  export type { TextLoaderPluginOptions, LoaderKind } from './plugins/text-loader.js';
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ export { giRuntimePathsStub } from './plugins/gi-runtime-paths.js';
9
9
  export { giOptionalPlugin, giOptionalShimSource, giOptionalNodeShimSource, giOptionalMarkerSource, parseOptionalGiSpecifier, GI_OPTIONAL_FLAG, GI_OPTIONAL_MARKER, } from './plugins/gi-optional.js';
10
10
  export { bindConsoleLocally, consoleAssignPlugin, freeConsoleAssignmentInsertion, CONSOLE_LOCAL_BINDING, } from './plugins/console-assign.js';
11
11
  export { findImplicitGlobalAssignments, rewriteImplicitGlobalAssignments, implicitGlobalAssignPlugin, IMPLICIT_GLOBAL_ASSIGN_PLUGIN, } from './plugins/implicit-global-assign.js';
12
+ export { gschemaPlugin, parseGschema, GSCHEMA_PLUGIN } from './plugins/gschema.js';
12
13
  export { cssAsStringPlugin } from './plugins/css-as-string.js';
13
14
  export { textLoaderPlugin } from './plugins/text-loader.js';
14
15
  export { shebangPlugin, GJS_SHEBANG, NODE_SHEBANG, expandEnvTemplate, resolveShebangLine } from './plugins/shebang.js';
@@ -0,0 +1,17 @@
1
+ import type { Plugin } from 'rolldown';
2
+ export declare const GSCHEMA_PLUGIN = "gjsify-gschema";
3
+ export interface GschemaKey {
4
+ type: 'b' | 'i' | 'u' | 'd' | 's';
5
+ default: boolean | number | string;
6
+ }
7
+ export interface GschemaOut {
8
+ id: string;
9
+ path: string;
10
+ keys: Record<string, GschemaKey>;
11
+ }
12
+ export declare function parseGschema(xml: string, file: string): GschemaOut;
13
+ export interface GschemaPluginOptions {
14
+ /** `emit`: the schema object (browser, NativeScript). `ignore`: the file path, for GJS/Node. */
15
+ mode: 'emit' | 'ignore';
16
+ }
17
+ export declare function gschemaPlugin({ mode }: GschemaPluginOptions): Plugin;
@@ -0,0 +1,116 @@
1
+ // `./x.gschema.xml?schema` (ADR 0099 § 1): the schema a `Settings` is registered with.
2
+ //
3
+ // The `.gschema.xml` stays the single source. On the targets with no Gio.Settings (browser,
4
+ // NativeScript) the build reads it and emits `{ id, path, keys: { name: { type, default } } }`;
5
+ // the runtime never parses XML. On GJS and Node the compiled schema is what GSettings reads
6
+ // (`gjsify gsettings` builds it), so the import evaluates to the file's path and is ignored by
7
+ // the no-op `Settings.register`.
8
+ //
9
+ // What the subset cannot honour is refused HERE, naming the key and the line, rather than
10
+ // compiled into a schema that behaves differently from GNOME's: a key type outside `b i u d s`,
11
+ // `<range>`, `<choices>`, `<enum>`, `<flags>`, `<child>`, `<override>`, and a schema without
12
+ // `path` (the storage key is `<path><key>`).
13
+ import { readFile } from 'node:fs/promises';
14
+ export const GSCHEMA_PLUGIN = 'gjsify-gschema';
15
+ const KEY_TYPES = new Set(['b', 'i', 'u', 'd', 's']);
16
+ const REFUSED_TAGS = ['range', 'choices', 'enum', 'flags', 'child', 'override'];
17
+ function lineOf(xml, index) {
18
+ return xml.slice(0, index).split('\n').length;
19
+ }
20
+ function attr(tag, name) {
21
+ return new RegExp(`\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`)
22
+ .exec(tag)
23
+ ?.slice(1)
24
+ .find((v) => v !== undefined);
25
+ }
26
+ function unescapeXml(s) {
27
+ return s
28
+ .replace(/&lt;/g, '<')
29
+ .replace(/&gt;/g, '>')
30
+ .replace(/&quot;/g, '"')
31
+ .replace(/&apos;/g, "'")
32
+ .replace(/&amp;/g, '&');
33
+ }
34
+ /** GVariant text format for the five supported types. */
35
+ function parseDefault(raw, type, where) {
36
+ const text = unescapeXml(raw.trim());
37
+ if (type === 'b') {
38
+ if (text === 'true' || text === 'false')
39
+ return text === 'true';
40
+ }
41
+ else if (type === 's') {
42
+ const m = /^(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")$/.exec(text);
43
+ if (m)
44
+ return (m[1] ?? m[2]).replace(/\\(.)/g, '$1');
45
+ }
46
+ else {
47
+ const n = Number(text);
48
+ if (text !== '' && Number.isFinite(n) && (type === 'd' || Number.isInteger(n)) && !(type === 'u' && n < 0)) {
49
+ return n;
50
+ }
51
+ }
52
+ throw new Error(`${where}: default '${text}' is not a valid '${type}' value`);
53
+ }
54
+ export function parseGschema(xml, file) {
55
+ const source = xml.replace(/<!--[\s\S]*?-->/g, (c) => c.replace(/[^\n]/g, ' '));
56
+ for (const tag of REFUSED_TAGS) {
57
+ const hit = new RegExp(`<${tag}[\\s>/]`).exec(source);
58
+ if (hit) {
59
+ throw new Error(`${file}:${lineOf(source, hit.index)}: <${tag}> is not part of the Gio.Settings subset (ADR 0099 § 1)`);
60
+ }
61
+ }
62
+ const schemas = [...source.matchAll(/<schema\b[^>]*>/g)];
63
+ if (schemas.length !== 1) {
64
+ throw new Error(`${file}: expected exactly one <schema>, found ${schemas.length} (ADR 0099 § 1)`);
65
+ }
66
+ const head = schemas[0];
67
+ const id = attr(head[0], 'id');
68
+ const path = attr(head[0], 'path');
69
+ const at = `${file}:${lineOf(source, head.index)}`;
70
+ if (!id)
71
+ throw new Error(`${at}: <schema> has no id`);
72
+ if (!path)
73
+ throw new Error(`${at}: <schema id="${id}"> has no path; the browser and Android need one to build a storage key`);
74
+ const keys = {};
75
+ for (const m of source.matchAll(/<key\b([^>]*)>([\s\S]*?)<\/key>/g)) {
76
+ const where = `${file}:${lineOf(source, m.index)}`;
77
+ const name = attr(m[1], 'name');
78
+ const type = attr(m[1], 'type');
79
+ if (!name)
80
+ throw new Error(`${where}: <key> has no name`);
81
+ if (!type || !KEY_TYPES.has(type)) {
82
+ throw new Error(`${where}: key '${name}' has type '${type}'; the subset supports b, i, u, d, s`);
83
+ }
84
+ const def = /<default\b[^>]*>([\s\S]*?)<\/default>/.exec(m[2]);
85
+ if (!def)
86
+ throw new Error(`${where}: key '${name}' has no <default>`);
87
+ keys[name] = {
88
+ type: type,
89
+ default: parseDefault(def[1], type, `${where}: key '${name}'`),
90
+ };
91
+ }
92
+ return { id, path, keys };
93
+ }
94
+ const SUFFIX = /\.gschema\.xml\?schema(?:&|$)/;
95
+ export function gschemaPlugin({ mode }) {
96
+ return {
97
+ name: GSCHEMA_PLUGIN,
98
+ async resolveId(source, importer) {
99
+ if (!SUFFIX.test(source))
100
+ return null;
101
+ const file = source.slice(0, source.indexOf('?'));
102
+ const resolved = await this.resolve(file, importer, { skipSelf: true });
103
+ return resolved === null ? null : `${resolved.id}${source.slice(source.indexOf('?'))}`;
104
+ },
105
+ async load(id) {
106
+ if (!SUFFIX.test(id))
107
+ return null;
108
+ const file = id.slice(0, id.indexOf('?'));
109
+ if (mode === 'ignore')
110
+ return `export default ${JSON.stringify(file)};`;
111
+ const schema = parseGschema(await readFile(file, 'utf8'), file);
112
+ this.addWatchFile(file);
113
+ return `export default ${JSON.stringify(schema)};`;
114
+ },
115
+ };
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/rolldown-plugin-gjsify",
3
- "version": "0.60.0",
3
+ "version": "0.61.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",
@@ -63,12 +63,12 @@
63
63
  ],
64
64
  "license": "MIT",
65
65
  "dependencies": {
66
- "@gjsify/console": "^0.60.0",
67
- "@gjsify/resolve-npm": "^0.60.0",
68
- "@gjsify/rolldown-plugin-deepkit": "^0.60.0",
69
- "@gjsify/rolldown-plugin-pnp": "^0.60.0",
70
- "@gjsify/utils": "^0.60.0",
71
- "@gjsify/vite-plugin-blueprint": "^0.60.0",
66
+ "@gjsify/console": "^0.61.0",
67
+ "@gjsify/resolve-npm": "^0.61.0",
68
+ "@gjsify/rolldown-plugin-deepkit": "^0.61.0",
69
+ "@gjsify/rolldown-plugin-pnp": "^0.61.0",
70
+ "@gjsify/utils": "^0.61.0",
71
+ "@gjsify/vite-plugin-blueprint": "^0.61.0",
72
72
  "@rollup/pluginutils": "^5.4.0",
73
73
  "acorn": "^8.17.0",
74
74
  "acorn-typescript": "^1.4.13",
@@ -78,7 +78,7 @@
78
78
  "sass": "^1.101.0"
79
79
  },
80
80
  "peerDependencies": {
81
- "@gjsify/lightningcss-native": "^0.60.0",
81
+ "@gjsify/lightningcss-native": "^0.61.0",
82
82
  "rolldown": "^1.1.4"
83
83
  },
84
84
  "peerDependenciesMeta": {
@@ -90,8 +90,8 @@
90
90
  }
91
91
  },
92
92
  "devDependencies": {
93
- "@gjsify/cli": "^0.60.0",
94
- "@gjsify/unit": "^0.60.0",
93
+ "@gjsify/cli": "^0.61.0",
94
+ "@gjsify/unit": "^0.61.0",
95
95
  "@types/node": "^25.9.2",
96
96
  "rolldown": "^1.1.4",
97
97
  "typescript": "^6.0.3"