@akanjs/cli 3.1.0-rc.1 → 3.1.0-rc.11

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 (98) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-kyprmy06.js → CodeAgent-rkjyxgec.js} +2 -2
  3. package/{CodeTui-21mtze3j.js → CodeTui-h7pyj6xa.js} +1 -1
  4. package/TunnelShare-661cxz5e.js +14 -0
  5. package/{agent.command-f3gsc1gd.js → agent.command-3qxf4xcj.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-5s780vnz.js → application.command-m1kcd7jf.js} +54 -42
  8. package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-dj0xkrep.js} +26 -33
  9. package/buildBatch.proc.js +24 -15
  10. package/{cloud.command-yj6xb2a7.js → cloud.command-9cp1t0f5.js} +60 -67
  11. package/cloud.script-dgstqt1m.js +24 -0
  12. package/{code.command-3wkn4ja5.js → code.command-0ad6gw2a.js} +6 -6
  13. package/{context.command-9jjn3gcw.js → context.command-0qx447mb.js} +17 -13
  14. package/context.runner-1vsjztjg.js +25 -0
  15. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  16. package/{guideline.command-dtczrqpf.js → guideline.command-cv5bmrx2.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +3 -3
  18. package/guidelines/conventions/conventions.instruction.md +457 -1071
  19. package/guidelines/cssRule/cssRule.instruction.md +28 -7
  20. package/guidelines/fieldRule/fieldRule.instruction.md +62 -0
  21. package/guidelines/framework/framework.instruction.md +4 -7
  22. package/guidelines/mcpRule/mcpRule.instruction.md +43 -20
  23. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  24. package/guidelines/modelStore/modelStore.instruction.md +9 -0
  25. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  26. package/guidelines/queryRule/queryRule.instruction.md +44 -11
  27. package/guidelines/runtimeRule/runtimeRule.instruction.md +113 -17
  28. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  29. package/guidelines/transportRule/transportRule.instruction.md +135 -13
  30. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  31. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +68 -7
  32. package/incrementalBuilder.proc.js +10 -7
  33. package/index-192ha4xh.js +1173 -0
  34. package/{index-c76k723e.js → index-1p98exm2.js} +2 -2
  35. package/{index-rwq3ekqc.js → index-1xj2t7cb.js} +7 -7
  36. package/{index-rtct7d43.js → index-20bxxyvn.js} +80 -80
  37. package/{index-qt22xstj.js → index-35hc95h5.js} +5 -3
  38. package/{index-8r4d7cmt.js → index-4mhy6qme.js} +4 -4
  39. package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
  40. package/{index-2mx692qq.js → index-5edzv8fn.js} +5 -5
  41. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  42. package/index-62md4dwy.js +260 -0
  43. package/index-665zp9rs.js +271 -0
  44. package/{index-2etcv0sc.js → index-86jt2a0e.js} +1 -1
  45. package/{index-krq7btbt.js → index-8fp3b47h.js} +419 -418
  46. package/{index-c2yrsxdh.js → index-9z426msb.js} +109 -328
  47. package/{index-ctanya41.js → index-awbdfcf2.js} +4 -4
  48. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  49. package/{index-rc5rwyps.js → index-bspm21y1.js} +71 -60
  50. package/index-cpb3nvma.js +667 -0
  51. package/{index-jx4bzvwg.js → index-d47ypebv.js} +97 -55
  52. package/{index-ae5pwhgs.js → index-d72hfmdm.js} +15 -13
  53. package/{index-e0cvze1s.js → index-fmzcs8v6.js} +4 -4
  54. package/{index-8ggztp8k.js → index-fyq1z6kt.js} +68 -201
  55. package/{index-1w2qaee2.js → index-g1mbppa9.js} +331 -259
  56. package/{index-gnyf1fqy.js → index-j237xtew.js} +6 -6
  57. package/{index-fm46xrf1.js → index-j4mkhz93.js} +3 -3
  58. package/{index-6jjtnhkw.js → index-jw1zm273.js} +74 -34
  59. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  60. package/index-mdjc93e1.js +33 -0
  61. package/{index-c1x0be5j.js → index-pydbnfez.js} +2 -2
  62. package/{index-56d2g8ms.js → index-qwa5k1j8.js} +1 -1
  63. package/{index-v29746vb.js → index-tamvyvj3.js} +3 -3
  64. package/{index-05g3jayg.js → index-trpasv9q.js} +260 -259
  65. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  66. package/{index-55mynnqs.js → index-va06nr10.js} +1434 -1173
  67. package/index-vk0pa0mn.js +240 -0
  68. package/{index-aprt7pkz.js → index-w44cy4vs.js} +2271 -3369
  69. package/index.js +22 -22
  70. package/{library.command-gaga5bmq.js → library.command-erp2f56n.js} +3 -3
  71. package/{localRegistry.command-vsreyy42.js → localRegistry.command-y176p558.js} +15 -11
  72. package/{module.command-7vpf6db4.js → module.command-gs2r615e.js} +5 -5
  73. package/{package.command-twmvvf69.js → package.command-j10r76ax.js} +4 -4
  74. package/package.json +21 -21
  75. package/{page.command-5asq7fdf.js → page.command-4h4a8pnh.js} +3 -3
  76. package/{pipeline.command-d0v38572.js → pipeline.command-a2nrk26m.js} +221 -220
  77. package/{primitive.command-dyrs2fm8.js → primitive.command-w6t0asqz.js} +6 -6
  78. package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-f534enqj.js} +1 -1
  79. package/{quality.command-6emsqpbg.js → quality.command-77x7vbbx.js} +4 -4
  80. package/{repair.command-8t6aegv5.js → repair.command-y6sstev7.js} +5 -5
  81. package/{scalar.command-bnggy1x7.js → scalar.command-20b8cy0k.js} +4 -4
  82. package/{subspace.command-md2teygq.js → subspace.command-5c7yasd7.js} +424 -364
  83. package/templates/appSample/lib/task/Task.Util.tsx +7 -7
  84. package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
  85. package/templates/appSample/ui/QuantityControl.tsx +2 -2
  86. package/templates/crudSinglePage/page.tsx +1 -1
  87. package/templates/lib/cnst.ts +0 -1
  88. package/templates/workspaceRoot/.gitignore.template +23 -52
  89. package/templates/workspaceRoot/biome.json.template +1 -1
  90. package/{tunnel.command-jx3tdm2d.js → tunnel.command-2kw8vt9g.js} +8 -4
  91. package/{workflow.command-jx0wag5r.js → workflow.command-2djwmpqf.js} +10 -10
  92. package/{workspace.command-yxd7rr7g.js → workspace.command-6qfgq4b4.js} +38 -34
  93. package/TunnelShare-q4jq08qg.js +0 -9
  94. package/cloud.script-h6pvcpa2.js +0 -20
  95. package/context.runner-atcrzz5b.js +0 -21
  96. package/index-6w0rbr9d.js +0 -68
  97. package/index-n68jx79w.js +0 -120
  98. package/index-pgj2rt78.js +0 -404
@@ -3,28 +3,43 @@ import {
3
3
  getDefaultHostConfig,
4
4
  GlobalConfig,
5
5
  CloudApi,
6
- UpdatePackUpload
7
- } from "./index-pgj2rt78.js";
8
- import {
9
- runner
10
- } from "./index-6jjtnhkw.js";
6
+ CloudUpdates,
7
+ PipelineSecrets
8
+ } from "./index-cpb3nvma.js";
11
9
  import {
12
10
  WorkspaceExecutor,
13
11
  AppExecutor
14
- } from "./index-55mynnqs.js";
12
+ } from "./index-va06nr10.js";
15
13
  import {
16
- NativeApp
17
- } from "./index-1w2qaee2.js";
14
+ runner,
15
+ assertPromptable
16
+ } from "./index-jw1zm273.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";
27
23
 
24
+ // pkgs/@akanjs/cli/npmRegistry.ts
25
+ var defaultNpmRegistry = "https://registry.npmjs.org";
26
+ var getNpmRegistryUrl = (registryUrl = process.env.AKAN_NPM_REGISTRY ?? defaultNpmRegistry) => registryUrl.replace(/\/+$/, "");
27
+ async function getLatestPackageVersion(packageName, tag = "latest", registryUrl) {
28
+ const registry = getNpmRegistryUrl(registryUrl);
29
+ const url = `${registry}/${encodeURIComponent(packageName).replace(/^%40/, "@")}`;
30
+ const res = await fetch(url);
31
+ if (!res.ok)
32
+ throw new Error(`Failed to fetch ${packageName} metadata from ${registry}`);
33
+ const metadata = await res.json();
34
+ const version = metadata["dist-tags"]?.[tag];
35
+ if (!version)
36
+ throw new Error(`No npm dist-tag "${tag}" found for ${packageName}`);
37
+ return version;
38
+ }
39
+
40
+ // pkgs/@akanjs/cli/cloud/cloud.runner.ts
41
+ import path7 from "path";
42
+
28
43
  // pkgs/@akanjs/devkit/platformTest/PlatformTestRun.ts
29
44
  import path6 from "path";
30
45
  import chalk from "chalk";
@@ -212,11 +227,13 @@ class PlatformTestTarget {
212
227
  const install = await this.exec("bun install --frozen-lockfile", logPath, PlatformTestTarget.installIdleMs);
213
228
  if (install.exitCode === 0)
214
229
  return;
215
- const failedScripts = [...install.output.matchAll(/(?:pre|post)?install script from "([^"]+)" exited/g)].map((match) => match[1] ?? "");
216
- const tolerable = failedScripts.length > 0 && failedScripts.every((pkg) => this.tolerateScriptFailures.includes(pkg));
230
+ const failedPackages = [
231
+ ...install.output.matchAll(/(?:pre|post)?install script from "([^"]+)" exited|failed to link binaries for package: (@?[^@\s]+)@/g)
232
+ ].map((match) => match[1] ?? match[2] ?? "");
233
+ const tolerable = failedPackages.length > 0 && failedPackages.every((pkg) => this.tolerateScriptFailures.includes(pkg));
217
234
  if (!tolerable)
218
235
  throw new Error(`bun install failed \u2014 see ${logPath}`);
219
- envWarnings.push(`install script failed for ${failedScripts.join(", ")} \u2014 tolerated on ${this.platform}`);
236
+ envWarnings.push(`install failed for ${failedPackages.join(", ")} \u2014 tolerated on ${this.platform}`);
220
237
  const relink = await this.exec("bun install --frozen-lockfile --ignore-scripts", this.#logPath(context, "install-relink"), PlatformTestTarget.installIdleMs);
221
238
  if (relink.exitCode !== 0)
222
239
  throw new Error(`bun install --ignore-scripts failed \u2014 see ${logPath}`);
@@ -253,7 +270,7 @@ class LinuxDockerTarget extends PlatformTestTarget {
253
270
  }
254
271
  static dockerfile = [
255
272
  `FROM oven/bun:${Bun.version}-slim`,
256
- "RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates procps lsof && rm -rf /var/lib/apt/lists/*"
273
+ "RUN sed -i 's|http://|https://|g' /etc/apt/sources.list.d/debian.sources && apt-get update && apt-get install -y --no-install-recommends git ca-certificates procps lsof && rm -rf /var/lib/apt/lists/*"
257
274
  ].join(`
258
275
  `);
259
276
  get image() {
@@ -416,7 +433,7 @@ import path5 from "path";
416
433
  class WindowsSshTarget extends PlatformTestTarget {
417
434
  static utmctlPath = "/Applications/UTM.app/Contents/MacOS/utmctl";
418
435
  static defaultWorkRoot = "C:\\akan-test";
419
- static defaultTolerateScriptFailures = ["sharp", "puppeteer"];
436
+ static defaultTolerateScriptFailures = ["sharp", "puppeteer", "msgpackr"];
420
437
  static #runDirPattern = "^\\d{8}-\\d{6}$";
421
438
  static #keptRuns = 2;
422
439
  platform = "windows";
@@ -657,31 +674,10 @@ class PlatformTestRun {
657
674
  }
658
675
 
659
676
  // pkgs/@akanjs/cli/cloud/cloud.runner.ts
660
- import { rm } from "fs/promises";
661
- import os3 from "os";
662
- import path7 from "path";
663
- import { confirm, input, password, select } from "@inquirer/prompts";
664
- import { Logger, PipelineNames, sleep } from "akanjs/common";
677
+ import { confirm, input, select } from "@inquirer/prompts";
678
+ import { Logger, sleep } from "akanjs/common";
665
679
  import chalk2 from "chalk";
666
680
  import * as QRcode from "qrcode";
667
-
668
- // pkgs/@akanjs/cli/npmRegistry.ts
669
- var defaultNpmRegistry = "https://registry.npmjs.org";
670
- var getNpmRegistryUrl = (registryUrl = process.env.AKAN_NPM_REGISTRY ?? defaultNpmRegistry) => registryUrl.replace(/\/+$/, "");
671
- async function getLatestPackageVersion(packageName, tag = "latest", registryUrl) {
672
- const registry = getNpmRegistryUrl(registryUrl);
673
- const url = `${registry}/${encodeURIComponent(packageName).replace(/^%40/, "@")}`;
674
- const res = await fetch(url);
675
- if (!res.ok)
676
- throw new Error(`Failed to fetch ${packageName} metadata from ${registry}`);
677
- const metadata = await res.json();
678
- const version = metadata["dist-tags"]?.[tag];
679
- if (!version)
680
- throw new Error(`No npm dist-tag "${tag}" found for ${packageName}`);
681
- return version;
682
- }
683
-
684
- // pkgs/@akanjs/cli/cloud/cloud.runner.ts
685
681
  var addRemoteEnvServerValue = "__addRemoteEnvServer";
686
682
  var removeRemoteEnvServerValue = "__removeRemoteEnvServer";
687
683
 
@@ -766,6 +762,9 @@ class CloudRunner extends runner("cloud") {
766
762
  return { name: `${name} (${this.#getSshTarget(config)}${config.port ? `:${config.port}` : ""})`, value: name };
767
763
  }
768
764
  async#getRemoteEnvServerWithUsername() {
765
+ assertPromptable("The env archive goes over scp to a remote server picked here, as AKAN_WORKSPACE_ID is not set", {
766
+ remedy: "set AKAN_WORKSPACE_ID in the workspace .env to use Akan Cloud, or run it once in a terminal"
767
+ });
769
768
  const remoteServer = await this.#selectRemoteEnvServer();
770
769
  if (remoteServer.config.username)
771
770
  return remoteServer;
@@ -891,6 +890,9 @@ ${run.format(results)}
891
890
  const reason = result.infraError ?? "failing tests";
892
891
  if (!interactive)
893
892
  continue;
893
+ assertPromptable(`${result.platform} did not pass (${reason}) \u2014 ${run.logDir}`, {
894
+ remedy: "leave it out of --platforms to publish without it, or run it in a terminal to be asked"
895
+ });
894
896
  const proceed = await confirm({
895
897
  message: `${result.platform} did not pass (${reason}). Continue without it?`,
896
898
  default: false
@@ -909,6 +911,9 @@ ${run.format(results)}
909
911
  return results;
910
912
  }
911
913
  async#askWindowsTestTarget() {
914
+ assertPromptable("No Windows test target is configured", {
915
+ remedy: "run it once in a terminal, which saves the target to ~/.akan/config.json"
916
+ });
912
917
  Logger.info("No Windows test target is configured yet; it is saved to ~/.akan/config.json once entered.");
913
918
  const required = (value) => !!value.trim();
914
919
  const host = await this.#ask("Windows host (ip or name): ", required);
@@ -944,11 +949,15 @@ ${run.format(results)}
944
949
  for (const library of akanPkgs) {
945
950
  const packageJson = await workspace.readJson(`pkgs/${library}/package.json`);
946
951
  const newPackageJson = this.#normalizeAkanPackageJson(packageJson, library, nextVersion);
947
- await workspace.writeFile(`pkgs/${library}/package.json`, JSON.stringify(newPackageJson, null, 2));
952
+ await workspace.writeJson(`pkgs/${library}/package.json`, newPackageJson);
948
953
  const distPackageJson = await workspace.readJson(`dist/pkgs/${library}/package.json`);
949
954
  const newDistPackageJson = this.#normalizeAkanPackageJson(distPackageJson, library, nextVersion);
950
955
  await workspace.writeJson(`dist/pkgs/${library}/package.json`, newDistPackageJson);
951
956
  }
957
+ if (confirmPublish)
958
+ assertPromptable(`Publishing ${akanPkgs.join(", ")}@${nextVersion} asks for a confirmation`, {
959
+ remedy: "run it in a terminal"
960
+ });
952
961
  if (confirmPublish && !await confirm({ message: "Are you sure you want to deploy the libraries?" })) {
953
962
  Logger.error("Deployment cancelled");
954
963
  return;
@@ -1014,7 +1023,7 @@ ${run.format(results)}
1014
1023
  async downloadEnv(cloudApi, workspace, workspaceId) {
1015
1024
  await workspace.mkdir("local");
1016
1025
  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`");
1026
+ throw CloudApi.refusal(error, "downloading its env archive takes the project owner's own `akan login`");
1018
1027
  });
1019
1028
  const relativePath = path7.relative(workspace.workspaceRoot, localPath).split(path7.sep).join("/");
1020
1029
  await workspace.spawn("tar", ["-xf", relativePath], { cwd: workspace.workspaceRoot });
@@ -1023,7 +1032,7 @@ ${run.format(results)}
1023
1032
  async uploadEnv(cloudApi, workspaceId, filePath) {
1024
1033
  const file = new File([Bun.file(filePath)], path7.basename(filePath));
1025
1034
  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`");
1035
+ throw CloudApi.refusal(error, "an env archive is uploaded by a project member signed in with `akan login`");
1027
1036
  });
1028
1037
  }
1029
1038
  async downloadEnvByScp(workspace) {
@@ -1096,7 +1105,7 @@ ${run.format(results)}
1096
1105
  }));
1097
1106
  return secretPaths.flat();
1098
1107
  }
1099
- async secretProject(workspace, subspace) {
1108
+ async projectOf(workspace, subspace, appName) {
1100
1109
  if (!subspace) {
1101
1110
  const workspaceId = workspace.getWorkspaceId({ allowEmpty: true });
1102
1111
  if (!workspaceId)
@@ -1109,172 +1118,30 @@ ${run.format(results)}
1109
1118
  const [declaration] = config.select([subspace]);
1110
1119
  if (!declaration?.workspaceId)
1111
1120
  throw new Error(`Subspace "${subspace}" declares no workspaceId in ${SubspaceConfig.source}.`);
1121
+ if (appName && !declaration.apps.includes(appName))
1122
+ throw new Error(`Subspace "${subspace}" declares no app ${appName} in ${SubspaceConfig.source}; it serves ${declaration.apps.join(", ")}.`);
1112
1123
  return declaration.workspaceId;
1113
1124
  }
1114
1125
  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.`);
1126
+ return await PipelineSecrets.readValue(name, file);
1127
+ }
1128
+ async uploadSecret(cloudApi, devProjectId, secret) {
1129
+ await new PipelineSecrets(cloudApi, devProjectId).upload(secret);
1133
1130
  }
1134
1131
  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.`);
1153
- }
1154
- static #packRecords = ["manifest.template.json", "bundle.json", "pack.json", "compat.json"];
1155
- static async#tarPack(pack) {
1156
- const templateFile = Bun.file(path7.join(pack.out, "manifest.template.json"));
1157
- if (!await templateFile.exists())
1158
- throw new Error(`${pack.out} holds no manifest.template.json: give the folder \`akan pack-update\` wrote.`);
1159
- const template = await templateFile.json();
1160
- const hashes = new Set((template.files ?? []).map((file) => file.sha256));
1161
- if (![...hashes].every((hash) => typeof hash === "string" && /^[0-9a-f]{64}$/.test(hash)))
1162
- throw new Error(`${pack.out}/manifest.template.json lists a file whose sha256 is not 64 hex digits.`);
1163
- const present = await Promise.all(CloudRunner.#packRecords.map((name) => Bun.file(path7.join(pack.out, name)).exists()));
1164
- const entries = [
1165
- ...CloudRunner.#packRecords.filter((_, idx) => present[idx]),
1166
- ...[...hashes].map((hash) => `files/${hash}`)
1167
- ];
1168
- const base = path7.join(os3.tmpdir(), `akan-pack-${pack.app}-${pack.target}-${pack.platform}-${process.pid}`);
1169
- await Bun.write(`${base}.list`, entries.map((entry) => `./${entry}
1170
- `).join(""));
1171
- try {
1172
- const tar = Bun.spawn(["tar", "-czf", `${base}.tgz`, "-C", pack.out, "-T", `${base}.list`], {
1173
- stdout: "ignore",
1174
- stderr: "pipe"
1175
- });
1176
- if (await tar.exited !== 0)
1177
- throw new Error(`tar of ${pack.out} failed: ${(await new Response(tar.stderr).text()).trim()}`);
1178
- } finally {
1179
- await rm(`${base}.list`, { force: true });
1180
- }
1181
- return `${base}.tgz`;
1132
+ await new PipelineSecrets(cloudApi, devProjectId).list(branch);
1182
1133
  }
1183
- async pushUpdate(cloudApi, devProjectId, pack, { channel, notes }) {
1184
- const archive = await CloudRunner.#tarPack(pack);
1185
- const target = channel ?? pack.channel;
1186
- try {
1187
- const release = await new UpdatePackUpload(cloudApi, devProjectId).send(archive, { appName: pack.app, target: pack.target, platform: pack.platform, channel: target, notes }).catch((error) => {
1188
- throw CloudRunner.#refusal(error, "a pack is uploaded by a project member signed in with `akan login`");
1189
- });
1190
- const label = `${pack.app} ${pack.target} ${pack.platform} on channel ${target}: release ${release.id}`;
1191
- if (release.status === "rejected") {
1192
- process.exitCode = 2;
1193
- Logger.error(`The project rejected ${label}:
1194
- ${(release.problems ?? []).map((problem) => ` - ${problem}`).join(`
1195
- `)}`);
1196
- } else if (release.status === "published")
1197
- Logger.rawLog(`Published ${label} (${pack.files} files, native API ${pack.nativeApi}).`);
1198
- else
1199
- Logger.rawLog(`Staged ${label} (${pack.files} files, native API ${pack.nativeApi}). Publish it from the project's release screen.`);
1200
- return release;
1201
- } finally {
1202
- await rm(archive, { force: true });
1203
- }
1134
+ async removeSecret(cloudApi, devProjectId, secret) {
1135
+ await new PipelineSecrets(cloudApi, devProjectId).remove(secret);
1204
1136
  }
1205
- async registerBinary(cloudApi, devProjectId, app, pack, { distribution, env }) {
1206
- const [mobileTarget] = await resolveMobileTargets(app, pack.target);
1207
- if (!mobileTarget)
1208
- throw new Error(`Native target '${pack.target}' was not found.`);
1209
- const bundlePath = path7.join(new NativeApp(app, mobileTarget).outDir(pack.platform), "bundle.json");
1210
- const bundleFile = Bun.file(bundlePath);
1211
- if (!await bundleFile.exists())
1212
- throw new Error(`No store build at ${bundlePath}: run \`akan release-${pack.platform} ${app.name} --target ${pack.target} --env ${env}\` first.`);
1213
- const bundle = await bundleFile.json();
1214
- if (!bundle.updates)
1215
- throw new Error(`The store build at ${bundlePath} takes no updates: give the target native.updates.`);
1216
- if (bundle.nativeApi?.hash !== pack.nativeApi)
1217
- 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.`);
1218
- const archive = await CloudRunner.#tarPack(pack);
1219
- const channel = bundle.updates.channel ?? pack.channel;
1220
- const upload = new UpdatePackUpload(cloudApi, devProjectId);
1221
- try {
1222
- const binary = await upload.stage(archive, { appName: pack.app, target: pack.target, platform: pack.platform, channel, notes: null }).then((packUploadId) => upload.settle(() => cloudApi.registerAppBinary(devProjectId, {
1223
- appName: pack.app,
1224
- target: pack.target,
1225
- distribution,
1226
- bundle: bundleFile,
1227
- packUploadId,
1228
- channel
1229
- }))).catch((error) => {
1230
- throw CloudRunner.#refusal(error, "a binary is registered by a project member signed in with `akan login`");
1231
- });
1232
- Logger.rawLog(`Registered ${pack.app} ${pack.target} ${pack.platform} (${distribution}): binary ${binary.id}, native API ${pack.nativeApi}.`);
1233
- return binary;
1234
- } finally {
1235
- await rm(archive, { force: true });
1236
- }
1137
+ async readPack(app, dir, flags) {
1138
+ return await CloudUpdates.readPack(app, dir, flags);
1237
1139
  }
1238
- static formatSecrets(secrets) {
1239
- if (!secrets.length)
1240
- return "No pipeline secrets.";
1241
- const rows = [...secrets].sort((a, b) => a.name.localeCompare(b.name) || a.scope.localeCompare(b.scope)).map((secret) => [
1242
- secret.name,
1243
- secret.scope === "*" ? "every branch" : secret.scope,
1244
- secret.updatedAt,
1245
- secret.updatedBy
1246
- ]);
1247
- const header = ["NAME", "SCOPE", "UPDATED", "BY"];
1248
- const widths = header.map((title, column) => Math.max(title.length, ...rows.map((row) => row[column]?.length ?? 0)));
1249
- return [header, ...rows].map((row) => row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd()).join(`
1250
- `);
1140
+ async pushUpdate(cloudApi, devProjectId, pack, options) {
1141
+ return await new CloudUpdates(cloudApi, devProjectId).push(pack, options);
1251
1142
  }
1252
- static #secretBytes = 64 * 1024;
1253
- static #secretBranches = ["debug", "develop", "main"];
1254
- static #secretsUnavailable = "The server is not configured for pipeline secrets yet.";
1255
- static #assertSecretName(name) {
1256
- const problem = PipelineNames.secretNameError(name);
1257
- if (problem)
1258
- throw new Error(problem);
1259
- }
1260
- static #assertSecretBranch(branch) {
1261
- if (branch !== null && !CloudRunner.#secretBranches.includes(branch))
1262
- throw new Error(`--branch ${branch} is not a deployed branch: ${CloudRunner.#secretBranches.join(", ")}.`);
1263
- }
1264
- static #refusal(error, who, { unavailable } = {}) {
1265
- const message = error instanceof Error ? error.message : String(error);
1266
- const status = CloudApi.statusOf(error);
1267
- if (status === 401)
1268
- return new Error("The server did not accept your sign-in: run `akan login`, then try again.");
1269
- if (status === 403)
1270
- return new Error(`The project refused: ${who}.`);
1271
- if (status === 400) {
1272
- const key = /"error"\s*:\s*"([^"]+)"/.exec(message)?.[1];
1273
- return new Error(`The project refused the request${key ? `: ${key}` : ""}.`);
1274
- }
1275
- if (status === 503 && unavailable)
1276
- return new Error(unavailable);
1277
- return error;
1143
+ async registerBinary(cloudApi, devProjectId, app, pack, options) {
1144
+ return await new CloudUpdates(cloudApi, devProjectId).registerBinary(app, pack, options);
1278
1145
  }
1279
1146
  async#syncSecretGitignore(workspace, appNames) {
1280
1147
  const patterns = (await Promise.all(appNames.map(async (appName) => {