@gjsify/cli 0.49.0 → 0.50.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 +97 -97
- package/lib/actions/barrels-generate.d.ts +22 -0
- package/lib/actions/barrels-generate.js +36 -1
- package/lib/actions/build.js +8 -5
- package/lib/commands/barrels.js +19 -2
- package/lib/commands/foreach.js +11 -19
- package/lib/commands/test.d.ts +16 -0
- package/lib/commands/test.js +30 -29
- package/lib/commands/workspace.js +47 -44
- package/lib/index.js +10 -0
- package/lib/utils/build-cache.d.ts +15 -4
- package/lib/utils/build-cache.js +62 -67
- package/lib/utils/cli-exit.d.ts +23 -0
- package/lib/utils/cli-exit.js +78 -0
- package/lib/utils/package-inputs.d.ts +81 -0
- package/lib/utils/package-inputs.js +233 -0
- package/lib/utils/ship/changelog.d.ts +15 -14
- package/lib/utils/ship/changelog.js +14 -13
- package/lib/utils/ship/formats.js +23 -2
- package/lib/utils/ship/gzip.d.ts +31 -2
- package/lib/utils/ship/gzip.js +55 -3
- package/lib/utils/ship/plan.d.ts +32 -4
- package/lib/utils/ship/plan.js +61 -10
- package/lib/utils/simple-command.d.ts +17 -2
- package/lib/utils/simple-command.js +9 -4
- package/package.json +19 -19
|
@@ -450,7 +450,21 @@ export const FORMATS = {
|
|
|
450
450
|
// is a download: it lands in a browser's downloads folder beside other
|
|
451
451
|
// files, so it carries the version and the arch, and it avoids the spaces
|
|
452
452
|
// a display name may contain.
|
|
453
|
-
|
|
453
|
+
//
|
|
454
|
+
// AND THE OPERATING SYSTEM, which the extension does not name. Every other
|
|
455
|
+
// row's suffix names exactly one platform — `.deb`, `.rpm`, `.flatpak`,
|
|
456
|
+
// `.dmg`, `.msi` — and `.zip` names none, so this row and `windows-dir-zip`
|
|
457
|
+
// are the only two artifacts in the table a user cannot tell apart by
|
|
458
|
+
// looking at them. Before this token they were separated by nothing but a
|
|
459
|
+
// coincidence between two unrelated arch tables (`MACOS_ARCH` maps x64 to
|
|
460
|
+
// `x86_64`, `WINDOWS_ARCH` to `x64`), which put a bare `…-1.arm64.zip` on a
|
|
461
|
+
// release page beside a Windows zip with no way to choose — and would have
|
|
462
|
+
// had the two formats silently overwrite each other in `ship/out/` the day
|
|
463
|
+
// a Windows/arm64 row became possible, an upstream blocker (#1117) being
|
|
464
|
+
// all that stood in the way. `macos`, not `darwin`, for the same reason
|
|
465
|
+
// `MACOS_ARCH` writes `x86_64` rather than `x64`: this label is read by
|
|
466
|
+
// whoever downloads the file, never by `process.platform`.
|
|
467
|
+
fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.macos.${archLabel}.zip`,
|
|
454
468
|
artifactKind: 'file',
|
|
455
469
|
},
|
|
456
470
|
// ── macOS (#1354 M4) ─────────────────────────────────────────────────
|
|
@@ -661,7 +675,14 @@ export const FORMATS = {
|
|
|
661
675
|
// pair splits them: this one is a download that lands in a browser's
|
|
662
676
|
// folder beside other files, so it carries the version and the arch and
|
|
663
677
|
// avoids the spaces a display name may contain.
|
|
664
|
-
|
|
678
|
+
//
|
|
679
|
+
// …and the OS, for the reason spelled out on the `macos-app-zip` row it was
|
|
680
|
+
// copied from: `.zip` is the one container this table puts on more than one
|
|
681
|
+
// operating system, so it is the one name that has to say which. `windows`
|
|
682
|
+
// rather than `win32` — the spelling is the user's, like the `x64` this row
|
|
683
|
+
// keeps because Node's own archives, gvsbuild's assets and the runner label
|
|
684
|
+
// all use it.
|
|
685
|
+
fileName: (s, archLabel) => `${s.binaryName}-${s.version}-${s.release}.windows.${archLabel}.zip`,
|
|
665
686
|
artifactKind: 'file',
|
|
666
687
|
},
|
|
667
688
|
// ── The Windows installer (#1354 M5) ─────────────────────────────────
|
package/lib/utils/ship/gzip.d.ts
CHANGED
|
@@ -1,2 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* gzip with the header's timestamp zeroed and the OS byte pinned to Unix.
|
|
3
|
+
*
|
|
4
|
+
* @param level 0-9, or omitted for zlib's default. THE ONLY HONEST WAY TO SET XFL:
|
|
5
|
+
* byte 8 of the header records how hard the compressor tried (2 = maximum,
|
|
6
|
+
* 4 = fastest, 0 = neither), `lintian` reads it to raise
|
|
7
|
+
* `changelog-not-compressed-with-max-compression`, and `file` prints it as
|
|
8
|
+
* "max compression". Stamping that byte here beside the mtime and OS bytes would
|
|
9
|
+
* be a different thing entirely and was rejected for it: mtime and OS are facts
|
|
10
|
+
* about the build ENVIRONMENT, which a reproducible build is entitled to
|
|
11
|
+
* normalise, while XFL is a statement about the compression actually performed.
|
|
12
|
+
* Writing a 2 we had not earned would make the artifact lie to the tool reading
|
|
13
|
+
* it. So the level travels down to the compressor instead.
|
|
14
|
+
*
|
|
15
|
+
* AND THE TEMPTATION IS REAL, which is why this is worth writing down: measured
|
|
16
|
+
* on gjsify's own `.deb`, the changelog member compressed at the default level
|
|
17
|
+
* and at 9 differs in EXACTLY ONE BYTE — position 9, XFL, 0 against 2 — and is
|
|
18
|
+
* 1152 bytes either way. For this input, stamping the byte would have produced
|
|
19
|
+
* the identical artifact. It is identical by coincidence of a small input: over
|
|
20
|
+
* this repo's full `CHANGELOG.md` the same two levels differ by thousands of
|
|
21
|
+
* bytes. A rule that happens to hold on today's input is not the rule.
|
|
22
|
+
*/
|
|
23
|
+
export declare function gzipDeterministic(input: Uint8Array, level?: number): Promise<Uint8Array>;
|
|
24
|
+
/**
|
|
25
|
+
* `gzip -9`, the level Debian Policy § 4.4 asks of a packaging changelog.
|
|
26
|
+
*
|
|
27
|
+
* Named rather than spelled `9` at the call site so the reason travels with the
|
|
28
|
+
* number: the `-n` half of Policy's `gzip -9 -n` is what {@link gzipDeterministic}
|
|
29
|
+
* already does to bytes 4-7.
|
|
30
|
+
*/
|
|
31
|
+
export declare const POLICY_MAX_COMPRESSION = 9;
|
package/lib/utils/ship/gzip.js
CHANGED
|
@@ -5,10 +5,54 @@
|
|
|
5
5
|
// input produce different bytes — which no test notices, because every test
|
|
6
6
|
// compares the DECOMPRESSED content. It is the single most-missed
|
|
7
7
|
// reproducibility bug in package writers, and it costs nothing to close.
|
|
8
|
+
//
|
|
9
|
+
// WHAT "DETERMINISTIC" DOES NOT COVER, measured 2026-09-11 and named here because
|
|
10
|
+
// the function's own name overstates it: the bytes are reproducible for a given
|
|
11
|
+
// HOST, not for a given artifact. `@gjsify/tar`'s gzip runs on the platform's
|
|
12
|
+
// zlib, and the platforms do not ship the same one — Fedora's libz is
|
|
13
|
+
// `zlib-ng-compat` (2.3.3) while Node bundles its own zlib (1.3.2.1-motley). Over
|
|
14
|
+
// this repo's `CHANGELOG.md` (876 192 bytes) at the DEFAULT level they produce
|
|
15
|
+
// 272 003 and 272 000 bytes respectively, so a `.deb` packed under GJS and one
|
|
16
|
+
// packed under Node already differ, today, before any level argument exists.
|
|
17
|
+
// Levels 1-4 differ too, in the other direction (zlib-ng is smaller). Two
|
|
18
|
+
// consequences worth keeping:
|
|
19
|
+
//
|
|
20
|
+
// * `tests/e2e/ship-from-stage` asserts byte-equality between a direct pack and
|
|
21
|
+
// a `--from-stage` pack, and it holds because both run on ONE host. It is
|
|
22
|
+
// structurally blind to this, and a cross-host pack is exactly what
|
|
23
|
+
// `--from-stage` exists to enable.
|
|
24
|
+
// * zlib-ng's level 9 is WORSE than its level 8 on large inputs — 277 974 vs
|
|
25
|
+
// 270 255 bytes on the file above, ~2.9 % bigger — which is why `level: 9` is
|
|
26
|
+
// asked for only where a reader demands it (see below) and not blanket-applied
|
|
27
|
+
// to the payload tarballs.
|
|
28
|
+
//
|
|
29
|
+
// Ledgered in `status/open-todos.md`; closing it means pinning ONE deflate
|
|
30
|
+
// implementation, which is a larger decision than this file.
|
|
8
31
|
import { gzip } from '@gjsify/tar';
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
32
|
+
/**
|
|
33
|
+
* gzip with the header's timestamp zeroed and the OS byte pinned to Unix.
|
|
34
|
+
*
|
|
35
|
+
* @param level 0-9, or omitted for zlib's default. THE ONLY HONEST WAY TO SET XFL:
|
|
36
|
+
* byte 8 of the header records how hard the compressor tried (2 = maximum,
|
|
37
|
+
* 4 = fastest, 0 = neither), `lintian` reads it to raise
|
|
38
|
+
* `changelog-not-compressed-with-max-compression`, and `file` prints it as
|
|
39
|
+
* "max compression". Stamping that byte here beside the mtime and OS bytes would
|
|
40
|
+
* be a different thing entirely and was rejected for it: mtime and OS are facts
|
|
41
|
+
* about the build ENVIRONMENT, which a reproducible build is entitled to
|
|
42
|
+
* normalise, while XFL is a statement about the compression actually performed.
|
|
43
|
+
* Writing a 2 we had not earned would make the artifact lie to the tool reading
|
|
44
|
+
* it. So the level travels down to the compressor instead.
|
|
45
|
+
*
|
|
46
|
+
* AND THE TEMPTATION IS REAL, which is why this is worth writing down: measured
|
|
47
|
+
* on gjsify's own `.deb`, the changelog member compressed at the default level
|
|
48
|
+
* and at 9 differs in EXACTLY ONE BYTE — position 9, XFL, 0 against 2 — and is
|
|
49
|
+
* 1152 bytes either way. For this input, stamping the byte would have produced
|
|
50
|
+
* the identical artifact. It is identical by coincidence of a small input: over
|
|
51
|
+
* this repo's full `CHANGELOG.md` the same two levels differ by thousands of
|
|
52
|
+
* bytes. A rule that happens to hold on today's input is not the rule.
|
|
53
|
+
*/
|
|
54
|
+
export async function gzipDeterministic(input, level) {
|
|
55
|
+
const out = await gzip(input, level === undefined ? undefined : { level });
|
|
12
56
|
if (out.byteLength < 10 || out[0] !== 0x1f || out[1] !== 0x8b) {
|
|
13
57
|
throw new Error('gjsify ship: internal error — gzip did not produce a gzip stream.');
|
|
14
58
|
}
|
|
@@ -19,3 +63,11 @@ export async function gzipDeterministic(input) {
|
|
|
19
63
|
out[9] = 3;
|
|
20
64
|
return out;
|
|
21
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* `gzip -9`, the level Debian Policy § 4.4 asks of a packaging changelog.
|
|
68
|
+
*
|
|
69
|
+
* Named rather than spelled `9` at the call site so the reason travels with the
|
|
70
|
+
* number: the `-n` half of Policy's `gzip -9 -n` is what {@link gzipDeterministic}
|
|
71
|
+
* already does to bytes 4-7.
|
|
72
|
+
*/
|
|
73
|
+
export const POLICY_MAX_COMPRESSION = 9;
|
package/lib/utils/ship/plan.d.ts
CHANGED
|
@@ -67,11 +67,39 @@ export declare function planOverlay(settings: ShipSettings, format: FormatDescri
|
|
|
67
67
|
*/
|
|
68
68
|
export declare function assertOverlayIsLicensed(formatId: FormatId, binaryName: string, overlay: readonly StagedFile[]): void;
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* Whether an icon belongs in the theme's SYMBOLIC context rather than at a size.
|
|
71
|
+
*
|
|
72
|
+
* `symbolic` is a directory of its own in a hicolor theme and NOT a size value.
|
|
73
|
+
* Measured in `refs/adwaita-icon-theme/index.theme`, which lists `symbolic/apps`
|
|
74
|
+
* in `Directories=` beside `scalable/apps` and `16x16/apps` and gives it its own
|
|
75
|
+
* section — `Context=Applications`, `Size=16`, `MinSize=8`, `MaxSize=512`,
|
|
76
|
+
* `Type=Scalable` — against the scalable row's `Size=128`. The freedesktop icon
|
|
77
|
+
* theme specification has no notion of `symbolic` at all: a theme expresses it by
|
|
78
|
+
* giving the directory a section, which is exactly why reading the EXTENSION
|
|
79
|
+
* cannot see it, and why this question has to be asked before the size one.
|
|
80
|
+
*
|
|
81
|
+
* TWO SIGNALS, the same pair `iconThemeDir` already reads for a size: the
|
|
82
|
+
* directory an author put the file in, and the name they gave it. GTK resolves a
|
|
83
|
+
* symbolic icon by the `-symbolic` name suffix, so an author who wrote the name
|
|
84
|
+
* has said as much as one who wrote the path.
|
|
85
|
+
*
|
|
86
|
+
* SVG ONLY, because the symbolic directory is declared `Type=Scalable`: the
|
|
87
|
+
* raster form GTK's own `gtk-encode-symbolic-svg` produces
|
|
88
|
+
* (`<name>-symbolic.symbolic.png`) is installed at a SIZE instead. So a PNG keeps
|
|
89
|
+
* answering the size question, and one that cannot answer it stays refused rather
|
|
90
|
+
* than quietly becoming a scalable icon that does not scale.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isSymbolicIcon(iconPath: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The hicolor subdirectory for an icon: the `symbolic` CONTEXT, else `scalable`
|
|
95
|
+
* for an SVG, else the pixel size read from a `<n>x<n>` path component or a
|
|
96
|
+
* trailing number in the filename.
|
|
97
|
+
*
|
|
98
|
+
* Named for the theme DIRECTORY rather than a size from the moment the first of
|
|
99
|
+
* those answers stopped being one: `iconSizeDir` returning `symbolic` would be a
|
|
100
|
+
* name every caller has to read past.
|
|
73
101
|
*/
|
|
74
|
-
export declare function
|
|
102
|
+
export declare function iconThemeDir(iconPath: string): string;
|
|
75
103
|
/**
|
|
76
104
|
* A native module inside the payload has to keep its executable bit.
|
|
77
105
|
*
|
package/lib/utils/ship/plan.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// as `utils/copy-targets.ts`, and for the same reason: a stager that discovers
|
|
4
4
|
// its own inputs can only be tested by building a real project.
|
|
5
5
|
import { renderDebianChangelog } from './changelog.js';
|
|
6
|
-
import { gzipDeterministic } from './gzip.js';
|
|
6
|
+
import { gzipDeterministic, POLICY_MAX_COMPRESSION } from './gzip.js';
|
|
7
7
|
import { renderMimePackage } from './mime.js';
|
|
8
8
|
import { SHARE } from './share-dirs.js';
|
|
9
9
|
import { basename, extname, posix } from 'node:path';
|
|
@@ -174,12 +174,19 @@ export async function planOverlay(settings, format, inputs) {
|
|
|
174
174
|
// gzip performed on the packing host would put a second compressor's output in
|
|
175
175
|
// an artifact whose every other byte came from the first. `gzipDeterministic`
|
|
176
176
|
// zeroes the header stamp for the usual reason (utils/ship/gzip.ts).
|
|
177
|
+
//
|
|
178
|
+
// It is also the one gzip member in the artifact whose bytes cannot vary with
|
|
179
|
+
// the packing host, for the same reason: the two hosts' zlibs disagree (that
|
|
180
|
+
// file's header measurement), and this member is compressed once, here.
|
|
177
181
|
if (format.changelogDest !== undefined) {
|
|
178
182
|
const changelog = renderDebianChangelog(settings, inputs.changelogText, inputs.mtime);
|
|
179
183
|
files.push({
|
|
180
184
|
path: format.changelogDest(settings.binaryName),
|
|
181
185
|
mode: 0o644,
|
|
182
|
-
source: {
|
|
186
|
+
source: {
|
|
187
|
+
kind: 'bytes',
|
|
188
|
+
data: await gzipDeterministic(new TextEncoder().encode(changelog), POLICY_MAX_COMPRESSION),
|
|
189
|
+
},
|
|
183
190
|
});
|
|
184
191
|
}
|
|
185
192
|
return files;
|
|
@@ -233,19 +240,27 @@ function renderDebianCopyright(settings, licenseText) {
|
|
|
233
240
|
* Icons land in the hicolor theme, named after the app id — that name is what
|
|
234
241
|
* the desktop entry's `Icon=` and the MetaInfo `<id>` both point at, so a file
|
|
235
242
|
* keeping its source basename installs an icon nothing ever looks up.
|
|
243
|
+
*
|
|
244
|
+
* A SYMBOLIC icon keeps the `-symbolic` half of that name, and for the same
|
|
245
|
+
* reason rather than as an exception: the name GTK resolves a symbolic icon by
|
|
246
|
+
* is `<app id>-symbolic`, so renaming the file to the bare id installs an icon
|
|
247
|
+
* nothing ever looks up — one sentence, applied to both lookups.
|
|
236
248
|
*/
|
|
237
249
|
function planIcons(settings) {
|
|
238
250
|
const out = [];
|
|
239
251
|
const seen = new Map();
|
|
240
252
|
for (const icon of settings.iconFiles) {
|
|
241
253
|
const ext = extname(icon);
|
|
242
|
-
const dir =
|
|
243
|
-
const
|
|
254
|
+
const dir = iconThemeDir(icon);
|
|
255
|
+
const name = isSymbolicIcon(icon) ? `${settings.appId}-symbolic` : settings.appId;
|
|
256
|
+
const path = `${SHARE.icons}/${dir}/apps/${name}${ext}`;
|
|
244
257
|
const previous = seen.get(path);
|
|
245
258
|
if (previous !== undefined) {
|
|
246
259
|
throw new Error(`gjsify ship: ${icon} and ${previous} both install as ${path}. ` +
|
|
247
|
-
'
|
|
248
|
-
'
|
|
260
|
+
'An icon is keyed on its context and its size: `symbolic` is read from a `symbolic/` ' +
|
|
261
|
+
'path component or a `-symbolic` filename, the size from the path (`.../128x128/...`) ' +
|
|
262
|
+
'or the filename (`icon-128.png`). Give them distinguishable sizes, or point ' +
|
|
263
|
+
'`gjsify.ship.icon` at a single file.');
|
|
249
264
|
}
|
|
250
265
|
seen.set(path, icon);
|
|
251
266
|
out.push({ path, mode: 0o644, source: { kind: 'file', path: icon } });
|
|
@@ -253,11 +268,47 @@ function planIcons(settings) {
|
|
|
253
268
|
return out;
|
|
254
269
|
}
|
|
255
270
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
271
|
+
* Whether an icon belongs in the theme's SYMBOLIC context rather than at a size.
|
|
272
|
+
*
|
|
273
|
+
* `symbolic` is a directory of its own in a hicolor theme and NOT a size value.
|
|
274
|
+
* Measured in `refs/adwaita-icon-theme/index.theme`, which lists `symbolic/apps`
|
|
275
|
+
* in `Directories=` beside `scalable/apps` and `16x16/apps` and gives it its own
|
|
276
|
+
* section — `Context=Applications`, `Size=16`, `MinSize=8`, `MaxSize=512`,
|
|
277
|
+
* `Type=Scalable` — against the scalable row's `Size=128`. The freedesktop icon
|
|
278
|
+
* theme specification has no notion of `symbolic` at all: a theme expresses it by
|
|
279
|
+
* giving the directory a section, which is exactly why reading the EXTENSION
|
|
280
|
+
* cannot see it, and why this question has to be asked before the size one.
|
|
281
|
+
*
|
|
282
|
+
* TWO SIGNALS, the same pair `iconThemeDir` already reads for a size: the
|
|
283
|
+
* directory an author put the file in, and the name they gave it. GTK resolves a
|
|
284
|
+
* symbolic icon by the `-symbolic` name suffix, so an author who wrote the name
|
|
285
|
+
* has said as much as one who wrote the path.
|
|
286
|
+
*
|
|
287
|
+
* SVG ONLY, because the symbolic directory is declared `Type=Scalable`: the
|
|
288
|
+
* raster form GTK's own `gtk-encode-symbolic-svg` produces
|
|
289
|
+
* (`<name>-symbolic.symbolic.png`) is installed at a SIZE instead. So a PNG keeps
|
|
290
|
+
* answering the size question, and one that cannot answer it stays refused rather
|
|
291
|
+
* than quietly becoming a scalable icon that does not scale.
|
|
292
|
+
*/
|
|
293
|
+
export function isSymbolicIcon(iconPath) {
|
|
294
|
+
if (extname(iconPath).toLowerCase() !== '.svg')
|
|
295
|
+
return false;
|
|
296
|
+
if (/(?:^|[\\/])symbolic[\\/]/.test(iconPath))
|
|
297
|
+
return true;
|
|
298
|
+
return basename(iconPath, extname(iconPath)).endsWith('-symbolic');
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The hicolor subdirectory for an icon: the `symbolic` CONTEXT, else `scalable`
|
|
302
|
+
* for an SVG, else the pixel size read from a `<n>x<n>` path component or a
|
|
303
|
+
* trailing number in the filename.
|
|
304
|
+
*
|
|
305
|
+
* Named for the theme DIRECTORY rather than a size from the moment the first of
|
|
306
|
+
* those answers stopped being one: `iconSizeDir` returning `symbolic` would be a
|
|
307
|
+
* name every caller has to read past.
|
|
259
308
|
*/
|
|
260
|
-
export function
|
|
309
|
+
export function iconThemeDir(iconPath) {
|
|
310
|
+
if (isSymbolicIcon(iconPath))
|
|
311
|
+
return 'symbolic';
|
|
261
312
|
if (extname(iconPath).toLowerCase() === '.svg')
|
|
262
313
|
return 'scalable';
|
|
263
314
|
const square = /(?:^|[\\/])(\d{1,4})x\1(?:[\\/]|$)/.exec(iconPath);
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
export interface TokenizeOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Accept an unquoted `*`/`?` as an ordinary token character.
|
|
4
|
+
*
|
|
5
|
+
* Off by default, and the default is about EXECUTION: a caller that RUNS
|
|
6
|
+
* the tokens must not pass `*.tsbuildinfo` through unexpanded, because the
|
|
7
|
+
* shell it replaces would have expanded it first. A caller that only READS
|
|
8
|
+
* the targets — `utils/package-inputs.ts`, asking which paths a package
|
|
9
|
+
* declares it produces — wants the pattern itself, and `gjsify clear`
|
|
10
|
+
* expands the same two characters in its own last segment anyway
|
|
11
|
+
* (`utils/clear-targets.ts`). Nothing else moves: every operator,
|
|
12
|
+
* substitution, brace and tilde still returns `null`.
|
|
13
|
+
*/
|
|
14
|
+
allowGlobs?: boolean;
|
|
15
|
+
}
|
|
1
16
|
/**
|
|
2
17
|
* Tokenize `cmd`, or return `null` if the string contains anything the shell
|
|
3
18
|
* would treat specially — operators (`&& | ; < > &`), substitutions (`$(...)` /
|
|
@@ -7,10 +22,10 @@
|
|
|
7
22
|
* single literal token, exactly as the shell would hand it to `gjsify` (which
|
|
8
23
|
* does its own glob expansion).
|
|
9
24
|
*/
|
|
10
|
-
export declare function tokenizeSimpleCommand(cmd: string): string[] | null;
|
|
25
|
+
export declare function tokenizeSimpleCommand(cmd: string, options?: TokenizeOptions): string[] | null;
|
|
11
26
|
/**
|
|
12
27
|
* The argv of a script that is a single `gjsify <subcommand> …` command — i.e.
|
|
13
28
|
* the tokens after `gjsify` — or `null` when it is anything else. `null` is the
|
|
14
29
|
* signal to hand the literal to a shell.
|
|
15
30
|
*/
|
|
16
|
-
export declare function gjsifyCommandArgv(literal: string): string[] | null;
|
|
31
|
+
export declare function gjsifyCommandArgv(literal: string, options?: TokenizeOptions): string[] | null;
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
// deliberately conservative in ONE direction — a `null` costs the caller its
|
|
7
7
|
// fast path, a wrong token list would run a different command than the script
|
|
8
8
|
// says.
|
|
9
|
+
/** Characters that make a string something only a shell may execute. */
|
|
10
|
+
const SHELL_SPECIAL = '|&;<>`$()\\\n\r*?{}[]~#!';
|
|
11
|
+
/** The same set minus `*` and `?` — see {@link TokenizeOptions.allowGlobs}. */
|
|
12
|
+
const SHELL_SPECIAL_ALLOWING_GLOBS = SHELL_SPECIAL.replace('*', '').replace('?', '');
|
|
9
13
|
/**
|
|
10
14
|
* Tokenize `cmd`, or return `null` if the string contains anything the shell
|
|
11
15
|
* would treat specially — operators (`&& | ; < > &`), substitutions (`$(...)` /
|
|
@@ -15,7 +19,8 @@
|
|
|
15
19
|
* single literal token, exactly as the shell would hand it to `gjsify` (which
|
|
16
20
|
* does its own glob expansion).
|
|
17
21
|
*/
|
|
18
|
-
export function tokenizeSimpleCommand(cmd) {
|
|
22
|
+
export function tokenizeSimpleCommand(cmd, options = {}) {
|
|
23
|
+
const special = options.allowGlobs === true ? SHELL_SPECIAL_ALLOWING_GLOBS : SHELL_SPECIAL;
|
|
19
24
|
const tokens = [];
|
|
20
25
|
let cur = '';
|
|
21
26
|
let has = false;
|
|
@@ -53,7 +58,7 @@ export function tokenizeSimpleCommand(cmd) {
|
|
|
53
58
|
}
|
|
54
59
|
continue;
|
|
55
60
|
}
|
|
56
|
-
if (
|
|
61
|
+
if (special.includes(c))
|
|
57
62
|
return null;
|
|
58
63
|
cur += c;
|
|
59
64
|
has = true;
|
|
@@ -69,8 +74,8 @@ export function tokenizeSimpleCommand(cmd) {
|
|
|
69
74
|
* the tokens after `gjsify` — or `null` when it is anything else. `null` is the
|
|
70
75
|
* signal to hand the literal to a shell.
|
|
71
76
|
*/
|
|
72
|
-
export function gjsifyCommandArgv(literal) {
|
|
73
|
-
const tokens = tokenizeSimpleCommand(literal);
|
|
77
|
+
export function gjsifyCommandArgv(literal, options = {}) {
|
|
78
|
+
const tokens = tokenizeSimpleCommand(literal, options);
|
|
74
79
|
if (!tokens || tokens.length < 2 || tokens[0] !== 'gjsify')
|
|
75
80
|
return null;
|
|
76
81
|
return tokens.slice(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -142,21 +142,21 @@
|
|
|
142
142
|
"cli"
|
|
143
143
|
],
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@gjsify/buffer": "^0.
|
|
146
|
-
"@gjsify/create-app": "^0.
|
|
147
|
-
"@gjsify/node-globals": "^0.
|
|
148
|
-
"@gjsify/node-polyfills": "^0.
|
|
149
|
-
"@gjsify/npm-registry": "^0.
|
|
150
|
-
"@gjsify/resolve-npm": "^0.
|
|
151
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.
|
|
152
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
153
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
154
|
-
"@gjsify/semver": "^0.
|
|
155
|
-
"@gjsify/tar": "^0.
|
|
156
|
-
"@gjsify/tsc": "^0.
|
|
157
|
-
"@gjsify/utils": "^0.
|
|
158
|
-
"@gjsify/web-polyfills": "^0.
|
|
159
|
-
"@gjsify/workspace": "^0.
|
|
145
|
+
"@gjsify/buffer": "^0.50.0",
|
|
146
|
+
"@gjsify/create-app": "^0.50.0",
|
|
147
|
+
"@gjsify/node-globals": "^0.50.0",
|
|
148
|
+
"@gjsify/node-polyfills": "^0.50.0",
|
|
149
|
+
"@gjsify/npm-registry": "^0.50.0",
|
|
150
|
+
"@gjsify/resolve-npm": "^0.50.0",
|
|
151
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.50.0",
|
|
152
|
+
"@gjsify/vite-plugin-blueprint": "^0.50.0",
|
|
153
|
+
"@gjsify/rolldown-plugin-pnp": "^0.50.0",
|
|
154
|
+
"@gjsify/semver": "^0.50.0",
|
|
155
|
+
"@gjsify/tar": "^0.50.0",
|
|
156
|
+
"@gjsify/tsc": "^0.50.0",
|
|
157
|
+
"@gjsify/utils": "^0.50.0",
|
|
158
|
+
"@gjsify/web-polyfills": "^0.50.0",
|
|
159
|
+
"@gjsify/workspace": "^0.50.0",
|
|
160
160
|
"acorn": "^8.17.0",
|
|
161
161
|
"cosmiconfig": "^9.0.2",
|
|
162
162
|
"get-tsconfig": "^4.14.0",
|
|
@@ -165,13 +165,13 @@
|
|
|
165
165
|
"yargs": "^18.0.0"
|
|
166
166
|
},
|
|
167
167
|
"devDependencies": {
|
|
168
|
-
"@gjsify/unit": "^0.
|
|
168
|
+
"@gjsify/unit": "^0.50.0",
|
|
169
169
|
"@types/yargs": "^17.0.35",
|
|
170
170
|
"typescript": "^6.0.3"
|
|
171
171
|
},
|
|
172
172
|
"peerDependencies": {
|
|
173
|
-
"@gjsify/oxfmt-native": "^0.
|
|
174
|
-
"@gjsify/rolldown-native": "^0.
|
|
173
|
+
"@gjsify/oxfmt-native": "^0.50.0",
|
|
174
|
+
"@gjsify/rolldown-native": "^0.50.0"
|
|
175
175
|
},
|
|
176
176
|
"peerDependenciesMeta": {
|
|
177
177
|
"@gjsify/oxfmt-native": {
|