@gjsify/cli 0.35.0 → 0.37.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 +136 -126
- package/lib/actions/build.d.ts +19 -0
- package/lib/actions/build.js +2 -2
- package/lib/bundler-pick.js +72 -98
- package/lib/cli-fail.d.ts +4 -7
- package/lib/cli-fail.js +29 -55
- package/lib/commands/affected.js +161 -286
- package/lib/commands/install.d.ts +63 -66
- package/lib/commands/install.js +372 -536
- package/lib/commands/run.d.ts +1 -0
- package/lib/commands/run.js +37 -0
- package/lib/templates/install.mjs.tmpl +38 -68
- package/lib/types/cli-build-options.d.ts +33 -67
- package/lib/types/command.d.ts +9 -12
- package/lib/types/config-data.d.ts +125 -221
- package/lib/utils/auth-npmrc.d.ts +5 -10
- package/lib/utils/auth-npmrc.js +11 -21
- package/lib/utils/check-system-deps.d.ts +16 -0
- package/lib/utils/check-system-deps.js +49 -0
- package/lib/utils/clear-targets.d.ts +8 -11
- package/lib/utils/clear-targets.js +22 -29
- package/lib/utils/copy-targets.d.ts +13 -19
- package/lib/utils/copy-targets.js +31 -44
- package/lib/utils/dep-aggregation.d.ts +7 -15
- package/lib/utils/dep-aggregation.js +9 -22
- package/lib/utils/detect-native-packages.d.ts +83 -116
- package/lib/utils/detect-native-packages.js +111 -149
- package/lib/utils/dir-link.d.ts +29 -48
- package/lib/utils/dir-link.js +33 -56
- package/lib/utils/discover-showcases.d.ts +6 -7
- package/lib/utils/discover-showcases.js +11 -15
- package/lib/utils/dlx-cache.d.ts +29 -38
- package/lib/utils/dlx-cache.js +53 -63
- package/lib/utils/force-exit.d.ts +0 -3
- package/lib/utils/force-exit.js +13 -24
- package/lib/utils/gi-search-path.d.ts +16 -24
- package/lib/utils/gi-search-path.js +54 -82
- package/lib/utils/gjs-bundle-guard.d.ts +5 -13
- package/lib/utils/gjs-bundle-guard.js +35 -57
- package/lib/utils/gjs-source-escape.d.ts +2 -2
- package/lib/utils/gjs-source-escape.js +21 -39
- package/lib/utils/gjsify-shim.d.ts +26 -8
- package/lib/utils/gjsify-shim.js +128 -80
- package/lib/utils/html-entry.d.ts +8 -12
- package/lib/utils/html-entry.js +19 -28
- package/lib/utils/install-backend-native.d.ts +123 -161
- package/lib/utils/install-backend-native.js +536 -737
- package/lib/utils/install-backend.d.ts +42 -57
- package/lib/utils/install-backend.js +16 -21
- package/lib/utils/install-cache-fs.d.ts +10 -15
- package/lib/utils/install-cache-fs.js +18 -26
- package/lib/utils/install-lock.d.ts +5 -6
- package/lib/utils/install-lock.js +46 -65
- package/lib/utils/install-packument-cache.d.ts +27 -33
- package/lib/utils/install-packument-cache.js +44 -57
- package/lib/utils/install-progress.d.ts +2 -9
- package/lib/utils/install-progress.js +14 -39
- package/lib/utils/install-tarball-cache.d.ts +14 -20
- package/lib/utils/install-tarball-cache.js +41 -59
- package/lib/utils/library-output.d.ts +9 -22
- package/lib/utils/library-output.js +19 -37
- package/lib/utils/load-npmrc.d.ts +5 -6
- package/lib/utils/load-npmrc.js +18 -27
- package/lib/utils/node-bundle-guard.d.ts +4 -8
- package/lib/utils/node-bundle-guard.js +21 -35
- package/lib/utils/node-script.d.ts +12 -0
- package/lib/utils/node-script.js +95 -0
- package/lib/utils/node-version.d.ts +5 -14
- package/lib/utils/node-version.js +15 -24
- package/lib/utils/normalize-bundler-options.d.ts +7 -11
- package/lib/utils/normalize-bundler-options.js +24 -42
- package/lib/utils/npm-oidc.d.ts +23 -37
- package/lib/utils/npm-oidc.js +28 -53
- package/lib/utils/npm-otp-cache.d.ts +4 -4
- package/lib/utils/npm-otp-cache.js +20 -25
- package/lib/utils/npm-otp.d.ts +20 -30
- package/lib/utils/npm-otp.js +32 -51
- package/lib/utils/onboard-probe.d.ts +7 -13
- package/lib/utils/onboard-probe.js +16 -27
- package/lib/utils/parse-spec.d.ts +3 -3
- package/lib/utils/parse-spec.js +4 -5
- package/lib/utils/pkg-json-edit.d.ts +7 -12
- package/lib/utils/pkg-json-edit.js +13 -26
- package/lib/utils/pkg-json.d.ts +11 -18
- package/lib/utils/pkg-json.js +19 -28
- package/lib/utils/prefixed-output.d.ts +3 -4
- package/lib/utils/prefixed-output.js +13 -19
- package/lib/utils/prompt.d.ts +3 -4
- package/lib/utils/prompt.js +14 -18
- package/lib/utils/publish-diagnose.d.ts +11 -16
- package/lib/utils/publish-diagnose.js +15 -28
- package/lib/utils/publish-headers.d.ts +7 -11
- package/lib/utils/publish-headers.js +36 -65
- package/lib/utils/publishable-packages.d.ts +4 -6
- package/lib/utils/publishable-packages.js +14 -18
- package/lib/utils/resolve-gjs-entry.d.ts +5 -8
- package/lib/utils/resolve-gjs-entry.js +10 -27
- package/lib/utils/resolve-npm-package.d.ts +6 -11
- package/lib/utils/resolve-npm-package.js +29 -63
- package/lib/utils/resolve-plugin-by-name.d.ts +11 -19
- package/lib/utils/resolve-plugin-by-name.js +9 -14
- package/lib/utils/run-gjs.d.ts +19 -17
- package/lib/utils/run-gjs.js +37 -51
- package/lib/utils/run-lifecycle-script.d.ts +5 -9
- package/lib/utils/run-lifecycle-script.js +23 -42
- package/lib/utils/run-node.d.ts +23 -33
- package/lib/utils/run-node.js +56 -60
- package/lib/utils/runtimes.d.ts +25 -35
- package/lib/utils/runtimes.js +34 -46
- package/lib/utils/spawn.d.ts +32 -48
- package/lib/utils/spawn.js +51 -84
- package/lib/utils/stdin.d.ts +2 -2
- package/lib/utils/stdin.js +10 -25
- package/lib/utils/suggest.d.ts +6 -7
- package/lib/utils/suggest.js +8 -10
- package/lib/utils/system-gi.d.ts +18 -27
- package/lib/utils/system-gi.js +60 -114
- package/lib/utils/trust-registry.d.ts +4 -8
- package/lib/utils/trust-registry.js +7 -15
- package/lib/utils/win32-command.d.ts +8 -14
- package/lib/utils/win32-command.js +29 -43
- package/lib/utils/workspace-root.js +7 -12
- package/package.json +18 -17
- package/showcases.json +6 -0
|
@@ -4,7 +4,6 @@ import type { TrustRequester } from '../commands/trust.js';
|
|
|
4
4
|
import { type TrustState } from './trust-registry.js';
|
|
5
5
|
/** What a package needs, computed from its published + trust state. */
|
|
6
6
|
export type PkgAction = 'skip' | 'trust' | 'publish-and-trust' | 'blocked';
|
|
7
|
-
/** A per-package plan produced by the probe phase. */
|
|
8
7
|
export interface PkgPlan {
|
|
9
8
|
ws: Workspace;
|
|
10
9
|
registry: string;
|
|
@@ -13,7 +12,6 @@ export interface PkgPlan {
|
|
|
13
12
|
httpStatus: number;
|
|
14
13
|
action: PkgAction;
|
|
15
14
|
}
|
|
16
|
-
/** Everything a probe needs beyond the requester + workspace. */
|
|
17
15
|
export interface ProbeContext {
|
|
18
16
|
registryOverride?: string;
|
|
19
17
|
npmrc: NpmrcConfig;
|
|
@@ -29,26 +27,22 @@ export interface ProbeOptions {
|
|
|
29
27
|
/** Injected delay (tests pass a no-op). Default: real `setTimeout`. */
|
|
30
28
|
sleep?: (ms: number) => Promise<void>;
|
|
31
29
|
}
|
|
32
|
-
/** Small backoff before the one-shot retry of a transient 401 read. */
|
|
33
30
|
export declare const PROBE_RETRY_DELAY_MS = 400;
|
|
34
31
|
/** Default probe concurrency — kept SMALL so a single token never bursts npm. */
|
|
35
32
|
export declare const DEFAULT_PROBE_CONCURRENCY = 4;
|
|
36
33
|
/** Map a classified trust state to the action the onboard sweep should take. */
|
|
37
34
|
export declare function actionForState(state: TrustState): PkgAction;
|
|
38
35
|
/**
|
|
39
|
-
* Read one package's published + trust state
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* it retries ONCE after a short backoff.
|
|
36
|
+
* Read one package's published + trust state through the SHARED trust requester.
|
|
37
|
+
* A surviving `auth-required` means the 401 was not an answerable OTP challenge —
|
|
38
|
+
* a parallel-burst rejection or a lapsed 2FA-skip window — and both recover on a
|
|
39
|
+
* single retry after a short backoff.
|
|
44
40
|
*/
|
|
45
41
|
export declare function probeTrustState(request: TrustRequester, ws: Workspace, ctx: ProbeContext, opts?: ProbeOptions): Promise<PkgPlan>;
|
|
46
42
|
/**
|
|
47
|
-
* Probe every selected workspace. The FIRST
|
|
48
|
-
* 2FA-gated read triggers the
|
|
49
|
-
*
|
|
50
|
-
* never faces a 127-wide parallel read. The remaining packages are read with a
|
|
51
|
-
* bounded (small) concurrency cap.
|
|
43
|
+
* Probe every selected workspace. The FIRST is read SERIALLY on purpose: a
|
|
44
|
+
* 2FA-gated read triggers the shared-OTP prompt once, before the burst, so the rest
|
|
45
|
+
* reuse the cached code instead of each prompting.
|
|
52
46
|
*/
|
|
53
47
|
export declare function probeAllTrustStates(request: TrustRequester, selected: readonly Workspace[], concurrency: number, ctx: ProbeContext, opts?: ProbeOptions): Promise<PkgPlan[]>;
|
|
54
48
|
/** Map with bounded concurrency, preserving input order in the result. */
|
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
// Trusted-Publisher STATE-READ path for `gjsify onboard`, extracted so it can
|
|
2
|
-
//
|
|
1
|
+
// Trusted-Publisher STATE-READ path for `gjsify onboard`, extracted so it can be
|
|
2
|
+
// unit-tested against a mocked requester (no live npm).
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// answered with the shared code, and non-OTP transient 401s are tolerated.
|
|
12
|
-
//
|
|
13
|
-
// Fix: onboard now reads through the EXACT same `TrustRequester` path as
|
|
14
|
-
// `gjsify trust` (identical `trustUrl` + `buildHeaders` auth + OTP handling),
|
|
15
|
-
// PLUS a one-shot retry on a transient 401, PLUS a first-serial-then-bounded
|
|
16
|
-
// probe order so the ONE shared OTP is prompted at most once and npm is never
|
|
17
|
-
// hit with a 127-wide parallel burst from a single token.
|
|
4
|
+
// Onboard MUST read through the same `TrustRequester` as `gjsify trust`, never a
|
|
5
|
+
// plain authenticated `fetch`. It once used the latter, in a wide concurrent burst:
|
|
6
|
+
// every one of ~127 reads came back `401 → auth-required → unreadable` and the
|
|
7
|
+
// sweep planned `0 to publish+trust, 0 to trust, 127 unreadable`, while a targeted
|
|
8
|
+
// `gjsify trust <pkg> --otp <code>` read the same state fine on the SAME token —
|
|
9
|
+
// because the shared requester goes through `withOtpRetry`, which answers a 401
|
|
10
|
+
// that is an OTP challenge and tolerates transient non-OTP ones.
|
|
18
11
|
import { DEFAULT_REGISTRY, registryFor } from '@gjsify/npm-registry';
|
|
19
12
|
import { classifyTrustList, trustUrl } from './trust-registry.js';
|
|
20
|
-
/** Small backoff before the one-shot retry of a transient 401 read. */
|
|
21
13
|
export const PROBE_RETRY_DELAY_MS = 400;
|
|
22
14
|
/** Default probe concurrency — kept SMALL so a single token never bursts npm. */
|
|
23
15
|
export const DEFAULT_PROBE_CONCURRENCY = 4;
|
|
@@ -39,11 +31,10 @@ function defaultSleep(ms) {
|
|
|
39
31
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
40
32
|
}
|
|
41
33
|
/**
|
|
42
|
-
* Read one package's published + trust state
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* it retries ONCE after a short backoff.
|
|
34
|
+
* Read one package's published + trust state through the SHARED trust requester.
|
|
35
|
+
* A surviving `auth-required` means the 401 was not an answerable OTP challenge —
|
|
36
|
+
* a parallel-burst rejection or a lapsed 2FA-skip window — and both recover on a
|
|
37
|
+
* single retry after a short backoff.
|
|
47
38
|
*/
|
|
48
39
|
export async function probeTrustState(request, ws, ctx, opts = {}) {
|
|
49
40
|
const registry = ctx.registryOverride ?? registryFor(ws.name, ctx.npmrc) ?? DEFAULT_REGISTRY;
|
|
@@ -60,11 +51,9 @@ export async function probeTrustState(request, ws, ctx, opts = {}) {
|
|
|
60
51
|
return { ws, registry, url, state, httpStatus: res.status, action: actionForState(state) };
|
|
61
52
|
}
|
|
62
53
|
/**
|
|
63
|
-
* Probe every selected workspace. The FIRST
|
|
64
|
-
* 2FA-gated read triggers the
|
|
65
|
-
*
|
|
66
|
-
* never faces a 127-wide parallel read. The remaining packages are read with a
|
|
67
|
-
* bounded (small) concurrency cap.
|
|
54
|
+
* Probe every selected workspace. The FIRST is read SERIALLY on purpose: a
|
|
55
|
+
* 2FA-gated read triggers the shared-OTP prompt once, before the burst, so the rest
|
|
56
|
+
* reuse the cached code instead of each prompting.
|
|
68
57
|
*/
|
|
69
58
|
export async function probeAllTrustStates(request, selected, concurrency, ctx, opts = {}) {
|
|
70
59
|
if (selected.length === 0)
|
|
@@ -13,8 +13,8 @@ export type ParsedSpec = {
|
|
|
13
13
|
* Local: starts with `./`, `../`, `/`, or is an existing directory path.
|
|
14
14
|
* Registry: `<name>` | `<name>@<version>` | `@scope/<name>` | `@scope/<name>@<version>`
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* `spec` keeps the input verbatim so the registry case can be handed straight to
|
|
17
|
+
* `npm install`, which already understands dist-tags, ranges, git URIs and tarball
|
|
18
|
+
* URLs — none of those are re-parsed here.
|
|
19
19
|
*/
|
|
20
20
|
export declare function parseSpec(input: string): ParsedSpec;
|
package/lib/utils/parse-spec.js
CHANGED
|
@@ -8,9 +8,9 @@ const NPM_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
|
8
8
|
* Local: starts with `./`, `../`, `/`, or is an existing directory path.
|
|
9
9
|
* Registry: `<name>` | `<name>@<version>` | `@scope/<name>` | `@scope/<name>@<version>`
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `spec` keeps the input verbatim so the registry case can be handed straight to
|
|
12
|
+
* `npm install`, which already understands dist-tags, ranges, git URIs and tarball
|
|
13
|
+
* URLs — none of those are re-parsed here.
|
|
14
14
|
*/
|
|
15
15
|
export function parseSpec(input) {
|
|
16
16
|
if (!input)
|
|
@@ -21,8 +21,7 @@ export function parseSpec(input) {
|
|
|
21
21
|
if (existsSync(input)) {
|
|
22
22
|
return { kind: 'local', path: resolve(input) };
|
|
23
23
|
}
|
|
24
|
-
//
|
|
25
|
-
// leading scope separator.
|
|
24
|
+
// Split the version off the LAST `@` — `lastAt > 0` skips a leading scope `@`.
|
|
26
25
|
let name = input;
|
|
27
26
|
let version;
|
|
28
27
|
const lastAt = input.lastIndexOf('@');
|
|
@@ -29,21 +29,16 @@ export declare function parseSpec(spec: string): {
|
|
|
29
29
|
range?: string;
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `
|
|
35
|
-
* with the project's existing dependency manifest — equivalent to
|
|
36
|
-
* `npm install` reading `package.json`.
|
|
32
|
+
* Existing dependencies + devDependencies + optionalDependencies as `name@range`
|
|
33
|
+
* specs — what seeds the resolver for a bare `gjsify install`, the way `npm install`
|
|
34
|
+
* reads `package.json`.
|
|
37
35
|
*/
|
|
38
36
|
export declare function projectSpecsFromPackageJson(pkg: PackageJson): string[];
|
|
39
37
|
/**
|
|
40
|
-
* Add or update a dependency entry in `pkg`.
|
|
41
|
-
* a
|
|
42
|
-
*
|
|
38
|
+
* Add or update a dependency entry in `pkg`. When the spec carried no range, the
|
|
39
|
+
* caller calls this a second time after resolution, with `range` derived from the
|
|
40
|
+
* installed version via {@link defaultRangeFromVersion}.
|
|
43
41
|
*/
|
|
44
42
|
export declare function addDependencyEntry(pkg: PackageJson, name: string, range: string, kind: DependencyKind): void;
|
|
45
|
-
/**
|
|
46
|
-
* Default version range when the user didn't pin one: `^x.y.z` from the
|
|
47
|
-
* installed version. Mirrors npm's `save-prefix` default (`^`).
|
|
48
|
-
*/
|
|
43
|
+
/** Range used when the user pinned none — npm's `save-prefix` default (`^`). */
|
|
49
44
|
export declare function defaultRangeFromVersion(version: string): string;
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
// Helpers for editing `package.json` during `gjsify install <pkg
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// - --save-dev → devDependencies
|
|
6
|
-
// - --save-peer → peerDependencies
|
|
7
|
-
// - --save-optional → optionalDependencies
|
|
8
|
-
//
|
|
9
|
-
// Version specifier resolution mirrors npm's default (`^x.y.z` from the
|
|
10
|
-
// installed version), unless the user passed an explicit range in the spec
|
|
11
|
-
// (`react@^18` → keep `^18`).
|
|
1
|
+
// Helpers for editing `package.json` during `gjsify install <pkg>`, mirroring npm:
|
|
2
|
+
// `--save-{dev,peer,optional}` pick the matching block and no flag means
|
|
3
|
+
// `dependencies`; the saved range is `^x.y.z` off the installed version unless the
|
|
4
|
+
// spec carried an explicit one (`react@^18` stays `^18`).
|
|
12
5
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
13
6
|
export function readPackageJson(pkgPath) {
|
|
14
7
|
if (!existsSync(pkgPath))
|
|
@@ -48,11 +41,9 @@ export function parseSpec(spec) {
|
|
|
48
41
|
return { name: spec.slice(0, at), range: spec.slice(at + 1) };
|
|
49
42
|
}
|
|
50
43
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `
|
|
54
|
-
* with the project's existing dependency manifest — equivalent to
|
|
55
|
-
* `npm install` reading `package.json`.
|
|
44
|
+
* Existing dependencies + devDependencies + optionalDependencies as `name@range`
|
|
45
|
+
* specs — what seeds the resolver for a bare `gjsify install`, the way `npm install`
|
|
46
|
+
* reads `package.json`.
|
|
56
47
|
*/
|
|
57
48
|
export function projectSpecsFromPackageJson(pkg) {
|
|
58
49
|
const out = [];
|
|
@@ -61,9 +52,8 @@ export function projectSpecsFromPackageJson(pkg) {
|
|
|
61
52
|
if (!block)
|
|
62
53
|
continue;
|
|
63
54
|
for (const [name, range] of Object.entries(block)) {
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// the project-local install path.
|
|
55
|
+
// `workspace:`/`link:`/`file:`/`portal:` are workspace-local references
|
|
56
|
+
// resolved by the workspace install path, not this project-local one.
|
|
67
57
|
if (typeof range !== 'string')
|
|
68
58
|
continue;
|
|
69
59
|
if (/^(workspace|link|file|portal|git\+|https?):/.test(range))
|
|
@@ -74,9 +64,9 @@ export function projectSpecsFromPackageJson(pkg) {
|
|
|
74
64
|
return out;
|
|
75
65
|
}
|
|
76
66
|
/**
|
|
77
|
-
* Add or update a dependency entry in `pkg`.
|
|
78
|
-
* a
|
|
79
|
-
*
|
|
67
|
+
* Add or update a dependency entry in `pkg`. When the spec carried no range, the
|
|
68
|
+
* caller calls this a second time after resolution, with `range` derived from the
|
|
69
|
+
* installed version via {@link defaultRangeFromVersion}.
|
|
80
70
|
*/
|
|
81
71
|
export function addDependencyEntry(pkg, name, range, kind) {
|
|
82
72
|
if (pkg[kind] === undefined) {
|
|
@@ -84,10 +74,7 @@ export function addDependencyEntry(pkg, name, range, kind) {
|
|
|
84
74
|
}
|
|
85
75
|
pkg[kind][name] = range;
|
|
86
76
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Default version range when the user didn't pin one: `^x.y.z` from the
|
|
89
|
-
* installed version. Mirrors npm's `save-prefix` default (`^`).
|
|
90
|
-
*/
|
|
77
|
+
/** Range used when the user pinned none — npm's `save-prefix` default (`^`). */
|
|
91
78
|
export function defaultRangeFromVersion(version) {
|
|
92
79
|
return `^${version}`;
|
|
93
80
|
}
|
package/lib/utils/pkg-json.d.ts
CHANGED
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
import type { PackageJson } from './pkg-json-edit.js';
|
|
2
2
|
/**
|
|
3
|
-
* Drop a leading UTF-8 BOM (`U+FEFF
|
|
3
|
+
* Drop a leading UTF-8 BOM (`U+FEFF`).
|
|
4
4
|
*
|
|
5
|
-
* `JSON.parse` rejects a BOM — it is not whitespace —
|
|
6
|
-
* one
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* manifest produces one. Notepad offers "UTF-8 with BOM" too.
|
|
5
|
+
* `JSON.parse` rejects a BOM — it is not whitespace — yet Windows PowerShell 5.1
|
|
6
|
+
* writes one for `-Encoding utf8`, its DEFAULT for `Out-File`/`Set-Content`, and
|
|
7
|
+
* npm reads such a manifest without complaint. The file is valid to the
|
|
8
|
+
* ecosystem, so gjsify must not be the strict one.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* concerned and gjsify was the strict one. Stripping matches npm, VS Code and
|
|
14
|
-
* `parse-json`'s own guidance; nothing downstream wants the character.
|
|
15
|
-
*
|
|
16
|
-
* Only a LEADING BOM: `U+FEFF` elsewhere is a zero-width no-break space inside
|
|
17
|
-
* a string value, and removing it would corrupt the data.
|
|
10
|
+
* Only a LEADING BOM: `U+FEFF` elsewhere is a zero-width no-break space inside a
|
|
11
|
+
* string value, and stripping it would corrupt the data.
|
|
18
12
|
*/
|
|
19
13
|
export declare function stripBom(text: string): string;
|
|
20
14
|
/**
|
|
21
15
|
* Read + parse a `package.json` at `pkgPath`. Permissive: any error (missing
|
|
22
16
|
* file, unreadable, invalid JSON) yields `null`.
|
|
23
17
|
*
|
|
24
|
-
* The BOM strip matters
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `check` script, discovery skipped the package, and nothing said why.
|
|
18
|
+
* The BOM strip matters more here than in a throwing reader, because callers
|
|
19
|
+
* read `null` as "no package.json": on Windows a BOM produced no error at all,
|
|
20
|
+
* it made the package look manifest-less, so `gjsify check` found no `check`
|
|
21
|
+
* script, discovery skipped the package, and nothing said why.
|
|
29
22
|
*/
|
|
30
23
|
export declare function readPackageJson(pkgPath: string): PackageJson | null;
|
package/lib/utils/pkg-json.js
CHANGED
|
@@ -1,31 +1,23 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// manifest rather than crash the whole command.
|
|
1
|
+
// Permissive `package.json` reader for the CLI's workspace- and
|
|
2
|
+
// dependency-discovery paths: `null` when the file is missing OR unparseable, so
|
|
3
|
+
// discovery degrades gracefully on a stray or corrupt manifest instead of
|
|
4
|
+
// crashing the whole command.
|
|
6
5
|
//
|
|
7
|
-
// The install-save path keeps
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// — do not fold it into this one.
|
|
6
|
+
// The install-save path deliberately keeps a STRICTER reader in
|
|
7
|
+
// `pkg-json-edit.ts` — `null` only when absent, THROWS on invalid JSON — so
|
|
8
|
+
// `gjsify install <pkg>` never silently overwrites a corrupt manifest. Do not
|
|
9
|
+
// fold the two together.
|
|
12
10
|
import { readFileSync } from 'node:fs';
|
|
13
11
|
/**
|
|
14
|
-
* Drop a leading UTF-8 BOM (`U+FEFF
|
|
12
|
+
* Drop a leading UTF-8 BOM (`U+FEFF`).
|
|
15
13
|
*
|
|
16
|
-
* `JSON.parse` rejects a BOM — it is not whitespace —
|
|
17
|
-
* one
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* manifest produces one. Notepad offers "UTF-8 with BOM" too.
|
|
14
|
+
* `JSON.parse` rejects a BOM — it is not whitespace — yet Windows PowerShell 5.1
|
|
15
|
+
* writes one for `-Encoding utf8`, its DEFAULT for `Out-File`/`Set-Content`, and
|
|
16
|
+
* npm reads such a manifest without complaint. The file is valid to the
|
|
17
|
+
* ecosystem, so gjsify must not be the strict one.
|
|
21
18
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* concerned and gjsify was the strict one. Stripping matches npm, VS Code and
|
|
25
|
-
* `parse-json`'s own guidance; nothing downstream wants the character.
|
|
26
|
-
*
|
|
27
|
-
* Only a LEADING BOM: `U+FEFF` elsewhere is a zero-width no-break space inside
|
|
28
|
-
* a string value, and removing it would corrupt the data.
|
|
19
|
+
* Only a LEADING BOM: `U+FEFF` elsewhere is a zero-width no-break space inside a
|
|
20
|
+
* string value, and stripping it would corrupt the data.
|
|
29
21
|
*/
|
|
30
22
|
export function stripBom(text) {
|
|
31
23
|
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
@@ -34,11 +26,10 @@ export function stripBom(text) {
|
|
|
34
26
|
* Read + parse a `package.json` at `pkgPath`. Permissive: any error (missing
|
|
35
27
|
* file, unreadable, invalid JSON) yields `null`.
|
|
36
28
|
*
|
|
37
|
-
* The BOM strip matters
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* `check` script, discovery skipped the package, and nothing said why.
|
|
29
|
+
* The BOM strip matters more here than in a throwing reader, because callers
|
|
30
|
+
* read `null` as "no package.json": on Windows a BOM produced no error at all,
|
|
31
|
+
* it made the package look manifest-less, so `gjsify check` found no `check`
|
|
32
|
+
* script, discovery skipped the package, and nothing said why.
|
|
42
33
|
*/
|
|
43
34
|
export function readPackageJson(pkgPath) {
|
|
44
35
|
try {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Prefix every line of `src` with `prefix` and route it to `sink`.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* — call it once the child has closed.
|
|
2
|
+
* Prefix every line of `src` with `prefix` and route it to `sink`. `buffered`
|
|
3
|
+
* withholds everything until the returned `flush()` runs — call that only once
|
|
4
|
+
* the child has closed.
|
|
6
5
|
*/
|
|
7
6
|
export declare function prefixLines(src: NodeJS.ReadableStream, sink: NodeJS.WritableStream, prefix: string, buffered: boolean): () => void;
|
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
// Shared line-prefixed child-output forwarding for the parallel runners
|
|
2
|
-
// (`gjsify foreach`, `gjsify check`)
|
|
2
|
+
// (`gjsify foreach`, `gjsify check`) — one home for it, because the second,
|
|
3
|
+
// unbuffered copy kept the hang below alive after the first was fixed.
|
|
3
4
|
//
|
|
4
|
-
// Under GJS
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// and concurrent flushes serialize into brief loop stalls instead of a
|
|
13
|
-
// deadlock. On a tty (interactive) lines are written live for responsive
|
|
14
|
-
// output.
|
|
15
|
-
//
|
|
16
|
-
// This helper is the single home for that logic — `foreach` shipped the fix
|
|
17
|
-
// first; `check` had an unbuffered copy that kept the hang class alive.
|
|
5
|
+
// Under GJS `process.stdout.write` is a BLOCKING `Gio.write_all`. Writing lines
|
|
6
|
+
// live during a PARALLEL run into a backpressuring sink (a slow-draining CI log
|
|
7
|
+
// collector) stalls the single GLib main loop on a full pipe → every child's pipe
|
|
8
|
+
// backs up → their reads stall → the run HANGS. A tty or file sink never
|
|
9
|
+
// backpressures, which is why this only bites in CI. So on a NON-tty sink each
|
|
10
|
+
// child's output is buffered and flushed as ONE write after it closes: the child
|
|
11
|
+
// can no longer stall, and concurrent flushes serialize into brief loop stalls
|
|
12
|
+
// instead of a deadlock. A tty gets live lines, for responsiveness.
|
|
18
13
|
/**
|
|
19
|
-
* Prefix every line of `src` with `prefix` and route it to `sink`.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* — call it once the child has closed.
|
|
14
|
+
* Prefix every line of `src` with `prefix` and route it to `sink`. `buffered`
|
|
15
|
+
* withholds everything until the returned `flush()` runs — call that only once
|
|
16
|
+
* the child has closed.
|
|
23
17
|
*/
|
|
24
18
|
export function prefixLines(src, sink, prefix, buffered) {
|
|
25
19
|
let buf = '';
|
package/lib/utils/prompt.d.ts
CHANGED
|
@@ -15,10 +15,9 @@ export interface KeyOutcome {
|
|
|
15
15
|
interrupt: boolean;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Pure key handler for the raw-mode line editor — the single source of truth
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* passwords, and treats Ctrl-C as an interrupt rather than text.
|
|
18
|
+
* Pure key handler for the raw-mode line editor — the single source of truth for how a keystroke
|
|
19
|
+
* updates the buffer + echo. Pure (no I/O) so the rules are unit-testable: Enter is `\r` OR `\n`,
|
|
20
|
+
* passwords mask, and Ctrl-C is an interrupt rather than text.
|
|
22
21
|
*/
|
|
23
22
|
export declare function applyKey(buf: string, ch: string, mask: boolean): KeyOutcome;
|
|
24
23
|
/** Print a question and read one visible line. */
|
package/lib/utils/prompt.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
// Interactive prompts for `gjsify login` / `gjsify trust`.
|
|
2
2
|
//
|
|
3
|
-
// On a TTY every prompt runs inside a single RAW-mode session (`runRawSession`)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// line
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// resume/pause churn on the shared stdin singleton between two sequential
|
|
12
|
-
// prompts also intermittently dropped the line or resolved it empty.
|
|
13
|
-
// - Reading raw + handling `\r`/`\n` ourselves removes both failure modes.
|
|
3
|
+
// On a TTY every prompt runs inside a single RAW-mode session (`runRawSession`) reading
|
|
4
|
+
// key-by-key with manual echo and a guaranteed cooked-mode restore. It deliberately relies on
|
|
5
|
+
// neither the terminal's cooked line-discipline nor the shared `process.stdin` resume/pause
|
|
6
|
+
// cycle: a cooked read with the line discipline in an unexpected state (ICRNL off after a prior
|
|
7
|
+
// raw prompt) saw Enter arrive as a bare `\r` that never terminated the line, hanging the prompt
|
|
8
|
+
// showing `name^M`, and the resume/pause churn on the shared stdin singleton between sequential
|
|
9
|
+
// prompts intermittently dropped the line or resolved it empty. Handling `\r`/`\n` here removes
|
|
10
|
+
// both failure modes.
|
|
14
11
|
//
|
|
15
|
-
// Raw mode (via @gjsify/process → terminal-native)
|
|
16
|
-
//
|
|
17
|
-
//
|
|
12
|
+
// Raw mode (via @gjsify/process → terminal-native) also clears ISIG, so Ctrl-C arrives as a
|
|
13
|
+
// `\x03` keystroke handled below (restore + exit) rather than a SIGINT that would kill the
|
|
14
|
+
// process leaving the terminal in raw mode.
|
|
18
15
|
//
|
|
19
16
|
// Non-TTY stdin (piped input, CI) keeps a plain line read so
|
|
20
17
|
// `printf 'user\npass\n' | gjsify login` still works.
|
|
@@ -26,10 +23,9 @@ function isTtyStdin() {
|
|
|
26
23
|
return Boolean(stdin.isTTY) && typeof stdin.setRawMode === 'function';
|
|
27
24
|
}
|
|
28
25
|
/**
|
|
29
|
-
* Pure key handler for the raw-mode line editor — the single source of truth
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* passwords, and treats Ctrl-C as an interrupt rather than text.
|
|
26
|
+
* Pure key handler for the raw-mode line editor — the single source of truth for how a keystroke
|
|
27
|
+
* updates the buffer + echo. Pure (no I/O) so the rules are unit-testable: Enter is `\r` OR `\n`,
|
|
28
|
+
* passwords mask, and Ctrl-C is an interrupt rather than text.
|
|
33
29
|
*/
|
|
34
30
|
export function applyKey(buf, ch, mask) {
|
|
35
31
|
if (ch === '\r' || ch === '\n') {
|
|
@@ -3,36 +3,31 @@ export type Diagnose404Reason = 'dead-token' | 'live-token-404' | 'unknown';
|
|
|
3
3
|
export interface Diagnose404Result {
|
|
4
4
|
/** Discriminant — drives the JSON shape + exit-code path in the caller. */
|
|
5
5
|
reason: Diagnose404Reason;
|
|
6
|
-
/**
|
|
6
|
+
/** Set only when `reason === 'live-token-404'`. */
|
|
7
7
|
username?: string;
|
|
8
|
-
/** Multi-line
|
|
8
|
+
/** Multi-line hint, ready for stderr as-is. */
|
|
9
9
|
message: string;
|
|
10
10
|
}
|
|
11
11
|
interface Diagnose404Input {
|
|
12
12
|
/** Full package name including scope, e.g. `@gjsify/abort-controller`. */
|
|
13
13
|
packageName: string;
|
|
14
|
-
/** Pinned version we tried to publish. */
|
|
15
14
|
version: string;
|
|
16
|
-
/** Registry URL
|
|
15
|
+
/** Registry URL, with or without trailing slash. */
|
|
17
16
|
registry: string;
|
|
18
|
-
/**
|
|
17
|
+
/** Source of the Authorization header on the whoami probe. */
|
|
19
18
|
npmrc: NpmrcConfig | undefined;
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
* Probe `/-/whoami` to disambiguate the 404 cause. Best-effort:
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Pure async I/O — no side effects, no process.exit, no console writes.
|
|
27
|
-
* The caller owns presentation (stderr vs stdout, plain vs JSON).
|
|
21
|
+
* Probe `/-/whoami` to disambiguate the 404 cause. Best-effort: a thrown error or
|
|
22
|
+
* non-2xx yields `reason: 'unknown'`, leaving the caller's generic error path
|
|
23
|
+
* untouched. No side effects and no writes — the caller owns presentation.
|
|
28
24
|
*/
|
|
29
25
|
export declare function diagnose404(input: Diagnose404Input): Promise<Diagnose404Result>;
|
|
30
26
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* keep the generic error path.
|
|
27
|
+
* Is the 404 body the "dead-token-or-missing-package" shape? npm's PUT answers
|
|
28
|
+
* with plain-text `Not Found` or an empty body; the JSON `{"error":"Not Found"}`
|
|
29
|
+
* form is accepted too. Any other 404 body — notably one carrying a structured npm
|
|
30
|
+
* error code — keeps the generic error path.
|
|
36
31
|
*/
|
|
37
32
|
export declare function is404DiagnosticCandidate(body: string): boolean;
|
|
38
33
|
export {};
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
// Dead-token vs new-package diagnostic for `gjsify publish`.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// A `404 Not Found` on the publish PUT is npm's shape for two very different
|
|
4
|
+
// causes, and guessing sends the user down the wrong path entirely: the
|
|
5
|
+
// `_authToken` has been revoked/expired (npm answers an unrecognised bearer with
|
|
6
|
+
// 404, not 401), or the scoped package genuinely is not on npm yet and needs the
|
|
7
|
+
// first-publish bootstrap from AGENTS.md.
|
|
5
8
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// user down the wrong path (Trusted-Publisher-bootstrap setup).
|
|
10
|
-
// 2. The scoped `@gjsify/<pkg>` genuinely doesn't exist on npm yet —
|
|
11
|
-
// the first-publish bootstrap step from AGENTS.md.
|
|
12
|
-
//
|
|
13
|
-
// `GET /-/whoami` with the SAME Authorization header disambiguates: a live
|
|
14
|
-
// token returns `{"username": "..."}`, a dead token returns `{}` (status 200
|
|
15
|
-
// in both cases — the empty body is npm's signal).
|
|
16
|
-
//
|
|
17
|
-
// `npm publish` from npm-cli handles dead tokens with `401 EOTP` + a clear
|
|
18
|
-
// "one-time-password or refresh the token" hint. The 404 path is npm's
|
|
19
|
-
// PUT-specific shape and `gjsify publish` previously just printed the
|
|
20
|
-
// opaque body. This helper closes the diagnostic gap.
|
|
9
|
+
// `GET /-/whoami` with the SAME Authorization header disambiguates: a live token
|
|
10
|
+
// returns `{"username": "…"}`, a dead one returns `{}` — status 200 for both, so
|
|
11
|
+
// the EMPTY BODY is the signal.
|
|
21
12
|
import { whoami } from '@gjsify/npm-registry';
|
|
22
13
|
/**
|
|
23
|
-
* Probe `/-/whoami` to disambiguate the 404 cause. Best-effort:
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* Pure async I/O — no side effects, no process.exit, no console writes.
|
|
28
|
-
* The caller owns presentation (stderr vs stdout, plain vs JSON).
|
|
14
|
+
* Probe `/-/whoami` to disambiguate the 404 cause. Best-effort: a thrown error or
|
|
15
|
+
* non-2xx yields `reason: 'unknown'`, leaving the caller's generic error path
|
|
16
|
+
* untouched. No side effects and no writes — the caller owns presentation.
|
|
29
17
|
*/
|
|
30
18
|
export async function diagnose404(input) {
|
|
31
19
|
const { packageName, version, registry, npmrc } = input;
|
|
@@ -85,11 +73,10 @@ function formatUnknown(name, version) {
|
|
|
85
73
|
return `gjsify publish: ${name}@${version} — 404 Not Found`;
|
|
86
74
|
}
|
|
87
75
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* keep the generic error path.
|
|
76
|
+
* Is the 404 body the "dead-token-or-missing-package" shape? npm's PUT answers
|
|
77
|
+
* with plain-text `Not Found` or an empty body; the JSON `{"error":"Not Found"}`
|
|
78
|
+
* form is accepted too. Any other 404 body — notably one carrying a structured npm
|
|
79
|
+
* error code — keeps the generic error path.
|
|
93
80
|
*/
|
|
94
81
|
export function is404DiagnosticCandidate(body) {
|
|
95
82
|
const trimmed = body.trim();
|