@akanjs/cli 3.1.0-rc.0 → 3.1.0-rc.2

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 (76) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-q7a4srt6.js → CodeAgent-q50k4s45.js} +2 -2
  3. package/{CodeTui-ak23dxc9.js → CodeTui-62g35ztk.js} +1 -1
  4. package/TunnelShare-f1egrktx.js +13 -0
  5. package/{agent.command-f3gsc1gd.js → agent.command-adfd5299.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-3v2d53gd.js → application.command-hj3gv1rx.js} +52 -41
  8. package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-by10r9gf.js} +16 -30
  9. package/buildBatch.proc.js +22 -13
  10. package/{cloud.command-kk2rwkvw.js → cloud.command-k2fhaape.js} +60 -63
  11. package/cloud.script-z8p5dz7e.js +23 -0
  12. package/{code.command-30sca09d.js → code.command-kt5zgcc2.js} +6 -6
  13. package/{context.command-k7eqacq6.js → context.command-8fgn42jw.js} +16 -13
  14. package/context.runner-hnh0rg7m.js +24 -0
  15. package/{guideline.command-dtczrqpf.js → guideline.command-3yxxr2mm.js} +2 -2
  16. package/guidelines/conventions/conventions.instruction.md +30 -15
  17. package/guidelines/cssRule/cssRule.instruction.md +12 -6
  18. package/guidelines/mcpRule/mcpRule.instruction.md +32 -11
  19. package/guidelines/runtimeRule/runtimeRule.instruction.md +24 -15
  20. package/guidelines/transportRule/transportRule.instruction.md +28 -5
  21. package/incrementalBuilder.proc.js +9 -6
  22. package/{index-kjvrq3nc.js → index-0909wckt.js} +361 -282
  23. package/{index-p9ystjrp.js → index-20cebtth.js} +92 -52
  24. package/{index-55mynnqs.js → index-2etqq75t.js} +455 -206
  25. package/{index-6jjtnhkw.js → index-31ce9vhg.js} +51 -9
  26. package/{index-y8q31011.js → index-3reaaa1h.js} +42 -153
  27. package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
  28. package/{index-qt22xstj.js → index-58vvz603.js} +5 -3
  29. package/index-6726qr1m.js +668 -0
  30. package/index-8935h3n1.js +33 -0
  31. package/{index-nz15k0b6.js → index-8zdwpxef.js} +1 -1
  32. package/{index-c2yrsxdh.js → index-azh29dc8.js} +96 -320
  33. package/{index-c1x0be5j.js → index-bea2gwc6.js} +2 -2
  34. package/{index-v29746vb.js → index-e9prnmyt.js} +3 -3
  35. package/{index-tn4rpqp1.js → index-fjsv10hd.js} +73 -49
  36. package/index-g0vwzy9m.js +241 -0
  37. package/{index-c76k723e.js → index-h85cxfkg.js} +2 -2
  38. package/{index-rwq3ekqc.js → index-hnby7p9n.js} +7 -7
  39. package/{index-2etcv0sc.js → index-jwqtchha.js} +1 -1
  40. package/index-kc8z134d.js +248 -0
  41. package/index-nq045px5.js +1124 -0
  42. package/{index-fm46xrf1.js → index-nq2ja5e0.js} +3 -3
  43. package/{index-0bm04n5g.js → index-pyxn1b53.js} +10 -8
  44. package/{index-05g3jayg.js → index-rhbabmja.js} +1 -1
  45. package/{index-2mx692qq.js → index-rk9sdys3.js} +2 -2
  46. package/{index-7bymavqk.js → index-sby8fe1p.js} +1 -1
  47. package/{index-ctanya41.js → index-sqfdmb5b.js} +4 -4
  48. package/{index-2t4tqd1c.js → index-t7nws9mx.js} +3 -3
  49. package/{index-jyv387qw.js → index-tv8fjaba.js} +4 -4
  50. package/{index-gnyf1fqy.js → index-tz5evhdr.js} +2 -2
  51. package/{index-aprt7pkz.js → index-xpdgxsj5.js} +229 -1327
  52. package/{index-e0cvze1s.js → index-z507nzhg.js} +4 -4
  53. package/index.js +22 -22
  54. package/{library.command-gaga5bmq.js → library.command-8tw59xzj.js} +3 -3
  55. package/{localRegistry.command-9e84e32q.js → localRegistry.command-tf3dmznd.js} +12 -9
  56. package/{module.command-7vpf6db4.js → module.command-9vm8hffy.js} +5 -5
  57. package/{package.command-twmvvf69.js → package.command-erbxe2tz.js} +3 -3
  58. package/package.json +2 -2
  59. package/{page.command-5asq7fdf.js → page.command-jpavcj0p.js} +3 -3
  60. package/{pipeline.command-d0v38572.js → pipeline.command-b8rzvjvv.js} +220 -219
  61. package/{primitive.command-dyrs2fm8.js → primitive.command-sm32chzk.js} +6 -6
  62. package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-jamtx95x.js} +1 -1
  63. package/{quality.command-6emsqpbg.js → quality.command-pkd17n6y.js} +2 -2
  64. package/{repair.command-8t6aegv5.js → repair.command-8y4mq6hs.js} +5 -5
  65. package/{scalar.command-bnggy1x7.js → scalar.command-q8khmyx5.js} +4 -4
  66. package/{subspace.command-f02j5tge.js → subspace.command-51n0ykbx.js} +421 -362
  67. package/templates/app/env/env.client.type.ts +2 -3
  68. package/templates/app/page/styles.css.template +8 -0
  69. package/{tunnel.command-9x8nfn75.js → tunnel.command-7820vtxq.js} +7 -4
  70. package/{workflow.command-jx0wag5r.js → workflow.command-n5sv4wqz.js} +10 -10
  71. package/{workspace.command-cmj6mqyk.js → workspace.command-zdy60jh8.js} +33 -30
  72. package/TunnelShare-21mm6dr5.js +0 -9
  73. package/cloud.script-r7eyfey4.js +0 -20
  74. package/context.runner-hqd1ppqe.js +0 -21
  75. package/index-6w0rbr9d.js +0 -68
  76. package/index-r2tpeb3n.js +0 -392
@@ -10,7 +10,7 @@ import {
10
10
  LibExecutor,
11
11
  PkgExecutor,
12
12
  ModuleExecutor
13
- } from "./index-55mynnqs.js";
13
+ } from "./index-2etqq75t.js";
14
14
 
15
15
  // pkgs/@akanjs/devkit/commandDecorators/camelToKebabCase.ts
16
16
  var camelToKebabCase = (str) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -40,7 +40,7 @@ var getArgMetas = (command, key) => {
40
40
  const internalArgMetas = allArgMetas.filter((argMeta) => argMeta.type !== "Option");
41
41
  return [allArgMetas, argMetas, internalArgMetas];
42
42
  };
43
- var createInternalArgToken = (type) => ({ type });
43
+ var createInternalArgToken = (type, option) => option ? { type, option } : { type };
44
44
  var normalizePrimitiveArgType = (type) => {
45
45
  if (type === String)
46
46
  return "string";
@@ -54,6 +54,7 @@ var App = createInternalArgToken("App");
54
54
  var Apps = createInternalArgToken("Apps");
55
55
  var Lib = createInternalArgToken("Lib");
56
56
  var Sys = createInternalArgToken("Sys");
57
+ var SysOrOnlyApp = createInternalArgToken("Sys", { onlyApp: true });
57
58
  var Exec = createInternalArgToken("Exec");
58
59
  var Pkg = createInternalArgToken("Pkg");
59
60
  var Module = createInternalArgToken("Module");
@@ -160,6 +161,19 @@ class AppSelectionMemory {
160
161
  }
161
162
  }
162
163
 
164
+ // pkgs/@akanjs/devkit/commandDecorators/exitCodeError.ts
165
+ class ExitCodeError extends Error {
166
+ exitCode;
167
+ name = "ExitCodeError";
168
+ constructor(message, exitCode) {
169
+ super(message);
170
+ this.exitCode = exitCode;
171
+ }
172
+ static codeOf(error) {
173
+ return error instanceof ExitCodeError ? error.exitCode : null;
174
+ }
175
+ }
176
+
163
177
  // pkgs/@akanjs/devkit/commandDecorators/helpFormatter.ts
164
178
  import chalk from "chalk";
165
179
  var formatChoice = (choice) => typeof choice === "object" ? choice.label : choice.toString();
@@ -352,6 +366,7 @@ var select = (config, context) => prompts().then((m) => m.select(config, context
352
366
  var confirm = (config, context) => prompts().then((m) => m.confirm(config, context));
353
367
  var input = (config, context) => prompts().then((m) => m.input(config, context));
354
368
  var checkbox = (config, context) => prompts().then((m) => m.checkbox(config, context));
369
+ var argLabelOf = (argMeta) => argMeta.type === "Option" ? `option '--${camelToKebabCase(argMeta.name)}'` : `argument '${argMeta.name}'`;
355
370
  var assertEnumChoice = (argMeta, value) => {
356
371
  const enumChoices = argMeta.argsOption.enum;
357
372
  if (!enumChoices || typeof enumChoices === "function")
@@ -359,8 +374,12 @@ var assertEnumChoice = (argMeta, value) => {
359
374
  const choices = normalizeEnumChoices(enumChoices);
360
375
  if (choices.some((choice) => String(choice.value) === String(value)))
361
376
  return;
362
- const label = argMeta.type === "Option" ? `option '--${camelToKebabCase(argMeta.name)}'` : `argument '${argMeta.name}'`;
363
- throw new Error(`${label} argument '${String(value)}' is invalid. Allowed choices are ${choices.map((choice) => choice.name).join(", ")}.`);
377
+ throw new Error(`${argLabelOf(argMeta)} argument '${String(value)}' is invalid. Allowed choices are ${choices.map((choice) => choice.name).join(", ")}.`);
378
+ };
379
+ var assertPromptable = (problem, { choices = [], remedy = "pass it on the command line" } = {}) => {
380
+ if (process.stdin.isTTY)
381
+ return;
382
+ throw new Error(`${problem}: stdin is not a terminal to prompt on, so ${remedy}${choices.length ? ` (choices: ${choices.join(", ")})` : ""}.`);
364
383
  };
365
384
  var inputMessageOf = ({ name, argsOption: { desc, example, ask } }) => ask ? `${ask}: ` : desc ? `${desc}: ` : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
366
385
  var getOptionValue = async (argMeta, opt, context) => {
@@ -379,8 +398,11 @@ var getOptionValue = async (argMeta, opt, context) => {
379
398
  const choices = normalizeEnumChoices((typeof enumChoices === "function" ? await enumChoices(context) : enumChoices) ?? []);
380
399
  if (choices.length === 1)
381
400
  return choices[0]?.value;
401
+ assertPromptable(`Missing ${argLabelOf(argMeta)}`, { choices: choices.map((choice) => choice.value) });
382
402
  return await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
383
- } else if (type === "boolean") {
403
+ }
404
+ assertPromptable(`Missing ${argLabelOf(argMeta)}`);
405
+ if (type === "boolean") {
384
406
  const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
385
407
  return await confirm({ message });
386
408
  } else
@@ -395,6 +417,10 @@ var getArgumentValue = async (argMeta, value) => {
395
417
  return defaultValue;
396
418
  else if (nullable)
397
419
  return null;
420
+ const { enum: enumChoices } = argMeta.argsOption;
421
+ assertPromptable(`Missing ${argLabelOf(argMeta)}`, {
422
+ choices: enumChoices && typeof enumChoices !== "function" ? normalizeEnumChoices(enumChoices).map(({ value }) => value) : []
423
+ });
398
424
  return convertArgValue(await input({ message: inputMessageOf(argMeta) }), type ?? "string");
399
425
  };
400
426
  var assignCommandContext = (context, argMeta, value) => {
@@ -445,6 +471,7 @@ var getAppsArgumentValue = async (value, workspace) => {
445
471
  }
446
472
  if (appNames.length === 1 && appNames[0])
447
473
  return [AppExecutor.from(workspace, appNames[0])];
474
+ assertPromptable("Missing apps", { choices: [...appNames, "all"] });
448
475
  const remembered = new Set(await AppSelectionMemory.read(workspace.workspaceRoot));
449
476
  const picked = await checkbox({
450
477
  message: "Select the apps to run (space to toggle, enter to confirm)",
@@ -459,6 +486,7 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
459
486
  return workspace;
460
487
  const sysType = argMeta.type.toLowerCase();
461
488
  const [appNames, libNames, pkgNames] = await workspace.getExecs();
489
+ const problemOf = (kind) => value ? `Unknown ${kind} '${value}'` : `Missing ${kind}`;
462
490
  if (sysType === "sys" || sysType === "exec") {
463
491
  const pkgChoices = sysType === "exec" ? pkgNames : [];
464
492
  const execOf = (name) => {
@@ -473,6 +501,11 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
473
501
  const found = value ? execOf(value) : null;
474
502
  if (found)
475
503
  return found;
504
+ if (!value && argMeta.option?.onlyApp && appNames.length === 1 && appNames[0])
505
+ return AppExecutor.from(workspace, appNames[0]);
506
+ assertPromptable(problemOf(sysType === "exec" ? "app, lib or pkg" : "app or lib"), {
507
+ choices: [...appNames, ...libNames, ...pkgChoices]
508
+ });
476
509
  const name = await select({
477
510
  message: sysType === "exec" ? `Select the App or Lib or Pkg name` : `Select the App or Lib name`,
478
511
  choices: [...appNames, ...libNames, ...pkgChoices]
@@ -486,17 +519,20 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
486
519
  return AppExecutor.from(workspace, value);
487
520
  if (!value && appNames.length === 1 && appNames[0])
488
521
  return AppExecutor.from(workspace, appNames[0]);
522
+ assertPromptable(problemOf("app"), { choices: appNames });
489
523
  const appName = await select({ message: `Select the ${sysType} name`, choices: appNames });
490
524
  return AppExecutor.from(workspace, appName);
491
525
  } else if (sysType === "lib") {
492
526
  if (value && libNames.includes(value))
493
527
  return LibExecutor.from(workspace, value);
528
+ assertPromptable(problemOf("lib"), { choices: libNames });
494
529
  const libName = await select({ message: `Select the ${sysType} name`, choices: libNames });
495
530
  return LibExecutor.from(workspace, libName);
496
531
  } else if (sysType === "pkg") {
497
532
  const pkgs = await workspace.getPkgs();
498
533
  if (value && pkgs.includes(value))
499
534
  return PkgExecutor.from(workspace, value);
535
+ assertPromptable(problemOf("pkg"), { choices: pkgs });
500
536
  const pkgName = await select({ message: `Select the ${sysType} name`, choices: pkgs });
501
537
  return PkgExecutor.from(workspace, pkgName);
502
538
  } else if (sysType === "module") {
@@ -519,6 +555,7 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
519
555
  } else
520
556
  throw new Error(`Invalid system name: ${sysName}`);
521
557
  }
558
+ assertPromptable(problemOf("module"));
522
559
  const { type, name } = await select({
523
560
  message: `select the App or Lib name`,
524
561
  choices: [
@@ -540,7 +577,7 @@ var runCommands = async (...commands) => {
540
577
  let failureExitCode = 1;
541
578
  process.on("unhandledRejection", (error) => {
542
579
  printCliError(error);
543
- process.exit(failureExitCode);
580
+ process.exit(ExitCodeError.codeOf(error) ?? failureExitCode);
544
581
  });
545
582
  const __dirname2 = getDirname2(import.meta.url);
546
583
  const packageJsonCandidates = [`${path2.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
@@ -647,7 +684,7 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
647
684
  }
648
685
  await program.parseAsync(process.argv).catch((error) => {
649
686
  printCliError(error);
650
- process.exit(failureExitCode);
687
+ process.exit(ExitCodeError.codeOf(error) ?? failureExitCode);
651
688
  });
652
689
  };
653
690
 
@@ -671,7 +708,12 @@ class TargetBuilder {
671
708
  return this.#add(argMetaOf("Option", name, type, argsOption, this.#args.length));
672
709
  }
673
710
  with(...tokens) {
674
- return this.#add(...tokens.map((token, offset) => ({ key: "", idx: this.#args.length + offset, type: token.type })));
711
+ return this.#add(...tokens.map((token, offset) => ({
712
+ key: "",
713
+ idx: this.#args.length + offset,
714
+ type: token.type,
715
+ ...token.option ? { option: token.option } : {}
716
+ })));
675
717
  }
676
718
  exec(handler) {
677
719
  return {
@@ -718,4 +760,4 @@ function command(refName, depsOrBuilder, builder) {
718
760
  }
719
761
  return CommandBase;
720
762
  }
721
- export { getTargetMetas, getTargetCommandNames, App, Apps, Lib, Sys, Exec, Pkg, Module, Workspace, CommandContainer, runner, script, runCommands, command };
763
+ export { getTargetMetas, getTargetCommandNames, App, Apps, Lib, Sys, SysOrOnlyApp, Exec, Pkg, Module, Workspace, CommandContainer, runner, script, ExitCodeError, assertPromptable, runCommands, command };
@@ -3,24 +3,20 @@ import {
3
3
  getDefaultHostConfig,
4
4
  GlobalConfig,
5
5
  CloudApi,
6
- UpdatePackUpload
7
- } from "./index-r2tpeb3n.js";
8
- import {
9
- runner
10
- } from "./index-6jjtnhkw.js";
6
+ CloudUpdates,
7
+ PipelineSecrets
8
+ } from "./index-6726qr1m.js";
11
9
  import {
12
10
  WorkspaceExecutor,
13
11
  AppExecutor
14
- } from "./index-55mynnqs.js";
12
+ } from "./index-2etqq75t.js";
15
13
  import {
16
- NativeApp
17
- } from "./index-kjvrq3nc.js";
14
+ runner,
15
+ assertPromptable
16
+ } from "./index-31ce9vhg.js";
18
17
  import {
19
18
  SubspaceConfig
20
- } from "./index-ww9bhp67.js";
21
- import {
22
- resolveMobileTargets
23
- } from "./index-q958tqqg.js";
19
+ } from "./index-4pf8g7ff.js";
24
20
  import {
25
21
  openBrowser
26
22
  } from "./index-9dp031by.js";
@@ -657,11 +653,9 @@ class PlatformTestRun {
657
653
  }
658
654
 
659
655
  // pkgs/@akanjs/cli/cloud/cloud.runner.ts
660
- import { rm } from "fs/promises";
661
- import os3 from "os";
662
656
  import path7 from "path";
663
- import { confirm, input, password, select } from "@inquirer/prompts";
664
- import { Logger, PipelineNames, sleep } from "akanjs/common";
657
+ import { confirm, input, select } from "@inquirer/prompts";
658
+ import { Logger, sleep } from "akanjs/common";
665
659
  import chalk2 from "chalk";
666
660
  import * as QRcode from "qrcode";
667
661
 
@@ -766,6 +760,9 @@ class CloudRunner extends runner("cloud") {
766
760
  return { name: `${name} (${this.#getSshTarget(config)}${config.port ? `:${config.port}` : ""})`, value: name };
767
761
  }
768
762
  async#getRemoteEnvServerWithUsername() {
763
+ assertPromptable("The env archive goes over scp to a remote server picked here, as AKAN_WORKSPACE_ID is not set", {
764
+ remedy: "set AKAN_WORKSPACE_ID in the workspace .env to use Akan Cloud, or run it once in a terminal"
765
+ });
769
766
  const remoteServer = await this.#selectRemoteEnvServer();
770
767
  if (remoteServer.config.username)
771
768
  return remoteServer;
@@ -891,6 +888,9 @@ ${run.format(results)}
891
888
  const reason = result.infraError ?? "failing tests";
892
889
  if (!interactive)
893
890
  continue;
891
+ assertPromptable(`${result.platform} did not pass (${reason}) \u2014 ${run.logDir}`, {
892
+ remedy: "leave it out of --platforms to publish without it, or run it in a terminal to be asked"
893
+ });
894
894
  const proceed = await confirm({
895
895
  message: `${result.platform} did not pass (${reason}). Continue without it?`,
896
896
  default: false
@@ -909,6 +909,9 @@ ${run.format(results)}
909
909
  return results;
910
910
  }
911
911
  async#askWindowsTestTarget() {
912
+ assertPromptable("No Windows test target is configured", {
913
+ remedy: "run it once in a terminal, which saves the target to ~/.akan/config.json"
914
+ });
912
915
  Logger.info("No Windows test target is configured yet; it is saved to ~/.akan/config.json once entered.");
913
916
  const required = (value) => !!value.trim();
914
917
  const host = await this.#ask("Windows host (ip or name): ", required);
@@ -944,11 +947,15 @@ ${run.format(results)}
944
947
  for (const library of akanPkgs) {
945
948
  const packageJson = await workspace.readJson(`pkgs/${library}/package.json`);
946
949
  const newPackageJson = this.#normalizeAkanPackageJson(packageJson, library, nextVersion);
947
- await workspace.writeFile(`pkgs/${library}/package.json`, JSON.stringify(newPackageJson, null, 2));
950
+ await workspace.writeJson(`pkgs/${library}/package.json`, newPackageJson);
948
951
  const distPackageJson = await workspace.readJson(`dist/pkgs/${library}/package.json`);
949
952
  const newDistPackageJson = this.#normalizeAkanPackageJson(distPackageJson, library, nextVersion);
950
953
  await workspace.writeJson(`dist/pkgs/${library}/package.json`, newDistPackageJson);
951
954
  }
955
+ if (confirmPublish)
956
+ assertPromptable(`Publishing ${akanPkgs.join(", ")}@${nextVersion} asks for a confirmation`, {
957
+ remedy: "run it in a terminal"
958
+ });
952
959
  if (confirmPublish && !await confirm({ message: "Are you sure you want to deploy the libraries?" })) {
953
960
  Logger.error("Deployment cancelled");
954
961
  return;
@@ -1014,7 +1021,7 @@ ${run.format(results)}
1014
1021
  async downloadEnv(cloudApi, workspace, workspaceId) {
1015
1022
  await workspace.mkdir("local");
1016
1023
  const localPath = await cloudApi.downloadEnv(workspaceId).catch((error) => {
1017
- throw CloudRunner.#refusal(error, "downloading its env archive takes the project owner's own `akan login`");
1024
+ throw CloudApi.refusal(error, "downloading its env archive takes the project owner's own `akan login`");
1018
1025
  });
1019
1026
  const relativePath = path7.relative(workspace.workspaceRoot, localPath).split(path7.sep).join("/");
1020
1027
  await workspace.spawn("tar", ["-xf", relativePath], { cwd: workspace.workspaceRoot });
@@ -1023,7 +1030,7 @@ ${run.format(results)}
1023
1030
  async uploadEnv(cloudApi, workspaceId, filePath) {
1024
1031
  const file = new File([Bun.file(filePath)], path7.basename(filePath));
1025
1032
  await cloudApi.uploadEnv(workspaceId, file).catch((error) => {
1026
- throw CloudRunner.#refusal(error, "an env archive is uploaded by a project member signed in with `akan login`");
1033
+ throw CloudApi.refusal(error, "an env archive is uploaded by a project member signed in with `akan login`");
1027
1034
  });
1028
1035
  }
1029
1036
  async downloadEnvByScp(workspace) {
@@ -1096,7 +1103,7 @@ ${run.format(results)}
1096
1103
  }));
1097
1104
  return secretPaths.flat();
1098
1105
  }
1099
- async secretProject(workspace, subspace) {
1106
+ async projectOf(workspace, subspace, appName) {
1100
1107
  if (!subspace) {
1101
1108
  const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
1102
1109
  if (!workspaceId)
@@ -1109,148 +1116,30 @@ ${run.format(results)}
1109
1116
  const [declaration] = config.select([subspace]);
1110
1117
  if (!declaration?.workspaceId)
1111
1118
  throw new Error(`Subspace "${subspace}" declares no workspaceId in ${SubspaceConfig.source}.`);
1119
+ if (appName && !declaration.apps.includes(appName))
1120
+ throw new Error(`Subspace "${subspace}" declares no app ${appName} in ${SubspaceConfig.source}; it serves ${declaration.apps.join(", ")}.`);
1112
1121
  return declaration.workspaceId;
1113
1122
  }
1114
1123
  async readSecretValue(name, file) {
1115
- const raw = file ? await Bun.file(file).text() : process.stdin.isTTY ? await password({ message: `Value of ${name} (hidden; it can never be read back)`, mask: "*" }) : await Bun.stdin.text();
1116
- const value = raw.replace(/\r?\n$/, "");
1117
- if (!value)
1118
- throw new Error(`The value of ${name} is empty.`);
1119
- const bytes = new TextEncoder().encode(value).length;
1120
- if (bytes > CloudRunner.#secretBytes)
1121
- throw new Error(`The value of ${name} is ${bytes} bytes, over ${CloudRunner.#secretBytes}.`);
1122
- return value;
1123
- }
1124
- async uploadSecret(cloudApi, devProjectId, { name, branch, value }) {
1125
- CloudRunner.#assertSecretName(name);
1126
- CloudRunner.#assertSecretBranch(branch);
1127
- const saved = await cloudApi.uploadSecret(devProjectId, { scope: branch ?? "*", name, value }).catch((error) => {
1128
- throw CloudRunner.#refusal(error, "a secret is written by a project member signed in with `akan login`", {
1129
- unavailable: CloudRunner.#secretsUnavailable
1130
- });
1131
- });
1132
- Logger.rawLog(`Saved ${saved.name} for ${saved.scope === "*" ? "every branch" : `branch ${saved.scope}`}. Builds that start from now on use it.`);
1124
+ return await PipelineSecrets.readValue(name, file);
1133
1125
  }
1134
- async listSecrets(cloudApi, devProjectId, branch) {
1135
- CloudRunner.#assertSecretBranch(branch);
1136
- const listed = await cloudApi.listSecrets(devProjectId).catch((error) => {
1137
- throw CloudRunner.#refusal(error, "the secrets are listed for a project member", {
1138
- unavailable: CloudRunner.#secretsUnavailable
1139
- });
1140
- });
1141
- const secrets = listed.filter((secret) => !branch || secret.scope === "*" || secret.scope === branch);
1142
- Logger.rawLog(CloudRunner.formatSecrets(secrets));
1143
- }
1144
- async removeSecret(cloudApi, devProjectId, { name, branch }) {
1145
- CloudRunner.#assertSecretName(name);
1146
- CloudRunner.#assertSecretBranch(branch);
1147
- const removed = await cloudApi.removeSecret(devProjectId, { scope: branch ?? "*", name }).catch((error) => {
1148
- throw CloudRunner.#refusal(error, "a secret is removed by a project member signed in with `akan login`", {
1149
- unavailable: CloudRunner.#secretsUnavailable
1150
- });
1151
- });
1152
- Logger.rawLog(removed ? `Removed ${name} for ${branch ? `branch ${branch}` : "every branch"}.` : `No ${name} for ${branch ? `branch ${branch}` : "every branch"} to remove.`);
1126
+ async uploadSecret(cloudApi, devProjectId, secret) {
1127
+ await new PipelineSecrets(cloudApi, devProjectId).upload(secret);
1153
1128
  }
1154
- static async#tarPack(pack) {
1155
- const archive = path7.join(os3.tmpdir(), `akan-pack-${pack.app}-${pack.target}-${pack.platform}-${process.pid}.tgz`);
1156
- const tar = Bun.spawn(["tar", "-czf", archive, "-C", pack.out, "."], { stdout: "ignore", stderr: "pipe" });
1157
- if (await tar.exited !== 0)
1158
- throw new Error(`tar of ${pack.out} failed: ${(await new Response(tar.stderr).text()).trim()}`);
1159
- return archive;
1129
+ async listSecrets(cloudApi, devProjectId, branch) {
1130
+ await new PipelineSecrets(cloudApi, devProjectId).list(branch);
1160
1131
  }
1161
- async pushUpdate(cloudApi, devProjectId, pack, { channel, notes }) {
1162
- const archive = await CloudRunner.#tarPack(pack);
1163
- const target = channel ?? pack.channel;
1164
- try {
1165
- const release = await new UpdatePackUpload(cloudApi, devProjectId).send(archive, { appName: pack.app, target: pack.target, platform: pack.platform, channel: target, notes }).catch((error) => {
1166
- throw CloudRunner.#refusal(error, "a pack is uploaded by a project member signed in with `akan login`");
1167
- });
1168
- const label = `${pack.app} ${pack.target} ${pack.platform} on channel ${target}: release ${release.id}`;
1169
- if (release.status === "rejected") {
1170
- process.exitCode = 2;
1171
- Logger.error(`The project rejected ${label}:
1172
- ${(release.problems ?? []).map((problem) => ` - ${problem}`).join(`
1173
- `)}`);
1174
- } else if (release.status === "published")
1175
- Logger.rawLog(`Published ${label} (${pack.files} files, native API ${pack.nativeApi}).`);
1176
- else
1177
- Logger.rawLog(`Staged ${label} (${pack.files} files, native API ${pack.nativeApi}). Publish it from the project's release screen.`);
1178
- return release;
1179
- } finally {
1180
- await rm(archive, { force: true });
1181
- }
1132
+ async removeSecret(cloudApi, devProjectId, secret) {
1133
+ await new PipelineSecrets(cloudApi, devProjectId).remove(secret);
1182
1134
  }
1183
- async registerBinary(cloudApi, devProjectId, app, pack, { distribution, env }) {
1184
- const [mobileTarget] = await resolveMobileTargets(app, pack.target);
1185
- if (!mobileTarget)
1186
- throw new Error(`Native target '${pack.target}' was not found.`);
1187
- const bundlePath = path7.join(new NativeApp(app, mobileTarget).outDir(pack.platform), "bundle.json");
1188
- const bundleFile = Bun.file(bundlePath);
1189
- if (!await bundleFile.exists())
1190
- throw new Error(`No store build at ${bundlePath}: run \`akan release-${pack.platform} ${app.name} --target ${pack.target} --env ${env}\` first.`);
1191
- const bundle = await bundleFile.json();
1192
- if (!bundle.updates)
1193
- throw new Error(`The store build at ${bundlePath} takes no updates: give the target native.updates.`);
1194
- if (bundle.nativeApi?.hash !== pack.nativeApi)
1195
- throw new Error(`The store build at ${bundlePath} provides native API ${bundle.nativeApi?.hash}, and this commit's pack needs ${pack.nativeApi}: build the store binary from this commit.`);
1196
- const archive = await CloudRunner.#tarPack(pack);
1197
- try {
1198
- const binary = await cloudApi.registerAppBinary(devProjectId, {
1199
- appName: pack.app,
1200
- target: pack.target,
1201
- distribution,
1202
- bundle: bundleFile,
1203
- pack: Bun.file(archive),
1204
- ...bundle.updates.channel ? { channel: bundle.updates.channel } : {}
1205
- }).catch((error) => {
1206
- throw CloudRunner.#refusal(error, "a binary is registered by a project member signed in with `akan login`");
1207
- });
1208
- Logger.rawLog(`Registered ${pack.app} ${pack.target} ${pack.platform} (${distribution}): binary ${binary.id}, native API ${pack.nativeApi}.`);
1209
- return binary;
1210
- } finally {
1211
- await rm(archive, { force: true });
1212
- }
1135
+ async readPack(app, dir, flags) {
1136
+ return await CloudUpdates.readPack(app, dir, flags);
1213
1137
  }
1214
- static formatSecrets(secrets) {
1215
- if (!secrets.length)
1216
- return "No pipeline secrets.";
1217
- const rows = [...secrets].sort((a, b) => a.name.localeCompare(b.name) || a.scope.localeCompare(b.scope)).map((secret) => [
1218
- secret.name,
1219
- secret.scope === "*" ? "every branch" : secret.scope,
1220
- secret.updatedAt,
1221
- secret.updatedBy
1222
- ]);
1223
- const header = ["NAME", "SCOPE", "UPDATED", "BY"];
1224
- const widths = header.map((title, column) => Math.max(title.length, ...rows.map((row) => row[column]?.length ?? 0)));
1225
- return [header, ...rows].map((row) => row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd()).join(`
1226
- `);
1138
+ async pushUpdate(cloudApi, devProjectId, pack, options) {
1139
+ return await new CloudUpdates(cloudApi, devProjectId).push(pack, options);
1227
1140
  }
1228
- static #secretBytes = 64 * 1024;
1229
- static #secretBranches = ["debug", "develop", "main"];
1230
- static #secretsUnavailable = "The server is not configured for pipeline secrets yet.";
1231
- static #assertSecretName(name) {
1232
- const problem = PipelineNames.secretNameError(name);
1233
- if (problem)
1234
- throw new Error(problem);
1235
- }
1236
- static #assertSecretBranch(branch) {
1237
- if (branch !== null && !CloudRunner.#secretBranches.includes(branch))
1238
- throw new Error(`--branch ${branch} is not a deployed branch: ${CloudRunner.#secretBranches.join(", ")}.`);
1239
- }
1240
- static #refusal(error, who, { unavailable } = {}) {
1241
- const message = error instanceof Error ? error.message : String(error);
1242
- const status = CloudApi.statusOf(error);
1243
- if (status === 401)
1244
- return new Error("The server did not accept your sign-in: run `akan login`, then try again.");
1245
- if (status === 403)
1246
- return new Error(`The project refused: ${who}.`);
1247
- if (status === 400) {
1248
- const key = /"error"\s*:\s*"([^"]+)"/.exec(message)?.[1];
1249
- return new Error(`The project refused the request${key ? `: ${key}` : ""}.`);
1250
- }
1251
- if (status === 503 && unavailable)
1252
- return new Error(unavailable);
1253
- return error;
1141
+ async registerBinary(cloudApi, devProjectId, app, pack, options) {
1142
+ return await new CloudUpdates(cloudApi, devProjectId).registerBinary(app, pack, options);
1254
1143
  }
1255
1144
  async#syncSecretGitignore(workspace, appNames) {
1256
1145
  const patterns = (await Promise.all(appNames.map(async (appName) => {
@@ -55,7 +55,7 @@ class SubspaceConfig {
55
55
  assertPushable(branch) {
56
56
  if (this.pushableBranches.includes(branch))
57
57
  return;
58
- throw new Error(`Branch "${branch}" is not pushable. ${SubspaceConfig.source} allows: ${this.pushableBranches.join(", ")}`);
58
+ throw new Error(`Branch "${branch}" is not pushable. \`subspace.pushableBranches\` in the workspace root akan.config.ts allows: ${this.pushableBranches.join(", ")}`);
59
59
  }
60
60
  }
61
61
 
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AppInfo
4
- } from "./index-55mynnqs.js";
4
+ } from "./index-2etqq75t.js";
5
5
 
6
6
  // pkgs/@akanjs/devkit/slicePlanner.ts
7
7
  class SlicePlanner {
@@ -33,6 +33,9 @@ class SlicePlanner {
33
33
  }
34
34
  return found ? peers : null;
35
35
  }
36
+ static rootEntriesOf(rootFiles) {
37
+ return [...new Set(rootFiles.map((file) => file.split("/")[0] ?? file))].sort();
38
+ }
36
39
  static typesPackageOf(dependency) {
37
40
  return dependency.startsWith("@") ? `@types/${dependency.slice(1).replace("/", "__")}` : `@types/${dependency}`;
38
41
  }
@@ -131,7 +134,6 @@ class SlicePlanner {
131
134
  };
132
135
  }
133
136
  }
134
- var rootEntriesOf = (rootFiles) => [...new Set(rootFiles.map((file) => file.split("/")[0] ?? file))].sort();
135
137
  function formatSlicePlan(plan) {
136
138
  const libCounts = Object.entries(plan.libFiles).map(([lib, files]) => ` - libs/${lib}: ${files.length} files`);
137
139
  const sections = [
@@ -139,7 +141,7 @@ function formatSlicePlan(plan) {
139
141
  `app: ${plan.app} (apps/${plan.app}: ${plan.appFiles.length} files)`,
140
142
  `libs: ${plan.libs.length ? plan.libs.join(", ") : "(none)"}`,
141
143
  ...libCounts,
142
- `workspace shell: ${plan.rootFiles.length} files across ${rootEntriesOf(plan.rootFiles).join(", ")}`,
144
+ `workspace shell: ${plan.rootFiles.length} files across ${SlicePlanner.rootEntriesOf(plan.rootFiles).join(", ")}`,
143
145
  "",
144
146
  `Root dependencies this slice does not use (${plan.unusedDependencies.length}) \u2014 pruned:`,
145
147
  "",