@swipt/cli 0.1.0-nightly.30 → 0.1.0-nightly.32

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 (2) hide show
  1. package/cli.mjs +95 -56
  2. package/package.json +2 -2
package/cli.mjs CHANGED
@@ -265,62 +265,104 @@ async function buildCommand(root) {
265
265
 
266
266
  const manifestEntry = /^(?:[^/]+\/)?manifest\.json$/;
267
267
 
268
- function sourceCommand(files, base, output, name, icon, image) {
269
- let shownIcon;
270
- if (icon !== undefined) {
271
- const [color, ...symbol] = icon.split("/");
272
- if (!color || !symbol.join("/")) {
273
- throw new UsageError("--icon takes <color>/<symbol>, such as blue/sparkles");
268
+ function loadSource(root) {
269
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
270
+ const config = manifest.source ?? {};
271
+ for (const field of ["name", "image", "url"]) {
272
+ if (config[field] !== undefined && typeof config[field] !== "string") {
273
+ throw new Error(`"source.${field}" in package.json must be a string`);
274
274
  }
275
- shownIcon = { color, symbol: symbol.join("/") };
275
+ }
276
+ const localizedNames = config.localizedNames ?? {};
277
+ if (localizedNames === null || typeof localizedNames !== "object" || !Object.values(localizedNames).every(name => typeof name === "string")) {
278
+ throw new Error('"source.localizedNames" in package.json must map languages to names');
279
+ }
280
+ if (config.icon !== undefined && (typeof config.icon?.color !== "string" || typeof config.icon?.symbol !== "string")) {
281
+ throw new Error('"source.icon" in package.json must have a color and a symbol');
276
282
  }
277
283
  let prefix;
278
- if (base !== undefined) {
284
+ if (config.url !== undefined) {
279
285
  try {
280
- prefix = new URL(base.endsWith("/") ? base : `${base}/`);
286
+ prefix = new URL(config.url.endsWith("/") ? config.url : `${config.url}/`);
281
287
  } catch {
282
- throw new UsageError(`--url ${JSON.stringify(base)} is not an absolute URL`);
288
+ throw new Error('"source.url" in package.json is not an absolute URL');
283
289
  }
284
290
  }
285
- const swiplets = new Map();
286
- for (const file of files) {
287
- const entry = execFileSync("unzip", ["-Z1", file], { encoding: "utf8" }).split("\n").filter(name => manifestEntry.test(name)).sort((a, b) => a.length - b.length)[0];
288
- if (entry === undefined) {
289
- throw new Error(`${file} has no manifest.json`);
290
- }
291
- const manifest = JSON.parse(execFileSync("unzip", ["-p", file, entry], { encoding: "utf8" }));
292
- if (typeof manifest.id !== "string" || !idPattern.test(manifest.id)) {
293
- throw new Error(`${file} has no valid swiplet id in its manifest`);
291
+ const workspaces = Array.isArray(manifest.workspaces) ? manifest.workspaces : manifest.workspaces?.packages ?? [];
292
+ return { root, config, localizedNames, prefix, workspaces };
293
+ }
294
+
295
+ function workspaceSwiplets(source) {
296
+ const files = [];
297
+ for (const pattern of source.workspaces) {
298
+ for (const directory of fs.globSync(pattern, { cwd: source.root }).sort()) {
299
+ const project = path.join(source.root, directory, "package.json");
300
+ if (!fs.existsSync(project)) {
301
+ continue;
302
+ }
303
+ const id = JSON.parse(fs.readFileSync(project, "utf8")).swiplet?.id;
304
+ if (typeof id !== "string") {
305
+ continue;
306
+ }
307
+ const built = path.join(source.root, directory, "dist", `${id}.swiplet`);
308
+ if (!fs.existsSync(built)) {
309
+ throw new Error(`${path.relative(source.root, built)} is missing; build ${directory} first`);
310
+ }
311
+ files.push(built);
294
312
  }
295
- if (swiplets.has(manifest.id)) {
296
- throw new Error(`${manifest.id} is both ${swiplets.get(manifest.id).file} and ${file}`);
313
+ }
314
+ return files;
315
+ }
316
+
317
+ function readSwiplet(file) {
318
+ const entry = execFileSync("unzip", ["-Z1", file], { encoding: "utf8" }).split("\n").filter(name => manifestEntry.test(name)).sort((a, b) => a.length - b.length)[0];
319
+ if (entry === undefined) {
320
+ throw new Error(`${file} has no manifest.json`);
321
+ }
322
+ const manifest = JSON.parse(execFileSync("unzip", ["-p", file, entry], { encoding: "utf8" }));
323
+ if (typeof manifest.id !== "string" || !idPattern.test(manifest.id)) {
324
+ throw new Error(`${file} has no valid swiplet id in its manifest`);
325
+ }
326
+ return { file, manifest, data: fs.readFileSync(file) };
327
+ }
328
+
329
+ function sourceCommand(root, given) {
330
+ const source = loadSource(root);
331
+ const files = given.length > 0 ? given.map(file => path.resolve(file)) : workspaceSwiplets(source);
332
+ if (files.length === 0) {
333
+ throw new Error("package.json lists no workspaces with swiplets; name the swiplets to list");
334
+ }
335
+ const swiplets = new Map();
336
+ for (const read of files.map(readSwiplet)) {
337
+ const { id } = read.manifest;
338
+ if (swiplets.has(id)) {
339
+ throw new Error(`${id} is both ${swiplets.get(id).file} and ${read.file}`);
297
340
  }
298
- const data = fs.readFileSync(file);
299
- swiplets.set(manifest.id, {
300
- file,
301
- entry: {
302
- ...manifest,
303
- url: prefix ? new URL(path.basename(file), prefix).href : path.basename(file),
304
- size: data.length,
305
- sha256: createHash("sha256").update(data).digest("hex"),
306
- },
307
- });
341
+ swiplets.set(id, read);
308
342
  }
309
- const listed = `${JSON.stringify(
310
- {
311
- ...(name ? { name } : {}),
312
- ...(shownIcon ? { icon: shownIcon } : {}),
313
- ...(image ? { image: prefix ? new URL(image, prefix).href : image } : {}),
314
- swiplets: [...swiplets.keys()].sort().map(id => swiplets.get(id).entry),
315
- },
316
- null,
317
- 2,
318
- )}\n`;
319
- if (output) {
320
- fs.writeFileSync(output, listed);
321
- } else {
322
- process.stdout.write(listed);
343
+ const image = source.config.image === undefined ? undefined : { name: path.basename(source.config.image), data: fs.readFileSync(path.resolve(root, source.config.image)) };
344
+ const located = name => (source.prefix ? new URL(name, source.prefix).href : name);
345
+ const dist = path.join(root, "dist");
346
+ fs.rmSync(dist, { recursive: true, force: true });
347
+ fs.mkdirSync(dist, { recursive: true });
348
+ const entries = [...swiplets.keys()].sort().map(id => {
349
+ const { manifest, data } = swiplets.get(id);
350
+ const name = `${id}.swiplet`;
351
+ fs.writeFileSync(path.join(dist, name), data);
352
+ return { ...manifest, url: located(name), size: data.length, sha256: createHash("sha256").update(data).digest("hex") };
353
+ });
354
+ if (image) {
355
+ fs.writeFileSync(path.join(dist, image.name), image.data);
323
356
  }
357
+ const index = {
358
+ ...(source.config.name ? { name: source.config.name } : {}),
359
+ ...(Object.keys(source.localizedNames).length > 0 ? { localizedNames: source.localizedNames } : {}),
360
+ ...(source.config.icon ? { icon: { color: source.config.icon.color, symbol: source.config.icon.symbol } } : {}),
361
+ ...(image ? { image: located(image.name) } : {}),
362
+ swiplets: entries,
363
+ };
364
+ fs.writeFileSync(path.join(dist, "swiplets.json"), `${JSON.stringify(index, null, 2)}\n`);
365
+ console.log(path.relative(process.cwd(), path.join(dist, "swiplets.json")));
324
366
  }
325
367
 
326
368
  const idOf = name => name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+/, "").slice(0, 128).replace(/[._-]+$/, "") || "swiplet";
@@ -417,24 +459,23 @@ function createCommand(directory) {
417
459
 
418
460
  const usage = `usage: swiplet create <directory>
419
461
  swiplet build
420
- swiplet source [--name <name>] [--icon <color>/<symbol>] [--image <file>] [--url <address>] [--output <file>] <swiplet>...
462
+ swiplet source [<swiplet>...]
421
463
 
422
464
  create make a swiplet project in a new or empty directory
423
465
  build bundle the project in this directory into dist/<swiplet.id>.swiplet
424
- source list built swiplets in a source; files are expected next to it unless --url says where they are
466
+ source gather the built swiplets of this directory's workspaces, or the given ones, into dist/
467
+ with a swiplets.json described by "source" in package.json
425
468
 
426
469
  Type-check a project with its "check" script.`;
427
470
 
428
- const commands = { create: 1, build: 0, source: -1 };
471
+ const commands = { create: 1, build: 0, source: null };
429
472
 
430
473
  class UsageError extends Error {}
431
474
 
432
475
  try {
433
476
  let parsed;
434
477
  try {
435
- parsed = parseArgs({ allowPositionals: true, options: {
436
- help: { type: "boolean", short: "h" }, name: { type: "string" }, icon: { type: "string" }, image: { type: "string" }, url: { type: "string" }, output: { type: "string" },
437
- } });
478
+ parsed = parseArgs({ allowPositionals: true, options: { help: { type: "boolean", short: "h" } } });
438
479
  } catch (error) {
439
480
  throw new UsageError(error.message);
440
481
  }
@@ -445,14 +486,12 @@ try {
445
486
  throw new UsageError("a command is missing");
446
487
  } else if (!Object.hasOwn(commands, command)) {
447
488
  throw new UsageError(`unknown command ${JSON.stringify(command)}`);
448
- } else if (commands[command] < 0 ? rest.length === 0 : rest.length !== commands[command]) {
449
- throw new UsageError(
450
- commands[command] === 0 ? `${command} takes no arguments` : commands[command] < 0 ? `${command} takes one or more swiplets` : `${command} takes one directory`,
451
- );
489
+ } else if (commands[command] !== null && rest.length !== commands[command]) {
490
+ throw new UsageError(commands[command] === 0 ? `${command} takes no arguments` : `${command} takes one directory`);
452
491
  } else if (command === "create") {
453
492
  createCommand(rest[0]);
454
493
  } else if (command === "source") {
455
- sourceCommand(rest, parsed.values.url, parsed.values.output, parsed.values.name, parsed.values.icon, parsed.values.image);
494
+ sourceCommand(process.cwd(), rest);
456
495
  } else {
457
496
  await buildCommand(process.cwd());
458
497
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swipt/cli",
3
- "version": "0.1.0-nightly.30",
3
+ "version": "0.1.0-nightly.32",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,6 @@
21
21
  "esbuild": "^0.28.2"
22
22
  },
23
23
  "peerDependencies": {
24
- "@swipt/api": "0.1.0-nightly.30"
24
+ "@swipt/api": "0.1.0-nightly.32"
25
25
  }
26
26
  }