@gjsify/cli 0.4.20 → 0.4.21
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 +76 -76
- package/lib/commands/install.js +15 -9
- package/lib/commands/publish.d.ts +1 -0
- package/lib/commands/publish.js +34 -0
- package/lib/index.js +20 -0
- package/lib/utils/install-backend-native.d.ts +8 -0
- package/lib/utils/install-backend-native.js +20 -6
- package/package.json +15 -15
package/lib/commands/install.js
CHANGED
|
@@ -276,17 +276,23 @@ async function workspaceInstall(cwd, args) {
|
|
|
276
276
|
continue;
|
|
277
277
|
const linkPath = join(target.location, 'node_modules', link.depName);
|
|
278
278
|
mkdirSync(dirname(linkPath), { recursive: true });
|
|
279
|
-
// Remove any prior entry
|
|
279
|
+
// Remove any prior entry — regular dir, broken symlink, file, or
|
|
280
|
+
// a normal symlink left over from a previous install. Using
|
|
281
|
+
// `{ recursive: true, force: true }` handles every shape in one
|
|
282
|
+
// call: `rmSync` no-ops on missing paths under `force: true`, and
|
|
283
|
+
// `recursive: true` covers the directory case. Avoids the EEXIST
|
|
284
|
+
// race a previous lstat-then-branch version hit when the stat's
|
|
285
|
+
// type-discrimination missed an edge case (e.g. broken symlink
|
|
286
|
+
// whose `isSymbolicLink()` returned a non-truthy value through
|
|
287
|
+
// Gio's NOFOLLOW path, leaving a leftover entry that
|
|
288
|
+
// `symlinkSync` would then refuse to overwrite).
|
|
280
289
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
rmSync(linkPath, { recursive: true, force: true });
|
|
287
|
-
}
|
|
290
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
291
|
+
}
|
|
292
|
+
catch { /* unexpected — Gio failure on a path we just lstat'd to
|
|
293
|
+
decide we wanted to remove. The subsequent symlinkSync
|
|
294
|
+
will surface the real reason if there is one. */
|
|
288
295
|
}
|
|
289
|
-
catch { /* ENOENT — fine, nothing to remove */ }
|
|
290
296
|
// Relative symlink so the repo is portable across checkout paths.
|
|
291
297
|
const relTarget = relative(dirname(linkPath), link.targetLocation);
|
|
292
298
|
symlinkSync(relTarget, linkPath);
|
package/lib/commands/publish.js
CHANGED
|
@@ -57,6 +57,11 @@ export const publishCommand = {
|
|
|
57
57
|
description: 'Treat "version already published" as success — covers both classic 409 Conflict and the npm OIDC-path 403 Forbidden + `"previously published"` body shape. Matches yarn `--tolerate-republish`.',
|
|
58
58
|
type: 'boolean',
|
|
59
59
|
default: false,
|
|
60
|
+
})
|
|
61
|
+
.option('tolerate-untrusted-new', {
|
|
62
|
+
description: 'Skip (exit 0) when OIDC token exchange returns `package not found` AND no fallback token is configured — i.e. a never-before-published `@scope/<name>` whose Trusted Publisher entry hasn\'t been set up on npmjs.com yet. Without this flag, one un-bootstrapped new package breaks the entire serialized `gjsify foreach publish` loop. Pair with `--tolerate-republish` in CI release workflows so a fresh-merged package gracefully skips its first CI publish, leaving the manual-bootstrap step to a maintainer (see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap").',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
60
65
|
})
|
|
61
66
|
.option('provenance', {
|
|
62
67
|
description: 'Pass-through flag — recorded in the payload but no signing happens (gjsify doesn\'t ship a sigstore signer yet).',
|
|
@@ -91,6 +96,7 @@ export const publishCommand = {
|
|
|
91
96
|
const tag = args.tag ?? 'latest';
|
|
92
97
|
const access = args.access;
|
|
93
98
|
const tolerate = args['tolerate-republish'] === true;
|
|
99
|
+
const tolerateUntrustedNew = args['tolerate-untrusted-new'] === true;
|
|
94
100
|
const provenance = args.provenance === true;
|
|
95
101
|
const dryRun = args['dry-run'] === true;
|
|
96
102
|
const checkTrustedOnly = args['check-trusted'] === true;
|
|
@@ -243,6 +249,34 @@ export const publishCommand = {
|
|
|
243
249
|
}
|
|
244
250
|
}
|
|
245
251
|
catch (err) {
|
|
252
|
+
// Detect the "never-before-published @scope/pkg" shape:
|
|
253
|
+
// npm's OIDC exchange returns 404 with body
|
|
254
|
+
// {"message":"OIDC token exchange error - package not found"}
|
|
255
|
+
// for any package that has no Trusted Publisher entry (which
|
|
256
|
+
// includes every package that doesn't exist on npm yet —
|
|
257
|
+
// see AGENTS.md "New @gjsify/* package: first-publish +
|
|
258
|
+
// Trusted Publisher bootstrap"). Skip such a package when
|
|
259
|
+
// --tolerate-untrusted-new is set so one un-bootstrapped
|
|
260
|
+
// package doesn't break the entire serialized publish loop.
|
|
261
|
+
const isUntrustedNewPackage = err instanceof OidcExchangeError &&
|
|
262
|
+
err.status === 404 &&
|
|
263
|
+
/package not found/i.test(err.body);
|
|
264
|
+
if (isUntrustedNewPackage && tolerateUntrustedNew) {
|
|
265
|
+
const headerMsg = `${packed.name}@${packed.version} (skipped — no Trusted Publisher on npm, see AGENTS.md "New @gjsify/* package: first-publish + Trusted Publisher bootstrap")`;
|
|
266
|
+
if (args.json) {
|
|
267
|
+
process.stdout.write(`${JSON.stringify({
|
|
268
|
+
ok: true,
|
|
269
|
+
action: 'skipped-untrusted-new',
|
|
270
|
+
name: packed.name,
|
|
271
|
+
version: packed.version,
|
|
272
|
+
reason: 'no-trusted-publisher',
|
|
273
|
+
}, null, 2)}\n`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
process.stdout.write(`~ ${headerMsg}\n`);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
246
280
|
if (trustedFlag === true) {
|
|
247
281
|
// Explicit --trusted: bail with a clear error.
|
|
248
282
|
handleOidcFailure(err, packed.name, args.json === true);
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
2
5
|
import yargs from 'yargs';
|
|
3
6
|
import { hideBin } from 'yargs/helpers';
|
|
4
7
|
import { buildCommand as build, testCommand as test, runCommand as run, infoCommand as info, checkCommand as check, showcaseCommand as showcase, createCommand as create, gresourceCommand as gresource, gettextCommand as gettext, gsettingsCommand as gsettings, flatpakCommand as flatpak, dlxCommand as dlx, installCommand as install, foreachCommand as foreach, workspaceCommand as workspace, packCommand as pack, publishCommand as publish, selfUpdateCommand as selfUpdate, generateInstallerCommand as generateInstaller, uninstallCommand as uninstall, formatCommand as format, lintCommand as lint, fixCommand as fix, upgradeCommand as upgrade, barrelsCommand as barrels, } from './commands/index.js';
|
|
5
8
|
import { APP_NAME } from './constants.js';
|
|
9
|
+
// Read the version from package.json adjacent to the bundle. yargs's
|
|
10
|
+
// auto-version-discovery (its `pkg-up`-driven default) doesn't reach
|
|
11
|
+
// through the bundled `dist/cli.gjs.mjs` path on GJS — falls back to
|
|
12
|
+
// "unknown". Both layouts are covered:
|
|
13
|
+
// - dev (tsx, `yarn workspace`): src/index.ts → ../package.json
|
|
14
|
+
// - bundled (install -g): dist/cli.gjs.mjs → ../package.json
|
|
15
|
+
function readBundleVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
19
|
+
return typeof pkg.version === 'string' ? pkg.version : 'unknown';
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return 'unknown';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
6
25
|
// `parseAsync()` instead of `.argv` so the top-level await keeps the
|
|
7
26
|
// process alive until command handlers complete. Under Node this is
|
|
8
27
|
// cosmetic — the event loop holds the process up — but under GJS the
|
|
@@ -11,6 +30,7 @@ import { APP_NAME } from './constants.js';
|
|
|
11
30
|
const cli = yargs(hideBin(process.argv));
|
|
12
31
|
await cli
|
|
13
32
|
.scriptName(APP_NAME)
|
|
33
|
+
.version(readBundleVersion())
|
|
14
34
|
.strict()
|
|
15
35
|
// Use the full terminal width for help. yargs's default caps at 80
|
|
16
36
|
// (`Math.min(80, process.stdout.columns)`); we explicitly opt into
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { type Packument } from "@gjsify/npm-registry";
|
|
1
2
|
import type { InstallOptions } from "./install-backend.ts";
|
|
3
|
+
interface ParsedSpec {
|
|
4
|
+
name: string;
|
|
5
|
+
range: string;
|
|
6
|
+
}
|
|
2
7
|
export interface InstalledTopLevel {
|
|
3
8
|
name: string;
|
|
4
9
|
version: string;
|
|
5
10
|
}
|
|
6
11
|
export declare function installPackagesNative(opts: InstallOptions): Promise<InstalledTopLevel[]>;
|
|
12
|
+
export declare function parseSpec(raw: string): ParsedSpec;
|
|
13
|
+
export declare function pickVersion(packument: Packument, range: string): string | null;
|
|
14
|
+
export {};
|
|
@@ -384,22 +384,36 @@ function describeLockfileDrift(lockfile, specs) {
|
|
|
384
384
|
lines.push(` - ${removed.sort().join("\n - ")}`);
|
|
385
385
|
return lines.join("\n");
|
|
386
386
|
}
|
|
387
|
-
function
|
|
387
|
+
// Exported for unit-testing — keep the function name + signature
|
|
388
|
+
// stable, the install-backend itself still calls it via the local
|
|
389
|
+
// binding below. Internal API.
|
|
390
|
+
export function parseSpec(raw) {
|
|
391
|
+
// Bare names without an explicit `@version` resolve to the `latest`
|
|
392
|
+
// dist-tag. This matches npm CLI behaviour (`npm install foo` →
|
|
393
|
+
// foo@latest) and — crucially — picks up prereleases when the
|
|
394
|
+
// publisher has tagged them as `latest`. Using semver `*` here
|
|
395
|
+
// would silently exclude any version with a `-` (rc, beta, alpha,
|
|
396
|
+
// …) suffix per semver §9 ("Pre-release versions have a lower
|
|
397
|
+
// precedence than the associated normal version"); ts-for-gir
|
|
398
|
+
// shipped only prereleases (4.0.0-rc.17 is the `latest` tag, no
|
|
399
|
+
// stable 4.x yet) and `*` was selecting the abandoned 3.3.0
|
|
400
|
+
// instead.
|
|
388
401
|
if (raw.startsWith("@")) {
|
|
389
402
|
const slash = raw.indexOf("/");
|
|
390
403
|
if (slash < 0)
|
|
391
404
|
throw new Error(`Invalid spec (scoped name without slash): ${raw}`);
|
|
392
405
|
const at = raw.indexOf("@", slash);
|
|
393
406
|
if (at < 0)
|
|
394
|
-
return { name: raw, range: "
|
|
395
|
-
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "
|
|
407
|
+
return { name: raw, range: "latest" };
|
|
408
|
+
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "latest" };
|
|
396
409
|
}
|
|
397
410
|
const at = raw.indexOf("@");
|
|
398
411
|
if (at < 0)
|
|
399
|
-
return { name: raw, range: "
|
|
400
|
-
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "
|
|
412
|
+
return { name: raw, range: "latest" };
|
|
413
|
+
return { name: raw.slice(0, at), range: raw.slice(at + 1) || "latest" };
|
|
401
414
|
}
|
|
402
|
-
|
|
415
|
+
// Exported for unit-testing. Internal API.
|
|
416
|
+
export function pickVersion(packument, range) {
|
|
403
417
|
// dist-tag fast path: `latest`, `next`, ...
|
|
404
418
|
if (packument["dist-tags"][range])
|
|
405
419
|
return packument["dist-tags"][range];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"description": "CLI for Gjsify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -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.21",
|
|
41
|
+
"@gjsify/create-app": "^0.4.21",
|
|
42
|
+
"@gjsify/node-globals": "^0.4.21",
|
|
43
|
+
"@gjsify/node-polyfills": "^0.4.21",
|
|
44
|
+
"@gjsify/npm-registry": "^0.4.21",
|
|
45
|
+
"@gjsify/resolve-npm": "^0.4.21",
|
|
46
|
+
"@gjsify/rolldown-plugin-gjsify": "^0.4.21",
|
|
47
|
+
"@gjsify/rolldown-plugin-pnp": "^0.4.21",
|
|
48
|
+
"@gjsify/semver": "^0.4.21",
|
|
49
|
+
"@gjsify/tar": "^0.4.21",
|
|
50
|
+
"@gjsify/web-polyfills": "^0.4.21",
|
|
51
|
+
"@gjsify/workspace": "^0.4.21",
|
|
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.21",
|
|
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.21"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@gjsify/rolldown-native": {
|