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

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 (74) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-kyprmy06.js → CodeAgent-ctk4fh2b.js} +2 -2
  3. package/{CodeTui-21mtze3j.js → CodeTui-mrxtrdzw.js} +1 -1
  4. package/TunnelShare-rj74jd6x.js +13 -0
  5. package/{agent.command-f3gsc1gd.js → agent.command-ncre6kwz.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-5s780vnz.js → application.command-54mawreg.js} +52 -41
  8. package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-8573zr2w.js} +16 -30
  9. package/buildBatch.proc.js +22 -13
  10. package/{cloud.command-yj6xb2a7.js → cloud.command-nyemv0m3.js} +57 -65
  11. package/cloud.script-ht6yb203.js +23 -0
  12. package/{code.command-3wkn4ja5.js → code.command-spsrzg63.js} +6 -6
  13. package/{context.command-9jjn3gcw.js → context.command-768zv7z4.js} +16 -13
  14. package/context.runner-tf1b53qq.js +24 -0
  15. package/{guideline.command-dtczrqpf.js → guideline.command-4ycwc51q.js} +2 -2
  16. package/guidelines/conventions/conventions.instruction.md +28 -15
  17. package/guidelines/cssRule/cssRule.instruction.md +12 -6
  18. package/guidelines/mcpRule/mcpRule.instruction.md +27 -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-2etcv0sc.js → index-00seamy5.js} +1 -1
  23. package/{index-jx4bzvwg.js → index-1a7nhq56.js} +92 -52
  24. package/{index-e0cvze1s.js → index-1nyxvz8j.js} +4 -4
  25. package/index-2z3kykvd.js +248 -0
  26. package/{index-rtct7d43.js → index-3675mvjw.js} +1 -1
  27. package/{index-56d2g8ms.js → index-3ddt3qjg.js} +1 -1
  28. package/{index-6jjtnhkw.js → index-3x7yeecy.js} +51 -9
  29. package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
  30. package/index-8935h3n1.js +33 -0
  31. package/{index-v29746vb.js → index-8eqkxwsx.js} +3 -3
  32. package/{index-c2yrsxdh.js → index-91rt5bye.js} +96 -320
  33. package/{index-55mynnqs.js → index-bv210r6t.js} +459 -210
  34. package/{index-c76k723e.js → index-c4h5b5nq.js} +2 -2
  35. package/{index-qt22xstj.js → index-c9zjb3tw.js} +5 -3
  36. package/{index-c1x0be5j.js → index-dmsvd2rv.js} +2 -2
  37. package/index-ec080x99.js +1124 -0
  38. package/{index-ctanya41.js → index-fjqvd5mx.js} +4 -4
  39. package/{index-aprt7pkz.js → index-fvn6f5wx.js} +229 -1327
  40. package/{index-rc5rwyps.js → index-jj7yapwq.js} +71 -60
  41. package/index-kmamh5bv.js +241 -0
  42. package/{index-fm46xrf1.js → index-q5abh0jq.js} +3 -3
  43. package/{index-1w2qaee2.js → index-qf8g88j9.js} +361 -291
  44. package/{index-gnyf1fqy.js → index-qvghfv6v.js} +2 -2
  45. package/{index-ae5pwhgs.js → index-r5d6rq19.js} +10 -8
  46. package/{index-2mx692qq.js → index-vhfjzda3.js} +2 -2
  47. package/{index-8r4d7cmt.js → index-w9b71b6y.js} +4 -4
  48. package/{index-8ggztp8k.js → index-wanhh011.js} +42 -177
  49. package/{index-05g3jayg.js → index-wdze51wz.js} +1 -1
  50. package/index-wjqbc7sf.js +668 -0
  51. package/{index-rwq3ekqc.js → index-xf40a295.js} +7 -7
  52. package/{index-krq7btbt.js → index-xm5tmpv0.js} +3 -3
  53. package/index.js +22 -22
  54. package/{library.command-gaga5bmq.js → library.command-88ahgych.js} +3 -3
  55. package/{localRegistry.command-vsreyy42.js → localRegistry.command-ykea1m7b.js} +12 -9
  56. package/{module.command-7vpf6db4.js → module.command-zky4b7m9.js} +5 -5
  57. package/{package.command-twmvvf69.js → package.command-vh8pcedh.js} +3 -3
  58. package/package.json +2 -2
  59. package/{page.command-5asq7fdf.js → page.command-fyqeqxc3.js} +3 -3
  60. package/{pipeline.command-d0v38572.js → pipeline.command-r5t30py6.js} +220 -219
  61. package/{primitive.command-dyrs2fm8.js → primitive.command-a0516zfk.js} +6 -6
  62. package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-7djd4z50.js} +1 -1
  63. package/{quality.command-6emsqpbg.js → quality.command-k7jcnfn0.js} +2 -2
  64. package/{repair.command-8t6aegv5.js → repair.command-85zg2an3.js} +5 -5
  65. package/{scalar.command-bnggy1x7.js → scalar.command-g5vmqwca.js} +4 -4
  66. package/{subspace.command-md2teygq.js → subspace.command-mp4evgsj.js} +421 -362
  67. package/{tunnel.command-jx3tdm2d.js → tunnel.command-kbnr99sj.js} +7 -4
  68. package/{workflow.command-jx0wag5r.js → workflow.command-9y9p1n8y.js} +10 -10
  69. package/{workspace.command-yxd7rr7g.js → workspace.command-jmec2kjg.js} +33 -30
  70. package/TunnelShare-q4jq08qg.js +0 -9
  71. package/cloud.script-h6pvcpa2.js +0 -20
  72. package/context.runner-atcrzz5b.js +0 -21
  73. package/index-6w0rbr9d.js +0 -68
  74. package/index-pgj2rt78.js +0 -404
@@ -257,9 +257,10 @@ const config: AppConfig = {
257
257
  Intel Macs are not a target, so `--arch x64` on macOS is refused.
258
258
  - **A release is signed only with `--sign` and notarized only with `--notarize`** (`build-desktop`,
259
259
  `publish-update`); the environment names the identity and never decides to use it, so a shell exporting a Developer
260
- ID signs and uploads nothing unasked. Before either, the server the app carries is searched for every secret value
261
- of the workspace's server env files and refused with the paths, and one line names the identity and the service
262
- the app goes to.
260
+ ID signs and uploads nothing unasked. Every release build and every `publish-update` of a target that carries a
261
+ server, signed or not, first searches that server for every secret value of the workspace's server env files and
262
+ refuses with the paths; an env file it cannot evaluate stops the release too, and a debug build is not searched.
263
+ One line then names the identity and the service the app goes to.
263
264
  - **A macOS release for download is signed with a Developer ID and notarized.**
264
265
  `AKAN_NATIVE_MACOS_IDENTITY` names a keychain identity, or `AKAN_NATIVE_MACOS_CERTIFICATE` +
265
266
  `_CERTIFICATE_PASSWORD` a `.p12` the build imports into a keychain of its own (a CI runner);
@@ -280,12 +281,15 @@ const config: AppConfig = {
280
281
  key. List the online signing key first and offline spares after it (up to three): a phone binary trusts all of
281
282
  them, and a key may revoke only the keys listed before it, so a leaked key cannot lock out its spares. A desktop
282
283
  app trusts the first; `publicKey` is the one-key form. `akan publish-update <app>` builds and signs a release
283
- into `updates/`: upload that folder to `url`, the manifests last, or let `--upload s3://<bucket>/<prefix>` do it
284
- (credentials and endpoint from `S3_*` or `AWS_*`; a release file the bucket holds is skipped). A phone reads only
285
- `<url>/<platform>/<channel>/<nativeApi>.json`, so each store generation on a channel takes its own fixes.
284
+ into `updates/`: upload that release's files to `url` and its manifest last, or let
285
+ `--upload s3://<bucket>/<prefix>` do it (credentials and endpoint from `S3_*` or `AWS_*`; a release file the bucket
286
+ holds is skipped), which sends that release alone, and nothing when it is missing or fails a check. Never upload the
287
+ folder whole: a phone's is also where `pack-update` writes by default.
288
+ A phone reads only `<url>/<platform>/<channel>/<nativeApi>.json`, so each store generation on a channel takes its
289
+ own fixes.
286
290
  `akan pack-update` writes an unsigned phone bundle for whoever holds the key to sign and records it in
287
291
  `<out>/pack.json`, exiting 2 when `--against` says it needs a new store build; `akan push-update` packs and uploads to
288
- an akasys project that holds the key, and `akan register-binary` registers a store build after `release-*`. A
292
+ the Akan Cloud project that holds the key, and `akan register-binary` registers a store build after `release-*`. A
289
293
  release that hangs is rolled back while the app runs, and `report: { url }` has a phone post what became of each
290
294
  release (applied, confirmed, rolled back and why) at its next launch, so rollback rates are visible. `devtools: true`
291
295
  opens the web inspector of a release build for an internal test target. The channel is the backend env the binary was built for unless `channel` names one;
@@ -307,10 +311,11 @@ const config: AppConfig = {
307
311
  their page paints, so a dark board never flashes white, and `desktop.publisher` names who publishes a Windows app in
308
312
  Apps & features and its executable's company (the app name when unset, never Bun's own).
309
313
  - **A third-party page shown first-party** — a scoreboard, a menu board — is the `external-view` plugin, listed in
310
- `native.plugins`: a webview over the window with a browser profile of its own, held to the origins it was opened
311
- for, with an init script, `evaluate`, a trusted `sendKey`, `capture`, and `loadFailed` / `gone` (it reloads, or is
312
- rebuilt, by itself) / `consoleMessage` events. Every method on Windows; macOS 14+ lacks `sendKey`, `setMuted` and
313
- `capture`.
314
+ `native.plugins`: a webview over the window with a browser profile of its own, its top level held to the origins it
315
+ was opened for while its frames load web content from anywhere, with an init script that runs in the top document
316
+ only, `evaluate`, a trusted `sendKey`, `capture`, and `loadFailed` / `gone` (it reloads, or is rebuilt, by itself;
317
+ the reason is `terminated` on macOS, and `rebuildFailed` when a rebuild failed and the view is closed, to be opened
318
+ again) / `consoleMessage` events. Every method on Windows; macOS 14+ lacks `sendKey`, `setMuted` and `capture`.
314
319
 
315
320
  ## A Desktop App's Server — `bin` And `trustedDependencies`
316
321
 
@@ -416,10 +421,14 @@ in those alone, and `modules: ["@edge"]` (or `AKAN_MODULES=@edge`) names every m
416
421
  inject, cascade to and hold a relation to. `desktop: { server: { profile: "edge", modules: ["@edge"] } }` builds the
417
422
  carried server that way: `env/env.server.<env>.edge.ts` is bundled in place of `env/env.server.<env>.ts`, which never
418
423
  enters it, and the build evaluates the profile file with no app env and refuses one whose secret-named keys hold a
419
- value — a `process.env.X ?? "literal"` counts as the literal. Each lib's env defaults (`env.server.testing.ts`) are
424
+ value — a `process.env.X ?? "literal"` counts as the literal. Signing, encryption, master and session keys, `pwd`, a
425
+ `…dsn` and a connection string are secret-named, and a URL holding a password or a secret-named query parameter is a
426
+ secret under any key. The profile may spread `libEnv`, and the lib defaults it reaches are checked as the bundle
427
+ carries them; a profile that imports the file it replaces is refused. Each lib's env defaults (`env.server.testing.ts`) are
420
428
  swapped the same way: for the lib's `env/env.server.testing.edge.ts`, held to the same check, or for the defaults with
421
- every secret-named value left out, which the build names in a warning. `akan build <app> --profile edge` does the
422
- same for an image. A module listing only edge does not mount under `akan start` (local): list `local` too to develop
429
+ every secret-named value left out, which the build names in a warning — and a default a JSON snapshot cannot carry (a
430
+ function, Map, Set, Date, class instance, bigint or non-finite number) is refused unless the lib has its profile file.
431
+ `akan build <app> --profile edge` does the same for an image. A module listing only edge does not mount under `akan start` (local): list `local` too to develop
423
432
  it there.
424
433
 
425
434
  **A file the user picks reaches the server as a grant, never as a copy or a path.** With `"file-picker"` in
@@ -556,7 +565,7 @@ const config: AppConfig = { assets: { pruneFonts: false } }; // emergency valve,
556
565
 
557
566
  Akan Cloud builds and deploys each app as `build` → `docker` → `deploy`, and `ota` after them when asked. **An app's own stages go in
558
567
  `apps/<app>/akan.config.ts` under `pipeline`; stages around every app go in the workspace root's
559
- `akan.config.ts`**, typed `WorkspaceConfig` (`before`, `after`, `post`, `env`, `timeout`). Both are `import type`
568
+ `akan.config.ts`**, typed `WorkspaceConfig`, under its own `pipeline` (`before`, `after`, `post`, `env`, `timeout`). Both are `import type`
560
569
  objects (or `(ctx) => …`), and `akan pipeline dry-run | validate | plan` turns them into the plan Akan Cloud runs —
561
570
  locally, running nothing.
562
571
 
@@ -47,14 +47,17 @@ mutation is mounted under. `conventions` carries the invariants — this is the
47
47
  change revokes it, `disconnect` when the socket closes while still subscribed — so a room already left runs
48
48
  neither again, and cleanup that must happen either way registers for both. From a `message` handler it belongs to
49
49
  the socket and runs at close. A handler that throws is logged and never blocks the rest of the teardown.
50
+ - **A subscribe the client leaves before it finishes joins nothing.** When an `unsubscribe` for the room, or the
51
+ socket's close, arrives while the subscribe is still running, the subscribe answers `subscribe: false` and runs its
52
+ `unsubscribe` cleanup (and its `disconnect` one when the socket closed) instead of joining the room.
50
53
 
51
54
  ## Request Deadlines
52
55
 
53
56
  - **`{ timeout: <ms> }` in an endpoint's signal option is the whole configuration surface**, and it reaches both
54
57
  ends of the call. Server side, the `Timeout` middleware races the handler against it and answers
55
58
  `base.error.gatewayTimeout` (504) once it is spent. Client side, `FetchSerializer` ships the value on the
56
- serialized endpoint and `fetch.<endpoint>()` uses it as that request's `AbortSignal.timeout` budget, so the
57
- browser does not abandon a call the server is still allowed to be working on.
59
+ serialized endpoint and `fetch.<endpoint>()` uses it as that call's deadline — every retry of the call and every
60
+ wait between them included — so the browser does not abandon a call the server is still allowed to be working on.
58
61
  - **The middleware is registered by default and has no default of its own.** An endpoint that declares no
59
62
  `timeout` is passed straight through — a default here would put a deadline on every endpoint in the app that
60
63
  nobody asked for, which is why the middleware used to be opt-in and, being opt-in, made the declared value
@@ -92,6 +95,11 @@ mutation is mounted under. `conventions` carries the invariants — this is the
92
95
  - **The entry is the handler's result, not the response.** The lookup wraps the handler only, so `resolveReturn`
93
96
  still runs per call on the cached value — field masking, `hidden`/`secret` stripping and relation resolution are
94
97
  never cached, and a relation still costs its own load. The cache saves the handler, not the serialization.
98
+ - **A hit hands `resolveReturn` an instance of the declared return model**, rebuilt from an entry that keeps its
99
+ `Map`s, bytes and dates, so a `resolveField` reads the same methods and `Dayjs` values on a hit as on a miss. It is
100
+ the declared model, not the service's document: a method only the document has is not there on a hit, and a return
101
+ declared as a `Light` model shows `resolveField` its light fields only. The entry still holds `hidden` and `secret`
102
+ values for its TTL, as the handler returned them.
95
103
  - **A cache backend that is down does not take the endpoint down.** A failed read is warned and the call runs
96
104
  uncached; a failed write is warned and dropped.
97
105
  - The default middleware chain is `Logging → Timeout → <lib middlewares>`, and the call inside it runs an
@@ -99,6 +107,21 @@ mutation is mounted under. `conventions` carries the invariants — this is the
99
107
  handler: a hung cache backend is bounded by the same deadline as the handler it stands in for. **`Retry` was removed** — an automatic re-run of an endpoint
100
108
  whose failure it cannot classify replays whatever the first attempt already did.
101
109
 
110
+ ## Repeated Mutations
111
+
112
+ - **A mutation sent again under the same `Idempotency-Key` header runs once.** The key is checked after the
113
+ resource guards and the internal arguments, and it is scoped to the endpoint, the caller and a digest of the
114
+ arguments, so one caller's key never hands another caller its answer. A repeat while the first run is still going
115
+ answers 409 `base.error.idempotencyInFlight`.
116
+ - **A repeat answers the first response as it was sent** — resolved, masked and serialized — for 24 hours. What
117
+ is kept is that wire form, never the handler's result, so no `hidden` or `secret` field reaches the cache.
118
+ - A handler that throws frees the key, so a repeat runs it again. A failure after the handler returned is kept and
119
+ replayed instead, for the same 24 hours and a transient one (a relation read during a database blip) included:
120
+ the handler's writes stand, so running it again would repeat them.
121
+ - **Only an HTTP call is deduplicated.** A signed-out call, one whose handler returns a `Response` (whose headers,
122
+ such as a session's `Set-Cookie`, cannot be replayed), a websocket call, and a call through MCP all run every
123
+ time.
124
+
102
125
  ## Rate Limits
103
126
 
104
127
  - **`{ rateLimit: { calls, windowMs?, by? } }` in an endpoint's signal option** counts calls per caller per window
@@ -107,9 +130,9 @@ mutation is mounted under. `conventions` carries the invariants — this is the
107
130
  checked after the middlewares and before the guards, keyed by `accountKey(account)`; a caller it names nothing
108
131
  for — an anonymous one — is counted by address. Leaving a pubsub room is never counted.
109
132
  - **The answer is 429 `base.error.tooManyRequests` with `data: { seconds }`** and a `Retry-After` header; a WS
110
- call gets the same payload as its error frame, and the HTTP client restores a proxy's own non-JSON 429 under the
111
- same key (seconds from its `Retry-After`, else 60). The first refusal per endpoint per process is warned once;
112
- later ones log at `debug`, so a flood does not become a log flood.
133
+ call gets the same payload as its error frame, and the HTTP client restores a proxy's own 429 — a page, or JSON
134
+ naming no error key of ours — under the same key (seconds from its `Retry-After`, `0` included, else 60). The first
135
+ refusal per endpoint per process is warned once; later ones log at `debug`, so a flood does not become a log flood.
113
136
  - **Keys and addresses.** The address is `context.getClientIp()` — the trusted-proxy answer, never a raw header
114
137
  from an untrusted peer. Callers whose address is unknown share one `unknown` budget (fail closed), and the first
115
138
  such call is warned about, since it usually means the proxy in front records no `X-Real-IP`/`X-Forwarded-For`
@@ -8,19 +8,21 @@ import {
8
8
  CodegenLock,
9
9
  WorkspaceExecutor,
10
10
  AppExecutor
11
- } from "./index-55mynnqs.js";
11
+ } from "./index-bv210r6t.js";
12
12
  import"./index-52w0fvh8.js";
13
13
  import {
14
14
  GraphClientEntryDiscovery,
15
15
  RouteClientBuilder,
16
16
  AutoImportSync,
17
- ServerGraphFile,
18
17
  CsrDevPatcher,
19
18
  CsrDevBundler,
20
19
  DevChangePlanner,
21
20
  DevGeneratedIndexSync,
22
21
  SsrDevBundler
23
- } from "./index-aprt7pkz.js";
22
+ } from "./index-fvn6f5wx.js";
23
+ import {
24
+ ServerGraphFile
25
+ } from "./index-ec080x99.js";
24
26
  import {
25
27
  prepareDevWatchBatch
26
28
  } from "./index-a5vmm6x4.js";
@@ -235,7 +237,7 @@ class IncrementalBuilder {
235
237
  #autoImportSync;
236
238
  #watcher = null;
237
239
  #generation = 0;
238
- #shellGeneration = null;
240
+ #shellKey = null;
239
241
  #discoveredThrough = 0;
240
242
  #csrActive = IncrementalBuilder.#csrArmedByEnv();
241
243
  #csrBundler;
@@ -948,13 +950,14 @@ class IncrementalBuilder {
948
950
  async#buildShell(msg) {
949
951
  await this.#workQueue.enqueue("build-shell", async () => {
950
952
  const generation = this.#generation;
953
+ const shellKey = `${generation}:${JSON.stringify(this.#artifact.cssAssets)}`;
951
954
  let error;
952
- if (this.#shellGeneration !== generation)
955
+ if (this.#shellKey !== shellKey)
953
956
  try {
954
957
  const result = await this.#batchRunner.run(await this.#batchRequest({ generation, needs: ["shell"], changedFiles: [] }));
955
958
  error = result.errors.shell;
956
959
  if (!error)
957
- this.#shellGeneration = generation;
960
+ this.#shellKey = shellKey;
958
961
  } catch (err) {
959
962
  error = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
960
963
  }
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6jjtnhkw.js";
5
+ } from "./index-3x7yeecy.js";
6
6
 
7
7
  // pkgs/@akanjs/cli/page/page.runner.ts
8
8
  class PageRunner extends runner("page") {
@@ -38,40 +38,45 @@ import {
38
38
  } from "./index-k9rrten6.js";
39
39
  import {
40
40
  runner,
41
- script
42
- } from "./index-6jjtnhkw.js";
41
+ script,
42
+ assertPromptable
43
+ } from "./index-3x7yeecy.js";
43
44
  import {
44
45
  WorkspaceExecutor,
45
46
  AppExecutor,
46
47
  LibExecutor,
47
48
  PkgExecutor
48
- } from "./index-55mynnqs.js";
49
+ } from "./index-bv210r6t.js";
49
50
  import {
50
51
  DesktopServerStage,
51
- NativeApp,
52
52
  UpdateUploader
53
- } from "./index-1w2qaee2.js";
53
+ } from "./index-kmamh5bv.js";
54
+ import {
55
+ NativePublicEnv,
56
+ UpdatePackFolder,
57
+ NativeApp
58
+ } from "./index-qf8g88j9.js";
54
59
  import {
55
60
  LibraryScript
56
- } from "./index-c1x0be5j.js";
61
+ } from "./index-dmsvd2rv.js";
57
62
  import {
58
63
  stripAnsi
59
64
  } from "./index-7md2g9vw.js";
60
65
  import {
61
66
  SlicePlanner,
62
67
  formatSlicePlan
63
- } from "./index-qt22xstj.js";
68
+ } from "./index-c9zjb3tw.js";
69
+ import {
70
+ openBrowser
71
+ } from "./index-9dp031by.js";
64
72
  import {
65
73
  resolveMobileTargets,
66
74
  resolveAppId
67
75
  } from "./index-q958tqqg.js";
68
- import {
69
- openBrowser
70
- } from "./index-9dp031by.js";
71
76
 
72
77
  // pkgs/@akanjs/cli/application/application.script.ts
73
78
  import { confirm } from "@inquirer/prompts";
74
- import { Logger as Logger6 } from "akanjs/common";
79
+ import { Logger as Logger6, toError as toError2 } from "akanjs/common";
75
80
 
76
81
  // pkgs/@akanjs/cli/application/application.runner.ts
77
82
  import { realpathSync } from "fs";
@@ -1888,8 +1893,8 @@ ${uniqueCandidates.map((candidate) => ` - ${candidate}`).join(`
1888
1893
  }
1889
1894
 
1890
1895
  // pkgs/@akanjs/cli/application/application.runner.ts
1891
- import { Logger as Logger3 } from "akanjs/common";
1892
- var loadBuildRunner = async () => (await import("./applicationBuildRunner-1yaqsrdq.js")).ApplicationBuildRunner;
1896
+ import { Logger as Logger3, toError } from "akanjs/common";
1897
+ var loadBuildRunner = async () => (await import("./applicationBuildRunner-8573zr2w.js")).ApplicationBuildRunner;
1893
1898
  var loadPrompts = async () => await import("@inquirer/prompts");
1894
1899
 
1895
1900
  class ApplicationRunner extends runner("application") {
@@ -1914,6 +1919,9 @@ class ApplicationRunner extends runner("application") {
1914
1919
  throw new Error(`No script files found. make a script file in apps/${app.name}/script folder`);
1915
1920
  }
1916
1921
  const scriptFiles = (await app.readdir("script")).filter((file) => file.endsWith(".ts"));
1922
+ assertPromptable("Missing argument 'filename'", {
1923
+ choices: scriptFiles.map((file) => file.replace(".ts", ""))
1924
+ });
1917
1925
  const scriptFile = await (await loadPrompts()).select({
1918
1926
  message: "Select script to run",
1919
1927
  choices: scriptFiles.map((file) => ({ name: file, value: file.replace(".ts", "") }))
@@ -2047,7 +2055,7 @@ try {
2047
2055
  await new (await loadBuildRunner())(app).typecheck(options);
2048
2056
  }
2049
2057
  async typecheckLibrary(lib, options = {}) {
2050
- const { ProjectTypecheck } = await import("./projectTypecheck-cvtkrzp2.js");
2058
+ const { ProjectTypecheck } = await import("./projectTypecheck-7djd4z50.js");
2051
2059
  const root = lib.workspace.workspaceRoot;
2052
2060
  await new ProjectTypecheck(lib.cwdPath, root, { ...process.env, AKAN_WORKSPACE_ROOT: root }).run([
2053
2061
  "../../server.ts",
@@ -2078,9 +2086,9 @@ try {
2078
2086
  fast = false,
2079
2087
  reporter,
2080
2088
  spinner = false,
2081
- profile
2089
+ envProfile
2082
2090
  } = {}) {
2083
- return new (await loadBuildRunner())(app, { fast, reporter, profile }).build({ spinner });
2091
+ return new (await loadBuildRunner())(app, { fast, reporter, profile: envProfile }).build({ spinner });
2084
2092
  }
2085
2093
  async start(app, {
2086
2094
  open = false,
@@ -2189,44 +2197,46 @@ try {
2189
2197
  };
2190
2198
  return canonical(reported) === canonical(own);
2191
2199
  }
2192
- async releaseIos(app, { target, env = "main", teamId, adHoc = false } = {}) {
2200
+ async releaseIos(app, { target, env = "main", teamId, adHoc = false, publicEnv } = {}) {
2193
2201
  const targets = await resolveMobileTargets(app, target);
2194
- await this.#buildMobileCsr(app, env);
2202
+ await this.#buildMobileCsr(app, env, undefined, publicEnv ? await NativePublicEnv.load(publicEnv) : null);
2195
2203
  for (const mobileTarget of targets)
2196
2204
  this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).releaseIos({ teamId, adHoc }));
2197
2205
  }
2198
- async releaseAndroid(app, format, { target, env = "main" } = {}) {
2206
+ async releaseAndroid(app, format, { target, env = "main", publicEnv } = {}) {
2199
2207
  const targets = await resolveMobileTargets(app, target);
2200
2208
  NativeApp.androidSigning();
2201
- await this.#buildMobileCsr(app, env);
2209
+ await this.#buildMobileCsr(app, env, undefined, publicEnv ? await NativePublicEnv.load(publicEnv) : null);
2202
2210
  for (const mobileTarget of targets)
2203
2211
  this.#reportBuild(app, mobileTarget, await new NativeApp(app, mobileTarget, env).releaseAndroid({ formats: [format] }));
2204
2212
  }
2205
- async packUpdate(app, platform, { target, env = "main", out, against }) {
2213
+ async packUpdate(app, platform, { target, env = "main", out, against, publicEnv }) {
2206
2214
  const targets = await resolveMobileTargets(app, target);
2207
2215
  const [mobileTarget] = targets;
2208
2216
  if (!mobileTarget || targets.length > 1)
2209
2217
  throw new Error("pack-update packs one native target at a time; pass --target <name>.");
2210
- await this.#buildMobileCsr(app, env);
2211
- const { pack } = await new NativeApp(app, mobileTarget, env).packUpdate(platform, { out });
2218
+ const nativeApp = new NativeApp(app, mobileTarget, env);
2219
+ await UpdatePackFolder.clear(path5.resolve(out ?? path5.join(nativeApp.updatesDir, platform)));
2220
+ const pinned = publicEnv ? await NativePublicEnv.load(publicEnv) : against ? await NativePublicEnv.recordedIn(against) : null;
2221
+ await this.#buildMobileCsr(app, env, undefined, pinned);
2222
+ const { pack } = await nativeApp.packUpdate(platform, { out });
2212
2223
  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
2224
  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)}
2217
- `);
2225
+ const bundle = path5.join(pack.out, UpdatePackFolder.bundleFile);
2226
+ const compat = await NativeApp.compareBundles(app.cwdPath, against, bundle);
2227
+ const envProblem = await NativePublicEnv.problemAgainst(against, bundle);
2228
+ await UpdatePackFolder.writeCompat(pack, {
2229
+ against,
2230
+ compatible: compat.compatible && !envProblem,
2231
+ problems: [...compat.problems, ...envProblem ? [envProblem] : []]
2232
+ });
2218
2233
  }
2219
- await NativeApp.writePack(pack);
2220
- if (!pack.compat)
2221
- return pack;
2222
- if (pack.compat.compatible) {
2234
+ await UpdatePackFolder.write(pack);
2235
+ const verdict = UpdatePackFolder.storeBuildVerdict(pack);
2236
+ if (verdict)
2237
+ throw verdict;
2238
+ if (pack.compat)
2223
2239
  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; it differs from the one ${against} came from:
2227
- - ${pack.compat.problems.join(`
2228
- - `)}`);
2229
- process.exitCode = 2;
2230
2240
  return pack;
2231
2241
  }
2232
2242
  async updateKeygen(app, platform, target) {
@@ -2237,16 +2247,27 @@ try {
2237
2247
  if (seen.has(appId))
2238
2248
  continue;
2239
2249
  seen.add(appId);
2240
- const { publicKey, keyPath, created } = await new NativeApp(app, mobileTarget).updateKeygen(nativePlatform);
2250
+ const { publicKey, keyId, keyPath, created } = await new NativeApp(app, mobileTarget).updateKeygen(nativePlatform);
2241
2251
  app.log(`${appId}: ${created ? "made" : "read"} ${keyPath}${created ? " (keep it private and backed up: without it, installed apps take no more updates)" : ""}`);
2242
2252
  app.log(` native: { updates: { url: "https://\u2026/${app.name}", publicKeys: [${JSON.stringify(publicKey)}] } }`);
2253
+ app.log(` keyId ${keyId}: what publish-update --revoke and updates.revokedKeys name this key by`);
2243
2254
  const { publicKey: one, publicKeys } = mobileTarget.config.updates ?? {};
2244
2255
  const declared = publicKeys ?? (one ? [one] : null);
2245
2256
  if (declared && !declared.includes(publicKey))
2246
2257
  app.logger.warn(`native.targets.${mobileTarget.name}.updates.publicKeys does not hold this key; releases it signs would not verify.`);
2247
2258
  }
2248
2259
  }
2249
- async publishUpdate(app, platform, { target, env = "main", channel, upload, arch, sign = false, notarize = false } = {}) {
2260
+ async publishUpdate(app, platform, {
2261
+ target,
2262
+ env = "main",
2263
+ channel,
2264
+ upload,
2265
+ arch,
2266
+ sign = false,
2267
+ notarize = false,
2268
+ revoke,
2269
+ resetSequence
2270
+ } = {}) {
2250
2271
  if (arch && platform !== "desktop")
2251
2272
  throw new Error(`--arch picks a desktop release's CPU; ${platform} publishes its web bundle.`);
2252
2273
  const targets = await resolveMobileTargets(app, target);
@@ -2260,7 +2281,9 @@ try {
2260
2281
  await new NativeApp(app, mobileTarget, env).assertPublishable(nativePlatform, {
2261
2282
  server: ApplicationRunner.carriesServer(mobileTarget, nativePlatform),
2262
2283
  ...channel ? { channel } : {},
2263
- ...arch ? { arch } : {}
2284
+ ...arch ? { arch } : {},
2285
+ revoke,
2286
+ resetSequence
2264
2287
  });
2265
2288
  const carried = await this.#stageMobile(app, nativePlatform, targets, env, arch);
2266
2289
  for (const mobileTarget of targets) {
@@ -2270,15 +2293,17 @@ try {
2270
2293
  ...arch ? { arch } : {},
2271
2294
  sign,
2272
2295
  notarize,
2296
+ revoke,
2297
+ resetSequence,
2273
2298
  ...carried && ApplicationRunner.carriesServer(mobileTarget, nativePlatform) ? { server: carried } : {}
2274
2299
  });
2275
2300
  this.#reportBuild(app, mobileTarget, published.build);
2276
2301
  app.log(`${app.name}/${mobileTarget.name} ${published.bundle}: ${published.dir}/${published.channel}.json, ${published.files} files, ${Math.round(published.size / 1024)} KiB`);
2277
2302
  if (!uploader) {
2278
- app.log(`Upload ${nativeApp.updatesDir} to ${mobileTarget.config.updates?.url}, the manifests last.`);
2303
+ app.log(`Upload ${nativeApp.updatesDir} to ${mobileTarget.config.updates?.url}, the manifests last, without pack-update's records (pack.json, compat.json, manifest.template.json, bundle.json); --upload s3://\u2026 sends just this release.`);
2279
2304
  continue;
2280
2305
  }
2281
- const { uploaded, skipped } = await uploader.upload(nativeApp.updatesDir);
2306
+ const { uploaded, skipped } = await uploader.upload(nativeApp.updatesDir, published);
2282
2307
  app.log(`Uploaded ${uploaded} file(s) to ${upload}, the manifests last; ${skipped} the bucket already held. Installed apps read ${mobileTarget.config.updates?.url}.`);
2283
2308
  }
2284
2309
  }
@@ -2300,13 +2325,14 @@ try {
2300
2325
  if (typeof server !== "object")
2301
2326
  return { omit: [], modules: [] };
2302
2327
  const { omit = [], modules = [], profile, env, restart, watchdog } = server;
2328
+ const { intervalMs, failures } = typeof watchdog === "object" ? watchdog : {};
2303
2329
  return {
2304
2330
  omit,
2305
2331
  modules,
2306
2332
  ...profile ? { profile } : {},
2307
2333
  ...env ? { env } : {},
2308
2334
  ...restart !== undefined ? { restart } : {},
2309
- ...typeof watchdog === "object" ? { watchdog } : {}
2335
+ ...intervalMs && failures ? { watchdog: { intervalMs, failures } } : {}
2310
2336
  };
2311
2337
  });
2312
2338
  const [first = { omit: [], modules: [] }, ...rest] = servers;
@@ -2318,7 +2344,7 @@ try {
2318
2344
  agree("omit different packages", ({ omit }) => omit.join(", "));
2319
2345
  agree("name different env profiles", ({ profile }) => profile ?? "");
2320
2346
  agree("mount different modules", ({ modules }) => modules.join(", "));
2321
- agree("set a different env", ({ env }) => env ? JSON.stringify(env) : "");
2347
+ agree("set a different env", ({ env }) => env ? JSON.stringify(env, Object.keys(env).sort()) : "");
2322
2348
  agree("restart differently", ({ restart }) => String(restart ?? ""));
2323
2349
  agree("watch it differently", ({ watchdog }) => watchdog ? JSON.stringify(watchdog) : "");
2324
2350
  return first;
@@ -2331,7 +2357,11 @@ try {
2331
2357
  const { modules, env: serverEnv, restart, watchdog } = server;
2332
2358
  return await stage?.prepare(env, { modules, env: serverEnv, restart, watchdog });
2333
2359
  }
2334
- async#buildMobileCsr(app, env, profile) {
2360
+ async#buildMobileCsr(app, env, envProfile, publicEnv = null) {
2361
+ if (publicEnv?.AKAN_PUBLIC_ENV !== undefined && publicEnv.AKAN_PUBLIC_ENV !== env)
2362
+ throw new Error(`--env ${env} differs from the public env's AKAN_PUBLIC_ENV ${publicEnv.AKAN_PUBLIC_ENV}.`);
2363
+ if (publicEnv)
2364
+ NativePublicEnv.pin(publicEnv);
2335
2365
  const prevEnv = {
2336
2366
  AKAN_PUBLIC_ENV: process.env.AKAN_PUBLIC_ENV,
2337
2367
  AKAN_PUBLIC_OPERATION_MODE: process.env.AKAN_PUBLIC_OPERATION_MODE,
@@ -2343,7 +2373,7 @@ try {
2343
2373
  APP_OPERATION_MODE: "release"
2344
2374
  });
2345
2375
  try {
2346
- await new (await loadBuildRunner())(app, { environment: env, profile }).build({ spinner: true });
2376
+ await new (await loadBuildRunner())(app, { environment: env, profile: envProfile }).build({ spinner: true });
2347
2377
  } finally {
2348
2378
  for (const [key, value] of Object.entries(prevEnv)) {
2349
2379
  if (value === undefined)
@@ -2352,6 +2382,11 @@ try {
2352
2382
  process.env[key] = value;
2353
2383
  }
2354
2384
  }
2385
+ if (!publicEnv)
2386
+ return;
2387
+ const differs = NativePublicEnv.diff(publicEnv, await NativePublicEnv.built(app) ?? {});
2388
+ if (differs.length)
2389
+ throw new Error(`The web build inlined other values than --public-env gave for ${differs.join(", ")}.`);
2355
2390
  }
2356
2391
  async#runMobileTargets(targets, task) {
2357
2392
  const results = [];
@@ -2367,7 +2402,7 @@ try {
2367
2402
  if (failures.length === 0)
2368
2403
  return;
2369
2404
  for (const failure of failures) {
2370
- const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
2405
+ const message = toError(failure.error).message;
2371
2406
  Logger3.rawLog(`Native target ${failure.target} failed: ${message}`, undefined, "error");
2372
2407
  }
2373
2408
  throw new Error(`${failures.length}/${results.length} native targets failed`);
@@ -3240,6 +3275,9 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3240
3275
  static devServerReadyTimeoutMs = DevSupervisor.bootTimeoutMs;
3241
3276
  #interrupt = new InterruptTeardown;
3242
3277
  async confirmDatabaseModeDependencyInstall(databaseMode, installSpecs) {
3278
+ assertPromptable(`Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}`, {
3279
+ remedy: `install them first with \`bun add ${installSpecs.join(" ")}\``
3280
+ });
3243
3281
  return await confirm({
3244
3282
  message: `Database mode '${databaseMode}' requires missing dependencies: ${installSpecs.join(", ")}. Install them now?`,
3245
3283
  default: true
@@ -3312,7 +3350,7 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3312
3350
  if (!quiet)
3313
3351
  Logger6.rawLog(`Creating an optimized production build for ${app.name}...`);
3314
3352
  try {
3315
- const result = await this.applicationRunner.build(app, { fast, spinner: !quiet, profile });
3353
+ const result = await this.applicationRunner.build(app, { fast, spinner: !quiet, envProfile: profile });
3316
3354
  Logger6.rawLog(`${app.name} built in dist/apps/${app.name}`);
3317
3355
  if (!quiet)
3318
3356
  ApplicationBuildReporter.printSummary(result);
@@ -3325,10 +3363,12 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3325
3363
  async typecheck(exec, { write = true, clean = false, incremental = true, tests = false } = {}) {
3326
3364
  if (!(exec instanceof AppExecutor) && !(exec instanceof LibExecutor))
3327
3365
  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)
3366
+ if (exec instanceof AppExecutor)
3367
+ await exec.scanSync({ write });
3368
+ else if (write)
3329
3369
  await this.libraryScript.syncLibrary(exec);
3330
3370
  else
3331
- await exec.scanSync({ write });
3371
+ await exec.scan({ write: false });
3332
3372
  const spinner = exec.spinning(`Typechecking ${exec.name}${tests ? " with its tests" : ""}...`);
3333
3373
  try {
3334
3374
  if (exec instanceof LibExecutor)
@@ -3593,13 +3633,13 @@ class ApplicationScript extends script("application", [ApplicationRunner, Librar
3593
3633
  await Promise.all(shares.map(async (share) => await share.close()));
3594
3634
  }, "Abandoning the tunnel release; the shares expire on their own.");
3595
3635
  await Promise.all(apps.map(async (app) => {
3596
- const { TunnelShare } = await import("./TunnelShare-q4jq08qg.js");
3636
+ const { TunnelShare } = await import("./TunnelShare-rj74jd6x.js");
3597
3637
  const share = await TunnelShare.open(app, { workspace: app.workspace });
3598
3638
  shares.push(share);
3599
3639
  urls.set(app.name, share.url);
3600
3640
  Logger6.rawLog(`${app.name} is shared at ${share.url}`);
3601
3641
  })).catch((error) => {
3602
- Logger6.rawLog(`Could not open the tunnel: ${error instanceof Error ? error.message : String(error)}`, undefined, "error");
3642
+ Logger6.rawLog(`Could not open the tunnel: ${toError2(error).message}`, undefined, "error");
3603
3643
  });
3604
3644
  return urls;
3605
3645
  }
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6jjtnhkw.js";
5
+ } from "./index-3x7yeecy.js";
6
6
  import {
7
7
  ModuleExecutor
8
- } from "./index-55mynnqs.js";
8
+ } from "./index-bv210r6t.js";
9
9
  import {
10
10
  sourceFile,
11
11
  moduleSourcePaths,
@@ -13,10 +13,10 @@ import {
13
13
  createPassedPrimitiveReport,
14
14
  bilingualLabelForField,
15
15
  bilingualDescriptionForField
16
- } from "./index-05g3jayg.js";
16
+ } from "./index-wdze51wz.js";
17
17
  import {
18
18
  PageScript
19
- } from "./index-2etcv0sc.js";
19
+ } from "./index-00seamy5.js";
20
20
  import {
21
21
  pluralizeName
22
22
  } from "./index-nz8prx9g.js";