@akanjs/cli 3.0.2-rc.0 → 3.1.0-rc.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 (86) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-szbjv4gv.js → CodeAgent-q7a4srt6.js} +2 -2
  3. package/{CodeTui-5nw86ab6.js → CodeTui-ak23dxc9.js} +1 -1
  4. package/{TunnelShare-cwh9ezsm.js → TunnelShare-21mm6dr5.js} +2 -2
  5. package/{agent.command-np1dffkb.js → agent.command-f3gsc1gd.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-fyjy9ryc.js → application.command-3v2d53gd.js} +38 -16
  8. package/{applicationBuildRunner-etrzfwjj.js → applicationBuildRunner-1yaqsrdq.js} +115 -103
  9. package/buildBatch.proc.js +17 -6
  10. package/cloud.command-kk2rwkvw.js +187 -0
  11. package/cloud.script-r7eyfey4.js +20 -0
  12. package/{code.command-hmtx14b2.js → code.command-30sca09d.js} +6 -6
  13. package/commandManifest.json +1 -1
  14. package/{context.command-6aqvdx43.js → context.command-k7eqacq6.js} +13 -13
  15. package/context.runner-hqd1ppqe.js +21 -0
  16. package/{guideline.command-axvf68gc.js → guideline.command-dtczrqpf.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +10 -2
  18. package/guidelines/conventions/conventions.instruction.md +68 -25
  19. package/guidelines/cssRule/cssRule.instruction.md +19 -0
  20. package/guidelines/mcpRule/mcpRule.instruction.md +67 -12
  21. package/guidelines/queryRule/queryRule.instruction.md +11 -1
  22. package/guidelines/runtimeRule/runtimeRule.instruction.md +97 -16
  23. package/guidelines/ssrRule/ssrRule.instruction.md +10 -7
  24. package/guidelines/transportRule/transportRule.instruction.md +19 -0
  25. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +6 -6
  26. package/incrementalBuilder.proc.js +25 -4
  27. package/{index-h9ht6s60.js → index-05g3jayg.js} +1 -1
  28. package/{index-j38qkvkk.js → index-0bm04n5g.js} +12 -10
  29. package/{index-g38nhv87.js → index-2etcv0sc.js} +1 -1
  30. package/{index-zyw622y1.js → index-2mx692qq.js} +3 -3
  31. package/{index-jb0hmt3k.js → index-2t4tqd1c.js} +3 -3
  32. package/{index-50wb2a26.js → index-55mynnqs.js} +354 -129
  33. package/{index-q6ft9fkc.js → index-6jjtnhkw.js} +8 -3
  34. package/index-6w0rbr9d.js +68 -0
  35. package/{index-2wc41k5w.js → index-7bymavqk.js} +2 -2
  36. package/{index-krjcg4y6.js → index-aprt7pkz.js} +7 -10
  37. package/{index-1bjkfvm9.js → index-c1x0be5j.js} +2 -2
  38. package/{index-d95tfg3n.js → index-c2yrsxdh.js} +14 -8
  39. package/{index-cb6yc5mb.js → index-c76k723e.js} +2 -2
  40. package/{index-6keyf4sg.js → index-ctanya41.js} +4 -4
  41. package/{index-f4e08ptp.js → index-e0cvze1s.js} +4 -4
  42. package/{index-hp3stk1m.js → index-fm46xrf1.js} +3 -3
  43. package/{index-3xmzr1p1.js → index-gnyf1fqy.js} +2 -2
  44. package/{index-zwd84m0w.js → index-jyv387qw.js} +4 -4
  45. package/{index-senscydg.js → index-k9rrten6.js} +6 -1
  46. package/{index-jk9xnck6.js → index-kjvrq3nc.js} +231 -133
  47. package/{index-xwprn9nr.js → index-nz15k0b6.js} +1 -1
  48. package/{index-61mmsdjg.js → index-p9ystjrp.js} +174 -72
  49. package/index-q958tqqg.js +77 -0
  50. package/{index-50jjwg8y.js → index-qt22xstj.js} +1 -1
  51. package/{index-94s080qm.js → index-r2tpeb3n.js} +127 -1
  52. package/{index-bsv901fd.js → index-rwq3ekqc.js} +7 -7
  53. package/{index-gvpvxdm1.js → index-tn4rpqp1.js} +96 -12
  54. package/{index-1gs9g12n.js → index-v29746vb.js} +4 -4
  55. package/index-ww9bhp67.js +62 -0
  56. package/{index-hxnnfy57.js → index-y8q31011.js} +180 -8
  57. package/index.js +22 -21
  58. package/{library.command-9m2rdwwz.js → library.command-gaga5bmq.js} +3 -3
  59. package/{localRegistry.command-vacc2njh.js → localRegistry.command-9e84e32q.js} +10 -10
  60. package/{module.command-q63dffya.js → module.command-7vpf6db4.js} +5 -5
  61. package/{package.command-y2jh0m99.js → package.command-twmvvf69.js} +3 -3
  62. package/package.json +2 -2
  63. package/{page.command-f3e4cwhh.js → page.command-5asq7fdf.js} +3 -3
  64. package/pipeline.command-d0v38572.js +1334 -0
  65. package/{primitive.command-r4kq75qj.js → primitive.command-dyrs2fm8.js} +6 -6
  66. package/projectTypecheck-cvtkrzp2.js +7 -0
  67. package/{quality.command-f7gzh26z.js → quality.command-6emsqpbg.js} +5 -4
  68. package/{repair.command-0qmyr6qa.js → repair.command-8t6aegv5.js} +5 -5
  69. package/{scalar.command-kddn7av4.js → scalar.command-bnggy1x7.js} +4 -4
  70. package/{subspace.command-j7ayp382.js → subspace.command-f02j5tge.js} +158 -91
  71. package/templates/app/tsconfig.json.template +1 -6
  72. package/templates/appSample/page/task/[taskId]/_index.tsx +2 -2
  73. package/templates/appSample/page/task/[taskId]/edit.tsx +2 -2
  74. package/templates/appSample/page/task/_index.tsx +2 -2
  75. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -2
  76. package/templates/crudPages/[__model__Id]/page.tsx +3 -3
  77. package/templates/crudPages/page.tsx +2 -2
  78. package/templates/crudSinglePage/page.tsx +3 -3
  79. package/templates/lib/cnst.ts +2 -2
  80. package/templates/lib/sig.ts +3 -3
  81. package/{tunnel.command-b3ymdk4f.js → tunnel.command-9x8nfn75.js} +16 -13
  82. package/{workflow.command-z1srcdfw.js → workflow.command-jx0wag5r.js} +10 -10
  83. package/{workspace.command-17c46dbp.js → workspace.command-cmj6mqyk.js} +58 -28
  84. package/cloud.command-c0rnwy65.js +0 -95
  85. package/cloud.script-6ba8e9v8.js +0 -20
  86. package/context.runner-hnkvv3z3.js +0 -21
@@ -1,6 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  ApplicationBuildReporter,
4
+ UNAVAILABLE_REDIS_HOST,
5
+ redisTunnelPolicy,
4
6
  BUILDER_MIN_RSS_RECYCLE_INTERVAL_MS,
5
7
  SOURCE_EXTS,
6
8
  shouldRestartBackendByDevPlan,
@@ -33,33 +35,36 @@ import {
33
35
  filesChangedSince,
34
36
  HmrWatcher,
35
37
  WatchRootResolver
36
- } from "./index-senscydg.js";
38
+ } from "./index-k9rrten6.js";
37
39
  import {
38
40
  runner,
39
41
  script
40
- } from "./index-q6ft9fkc.js";
42
+ } from "./index-6jjtnhkw.js";
41
43
  import {
42
44
  WorkspaceExecutor,
43
45
  AppExecutor,
44
46
  LibExecutor,
45
47
  PkgExecutor
46
- } from "./index-50wb2a26.js";
48
+ } from "./index-55mynnqs.js";
47
49
  import {
48
50
  DesktopServerStage,
49
- resolveMobileTargets,
50
- resolveAppId,
51
- NativeApp
52
- } from "./index-jk9xnck6.js";
51
+ NativeApp,
52
+ UpdateUploader
53
+ } from "./index-kjvrq3nc.js";
53
54
  import {
54
55
  LibraryScript
55
- } from "./index-1bjkfvm9.js";
56
+ } from "./index-c1x0be5j.js";
56
57
  import {
57
58
  stripAnsi
58
59
  } from "./index-7md2g9vw.js";
59
60
  import {
60
61
  SlicePlanner,
61
62
  formatSlicePlan
62
- } from "./index-50jjwg8y.js";
63
+ } from "./index-qt22xstj.js";
64
+ import {
65
+ resolveMobileTargets,
66
+ resolveAppId
67
+ } from "./index-q958tqqg.js";
63
68
  import {
64
69
  openBrowser
65
70
  } from "./index-9dp031by.js";
@@ -187,20 +192,29 @@ var closeBunSocket = (socket, error) => {
187
192
  // pkgs/@akanjs/devkit/createTunnel.ts
188
193
  var createTunnel = async (service, { app, environment, port = service === "postgres" ? 5432 : 6379 }) => {
189
194
  const { serveDomain, repoName } = WorkspaceExecutor.getBaseDevEnv();
190
- await createSshTunnel({
191
- localHost: "0.0.0.0",
192
- localPort: port,
193
- srcHost: "0.0.0.0",
194
- srcPort: port,
195
- dstHost: `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`,
196
- dstPort: service === "postgres" ? 5432 : 6379,
197
- sshOptions: {
198
- host: `${app.name}-${environment}.${serveDomain}`,
199
- port: process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767,
200
- username: process.env.SSH_TUNNEL_USERNAME ?? "root",
201
- password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
202
- }
203
- });
195
+ const host = `${app.name}-${environment}.${serveDomain}`;
196
+ const sshPort = process.env.SSH_TUNNEL_PORT ? parseInt(process.env.SSH_TUNNEL_PORT) : 32767;
197
+ const dstHost = `${service}-0.${service}-svc.${app.name}-${environment}.svc.cluster.local`;
198
+ const dstPort = service === "postgres" ? 5432 : 6379;
199
+ try {
200
+ await createSshTunnel({
201
+ localHost: "0.0.0.0",
202
+ localPort: port,
203
+ srcHost: "0.0.0.0",
204
+ srcPort: port,
205
+ dstHost,
206
+ dstPort,
207
+ sshOptions: {
208
+ host: host.endsWith(".localhost") ? "localhost" : host,
209
+ port: sshPort,
210
+ username: process.env.SSH_TUNNEL_USERNAME ?? "root",
211
+ password: process.env.SSH_TUNNEL_PASSWORD ?? repoName
212
+ }
213
+ });
214
+ } catch (error) {
215
+ const reasons = error instanceof AggregateError ? error.errors.map((reason) => reason instanceof Error ? reason.message : String(reason)).join("; ") : error instanceof Error ? error.message : String(error);
216
+ throw new Error(`Could not open the ${service} tunnel for ${app.name}-${environment}: SSH ${host}:${sshPort} -> ${dstHost}:${dstPort} failed (${reasons})`, { cause: error });
217
+ }
204
218
  return `localhost:${port}`;
205
219
  };
206
220
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
@@ -818,9 +832,17 @@ class AkanAppHost {
818
832
  }
819
833
  async#prepareDatabase(type) {
820
834
  const environment = WorkspaceExecutor.getBaseDevEnv().env;
821
- if (environment === "local")
835
+ const policy = redisTunnelPolicy({ environment, databaseMode: this.env.AKAN_DATABASE_MODE });
836
+ if (policy === "none")
822
837
  return "localhost";
823
- return await createTunnel(type, { app: this.app, environment });
838
+ if (policy === "required")
839
+ return await createTunnel(type, { app: this.app, environment });
840
+ try {
841
+ return await createTunnel(type, { app: this.app, environment });
842
+ } catch (error) {
843
+ this.logger.warn(`${error instanceof Error ? error.message : String(error)} - continuing without Redis: the single database mode does not use it unless an app adaptor does`);
844
+ return UNAVAILABLE_REDIS_HOST;
845
+ }
824
846
  }
825
847
  #startBackend(startStatus = null) {
826
848
  if (this.#stopping)
@@ -1813,7 +1835,7 @@ ${this.#backendStderrTail.join(`
1813
1835
  this.#wakeFromIdle(`${message.type} arrived while suspended`);
1814
1836
  return;
1815
1837
  }
1816
- if (message.type === "build-csr" && this.env.AKAN_DEV_CSR_REBUILD !== "1") {
1838
+ if (message.type === "build-csr" && !message.shell && this.env.AKAN_DEV_CSR_REBUILD !== "1") {
1817
1839
  Object.assign(this.env, { AKAN_DEV_CSR_REBUILD: "1" });
1818
1840
  this.logger.verbose(`[csr] armed dev CSR rebuilds (${message.reason})`);
1819
1841
  }
@@ -1867,7 +1889,7 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1867
1889
 
1868
1890
  // pkgs/@akanjs/cli/application/application.runner.ts
1869
1891
  import { Logger as Logger3 } from "akanjs/common";
1870
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-etrzfwjj.js")).ApplicationBuildRunner;
1892
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-1yaqsrdq.js")).ApplicationBuildRunner;
1871
1893
  var loadPrompts = async () => await import("@inquirer/prompts");
1872
1894
 
1873
1895
  class ApplicationRunner extends runner("application") {
@@ -2024,6 +2046,18 @@ try {
2024
2046
  async typecheck(app, options = {}) {
2025
2047
  await new (await loadBuildRunner())(app).typecheck(options);
2026
2048
  }
2049
+ async typecheckLibrary(lib, options = {}) {
2050
+ const { ProjectTypecheck } = await import("./projectTypecheck-cvtkrzp2.js");
2051
+ const root = lib.workspace.workspaceRoot;
2052
+ await new ProjectTypecheck(lib.cwdPath, root, { ...process.env, AKAN_WORKSPACE_ROOT: root }).run([
2053
+ "../../server.ts",
2054
+ "../../client.ts",
2055
+ "../../index.ts",
2056
+ "../../page/**/*.ts",
2057
+ "../../page/**/*.tsx",
2058
+ "../../../../pkgs/akanjs/*/types/**/*.d.ts"
2059
+ ], options);
2060
+ }
2027
2061
  async test(exec) {
2028
2062
  const isSignalTarget = exec instanceof AppExecutor || exec instanceof LibExecutor;
2029
2063
  const preloadPath = isSignalTarget ? await resolveSignalTestPreloadPath(exec) : null;
@@ -2043,9 +2077,10 @@ try {
2043
2077
  async build(app, {
2044
2078
  fast = false,
2045
2079
  reporter,
2046
- spinner = false
2080
+ spinner = false,
2081
+ profile
2047
2082
  } = {}) {
2048
- return new (await loadBuildRunner())(app, { fast, reporter }).build({ spinner });
2083
+ return new (await loadBuildRunner())(app, { fast, reporter, profile }).build({ spinner });
2049
2084
  }
2050
2085
  async start(app, {
2051
2086
  open = false,
@@ -2060,7 +2095,15 @@ try {
2060
2095
  setTimeout(() => openBrowser(`http://localhost:${env.AKAN_PUBLIC_CLIENT_PORT ?? env.PORT ?? "8282"}`), 3000);
2061
2096
  return appHost;
2062
2097
  }
2063
- async buildMobile(app, platform, { target, env = "debug", profile = "release", installer = false, arch } = {}) {
2098
+ async buildMobile(app, platform, {
2099
+ target,
2100
+ env = "debug",
2101
+ profile = "release",
2102
+ installer = false,
2103
+ arch,
2104
+ sign = false,
2105
+ notarize = false
2106
+ } = {}) {
2064
2107
  const targets = await resolveMobileTargets(app, target);
2065
2108
  NativeApp.assertInstaller(platform, installer);
2066
2109
  if (arch && platform !== "macos" && platform !== "windows" && platform !== "linux")
@@ -2073,7 +2116,9 @@ try {
2073
2116
  profile,
2074
2117
  ...carried && ApplicationRunner.carriesServer(mobileTarget, platform) ? { server: carried } : {},
2075
2118
  ...installer ? { installer } : {},
2076
- ...arch ? { arch } : {}
2119
+ ...arch ? { arch } : {},
2120
+ sign,
2121
+ notarize
2077
2122
  }));
2078
2123
  });
2079
2124
  }
@@ -2163,20 +2208,26 @@ try {
2163
2208
  if (!mobileTarget || targets.length > 1)
2164
2209
  throw new Error("pack-update packs one native target at a time; pass --target <name>.");
2165
2210
  await this.#buildMobileCsr(app, env);
2166
- const packed = await new NativeApp(app, mobileTarget, env).packUpdate(platform, { out });
2167
- const size = packed.manifest.files.reduce((sum, file) => sum + file.size, 0);
2168
- app.logger.info(`packed ${mobileTarget.name} ${platform} for channel '${packed.channel}' to ${packed.out}: ${packed.manifest.files.length} files, ${(size / 1024).toFixed(0)} KiB, native API ${packed.manifest.nativeApi}`);
2169
- if (!against)
2170
- return;
2171
- const bundle = path5.join(packed.out, "bundle.json");
2172
- const compat = await NativeApp.compareBundles(app.cwdPath, against, bundle);
2173
- await Bun.write(path5.join(packed.out, "compat.json"), `${JSON.stringify({ against, ...compat }, null, 2)}
2211
+ const { pack } = await new NativeApp(app, mobileTarget, env).packUpdate(platform, { out });
2212
+ app.logger.info(`packed ${mobileTarget.name} ${platform} for channel '${pack.channel}' to ${pack.out}: ${pack.files} files, ${(pack.size / 1024).toFixed(0)} KiB, native API ${pack.nativeApi}`);
2213
+ if (against) {
2214
+ const compat = await NativeApp.compareBundles(app.cwdPath, against, path5.join(pack.out, "bundle.json"));
2215
+ pack.compat = { against, ...compat };
2216
+ await Bun.write(path5.join(pack.out, "compat.json"), `${JSON.stringify(pack.compat, null, 2)}
2174
2217
  `);
2175
- if (!compat.compatible)
2176
- throw new Error(`This bundle needs a new store build; the one ${against} came from provides another native API:
2177
- - ${compat.problems.join(`
2218
+ }
2219
+ await NativeApp.writePack(pack);
2220
+ if (!pack.compat)
2221
+ return pack;
2222
+ if (pack.compat.compatible) {
2223
+ app.logger.info(`runs in the store build of ${against}`);
2224
+ return pack;
2225
+ }
2226
+ app.logger.error(`This bundle needs a new store build; the one ${against} came from provides another native API:
2227
+ - ${pack.compat.problems.join(`
2178
2228
  - `)}`);
2179
- app.logger.info(`runs in the store build of ${against}`);
2229
+ process.exitCode = 2;
2230
+ return pack;
2180
2231
  }
2181
2232
  async updateKeygen(app, platform, target) {
2182
2233
  const nativePlatform = platform === "desktop" ? NativeApp.desktopPlatform() : platform;
@@ -2188,30 +2239,47 @@ try {
2188
2239
  seen.add(appId);
2189
2240
  const { publicKey, keyPath, created } = await new NativeApp(app, mobileTarget).updateKeygen(nativePlatform);
2190
2241
  app.log(`${appId}: ${created ? "made" : "read"} ${keyPath}${created ? " (keep it private and backed up: without it, installed apps take no more updates)" : ""}`);
2191
- app.log(` native: { updates: { url: "https://\u2026/${app.name}", publicKey: ${JSON.stringify(publicKey)} } }`);
2192
- const declared = mobileTarget.config.updates?.publicKey;
2193
- if (declared && declared !== publicKey)
2194
- app.logger.warn(`native.targets.${mobileTarget.name}.updates.publicKey is another key's; releases would not verify.`);
2242
+ app.log(` native: { updates: { url: "https://\u2026/${app.name}", publicKeys: [${JSON.stringify(publicKey)}] } }`);
2243
+ const { publicKey: one, publicKeys } = mobileTarget.config.updates ?? {};
2244
+ const declared = publicKeys ?? (one ? [one] : null);
2245
+ if (declared && !declared.includes(publicKey))
2246
+ app.logger.warn(`native.targets.${mobileTarget.name}.updates.publicKeys does not hold this key; releases it signs would not verify.`);
2195
2247
  }
2196
2248
  }
2197
- async publishUpdate(app, platform, { target, env = "main", channel } = {}) {
2249
+ async publishUpdate(app, platform, { target, env = "main", channel, upload, arch, sign = false, notarize = false } = {}) {
2250
+ if (arch && platform !== "desktop")
2251
+ throw new Error(`--arch picks a desktop release's CPU; ${platform} publishes its web bundle.`);
2198
2252
  const targets = await resolveMobileTargets(app, target);
2253
+ if (upload && targets.length > 1)
2254
+ throw new Error(`--upload takes one target's release; name it with --target (${targets.map(({ name }) => name).join(", ")}).`);
2255
+ const uploader = upload ? UpdateUploader.forTarget(upload) : null;
2199
2256
  const nativePlatform = platform === "desktop" ? NativeApp.desktopPlatform() : platform;
2257
+ if (arch === "x64" && nativePlatform === "macos")
2258
+ throw new Error("A macOS app is built for Apple silicon (arm64) only.");
2200
2259
  for (const mobileTarget of targets)
2201
2260
  await new NativeApp(app, mobileTarget, env).assertPublishable(nativePlatform, {
2202
2261
  server: ApplicationRunner.carriesServer(mobileTarget, nativePlatform),
2203
- ...channel ? { channel } : {}
2262
+ ...channel ? { channel } : {},
2263
+ ...arch ? { arch } : {}
2204
2264
  });
2205
- const carried = await this.#stageMobile(app, nativePlatform, targets, env);
2265
+ const carried = await this.#stageMobile(app, nativePlatform, targets, env, arch);
2206
2266
  for (const mobileTarget of targets) {
2207
2267
  const nativeApp = new NativeApp(app, mobileTarget, env);
2208
2268
  const published = await nativeApp.publishUpdate(nativePlatform, {
2209
2269
  ...channel ? { channel } : {},
2270
+ ...arch ? { arch } : {},
2271
+ sign,
2272
+ notarize,
2210
2273
  ...carried && ApplicationRunner.carriesServer(mobileTarget, nativePlatform) ? { server: carried } : {}
2211
2274
  });
2212
2275
  this.#reportBuild(app, mobileTarget, published.build);
2213
2276
  app.log(`${app.name}/${mobileTarget.name} ${published.bundle}: ${published.dir}/${published.channel}.json, ${published.files} files, ${Math.round(published.size / 1024)} KiB`);
2214
- app.log(`Upload ${nativeApp.updatesDir} to ${mobileTarget.config.updates?.url}, the manifests last.`);
2277
+ if (!uploader) {
2278
+ app.log(`Upload ${nativeApp.updatesDir} to ${mobileTarget.config.updates?.url}, the manifests last.`);
2279
+ continue;
2280
+ }
2281
+ const { uploaded, skipped } = await uploader.upload(nativeApp.updatesDir);
2282
+ app.log(`Uploaded ${uploaded} file(s) to ${upload}, the manifests last; ${skipped} the bucket already held. Installed apps read ${mobileTarget.config.updates?.url}.`);
2215
2283
  }
2216
2284
  }
2217
2285
  #reportBuild(app, mobileTarget, { artifacts, warnings, signing }) {
@@ -2225,24 +2293,45 @@ try {
2225
2293
  static carriesServer({ config }, platform = NativeApp.desktopPlatform()) {
2226
2294
  return platform !== "ios" && platform !== "android" && !!config.desktop?.server;
2227
2295
  }
2228
- static serverOmit(targets, platform) {
2296
+ static carriedServer(targets, platform) {
2229
2297
  const carrying = targets.filter((target) => ApplicationRunner.carriesServer(target, platform));
2230
- const omits = carrying.map(({ config }) => {
2298
+ const servers = carrying.map(({ config }) => {
2231
2299
  const server = config.desktop?.server;
2232
- return typeof server === "object" ? server.omit ?? [] : [];
2300
+ if (typeof server !== "object")
2301
+ return { omit: [], modules: [] };
2302
+ const { omit = [], modules = [], profile, env, restart, watchdog } = server;
2303
+ return {
2304
+ omit,
2305
+ modules,
2306
+ ...profile ? { profile } : {},
2307
+ ...env ? { env } : {},
2308
+ ...restart !== undefined ? { restart } : {},
2309
+ ...typeof watchdog === "object" ? { watchdog } : {}
2310
+ };
2233
2311
  });
2234
- const [first = [], ...rest] = omits;
2235
- if (rest.some((omit) => omit.join("\x00") !== first.join("\x00")))
2236
- throw new Error(`The targets that carry the server omit different packages (${carrying.map(({ name }, idx) => `${name}: ${omits[idx]?.join(", ") || "none"}`).join("; ")}); build them one --target at a time.`);
2312
+ const [first = { omit: [], modules: [] }, ...rest] = servers;
2313
+ const agree = (what, of) => {
2314
+ if (rest.every((server) => of(server) === of(first)))
2315
+ return;
2316
+ throw new Error(`The targets that carry the server ${what} (${carrying.map(({ name }, idx) => `${name}: ${servers[idx] && of(servers[idx]) || "none"}`).join("; ")}); build them one --target at a time.`);
2317
+ };
2318
+ agree("omit different packages", ({ omit }) => omit.join(", "));
2319
+ agree("name different env profiles", ({ profile }) => profile ?? "");
2320
+ agree("mount different modules", ({ modules }) => modules.join(", "));
2321
+ agree("set a different env", ({ env }) => env ? JSON.stringify(env) : "");
2322
+ agree("restart differently", ({ restart }) => String(restart ?? ""));
2323
+ agree("watch it differently", ({ watchdog }) => watchdog ? JSON.stringify(watchdog) : "");
2237
2324
  return first;
2238
2325
  }
2239
2326
  async#stageMobile(app, platform, targets, env, arch) {
2240
2327
  const stage = targets.some((mobileTarget) => ApplicationRunner.carriesServer(mobileTarget, platform)) ? new DesktopServerStage(app) : null;
2241
- await stage?.install(arch, ApplicationRunner.serverOmit(targets, platform));
2242
- await this.#buildMobileCsr(app, env);
2243
- return await stage?.prepare(env);
2328
+ const server = ApplicationRunner.carriedServer(targets, platform);
2329
+ await stage?.install(arch, server.omit);
2330
+ await this.#buildMobileCsr(app, env, stage ? server.profile : undefined);
2331
+ const { modules, env: serverEnv, restart, watchdog } = server;
2332
+ return await stage?.prepare(env, { modules, env: serverEnv, restart, watchdog });
2244
2333
  }
2245
- async#buildMobileCsr(app, env) {
2334
+ async#buildMobileCsr(app, env, profile) {
2246
2335
  const prevEnv = {
2247
2336
  AKAN_PUBLIC_ENV: process.env.AKAN_PUBLIC_ENV,
2248
2337
  AKAN_PUBLIC_OPERATION_MODE: process.env.AKAN_PUBLIC_OPERATION_MODE,
@@ -2254,7 +2343,7 @@ try {
2254
2343
  APP_OPERATION_MODE: "release"
2255
2344
  });
2256
2345
  try {
2257
- await new (await loadBuildRunner())(app, { environment: env }).build({ spinner: true });
2346
+ await new (await loadBuildRunner())(app, { environment: env, profile }).build({ spinner: true });
2258
2347
  } finally {
2259
2348
  for (const [key, value] of Object.entries(prevEnv)) {
2260
2349
  if (value === undefined)
@@ -3213,12 +3302,17 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3213
3302
  async logs(app, options) {
3214
3303
  await this.applicationRunner.runLogs(app, options);
3215
3304
  }
3216
- async build(app, { write = true, fast = false, quiet = false } = {}) {
3305
+ async build(app, {
3306
+ write = true,
3307
+ fast = false,
3308
+ quiet = false,
3309
+ profile
3310
+ } = {}) {
3217
3311
  await app.scanSync({ write });
3218
3312
  if (!quiet)
3219
3313
  Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
3220
3314
  try {
3221
- const result = await this.applicationRunner.build(app, { fast, spinner: !quiet });
3315
+ const result = await this.applicationRunner.build(app, { fast, spinner: !quiet, profile });
3222
3316
  Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
3223
3317
  if (!quiet)
3224
3318
  ApplicationBuildReporter.printSummary(result);
@@ -3228,14 +3322,22 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3228
3322
  throw error;
3229
3323
  }
3230
3324
  }
3231
- async typecheck(app, { write = true, clean = false, incremental = true } = {}) {
3232
- await app.scanSync({ write });
3233
- const spinner = app.spinning(`Typechecking ${app.name}...`);
3325
+ async typecheck(exec, { write = true, clean = false, incremental = true, tests = false } = {}) {
3326
+ if (!(exec instanceof AppExecutor) && !(exec instanceof LibExecutor))
3327
+ throw new Error(`akan typecheck takes an app or a library; ${exec.name} is a package, checked by its own tsconfig.`);
3328
+ if (exec instanceof LibExecutor)
3329
+ await this.libraryScript.syncLibrary(exec);
3330
+ else
3331
+ await exec.scanSync({ write });
3332
+ const spinner = exec.spinning(`Typechecking ${exec.name}${tests ? " with its tests" : ""}...`);
3234
3333
  try {
3235
- await this.applicationRunner.typecheck(app, { clean, incremental });
3236
- spinner.succeed(`${app.name} typechecked`);
3334
+ if (exec instanceof LibExecutor)
3335
+ await this.applicationRunner.typecheckLibrary(exec, { clean, incremental, tests });
3336
+ else
3337
+ await this.applicationRunner.typecheck(exec, { clean, incremental, tests });
3338
+ spinner.succeed(`${exec.name} typechecked`);
3237
3339
  } catch (error) {
3238
- spinner.fail(`${app.name} typecheck failed`);
3340
+ spinner.fail(`${exec.name} typecheck failed`);
3239
3341
  Logger6.rawLog(ApplicationBuildReporter.formatError(error), undefined, "error");
3240
3342
  throw error;
3241
3343
  }
@@ -3437,7 +3539,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3437
3539
  } = {}) {
3438
3540
  await app.scanSync({ write });
3439
3541
  ApplicationScript.#assertReleaseEnv("packUpdate", options.env ?? "main", allowLocalRelease);
3440
- await this.applicationRunner.packUpdate(app, platform, options);
3542
+ return await this.applicationRunner.packUpdate(app, platform, options);
3441
3543
  }
3442
3544
  async updateKeygen(app, platform, { target } = {}) {
3443
3545
  await this.applicationRunner.updateKeygen(app, platform, target);
@@ -3491,7 +3593,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3491
3593
  await Promise.all(shares.map(async (share) => await share.close()));
3492
3594
  }, "Abandoning the tunnel release; the shares expire on their own.");
3493
3595
  await Promise.all(apps.map(async (app) => {
3494
- const { TunnelShare } = await import("./TunnelShare-cwh9ezsm.js");
3596
+ const { TunnelShare } = await import("./TunnelShare-21mm6dr5.js");
3495
3597
  const share = await TunnelShare.open(app, { workspace: app.workspace });
3496
3598
  shares.push(share);
3497
3599
  urls.set(app.name, share.url);
@@ -0,0 +1,77 @@
1
+ // @bun
2
+ // pkgs/@akanjs/devkit/mobile/mobileTarget.ts
3
+ var trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
4
+ var getMobileTargets = async (app) => {
5
+ const config = await app.getConfig();
6
+ return Object.entries(config.native.targets).map(([name, target]) => ({ name, config: target }));
7
+ };
8
+ var isTemplateOnly = ({ basePaths, native }) => basePaths.size > 0 && Object.values(native.targets).every((target) => !target.basePath);
9
+ var getMobileTargetChoices = async (app) => {
10
+ const config = await app.getConfig();
11
+ const targetNames = Object.keys(config.native.targets);
12
+ if (targetNames.length > 0 && !isTemplateOnly(config))
13
+ return targetNames;
14
+ return [...config.basePaths];
15
+ };
16
+ var resolveMobileTargetByBasePath = (targets, basePath) => {
17
+ const normalizedBasePath = trimSlashes(basePath);
18
+ const byBasePath = targets.find(({ config }) => config.basePath !== undefined && trimSlashes(config.basePath) === normalizedBasePath);
19
+ if (byBasePath)
20
+ return byBasePath;
21
+ const [template] = targets;
22
+ if (!template)
23
+ return;
24
+ return {
25
+ name: normalizedBasePath,
26
+ config: { ...template.config, name: normalizedBasePath, basePath: normalizedBasePath }
27
+ };
28
+ };
29
+ var resolveMobileTargets = async (app, selection) => {
30
+ const config = await app.getConfig();
31
+ const targets = await getMobileTargets(app);
32
+ if (targets.length === 0)
33
+ throw new Error(`No native targets configured for ${app.name}`);
34
+ if (!selection && targets.length === 1 && !isTemplateOnly(config))
35
+ return targets;
36
+ if (!selection) {
37
+ const choices = await getMobileTargetChoices(app);
38
+ if (choices.length === 1)
39
+ return resolveMobileTargets(app, choices[0]);
40
+ throw new Error(`Multiple native targets found for ${app.name}. Pass --target <${choices.join("|")}|all>.`);
41
+ }
42
+ if (selection === "all") {
43
+ if (Object.keys(config.native.targets).length > 1)
44
+ return targets;
45
+ const basePaths = [...config.basePaths];
46
+ if (basePaths.length > 1 || isTemplateOnly(config)) {
47
+ return basePaths.flatMap((basePath) => {
48
+ const resolved = resolveMobileTargetByBasePath(targets, basePath);
49
+ return resolved ? [resolved] : [];
50
+ });
51
+ }
52
+ return targets;
53
+ }
54
+ const target = targets.find((candidate) => candidate.name === selection);
55
+ if (target)
56
+ return [target];
57
+ const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
58
+ if (basePathTarget && config.basePaths.has(trimSlashes(selection)))
59
+ return [basePathTarget];
60
+ const choices = await getMobileTargetChoices(app);
61
+ throw new Error(`Native target '${selection}' was not found. Available: ${choices.join(", ")}`);
62
+ };
63
+ var resolveAppId = (appId, platform) => {
64
+ if (typeof appId === "string")
65
+ return appId;
66
+ const id = appId[platform] ?? appId.default;
67
+ if (!id)
68
+ throw new Error(`native.appId names no id for ${platform}; give it ${platform} or default.`);
69
+ return id;
70
+ };
71
+ var appIdsOf = (appId) => typeof appId === "string" ? [appId] : Object.values(appId).filter((id) => !!id);
72
+ var targetHtmlFilename = (target) => {
73
+ const basePath = trimSlashes(target.basePath ?? "");
74
+ return basePath ? `${basePath}.html` : "index.html";
75
+ };
76
+
77
+ export { getMobileTargets, getMobileTargetChoices, resolveMobileTargets, resolveAppId, appIdsOf, targetHtmlFilename };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AppInfo
4
- } from "./index-50wb2a26.js";
4
+ } from "./index-55mynnqs.js";
5
5
 
6
6
  // pkgs/@akanjs/devkit/slicePlanner.ts
7
7
  class SlicePlanner {
@@ -52,6 +52,11 @@ class GlobalConfig {
52
52
  await GlobalConfig.#setAkanGlobalConfig(mutate(await GlobalConfig.#getAkanGlobalConfig()));
53
53
  });
54
54
  }
55
+ static async hostFor(workspace, host) {
56
+ if (host)
57
+ return host;
58
+ return (await workspace.getConfig()).cloudHost ?? GlobalConfig.akanCloudHost;
59
+ }
55
60
  static async getHostConfig(host = GlobalConfig.akanCloudHost) {
56
61
  const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
57
62
  return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
@@ -197,6 +202,10 @@ class HttpClient {
197
202
  }
198
203
 
199
204
  class CloudApi {
205
+ static statusOf(error) {
206
+ const status = Number(/(?:^|: )(\d{3}) /.exec(error instanceof Error ? error.message : String(error))?.[1]);
207
+ return Number.isNaN(status) ? null : status;
208
+ }
200
209
  #api;
201
210
  #accessToken = null;
202
211
  #workspace;
@@ -231,6 +240,32 @@ class CloudApi {
231
240
  formData.append("file", file);
232
241
  return await this.#api.post(`/uploadEnv/${devProjectId}`, formData);
233
242
  }
243
+ async uploadSecret(devProjectId, secret) {
244
+ return await this.#api.post(`/uploadSecret/${encodeURIComponent(devProjectId)}`, secret);
245
+ }
246
+ async listSecrets(devProjectId) {
247
+ return await this.#api.get(`/listSecrets/${encodeURIComponent(devProjectId)}`);
248
+ }
249
+ async removeSecret(devProjectId, secret) {
250
+ return await this.#api.post(`/removeSecret/${encodeURIComponent(devProjectId)}`, secret);
251
+ }
252
+ async requestUpdateUpload(devProjectId, upload) {
253
+ return await this.#api.post(`/requestUpdateUpload/${encodeURIComponent(devProjectId)}`, upload);
254
+ }
255
+ async commitUpdateUpload(devProjectId, uploadId) {
256
+ return await this.#api.post(`/commitUpdateUpload/${encodeURIComponent(devProjectId)}`, { uploadId }, { signal: AbortSignal.timeout(11 * 60000) });
257
+ }
258
+ async registerAppBinary(devProjectId, binary) {
259
+ const formData = new FormData;
260
+ for (const key of ["appName", "target", "distribution"])
261
+ formData.append(key, binary[key]);
262
+ if (binary.channel)
263
+ formData.append("channel", binary.channel);
264
+ formData.append("bundle", binary.bundle, "bundle.json");
265
+ if (binary.pack)
266
+ formData.append("pack", binary.pack, "pack.tgz");
267
+ return await this.#api.post(`/registerAppBinary/${encodeURIComponent(devProjectId)}`, formData);
268
+ }
234
269
  async downloadEnv(devProjectId) {
235
270
  const localPath = `${this.#workspace.workspaceRoot}/local/env.tar`;
236
271
  await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
@@ -263,4 +298,95 @@ class CloudApi {
263
298
  }
264
299
  }
265
300
  }
266
- export { getDefaultHostConfig, GlobalConfig, CloudApi };
301
+ // pkgs/@akanjs/devkit/cloud/updatePackUpload.ts
302
+ import { Logger, sleep } from "akanjs/common";
303
+ class UpdatePackUpload {
304
+ static maxBytes = 200 * 1024 * 1024;
305
+ static #transientPut = new Set([0, 408, 429, 500, 502, 503, 504]);
306
+ #api;
307
+ #devProjectId;
308
+ #putRetryMs;
309
+ #commitRetryMs;
310
+ #cutOffWindowMs;
311
+ #commitLimitMs;
312
+ constructor(api, devProjectId, {
313
+ putRetryMs = 1000,
314
+ commitRetryMs = 1e4,
315
+ cutOffWindowMs = 15 * 60000,
316
+ commitLimitMs = 60 * 60000
317
+ } = {}) {
318
+ this.#api = api;
319
+ this.#devProjectId = devProjectId;
320
+ this.#putRetryMs = putRetryMs;
321
+ this.#commitRetryMs = commitRetryMs;
322
+ this.#cutOffWindowMs = cutOffWindowMs;
323
+ this.#commitLimitMs = commitLimitMs;
324
+ }
325
+ async send(archive, fields) {
326
+ const file = Bun.file(archive);
327
+ if (file.size < 1 || file.size > UpdatePackUpload.maxBytes)
328
+ throw new Error(`The pack archive is ${file.size} bytes; a project takes 1 byte to 200 MiB.`);
329
+ const hasher = new Bun.CryptoHasher("sha256");
330
+ for await (const chunk of file.stream())
331
+ hasher.update(chunk);
332
+ const upload = { ...fields, size: file.size, sha256: hasher.digest("hex") };
333
+ for (let ticket = 1;; ticket++) {
334
+ const { uploadId, uploadUrl, contentType } = await this.#request(upload);
335
+ const status = await this.#put(uploadUrl, contentType, file);
336
+ if (status >= 200 && status < 300)
337
+ return await this.#commit(uploadId);
338
+ if (status !== 403 || ticket === 2)
339
+ throw new Error(`The storage refused the pack upload (${status ? `HTTP ${status}` : "no response"}).`);
340
+ }
341
+ }
342
+ async#request(upload) {
343
+ return await this.#api.requestUpdateUpload(this.#devProjectId, upload).catch((error) => {
344
+ const message = error instanceof Error ? error.message : String(error);
345
+ if (CloudApi.statusOf(error) === 404 && !/"error"\s*:\s*"[\w$-]+\.error\.[\w$-]+"/.test(message))
346
+ throw new Error("The project's server does not take direct pack uploads yet: update akasys and try again, or upload the pack from the project's release screen.");
347
+ throw error;
348
+ });
349
+ }
350
+ async#put(url, contentType, file) {
351
+ for (let retry = 0;; retry++) {
352
+ const status = await fetch(url, { method: "PUT", body: file, headers: { "Content-Type": contentType } }).then(async (response) => {
353
+ await response.arrayBuffer().catch(() => {
354
+ return;
355
+ });
356
+ return response.status;
357
+ }).catch(() => 0);
358
+ if (retry === 3 || !UpdatePackUpload.#transientPut.has(status))
359
+ return status;
360
+ await sleep(this.#putRetryMs * (retry + 1));
361
+ }
362
+ }
363
+ async#commit(uploadId) {
364
+ const limit = performance.now() + this.#commitLimitMs;
365
+ let cutOffSince = null;
366
+ for (;; ) {
367
+ try {
368
+ return await this.#api.commitUpdateUpload(this.#devProjectId, uploadId);
369
+ } catch (error) {
370
+ const now = performance.now();
371
+ if (CloudApi.statusOf(error) === 409)
372
+ cutOffSince = null;
373
+ else if (UpdatePackUpload.#cutOff(error))
374
+ cutOffSince ??= now;
375
+ else
376
+ throw error;
377
+ const deadline = cutOffSince === null ? limit : Math.min(limit, cutOffSince + this.#cutOffWindowMs);
378
+ if (now + this.#commitRetryMs > deadline)
379
+ throw error;
380
+ Logger.rawLog(`The project is still taking the pack; asking again in ${Math.round(this.#commitRetryMs / 1000)}s.`);
381
+ await sleep(this.#commitRetryMs);
382
+ }
383
+ }
384
+ }
385
+ static #cutOff(error) {
386
+ const status = CloudApi.statusOf(error);
387
+ if (status === null)
388
+ return !(error instanceof SyntaxError);
389
+ return status === 502 || status === 504 || status >= 520 && status <= 527;
390
+ }
391
+ }
392
+ export { getDefaultHostConfig, GlobalConfig, CloudApi, UpdatePackUpload };