@gjsify/cli 0.63.1-next.236 → 0.63.1-next.237

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.
@@ -107,6 +107,11 @@ export const buildCommand = {
107
107
  type: 'string',
108
108
  choices: ['silent', 'error', 'warning', 'info', 'debug', 'verbose'],
109
109
  default: 'warning',
110
+ })
111
+ .option('channel', {
112
+ description: 'Distribution channel this build is for (ADR 0113): flathub, web, microsoft-store or apple-app-store. Sets the __GJSIFY_CHANNEL__ define that @gjsify/distribution reads; overrides gjsify.ship.channel. Unset builds report no donation UI.',
113
+ type: 'string',
114
+ choices: ['flathub', 'web', 'microsoft-store', 'apple-app-store'],
110
115
  })
111
116
  .option('dialect', {
112
117
  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.",
@@ -26,6 +26,7 @@ function toMetadataInputs(inputs) {
26
26
  command: inputs.command,
27
27
  kind: inputs.kind,
28
28
  metadata: inputs.flatpak,
29
+ channel: 'flathub',
29
30
  configKey: 'gjsify.flatpak',
30
31
  };
31
32
  }
@@ -7,6 +7,7 @@ interface ShipOptions {
7
7
  'from-stage'?: string;
8
8
  'expect-target'?: string;
9
9
  'skip-build': boolean;
10
+ channel?: string;
10
11
  arch?: string;
11
12
  sign?: string;
12
13
  notarize?: string;
@@ -22,6 +22,7 @@
22
22
  // on the two rows that are host-bound: a `.dmg` is a UDIF image over a real
23
23
  // HFS+/APFS volume that no Linux host in this tree can write (§ A1), and a
24
24
  // signature is made where the identity lives (§ A14).
25
+ import { CHANNELS, CHANNEL_ENV, parseDeclaredChannel } from '../utils/channel.js';
25
26
  import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
26
27
  import { basename, dirname, join, relative, resolve } from 'node:path';
27
28
  import { Config } from '../config.js';
@@ -104,6 +105,12 @@ export const shipCommand = {
104
105
  type: 'string',
105
106
  description: 'With --from-stage: refuse unless the stage was assembled for this `<os>-<arch>`, e.g. ' +
106
107
  '`linux-arm64`.',
108
+ })
109
+ .option('channel', {
110
+ type: 'string',
111
+ choices: [...CHANNELS],
112
+ description: 'Distribution channel (ADR 0113). Overrides gjsify.ship.channel; `--target flatpak` implies flathub. ' +
113
+ 'Passed to the project build, and gates the metainfo donation URL.',
107
114
  })
108
115
  .option('skip-build', {
109
116
  type: 'boolean',
@@ -303,8 +310,13 @@ async function assemble(args) {
303
310
  if (!args.stage)
304
311
  for (const format of formats)
305
312
  assertCanPack(format);
313
+ // flag > `gjsify.ship.channel` > what the target implies. Only flatpak implies one: an MSIX or a
314
+ // `.app` can as well be sideloaded (ADR 0113 § 2).
315
+ const channel = parseDeclaredChannel(args.channel, '--channel') ??
316
+ parseDeclaredChannel(ship.channel, 'gjsify.ship.channel') ??
317
+ (formats.some((format) => format.id === 'flatpak') ? 'flathub' : undefined);
306
318
  if (!args['skip-build'])
307
- await runProjectBuild(projectDir);
319
+ await runProjectBuild(projectDir, channel);
308
320
  // THE OTHER HALF of the resolution above, and they are one decision: the
309
321
  // runtime a target execs and the entry it hands that runtime (#1545). Two
310
322
  // keys, resolved by two twin functions, compared by `assertEntryRunsUnder`
@@ -365,6 +377,7 @@ async function assemble(args) {
365
377
  command: settings.binaryName,
366
378
  kind: settings.kind,
367
379
  metadata,
380
+ channel,
368
381
  configKey: 'gjsify.ship',
369
382
  copyrightYear: new Date(mtime * 1000).getUTCFullYear(),
370
383
  };
@@ -1441,7 +1454,7 @@ function assertFormatCanRunInterpreter(format, app) {
1441
1454
  * `completion: 'return'` because this handler returns afterwards; on GJS that
1442
1455
  * selects the blocking path rather than arming a main loop only an exit quits.
1443
1456
  */
1444
- async function runProjectBuild(projectDir) {
1457
+ async function runProjectBuild(projectDir, channel) {
1445
1458
  const pkg = readPackageJson(join(projectDir, 'package.json'));
1446
1459
  if (typeof pkg?.scripts?.build !== 'string') {
1447
1460
  throw new Error('gjsify ship: this project has no `build` script to run. Add one, or pass --skip-build to ' +
@@ -1452,6 +1465,7 @@ async function runProjectBuild(projectDir) {
1452
1465
  completion: 'return',
1453
1466
  cwd: projectDir,
1454
1467
  stdio: 'inherit',
1468
+ env: channel === undefined ? undefined : { ...process.env, [CHANNEL_ENV]: channel },
1455
1469
  notFound: () => new Error('gjsify ship: `gjsify` is not on PATH, so the project build cannot be run. Build the project ' +
1456
1470
  'yourself and pass --skip-build.'),
1457
1471
  });
@@ -3,6 +3,7 @@ interface SiteCliOptions {
3
3
  operation: string;
4
4
  root?: string;
5
5
  out?: string;
6
+ overview?: string;
6
7
  offline: boolean;
7
8
  cache: boolean;
8
9
  cacheDir?: string;
@@ -21,9 +21,11 @@
21
21
  // The resolved install directory is passed to the child as `--package-dir`. A
22
22
  // bundle's `import.meta.url` is the OUTFILE, so the library cannot find its own
23
23
  // `po/` and `lib/client/` on its own — only the caller still knows.
24
+ import { spawnSync } from 'node:child_process';
24
25
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
25
26
  import { dirname, join, resolve } from 'node:path';
26
27
  import { hostRuntime } from '@gjsify/rolldown-plugin-gjsify/runtime';
28
+ import { resolveNodeGi } from '../utils/run-node.js';
27
29
  /** The `@gjsify/site` install reachable from `cwd`, walking the node_modules chain upwards. */
28
30
  function resolveSitePackage(cwd) {
29
31
  let dir = resolve(cwd);
@@ -37,6 +39,23 @@ function resolveSitePackage(cwd) {
37
39
  dir = parent;
38
40
  }
39
41
  }
42
+ function gjsOnPath() {
43
+ return spawnSync('gjs', ['--version'], { stdio: 'ignore' }).status === 0;
44
+ }
45
+ /**
46
+ * The runtime the page is built for. An explicit `--runtime` wins. Otherwise the host runtime —
47
+ * except that a non-gjs host without `@gjsify/node-gi` in the project cannot run the `--app node`
48
+ * bundle (GdkPixbuf is reached through it), so a consumer that only ran
49
+ * `npm i -D @gjsify/site` falls back to gjs when the system has one.
50
+ */
51
+ function pickRuntime(requested, cwd) {
52
+ if (requested)
53
+ return requested;
54
+ const host = hostRuntime();
55
+ if (host === 'gjs' || resolveNodeGi(cwd))
56
+ return host;
57
+ return gjsOnPath() ? 'gjs' : host;
58
+ }
40
59
  /**
41
60
  * The generated entry. Arguments arrive as ARGV rather than baked in, so the cached bundle serves
42
61
  * every invocation — flags change far more often than this does.
@@ -65,6 +84,10 @@ export const siteCommand = {
65
84
  .option('root', {
66
85
  description: 'Repository to read from (default: the working directory)',
67
86
  type: 'string',
87
+ })
88
+ .option('overview', {
89
+ description: 'build: render the support overview listed in this JSON file instead of one app',
90
+ type: 'string',
68
91
  })
69
92
  .option('out', { description: 'Output directory, emptied first (default: <root>/dist)', type: 'string' })
70
93
  .option('offline', { description: 'Skip every network call', type: 'boolean', default: false })
@@ -90,7 +113,8 @@ export const siteCommand = {
90
113
  })
91
114
  .option('runtime', {
92
115
  description: 'Runtime to build for and run on: gjs | node | bun | deno (default: the host runtime). ' +
93
- 'node/bun/deno build the same `--app node` bundle and require @gjsify/node-gi in the project.',
116
+ 'node/bun/deno build the same `--app node` bundle and require @gjsify/node-gi in the project; ' +
117
+ 'without it the default falls back to gjs.',
94
118
  type: 'string',
95
119
  choices: ['gjs', 'node', 'bun', 'deno'],
96
120
  defaultDescription: 'host runtime (gjs on gjs, else node/bun/deno)',
@@ -111,15 +135,29 @@ export const siteCommand = {
111
135
  ' In a source checkout, build the package first: `gjsify workspace site build`.');
112
136
  return process.exit(1);
113
137
  }
114
- const runtime = (args.runtime ?? hostRuntime());
138
+ const runtime = pickRuntime(args.runtime, cwd);
115
139
  const app = runtime === 'gjs' ? 'gjs' : 'node';
116
140
  const cacheDir = join(cwd, 'node_modules', '.cache', 'gjsify-site');
117
141
  mkdirSync(cacheDir, { recursive: true });
118
142
  const entryPath = join(cacheDir, `entry.${app}.ts`);
119
143
  const outPath = join(cacheDir, `site.${app}.mjs`);
120
144
  writeFileSync(entryPath, generateEntry(app));
121
- const { runCli } = await import('../cli-app.js');
122
- await runCli(['build', entryPath, '--app', app, '--globals', args.globals ?? 'auto', '--outfile', outPath]);
145
+ const globals = args.globals ?? 'auto';
146
+ if (app === 'gjs') {
147
+ // The consumer installed `@gjsify/site`, not the polyfills `--globals auto` injects
148
+ // (URL, DOMException, streams, …), so those resolve from beside the running CLI.
149
+ const { BuildAction } = await import('../actions/build.js');
150
+ await BuildAction.bundleFileForGjs(entryPath, outPath, {
151
+ globals,
152
+ preserveDefaultExport: false,
153
+ toolchainAnchor: BuildAction.runningCliFile(),
154
+ logLevel: 'silent',
155
+ });
156
+ }
157
+ else {
158
+ const { runCli } = await import('../cli-app.js');
159
+ await runCli(['build', entryPath, '--app', app, '--globals', globals, '--outfile', outPath]);
160
+ }
123
161
  // RESOLVED values, not the raw argv: the child parses its own flags and
124
162
  // cannot know where this CLI found the package.
125
163
  const forwarded = [args.operation, '--package-dir', packageDir];
@@ -127,6 +165,8 @@ export const siteCommand = {
127
165
  forwarded.push('--root', resolve(cwd, args.root));
128
166
  if (args.out)
129
167
  forwarded.push('--out', resolve(cwd, args.out));
168
+ if (args.overview)
169
+ forwarded.push('--overview', resolve(cwd, args.overview));
130
170
  if (args.offline)
131
171
  forwarded.push('--offline');
132
172
  if (!args.cache)
package/lib/config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { APP_NAME } from './constants.js';
2
+ import { CHANNEL_ENV, parseDeclaredChannel } from './utils/channel.js';
2
3
  import { cosmiconfig } from 'cosmiconfig';
3
4
  import { basename, dirname } from 'node:path';
4
5
  import { pathToFileURL } from 'node:url';
@@ -360,6 +361,11 @@ export class Config {
360
361
  return out;
361
362
  };
362
363
  const defineMap = parseKvPairs(cliArgs.define ?? [], 'define');
364
+ // flag > env (set by `gjsify ship` for the project's own build) > `gjsify.ship.channel`.
365
+ const channel = parseDeclaredChannel(cliArgs.channel ?? process.env[CHANNEL_ENV] ?? configData.ship?.channel, cliArgs.channel !== undefined ? '--channel' : 'gjsify.ship.channel');
366
+ if (channel !== undefined && defineMap.__GJSIFY_CHANNEL__ === undefined) {
367
+ defineMap.__GJSIFY_CHANNEL__ = JSON.stringify(channel);
368
+ }
363
369
  const aliasMap = parseKvPairs(cliArgs.alias ?? [], 'alias');
364
370
  if (Object.keys(aliasMap).length) {
365
371
  configData.aliases = { ...configData.aliases, ...aliasMap };
@@ -36,6 +36,8 @@ 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
+ /** Distribution channel (ADR 0113), becomes the `__GJSIFY_CHANNEL__` define. */
40
+ channel?: string;
39
41
  /**
40
42
  * Resolve `gi://Ns?version=X` to the target's widget renderer instead of an empty
41
43
  * module — `--gi-renderer` (ADR 0034 stage 9). `browser` and `nativescript` targets.
@@ -431,6 +431,13 @@ export interface AppMetadata {
431
431
  * `gjsify.flatpak` block usually needs no `gjsify.ship` block at all.
432
432
  */
433
433
  export interface ConfigDataShip extends AppMetadata {
434
+ /**
435
+ * Distribution channel (ADR 0113): `flathub | web | microsoft-store | apple-app-store`.
436
+ * Becomes the `__GJSIFY_CHANNEL__` define that `@gjsify/distribution` reads, and gates the
437
+ * metainfo `<url type="donation">`. `--channel` overrides it; `ship --target flatpak` implies
438
+ * `flathub` when nothing states one. Unset means no donation UI.
439
+ */
440
+ channel?: string;
434
441
  /** Reverse-DNS app id. Falls back to `gjsify.flatpak.appId`, then `package.json#name`. */
435
442
  appId?: string;
436
443
  /**
@@ -10,6 +10,8 @@ export interface AppMetadataInputs {
10
10
  /** `'app'` renders a desktop-application component, `'cli'` a console one. */
11
11
  kind: 'app' | 'cli';
12
12
  metadata: AppMetadata;
13
+ /** Distribution channel (ADR 0113). `<url type="donation">` is emitted only where it allows donations. */
14
+ channel?: string;
13
15
  /**
14
16
  * Config path the validator names in its hints, e.g. `gjsify.flatpak` or
15
17
  * `gjsify.ship`. Both read the same fields, so the hint has to say which
@@ -13,6 +13,7 @@
13
13
  // attributes, provides) has too many optional nested sections for a
14
14
  // template+placeholder approach to stay legible. The `.desktop` entry stays
15
15
  // template-based — it is flat `key=value`.
16
+ import { channelAllowsDonations } from './channel.js';
16
17
  /**
17
18
  * The `.desktop` skeleton, as source rather than as a file to find at runtime.
18
19
  *
@@ -190,7 +191,7 @@ function renderMetainfo(inputs, kind) {
190
191
  lines.push(` <url type="bugtracker">${escapeXml(m.bugtrackerUrl)}</url>`);
191
192
  if (m.vcsBrowserUrl)
192
193
  lines.push(` <url type="vcs-browser">${escapeXml(m.vcsBrowserUrl)}</url>`);
193
- if (m.donationUrl)
194
+ if (m.donationUrl && channelAllowsDonations(inputs.channel))
194
195
  lines.push(` <url type="donation">${escapeXml(m.donationUrl)}</url>`);
195
196
  if (m.translateUrl)
196
197
  lines.push(` <url type="translate">${escapeXml(m.translateUrl)}</url>`);
@@ -0,0 +1,8 @@
1
+ export declare const CHANNELS: readonly ["flathub", "web", "microsoft-store", "apple-app-store"];
2
+ export type DeclaredChannel = (typeof CHANNELS)[number];
3
+ /** Env var `gjsify ship` hands to the project's own build, which has no `--channel` to pass. */
4
+ export declare const CHANNEL_ENV = "GJSIFY_CHANNEL";
5
+ /** The channel `value` names, or a refusal that lists the alternatives. `undefined` stays unset. */
6
+ export declare function parseDeclaredChannel(value: unknown, source: string): DeclaredChannel | undefined;
7
+ /** Unset (an `unknown` build) permits no donation UI, as the ADR's quiet default. */
8
+ export declare function channelAllowsDonations(channel: string | undefined): boolean;
@@ -0,0 +1,24 @@
1
+ // The distribution channel (ADR 0113), as the CLI needs it: validate a value, and ask whether
2
+ // it permits donation UI. Mirrors `@gjsify/distribution`'s table on purpose and does not import
3
+ // it: the CLI would then depend on a package it must be able to build and publish first.
4
+ export const CHANNELS = ['flathub', 'web', 'microsoft-store', 'apple-app-store'];
5
+ const DONATIONS = {
6
+ flathub: true,
7
+ web: true,
8
+ 'microsoft-store': false,
9
+ 'apple-app-store': false,
10
+ };
11
+ /** Env var `gjsify ship` hands to the project's own build, which has no `--channel` to pass. */
12
+ export const CHANNEL_ENV = 'GJSIFY_CHANNEL';
13
+ /** The channel `value` names, or a refusal that lists the alternatives. `undefined` stays unset. */
14
+ export function parseDeclaredChannel(value, source) {
15
+ if (value === undefined || value === '')
16
+ return undefined;
17
+ if (CHANNELS.includes(value))
18
+ return value;
19
+ throw new Error(`${source}: "${String(value)}" is not a distribution channel. Known: ${CHANNELS.join(', ')}.`);
20
+ }
21
+ /** Unset (an `unknown` build) permits no donation UI, as the ADR's quiet default. */
22
+ export function channelAllowsDonations(channel) {
23
+ return channel !== undefined && DONATIONS[channel] === true;
24
+ }
@@ -63,8 +63,8 @@ export function nodeBinary() {
63
63
  export async function runNodeBundle(bundlePath, extraArgs = [], options) {
64
64
  const cwd = process.cwd();
65
65
  if (!resolveNodeGiForBundle(bundlePath, cwd)) {
66
- throw new Error('Cannot run the storybook on Node: `@gjsify/node-gi` is not installed.\n' +
67
- 'Add @gjsify/node-gi as a dependency to run the storybook on Node, e.g.:\n' +
66
+ throw new Error('Cannot run this bundle on Node: `@gjsify/node-gi` is not installed.\n' +
67
+ 'Add @gjsify/node-gi as a dependency to run it on Node, e.g.:\n' +
68
68
  ' "devDependencies": { "@gjsify/node-gi": "^0.13.0" }');
69
69
  }
70
70
  const { env: nativeEnv, envPrefix } = computeNativeEnvForBundle(bundlePath, cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.63.1-next.236",
3
+ "version": "0.63.1-next.237",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,22 +142,22 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/blueprint": "0.63.1-next.236",
146
- "@gjsify/buffer": "0.63.1-next.236",
147
- "@gjsify/create-app": "0.63.1-next.236",
148
- "@gjsify/node-globals": "0.63.1-next.236",
149
- "@gjsify/node-polyfills": "0.63.1-next.236",
150
- "@gjsify/npm-registry": "0.63.1-next.236",
151
- "@gjsify/resolve-npm": "0.63.1-next.236",
152
- "@gjsify/rolldown-plugin-gjsify": "0.63.1-next.236",
153
- "@gjsify/vite-plugin-blueprint": "0.63.1-next.236",
154
- "@gjsify/rolldown-plugin-pnp": "0.63.1-next.236",
155
- "@gjsify/semver": "0.63.1-next.236",
156
- "@gjsify/tar": "0.63.1-next.236",
157
- "@gjsify/tsc": "0.63.1-next.236",
158
- "@gjsify/utils": "0.63.1-next.236",
159
- "@gjsify/web-polyfills": "0.63.1-next.236",
160
- "@gjsify/workspace": "0.63.1-next.236",
145
+ "@gjsify/blueprint": "0.63.1-next.237",
146
+ "@gjsify/buffer": "0.63.1-next.237",
147
+ "@gjsify/create-app": "0.63.1-next.237",
148
+ "@gjsify/node-globals": "0.63.1-next.237",
149
+ "@gjsify/node-polyfills": "0.63.1-next.237",
150
+ "@gjsify/npm-registry": "0.63.1-next.237",
151
+ "@gjsify/resolve-npm": "0.63.1-next.237",
152
+ "@gjsify/rolldown-plugin-gjsify": "0.63.1-next.237",
153
+ "@gjsify/vite-plugin-blueprint": "0.63.1-next.237",
154
+ "@gjsify/rolldown-plugin-pnp": "0.63.1-next.237",
155
+ "@gjsify/semver": "0.63.1-next.237",
156
+ "@gjsify/tar": "0.63.1-next.237",
157
+ "@gjsify/tsc": "0.63.1-next.237",
158
+ "@gjsify/utils": "0.63.1-next.237",
159
+ "@gjsify/web-polyfills": "0.63.1-next.237",
160
+ "@gjsify/workspace": "0.63.1-next.237",
161
161
  "acorn": "^8.17.0",
162
162
  "cosmiconfig": "^9.0.2",
163
163
  "get-tsconfig": "^4.14.0",
@@ -166,13 +166,13 @@
166
166
  "yargs": "^18.0.0"
167
167
  },
168
168
  "devDependencies": {
169
- "@gjsify/unit": "0.63.1-next.236",
169
+ "@gjsify/unit": "0.63.1-next.237",
170
170
  "@types/yargs": "^17.0.35",
171
171
  "typescript": "^6.0.3"
172
172
  },
173
173
  "peerDependencies": {
174
- "@gjsify/oxfmt-native": "0.63.1-next.236",
175
- "@gjsify/rolldown-native": "0.63.1-next.236"
174
+ "@gjsify/oxfmt-native": "0.63.1-next.237",
175
+ "@gjsify/rolldown-native": "0.63.1-next.237"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "@gjsify/oxfmt-native": {
@@ -192,5 +192,5 @@
192
192
  "url": "https://github.com/gjsify/gjsify/issues"
193
193
  },
194
194
  "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/cli#readme",
195
- "gitHead": "d4ae412daabae9cb89f4fa11ee7279a308c9e4be"
195
+ "gitHead": "a349db400a7e68befc11ba6381ddb0e4caff09b6"
196
196
  }