@gjsify/cli 0.3.8 → 0.3.9
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/actions/build.js +28 -4
- package/lib/config.js +57 -10
- package/package.json +9 -9
- package/src/actions/build.ts +27 -1
- package/src/config.ts +59 -12
package/lib/actions/build.js
CHANGED
|
@@ -6,6 +6,20 @@ import { getPnpPlugin } from "@gjsify/resolve-npm/pnp-relay";
|
|
|
6
6
|
import { dirname, extname } from "node:path";
|
|
7
7
|
import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
8
8
|
const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
|
|
9
|
+
/**
|
|
10
|
+
* `true` when `path` points at a location that's unsafe to use as a build
|
|
11
|
+
* outfile (would overwrite source). Currently catches:
|
|
12
|
+
* - any TypeScript extension (`.ts`, `.tsx`, `.mts`, `.cts`, `.mtsx`, `.ctsx`)
|
|
13
|
+
* - paths that live under a `src/` segment (relative or absolute)
|
|
14
|
+
*/
|
|
15
|
+
function isUnsafeDefaultOutput(path) {
|
|
16
|
+
if (/\.[cm]?tsx?$/i.test(path))
|
|
17
|
+
return true;
|
|
18
|
+
const norm = path.replace(/\\/g, "/");
|
|
19
|
+
if (/(?:^|\/)src\//.test(norm))
|
|
20
|
+
return true;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
9
23
|
/**
|
|
10
24
|
* Resolve the gjsify-flavoured PnP plugin. Anchors the relay on this file's
|
|
11
25
|
* URL so transitive `@gjsify/*` polyfills (reached via @gjsify/cli's deps on
|
|
@@ -190,10 +204,20 @@ export class BuildAction {
|
|
|
190
204
|
!esbuild?.outfile &&
|
|
191
205
|
!esbuild?.outdir &&
|
|
192
206
|
(pgk?.main || pgk?.module)) {
|
|
193
|
-
esbuild
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
207
|
+
const candidate = esbuild?.format === "cjs"
|
|
208
|
+
? pgk.main || pgk.module
|
|
209
|
+
: pgk.module || pgk.main;
|
|
210
|
+
if (candidate && isUnsafeDefaultOutput(candidate)) {
|
|
211
|
+
// `package.json#main`/`module` commonly points at a TypeScript
|
|
212
|
+
// source (e.g. `src/index.ts` for TS-direct workflows). Falling
|
|
213
|
+
// back to that value would have esbuild OVERWRITE the source.
|
|
214
|
+
// Surface a clear error and require an explicit outfile/outdir
|
|
215
|
+
// instead of silently destroying the user's code.
|
|
216
|
+
throw new Error(`gjsify build: refusing to default --outfile to ${candidate} ` +
|
|
217
|
+
`(would overwrite a TypeScript source file). Pass --outfile/--outdir ` +
|
|
218
|
+
`explicitly, or set "gjsify.esbuild.outfile" in package.json.`);
|
|
219
|
+
}
|
|
220
|
+
esbuild.outfile = candidate;
|
|
197
221
|
}
|
|
198
222
|
const { consoleShim, globals } = this.configData;
|
|
199
223
|
const pluginOpts = {
|
package/lib/config.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { APP_NAME } from './constants.js';
|
|
2
2
|
import { cosmiconfig } from 'cosmiconfig';
|
|
3
|
+
/** Default cosmiconfig search places for a given module name (matches cosmiconfig defaults). */
|
|
4
|
+
function defaultSearchPlaces(name) {
|
|
5
|
+
return [
|
|
6
|
+
'package.json',
|
|
7
|
+
`.${name}rc`,
|
|
8
|
+
`.${name}rc.json`,
|
|
9
|
+
`.${name}rc.yaml`,
|
|
10
|
+
`.${name}rc.yml`,
|
|
11
|
+
`.${name}rc.js`,
|
|
12
|
+
`.${name}rc.ts`,
|
|
13
|
+
`.${name}rc.mjs`,
|
|
14
|
+
`.${name}rc.cjs`,
|
|
15
|
+
`${name}.config.js`,
|
|
16
|
+
`${name}.config.ts`,
|
|
17
|
+
`${name}.config.mjs`,
|
|
18
|
+
`${name}.config.cjs`,
|
|
19
|
+
];
|
|
20
|
+
}
|
|
3
21
|
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
4
22
|
import { getTsconfig } from 'get-tsconfig';
|
|
5
23
|
/** Deep merge objects (replaces lodash.merge) */
|
|
@@ -34,17 +52,46 @@ export class Config {
|
|
|
34
52
|
}
|
|
35
53
|
/** Loads gjsify config file, e.g `.gjsifyrc.js` */
|
|
36
54
|
async load(searchFrom) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
// cosmiconfig's default first-match-wins behaviour silently drops one
|
|
56
|
+
// source when both `package.json#gjsify` and an explicit config file
|
|
57
|
+
// (`.gjsifyrc.js`, `gjsify.config.mjs`, ...) are present. Project hits
|
|
58
|
+
// this footgun: adding `gjsify.bin` to package.json (so `gjsify dlx`
|
|
59
|
+
// resolves the GJS bundle) silently disables `.gjsifyrc.js`. We
|
|
60
|
+
// explicitly load both sources and merge — package.json is the lower
|
|
61
|
+
// layer, the explicit file wins on key collisions.
|
|
62
|
+
//
|
|
63
|
+
// Run two searches:
|
|
64
|
+
// 1. Default (includes package.json) — for projects that only use
|
|
65
|
+
// package.json#gjsify and no separate file.
|
|
66
|
+
// 2. Explicit-file only (package.json excluded) — to find the
|
|
67
|
+
// `.gjsifyrc.*` / `gjsify.config.*` regardless of whether
|
|
68
|
+
// package.json#gjsify exists.
|
|
69
|
+
const fileExplorer = cosmiconfig(APP_NAME, {
|
|
70
|
+
...this.loadOptions,
|
|
71
|
+
searchPlaces: (this.loadOptions.searchPlaces ?? defaultSearchPlaces(APP_NAME))
|
|
72
|
+
.filter((p) => p !== 'package.json'),
|
|
73
|
+
});
|
|
74
|
+
const fileResult = await fileExplorer.search(searchFrom);
|
|
75
|
+
const merged = {};
|
|
76
|
+
try {
|
|
77
|
+
const pkg = await this.readPackageJSON(searchFrom);
|
|
78
|
+
if (isPlainObject(pkg?.gjsify))
|
|
79
|
+
merge(merged, pkg.gjsify);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Missing or unreadable package.json — skip.
|
|
83
|
+
}
|
|
84
|
+
if (fileResult?.config && isPlainObject(fileResult.config)) {
|
|
85
|
+
merge(merged, fileResult.config);
|
|
86
|
+
}
|
|
87
|
+
merged.esbuild ||= {};
|
|
88
|
+
merged.library ||= {};
|
|
89
|
+
merged.typescript ||= {};
|
|
90
|
+
return {
|
|
91
|
+
config: merged,
|
|
92
|
+
filepath: fileResult?.filepath ?? '',
|
|
93
|
+
isEmpty: !fileResult && Object.keys(merged).length === 3, // only the three default-empty objects
|
|
42
94
|
};
|
|
43
|
-
configFile.config ||= {};
|
|
44
|
-
configFile.config.esbuild ||= {};
|
|
45
|
-
configFile.config.library ||= {};
|
|
46
|
-
configFile.config.typescript ||= {};
|
|
47
|
-
return configFile;
|
|
48
95
|
}
|
|
49
96
|
/** Loads package.json of the current project */
|
|
50
97
|
async readPackageJSON(dirPath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"cli"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@gjsify/create-app": "^0.3.
|
|
27
|
-
"@gjsify/esbuild-plugin-gjsify": "^0.3.
|
|
28
|
-
"@gjsify/node-polyfills": "^0.3.
|
|
29
|
-
"@gjsify/npm-registry": "^0.3.
|
|
30
|
-
"@gjsify/resolve-npm": "^0.3.
|
|
31
|
-
"@gjsify/semver": "^0.3.
|
|
32
|
-
"@gjsify/tar": "^0.3.
|
|
33
|
-
"@gjsify/web-polyfills": "^0.3.
|
|
26
|
+
"@gjsify/create-app": "^0.3.9",
|
|
27
|
+
"@gjsify/esbuild-plugin-gjsify": "^0.3.9",
|
|
28
|
+
"@gjsify/node-polyfills": "^0.3.9",
|
|
29
|
+
"@gjsify/npm-registry": "^0.3.9",
|
|
30
|
+
"@gjsify/resolve-npm": "^0.3.9",
|
|
31
|
+
"@gjsify/semver": "^0.3.9",
|
|
32
|
+
"@gjsify/tar": "^0.3.9",
|
|
33
|
+
"@gjsify/web-polyfills": "^0.3.9",
|
|
34
34
|
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.15",
|
|
35
35
|
"cosmiconfig": "^9.0.1",
|
|
36
36
|
"esbuild": "^0.28.0",
|
package/src/actions/build.ts
CHANGED
|
@@ -14,6 +14,19 @@ import { chmod, readFile, writeFile } from "node:fs/promises";
|
|
|
14
14
|
|
|
15
15
|
const GJS_SHEBANG = "#!/usr/bin/env -S gjs -m\n";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* `true` when `path` points at a location that's unsafe to use as a build
|
|
19
|
+
* outfile (would overwrite source). Currently catches:
|
|
20
|
+
* - any TypeScript extension (`.ts`, `.tsx`, `.mts`, `.cts`, `.mtsx`, `.ctsx`)
|
|
21
|
+
* - paths that live under a `src/` segment (relative or absolute)
|
|
22
|
+
*/
|
|
23
|
+
function isUnsafeDefaultOutput(path: string): boolean {
|
|
24
|
+
if (/\.[cm]?tsx?$/i.test(path)) return true;
|
|
25
|
+
const norm = path.replace(/\\/g, "/");
|
|
26
|
+
if (/(?:^|\/)src\//.test(norm)) return true;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
/**
|
|
18
31
|
* Resolve the gjsify-flavoured PnP plugin. Anchors the relay on this file's
|
|
19
32
|
* URL so transitive `@gjsify/*` polyfills (reached via @gjsify/cli's deps on
|
|
@@ -253,10 +266,23 @@ export class BuildAction {
|
|
|
253
266
|
!esbuild?.outdir &&
|
|
254
267
|
(pgk?.main || pgk?.module)
|
|
255
268
|
) {
|
|
256
|
-
|
|
269
|
+
const candidate =
|
|
257
270
|
esbuild?.format === "cjs"
|
|
258
271
|
? pgk.main || pgk.module
|
|
259
272
|
: pgk.module || pgk.main;
|
|
273
|
+
if (candidate && isUnsafeDefaultOutput(candidate)) {
|
|
274
|
+
// `package.json#main`/`module` commonly points at a TypeScript
|
|
275
|
+
// source (e.g. `src/index.ts` for TS-direct workflows). Falling
|
|
276
|
+
// back to that value would have esbuild OVERWRITE the source.
|
|
277
|
+
// Surface a clear error and require an explicit outfile/outdir
|
|
278
|
+
// instead of silently destroying the user's code.
|
|
279
|
+
throw new Error(
|
|
280
|
+
`gjsify build: refusing to default --outfile to ${candidate} ` +
|
|
281
|
+
`(would overwrite a TypeScript source file). Pass --outfile/--outdir ` +
|
|
282
|
+
`explicitly, or set "gjsify.esbuild.outfile" in package.json.`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
esbuild.outfile = candidate;
|
|
260
286
|
}
|
|
261
287
|
|
|
262
288
|
const { consoleShim, globals } = this.configData;
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { APP_NAME } from './constants.js';
|
|
2
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
|
+
}
|
|
3
22
|
import { readPackageJSON, resolvePackageJSON } from 'pkg-types';
|
|
4
23
|
import { getTsconfig } from 'get-tsconfig';
|
|
5
24
|
|
|
@@ -40,20 +59,48 @@ export class Config {
|
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
/** Loads gjsify config file, e.g `.gjsifyrc.js` */
|
|
43
|
-
private async load(searchFrom?: string) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
private async load(searchFrom?: string) {
|
|
63
|
+
// cosmiconfig's default first-match-wins behaviour silently drops one
|
|
64
|
+
// source when both `package.json#gjsify` and an explicit config file
|
|
65
|
+
// (`.gjsifyrc.js`, `gjsify.config.mjs`, ...) are present. Project hits
|
|
66
|
+
// this footgun: adding `gjsify.bin` to package.json (so `gjsify dlx`
|
|
67
|
+
// resolves the GJS bundle) silently disables `.gjsifyrc.js`. We
|
|
68
|
+
// explicitly load both sources and merge — package.json is the lower
|
|
69
|
+
// layer, the explicit file wins on key collisions.
|
|
70
|
+
//
|
|
71
|
+
// Run two searches:
|
|
72
|
+
// 1. Default (includes package.json) — for projects that only use
|
|
73
|
+
// package.json#gjsify and no separate file.
|
|
74
|
+
// 2. Explicit-file only (package.json excluded) — to find the
|
|
75
|
+
// `.gjsifyrc.*` / `gjsify.config.*` regardless of whether
|
|
76
|
+
// package.json#gjsify exists.
|
|
77
|
+
const fileExplorer = cosmiconfig(APP_NAME, {
|
|
78
|
+
...this.loadOptions,
|
|
79
|
+
searchPlaces: (this.loadOptions.searchPlaces ?? defaultSearchPlaces(APP_NAME))
|
|
80
|
+
.filter((p) => p !== 'package.json'),
|
|
81
|
+
});
|
|
82
|
+
const fileResult = await fileExplorer.search(searchFrom) as CosmiconfigResult<ConfigData> | null;
|
|
83
|
+
|
|
84
|
+
const merged: ConfigData = {};
|
|
85
|
+
try {
|
|
86
|
+
const pkg = await this.readPackageJSON(searchFrom) as { gjsify?: ConfigData };
|
|
87
|
+
if (isPlainObject(pkg?.gjsify)) merge(merged, pkg.gjsify);
|
|
88
|
+
} catch {
|
|
89
|
+
// Missing or unreadable package.json — skip.
|
|
50
90
|
}
|
|
91
|
+
if (fileResult?.config && isPlainObject(fileResult.config)) {
|
|
92
|
+
merge(merged, fileResult.config);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
merged.esbuild ||= {};
|
|
96
|
+
merged.library ||= {};
|
|
97
|
+
merged.typescript ||= {};
|
|
51
98
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
99
|
+
return {
|
|
100
|
+
config: merged,
|
|
101
|
+
filepath: fileResult?.filepath ?? '',
|
|
102
|
+
isEmpty: !fileResult && Object.keys(merged).length === 3, // only the three default-empty objects
|
|
103
|
+
};
|
|
57
104
|
}
|
|
58
105
|
|
|
59
106
|
/** Loads package.json of the current project */
|