@gjsify/rolldown-plugin-gjsify 0.4.0 → 0.4.4
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/package.json +71 -67
- package/src/app/browser.ts +0 -101
- package/src/app/gjs.ts +0 -334
- package/src/app/index.ts +0 -6
- package/src/app/node.ts +0 -127
- package/src/globals.ts +0 -11
- package/src/index.ts +0 -34
- package/src/library/index.ts +0 -2
- package/src/library/lib.ts +0 -141
- package/src/plugin.ts +0 -96
- package/src/plugins/alias.ts +0 -61
- package/src/plugins/css-as-string.ts +0 -189
- package/src/plugins/gjs-imports-empty.ts +0 -29
- package/src/plugins/process-stub.ts +0 -91
- package/src/plugins/rewrite-node-modules-paths.ts +0 -169
- package/src/plugins/shebang.ts +0 -93
- package/src/plugins/text-loader.ts +0 -54
- package/src/shims/console-gjs.ts +0 -25
- package/src/shims/unicorn-magic.ts +0 -75
- package/src/types/app.ts +0 -1
- package/src/types/index.ts +0 -3
- package/src/types/plugin-options.ts +0 -48
- package/src/types/resolve-alias-options.ts +0 -1
- package/src/utils/alias.ts +0 -46
- package/src/utils/auto-globals.ts +0 -321
- package/src/utils/detect-free-globals.ts +0 -284
- package/src/utils/entry-points.ts +0 -48
- package/src/utils/extension.ts +0 -7
- package/src/utils/index.ts +0 -7
- package/src/utils/inline-static-reads.ts +0 -541
- package/src/utils/merge.ts +0 -22
- package/src/utils/scan-globals.ts +0 -91
- package/tsconfig.json +0 -16
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// For `--app browser`: redirect `@girs/*` and `gi://*` imports to an empty
|
|
2
|
-
// module. These are GJS-specific (GObject introspection bindings / GI
|
|
3
|
-
// protocol) with no browser equivalent. They appear transitively via
|
|
4
|
-
// `@gjsify/unit` and similar packages that have GJS-specific code paths.
|
|
5
|
-
//
|
|
6
|
-
// Marking them external would leave bare specifiers in the bundle that the
|
|
7
|
-
// browser cannot resolve at runtime; instead we resolve them to a virtual
|
|
8
|
-
// empty ESM module so the bundle is self-contained.
|
|
9
|
-
|
|
10
|
-
import type { Plugin } from 'rolldown';
|
|
11
|
-
|
|
12
|
-
const GJSIMPORTS_VIRTUAL_ID = '\0gjsify-empty-gjs-import';
|
|
13
|
-
|
|
14
|
-
export function gjsImportsEmptyPlugin(): Plugin {
|
|
15
|
-
return {
|
|
16
|
-
name: 'gjsify-gjs-imports-empty',
|
|
17
|
-
resolveId: {
|
|
18
|
-
order: 'pre' as const,
|
|
19
|
-
filter: { id: /^(@girs\/|gi:\/\/)/ },
|
|
20
|
-
handler(_source) {
|
|
21
|
-
return { id: GJSIMPORTS_VIRTUAL_ID };
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
load(id) {
|
|
25
|
-
if (id !== GJSIMPORTS_VIRTUAL_ID) return null;
|
|
26
|
-
return { code: 'export {}; export default {};', moduleSideEffects: false };
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// Synchronous `globalThis.process` stub injected as a GJS bundle banner.
|
|
2
|
-
//
|
|
3
|
-
// Some npm packages (glob, path-scurry, readable-stream, …) access
|
|
4
|
-
// `globalThis.process.platform` at their top-level during lazy `__esm`
|
|
5
|
-
// initialisation — BEFORE any `import`-triggered side effects fire. A
|
|
6
|
-
// banner runs before everything, including bundler helpers and all
|
|
7
|
-
// bundled module code, making it the only reliable injection point for
|
|
8
|
-
// a synchronous global that must exist from byte 1 of execution.
|
|
9
|
-
//
|
|
10
|
-
// Only installed if `process` is absent; the full @gjsify/process
|
|
11
|
-
// implementation (with EventEmitter, real streams, etc.) is wired up
|
|
12
|
-
// later via `--globals auto` (which injects @gjsify/node-globals/register/process).
|
|
13
|
-
//
|
|
14
|
-
// Kept as a single line: the banner runs before any source-map-aware
|
|
15
|
-
// machinery, so newlines here would shift every line number by one. Single
|
|
16
|
-
// line = zero source-map drift for the actual bundle code below.
|
|
17
|
-
import type { Plugin } from 'rolldown';
|
|
18
|
-
|
|
19
|
-
export const GJS_PROCESS_STUB =
|
|
20
|
-
'if(typeof globalThis.process==="undefined"){' +
|
|
21
|
-
'const _s=imports.system,_G=imports.gi.GLib;' +
|
|
22
|
-
'globalThis.process={' +
|
|
23
|
-
'platform:"linux",arch:"x64",version:"v20.0.0",' +
|
|
24
|
-
'env:new Proxy({},{' +
|
|
25
|
-
'get(_,p){return typeof p==="string"?(_G.getenv(p)??undefined):undefined},' +
|
|
26
|
-
'set(_,p,v){if(typeof p==="string")_G.setenv(p,String(v),true);return true},' +
|
|
27
|
-
'has(_,p){return typeof p==="string"&&_G.getenv(p)!==null},' +
|
|
28
|
-
'deleteProperty(_,p){if(typeof p==="string")_G.unsetenv(p);return true},' +
|
|
29
|
-
'ownKeys(){return _G.listenv()??[]},' +
|
|
30
|
-
'getOwnPropertyDescriptor(_,p){const v=_G.getenv(p);return v!==null?{value:v,writable:true,enumerable:true,configurable:true}:undefined}' +
|
|
31
|
-
'}),' +
|
|
32
|
-
'argv:_s?.programArgs?["gjs",_s.programInvocationName||"",..._s.programArgs]:["gjs"],' +
|
|
33
|
-
'versions:{},config:{},' +
|
|
34
|
-
'cwd(){return _G.get_current_dir()||"/"},' +
|
|
35
|
-
'exit(c){_s.exit(c??0)},' +
|
|
36
|
-
'stderr:{write(s){printerr(s)}},stdout:{write(s){print(s)}},stdin:null,' +
|
|
37
|
-
'exitCode:undefined,' +
|
|
38
|
-
'nextTick(fn,...a){Promise.resolve().then(()=>fn(...a))},' +
|
|
39
|
-
'hrtime(t){return t?[0,0]:[0,0]},' +
|
|
40
|
-
'};' +
|
|
41
|
-
'}';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Compose the GJS process stub with the user-supplied banner so the result
|
|
45
|
-
* is valid syntax for `gjs -m`. A leading `#!shebang` line in the user
|
|
46
|
-
* banner is hoisted to byte 0 of the output. Any `#` character that appears
|
|
47
|
-
* anywhere except byte 0 is a fatal SyntaxError under SpiderMonkey 128+ —
|
|
48
|
-
* putting our process stub before the user's shebang would break the bundle.
|
|
49
|
-
*
|
|
50
|
-
* Output shape:
|
|
51
|
-
* [#!shebang\n][<process-stub>\n<rest-of-user-banner>]
|
|
52
|
-
*
|
|
53
|
-
* Either side of the bracket may be empty; the result is always concatenated
|
|
54
|
-
* without leading whitespace.
|
|
55
|
-
*/
|
|
56
|
-
export function composeBanner(stub: string, userBanner: string): string {
|
|
57
|
-
if (!userBanner) return stub;
|
|
58
|
-
const shebangMatch = userBanner.match(/^#![^\n]*\n/);
|
|
59
|
-
if (!shebangMatch) {
|
|
60
|
-
return stub + '\n' + userBanner;
|
|
61
|
-
}
|
|
62
|
-
const shebang = shebangMatch[0];
|
|
63
|
-
const rest = userBanner.slice(shebang.length);
|
|
64
|
-
return shebang + stub + (rest ? '\n' + rest : '');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Build a Rolldown plugin that injects the GJS process stub as a chunk
|
|
69
|
-
* banner. Runs with `enforce: 'post'`-equivalent ordering so the stub
|
|
70
|
-
* lands *after* any user `output.banner` value, except when the user
|
|
71
|
-
* banner starts with a `#!shebang` line — which is hoisted to byte 0
|
|
72
|
-
* by `composeBanner`.
|
|
73
|
-
*/
|
|
74
|
-
export interface ProcessStubPluginOptions {
|
|
75
|
-
/** User-supplied banner string. May contain a leading `#!shebang`. */
|
|
76
|
-
userBanner?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function processStubPlugin(options: ProcessStubPluginOptions = {}): Plugin {
|
|
80
|
-
const banner = composeBanner(GJS_PROCESS_STUB, options.userBanner ?? '');
|
|
81
|
-
return {
|
|
82
|
-
name: 'gjsify-process-stub',
|
|
83
|
-
renderChunk: {
|
|
84
|
-
order: 'post' as const,
|
|
85
|
-
handler(code, chunk) {
|
|
86
|
-
if (!chunk.isEntry) return null;
|
|
87
|
-
return { code: banner + '\n' + code, map: null };
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// Per-source rewriter for node_modules files that reference
|
|
2
|
-
// `import.meta.url`, `__dirname`, or `__filename`. Mirrors the esbuild
|
|
3
|
-
// predecessor's logic — body is identical because the rewrite is purely
|
|
4
|
-
// a string transform on already-loaded source. The only delta is the
|
|
5
|
-
// host: a Rolldown `transform(code, id)` plugin instead of an esbuild
|
|
6
|
-
// `onLoad` registered inside the PnP plugin.
|
|
7
|
-
//
|
|
8
|
-
// Why a separate plugin and not nested in the PnP loader, like esbuild?
|
|
9
|
-
// Rolldown / Rollup's `transform` hooks all run in sequence on every
|
|
10
|
-
// loaded module — there is no first-onLoad-wins race. So the PnP loader
|
|
11
|
-
// (`@gjsify/rolldown-plugin-pnp`) is solely responsible for reading
|
|
12
|
-
// zip-resident bytes; this plugin runs as a separate `transform` step
|
|
13
|
-
// after the bytes have been loaded, regardless of which loader produced
|
|
14
|
-
// them. No more F5-bug folklore.
|
|
15
|
-
|
|
16
|
-
import { dirname, join, relative, resolve } from 'node:path';
|
|
17
|
-
import type { Plugin } from 'rolldown';
|
|
18
|
-
|
|
19
|
-
import { inlineStaticReads } from '../utils/inline-static-reads.js';
|
|
20
|
-
|
|
21
|
-
export const REWRITE_FILTER = /\.(m?js|cjs|[cm]?tsx?)$/;
|
|
22
|
-
const DIRNAME_DECL_RE = /(?:var|let|const)\s+__dirname\b|export\s+(?:var|let|const)\s+__dirname\b/;
|
|
23
|
-
const FILENAME_DECL_RE = /(?:var|let|const)\s+__filename\b|export\s+(?:var|let|const)\s+__filename\b/;
|
|
24
|
-
|
|
25
|
-
/** True when the rewriter wants to look at this path — node_modules + supported ext. */
|
|
26
|
-
export function shouldRewrite(path: string): boolean {
|
|
27
|
-
return path.includes('node_modules') && REWRITE_FILTER.test(path);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Compute the directory the bundle's outfile lives in.
|
|
32
|
-
*
|
|
33
|
-
* For `import.meta.url` rewriting we emit a relative URL whose base is the
|
|
34
|
-
* bundle's `import.meta.url` — so we need to know where the bundle will be
|
|
35
|
-
* written. Both `output.file` and `output.dir` are accepted.
|
|
36
|
-
*/
|
|
37
|
-
export function getBundleDirFromOutput(opts: { file?: string; dir?: string }): string {
|
|
38
|
-
const outFile = opts.file ?? join(opts.dir ?? '.', 'bundle.mjs');
|
|
39
|
-
return dirname(resolve(outFile));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Pick the per-file loader Rolldown should re-parse with. */
|
|
43
|
-
function moduleTypeForPath(path: string): 'ts' | 'js' {
|
|
44
|
-
const ext = path.split('.').pop() ?? 'js';
|
|
45
|
-
return ['ts', 'mts', 'cts', 'tsx'].includes(ext) ? 'ts' : 'js';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface PreambleArgs {
|
|
49
|
-
needDirname: boolean;
|
|
50
|
-
needFilename: boolean;
|
|
51
|
-
dirnameDeclared: boolean;
|
|
52
|
-
filenameDeclared: boolean;
|
|
53
|
-
/** kind of rewrite: 'esm-relative' | 'esm-zip' | 'cjs-absolute' */
|
|
54
|
-
kind: 'esm-relative' | 'esm-zip' | 'cjs-absolute';
|
|
55
|
-
sourcePath: string;
|
|
56
|
-
sourceDir: string;
|
|
57
|
-
relDirWithSlash: string;
|
|
58
|
-
relPath: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function buildDirFilenamePreamble(args: PreambleArgs): string[] {
|
|
62
|
-
const lines: string[] = [];
|
|
63
|
-
if (args.needDirname && !args.dirnameDeclared) {
|
|
64
|
-
if (args.kind === 'esm-zip') {
|
|
65
|
-
lines.push(`var __dirname = new URL(".", import.meta.url).pathname.replace(/\\/$/, "");`);
|
|
66
|
-
} else if (args.kind === 'esm-relative') {
|
|
67
|
-
lines.push(`var __dirname = new URL(${JSON.stringify(args.relDirWithSlash)}, import.meta.url).pathname.replace(/\\/$/, "");`);
|
|
68
|
-
} else {
|
|
69
|
-
lines.push(`var __dirname = ${JSON.stringify(args.sourceDir)};`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (args.needFilename && !args.filenameDeclared) {
|
|
73
|
-
if (args.kind === 'esm-zip') {
|
|
74
|
-
lines.push(`var __filename = new URL(import.meta.url).pathname;`);
|
|
75
|
-
} else if (args.kind === 'esm-relative') {
|
|
76
|
-
lines.push(`var __filename = new URL(${JSON.stringify(args.relPath)}, import.meta.url).pathname;`);
|
|
77
|
-
} else {
|
|
78
|
-
lines.push(`var __filename = ${JSON.stringify(args.sourcePath)};`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return lines;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface RewriteResult {
|
|
85
|
-
code: string;
|
|
86
|
-
moduleType?: 'ts' | 'js';
|
|
87
|
-
map?: null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Pure rewriter — same body as the esbuild predecessor. Returns the rewritten
|
|
92
|
-
* code (and module type for re-parsing) or `null` if the file doesn't reference
|
|
93
|
-
* any of the patterns we care about.
|
|
94
|
-
*/
|
|
95
|
-
export function rewriteContents(
|
|
96
|
-
args: { path: string },
|
|
97
|
-
srcInput: string,
|
|
98
|
-
bundleDir: string,
|
|
99
|
-
): RewriteResult | null {
|
|
100
|
-
if (!shouldRewrite(args.path)) return null;
|
|
101
|
-
|
|
102
|
-
// Step 1: inline statically-resolvable filesystem reads.
|
|
103
|
-
const inlined = inlineStaticReads(srcInput, args.path);
|
|
104
|
-
const src = inlined.contents;
|
|
105
|
-
|
|
106
|
-
const hasMetaUrl = src.includes('import.meta.url');
|
|
107
|
-
const hasDirname = src.includes('__dirname');
|
|
108
|
-
const hasFilename = src.includes('__filename');
|
|
109
|
-
|
|
110
|
-
if (!hasMetaUrl && !hasDirname && !hasFilename) {
|
|
111
|
-
if (inlined.inlined === 0) return null;
|
|
112
|
-
return { code: src, moduleType: moduleTypeForPath(args.path) };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Step 2: classify rewrite kind.
|
|
116
|
-
const dir = dirname(args.path);
|
|
117
|
-
const relPath = hasMetaUrl ? relative(bundleDir, args.path) : '';
|
|
118
|
-
const isZipResident = hasMetaUrl && relPath.includes('.zip/');
|
|
119
|
-
const kind: 'esm-relative' | 'esm-zip' | 'cjs-absolute' =
|
|
120
|
-
!hasMetaUrl ? 'cjs-absolute' : isZipResident ? 'esm-zip' : 'esm-relative';
|
|
121
|
-
|
|
122
|
-
const preamble = buildDirFilenamePreamble({
|
|
123
|
-
needDirname: hasDirname,
|
|
124
|
-
needFilename: hasFilename,
|
|
125
|
-
dirnameDeclared: DIRNAME_DECL_RE.test(src),
|
|
126
|
-
filenameDeclared: FILENAME_DECL_RE.test(src),
|
|
127
|
-
kind,
|
|
128
|
-
sourcePath: args.path,
|
|
129
|
-
sourceDir: dir,
|
|
130
|
-
relPath,
|
|
131
|
-
relDirWithSlash: (relative(bundleDir, dir) || '.') + '/',
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Step 3: rewrite import.meta.url for the regular esm-relative case.
|
|
135
|
-
let code = src;
|
|
136
|
-
if (kind === 'esm-relative') {
|
|
137
|
-
const runtimeFileUrl = `new URL(${JSON.stringify(relPath)}, import.meta.url)`;
|
|
138
|
-
code = code.replace(/\bimport\.meta\.url\b/g, `${runtimeFileUrl}.href`);
|
|
139
|
-
}
|
|
140
|
-
if (preamble.length > 0) code = preamble.join('\n') + '\n' + code;
|
|
141
|
-
|
|
142
|
-
return { code, moduleType: moduleTypeForPath(args.path) };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export interface NodeModulesPathRewriteOptions {
|
|
146
|
-
/** Bundle output directory, derived from `output.file` / `output.dir`. */
|
|
147
|
-
bundleDir: string;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Build a Rolldown plugin that runs the path rewriter as a `transform(code, id)`
|
|
152
|
-
* hook with `order: 'post'` — runs after the deepkit/blueprint/css pre-transforms
|
|
153
|
-
* but still during module loading, before chunking.
|
|
154
|
-
*/
|
|
155
|
-
export function nodeModulesPathRewritePlugin(options: NodeModulesPathRewriteOptions): Plugin {
|
|
156
|
-
return {
|
|
157
|
-
name: 'gjsify-node-modules-path-rewrite',
|
|
158
|
-
transform: {
|
|
159
|
-
order: 'post' as const,
|
|
160
|
-
filter: { id: REWRITE_FILTER },
|
|
161
|
-
handler(code: string, id: string) {
|
|
162
|
-
if (!id.includes('node_modules')) return null;
|
|
163
|
-
const result = rewriteContents({ path: id }, code, options.bundleDir);
|
|
164
|
-
if (!result) return null;
|
|
165
|
-
return { code: result.code, map: null };
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
}
|
package/src/plugins/shebang.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Inject a `#!/usr/bin/env -S gjs -m` shebang at byte 0 of entry chunks.
|
|
2
|
-
//
|
|
3
|
-
// Rolldown's `output.banner` would also work, but a renderChunk hook with
|
|
4
|
-
// `order: 'post'` makes ordering declarative — the shebang lands AFTER all
|
|
5
|
-
// other banner / process-stub plugins have run, which is required because
|
|
6
|
-
// the `#` character is only valid as the very first byte of the file under
|
|
7
|
-
// SpiderMonkey 128+.
|
|
8
|
-
|
|
9
|
-
import type { Plugin } from 'rolldown';
|
|
10
|
-
|
|
11
|
-
export const GJS_SHEBANG = '#!/usr/bin/env -S gjs -m';
|
|
12
|
-
|
|
13
|
-
export interface ShebangPluginOptions {
|
|
14
|
-
enabled?: boolean;
|
|
15
|
-
/** Override the shebang line. Defaults to `GJS_SHEBANG`. */
|
|
16
|
-
line?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Strip a leading `#!…\n` from a source module. Rolldown preserves input
|
|
21
|
-
* shebangs verbatim, which ends up embedded mid-chunk after our process-stub
|
|
22
|
-
* banner — acorn (used by the auto-globals detector) then rejects `#` because
|
|
23
|
-
* it's not at byte 0 anymore. Stripping at the transform stage cleans both
|
|
24
|
-
* the analysis bundle and the final bundle; the gjsify-shebang renderChunk
|
|
25
|
-
* step then injects the correct line for the output target.
|
|
26
|
-
*/
|
|
27
|
-
const SHEBANG_RE = /^#![^\n]*\n/;
|
|
28
|
-
|
|
29
|
-
/** Always-on plugin half: strips input shebangs regardless of output options. */
|
|
30
|
-
export function inputShebangStripPlugin(): Plugin {
|
|
31
|
-
return {
|
|
32
|
-
name: 'gjsify-input-shebang-strip',
|
|
33
|
-
transform: {
|
|
34
|
-
order: 'pre' as const,
|
|
35
|
-
handler(code) {
|
|
36
|
-
if (!code.startsWith('#!')) return null;
|
|
37
|
-
return { code: code.replace(SHEBANG_RE, ''), map: null };
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function shebangPlugin(options: ShebangPluginOptions = {}): Plugin | null {
|
|
44
|
-
if (!options.enabled) return null;
|
|
45
|
-
const line = options.line ?? GJS_SHEBANG;
|
|
46
|
-
return {
|
|
47
|
-
name: 'gjsify-shebang',
|
|
48
|
-
renderChunk: {
|
|
49
|
-
order: 'post' as const,
|
|
50
|
-
handler(code, chunk) {
|
|
51
|
-
if (!chunk.isEntry) return null;
|
|
52
|
-
if (code.startsWith('#!')) return null;
|
|
53
|
-
return { code: line + '\n' + code, map: null };
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Expand `${env:NAME}` and `${env:NAME:-default}` placeholders against
|
|
61
|
-
* `process.env`. Missing without default → `''`. Used to let the shebang
|
|
62
|
-
* config field reference build-time env vars (e.g. `GJS_CONSOLE` set by
|
|
63
|
-
* meson-driven Flatpak builds where the GJS interpreter lives at
|
|
64
|
-
* `/usr/bin/gjs-console`).
|
|
65
|
-
*/
|
|
66
|
-
export function expandEnvTemplate(input: string, env: Record<string, string | undefined> = process.env): string {
|
|
67
|
-
return input.replace(/\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_match, name: string, fallback?: string) => {
|
|
68
|
-
const value = env[name];
|
|
69
|
-
if (value !== undefined && value !== '') return value;
|
|
70
|
-
return fallback ?? '';
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Normalize the user-facing `shebang` config value into the literal line
|
|
76
|
-
* that should be prepended to the bundle (without trailing newline), or
|
|
77
|
-
* `null` when shebang injection is disabled.
|
|
78
|
-
*
|
|
79
|
-
* `true` → default GJS shebang
|
|
80
|
-
* `false|undefined` → null (disabled)
|
|
81
|
-
* `"…"` → string with `${env:NAME[:-default]}` expanded
|
|
82
|
-
*
|
|
83
|
-
* If the resolved string does not start with `#!`, it is prefixed
|
|
84
|
-
* automatically so users can write `"shebang": "/usr/bin/gjs -m"` instead
|
|
85
|
-
* of `"#!/usr/bin/gjs -m"`.
|
|
86
|
-
*/
|
|
87
|
-
export function resolveShebangLine(value: boolean | string | undefined): string | null {
|
|
88
|
-
if (value === undefined || value === false) return null;
|
|
89
|
-
if (value === true) return GJS_SHEBANG;
|
|
90
|
-
const expanded = expandEnvTemplate(value);
|
|
91
|
-
if (!expanded.trim()) return null;
|
|
92
|
-
return expanded.startsWith('#!') ? expanded : '#!' + expanded;
|
|
93
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// Generic "load file as JS string default export" plugin.
|
|
2
|
-
//
|
|
3
|
-
// Mirrors `css-as-string` but lets the user opt-in arbitrary extensions
|
|
4
|
-
// through `bundler.loaders` config, e.g.:
|
|
5
|
-
//
|
|
6
|
-
// "bundler": { "loaders": { ".ui": "text", ".asm": "text" } }
|
|
7
|
-
//
|
|
8
|
-
// — replaces the esbuild `loader: { '.ui': 'text' }` shorthand from the
|
|
9
|
-
// pre-Rolldown era. Rolldown does not classify unknown extensions as text
|
|
10
|
-
// by default; without a hook it tries to parse them as JS and fails.
|
|
11
|
-
|
|
12
|
-
import { readFile } from 'node:fs/promises';
|
|
13
|
-
import type { Plugin } from 'rolldown';
|
|
14
|
-
|
|
15
|
-
export interface TextLoaderPluginOptions {
|
|
16
|
-
/**
|
|
17
|
-
* Map of file extension (with leading `.`) → loader kind. Currently only
|
|
18
|
-
* `'text'` is implemented; the field is shaped this way to leave room
|
|
19
|
-
* for `'json'` / `'binary'` later without a config break.
|
|
20
|
-
*/
|
|
21
|
-
loaders?: Record<string, 'text'>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function textLoaderPlugin(options: TextLoaderPluginOptions = {}): Plugin | null {
|
|
25
|
-
const exts = Object.entries(options.loaders ?? {})
|
|
26
|
-
.filter(([, kind]) => kind === 'text')
|
|
27
|
-
.map(([ext]) => ext);
|
|
28
|
-
|
|
29
|
-
if (exts.length === 0) return null;
|
|
30
|
-
|
|
31
|
-
// Build a single regex matching any of the configured extensions:
|
|
32
|
-
// ['.ui', '.asm'] → /\.(ui|asm)$/
|
|
33
|
-
const escaped = exts.map((e) => e.replace(/^\./, '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
34
|
-
const filter = new RegExp(`\\.(?:${escaped.join('|')})$`);
|
|
35
|
-
|
|
36
|
-
// Use the function-form `load(id)` (Rollup-compatible) rather than the
|
|
37
|
-
// newer `load: { filter, handler }` shape. The newer shape was observed
|
|
38
|
-
// not to claim unknown-extension files reliably under Rolldown rc.18 —
|
|
39
|
-
// Rolldown's parser ran BEFORE the filtered handler fired and rejected
|
|
40
|
-
// `.ui`/`.asm` content as invalid JS/JSX. Using the function form (same
|
|
41
|
-
// as `@gjsify/vite-plugin-blueprint`'s `.blp` hook) intercepts during
|
|
42
|
-
// module-load lookup and works under both Vite and Rolldown.
|
|
43
|
-
return {
|
|
44
|
-
name: 'gjsify-text-loader',
|
|
45
|
-
async load(id: string) {
|
|
46
|
-
if (!filter.test(id)) return null;
|
|
47
|
-
const code = await readFile(id, 'utf8');
|
|
48
|
-
return {
|
|
49
|
-
code: `export default ${JSON.stringify(code)};`,
|
|
50
|
-
moduleType: 'js' as const,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
package/src/shims/console-gjs.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// GJS console shim — bundled into GJS user builds via Rolldown's `inject`.
|
|
2
|
-
// Uses print()/printerr() on GJS, bypassing GLib.log_structured() — no
|
|
3
|
-
// `Gjs-Console-Message:` prefix, ANSI escapes work, output goes to
|
|
4
|
-
// stdout/stderr instead of GLib's logging stream.
|
|
5
|
-
//
|
|
6
|
-
// `@gjsify/console` is resolved by the user's `gjsify build` Rolldown
|
|
7
|
-
// run, NOT by tsc here. The bare specifier survives compilation and only
|
|
8
|
-
// gets followed at user-build time, where the CLI's `@gjsify/node-polyfills`
|
|
9
|
-
// dep tree has the package. tsc on this package would otherwise need the
|
|
10
|
-
// `@gjsify/console` lib to be pre-built (build-order coupling).
|
|
11
|
-
//
|
|
12
|
-
// We can't reassign `globalThis.console` on SpiderMonkey 128 — the
|
|
13
|
-
// property is non-configurable. Rolldown's `inject` option rewrites bare
|
|
14
|
-
// `console` references to a named import from this shim instead, leaving
|
|
15
|
-
// `globalThis.console` untouched and routing user `console.log(…)` calls
|
|
16
|
-
// through our object.
|
|
17
|
-
// @ts-ignore — resolved by Rolldown at user-build time, not by tsc here.
|
|
18
|
-
import { log, info, debug, warn, error, dir, dirxml, table, time, timeEnd, timeLog, trace, assert, clear, count, countReset, group, groupCollapsed, groupEnd, profile, profileEnd, timeStamp } from '@gjsify/console';
|
|
19
|
-
|
|
20
|
-
export const console = {
|
|
21
|
-
log, info, debug, warn, error, dir, dirxml, table,
|
|
22
|
-
time, timeEnd, timeLog, trace, assert, clear,
|
|
23
|
-
count, countReset, group, groupCollapsed, groupEnd,
|
|
24
|
-
profile, profileEnd, timeStamp,
|
|
25
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// Shim for `unicorn-magic` under --app gjs.
|
|
2
|
-
//
|
|
3
|
-
// The upstream package gates the full API (`toPath`, `traversePathUp`,
|
|
4
|
-
// `rootDirectory`, `execFile`, `execFileSync`) behind the `"node"`
|
|
5
|
-
// conditional exports entry. Under --app gjs we intentionally omit
|
|
6
|
-
// the `node` resolve-condition (cross-fetch-ponyfill ships
|
|
7
|
-
// Node-only code under that key — see `app/gjs.ts` conditionNames
|
|
8
|
-
// comment), so a bare `import { toPath } from 'unicorn-magic'` falls
|
|
9
|
-
// back to `default.js` which only exposes `delay`.
|
|
10
|
-
//
|
|
11
|
-
// This shim mirrors the node.js entry verbatim — the underlying
|
|
12
|
-
// `node:url`/`node:path`/`node:child_process`/`node:util` imports
|
|
13
|
-
// route through `@gjsify/{url,path,child_process,util}` under GJS
|
|
14
|
-
// and through real Node-internals under Node. The aliasPlugin
|
|
15
|
-
// points `unicorn-magic` here for --app gjs builds.
|
|
16
|
-
//
|
|
17
|
-
// Source-of-truth: refs/unicorn-magic/node.js (when added — for
|
|
18
|
-
// now mirrored from node_modules/unicorn-magic@0.3.0).
|
|
19
|
-
|
|
20
|
-
import { promisify } from 'node:util';
|
|
21
|
-
import { execFile as execFileCallback, execFileSync as execFileSyncOriginal } from 'node:child_process';
|
|
22
|
-
import path from 'node:path';
|
|
23
|
-
import { fileURLToPath } from 'node:url';
|
|
24
|
-
|
|
25
|
-
const execFileOriginal = promisify(execFileCallback);
|
|
26
|
-
|
|
27
|
-
export function toPath(urlOrPath) {
|
|
28
|
-
return urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function rootDirectory(pathInput) {
|
|
32
|
-
return path.parse(toPath(pathInput)).root;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function traversePathUp(startPath) {
|
|
36
|
-
return {
|
|
37
|
-
*[Symbol.iterator]() {
|
|
38
|
-
let currentPath = path.resolve(toPath(startPath));
|
|
39
|
-
let previousPath;
|
|
40
|
-
|
|
41
|
-
while (previousPath !== currentPath) {
|
|
42
|
-
yield currentPath;
|
|
43
|
-
previousPath = currentPath;
|
|
44
|
-
currentPath = path.resolve(currentPath, '..');
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const TEN_MEGABYTES_IN_BYTES = 10 * 1024 * 1024;
|
|
51
|
-
|
|
52
|
-
export async function execFile(file, arguments_, options = {}) {
|
|
53
|
-
return execFileOriginal(file, arguments_, {
|
|
54
|
-
maxBuffer: TEN_MEGABYTES_IN_BYTES,
|
|
55
|
-
...options,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function execFileSync(file, arguments_ = [], options = {}) {
|
|
60
|
-
return execFileSyncOriginal(file, arguments_, {
|
|
61
|
-
maxBuffer: TEN_MEGABYTES_IN_BYTES,
|
|
62
|
-
...options,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Re-export from default.js so the union API (delay + node helpers)
|
|
67
|
-
// stays intact for callers that import both.
|
|
68
|
-
export async function delay(opts: { seconds?: number; milliseconds?: number } = {}): Promise<void> {
|
|
69
|
-
const { seconds, milliseconds } = opts;
|
|
70
|
-
let duration: number;
|
|
71
|
-
if (typeof seconds === 'number') duration = seconds * 1000;
|
|
72
|
-
else if (typeof milliseconds === 'number') duration = milliseconds;
|
|
73
|
-
else throw new TypeError('Expected an object with either `seconds` or `milliseconds`.');
|
|
74
|
-
return new Promise<void>((resolveFn) => setTimeout(resolveFn, duration));
|
|
75
|
-
}
|
package/src/types/app.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type App = 'gjs' | 'node' | 'browser';
|
package/src/types/index.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { App } from './app.js';
|
|
2
|
-
|
|
3
|
-
/** CSS handling forwarded to Rolldown / Lightning CSS. */
|
|
4
|
-
export interface GjsifyCssOptions {
|
|
5
|
-
/** Browserslist-compatible target list. Defaults to `['firefox60']` for `--app gjs`. */
|
|
6
|
-
target?: string[];
|
|
7
|
-
/** Whether to minify the emitted CSS. Defaults to bundle-level `minify`. */
|
|
8
|
-
minify?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface PluginOptions {
|
|
12
|
-
debug?: boolean;
|
|
13
|
-
app?: App;
|
|
14
|
-
aliases?: Record<string, string>;
|
|
15
|
-
/** Glob patterns to exclude when expanding entry points. */
|
|
16
|
-
exclude?: string[];
|
|
17
|
-
jsExtension?: string;
|
|
18
|
-
/** Override the bundle output format. */
|
|
19
|
-
format?: 'esm' | 'cjs';
|
|
20
|
-
/**
|
|
21
|
-
* Library mode — `'esm' | 'cjs'`. When set, the plugin emits an
|
|
22
|
-
* unbundled multi-entry library suitable for republication on npm
|
|
23
|
-
* rather than a single application bundle.
|
|
24
|
-
*/
|
|
25
|
-
library?: 'esm' | 'cjs';
|
|
26
|
-
/**
|
|
27
|
-
* Inject a console shim into GJS builds that uses print()/printerr() instead
|
|
28
|
-
* of `GLib.log_structured()`. Removes the "Gjs-Console-Message:" prefix and
|
|
29
|
-
* lets the terminal interpret ANSI escape codes correctly. Only applies to
|
|
30
|
-
* `--app gjs`. Defaults to `true`.
|
|
31
|
-
*/
|
|
32
|
-
consoleShim?: boolean;
|
|
33
|
-
/** Enable Deepkit TypeScript reflection. Defaults to `false`. */
|
|
34
|
-
reflection?: boolean;
|
|
35
|
-
/** CSS pipeline options forwarded to the Rolldown / Lightning CSS layer. */
|
|
36
|
-
css?: GjsifyCssOptions;
|
|
37
|
-
/**
|
|
38
|
-
* Path to a pre-computed globals stub file. The stub is an ESM file
|
|
39
|
-
* containing one `import '<pkg>/register';` per entry from the user's
|
|
40
|
-
* `--globals` CLI flag. When set, the plugin appends the stub path to
|
|
41
|
-
* Rolldown's inject list alongside the console shim.
|
|
42
|
-
*
|
|
43
|
-
* The plugin does no scanning or inference at this layer — the CLI is the
|
|
44
|
-
* sole source of truth for which `/register` modules get included. Only
|
|
45
|
-
* applies to `--app gjs`.
|
|
46
|
-
*/
|
|
47
|
-
autoGlobalsInject?: string;
|
|
48
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export interface ResolveAliasOptions { }
|
package/src/utils/alias.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EXTERNALS_NODE,
|
|
3
|
-
EXTERNALS_NPM,
|
|
4
|
-
ALIASES_GENERAL_FOR_GJS,
|
|
5
|
-
ALIASES_NODE_FOR_GJS,
|
|
6
|
-
ALIASES_WEB_FOR_GJS,
|
|
7
|
-
ALIASES_GENERAL_FOR_NODE,
|
|
8
|
-
ALIASES_GJS_FOR_NODE,
|
|
9
|
-
ALIASES_WEB_FOR_NODE
|
|
10
|
-
} from "@gjsify/resolve-npm";
|
|
11
|
-
|
|
12
|
-
import type { ResolveAliasOptions } from '../types/index.js';
|
|
13
|
-
|
|
14
|
-
export const setNodeAliasPrefix = (ALIASES: Record<string, string>) => {
|
|
15
|
-
// Also resolve alias names with `node:${ALIAS}`
|
|
16
|
-
for (const ALIAS in ALIASES) {
|
|
17
|
-
if(ALIAS.startsWith('node:')) {
|
|
18
|
-
continue
|
|
19
|
-
}
|
|
20
|
-
const key = `node:${ALIAS}`;
|
|
21
|
-
if(!ALIASES[key]) ALIASES[key] = ALIASES[ALIAS];
|
|
22
|
-
}
|
|
23
|
-
return ALIASES;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const getAliasesGeneralForGjs = (options: ResolveAliasOptions) => ALIASES_GENERAL_FOR_GJS;
|
|
27
|
-
const getAliasesNodeForGjs = (options: ResolveAliasOptions) => setNodeAliasPrefix(ALIASES_NODE_FOR_GJS);
|
|
28
|
-
const getAliasesWebForGjs = (options: ResolveAliasOptions) => ALIASES_WEB_FOR_GJS;
|
|
29
|
-
|
|
30
|
-
const getAliasesGeneralForNode = (options: ResolveAliasOptions) => ALIASES_GENERAL_FOR_NODE;
|
|
31
|
-
const getAliasesGjsForNode = (options: ResolveAliasOptions) => ALIASES_GJS_FOR_NODE;
|
|
32
|
-
const getAliasesWebForNode = (options: ResolveAliasOptions) => ALIASES_WEB_FOR_NODE;
|
|
33
|
-
|
|
34
|
-
export const getAliasesForGjs = (options: ResolveAliasOptions) => {
|
|
35
|
-
return {...getAliasesGeneralForGjs(options), ...getAliasesNodeForGjs(options), ...getAliasesWebForGjs(options) }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const getAliasesForNode = (options: ResolveAliasOptions) => {
|
|
39
|
-
return {...getAliasesGeneralForNode(options), ...getAliasesGjsForNode(options), ...getAliasesWebForNode(options) }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Array of Node.js build in module names (also with node: prefix) */
|
|
43
|
-
export const externalNode = [...EXTERNALS_NODE, ...EXTERNALS_NODE.map(E => `node:${E}`)];
|
|
44
|
-
|
|
45
|
-
/** Array of NPM module names for which we have our own implementation */
|
|
46
|
-
export const externalNPM = [...EXTERNALS_NPM];
|