@gjsify/cli 0.44.0 → 0.45.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.
Files changed (73) hide show
  1. package/dist/cli.gjs.mjs +192 -165
  2. package/lib/commands/gettext.d.ts +2 -1
  3. package/lib/commands/gettext.js +105 -47
  4. package/lib/commands/onboard.d.ts +7 -0
  5. package/lib/commands/onboard.js +349 -96
  6. package/lib/commands/ship.d.ts +3 -0
  7. package/lib/commands/ship.js +833 -54
  8. package/lib/commands/trust.d.ts +6 -0
  9. package/lib/commands/trust.js +1 -1
  10. package/lib/types/config-data.d.ts +63 -1
  11. package/lib/utils/msgfmt-merge.d.ts +35 -0
  12. package/lib/utils/msgfmt-merge.js +102 -0
  13. package/lib/utils/npm-otp.d.ts +27 -2
  14. package/lib/utils/npm-otp.js +82 -7
  15. package/lib/utils/onboard-discovery.d.ts +68 -0
  16. package/lib/utils/onboard-discovery.js +178 -0
  17. package/lib/utils/onboard-probe.d.ts +118 -1
  18. package/lib/utils/onboard-probe.js +153 -4
  19. package/lib/utils/prompt-output.d.ts +12 -0
  20. package/lib/utils/prompt-output.js +54 -0
  21. package/lib/utils/prompt.js +22 -2
  22. package/lib/utils/publishable-packages.d.ts +1 -1
  23. package/lib/utils/publishable-packages.js +1 -0
  24. package/lib/utils/resolve-npm-package.d.ts +56 -0
  25. package/lib/utils/resolve-npm-package.js +69 -0
  26. package/lib/utils/ship/app-runtime.d.ts +214 -0
  27. package/lib/utils/ship/app-runtime.js +429 -0
  28. package/lib/utils/ship/depends.d.ts +50 -0
  29. package/lib/utils/ship/depends.js +117 -3
  30. package/lib/utils/ship/discover.js +18 -1
  31. package/lib/utils/ship/dmg.d.ts +71 -0
  32. package/lib/utils/ship/dmg.js +181 -0
  33. package/lib/utils/ship/flatpak.js +1 -1
  34. package/lib/utils/ship/formats.d.ts +85 -7
  35. package/lib/utils/ship/formats.js +734 -15
  36. package/lib/utils/ship/launcher.d.ts +13 -3
  37. package/lib/utils/ship/launcher.js +362 -17
  38. package/lib/utils/ship/layout.d.ts +268 -0
  39. package/lib/utils/ship/layout.js +351 -0
  40. package/lib/utils/ship/localize-metadata.d.ts +22 -0
  41. package/lib/utils/ship/localize-metadata.js +126 -0
  42. package/lib/utils/ship/mime.js +1 -9
  43. package/lib/utils/ship/msi.d.ts +126 -0
  44. package/lib/utils/ship/msi.js +417 -0
  45. package/lib/utils/ship/node-runtime.d.ts +71 -0
  46. package/lib/utils/ship/node-runtime.js +122 -0
  47. package/lib/utils/ship/payload.d.ts +156 -5
  48. package/lib/utils/ship/payload.js +580 -14
  49. package/lib/utils/ship/plan.d.ts +21 -0
  50. package/lib/utils/ship/plan.js +20 -7
  51. package/lib/utils/ship/plist.d.ts +58 -0
  52. package/lib/utils/ship/plist.js +198 -0
  53. package/lib/utils/ship/rpm.js +9 -3
  54. package/lib/utils/ship/schemas.d.ts +61 -0
  55. package/lib/utils/ship/schemas.js +118 -0
  56. package/lib/utils/ship/scripts.js +7 -3
  57. package/lib/utils/ship/settings.d.ts +19 -1
  58. package/lib/utils/ship/settings.js +12 -1
  59. package/lib/utils/ship/share-dirs.d.ts +23 -0
  60. package/lib/utils/ship/share-dirs.js +46 -0
  61. package/lib/utils/ship/signing.d.ts +195 -0
  62. package/lib/utils/ship/signing.js +317 -0
  63. package/lib/utils/ship/stage-manifest.d.ts +40 -12
  64. package/lib/utils/ship/stage-manifest.js +99 -13
  65. package/lib/utils/ship/stage-writer.d.ts +48 -0
  66. package/lib/utils/ship/stage-writer.js +71 -1
  67. package/lib/utils/ship/types.d.ts +158 -13
  68. package/lib/utils/ship/xml.d.ts +7 -0
  69. package/lib/utils/ship/xml.js +25 -0
  70. package/lib/utils/ship/zip.d.ts +69 -0
  71. package/lib/utils/ship/zip.js +245 -0
  72. package/lib/utils/trust-registry.js +5 -0
  73. package/package.json +19 -19
@@ -1,10 +1,11 @@
1
1
  import type { Command } from '../types/index.js';
2
- type GettextFormat = 'mo' | 'xml' | 'desktop' | 'json';
2
+ type GettextFormat = 'mo' | 'xml' | 'desktop';
3
3
  interface GettextOptions {
4
4
  poDir: string;
5
5
  outDir: string;
6
6
  domain: string;
7
7
  format?: GettextFormat;
8
+ template?: string;
8
9
  metainfo?: string;
9
10
  filename?: string;
10
11
  removeXmlComments?: boolean;
@@ -1,7 +1,9 @@
1
1
  import { execFile } from 'node:child_process';
2
- import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
2
+ import { mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
3
4
  import { basename, join, resolve } from 'node:path';
4
5
  import { promisify } from 'node:util';
6
+ import { mergeCatalogues } from '../utils/msgfmt-merge.js';
5
7
  const execFileAsync = promisify(execFile);
6
8
  async function listLanguages(poDir) {
7
9
  const entries = await readdir(poDir);
@@ -25,40 +27,79 @@ async function fileExists(path) {
25
27
  return false;
26
28
  }
27
29
  }
28
- async function compileBulkXml(opts) {
30
+ /**
31
+ * Fold every catalogue into ONE template.
32
+ *
33
+ * The chain, and the four measured msgfmt constraints that shape it, live in
34
+ * `utils/msgfmt-merge.ts` — `gjsify ship` folds the catalogues it stages into the
35
+ * metadata it generates through the same function, and a second copy of those
36
+ * constraints here would be the copy that drifts.
37
+ *
38
+ * What is local to this command: the output keeps the CALLER's `--filename`,
39
+ * while every INTERMEDIATE is named after the TEMPLATE. Those have to be two
40
+ * different names, because msgfmt finds its ITS rules by filename pattern
41
+ * (constraint 4 over there) and from the second catalogue on the intermediates
42
+ * ARE templates. Named after `--filename` instead, the command's answer depended
43
+ * on how many catalogues there were: measured, `--format xml --template
44
+ * app.metainfo.xml.in --filename out.xml` wrote a translated file for a one-language
45
+ * `po/` and died on a two-language one with `cannot locate ITS rules for
46
+ * <tmpdir>/0-out.xml` — naming an internal path over a rule the user could act on.
47
+ * The template's suffix is what msgfmt validated for call 1, so it is the one
48
+ * suffix known to work for calls 2..n.
49
+ */
50
+ async function compileMerged(opts) {
51
+ const languages = await listLanguages(opts.poDir);
52
+ if (languages.length === 0) {
53
+ console.warn(`[gjsify gettext] no .po files found in ${opts.poDir}`);
54
+ return;
55
+ }
29
56
  const outputFile = join(opts.outDir, opts.filename);
30
57
  await ensureDir(opts.outDir);
31
- const args = [`--output-file=${outputFile}`, '--xml', `--template=${opts.template}`, '-d', opts.poDir];
32
- if (opts.verbose) {
33
- console.log(`[gjsify gettext] msgfmt ${args.join(' ')}`);
58
+ const workDir = await mkdtemp(join(tmpdir(), 'gjsify-gettext-'));
59
+ try {
60
+ const merged = await readFile(mergeCatalogues({
61
+ mode: `--${opts.format}`,
62
+ template: opts.template,
63
+ // The TEMPLATE's name, minus the `.in` convention — see the header.
64
+ // `basename`, so an intermediate stays inside `workDir` rather than
65
+ // following the directory part of the path the user passed.
66
+ extension: `-${basename(opts.template).replace(/\.in$/, '')}`,
67
+ catalogues: languages.map((lang) => ({ locale: lang, po: join(opts.poDir, `${lang}.po`) })),
68
+ workDir,
69
+ onCall: opts.verbose ? (args) => console.log(`[gjsify gettext] msgfmt ${args.join(' ')}`) : undefined,
70
+ }), 'utf-8');
71
+ await writeFile(outputFile, opts.removeXmlComments && opts.format === 'xml' ? stripXmlComments(merged) : merged);
34
72
  }
35
- await execFileAsync('msgfmt', args);
36
- if (opts.removeXmlComments) {
37
- const content = await readFile(outputFile, 'utf-8');
38
- await writeFile(outputFile, stripXmlComments(content));
73
+ finally {
74
+ await rm(workDir, { recursive: true, force: true });
39
75
  }
40
76
  if (opts.verbose) {
41
- console.log(`[gjsify gettext] wrote ${outputFile}`);
77
+ console.log(`[gjsify gettext] merged ${languages.length} language(s) into ${outputFile}`);
42
78
  }
43
79
  }
44
- async function compilePerLanguage(opts) {
80
+ /**
81
+ * Compile each `<lang>.po` into `<outDir>/<lang>/LC_MESSAGES/<filename>`.
82
+ *
83
+ * `mo` only. The other two formats SUBSTITUTE a template rather than producing a
84
+ * catalogue, and there is no per-language file for them to write — which is the
85
+ * defect this signature now makes unrepresentable: the old shared loop passed
86
+ * `--desktop`/`--xml` with no `--template`, and msgfmt refuses that outright
87
+ * (`--desktop requires a "--template template" specification`, exit 1). Every
88
+ * `gjsify gettext --format=desktop` invocation had therefore always failed; the
89
+ * only e2e coverage passes `--format mo`, so nothing ever ran the other branch.
90
+ */
91
+ async function compileCatalogues(opts) {
45
92
  const languages = await listLanguages(opts.poDir);
46
93
  if (languages.length === 0) {
47
94
  console.warn(`[gjsify gettext] no .po files found in ${opts.poDir}`);
48
95
  return;
49
96
  }
50
97
  for (const lang of languages) {
51
- const poFile = join(opts.poDir, `${lang}.po`);
52
- const langDir = opts.format === 'mo' ? join(opts.outDir, lang, 'LC_MESSAGES') : join(opts.outDir, lang);
98
+ const langDir = join(opts.outDir, lang, 'LC_MESSAGES');
53
99
  await ensureDir(langDir);
54
- const outputFile = join(langDir, opts.filename);
55
- // msgfmt produces the binary .mo format by default — there is no
56
- // `--mo` flag (only --xml, --desktop, --properties-output, ...).
57
- const args = [`--output-file=${outputFile}`];
58
- if (opts.format !== 'mo') {
59
- args.push(`--${opts.format}`);
60
- }
61
- args.push(poFile);
100
+ // msgfmt produces the binary .mo format by default — there is no `--mo`
101
+ // flag (only --xml, --desktop, --properties-output, ...).
102
+ const args = [`--output-file=${join(langDir, opts.filename)}`, join(opts.poDir, `${lang}.po`)];
62
103
  if (opts.verbose) {
63
104
  console.log(`[gjsify gettext] msgfmt ${args.join(' ')}`);
64
105
  }
@@ -68,24 +109,22 @@ async function compilePerLanguage(opts) {
68
109
  console.log(`[gjsify gettext] compiled ${languages.length} language(s) into ${opts.outDir}`);
69
110
  }
70
111
  }
71
- function defaultFilename(domain, format, metainfoTemplate) {
112
+ function defaultFilename(domain, format, template) {
72
113
  switch (format) {
73
114
  case 'mo':
74
115
  return `${domain}.mo`;
75
- case 'json':
76
- return `${domain}.json`;
77
116
  case 'desktop':
78
117
  return `${domain}.desktop`;
79
118
  case 'xml': {
80
119
  // Mirror the template filename but without the trailing `.in` (convention for
81
120
  // pre-processed metainfo templates: `org.foo.Bar.metainfo.xml.in`).
82
- if (metainfoTemplate) {
121
+ if (template) {
83
122
  // `basename`, not a hand-rolled `lastIndexOf('/')`: this is a path the USER
84
123
  // passed, and on win32 it separates with `\`, which the slice read as part
85
124
  // of the name (#1143). `node:path` is the right owner for host tooling —
86
125
  // though it only answers correctly under Node until #1146 makes
87
126
  // `@gjsify/path` select win32 per host.
88
- return basename(metainfoTemplate.replace(/\.in$/, ''));
127
+ return basename(template.replace(/\.in$/, ''));
89
128
  }
90
129
  return `${domain}.xml`;
91
130
  }
@@ -93,7 +132,8 @@ function defaultFilename(domain, format, metainfoTemplate) {
93
132
  }
94
133
  export const gettextCommand = {
95
134
  command: 'gettext <poDir> <outDir>',
96
- description: 'Compile gettext .po files to .mo (per-language locale tree) or substitute a metainfo template via msgfmt --xml.',
135
+ description: 'Compile gettext .po files to .mo (per-language locale tree), or merge every catalogue into a ' +
136
+ '.desktop / AppStream template via msgfmt --desktop / --xml.',
97
137
  builder: (yargs) => {
98
138
  return yargs
99
139
  .positional('poDir', {
@@ -103,7 +143,7 @@ export const gettextCommand = {
103
143
  demandOption: true,
104
144
  })
105
145
  .positional('outDir', {
106
- description: 'Output directory (locale tree for --format=mo, plain dir for xml/desktop/json)',
146
+ description: 'Output directory (locale tree for --format=mo, plain dir for xml/desktop)',
107
147
  type: 'string',
108
148
  normalize: true,
109
149
  demandOption: true,
@@ -117,13 +157,21 @@ export const gettextCommand = {
117
157
  .option('format', {
118
158
  description: 'Output format',
119
159
  type: 'string',
120
- choices: ['mo', 'xml', 'desktop', 'json'],
160
+ choices: ['mo', 'xml', 'desktop'],
121
161
  default: 'mo',
162
+ })
163
+ .option('template', {
164
+ description: 'Required for --format=xml|desktop: the file msgfmt substitutes into. Its SUFFIX matters — ' +
165
+ 'msgfmt --xml finds its ITS rules by filename pattern, so an AppStream template must be ' +
166
+ 'named `*.metainfo.xml[.in]`.',
167
+ type: 'string',
168
+ normalize: true,
122
169
  })
123
170
  .option('metainfo', {
124
- description: 'For --format=xml: path to the template (`.metainfo.xml.in`) used as msgfmt --template',
171
+ description: 'Deprecated alias for --template.',
125
172
  type: 'string',
126
173
  normalize: true,
174
+ hidden: true,
127
175
  })
128
176
  .option('filename', {
129
177
  description: 'Override the output filename (defaults to <domain>.<ext>)',
@@ -146,8 +194,12 @@ export const gettextCommand = {
146
194
  const outDir = resolve(args.outDir);
147
195
  const domain = args.domain;
148
196
  const format = args.format ?? 'mo';
149
- const metainfo = args.metainfo ? resolve(args.metainfo) : undefined;
150
- const filename = args.filename ?? defaultFilename(domain, format, metainfo);
197
+ // `--metainfo` predates the option being useful for `.desktop` too. Kept as a
198
+ // hidden alias rather than removed: it is the spelling `cli-reference.md`
199
+ // documented, so removing it would break the invocation the docs taught.
200
+ const templateArg = args.template ?? args.metainfo;
201
+ const template = templateArg ? resolve(templateArg) : undefined;
202
+ const filename = args.filename ?? defaultFilename(domain, format, template);
151
203
  const verbose = !!args.verbose;
152
204
  const removeXmlComments = !!args['remove-xml-comments'];
153
205
  if (!(await fileExists(poDir))) {
@@ -155,28 +207,34 @@ export const gettextCommand = {
155
207
  process.exitCode = 1;
156
208
  return;
157
209
  }
210
+ // REFUSED, not worked around. `--xml` and `--desktop` substitute into a
211
+ // template, and msgfmt rejects both without one (`--desktop requires a
212
+ // "--template template" specification`, exit 1). The old code fell back to a
213
+ // per-language loop that passed no template at all, so the fallback could only
214
+ // ever reproduce that same error one layer further down — with the user's
215
+ // `--format` reported as the thing that failed rather than the missing template.
216
+ if (format !== 'mo' && template === undefined) {
217
+ console.error(`[gjsify gettext] --format=${format} needs a template to substitute into: pass ` +
218
+ '`--template <file>`. msgfmt has no way to produce this format from .po files alone.' +
219
+ (format === 'xml'
220
+ ? ' Name it `*.metainfo.xml[.in]` — msgfmt --xml finds its ITS rules by filename pattern.'
221
+ : ''));
222
+ process.exitCode = 1;
223
+ return;
224
+ }
158
225
  try {
159
- if (format === 'xml' && metainfo) {
160
- await compileBulkXml({
161
- poDir,
162
- outDir,
163
- domain,
164
- template: metainfo,
165
- filename,
166
- removeXmlComments,
167
- verbose,
168
- });
226
+ if (format === 'mo') {
227
+ await compileCatalogues({ poDir, outDir, filename, verbose });
169
228
  }
170
229
  else {
171
- if (format === 'xml') {
172
- console.warn('[gjsify gettext] --format=xml without --metainfo: falling back to per-language XML files');
173
- }
174
- await compilePerLanguage({
230
+ await compileMerged({
175
231
  poDir,
176
232
  outDir,
177
- domain,
178
233
  format,
234
+ // Narrowed by the refusal above.
235
+ template: template,
179
236
  filename,
237
+ removeXmlComments,
180
238
  verbose,
181
239
  });
182
240
  }
@@ -6,6 +6,13 @@ interface OnboardOptions {
6
6
  registry?: string;
7
7
  otp?: string;
8
8
  concurrency: number;
9
+ 'write-concurrency': number;
10
+ packages?: string[];
11
+ include?: string[];
12
+ exclude?: string[];
13
+ access: string;
14
+ build: boolean;
15
+ verbose?: boolean;
9
16
  'dry-run'?: boolean;
10
17
  json?: boolean;
11
18
  yes?: boolean;