@gjsify/cli 0.3.20 → 0.4.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/dist/cli.gjs.mjs +798 -0
- package/lib/actions/build.js +4 -17
- package/lib/bundler-pick.d.ts +79 -0
- package/lib/bundler-pick.js +428 -0
- package/lib/commands/foreach.d.ts +16 -0
- package/lib/commands/foreach.js +268 -0
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/install.d.ts +1 -0
- package/lib/commands/install.js +222 -26
- package/lib/commands/run.d.ts +1 -1
- package/lib/commands/run.js +133 -20
- package/lib/commands/workspace.d.ts +8 -0
- package/lib/commands/workspace.js +69 -0
- package/lib/config.js +26 -0
- package/lib/index.js +11 -3
- package/lib/types/config-data.d.ts +10 -1
- package/lib/utils/install-backend-native.d.ts +5 -1
- package/lib/utils/install-backend-native.js +88 -11
- package/lib/utils/install-backend.d.ts +11 -1
- package/lib/utils/install-backend.js +4 -2
- package/lib/utils/pkg-json-edit.d.ts +47 -0
- package/lib/utils/pkg-json-edit.js +108 -0
- package/package.json +36 -12
- package/src/actions/build.ts +0 -431
- package/src/actions/index.ts +0 -1
- package/src/commands/build.ts +0 -146
- package/src/commands/check.ts +0 -87
- package/src/commands/create.ts +0 -63
- package/src/commands/dlx.ts +0 -195
- package/src/commands/flatpak/build.ts +0 -225
- package/src/commands/flatpak/ci.ts +0 -173
- package/src/commands/flatpak/deps.ts +0 -120
- package/src/commands/flatpak/index.ts +0 -53
- package/src/commands/flatpak/init.ts +0 -191
- package/src/commands/flatpak/utils.ts +0 -76
- package/src/commands/gettext.ts +0 -258
- package/src/commands/gresource.ts +0 -97
- package/src/commands/gsettings.ts +0 -87
- package/src/commands/index.ts +0 -12
- package/src/commands/info.ts +0 -70
- package/src/commands/install.ts +0 -195
- package/src/commands/run.ts +0 -33
- package/src/commands/showcase.ts +0 -149
- package/src/config.ts +0 -289
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -37
- package/src/types/cli-build-options.ts +0 -100
- package/src/types/command.ts +0 -10
- package/src/types/config-data-library.ts +0 -5
- package/src/types/config-data-typescript.ts +0 -6
- package/src/types/config-data.ts +0 -225
- package/src/types/cosmiconfig-result.ts +0 -5
- package/src/types/index.ts +0 -6
- package/src/utils/check-system-deps.ts +0 -480
- package/src/utils/detect-native-packages.ts +0 -153
- package/src/utils/discover-showcases.ts +0 -75
- package/src/utils/dlx-cache.ts +0 -135
- package/src/utils/install-backend-native.ts +0 -363
- package/src/utils/install-backend.ts +0 -88
- package/src/utils/install-global.ts +0 -182
- package/src/utils/normalize-bundler-options.ts +0 -129
- package/src/utils/parse-spec.ts +0 -48
- package/src/utils/resolve-gjs-entry.ts +0 -96
- package/src/utils/resolve-plugin-by-name.ts +0 -106
- package/src/utils/run-gjs.ts +0 -90
- package/tsconfig.json +0 -16
package/src/commands/install.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// `gjsify install [pkg...]` — install packages with gjsify-aware post-checks.
|
|
2
|
-
//
|
|
3
|
-
// Modes:
|
|
4
|
-
// gjsify install → project install (npm install)
|
|
5
|
-
// gjsify install <pkg> [<pkg>...] → add package(s) to project (npm install <pkg>...)
|
|
6
|
-
// gjsify install -g <pkg> [...] → user-global install (XDG, GJS-runnable bin)
|
|
7
|
-
//
|
|
8
|
-
// Project mode delegates to `npm install` in cwd and runs `runMinimalChecks()`
|
|
9
|
-
// + `detectNativePackages()` to surface missing system deps and `@gjsify/*`
|
|
10
|
-
// packages with native prebuilds.
|
|
11
|
-
//
|
|
12
|
-
// Global mode is the GJS equivalent of `npm i -g`: extracts the package tree
|
|
13
|
-
// into `${XDG_DATA_HOME}/gjsify/global/node_modules/<pkg>/` via the native
|
|
14
|
-
// install backend (no Node/npm required at runtime), then symlinks the bins
|
|
15
|
-
// declared by `gjsify.bin` (preferred) or `bin` (fallback) into
|
|
16
|
-
// `~/.local/bin/`. Subsequent commands invoked by name resolve to the
|
|
17
|
-
// extracted package, so package-relative assets like `@ts-for-gir/cli`'s
|
|
18
|
-
// `dist-templates/` are found by ordinary `__dirname/..` resolution — no
|
|
19
|
-
// embedded asset stores, no separate release tarballs.
|
|
20
|
-
|
|
21
|
-
import { spawn } from 'node:child_process';
|
|
22
|
-
import { mkdirSync } from 'node:fs';
|
|
23
|
-
import type { Command } from '../types/index.js';
|
|
24
|
-
import {
|
|
25
|
-
buildInstallCommand,
|
|
26
|
-
detectPackageManager,
|
|
27
|
-
runMinimalChecks,
|
|
28
|
-
} from '../utils/check-system-deps.js';
|
|
29
|
-
import { detectNativePackages } from '../utils/detect-native-packages.js';
|
|
30
|
-
import { installPackages } from '../utils/install-backend.js';
|
|
31
|
-
import {
|
|
32
|
-
binDirOnPath,
|
|
33
|
-
defaultGlobalLayout,
|
|
34
|
-
linkGlobalBins,
|
|
35
|
-
specToPackageName,
|
|
36
|
-
} from '../utils/install-global.js';
|
|
37
|
-
|
|
38
|
-
interface InstallOptions {
|
|
39
|
-
packages?: string[];
|
|
40
|
-
global?: boolean;
|
|
41
|
-
'save-dev'?: boolean;
|
|
42
|
-
'save-peer'?: boolean;
|
|
43
|
-
'save-optional'?: boolean;
|
|
44
|
-
verbose: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export const installCommand: Command<any, InstallOptions> = {
|
|
48
|
-
command: 'install [packages..]',
|
|
49
|
-
description:
|
|
50
|
-
'Install npm dependencies in the current project (or globally with -g), then run gjsify-aware post-checks.',
|
|
51
|
-
builder: (yargs) =>
|
|
52
|
-
yargs
|
|
53
|
-
.positional('packages', {
|
|
54
|
-
description: 'Optional package specs. With none, runs a full project install.',
|
|
55
|
-
type: 'string',
|
|
56
|
-
array: true,
|
|
57
|
-
})
|
|
58
|
-
.option('global', {
|
|
59
|
-
description:
|
|
60
|
-
'Install into the user-global XDG location and symlink bins into ~/.local/bin.',
|
|
61
|
-
type: 'boolean',
|
|
62
|
-
alias: 'g',
|
|
63
|
-
default: false,
|
|
64
|
-
})
|
|
65
|
-
.option('save-dev', { type: 'boolean', alias: 'D' })
|
|
66
|
-
.option('save-peer', { type: 'boolean' })
|
|
67
|
-
.option('save-optional', { type: 'boolean', alias: 'O' })
|
|
68
|
-
.option('verbose', {
|
|
69
|
-
description: 'Verbose install logging.',
|
|
70
|
-
type: 'boolean',
|
|
71
|
-
default: false,
|
|
72
|
-
}),
|
|
73
|
-
handler: async (args) => {
|
|
74
|
-
if (args.global) {
|
|
75
|
-
if (!args.packages || args.packages.length === 0) {
|
|
76
|
-
console.error(
|
|
77
|
-
'gjsify install --global requires at least one <pkg> argument.',
|
|
78
|
-
);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
for (const flag of ['save-dev', 'save-peer', 'save-optional'] as const) {
|
|
82
|
-
if (args[flag]) {
|
|
83
|
-
console.warn(
|
|
84
|
-
`gjsify install --global ignores --${flag}: global installs do not modify a project package.json.`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
await installGlobalAndLink(args.packages, { verbose: args.verbose });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const npmArgs = ['install'];
|
|
93
|
-
if (args['save-dev']) npmArgs.push('--save-dev');
|
|
94
|
-
if (args['save-peer']) npmArgs.push('--save-peer');
|
|
95
|
-
if (args['save-optional']) npmArgs.push('--save-optional');
|
|
96
|
-
if (args.verbose) npmArgs.push('--loglevel', 'verbose');
|
|
97
|
-
if (args.packages && args.packages.length > 0) {
|
|
98
|
-
npmArgs.push(...args.packages);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
await spawnNpm(npmArgs);
|
|
102
|
-
|
|
103
|
-
await runPostInstallChecks();
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
async function spawnNpm(npmArgs: string[]): Promise<void> {
|
|
108
|
-
return new Promise<void>((resolve, reject) => {
|
|
109
|
-
const child = spawn('npm', npmArgs, { stdio: 'inherit' });
|
|
110
|
-
child.on('close', (code) => {
|
|
111
|
-
if (code === 0) resolve();
|
|
112
|
-
else reject(new Error(`npm install exited with code ${code}`));
|
|
113
|
-
});
|
|
114
|
-
child.on('error', (err) => {
|
|
115
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
116
|
-
const msg = code === 'ENOENT'
|
|
117
|
-
? 'npm not found on PATH — install Node.js first.'
|
|
118
|
-
: `npm install failed: ${err.message}`;
|
|
119
|
-
reject(new Error(msg));
|
|
120
|
-
});
|
|
121
|
-
}).catch((err: Error) => {
|
|
122
|
-
console.error(err.message);
|
|
123
|
-
process.exit(1);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function installGlobalAndLink(
|
|
128
|
-
specs: string[],
|
|
129
|
-
opts: { verbose: boolean },
|
|
130
|
-
): Promise<void> {
|
|
131
|
-
const layout = defaultGlobalLayout();
|
|
132
|
-
mkdirSync(layout.prefix, { recursive: true });
|
|
133
|
-
|
|
134
|
-
console.log(`gjsify install --global → ${layout.prefix}`);
|
|
135
|
-
console.log(` bins → ${layout.binDir}`);
|
|
136
|
-
|
|
137
|
-
await installPackages({
|
|
138
|
-
prefix: layout.prefix,
|
|
139
|
-
specs,
|
|
140
|
-
verbose: opts.verbose,
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const packageNames = specs.map(specToPackageName);
|
|
144
|
-
const created = linkGlobalBins(packageNames, layout);
|
|
145
|
-
|
|
146
|
-
if (created.length === 0) {
|
|
147
|
-
console.warn(
|
|
148
|
-
'\nNo bins declared (neither `gjsify.bin` nor `bin` in package.json) — nothing was symlinked.',
|
|
149
|
-
);
|
|
150
|
-
} else {
|
|
151
|
-
console.log(`\nLinked ${created.length} bin(s):`);
|
|
152
|
-
for (const e of created) {
|
|
153
|
-
console.log(` • ${e.link} → ${e.target}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (created.length > 0 && !binDirOnPath(layout.binDir)) {
|
|
158
|
-
console.warn(
|
|
159
|
-
`\nNote: ${layout.binDir} is not on your PATH.\n` +
|
|
160
|
-
`Add it to your shell rc file:\n export PATH="${layout.binDir}:$PATH"`,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async function runPostInstallChecks(): Promise<void> {
|
|
166
|
-
console.log('\n--- gjsify post-install checks ---');
|
|
167
|
-
|
|
168
|
-
// 1. System deps that GJS apps typically need.
|
|
169
|
-
const results = runMinimalChecks();
|
|
170
|
-
const missing = results.filter((r) => !r.found && r.severity === 'required');
|
|
171
|
-
if (missing.length > 0) {
|
|
172
|
-
console.warn('Missing required system dependencies:\n');
|
|
173
|
-
for (const dep of missing) {
|
|
174
|
-
console.warn(` ✗ ${dep.name}`);
|
|
175
|
-
}
|
|
176
|
-
const pm = detectPackageManager();
|
|
177
|
-
const cmd = buildInstallCommand(pm, missing);
|
|
178
|
-
if (cmd) console.warn(`\nInstall with:\n ${cmd}`);
|
|
179
|
-
} else {
|
|
180
|
-
console.log('System dependencies OK.');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// 2. Surface @gjsify/* packages with native prebuilds — `gjsify run`
|
|
184
|
-
// will set LD_LIBRARY_PATH / GI_TYPELIB_PATH for these automatically.
|
|
185
|
-
const native = detectNativePackages(process.cwd());
|
|
186
|
-
if (native.length > 0) {
|
|
187
|
-
console.log(
|
|
188
|
-
`\nDetected ${native.length} @gjsify/* package(s) with native prebuilds:`,
|
|
189
|
-
);
|
|
190
|
-
for (const pkg of native) {
|
|
191
|
-
console.log(` • ${pkg.name}`);
|
|
192
|
-
}
|
|
193
|
-
console.log('\nUse `gjsify run <bundle>` to launch with LD_LIBRARY_PATH/GI_TYPELIB_PATH set.');
|
|
194
|
-
}
|
|
195
|
-
}
|
package/src/commands/run.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'node:path';
|
|
2
|
-
import type { Command } from '../types/index.js';
|
|
3
|
-
import { runGjsBundle } from '../utils/run-gjs.js';
|
|
4
|
-
|
|
5
|
-
interface RunOptions {
|
|
6
|
-
file: string;
|
|
7
|
-
args: string[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const runCommand: Command<any, RunOptions> = {
|
|
11
|
-
command: 'run <file> [args..]',
|
|
12
|
-
description: 'Run a GJS bundle, automatically setting LD_LIBRARY_PATH and GI_TYPELIB_PATH for any installed native gjsify packages (e.g. @gjsify/webgl).',
|
|
13
|
-
builder: (yargs) => {
|
|
14
|
-
return yargs
|
|
15
|
-
.positional('file', {
|
|
16
|
-
description: 'The GJS bundle to run (e.g. dist/gjs.js)',
|
|
17
|
-
type: 'string',
|
|
18
|
-
normalize: true,
|
|
19
|
-
demandOption: true,
|
|
20
|
-
})
|
|
21
|
-
.positional('args', {
|
|
22
|
-
description: 'Extra arguments passed through to gjs',
|
|
23
|
-
type: 'string',
|
|
24
|
-
array: true,
|
|
25
|
-
default: [],
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
handler: async (args) => {
|
|
29
|
-
const file = resolve(args.file as string);
|
|
30
|
-
const extraArgs = (args.args as string[]) ?? [];
|
|
31
|
-
await runGjsBundle(file, extraArgs);
|
|
32
|
-
},
|
|
33
|
-
};
|
package/src/commands/showcase.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import type { Command } from '../types/index.js';
|
|
2
|
-
import { discoverShowcases, findShowcase } from '../utils/discover-showcases.js';
|
|
3
|
-
import {
|
|
4
|
-
runMinimalChecks,
|
|
5
|
-
detectPackageManager,
|
|
6
|
-
buildInstallCommand,
|
|
7
|
-
} from '../utils/check-system-deps.js';
|
|
8
|
-
import { spawn } from 'node:child_process';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
11
|
-
|
|
12
|
-
function readCliVersion(): string | undefined {
|
|
13
|
-
try {
|
|
14
|
-
const pkgUrl = new URL('../../package.json', import.meta.url);
|
|
15
|
-
const pkg = JSON.parse(readFileSync(pkgUrl, 'utf8')) as { version?: unknown };
|
|
16
|
-
return typeof pkg.version === 'string' ? pkg.version : undefined;
|
|
17
|
-
} catch {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface ShowcaseOptions {
|
|
23
|
-
name?: string;
|
|
24
|
-
json: boolean;
|
|
25
|
-
list: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const showcaseCommand: Command<any, ShowcaseOptions> = {
|
|
29
|
-
command: 'showcase [name]',
|
|
30
|
-
description: 'List or run curated gjsify showcase applications.',
|
|
31
|
-
builder: (yargs) =>
|
|
32
|
-
yargs
|
|
33
|
-
.positional('name', {
|
|
34
|
-
description: 'Showcase name to run (omit to list all)',
|
|
35
|
-
type: 'string',
|
|
36
|
-
})
|
|
37
|
-
.option('json', {
|
|
38
|
-
description: 'Output as JSON',
|
|
39
|
-
type: 'boolean',
|
|
40
|
-
default: false,
|
|
41
|
-
})
|
|
42
|
-
.option('list', {
|
|
43
|
-
description: 'List available showcases',
|
|
44
|
-
type: 'boolean',
|
|
45
|
-
default: false,
|
|
46
|
-
}),
|
|
47
|
-
handler: async (args) => {
|
|
48
|
-
// List mode: no name given, or --list flag
|
|
49
|
-
if (!args.name || args.list) {
|
|
50
|
-
const showcases = discoverShowcases();
|
|
51
|
-
|
|
52
|
-
if (args.json) {
|
|
53
|
-
console.log(JSON.stringify(showcases, null, 2));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (showcases.length === 0) {
|
|
58
|
-
console.log('No showcases found. The CLI ships a curated list in `showcases.json`; if it is missing the CLI install is incomplete.');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const grouped = new Map<string, typeof showcases>();
|
|
63
|
-
for (const sc of showcases) {
|
|
64
|
-
const list = grouped.get(sc.category) ?? [];
|
|
65
|
-
list.push(sc);
|
|
66
|
-
grouped.set(sc.category, list);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log('Available gjsify showcases:\n');
|
|
70
|
-
for (const [category, list] of grouped) {
|
|
71
|
-
console.log(` ${category.toUpperCase()}:`);
|
|
72
|
-
const maxNameLen = Math.max(...list.map((e) => e.name.length));
|
|
73
|
-
for (const sc of list) {
|
|
74
|
-
const pad = ' '.repeat(maxNameLen - sc.name.length + 2);
|
|
75
|
-
const desc = sc.description ? `${pad}${sc.description}` : '';
|
|
76
|
-
console.log(` ${sc.name}${desc}`);
|
|
77
|
-
}
|
|
78
|
-
console.log('');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
console.log('Run a showcase: gjsify showcase <name>');
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const showcase = findShowcase(args.name);
|
|
86
|
-
if (!showcase) {
|
|
87
|
-
console.error(`Unknown showcase: "${args.name}"`);
|
|
88
|
-
console.error('Run "gjsify showcase" to list available showcases.');
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// System dependency check before delegating — only system libs (gjs,
|
|
93
|
-
// gtk4, …). The showcase's npm deps (incl. `@gjsify/webgl` with the
|
|
94
|
-
// gwebgl Vala prebuild) are fetched by `gjsify dlx` into the npm
|
|
95
|
-
// cache, and `runGjsBundle()` picks the prebuild up from the bundle
|
|
96
|
-
// dir via `detectNativePackages()`. Pre-flight-checking npm deps
|
|
97
|
-
// here would fail for `npx @gjsify/cli showcase` (no project
|
|
98
|
-
// node_modules, CLI doesn't dep on the showcase libs).
|
|
99
|
-
const results = runMinimalChecks();
|
|
100
|
-
const missingHard = results.filter(
|
|
101
|
-
(r) => !r.found && r.severity === 'required',
|
|
102
|
-
);
|
|
103
|
-
if (missingHard.length > 0) {
|
|
104
|
-
console.error('Missing system dependencies:\n');
|
|
105
|
-
for (const dep of missingHard) {
|
|
106
|
-
console.error(` ✗ ${dep.name}`);
|
|
107
|
-
}
|
|
108
|
-
const pm = detectPackageManager();
|
|
109
|
-
const cmd = buildInstallCommand(pm, missingHard);
|
|
110
|
-
if (cmd) {
|
|
111
|
-
console.error(`\nInstall with:\n ${cmd}`);
|
|
112
|
-
}
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Delegate to `gjsify dlx <package>@<cli-version>` — same npm-cache,
|
|
117
|
-
// same atomic symlink-swap, same `gjsify.main` resolution. Re-spawning
|
|
118
|
-
// the CLI keeps the dlx logic in one place.
|
|
119
|
-
//
|
|
120
|
-
// Pinning to the CLI's own version is load-bearing: showcases ship in
|
|
121
|
-
// lockstep with the CLI, so users running `npx @gjsify/cli@X showcase
|
|
122
|
-
// <name>` expect the matching `@gjsify/example-*@X`. Without the pin,
|
|
123
|
-
// dlx caches the first resolved-latest on disk; subsequent CLI
|
|
124
|
-
// releases leave that cache untouched until the 7-day TTL expires,
|
|
125
|
-
// and the user gets a stale showcase that may be missing deps the
|
|
126
|
-
// newer CLI assumes (the `@gjsify/http-soup-bridge` regression
|
|
127
|
-
// reported against `@gjsify/cli@0.3.17`).
|
|
128
|
-
const cliVersion = readCliVersion();
|
|
129
|
-
const dlxSpec = cliVersion ? `${showcase.packageName}@${cliVersion}` : showcase.packageName;
|
|
130
|
-
console.log(`Running showcase: ${showcase.name} (via gjsify dlx ${dlxSpec})\n`);
|
|
131
|
-
const cliBin = fileURLToPath(new URL('../index.js', import.meta.url));
|
|
132
|
-
const child = spawn(process.execPath, [cliBin, 'dlx', dlxSpec], {
|
|
133
|
-
stdio: 'inherit',
|
|
134
|
-
});
|
|
135
|
-
await new Promise<void>((resolvePromise, reject) => {
|
|
136
|
-
child.on('close', (code) => {
|
|
137
|
-
if (code !== 0) {
|
|
138
|
-
reject(new Error(`gjsify dlx exited with code ${code}`));
|
|
139
|
-
} else {
|
|
140
|
-
resolvePromise();
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
child.on('error', reject);
|
|
144
|
-
}).catch((err) => {
|
|
145
|
-
console.error(err.message);
|
|
146
|
-
process.exit(1);
|
|
147
|
-
});
|
|
148
|
-
},
|
|
149
|
-
};
|
package/src/config.ts
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { APP_NAME } from './constants.js';
|
|
2
|
-
import { cosmiconfig, type Options as LoadOptions } from 'cosmiconfig';
|
|
3
|
-
|
|
4
|
-
/** Default cosmiconfig search places for a given module name (matches cosmiconfig defaults). */
|
|
5
|
-
function defaultSearchPlaces(name: string): string[] {
|
|
6
|
-
return [
|
|
7
|
-
'package.json',
|
|
8
|
-
`.${name}rc`,
|
|
9
|
-
`.${name}rc.json`,
|
|
10
|
-
`.${name}rc.yaml`,
|
|
11
|
-
`.${name}rc.yml`,
|
|
12
|
-
`.${name}rc.js`,
|
|
13
|
-
`.${name}rc.ts`,
|
|
14
|
-
`.${name}rc.mjs`,
|
|
15
|
-
`.${name}rc.cjs`,
|
|
16
|
-
`${name}.config.js`,
|
|
17
|
-
`${name}.config.ts`,
|
|
18
|
-
`${name}.config.mjs`,
|
|
19
|
-
`${name}.config.cjs`,
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
23
|
-
import { getTsconfig } from 'get-tsconfig';
|
|
24
|
-
|
|
25
|
-
/** Deep merge objects (replaces lodash.merge) */
|
|
26
|
-
function merge<T extends Record<string, any>>(target: T, ...sources: Record<string, any>[]): T {
|
|
27
|
-
for (const source of sources) {
|
|
28
|
-
if (!source) continue;
|
|
29
|
-
for (const key of Object.keys(source)) {
|
|
30
|
-
const targetVal = (target as any)[key];
|
|
31
|
-
const sourceVal = source[key];
|
|
32
|
-
if (sourceVal !== undefined) {
|
|
33
|
-
if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {
|
|
34
|
-
merge(targetVal, sourceVal);
|
|
35
|
-
} else {
|
|
36
|
-
(target as any)[key] = sourceVal;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return target;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isPlainObject(val: unknown): val is Record<string, any> {
|
|
45
|
-
return typeof val === 'object' && val !== null && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Read a dotted path (`a.b.c`) from a plain object. Returns `undefined` for
|
|
50
|
-
* any missing segment. Intentionally narrow — only used for surfacing
|
|
51
|
-
* `package.json` fields into compile-time defines, not for arbitrary deep
|
|
52
|
-
* traversal.
|
|
53
|
-
*/
|
|
54
|
-
function readDottedPath(obj: Record<string, unknown>, path: string): unknown {
|
|
55
|
-
if (!path.includes('.')) return obj[path];
|
|
56
|
-
let cursor: unknown = obj;
|
|
57
|
-
for (const segment of path.split('.')) {
|
|
58
|
-
if (cursor === null || cursor === undefined || typeof cursor !== 'object') return undefined;
|
|
59
|
-
cursor = (cursor as Record<string, unknown>)[segment];
|
|
60
|
-
}
|
|
61
|
-
return cursor;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
import type { CliBuildOptions, ConfigData, CosmiconfigResult, ConfigDataTypescript, ConfigDataLibrary} from './types/index.js';
|
|
65
|
-
import type { ArgumentsCamelCase } from 'yargs';
|
|
66
|
-
|
|
67
|
-
export class Config {
|
|
68
|
-
|
|
69
|
-
readonly loadOptions: Partial<LoadOptions> = {}
|
|
70
|
-
|
|
71
|
-
constructor(loadOptions: Partial<LoadOptions> = {}) {
|
|
72
|
-
if(Object.keys(loadOptions).length) {
|
|
73
|
-
this.loadOptions = loadOptions;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Loads gjsify config file, e.g `.gjsifyrc.js` */
|
|
78
|
-
private async load(searchFrom?: string) {
|
|
79
|
-
// cosmiconfig's default first-match-wins behaviour silently drops one
|
|
80
|
-
// source when both `package.json#gjsify` and an explicit config file
|
|
81
|
-
// (`.gjsifyrc.js`, `gjsify.config.mjs`, ...) are present. Project hits
|
|
82
|
-
// this footgun: adding `gjsify.bin` to package.json (so `gjsify dlx`
|
|
83
|
-
// resolves the GJS bundle) silently disables `.gjsifyrc.js`. We
|
|
84
|
-
// explicitly load both sources and merge — package.json is the lower
|
|
85
|
-
// layer, the explicit file wins on key collisions.
|
|
86
|
-
//
|
|
87
|
-
// Run two searches:
|
|
88
|
-
// 1. Default (includes package.json) — for projects that only use
|
|
89
|
-
// package.json#gjsify and no separate file.
|
|
90
|
-
// 2. Explicit-file only (package.json excluded) — to find the
|
|
91
|
-
// `.gjsifyrc.*` / `gjsify.config.*` regardless of whether
|
|
92
|
-
// package.json#gjsify exists.
|
|
93
|
-
const fileExplorer = cosmiconfig(APP_NAME, {
|
|
94
|
-
...this.loadOptions,
|
|
95
|
-
searchPlaces: (this.loadOptions.searchPlaces ?? defaultSearchPlaces(APP_NAME))
|
|
96
|
-
.filter((p) => p !== 'package.json'),
|
|
97
|
-
});
|
|
98
|
-
const fileResult = await fileExplorer.search(searchFrom) as CosmiconfigResult<ConfigData> | null;
|
|
99
|
-
|
|
100
|
-
const merged: ConfigData = {};
|
|
101
|
-
try {
|
|
102
|
-
const pkg = await this.readPackageJSON(searchFrom) as { gjsify?: ConfigData };
|
|
103
|
-
if (isPlainObject(pkg?.gjsify)) merge(merged, pkg.gjsify);
|
|
104
|
-
} catch {
|
|
105
|
-
// Missing or unreadable package.json — skip.
|
|
106
|
-
}
|
|
107
|
-
if (fileResult?.config && isPlainObject(fileResult.config)) {
|
|
108
|
-
merge(merged, fileResult.config);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
merged.bundler ||= {};
|
|
112
|
-
merged.library ||= {};
|
|
113
|
-
merged.typescript ||= {};
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
config: merged,
|
|
117
|
-
filepath: fileResult?.filepath ?? '',
|
|
118
|
-
isEmpty: !fileResult && Object.keys(merged).length === 3, // only the three default-empty objects
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Loads package.json of the current project */
|
|
123
|
-
private async readPackageJSON(dirPath?: string) {
|
|
124
|
-
dirPath = await resolvePackageJSON(dirPath)
|
|
125
|
-
const pkg = await readPackageJSON(dirPath);
|
|
126
|
-
return pkg;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Loads tsconfig.json of the current project */
|
|
130
|
-
private async readTSConfig(dirPath?: string) {
|
|
131
|
-
const tsconfig = getTsconfig(dirPath)?.config || {};
|
|
132
|
-
return tsconfig;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async forBuild(cliArgs: ArgumentsCamelCase<CliBuildOptions>) {
|
|
136
|
-
const configFile = await this.load(process.cwd());
|
|
137
|
-
const configData: ConfigData = {...configFile.config};
|
|
138
|
-
const configFilePath = configFile.filepath || process.cwd();
|
|
139
|
-
const pkg = await this.readPackageJSON(configFilePath) as ConfigDataLibrary;
|
|
140
|
-
const tsConfig = await this.readTSConfig(configFilePath) as ConfigDataTypescript;
|
|
141
|
-
|
|
142
|
-
tsConfig.reflection ||= cliArgs.reflection;
|
|
143
|
-
|
|
144
|
-
// TODO replace with `cliArgs.logLevel`
|
|
145
|
-
configData.verbose = cliArgs.verbose || false;
|
|
146
|
-
configData.exclude = cliArgs.exclude || [];
|
|
147
|
-
if (cliArgs.consoleShim !== undefined) configData.consoleShim = cliArgs.consoleShim;
|
|
148
|
-
if (cliArgs.globals !== undefined) configData.globals = cliArgs.globals;
|
|
149
|
-
if (cliArgs.shebang !== undefined) configData.shebang = cliArgs.shebang;
|
|
150
|
-
if (cliArgs.excludeGlobals) {
|
|
151
|
-
const raw = Array.isArray(cliArgs.excludeGlobals)
|
|
152
|
-
? cliArgs.excludeGlobals.join(',')
|
|
153
|
-
: String(cliArgs.excludeGlobals);
|
|
154
|
-
const ids = raw.split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
155
|
-
if (ids.length) configData.excludeGlobals = [...(configData.excludeGlobals ?? []), ...ids];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
merge(configData.library ??= {}, pkg, configData.library);
|
|
159
|
-
merge(configData.typescript ??= {}, tsConfig, configData.typescript);
|
|
160
|
-
|
|
161
|
-
// Parse `KEY=VALUE` style flags into Record<string, string>.
|
|
162
|
-
// - `--define`: VALUE is a JS expression (string literals must be
|
|
163
|
-
// pre-quoted by the caller, e.g. `'"1.2.3"'`).
|
|
164
|
-
// - `--alias`: VALUE is the substitute module specifier.
|
|
165
|
-
const parseKvPairs = (entries: readonly string[], flag: string): Record<string, string> => {
|
|
166
|
-
const out: Record<string, string> = {};
|
|
167
|
-
for (const entry of entries) {
|
|
168
|
-
const idx = entry.indexOf('=');
|
|
169
|
-
if (idx === -1) {
|
|
170
|
-
throw new Error(`Invalid --${flag} value '${entry}'. Expected KEY=VALUE.`);
|
|
171
|
-
}
|
|
172
|
-
const key = entry.slice(0, idx).trim();
|
|
173
|
-
const value = entry.slice(idx + 1);
|
|
174
|
-
if (!key) {
|
|
175
|
-
throw new Error(`Invalid --${flag} value '${entry}'. Empty key.`);
|
|
176
|
-
}
|
|
177
|
-
out[key] = value;
|
|
178
|
-
}
|
|
179
|
-
return out;
|
|
180
|
-
};
|
|
181
|
-
const defineMap = parseKvPairs(cliArgs.define ?? [], 'define');
|
|
182
|
-
const aliasMap = parseKvPairs(cliArgs.alias ?? [], 'alias');
|
|
183
|
-
if (Object.keys(aliasMap).length) {
|
|
184
|
-
configData.aliases = { ...(configData.aliases ?? {}), ...aliasMap };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Resolve `defineFromPackageJson` / `defineFromEnv` into raw
|
|
188
|
-
// KEY=<JSON-stringified value> entries that get merged into the
|
|
189
|
-
// bundler's `transform.define` map below. Both produce JS expressions
|
|
190
|
-
// (the value side of a Rolldown define is substituted at the call
|
|
191
|
-
// site, not stringified again) — so a missing env variable resolves
|
|
192
|
-
// to the literal `undefined`, letting consumer code use
|
|
193
|
-
// `typeof X === 'undefined'` or `X ?? fallback` guards.
|
|
194
|
-
const fromPkgDefines: Record<string, string> = {};
|
|
195
|
-
if (configData.defineFromPackageJson) {
|
|
196
|
-
for (const [name, spec] of Object.entries(configData.defineFromPackageJson)) {
|
|
197
|
-
if (!spec || typeof spec.field !== 'string' || !spec.field) {
|
|
198
|
-
throw new Error(
|
|
199
|
-
`gjsify config: defineFromPackageJson["${name}"] is missing a "field" string`,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
const value = readDottedPath(pkg as Record<string, unknown>, spec.field);
|
|
203
|
-
fromPkgDefines[name] = value === undefined ? 'undefined' : JSON.stringify(value);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
const fromEnvDefines: Record<string, string> = {};
|
|
207
|
-
if (configData.defineFromEnv) {
|
|
208
|
-
for (const [name, spec] of Object.entries(configData.defineFromEnv)) {
|
|
209
|
-
if (!spec || typeof spec.env !== 'string' || !spec.env) {
|
|
210
|
-
throw new Error(
|
|
211
|
-
`gjsify config: defineFromEnv["${name}"] is missing an "env" string`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
const raw = process.env[spec.env];
|
|
215
|
-
const value = raw !== undefined ? raw : spec.default;
|
|
216
|
-
fromEnvDefines[name] = value === undefined ? 'undefined' : JSON.stringify(value);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Merge CLI flags into the Rolldown-shape `bundler` field. Mappings:
|
|
221
|
-
// --entry-points → bundler.input
|
|
222
|
-
// --outfile → bundler.output.file
|
|
223
|
-
// --outdir → bundler.output.dir
|
|
224
|
-
// --format → bundler.output.format
|
|
225
|
-
// --minify → bundler.output.minify
|
|
226
|
-
// --log-level → bundler.logLevel
|
|
227
|
-
// --external → bundler.external
|
|
228
|
-
// --define → bundler.transform.define
|
|
229
|
-
const bundler = (configData.bundler ??= {});
|
|
230
|
-
const output = (bundler.output ??= {});
|
|
231
|
-
const transform = (bundler.transform ??= {});
|
|
232
|
-
|
|
233
|
-
if (cliArgs.entryPoints?.length) bundler.input = cliArgs.entryPoints;
|
|
234
|
-
// Fallback when neither the CLI flag nor the cosmiconfig data set an
|
|
235
|
-
// entry point. Applied here (post-merge) rather than as a yargs
|
|
236
|
-
// `default:` because yargs defaults are indistinguishable from
|
|
237
|
-
// user-set values, and would silently overwrite `bundler.input`
|
|
238
|
-
// declared in package.json#gjsify.
|
|
239
|
-
if (!bundler.input) bundler.input = ['src/index.ts'];
|
|
240
|
-
if (cliArgs.outfile !== undefined) output.file = cliArgs.outfile;
|
|
241
|
-
if (cliArgs.outdir !== undefined) output.dir = cliArgs.outdir;
|
|
242
|
-
if (cliArgs.format !== undefined) output.format = cliArgs.format as 'esm' | 'cjs' | 'iife';
|
|
243
|
-
// CLI flag wins over config; if neither is set, minify by default.
|
|
244
|
-
// Pretty-printed output is opt-in via `--no-minify` or
|
|
245
|
-
// `bundler.output.minify: false` in the config.
|
|
246
|
-
if (cliArgs.minify !== undefined) output.minify = cliArgs.minify;
|
|
247
|
-
if (output.minify === undefined) output.minify = true;
|
|
248
|
-
if (cliArgs.logLevel) {
|
|
249
|
-
// Map esbuild log levels to Rolldown's narrower set:
|
|
250
|
-
// esbuild → rolldown
|
|
251
|
-
// silent → silent
|
|
252
|
-
// error → warn (rolldown has no error-only)
|
|
253
|
-
// warning → warn
|
|
254
|
-
// info → info
|
|
255
|
-
// debug → debug
|
|
256
|
-
// verbose → debug (rolldown has no verbose)
|
|
257
|
-
const map: Record<string, 'silent' | 'warn' | 'info' | 'debug'> = {
|
|
258
|
-
silent: 'silent',
|
|
259
|
-
error: 'warn',
|
|
260
|
-
warning: 'warn',
|
|
261
|
-
warn: 'warn',
|
|
262
|
-
info: 'info',
|
|
263
|
-
debug: 'debug',
|
|
264
|
-
verbose: 'debug',
|
|
265
|
-
};
|
|
266
|
-
const level = map[cliArgs.logLevel] ?? 'warn';
|
|
267
|
-
bundler.logLevel = level;
|
|
268
|
-
}
|
|
269
|
-
if (cliArgs.external?.length) {
|
|
270
|
-
const userExternal = Array.isArray(bundler.external) ? bundler.external : [];
|
|
271
|
-
bundler.external = [...userExternal, ...cliArgs.external];
|
|
272
|
-
}
|
|
273
|
-
if (Object.keys(defineMap).length || Object.keys(fromPkgDefines).length || Object.keys(fromEnvDefines).length) {
|
|
274
|
-
// CLI --define wins over package.json/env (manual overrides during
|
|
275
|
-
// debugging beat declarative config).
|
|
276
|
-
transform.define = {
|
|
277
|
-
...(transform.define ?? {}),
|
|
278
|
-
...fromPkgDefines,
|
|
279
|
-
...fromEnvDefines,
|
|
280
|
-
...defineMap,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if(configData.verbose) console.debug("configData", configData);
|
|
285
|
-
|
|
286
|
-
return configData;
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
}
|
package/src/constants.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const APP_NAME = 'gjsify'
|