@gjsify/cli 0.47.0 → 0.49.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 +164 -153
- package/lib/actions/build.js +2 -1
- package/lib/commands/affected-classify.js +30 -1
- package/lib/commands/build.js +4 -0
- package/lib/commands/dlx.js +7 -3
- package/lib/commands/foreach.js +9 -2
- package/lib/commands/install.js +21 -1
- package/lib/commands/publish.js +1 -8
- package/lib/commands/run.js +7 -2
- package/lib/commands/ship.js +32 -4
- package/lib/commands/upgrade.js +78 -30
- package/lib/config.js +13 -0
- package/lib/types/cli-build-options.d.ts +6 -0
- package/lib/types/config-data.d.ts +51 -1
- package/lib/utils/base64.d.ts +34 -0
- package/lib/utils/base64.js +47 -0
- package/lib/utils/cli-runtime-closure.d.ts +61 -0
- package/lib/utils/cli-runtime-closure.js +54 -17
- package/lib/utils/detect-native-packages.d.ts +31 -0
- package/lib/utils/detect-native-packages.js +37 -1
- package/lib/utils/double-dash-args.d.ts +8 -0
- package/lib/utils/double-dash-args.js +64 -0
- package/lib/utils/ship/changelog.d.ts +56 -0
- package/lib/utils/ship/changelog.js +166 -0
- package/lib/utils/ship/discover.d.ts +10 -1
- package/lib/utils/ship/discover.js +76 -2
- package/lib/utils/ship/entry-interpreter.d.ts +44 -0
- package/lib/utils/ship/entry-interpreter.js +109 -0
- package/lib/utils/ship/formats.js +5 -0
- package/lib/utils/ship/gi-namespaces.js +175 -12
- package/lib/utils/ship/plan.d.ts +5 -1
- package/lib/utils/ship/plan.js +21 -2
- package/lib/utils/ship/settings.d.ts +48 -2
- package/lib/utils/ship/settings.js +63 -3
- package/lib/utils/ship/stage-manifest.d.ts +22 -3
- package/lib/utils/ship/stage-manifest.js +40 -13
- package/lib/utils/ship/types.d.ts +13 -0
- package/package.json +19 -19
package/lib/actions/build.js
CHANGED
|
@@ -630,7 +630,7 @@ export class BuildAction {
|
|
|
630
630
|
throw new Error('gjsify build: an .html entry needs an explicit --outfile ' +
|
|
631
631
|
'(e.g. --outfile dist-app/app.js); the emitted index.html is written beside it.');
|
|
632
632
|
}
|
|
633
|
-
const { consoleShim, globals, dialect } = this.configData;
|
|
633
|
+
const { consoleShim, globals, dialect, giRenderer } = this.configData;
|
|
634
634
|
const userExternal = Array.isArray(userBundler.external) ? userBundler.external : undefined;
|
|
635
635
|
const userBanner = typeof userBundler.output?.banner === 'string' ? userBundler.output.banner : undefined;
|
|
636
636
|
const pluginOpts = {
|
|
@@ -641,6 +641,7 @@ export class BuildAction {
|
|
|
641
641
|
reflection: typescript?.reflection,
|
|
642
642
|
consoleShim,
|
|
643
643
|
...(dialect !== undefined ? { dialect } : {}),
|
|
644
|
+
...(giRenderer === true ? { giRenderer: true } : {}),
|
|
644
645
|
...(aliases ? { aliases } : {}),
|
|
645
646
|
...(opts.preserveDefaultExport ? { preserveDefaultExport: true } : {}),
|
|
646
647
|
...(opts.toolchainAnchor !== undefined ? { toolchainAnchor: opts.toolchainAnchor } : {}),
|
|
@@ -146,6 +146,27 @@ const SCRIPT_COUPLINGS = [
|
|
|
146
146
|
runE2E: true,
|
|
147
147
|
why: '@gjsify/create-app build → node scripts/process-template.mjs reads templates/',
|
|
148
148
|
},
|
|
149
|
+
{
|
|
150
|
+
// THE SAME SHAPE AS `.githooks/` ABOVE, one layer up: the guard's trigger names
|
|
151
|
+
// the TEST's path instead of the thing under test. `AppRegistry.runApplication`
|
|
152
|
+
// holds three lines observable only by RUNNING the loop —
|
|
153
|
+
// `registerBuiltinWidgets()`, the `toShellOptions` passthrough and
|
|
154
|
+
// `provideWindowChrome()` — and their one external observer is
|
|
155
|
+
// `tests/e2e/react-native-devtools`, which main.yml's `e2e` job now runs under
|
|
156
|
+
// xvfb + a session bus (#1550). Without this entry a change confined to
|
|
157
|
+
// `packages/framework/react-native/**` seeds its own workspace, the e2e tier
|
|
158
|
+
// stays OFF (it turns on for a `tests/e2e/**` touch), and the suite is silent
|
|
159
|
+
// for exactly the PRs it exists to catch. Measured on #1540: deleting
|
|
160
|
+
// `provideWindowChrome()` left format, lint, tsc and all in-process assertions
|
|
161
|
+
// green while the application drew two header bars.
|
|
162
|
+
//
|
|
163
|
+
// No extra seeds — the package is a workspace and already seeds itself. The
|
|
164
|
+
// TIER is the point, as for `.githooks/`.
|
|
165
|
+
match: /^packages\/framework\/react-native\//,
|
|
166
|
+
seeds: [],
|
|
167
|
+
runE2E: true,
|
|
168
|
+
why: 'tests/e2e/react-native-devtools is the only external observer of AppRegistry.runApplication',
|
|
169
|
+
},
|
|
149
170
|
];
|
|
150
171
|
export function classifyAndExpand(workspaces, changedFiles) {
|
|
151
172
|
const files = changedFiles.map((f) => f.replace(/\\/g, '/')).filter((f) => f.length > 0);
|
|
@@ -255,7 +276,15 @@ export function classifyAndExpand(workspaces, changedFiles) {
|
|
|
255
276
|
global: false,
|
|
256
277
|
reason: `test-only (${remaining.length} file(s) in ${only})`,
|
|
257
278
|
workspaces: [only],
|
|
258
|
-
|
|
279
|
+
// `|| couplingRunE2E`: this shortcut skips CLOSURE EXPANSION, and a
|
|
280
|
+
// declared tier is not part of the closure. Dropping it here made a
|
|
281
|
+
// coupling's reach depend on the SHAPE of the changed path — the
|
|
282
|
+
// `.githooks/` entry survived only because a hook is not a spec file,
|
|
283
|
+
// while `packages/framework/react-native/src/widgets.spec.ts` fell
|
|
284
|
+
// through and turned the e2e tier back off. Measured by the vector
|
|
285
|
+
// "a react-native spec file ALSO turns the e2e tier on", which was RED
|
|
286
|
+
// when it was written (#1550).
|
|
287
|
+
runE2E: touchedE2E || couplingRunE2E,
|
|
259
288
|
runIntegration: touchedIntegration || isIntegrationWorkspace(only),
|
|
260
289
|
skipAll: false,
|
|
261
290
|
};
|
package/lib/commands/build.js
CHANGED
|
@@ -112,6 +112,10 @@ export const buildCommand = {
|
|
|
112
112
|
description: "The dialect the SOURCE is written in. 'react-native' aliases 'react-native' onto '@gjsify/react-native' and fails the build on an import whose ADR 0032 support-table status is not supported/partial. GJS and Node app builds. This is NOT gjsify.runtimes['react-native'], which declares the runtime a package RUNS on — the two answer opposite questions.",
|
|
113
113
|
type: 'string',
|
|
114
114
|
choices: [...SOURCE_DIALECTS],
|
|
115
|
+
})
|
|
116
|
+
.option('gi-renderer', {
|
|
117
|
+
description: "Resolve `gi://Ns?version=X` to the target's widget renderer instead of an empty module (ADR 0034 stage 9): --app browser answers gi://Adw and gi://Gtk out of @gjsify/adwaita-web, --app nativescript out of @gjsify/adwaita-nativescript. A namespace with no renderer, and a ?version= the renderer's vocabulary was not generated against, both FAIL the build by name. Off by default — a gi:// import on those targets is usually a GJS-only code path pulled in transitively.",
|
|
118
|
+
type: 'boolean',
|
|
115
119
|
})
|
|
116
120
|
.option('console-shim', {
|
|
117
121
|
description: 'Inject a console shim into GJS builds for clean output without the GLib prefix and with working ANSI colors. Use --no-console-shim to disable. Only applies to GJS app builds.',
|
package/lib/commands/dlx.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { runGjsBundle } from '../utils/run-gjs.js';
|
|
14
14
|
import { parseSpec } from '../utils/parse-spec.js';
|
|
15
15
|
import { resolveGjsEntry } from '../utils/resolve-gjs-entry.js';
|
|
16
|
+
import { doubleDashArgs } from '../utils/double-dash-args.js';
|
|
16
17
|
import { cacheDirFor, createCacheKey, getValidCachedPkg, makePrepareDir, resolveInstalledPkgDir, symlinkSwap, } from '../utils/dlx-cache.js';
|
|
17
18
|
import { installPackages, makeProgressReporter } from '../utils/install-backend.js';
|
|
18
19
|
export const dlxCommand = {
|
|
@@ -25,7 +26,11 @@ export const dlxCommand = {
|
|
|
25
26
|
// --help` shows ts-for-gir's --help instead of gjsify dlx's.
|
|
26
27
|
// Without `populate--`, the trailing `--help` is consumed at
|
|
27
28
|
// the gjsify level and the bundle never sees it.
|
|
28
|
-
|
|
29
|
+
// `parse-positional-numbers: false` alongside it: the `--` tail and the
|
|
30
|
+
// `[extraArgs..]` positional are the caller's argv, not numbers, and yargs
|
|
31
|
+
// otherwise retypes a bare `5` (#1531). `--cache-max-age` declares
|
|
32
|
+
// `type: 'number'` and is unaffected.
|
|
33
|
+
.parserConfiguration({ 'populate--': true, 'parse-positional-numbers': false })
|
|
29
34
|
.positional('spec', {
|
|
30
35
|
description: 'Package spec (`name`, `name@version`, `@scope/name@spec`, or local path).',
|
|
31
36
|
type: 'string',
|
|
@@ -84,8 +89,7 @@ export const dlxCommand = {
|
|
|
84
89
|
// number)[]`), so flags like `--help` reach the bundle untouched.
|
|
85
90
|
// Merge those into the positional extraArgs the splitter sees so
|
|
86
91
|
// both call shapes share one downstream path.
|
|
87
|
-
const
|
|
88
|
-
const extraArgsCombined = [...(args.extraArgs ?? []), ...passthroughDoubleDash];
|
|
92
|
+
const extraArgsCombined = [...(args.extraArgs ?? []), ...doubleDashArgs(args)];
|
|
89
93
|
const { binName, extraArgs } = splitBinAndArgs(pkgDir, args.binOrArg, extraArgsCombined);
|
|
90
94
|
const entry = resolveGjsEntry(pkgDir, binName);
|
|
91
95
|
if (entry.fromFallback) {
|
package/lib/commands/foreach.js
CHANGED
|
@@ -15,6 +15,7 @@ import { affectedClosure, buildDependencyGraph, discoverWorkspaces, filterWorksp
|
|
|
15
15
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
16
16
|
import { cliRuntimeClosure } from '../utils/cli-runtime-closure.js';
|
|
17
17
|
import { prefixLines } from '../utils/prefixed-output.js';
|
|
18
|
+
import { doubleDashArgs } from '../utils/double-dash-args.js';
|
|
18
19
|
import { BuildCacheRunner, buildCacheEnabledByEnv } from '../utils/build-cache.js';
|
|
19
20
|
// The ONE runner-selection rule, shared rather than re-derived. This file used to
|
|
20
21
|
// carry its own copy; `workspace.ts` grew the bootstrap-CLI branch and the copy
|
|
@@ -267,6 +268,12 @@ export const foreachCommand = {
|
|
|
267
268
|
// gjsify foreach --exec -- npm publish --tag latest
|
|
268
269
|
// without yargs grabbing --tag/--access/etc.
|
|
269
270
|
'populate--': true,
|
|
271
|
+
// …and keep that tail as the TEXT the caller typed. Without
|
|
272
|
+
// this yargs types a bare number in the array as a `number`,
|
|
273
|
+
// which is how `--verify-timeout 5 --tag latest` reached 209
|
|
274
|
+
// npm publishes as `--verify-timeout --tag latest` (#1531).
|
|
275
|
+
// Declared `type: 'number'` options (`--jobs`) are unaffected.
|
|
276
|
+
'parse-positional-numbers': false,
|
|
270
277
|
}),
|
|
271
278
|
handler: async (args) => {
|
|
272
279
|
// Walk up to the monorepo root — foreach is sometimes invoked
|
|
@@ -286,7 +293,7 @@ export const foreachCommand = {
|
|
|
286
293
|
// Script mode silently DROPPED everything after `--` (only --exec
|
|
287
294
|
// consumed args['--']). Forward them to each child invocation,
|
|
288
295
|
// matching `yarn workspaces foreach run <script> -- <flags>`.
|
|
289
|
-
const fromDoubleDash = (args
|
|
296
|
+
const fromDoubleDash = doubleDashArgs(args);
|
|
290
297
|
if (fromDoubleDash.length > 0)
|
|
291
298
|
cmdArgs = [...cmdArgs, ...fromDoubleDash];
|
|
292
299
|
}
|
|
@@ -294,7 +301,7 @@ export const foreachCommand = {
|
|
|
294
301
|
// With populate--:true, anything after the literal `--`
|
|
295
302
|
// separator lands in top-level args['--']. yargs DOES NOT
|
|
296
303
|
// attach it to args._ — it's a sibling array.
|
|
297
|
-
const fromDoubleDash = (args
|
|
304
|
+
const fromDoubleDash = doubleDashArgs(args);
|
|
298
305
|
if (fromDoubleDash.length > 0) {
|
|
299
306
|
if (!cmd) {
|
|
300
307
|
cmd = fromDoubleDash[0];
|
package/lib/commands/install.js
CHANGED
|
@@ -23,7 +23,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
23
23
|
import { forceExit } from '../utils/force-exit.js';
|
|
24
24
|
import { discoverWorkspaces } from '@gjsify/workspace';
|
|
25
25
|
import { buildInstallCommand, detectPackageManager, runMinimalChecks } from '../utils/check-system-deps.js';
|
|
26
|
-
import { detectNativePackages } from '../utils/detect-native-packages.js';
|
|
26
|
+
import { detectHostLibc, detectNativePackages, muslPrebuildFallbacks } from '../utils/detect-native-packages.js';
|
|
27
27
|
import { buildLauncherShims, buildNativeEnvPreamble } from '../utils/bin-shim.js';
|
|
28
28
|
import { installPackages, makeProgressReporter } from '../utils/install-backend.js';
|
|
29
29
|
import { atomicWriteStrict } from '../utils/install-cache-fs.js';
|
|
@@ -1339,6 +1339,26 @@ async function runPostInstallChecks(args) {
|
|
|
1339
1339
|
console.log(` • ${pkg.name}`);
|
|
1340
1340
|
}
|
|
1341
1341
|
console.log('\nUse `gjsify run <bundle>` to launch with LD_LIBRARY_PATH/GI_TYPELIB_PATH set.');
|
|
1342
|
+
// The install is the ONLY moment this is knowable: the libc axis is decided
|
|
1343
|
+
// here, the failure it predicts happens in another process, days later, and
|
|
1344
|
+
// names something else entirely (see `muslPrebuildFallbacks`). The list is
|
|
1345
|
+
// the point — it is what lets a later `Error relocating` line be matched to
|
|
1346
|
+
// a package.
|
|
1347
|
+
const fellBack = muslPrebuildFallbacks(detectHostLibc(process.platform), native);
|
|
1348
|
+
if (fellBack.length > 0) {
|
|
1349
|
+
console.warn(`\nWarning: musl host — ${fellBack.length} of ${native.length} package(s) ship no \`-musl\`` +
|
|
1350
|
+
' prebuild, so the\n default (glibc) build was used:');
|
|
1351
|
+
for (const name of fellBack)
|
|
1352
|
+
console.warn(` ${name}`);
|
|
1353
|
+
console.warn(' Most of these run anyway: a bridge that links only GLib records no libc at all.\n' +
|
|
1354
|
+
' One that links glibc loads too — musl aliases `libc.so.6` to itself — and then\n' +
|
|
1355
|
+
' fails at dlopen on its first glibc-only symbol:\n' +
|
|
1356
|
+
' Error relocating <prebuild>.so: <symbol>: symbol not found\n' +
|
|
1357
|
+
' A bridge lost that way is silently absent from the next build rather than\n' +
|
|
1358
|
+
' reported, so the first symptom can be an unrelated-looking bundler error\n' +
|
|
1359
|
+
' (`Could not load <file>` from rolldown, for the CSS bridge). Nothing to\n' +
|
|
1360
|
+
' install: no `-musl` package is published for any target yet.');
|
|
1361
|
+
}
|
|
1342
1362
|
}
|
|
1343
1363
|
maybeInstallGitHooks();
|
|
1344
1364
|
}
|
package/lib/commands/publish.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
import { readFileSync } from 'node:fs';
|
|
43
43
|
import { tmpdir } from 'node:os';
|
|
44
44
|
import { join, resolve } from 'node:path';
|
|
45
|
+
import { base64Encode } from '../utils/base64.js';
|
|
45
46
|
import { DEFAULT_REGISTRY, registryFor } from '@gjsify/npm-registry';
|
|
46
47
|
import { buildPublishHeaders, escapePackageName } from '../utils/publish-headers.js';
|
|
47
48
|
import { packWorkspace } from './pack.js';
|
|
@@ -789,14 +790,6 @@ function buildPublishPayload(opts) {
|
|
|
789
790
|
payload.access = access;
|
|
790
791
|
return payload;
|
|
791
792
|
}
|
|
792
|
-
function base64Encode(bytes) {
|
|
793
|
-
let str = '';
|
|
794
|
-
const chunk = 0x8000;
|
|
795
|
-
for (let i = 0; i < bytes.length; i += chunk) {
|
|
796
|
-
str += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
797
|
-
}
|
|
798
|
-
return btoa(str);
|
|
799
|
-
}
|
|
800
793
|
function handleOidcFailure(err, packageName, asJson) {
|
|
801
794
|
if (err instanceof OidcUnavailableError) {
|
|
802
795
|
const msg = `gjsify publish: OIDC not available — ${err.message}`;
|
package/lib/commands/run.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// to disambiguate can pass `./<file>` explicitly.
|
|
16
16
|
import { readFileSync, statSync } from 'node:fs';
|
|
17
17
|
import { basename, delimiter, join, resolve } from 'node:path';
|
|
18
|
+
import { doubleDashArgs } from '../utils/double-dash-args.js';
|
|
18
19
|
import { describeExit, spawnToCompletion } from '../utils/spawn.js';
|
|
19
20
|
import { runGjsBundle } from '../utils/run-gjs.js';
|
|
20
21
|
import { runRuntimeBundle } from '../utils/run-node.js';
|
|
@@ -93,6 +94,11 @@ export const runCommand = {
|
|
|
93
94
|
// Without this, anything after `--` lands in args._ and is
|
|
94
95
|
// silently dropped by the handler.
|
|
95
96
|
'populate--': true,
|
|
97
|
+
// …and hand that tail on as the TEXT the caller typed. Without
|
|
98
|
+
// this yargs types a bare number in the array — and in the
|
|
99
|
+
// `[args..]` positional — as a `number`, which the reader below
|
|
100
|
+
// used to drop outright (#1531).
|
|
101
|
+
'parse-positional-numbers': false,
|
|
96
102
|
// Forward UNKNOWN flags to the target without requiring `--`:
|
|
97
103
|
// gjsify run dist/app.gjs.mjs serve --port 8080 --year 2025
|
|
98
104
|
// Without this, the top-level `.strict()` (cli-app.ts) rejects
|
|
@@ -111,8 +117,7 @@ export const runCommand = {
|
|
|
111
117
|
// than silently discarding them (without it they land in args._ and
|
|
112
118
|
// are never forwarded to the child).
|
|
113
119
|
const positionalArgs = args.args ?? [];
|
|
114
|
-
const
|
|
115
|
-
const extraArgs = [...positionalArgs, ...doubleDashArgs];
|
|
120
|
+
const extraArgs = [...positionalArgs, ...doubleDashArgs(args)];
|
|
116
121
|
// `--node-script` (explicit) → run an UNBUNDLED Node-style script on the
|
|
117
122
|
// host runtime; under GJS it is bundled `--app gjs` on the fly first.
|
|
118
123
|
// Checked before `--runtime` and `--workspace`: it is a file mode, and
|
package/lib/commands/ship.js
CHANGED
|
@@ -42,13 +42,14 @@ import { isUnder, SHARE } from '../utils/ship/share-dirs.js';
|
|
|
42
42
|
import { compileSchemasForStage } from '../utils/ship/schemas.js';
|
|
43
43
|
import { buildMsi, MSI_PAYLOAD_DIR } from '../utils/ship/msi.js';
|
|
44
44
|
import { buildZip, zipEntriesFromPayload } from '../utils/ship/zip.js';
|
|
45
|
+
import { assertEntryRunsUnder } from '../utils/ship/entry-interpreter.js';
|
|
45
46
|
import { scanGiNamespaces } from '../utils/ship/gi-namespaces.js';
|
|
46
47
|
import { assertLauncherMatchesInterpreter, assertPayloadMatchesArch, buildTimestamp, isArchIndependent, linuxInstallDependent, readPayloadFacts, } from '../utils/ship/payload.js';
|
|
47
48
|
import { assertOverlayIsLicensed, planOverlay, planStage } from '../utils/ship/plan.js';
|
|
48
49
|
import { assertHostCanSign, notarizeArtifact, stapleArtifact, resolveNotaryPlan, resolveSignPlan, signPayload, } from '../utils/ship/signing.js';
|
|
49
50
|
import { renderLauncher } from '../utils/ship/launcher.js';
|
|
50
51
|
import { buildRpm } from '../utils/ship/rpm.js';
|
|
51
|
-
import {
|
|
52
|
+
import { resolveShipApp, resolveShipBundle, resolveShipSettings, } from '../utils/ship/settings.js';
|
|
52
53
|
import { assertExpectedTarget, assertFormatsStaged, formatTarget, overlayFiles, readStageManifest, resolveStageMtime, warnOnToolSkew, writeStageManifest, STAGE_MANIFEST_FILE, } from '../utils/ship/stage-manifest.js';
|
|
53
54
|
import { directorySize, readStage, writePayload, writeStage } from '../utils/ship/stage-writer.js';
|
|
54
55
|
const LOG = '[gjsify ship]';
|
|
@@ -280,12 +281,22 @@ async function assemble(args) {
|
|
|
280
281
|
assertCanPack(format);
|
|
281
282
|
if (!args['skip-build'])
|
|
282
283
|
await runProjectBuild(projectDir);
|
|
284
|
+
// THE OTHER HALF of the resolution above, and they are one decision: the
|
|
285
|
+
// runtime a target execs and the entry it hands that runtime (#1545). Two
|
|
286
|
+
// keys, resolved by two twin functions, compared by `assertEntryRunsUnder`
|
|
287
|
+
// as soon as both answers and the file exist.
|
|
288
|
+
const resolvedBundle = resolveShipBundle({ pkg, ship, layoutOs: layout.os });
|
|
289
|
+
if (resolvedBundle.overridden) {
|
|
290
|
+
console.log(`${LOG} entry for ${layout.name}: ${resolvedBundle.declared} — \`${resolvedBundle.key}\` ` +
|
|
291
|
+
'overrides `gjsify.main`.');
|
|
292
|
+
}
|
|
283
293
|
const discovered = discoverPayload({
|
|
284
294
|
projectDir,
|
|
285
295
|
pkg,
|
|
286
296
|
ship,
|
|
287
297
|
flatpakIcon: flatpak.icon,
|
|
288
|
-
declaredBundle:
|
|
298
|
+
declaredBundle: resolvedBundle.declared,
|
|
299
|
+
foreignBundles: resolvedBundle.foreign,
|
|
289
300
|
});
|
|
290
301
|
const { settings, metadata, warnings } = resolveShipSettings({
|
|
291
302
|
projectDir,
|
|
@@ -308,6 +319,21 @@ async function assemble(args) {
|
|
|
308
319
|
// the packing host.
|
|
309
320
|
for (const format of formats)
|
|
310
321
|
assertFormatCanRunInterpreter(format, settings.app);
|
|
322
|
+
// The entry, read ONCE: the pair check below and the namespace scan further
|
|
323
|
+
// down both ask what this file imports, and a bundle is the biggest file in
|
|
324
|
+
// the project.
|
|
325
|
+
const bundleSource = readFileSync(settings.bundlePath, 'utf-8');
|
|
326
|
+
// BEFORE anything is staged, for the reason above and one more: this is the
|
|
327
|
+
// refusal that says the artifact could never have started, and the run that
|
|
328
|
+
// reports it must not also print a stage path.
|
|
329
|
+
assertEntryRunsUnder({
|
|
330
|
+
interpreter: settings.app,
|
|
331
|
+
source: bundleSource,
|
|
332
|
+
entry: relative(projectDir, settings.bundlePath) || settings.bundlePath,
|
|
333
|
+
appKey: resolvedApp.key,
|
|
334
|
+
bundleKey: resolvedBundle.key,
|
|
335
|
+
layoutOs: layout.os,
|
|
336
|
+
});
|
|
311
337
|
const mtime = buildTimestamp(settings.bundlePath);
|
|
312
338
|
const metadataInputs = {
|
|
313
339
|
appId: settings.appId,
|
|
@@ -354,6 +380,8 @@ async function assemble(args) {
|
|
|
354
380
|
metainfo: translated.metainfo,
|
|
355
381
|
desktopEntry: translated.desktopEntry,
|
|
356
382
|
licenseText: settings.licenseFile === undefined ? undefined : readFileSync(settings.licenseFile, 'utf-8'),
|
|
383
|
+
changelogText: settings.changelogFile === undefined ? undefined : readFileSync(settings.changelogFile, 'utf-8'),
|
|
384
|
+
mtime,
|
|
357
385
|
};
|
|
358
386
|
const outRoot = resolve(projectDir, settings.outDir);
|
|
359
387
|
const stageDir = join(outRoot, 'stage');
|
|
@@ -466,7 +494,7 @@ async function assemble(args) {
|
|
|
466
494
|
// `gi://` specifiers are what the emitted `Depends:` is derived from
|
|
467
495
|
// (ADR 0024 § 6), and the packing host has the staged copy but no way to
|
|
468
496
|
// tell which staged file is the entry.
|
|
469
|
-
const namespaces = scanGiNamespaces(
|
|
497
|
+
const namespaces = scanGiNamespaces(bundleSource);
|
|
470
498
|
if (args.verbose)
|
|
471
499
|
console.log(`${LOG} gi namespaces: ${namespaces.join(', ') || '(none)'}`);
|
|
472
500
|
// PLACED, like the payload, and through the same map. `planOverlay` answers a
|
|
@@ -481,7 +509,7 @@ async function assemble(args) {
|
|
|
481
509
|
// overlay reaches it by the same route.
|
|
482
510
|
const overlay = new Map();
|
|
483
511
|
for (const format of formats) {
|
|
484
|
-
overlay.set(format.id, planOverlay(settings, format, stageInputs).map((file) => ({
|
|
512
|
+
overlay.set(format.id, (await planOverlay(settings, format, stageInputs)).map((file) => ({
|
|
485
513
|
...file,
|
|
486
514
|
path: place(layout, settings, file.path),
|
|
487
515
|
})));
|
package/lib/commands/upgrade.js
CHANGED
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
// matching packages automatically without prompting; same selection
|
|
13
13
|
// logic as above but no UI loop.
|
|
14
14
|
//
|
|
15
|
-
// 3. `--align`: offline
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// 3. `--align`: offline repair mode. Finds deps declared at multiple ranges
|
|
16
|
+
// across the workspace and proposes a single range (the highest declared)
|
|
17
|
+
// — no registry calls. With `--exact` it repairs the second question too:
|
|
18
|
+
// a dep every manifest declares identically as `^4.1.0` is consistent and
|
|
19
|
+
// still carries an operator, so the operator goes as well.
|
|
18
20
|
//
|
|
19
|
-
// 4. `--check`: CI gate. Exits non-zero when any inconsistency exists
|
|
21
|
+
// 4. `--check`: CI gate. Exits non-zero when any inconsistency exists; with
|
|
22
|
+
// `--exact`, also when any matched declaration carries a range operator.
|
|
23
|
+
// Whatever `--check` rejects, `--align` with the same flags repairs.
|
|
20
24
|
//
|
|
21
25
|
// Filters:
|
|
22
26
|
//
|
|
@@ -36,7 +40,7 @@ import { parse } from '@gjsify/semver';
|
|
|
36
40
|
import { DEFAULT_REGISTRY, fetchPackument, parseNpmrc } from '@gjsify/npm-registry';
|
|
37
41
|
import { discoverWorkspaces, filterWorkspaces } from '@gjsify/workspace';
|
|
38
42
|
import { findWorkspaceRoot } from '../utils/workspace-root.js';
|
|
39
|
-
import { groupByDependency, findInconsistencies, } from '../utils/dep-aggregation.js';
|
|
43
|
+
import { groupByDependency, findInconsistencies, isInconsistent, } from '../utils/dep-aggregation.js';
|
|
40
44
|
export const upgradeCommand = {
|
|
41
45
|
command: 'upgrade',
|
|
42
46
|
description: 'Workspace-aware dependency upgrades. Walks every package.json in the monorepo, groups by dep, flags inconsistencies. `--latest` / `--minor` / `--patch` for bulk-mode, `--align` for offline consistency-only mode, `--check` for CI drift detection.',
|
|
@@ -71,7 +75,7 @@ export const upgradeCommand = {
|
|
|
71
75
|
type: 'string',
|
|
72
76
|
})
|
|
73
77
|
.option('align', {
|
|
74
|
-
description: 'Offline
|
|
78
|
+
description: 'Offline repair mode: find deps declared at multiple ranges across the workspace and align them to the highest. With `--exact`, also drop the range operator from declarations that already agree. No registry calls.',
|
|
75
79
|
type: 'boolean',
|
|
76
80
|
default: false,
|
|
77
81
|
})
|
|
@@ -81,7 +85,7 @@ export const upgradeCommand = {
|
|
|
81
85
|
default: false,
|
|
82
86
|
})
|
|
83
87
|
.option('exact', {
|
|
84
|
-
description: 'Pin without a range operator. When writing, emits `1.2.3` instead of `^1.2.3`; with `--check`, fails on any matched dep that carries one. Pair with `--filter` — a repository-wide exactness
|
|
88
|
+
description: 'Pin without a range operator. When writing, emits `1.2.3` instead of `^1.2.3`; with `--check`, fails on any matched dep that carries one; with `--align`, repairs exactly that. Pair with `--filter` — a repository-wide exactness run touches every ordinary caret dep by design.',
|
|
85
89
|
type: 'boolean',
|
|
86
90
|
default: false,
|
|
87
91
|
})
|
|
@@ -348,22 +352,63 @@ function runCheckMode(groups, exact = false) {
|
|
|
348
352
|
console.error(` ${range.padEnd(16)} — ${holders.join(', ')}`);
|
|
349
353
|
}
|
|
350
354
|
}
|
|
351
|
-
console.error(
|
|
355
|
+
console.error(exact
|
|
356
|
+
? `\nFix: run \`gjsify upgrade --align --exact\` (offline; aligns each dep to its highest declared version and drops the operator).`
|
|
357
|
+
: `\nFix: run \`gjsify upgrade --align\` (offline; aligns each dep to its highest declared range).`);
|
|
352
358
|
process.exit(1);
|
|
353
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* The groups `--align` has work on.
|
|
362
|
+
*
|
|
363
|
+
* Inconsistency is the whole question for a bare `--align`: a dep every manifest declares
|
|
364
|
+
* identically is done. `--exact` asks a second one, and the two do not overlap — a tree
|
|
365
|
+
* where every manifest says `^4.1.0` is perfectly consistent and carries an operator on
|
|
366
|
+
* every one of those declarations, which is precisely what `--check --exact` rejects.
|
|
367
|
+
*
|
|
368
|
+
* Selecting on inconsistency alone therefore made the repair that `--check --exact`'s own
|
|
369
|
+
* failure message names answer `nothing to do` to the tree it was pointed at: the gate
|
|
370
|
+
* demanded a property the fix could not deliver, cheerfully. Measured on bauplaner's five
|
|
371
|
+
* `@girs/*` declarations, all five `^4.1.0` and all five consistent.
|
|
372
|
+
*/
|
|
373
|
+
function alignTargets(groups, exact) {
|
|
374
|
+
if (!exact)
|
|
375
|
+
return findInconsistencies(groups);
|
|
376
|
+
return groups.filter((g) => isInconsistent(g) || g.occurrences.some((o) => o.prefix !== ''));
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* A repair that silently skipped part of its job must not report success.
|
|
380
|
+
*
|
|
381
|
+
* The loop that produces is gate red → repair "done" → gate red, where the second red
|
|
382
|
+
* reads as if the repair had never run. `^1.x` is the shape that lands here: `--check
|
|
383
|
+
* --exact` flags the operator, and no single version can be read out of the range to pin
|
|
384
|
+
* it at, so there is nothing to write and the CLI says which deps those are instead.
|
|
385
|
+
*/
|
|
386
|
+
function reportUnrepairable(groups) {
|
|
387
|
+
console.error(`\ngjsify upgrade --align: ${groups.length} dep(s) left unrepaired — no version to align on:\n`);
|
|
388
|
+
for (const g of groups) {
|
|
389
|
+
console.error(` ${ANSI.bold}${g.name}${ANSI.reset} ${[...g.declaredRanges].join(', ')}`);
|
|
390
|
+
}
|
|
391
|
+
console.error(`\nThose ranges name no single version. Edit them by hand, or resolve one with \`--latest\`.`);
|
|
392
|
+
return process.exit(1);
|
|
393
|
+
}
|
|
354
394
|
function runAlignMode(groups, args) {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
395
|
+
const exact = args.exact ?? false;
|
|
396
|
+
const targets = alignTargets(groups, exact);
|
|
397
|
+
if (targets.length === 0) {
|
|
398
|
+
console.log(`gjsify upgrade --align: nothing to do. ${groups.length} dep(s) already consistent` +
|
|
399
|
+
(exact ? `, every declaration pinned exactly.` : `.`));
|
|
358
400
|
return;
|
|
359
401
|
}
|
|
360
|
-
// For each
|
|
361
|
-
//
|
|
362
|
-
//
|
|
402
|
+
// For each target, the alignment version is the highest declared one — preserve the
|
|
403
|
+
// prefix of the dominant occurrence so the range shape doesn't mutate (caret stays
|
|
404
|
+
// caret, tilde stays tilde). Under `--exact` the prefix is dropped, in `targetRange`.
|
|
363
405
|
const updates = [];
|
|
364
|
-
|
|
365
|
-
|
|
406
|
+
const unrepairable = [];
|
|
407
|
+
for (const g of targets) {
|
|
408
|
+
if (!g.highestVersion) {
|
|
409
|
+
unrepairable.push(g);
|
|
366
410
|
continue;
|
|
411
|
+
}
|
|
367
412
|
// Pick a prefix: use the prefix of the dominant range; if dominant has
|
|
368
413
|
// no prefix, default to "^" (the npm-cli default).
|
|
369
414
|
const dominantOcc = g.occurrences.find((o) => o.currentRange === g.dominantRange);
|
|
@@ -375,21 +420,24 @@ function runAlignMode(groups, args) {
|
|
|
375
420
|
occurrences: g.occurrences.map((o) => ({ ...o, prefix })),
|
|
376
421
|
});
|
|
377
422
|
}
|
|
378
|
-
if (updates.length
|
|
379
|
-
console.log(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
423
|
+
if (updates.length > 0) {
|
|
424
|
+
console.log(exact
|
|
425
|
+
? `gjsify upgrade --align --exact: pinning ${updates.length} dep(s) to their highest declared version, without a range operator:\n`
|
|
426
|
+
: `gjsify upgrade --align: aligning ${updates.length} inconsistent dep(s) to their highest declared version:\n`);
|
|
427
|
+
for (const u of updates) {
|
|
428
|
+
const newPrefix = u.occurrences[0]?.prefix ?? '^';
|
|
429
|
+
console.log(` ${u.name.padEnd(28)} ranges: ${[...u.declaredRanges].join(', ')} → ${targetRange(newPrefix, u.latestVersion, exact)}`);
|
|
430
|
+
}
|
|
431
|
+
if (args.dryRun) {
|
|
432
|
+
console.log('\n[gjsify upgrade --align] --dry-run: no files changed.');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const files = applyToFiles(updates, exact);
|
|
436
|
+
console.log(`\n✏️ updated ${updates.length} dep(s) across ${files} package.json file(s).`);
|
|
437
|
+
}
|
|
390
438
|
}
|
|
391
|
-
|
|
392
|
-
|
|
439
|
+
if (unrepairable.length > 0)
|
|
440
|
+
return reportUnrepairable(unrepairable);
|
|
393
441
|
}
|
|
394
442
|
// ─── Registry resolution (group-aware) ─────────────────────────────────
|
|
395
443
|
async function resolveCandidateGroups(groups, npmrc, verbose, mode) {
|
package/lib/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
5
5
|
import { isGjs, hostRuntime } from '@gjsify/rolldown-plugin-gjsify/runtime';
|
|
6
6
|
import { buildAppForRuntime } from './utils/runtimes.js';
|
|
7
7
|
import { DIALECT_APPS, isSourceDialect, SOURCE_DIALECTS } from '@gjsify/rolldown-plugin-gjsify';
|
|
8
|
+
import { GI_RENDERER_APPS } from '@gjsify/resolve-npm';
|
|
8
9
|
/**
|
|
9
10
|
* Does the failed `import()` of a config look like a MODULE-RESOLUTION failure
|
|
10
11
|
* (the config imports something GJS's ESM loader can't resolve for an external
|
|
@@ -269,6 +270,8 @@ export class Config {
|
|
|
269
270
|
configData.exclude = cliArgs.exclude || [];
|
|
270
271
|
if (cliArgs.consoleShim !== undefined)
|
|
271
272
|
configData.consoleShim = cliArgs.consoleShim;
|
|
273
|
+
if (cliArgs.giRenderer !== undefined)
|
|
274
|
+
configData.giRenderer = cliArgs.giRenderer;
|
|
272
275
|
// `--dialect` narrows here rather than at the call site. yargs `choices`
|
|
273
276
|
// already rejects an unknown value typed on the command line, but a
|
|
274
277
|
// `gjsify.dialect` in package.json or `.gjsifyrc.*` never passes through
|
|
@@ -303,6 +306,16 @@ export class Config {
|
|
|
303
306
|
`It composes on ${[...DIALECT_APPS].join(' and ')} only. ` +
|
|
304
307
|
'Drop the dialect, or build one of those targets.');
|
|
305
308
|
}
|
|
309
|
+
// Same rule for the `gi://` arm, and the same reason: `--gi-renderer --app gjs`
|
|
310
|
+
// would parse, resolve and compose nothing, so the flag would be accepted and
|
|
311
|
+
// ignored. On `gjs` and `node` the specifier is already answered — natively and
|
|
312
|
+
// through `requireGi` — so there is nothing for a renderer to substitute for.
|
|
313
|
+
if (configData.giRenderer === true && !GI_RENDERER_APPS.includes(configData.app)) {
|
|
314
|
+
throw new Error(`gjsify build: --gi-renderer has no effect on --app ${configData.app}. ` +
|
|
315
|
+
`It composes on ${GI_RENDERER_APPS.join(' and ')} only — the targets with no GObject ` +
|
|
316
|
+
'introspection to answer `gi://` with. `--app gjs` resolves it natively and `--app node` ' +
|
|
317
|
+
'through @gjsify/node-gi, so neither needs a widget renderer standing in for it.');
|
|
318
|
+
}
|
|
306
319
|
if (cliArgs.globals !== undefined)
|
|
307
320
|
configData.globals = cliArgs.globals;
|
|
308
321
|
// Fallback applied post-merge (not as a yargs `default:`) so a
|
|
@@ -36,6 +36,12 @@ export interface CliBuildOptions {
|
|
|
36
36
|
* whatever was typed, and the narrowing happens once, where the value is read.
|
|
37
37
|
*/
|
|
38
38
|
dialect?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve `gi://Ns?version=X` to the target's widget renderer instead of an empty
|
|
41
|
+
* module — `--gi-renderer` (ADR 0034 stage 9). `browser` and `nativescript` targets.
|
|
42
|
+
* Default: unset; see `ConfigData.giRenderer` for why it is opt-in.
|
|
43
|
+
*/
|
|
44
|
+
giRenderer?: boolean;
|
|
39
45
|
/**
|
|
40
46
|
* Comma-separated global identifiers the code needs, e.g.
|
|
41
47
|
* `"fetch,Buffer,process,URL,crypto"`. Each maps to the corresponding
|
|
@@ -96,6 +96,20 @@ export interface ConfigData {
|
|
|
96
96
|
* see the header of `SourceDialect`.
|
|
97
97
|
*/
|
|
98
98
|
dialect?: SourceDialect;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve `gi://Ns?version=X` to the target's widget renderer rather than to an
|
|
101
|
+
* empty module (ADR 0034 stage 9). `--app browser` answers out of
|
|
102
|
+
* `@gjsify/adwaita-web`, `--app nativescript` out of `@gjsify/adwaita-nativescript`;
|
|
103
|
+
* `GI_RENDERERS` in `@gjsify/resolve-npm` is the table, and a target with no row
|
|
104
|
+
* there has no arm. Default: unset.
|
|
105
|
+
*
|
|
106
|
+
* Opt-in, never inferred: a `gi://` import reaching one of those targets today is
|
|
107
|
+
* usually a GJS-only code path pulled in transitively, which the empty module
|
|
108
|
+
* silences on purpose. On by default it would pull a widget toolkit into bundles
|
|
109
|
+
* that never named one, and turn every unanswerable namespace from a silent stub
|
|
110
|
+
* into a hard build failure.
|
|
111
|
+
*/
|
|
112
|
+
giRenderer?: boolean;
|
|
99
113
|
/** Comma-separated global identifiers to register. Format: see CliBuildOptions. */
|
|
100
114
|
globals?: string;
|
|
101
115
|
/**
|
|
@@ -464,14 +478,27 @@ export interface ConfigDataShip extends AppMetadata {
|
|
|
464
478
|
/**
|
|
465
479
|
* The built bundle `bin/<name>` executes. Default: `gjsify.main`, then
|
|
466
480
|
* `package.json#main`. Its whole directory is staged into `lib/<name>/`.
|
|
481
|
+
*
|
|
482
|
+
* A STRING is one entry for every target; a TABLE names one per OS, keyed
|
|
483
|
+
* like {@link ShipAppOptions} and resolved beside it. An application with two
|
|
484
|
+
* hosts has two bundles built from one source — `gjsify.app` decides which
|
|
485
|
+
* runtime a target execs, and this decides which of those bundles it hands
|
|
486
|
+
* that runtime (#1545).
|
|
467
487
|
*/
|
|
468
|
-
bundle?: string;
|
|
488
|
+
bundle?: string | ShipBundleOptions;
|
|
469
489
|
/** Icon file, or a directory of them. Sizes are read from the path or the filename. */
|
|
470
490
|
icon?: string;
|
|
471
491
|
/** A `*.gschema.xml` file, or a directory of them. */
|
|
472
492
|
schemas?: string;
|
|
473
493
|
/** Licence file. Default: the first of LICENSE, LICENSE.md, LICENSE.txt, COPYING. */
|
|
474
494
|
licenseFile?: string;
|
|
495
|
+
/**
|
|
496
|
+
* The project's own changelog, rendered into the `.deb`'s
|
|
497
|
+
* `share/doc/<package>/changelog.Debian.gz` (Debian Policy § 4.4).
|
|
498
|
+
* Default: the first of CHANGELOG.md, CHANGELOG, CHANGELOG.txt, NEWS.md,
|
|
499
|
+
* searched up to the repository root exactly as `licenseFile` is.
|
|
500
|
+
*/
|
|
501
|
+
changelogFile?: string;
|
|
475
502
|
/** deb `Section:`. Default: derived from `categories`. */
|
|
476
503
|
section?: string;
|
|
477
504
|
/** rpm `Group:`. Default: derived from `categories`. */
|
|
@@ -611,6 +638,29 @@ export interface ShipAppOptions {
|
|
|
611
638
|
/** What a Windows program directory carries. `node` is the only value its formats accept. */
|
|
612
639
|
win32?: 'gjs' | 'node';
|
|
613
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* `gjsify.ship.bundle` — the ENTRY per OS, the twin of {@link ShipAppOptions}.
|
|
643
|
+
*
|
|
644
|
+
* The two answer one question between them, and answering only half of it is
|
|
645
|
+
* what #1545 measured: `gjsify.ship.app.darwin: "node"` pointed the launcher at
|
|
646
|
+
* `node` while the payload stayed the project's `main` — a `--app gjs` bundle,
|
|
647
|
+
* because Linux is that project's primary target. Both halves were honoured,
|
|
648
|
+
* they contradicted each other, and the `.app` died on its first import.
|
|
649
|
+
*
|
|
650
|
+
* Same OS spelling as everywhere else in this block (`process.platform`), and
|
|
651
|
+
* the same fall-through: a target with no key here keeps `gjsify.main`. What it
|
|
652
|
+
* does NOT keep is another target's entry — a bundle declared for one OS is
|
|
653
|
+
* dropped from every other OS's payload, since no interpreter but that one can
|
|
654
|
+
* load it.
|
|
655
|
+
*/
|
|
656
|
+
export interface ShipBundleOptions {
|
|
657
|
+
/** The entry a `.deb`/`.rpm`/Flatpak installs. */
|
|
658
|
+
linux?: string;
|
|
659
|
+
/** The entry `<App>.app/Contents/Resources/lib/` carries. */
|
|
660
|
+
darwin?: string;
|
|
661
|
+
/** The entry the Windows program directory carries. */
|
|
662
|
+
win32?: string;
|
|
663
|
+
}
|
|
614
664
|
/**
|
|
615
665
|
* `gjsify.ship.sign` — an IDENTITY per OS, and never a certificate.
|
|
616
666
|
*
|