@gjsify/cli 0.4.5 → 0.4.10
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 +134 -130
- package/lib/commands/generate-installer.d.ts +10 -0
- package/lib/commands/generate-installer.js +113 -0
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/index.js +4 -0
- package/lib/commands/pack.d.ts +40 -0
- package/lib/commands/pack.js +335 -0
- package/lib/commands/publish.d.ts +12 -0
- package/lib/commands/publish.js +319 -0
- package/lib/commands/self-update.d.ts +8 -0
- package/lib/commands/self-update.js +138 -0
- package/lib/index.js +5 -1
- package/lib/templates/install.mjs.tmpl +248 -0
- package/lib/utils/install-global.js +13 -1
- package/package.json +17 -17
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env -S gjs -m
|
|
2
|
+
/**
|
|
3
|
+
* gjsify universal installer — bootstraps `@gjsify/cli` (or any GJS app
|
|
4
|
+
* published to npm) on a system that has only `gjs` (and `curl`/`wget`)
|
|
5
|
+
* available, without requiring Node.js or `npm`.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* gjs -m install.mjs # install / update @gjsify/cli
|
|
9
|
+
* gjs -m install.mjs --target @scope/x # install any other GJS-runnable npm package
|
|
10
|
+
* gjs -m install.mjs --tag next # pick an npm dist-tag or pinned version
|
|
11
|
+
* gjs -m install.mjs --force # reinstall even if already present
|
|
12
|
+
* gjs -m install.mjs --help
|
|
13
|
+
*
|
|
14
|
+
* How it works (two-stage bootstrap):
|
|
15
|
+
* 1. Download a small self-contained GJS bundle of the @gjsify/cli
|
|
16
|
+
* (`cli.gjs.mjs`) from this repo's GitHub releases. Verify SHA-256.
|
|
17
|
+
* 2. Spawn that bundle: `gjs -m <bundle> install -g <target>@<tag>`. The
|
|
18
|
+
* bundle handles transitive dependency resolution, native prebuilds,
|
|
19
|
+
* lockfiles, and the `~/.local/bin` launchers — all the things this
|
|
20
|
+
* thin bootstrapper deliberately does NOT re-implement.
|
|
21
|
+
*
|
|
22
|
+
* Generated by `gjsify generate-installer` for end-user GJS apps: in that
|
|
23
|
+
* mode the constants below (BOOTSTRAP_URL, DEFAULT_TARGET, DEFAULT_BIN_NAME)
|
|
24
|
+
* are pre-substituted to the consumer's package + custom bootstrap URL.
|
|
25
|
+
*
|
|
26
|
+
* Test hooks (set by tests/e2e/install-script/run.mjs):
|
|
27
|
+
* GJSIFY_INSTALL_BOOTSTRAP_URL override the cli.gjs.mjs download origin
|
|
28
|
+
* (accepts file:// for offline tests)
|
|
29
|
+
* GJSIFY_INSTALL_BOOTSTRAP_SHA256_URL override the .sha256 companion URL
|
|
30
|
+
* (set to empty string to skip SHA-256)
|
|
31
|
+
* GJSIFY_GLOBAL_PREFIX override install prefix (forwarded to cli)
|
|
32
|
+
* GJSIFY_GLOBAL_BIN_DIR override bin dir (forwarded to cli)
|
|
33
|
+
* GJSIFY_INSTALL_REGISTRY override npm registry (forwarded as npm_config_registry)
|
|
34
|
+
* GJSIFY_INSTALL_BOOTSTRAP_CACHE override the bootstrap cache dir
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import GLib from 'gi://GLib';
|
|
38
|
+
import Gio from 'gi://Gio';
|
|
39
|
+
import Soup from 'gi://Soup?version=3.0';
|
|
40
|
+
import system, { exit } from 'system';
|
|
41
|
+
|
|
42
|
+
Gio._promisify(Soup.Session.prototype, 'send_and_read_async');
|
|
43
|
+
Gio._promisify(Gio.Subprocess.prototype, 'wait_check_async');
|
|
44
|
+
|
|
45
|
+
// Substituted by `gjsify generate-installer` for end-user apps.
|
|
46
|
+
const DEFAULT_TARGET = '@gjsify/cli';
|
|
47
|
+
const DEFAULT_BIN_NAME = 'gjsify';
|
|
48
|
+
const DEFAULT_BOOTSTRAP_URL =
|
|
49
|
+
'https://github.com/gjsify/gjsify/releases/latest/download/cli.gjs.mjs';
|
|
50
|
+
const DEFAULT_BOOTSTRAP_SHA256_URL = `${DEFAULT_BOOTSTRAP_URL}.sha256`;
|
|
51
|
+
|
|
52
|
+
const USER_AGENT = 'gjsify-installer/1.0';
|
|
53
|
+
|
|
54
|
+
function info(msg) { print(`[gjsify] ${msg}`); }
|
|
55
|
+
function error(msg) { printerr(`[gjsify] ERROR: ${msg}`); }
|
|
56
|
+
|
|
57
|
+
function parseArgs() {
|
|
58
|
+
const argv = system?.programArgs ?? [];
|
|
59
|
+
let target = DEFAULT_TARGET;
|
|
60
|
+
let tag = 'latest';
|
|
61
|
+
let force = false;
|
|
62
|
+
let help = false;
|
|
63
|
+
let bootstrapUrl = GLib.getenv('GJSIFY_INSTALL_BOOTSTRAP_URL') || DEFAULT_BOOTSTRAP_URL;
|
|
64
|
+
let bootstrapSha256Url = GLib.getenv('GJSIFY_INSTALL_BOOTSTRAP_SHA256_URL');
|
|
65
|
+
if (bootstrapSha256Url === null || bootstrapSha256Url === undefined) {
|
|
66
|
+
bootstrapSha256Url = bootstrapUrl === DEFAULT_BOOTSTRAP_URL
|
|
67
|
+
? DEFAULT_BOOTSTRAP_SHA256_URL
|
|
68
|
+
: `${bootstrapUrl}.sha256`;
|
|
69
|
+
}
|
|
70
|
+
for (let i = 0; i < argv.length; i++) {
|
|
71
|
+
const a = argv[i];
|
|
72
|
+
if (a === '--force' || a === '-f') force = true;
|
|
73
|
+
else if (a === '--help' || a === '-h') help = true;
|
|
74
|
+
else if (a === '--target') target = argv[++i];
|
|
75
|
+
else if (a.startsWith('--target=')) target = a.slice('--target='.length);
|
|
76
|
+
else if (a === '--tag') tag = argv[++i];
|
|
77
|
+
else if (a.startsWith('--tag=')) tag = a.slice('--tag='.length);
|
|
78
|
+
else if (a === '--bootstrap-url') bootstrapUrl = argv[++i];
|
|
79
|
+
else if (a.startsWith('--bootstrap-url=')) bootstrapUrl = a.slice('--bootstrap-url='.length);
|
|
80
|
+
}
|
|
81
|
+
return { target, tag, force, help, bootstrapUrl, bootstrapSha256Url };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printUsage() {
|
|
85
|
+
print(`Usage: gjs -m install.mjs [options]
|
|
86
|
+
|
|
87
|
+
Installs (or updates) ${DEFAULT_TARGET} into the user-global XDG location,
|
|
88
|
+
using a self-contained GJS bundle of @gjsify/cli as a one-shot bootstrap.
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
--target <pkg> npm package to install (default: ${DEFAULT_TARGET})
|
|
92
|
+
--tag <tag> npm dist-tag or version (default: latest)
|
|
93
|
+
--force, -f Reinstall even when present.
|
|
94
|
+
--bootstrap-url <url> Override the cli.gjs.mjs download URL.
|
|
95
|
+
--help, -h Show this message.
|
|
96
|
+
|
|
97
|
+
Env vars:
|
|
98
|
+
GJSIFY_INSTALL_BOOTSTRAP_URL alternate bootstrap bundle URL (file:// OK)
|
|
99
|
+
GJSIFY_GLOBAL_PREFIX install prefix (default: ~/.local/share/gjsify/global)
|
|
100
|
+
GJSIFY_GLOBAL_BIN_DIR bin dir (default: ~/.local/bin)
|
|
101
|
+
GJSIFY_INSTALL_REGISTRY npm registry override
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
# Install / update the gjsify CLI itself:
|
|
105
|
+
gjs -m install.mjs
|
|
106
|
+
|
|
107
|
+
# Install some other GJS-runnable package from npm:
|
|
108
|
+
gjs -m install.mjs --target @ts-for-gir/cli
|
|
109
|
+
|
|
110
|
+
# Pin a specific version:
|
|
111
|
+
gjs -m install.mjs --tag 0.4.9
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function checkGjsVersion() {
|
|
116
|
+
// imports.system.version is packed: major*10000 + minor*100 + micro
|
|
117
|
+
const v = system?.version;
|
|
118
|
+
if (typeof v !== 'number') return;
|
|
119
|
+
const major = Math.floor(v / 10000);
|
|
120
|
+
const minor = Math.floor((v - major * 10000) / 100);
|
|
121
|
+
if (major < 1 || (major === 1 && minor < 86)) {
|
|
122
|
+
error(`gjs ${major}.${minor} is too old — gjsify requires gjs 1.86 or newer.`);
|
|
123
|
+
error('Install hints:');
|
|
124
|
+
error(' Fedora 43+: sudo dnf install gjs');
|
|
125
|
+
error(' Debian 13+: sudo apt install gjs');
|
|
126
|
+
error(' Arch: sudo pacman -S gjs');
|
|
127
|
+
exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function fetchBytes(session, url) {
|
|
132
|
+
if (url.startsWith('file://')) {
|
|
133
|
+
const path = url.slice('file://'.length);
|
|
134
|
+
const file = Gio.File.new_for_path(path);
|
|
135
|
+
const [, bytes] = file.load_contents(null);
|
|
136
|
+
return bytes;
|
|
137
|
+
}
|
|
138
|
+
const message = Soup.Message.new('GET', url);
|
|
139
|
+
message.request_headers.append('User-Agent', USER_AGENT);
|
|
140
|
+
const bytes = await session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, null);
|
|
141
|
+
const status = message.get_status();
|
|
142
|
+
if (status !== Soup.Status.OK) {
|
|
143
|
+
throw new Error(`HTTP ${status} from ${url}`);
|
|
144
|
+
}
|
|
145
|
+
return bytes.get_data();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function sha256Hex(bytes) {
|
|
149
|
+
const checksum = GLib.Checksum.new(GLib.ChecksumType.SHA256);
|
|
150
|
+
checksum.update(bytes);
|
|
151
|
+
return checksum.get_string();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function cacheDir() {
|
|
155
|
+
const override = GLib.getenv('GJSIFY_INSTALL_BOOTSTRAP_CACHE');
|
|
156
|
+
if (override) return override;
|
|
157
|
+
const xdg = GLib.getenv('XDG_CACHE_HOME') ||
|
|
158
|
+
GLib.build_filenamev([GLib.get_home_dir(), '.cache']);
|
|
159
|
+
return GLib.build_filenamev([xdg, 'gjsify', 'bootstrap']);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ensureDir(dir) {
|
|
163
|
+
Gio.File.new_for_path(dir).make_directory_with_parents(null);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function writeBytes(path, bytes) {
|
|
167
|
+
Gio.File.new_for_path(path).replace_contents(
|
|
168
|
+
bytes, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function downloadBootstrap(session, bootstrapUrl, sha256Url) {
|
|
173
|
+
info(`Downloading bootstrap from ${bootstrapUrl} ...`);
|
|
174
|
+
const bundleBytes = await fetchBytes(session, bootstrapUrl);
|
|
175
|
+
if (sha256Url && sha256Url !== '') {
|
|
176
|
+
info('Verifying SHA-256 ...');
|
|
177
|
+
let sumExpected;
|
|
178
|
+
try {
|
|
179
|
+
const sumBytes = await fetchBytes(session, sha256Url);
|
|
180
|
+
sumExpected = new TextDecoder().decode(sumBytes).trim().split(/\s+/)[0];
|
|
181
|
+
} catch (err) {
|
|
182
|
+
error(`Could not fetch ${sha256Url} — skipping verification: ${err.message}`);
|
|
183
|
+
}
|
|
184
|
+
if (sumExpected) {
|
|
185
|
+
const sumActual = sha256Hex(bundleBytes);
|
|
186
|
+
if (sumExpected.toLowerCase() !== sumActual.toLowerCase()) {
|
|
187
|
+
error(`SHA-256 mismatch: expected ${sumExpected}, got ${sumActual}`);
|
|
188
|
+
exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const dir = cacheDir();
|
|
193
|
+
try { ensureDir(dir); } catch { /* exists */ }
|
|
194
|
+
const bundlePath = GLib.build_filenamev([dir, 'cli.gjs.mjs']);
|
|
195
|
+
writeBytes(bundlePath, bundleBytes);
|
|
196
|
+
info(`Bootstrap cached at ${bundlePath} (${bundleBytes.length} bytes)`);
|
|
197
|
+
return bundlePath;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildSpec(target, tag) {
|
|
201
|
+
if (!tag || tag === 'latest') return target;
|
|
202
|
+
return `${target}@${tag}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function runInstall(bundlePath, spec) {
|
|
206
|
+
// `gjsify install -g <spec>` is always idempotent — it rewrites the tree
|
|
207
|
+
// unconditionally. There is no separate "force" flag to forward; the
|
|
208
|
+
// installer's own `--force` is satisfied by the fact that we always
|
|
209
|
+
// re-download the bootstrap and re-invoke the CLI.
|
|
210
|
+
info(`Running: gjs -m <bootstrap> install -g ${spec}`);
|
|
211
|
+
const argv = ['gjs', '-m', bundlePath, 'install', '-g', spec];
|
|
212
|
+
// Forward env vars verbatim so override paths set by tests / power-users
|
|
213
|
+
// reach the spawned CLI.
|
|
214
|
+
const launcher = new Gio.SubprocessLauncher({
|
|
215
|
+
flags: Gio.SubprocessFlags.NONE,
|
|
216
|
+
});
|
|
217
|
+
const proc = launcher.spawnv(argv);
|
|
218
|
+
await proc.wait_check_async(null);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function main() {
|
|
222
|
+
const opts = parseArgs();
|
|
223
|
+
if (opts.help) { printUsage(); exit(0); }
|
|
224
|
+
checkGjsVersion();
|
|
225
|
+
|
|
226
|
+
const session = new Soup.Session();
|
|
227
|
+
let bundlePath;
|
|
228
|
+
try {
|
|
229
|
+
bundlePath = await downloadBootstrap(session, opts.bootstrapUrl, opts.bootstrapSha256Url);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
error(`Bootstrap download failed: ${err.message}`);
|
|
232
|
+
exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const spec = buildSpec(opts.target, opts.tag);
|
|
236
|
+
try {
|
|
237
|
+
await runInstall(bundlePath, spec);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
error(`Install failed: ${err.message}`);
|
|
240
|
+
exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
info('');
|
|
244
|
+
info(`Installed ${spec}`);
|
|
245
|
+
info(`Run: ${DEFAULT_BIN_NAME} --help`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await main();
|
|
@@ -91,7 +91,19 @@ export function linkGlobalBins(packageNames, layout) {
|
|
|
91
91
|
// Inline `${target}` directly — this file is rewritten on every
|
|
92
92
|
// install, paths are user-owned, and POSIX `sh` quoting via
|
|
93
93
|
// single-quotes plus `'\''` for embedded quotes is well-defined.
|
|
94
|
-
|
|
94
|
+
//
|
|
95
|
+
// `.gjs.mjs` and `.mjs` bins are GJS-runnable bundles; we wrap them
|
|
96
|
+
// with `gjs -m` rather than direct-exec because not every published
|
|
97
|
+
// bundle ships a `#!/usr/bin/env -S gjs -m` shebang (the CLI's
|
|
98
|
+
// build:gjs-bundle script gained the `--shebang` flag late in
|
|
99
|
+
// Phase F, but published <=0.4.x tarballs predate it). Direct-
|
|
100
|
+
// exec'ing a shebang-less .mjs file falls back to /bin/sh which
|
|
101
|
+
// then tries to parse JavaScript as shell. Plain Node scripts
|
|
102
|
+
// with shebangs (lib/index.js) keep the direct-exec path.
|
|
103
|
+
const isGjsBundle = targetAbs.endsWith('.gjs.mjs') || targetAbs.endsWith('.mjs');
|
|
104
|
+
const launcher = isGjsBundle
|
|
105
|
+
? `#!/bin/sh\nexec gjs -m ${shQuote(targetAbs)} "$@"\n`
|
|
106
|
+
: `#!/bin/sh\nexec ${shQuote(targetAbs)} "$@"\n`;
|
|
95
107
|
fs.writeFileSync(linkPath, launcher);
|
|
96
108
|
fs.chmodSync(linkPath, 0o755);
|
|
97
109
|
created.push({ name: binName, target: targetAbs, link: linkPath });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"clear": "rm -rf lib dist tsconfig.tsbuildinfo || exit 0",
|
|
24
24
|
"check": "tsc --noEmit",
|
|
25
25
|
"start": "node lib/index.js",
|
|
26
|
-
"build": "tsc && gjsify run chmod",
|
|
27
|
-
"build:gjs-bundle": "node lib/index.js build src/index.ts --app gjs --outfile dist/cli.gjs.mjs",
|
|
26
|
+
"build": "tsc && mkdir -p lib/templates && cp -L src/templates/install.mjs.tmpl lib/templates/install.mjs.tmpl && gjsify run chmod",
|
|
27
|
+
"build:gjs-bundle": "node lib/index.js build src/index.ts --app gjs --outfile dist/cli.gjs.mjs --shebang",
|
|
28
28
|
"chmod": "chmod +x ./lib/index.js",
|
|
29
29
|
"build:test:node": "node lib/index.js build src/test.mts --app node --outfile dist/test.node.mjs",
|
|
30
30
|
"test:node": "node dist/test.node.mjs",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"cli"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@gjsify/buffer": "^0.4.
|
|
41
|
-
"@gjsify/create-app": "^0.4.
|
|
42
|
-
"@gjsify/node-globals": "^0.4.
|
|
43
|
-
"@gjsify/node-polyfills": "^0.4.
|
|
44
|
-
"@gjsify/npm-registry": "^0.4.
|
|
45
|
-
"@gjsify/resolve-npm": "^0.4.
|
|
46
|
-
"@gjsify/rolldown-plugin-gjsify": "^0.4.
|
|
47
|
-
"@gjsify/rolldown-plugin-pnp": "^0.4.
|
|
48
|
-
"@gjsify/semver": "^0.4.
|
|
49
|
-
"@gjsify/tar": "^0.4.
|
|
50
|
-
"@gjsify/web-polyfills": "^0.4.
|
|
51
|
-
"@gjsify/workspace": "^0.4.
|
|
40
|
+
"@gjsify/buffer": "^0.4.10",
|
|
41
|
+
"@gjsify/create-app": "^0.4.10",
|
|
42
|
+
"@gjsify/node-globals": "^0.4.10",
|
|
43
|
+
"@gjsify/node-polyfills": "^0.4.10",
|
|
44
|
+
"@gjsify/npm-registry": "^0.4.10",
|
|
45
|
+
"@gjsify/resolve-npm": "^0.4.10",
|
|
46
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.4.10",
|
|
47
|
+
"@gjsify/rolldown-plugin-pnp": "^0.4.10",
|
|
48
|
+
"@gjsify/semver": "^0.4.10",
|
|
49
|
+
"@gjsify/tar": "^0.4.10",
|
|
50
|
+
"@gjsify/web-polyfills": "^0.4.10",
|
|
51
|
+
"@gjsify/workspace": "^0.4.10",
|
|
52
52
|
"cosmiconfig": "^9.0.1",
|
|
53
53
|
"get-tsconfig": "^4.14.0",
|
|
54
54
|
"pkg-types": "^2.3.1",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"yargs": "^18.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@gjsify/unit": "^0.4.
|
|
59
|
+
"@gjsify/unit": "^0.4.10",
|
|
60
60
|
"@types/yargs": "^17.0.35",
|
|
61
61
|
"typescript": "^6.0.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@gjsify/rolldown-native": "^0.4.
|
|
64
|
+
"@gjsify/rolldown-native": "^0.4.10"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@gjsify/rolldown-native": {
|