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

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 (103) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-q50k4s45.js → CodeAgent-gzfc5t52.js} +2 -2
  3. package/{CodeAgentRpcHost-p7h6n2hp.js → CodeAgentRpcHost-9g27hmer.js} +1 -1
  4. package/{CodeTui-62g35ztk.js → CodeTui-bpsjs8wz.js} +47 -4
  5. package/TunnelShare-tfrnftgr.js +14 -0
  6. package/{agent.command-adfd5299.js → agent.command-67ysh3n6.js} +5 -5
  7. package/akanCode.js +7 -5
  8. package/{application.command-hj3gv1rx.js → application.command-q0tqw5pc.js} +38 -11
  9. package/{applicationBuildRunner-by10r9gf.js → applicationBuildRunner-wsszr305.js} +16 -9
  10. package/buildBatch.proc.js +7 -7
  11. package/{cloud.command-k2fhaape.js → cloud.command-e7tkxyx6.js} +17 -16
  12. package/cloud.script-krzh3d7k.js +24 -0
  13. package/{code.command-kt5zgcc2.js → code.command-ttk8tje2.js} +25 -12
  14. package/commandManifest.json +1 -1
  15. package/{context.command-8fgn42jw.js → context.command-bdsk2z2r.js} +17 -16
  16. package/context.runner-bxzv0gm0.js +25 -0
  17. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  18. package/{guideline.command-3yxxr2mm.js → guideline.command-man90n70.js} +2 -2
  19. package/guidelines/agentRule/agentRule.instruction.md +25 -10
  20. package/guidelines/conventions/conventions.instruction.md +486 -1084
  21. package/guidelines/cssRule/cssRule.instruction.md +16 -1
  22. package/guidelines/fieldRule/fieldRule.instruction.md +74 -0
  23. package/guidelines/framework/framework.instruction.md +4 -7
  24. package/guidelines/mcpRule/mcpRule.instruction.md +16 -9
  25. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  26. package/guidelines/modelStore/modelStore.instruction.md +16 -0
  27. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  28. package/guidelines/queryRule/queryRule.instruction.md +47 -12
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
  30. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  31. package/guidelines/transportRule/transportRule.instruction.md +107 -8
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +70 -7
  34. package/incrementalBuilder.proc.js +4 -4
  35. package/{index-rhbabmja.js → index-033wne2j.js} +260 -259
  36. package/{index-xpdgxsj5.js → index-1r5a8bzx.js} +2216 -2216
  37. package/{index-jwqtchha.js → index-27y8t17p.js} +1 -1
  38. package/{index-wa5jbxj1.js → index-2mrn1mrg.js} +1 -0
  39. package/{index-t7nws9mx.js → index-2w8j2fnv.js} +661 -448
  40. package/{index-0909wckt.js → index-35m6bzm7.js} +238 -236
  41. package/{index-sqfdmb5b.js → index-4393nb4b.js} +4 -4
  42. package/{index-sby8fe1p.js → index-48edp6q4.js} +1 -1
  43. package/{index-g0vwzy9m.js → index-4v43h00g.js} +20 -21
  44. package/{index-azh29dc8.js → index-54wc2phm.js} +19 -14
  45. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  46. package/index-62md4dwy.js +260 -0
  47. package/{index-hnby7p9n.js → index-75n5q4ph.js} +7 -7
  48. package/{index-fjsv10hd.js → index-844q7f8z.js} +7 -7
  49. package/{index-tv8fjaba.js → index-8jehkey8.js} +4 -4
  50. package/{index-bea2gwc6.js → index-9mt4smfy.js} +2 -2
  51. package/{index-d2242ksy.js → index-at7bz0xq.js} +3 -0
  52. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  53. package/{index-8zdwpxef.js → index-cae9m5jz.js} +119 -81
  54. package/{index-tz5evhdr.js → index-ebarbgba.js} +6 -6
  55. package/{index-2etqq75t.js → index-g6ax17x4.js} +1114 -1082
  56. package/{index-h85cxfkg.js → index-g9ks2k8c.js} +2 -2
  57. package/{index-nq045px5.js → index-h3r3nr9y.js} +356 -307
  58. package/{index-20cebtth.js → index-h6czey01.js} +79 -24
  59. package/{index-6726qr1m.js → index-jrmzcp66.js} +57 -58
  60. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  61. package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
  62. package/{index-rk9sdys3.js → index-nv79n88g.js} +5 -5
  63. package/{index-kc8z134d.js → index-nx64dz92.js} +38 -15
  64. package/{index-z507nzhg.js → index-p2r64bpa.js} +4 -4
  65. package/{index-nq2ja5e0.js → index-pg1t468s.js} +3 -3
  66. package/{index-3reaaa1h.js → index-rc51f9ht.js} +29 -27
  67. package/{index-e9prnmyt.js → index-ta1dychm.js} +3 -3
  68. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  69. package/{index-31ce9vhg.js → index-vkb9m4z7.js} +80 -38
  70. package/{index-58vvz603.js → index-xsd96yzw.js} +1 -1
  71. package/{index-pyxn1b53.js → index-zv5s8x58.js} +13 -13
  72. package/index.js +22 -22
  73. package/{library.command-8tw59xzj.js → library.command-6amzsrb8.js} +3 -3
  74. package/{localRegistry.command-tf3dmznd.js → localRegistry.command-yqc0nyhh.js} +15 -14
  75. package/{module.command-9vm8hffy.js → module.command-10x6jv6c.js} +5 -5
  76. package/{package.command-erbxe2tz.js → package.command-pwfvecjf.js} +4 -4
  77. package/package.json +21 -21
  78. package/{page.command-jpavcj0p.js → page.command-ahhgx9y1.js} +3 -3
  79. package/{pipeline.command-b8rzvjvv.js → pipeline.command-1ptr3e9y.js} +3 -3
  80. package/{primitive.command-sm32chzk.js → primitive.command-g5xwv6tj.js} +6 -6
  81. package/{projectTypecheck-jamtx95x.js → projectTypecheck-fx8x7cat.js} +1 -1
  82. package/{quality.command-pkd17n6y.js → quality.command-fw9ck061.js} +4 -4
  83. package/{repair.command-8y4mq6hs.js → repair.command-g8kecj96.js} +5 -5
  84. package/{scalar.command-q8khmyx5.js → scalar.command-fprap51w.js} +4 -4
  85. package/skills/akan-code/SKILL.md +4 -0
  86. package/skills/akan-store/SKILL.md +2 -1
  87. package/{subspace.command-51n0ykbx.js → subspace.command-sc0t527y.js} +11 -10
  88. package/templates/appSample/lib/task/Task.Util.tsx +7 -7
  89. package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
  90. package/templates/appSample/ui/QuantityControl.tsx +2 -2
  91. package/templates/crudSinglePage/page.tsx +1 -1
  92. package/templates/lib/cnst.ts +0 -1
  93. package/templates/migration/__migrationId__.ts +23 -0
  94. package/templates/server.ts +8 -1
  95. package/templates/workspaceRoot/.gitignore.template +23 -52
  96. package/templates/workspaceRoot/biome.json.template +1 -1
  97. package/{tunnel.command-7820vtxq.js → tunnel.command-mqnsqbwf.js} +8 -7
  98. package/{workflow.command-n5sv4wqz.js → workflow.command-j0jgkr13.js} +10 -10
  99. package/{workspace.command-zdy60jh8.js → workspace.command-1r9hvvw0.js} +29 -28
  100. package/TunnelShare-f1egrktx.js +0 -13
  101. package/cloud.script-z8p5dz7e.js +0 -23
  102. package/context.runner-hnh0rg7m.js +0 -24
  103. package/index-n68jx79w.js +0 -120
@@ -4,206 +4,21 @@ import {
4
4
  ServerEnvProfile,
5
5
  Executor,
6
6
  WorkspaceExecutor
7
- } from "./index-2etqq75t.js";
7
+ } from "./index-g6ax17x4.js";
8
8
  import {
9
9
  resolveAppId,
10
10
  targetHtmlFilename
11
- } from "./index-q958tqqg.js";
11
+ } from "./index-m9jj544v.js";
12
12
  import {
13
13
  ExitCodeError
14
- } from "./index-31ce9vhg.js";
14
+ } from "./index-vkb9m4z7.js";
15
15
  import {
16
16
  CsrArtifactBuilder
17
- } from "./index-kc8z134d.js";
18
-
19
- // pkgs/@akanjs/devkit/mobile/nativePublicEnv.ts
20
- import { createHash } from "crypto";
21
- class NativePublicEnv {
22
- static derivedKeys = [
23
- "AKAN_PUBLIC_APP_NAME",
24
- "AKAN_PUBLIC_BASE_PATHS",
25
- "AKAN_PUBLIC_API_PREFIX",
26
- "AKAN_PUBLIC_WS_PREFIX",
27
- "AKAN_PUBLIC_RENDER_ENV",
28
- "AKAN_PUBLIC_DEFAULT_LOCALE",
29
- "AKAN_PUBLIC_LOCALES"
30
- ];
31
- static #keyPattern = /^AKAN_PUBLIC_[A-Z0-9_]{1,128}$/;
32
- static #secretWords = ["SECRET", "TOKEN", "PASSWORD", "PRIVATE", "CREDENTIAL"];
33
- static #maxKeys = 256;
34
- static #maxBytes = 64 * 1024;
35
- static of(env) {
36
- return Object.fromEntries(Object.keys(env).filter((key) => key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key)).sort().map((key) => [key, String(env[key])]));
37
- }
38
- static hash(publicEnv) {
39
- return createHash("sha256").update(JSON.stringify(NativePublicEnv.of(publicEnv))).digest("hex");
40
- }
41
- static problems(publicEnv) {
42
- const keys = Object.keys(publicEnv);
43
- const problems = keys.flatMap((key) => {
44
- const value = publicEnv[key];
45
- if (!NativePublicEnv.#keyPattern.test(key))
46
- return [`${key} is not an AKAN_PUBLIC_[A-Z0-9_] name`];
47
- const word = NativePublicEnv.#secretWords.find((secret) => key.includes(secret));
48
- if (word)
49
- return [`${key} names a ${word.toLowerCase()}, which a page bundle must not carry`];
50
- if (typeof value !== "string")
51
- return [`${key} is not a string`];
52
- if (/[\u0000-\u001f\u007f]/.test(value))
53
- return [`${key} holds a control character`];
54
- return [];
55
- });
56
- if (keys.length > NativePublicEnv.#maxKeys)
57
- problems.push(`${keys.length} keys, more than ${NativePublicEnv.#maxKeys}`);
58
- const bytes = Buffer.byteLength(JSON.stringify(publicEnv));
59
- if (bytes > NativePublicEnv.#maxBytes)
60
- problems.push(`${bytes} bytes, more than ${NativePublicEnv.#maxBytes}`);
61
- return problems;
62
- }
63
- static diff(a, b) {
64
- return [...new Set([...Object.keys(a), ...Object.keys(b)])].filter((key) => a[key] !== b[key]).sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
65
- }
66
- static async built(app) {
67
- const file = Bun.file(CsrArtifactBuilder.publicEnvPath(app));
68
- if (!await file.exists())
69
- return null;
70
- return NativePublicEnv.of(await file.json());
71
- }
72
- static async recordedIn(bundleFile) {
73
- const file = Bun.file(bundleFile);
74
- if (!await file.exists())
75
- return null;
76
- const bundle = await file.json();
77
- return bundle.web?.publicEnv ?? null;
78
- }
79
- static async load(file) {
80
- const source = Bun.file(file);
81
- if (!await source.exists())
82
- throw new Error(`--public-env ${file} does not exist.`);
83
- const json = await source.json().catch(() => null);
84
- if (!json || typeof json !== "object" || Array.isArray(json))
85
- throw new Error(`--public-env ${file} is not a JSON object of AKAN_PUBLIC_* values or a bundle.json.`);
86
- const isBundle = json.schema === 1 && typeof json.web === "object";
87
- const map = isBundle ? json.web.publicEnv : json;
88
- if (!map)
89
- throw new Error(`--public-env ${file} is a bundle.json from a build that recorded no public env.`);
90
- const derived = Object.keys(map).filter((key) => NativePublicEnv.derivedKeys.includes(key));
91
- if (derived.length)
92
- throw new Error(`--public-env ${file} sets ${derived.join(", ")}, which akan.config.ts decides.`);
93
- const problems = NativePublicEnv.problems(map);
94
- if (problems.length)
95
- throw new Error(`--public-env ${file} is refused:
96
- - ${problems.join(`
97
- - `)}`);
98
- return NativePublicEnv.of(map);
99
- }
100
- static pin(publicEnv) {
101
- for (const key of Object.keys(process.env))
102
- if (key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key))
103
- delete process.env[key];
104
- Object.assign(process.env, publicEnv);
105
- WorkspaceExecutor.publicEnvPin = publicEnv;
106
- }
107
- static async problemAgainst(shipped, bundle) {
108
- const [before, now] = await Promise.all([NativePublicEnv.recordedIn(shipped), NativePublicEnv.recordedIn(bundle)]);
109
- if (!before)
110
- return null;
111
- const differs = NativePublicEnv.diff(before, now ?? {});
112
- if (!differs.length)
113
- return null;
114
- return `public env ${differs.join(", ")} differs from the store build's (pass --public-env ${shipped} to repeat it)`;
115
- }
116
- }
117
-
118
- // pkgs/@akanjs/devkit/mobile/updatePackFolder.ts
119
- import { mkdtemp, rm } from "fs/promises";
120
- import os from "os";
121
- import path from "path";
122
- class UpdatePackFolder {
123
- static packFile = "pack.json";
124
- static compatFile = "compat.json";
125
- static bundleFile = "bundle.json";
126
- static templateFile = "manifest.template.json";
127
- static filesDir = "files";
128
- static #records = [
129
- UpdatePackFolder.templateFile,
130
- UpdatePackFolder.bundleFile,
131
- UpdatePackFolder.packFile,
132
- UpdatePackFolder.compatFile
133
- ];
134
- static #sha256 = /^[0-9a-f]{64}$/;
135
- static async clear(dir) {
136
- await Promise.all([UpdatePackFolder.packFile, UpdatePackFolder.compatFile].map((name) => rm(path.join(dir, name), { force: true })));
137
- }
138
- static async read(dir) {
139
- const file = Bun.file(path.join(dir, UpdatePackFolder.packFile));
140
- if (!await file.exists())
141
- throw new Error(`${dir} holds no ${UpdatePackFolder.packFile}: give the folder \`akan pack-update\` wrote.`);
142
- const pack = await file.json();
143
- if (pack.schema !== 1)
144
- throw new Error(`${dir}/${UpdatePackFolder.packFile} is schema ${pack.schema}, and this akan reads schema 1.`);
145
- return { ...pack, out: path.resolve(dir) };
146
- }
147
- static async write({ out, ...record }) {
148
- await Bun.write(path.join(out, UpdatePackFolder.packFile), `${JSON.stringify(record, null, 2)}
149
- `);
150
- }
151
- static async writeCompat(pack, compat) {
152
- pack.compat = compat;
153
- await Bun.write(path.join(pack.out, UpdatePackFolder.compatFile), `${JSON.stringify(compat, null, 2)}
154
- `);
155
- }
156
- static async readBundle(file) {
157
- return await Bun.file(file).json();
158
- }
159
- static storeBuildVerdict({ out, compat }, then) {
160
- if (!compat || compat.compatible)
161
- return null;
162
- return new ExitCodeError(`The pack in ${out} needs a new store build; it differs from the one ${compat.against} came from:
163
- - ${compat.problems.join(`
164
- - `)}${then ? `
165
- ${then}` : ""}`, 2);
166
- }
167
- static async withArchive(pack, use) {
168
- const { templateFile, filesDir } = UpdatePackFolder;
169
- const template = Bun.file(path.join(pack.out, templateFile));
170
- if (!await template.exists())
171
- throw new Error(`${pack.out} holds no ${templateFile}: give the folder \`akan pack-update\` wrote.`);
172
- const { files = [] } = await template.json();
173
- const hashes = [...new Set(files.map((file) => file.sha256))];
174
- if (!hashes.every((hash) => typeof hash === "string" && UpdatePackFolder.#sha256.test(hash)))
175
- throw new Error(`${pack.out}/${templateFile} lists a file whose sha256 is not 64 hex digits.`);
176
- const present = await Promise.all(UpdatePackFolder.#records.map((name) => Bun.file(path.join(pack.out, name)).exists()));
177
- const entries = [
178
- ...UpdatePackFolder.#records.filter((_, idx) => present[idx]),
179
- ...hashes.map((hash) => `${filesDir}/${hash}`)
180
- ];
181
- const dir = await mkdtemp(path.join(os.tmpdir(), "akan-pack-"));
182
- try {
183
- const tar = Bun.spawn(["tar", "-czf", "pack.tgz", "-C", pack.out, "-T", "-"], {
184
- cwd: dir,
185
- stdin: new Blob([entries.map((entry) => `./${entry}
186
- `).join("")]),
187
- stdout: "ignore",
188
- stderr: "pipe"
189
- });
190
- const [code, stderr] = await Promise.all([tar.exited, new Response(tar.stderr).text()]);
191
- if (code !== 0)
192
- throw new Error(`tar of ${pack.out} failed: ${stderr.trim()}`);
193
- return await use(path.join(dir, "pack.tgz"));
194
- } finally {
195
- await rm(dir, { recursive: true, force: true });
196
- }
197
- }
198
- }
199
-
200
- // pkgs/@akanjs/devkit/mobile/nativeApp.ts
201
- import path8 from "path";
202
- import { toError } from "akanjs/common";
17
+ } from "./index-nx64dz92.js";
203
18
 
204
19
  // pkgs/@akanjs/devkit/mobile/desktopBin.ts
205
- import { chmod, mkdir, rename, rm as rm2, writeFile } from "fs/promises";
206
- import path2 from "path";
20
+ import { chmod, mkdir, rename, rm, writeFile } from "fs/promises";
21
+ import path from "path";
207
22
  class DesktopBin {
208
23
  app;
209
24
  config;
@@ -236,23 +51,23 @@ class DesktopBin {
236
51
  static fileName(name, executable, platform = process.platform) {
237
52
  if (platform !== "win32" || /\.(exe|cmd|bat|com)$/i.test(name))
238
53
  return name;
239
- return `${name}${path2.extname(executable).toLowerCase() || ".exe"}`;
54
+ return `${name}${path.extname(executable).toLowerCase() || ".exe"}`;
240
55
  }
241
56
  static downloadName(url) {
242
- const extension = /(?:\.tar)?\.[A-Za-z0-9]{1,8}$/.exec(path2.posix.basename(new URL(url).pathname))?.[0] ?? "";
57
+ const extension = /(?:\.tar)?\.[A-Za-z0-9]{1,8}$/.exec(path.posix.basename(new URL(url).pathname))?.[0] ?? "";
243
58
  return `download${extension.toLowerCase()}`;
244
59
  }
245
60
  static extractCommand(archive, dir, platform = process.platform) {
246
61
  if (platform === "linux" && /\.zip$/i.test(archive))
247
62
  return ["unzip", "-q", "-o", archive, "-d", dir];
248
- const tar = platform === "win32" ? path2.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
63
+ const tar = platform === "win32" ? path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
249
64
  return [tar, "-xf", archive, "-C", dir];
250
65
  }
251
66
  static async sha256(file) {
252
67
  return new Bun.CryptoHasher("sha256").update(await Bun.file(file).arrayBuffer()).digest("hex");
253
68
  }
254
69
  get #cacheDir() {
255
- return path2.join(this.app.cwdPath, ".akan", "cache", "bin");
70
+ return path.join(this.app.cwdPath, ".akan", "cache", "bin");
256
71
  }
257
72
  async stage(dir, arch = process.arch) {
258
73
  const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? await this.app.scan({ write: false });
@@ -262,14 +77,14 @@ class DesktopBin {
262
77
  const platform = DesktopBin.platform(arch);
263
78
  if (!platform)
264
79
  throw new Error(`bin: this desktop app is for ${process.platform}-${arch}, and a bin names only ${binPlatforms.join(", ")}.`);
265
- await rm2(dir, { recursive: true, force: true });
80
+ await rm(dir, { recursive: true, force: true });
266
81
  await mkdir(dir, { recursive: true });
267
82
  for (const [name, { owner, platforms }] of chosen) {
268
83
  const source = platforms[platform];
269
84
  if (!source)
270
85
  throw new Error(`${owner}: bin.${name} names no ${platform} file, the platform this desktop app is built for (it names ${Object.keys(platforms).join(", ") || "none"}).`);
271
86
  const executable = await this.#executable(`${owner}: bin.${name}.${platform}`, source);
272
- const target = path2.join(dir, DesktopBin.fileName(name, executable));
87
+ const target = path.join(dir, DesktopBin.fileName(name, executable));
273
88
  await Bun.write(target, Bun.file(executable));
274
89
  await chmod(target, 493);
275
90
  this.app.logger.info(`The desktop app carries ${name} from ${"url" in source ? source.url : source.path}`);
@@ -283,14 +98,14 @@ class DesktopBin {
283
98
  if (!source.file)
284
99
  return file;
285
100
  const key = "url" in source ? source.sha256 : await DesktopBin.sha256(file);
286
- const executable = path2.join(await this.#extract(file, key), ...source.file.split("/"));
101
+ const executable = path.join(await this.#extract(file, key), ...source.file.split("/"));
287
102
  if (!await Bun.file(executable).exists())
288
103
  throw new Error(`${at}: the archive holds no ${source.file}`);
289
104
  return executable;
290
105
  }
291
106
  async#download(at, { url, sha256 }) {
292
- const dir = path2.join(this.#cacheDir, sha256);
293
- const file = path2.join(dir, DesktopBin.downloadName(url));
107
+ const dir = path.join(this.#cacheDir, sha256);
108
+ const file = path.join(dir, DesktopBin.downloadName(url));
294
109
  if (await Bun.file(file).exists())
295
110
  return file;
296
111
  await mkdir(dir, { recursive: true });
@@ -307,10 +122,10 @@ class DesktopBin {
307
122
  return file;
308
123
  }
309
124
  async#extract(archive, key) {
310
- const dir = path2.join(this.#cacheDir, key, "extracted");
125
+ const dir = path.join(this.#cacheDir, key, "extracted");
311
126
  if (await Bun.file(`${dir}.done`).exists())
312
127
  return dir;
313
- await rm2(dir, { recursive: true, force: true });
128
+ await rm(dir, { recursive: true, force: true });
314
129
  await mkdir(dir, { recursive: true });
315
130
  const [command = "tar", ...args] = DesktopBin.extractCommand(archive, dir);
316
131
  if (command === "unzip" && !Bun.which("unzip"))
@@ -322,7 +137,7 @@ class DesktopBin {
322
137
  }
323
138
 
324
139
  // pkgs/@akanjs/devkit/mobile/nativeApi.ts
325
- import path3 from "path";
140
+ import path2 from "path";
326
141
 
327
142
  class NativeApi {
328
143
  static supportedMajor = 0;
@@ -334,7 +149,7 @@ class NativeApi {
334
149
  const akanjsPackage = NativeApi.#resolve("akanjs/package.json", appDir);
335
150
  if (!akanjsPackage)
336
151
  throw new Error(`akanjs is not installed for ${appDir}; the native build ships inside it.`);
337
- return path3.join(path3.dirname(akanjsPackage), "vendor", "@akanjs", "native", "packages", "cli", "src", "api.ts");
152
+ return path2.join(path2.dirname(akanjsPackage), "vendor", "@akanjs", "native", "packages", "cli", "src", "api.ts");
338
153
  }
339
154
  static async load(appDir) {
340
155
  const entry = NativeApi.entryOf(appDir);
@@ -355,6 +170,10 @@ class NativeApi {
355
170
  }
356
171
  }
357
172
 
173
+ // pkgs/@akanjs/devkit/mobile/nativeApp.ts
174
+ import path8 from "path";
175
+ import { toError } from "akanjs/common";
176
+
358
177
  // pkgs/@akanjs/devkit/mobile/nativeAppLine.ts
359
178
  class NativeAppLine {
360
179
  static #page = /^\[page(\+)?(?:#(\d+))? (trace|verbose|debug|log|info|warn|error)\] ?(.*)$/;
@@ -412,7 +231,8 @@ class NativeAppLine {
412
231
 
413
232
  // pkgs/@akanjs/devkit/mobile/nativeConfig.ts
414
233
  import { readFileSync } from "fs";
415
- import path4 from "path";
234
+ import path3 from "path";
235
+ import { trimSlashes } from "akanjs/common";
416
236
 
417
237
  // pkgs/@akanjs/devkit/mobile/usageDescriptions.ts
418
238
  import { capitalize } from "akanjs/common";
@@ -494,11 +314,11 @@ class NativeConfig {
494
314
  warnings.push(`Permission '${permission}' has no native plugin yet; the app ships without it.`);
495
315
  return used;
496
316
  });
497
- const abs = (relative) => path4.resolve(appPath, relative);
317
+ const abs = (relative) => path3.resolve(appPath, relative);
498
318
  const updates = NativeConfig.#updates(target, env);
499
- const folders = new Set(nativePlugins.map((plugin) => path4.resolve(plugin.dir)));
319
+ const folders = new Set(nativePlugins.map((plugin) => path3.resolve(plugin.dir)));
500
320
  const folderIds = new Set(nativePlugins.map((plugin) => plugin.id));
501
- const listed = (target.plugins ?? []).filter((spec) => NativeConfig.#isId(spec) ? !folderIds.has(spec) : !folders.has(path4.resolve(appPath, spec)));
321
+ const listed = (target.plugins ?? []).filter((spec) => NativeConfig.#isId(spec) ? !folderIds.has(spec) : !folders.has(path3.resolve(appPath, spec)));
502
322
  const plugins = [
503
323
  ...new Set([
504
324
  ...NativeConfig.basePlugins,
@@ -554,7 +374,7 @@ class NativeConfig {
554
374
  app: {
555
375
  id: resolveAppId(target.appId, platform),
556
376
  name: target.appName,
557
- fileName: target.fileName ?? NativeConfig.fileNameOf(path4.basename(appPath)),
377
+ fileName: target.fileName ?? NativeConfig.fileNameOf(path3.basename(appPath)),
558
378
  version: target.version,
559
379
  build: target.buildNum
560
380
  },
@@ -614,20 +434,20 @@ class NativeConfig {
614
434
  return url && keys ? { ...rest, url, ...keys, ...channel ? { channel } : {} } : undefined;
615
435
  }
616
436
  static fileNameOf(name) {
617
- return name.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "app";
437
+ return /[^-.](?:.*[^-.])?/.exec(name.replace(/[^A-Za-z0-9._-]/g, "-"))?.[0] ?? "app";
618
438
  }
619
439
  static #pluginId(appPath, spec) {
620
440
  if (NativeConfig.#isId(spec))
621
441
  return spec;
622
442
  try {
623
- const { id } = JSON.parse(readFileSync(path4.join(path4.resolve(appPath, spec), "native-plugin.json"), "utf8"));
443
+ const { id } = JSON.parse(readFileSync(path3.join(path3.resolve(appPath, spec), "native-plugin.json"), "utf8"));
624
444
  return typeof id === "string" ? id : spec;
625
445
  } catch {
626
446
  return spec;
627
447
  }
628
448
  }
629
449
  static #isId(spec) {
630
- return !spec.startsWith(".") && !path4.isAbsolute(spec);
450
+ return !spec.startsWith(".") && !path3.isAbsolute(spec);
631
451
  }
632
452
  static #compact(value) {
633
453
  const kept = Object.entries(value).filter(([, entry]) => Array.isArray(entry) ? entry.length > 0 : entry && typeof entry === "object" ? Object.keys(entry).length > 0 : entry !== undefined);
@@ -637,7 +457,7 @@ class NativeConfig {
637
457
  return Object.assign({}, ...contributed, own);
638
458
  }
639
459
  static #deepLinks(target, locales) {
640
- const basePath = target.basePath?.replace(/^\/+|\/+$/g, "");
460
+ const basePath = target.basePath ? trimSlashes(target.basePath) : "";
641
461
  const pathPrefixes = basePath ? locales.map((locale) => `/${locale}/${basePath}`) : undefined;
642
462
  return {
643
463
  ...target.deepLinks?.schemes?.length ? { schemes: target.deepLinks.schemes } : {},
@@ -648,23 +468,23 @@ class NativeConfig {
648
468
 
649
469
  // pkgs/@akanjs/devkit/mobile/nativePluginFolders.ts
650
470
  import { readdir } from "fs/promises";
651
- import path5 from "path";
471
+ import path4 from "path";
652
472
 
653
473
  class NativePluginFolders {
654
474
  static folder = "native";
655
475
  static async in(root, owner) {
656
- const base = path5.join(root, NativePluginFolders.folder);
476
+ const base = path4.join(root, NativePluginFolders.folder);
657
477
  const entries = await readdir(base, { withFileTypes: true }).catch(() => []);
658
478
  const folders = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
659
479
  return await Promise.all(folders.sort().map(async (name) => {
660
480
  const where = `${owner}/${NativePluginFolders.folder}/${name}`;
661
- const manifest = Bun.file(path5.join(base, name, "native-plugin.json"));
481
+ const manifest = Bun.file(path4.join(base, name, "native-plugin.json"));
662
482
  if (!await manifest.exists())
663
483
  throw new Error(`${where} has no native-plugin.json; a folder in native/ is one native plugin.`);
664
484
  const { id } = await manifest.json();
665
485
  if (id !== name)
666
486
  throw new Error(`${where}/native-plugin.json has id ${JSON.stringify(id)}; name the folder after its id.`);
667
- return { id, dir: path5.join(base, name), owner };
487
+ return { id, dir: path4.join(base, name), owner };
668
488
  }));
669
489
  }
670
490
  static select(own, fromLibs) {
@@ -683,15 +503,115 @@ class NativePluginFolders {
683
503
  const root = app.workspace.workspaceRoot;
684
504
  const [own, ...fromLibs] = await Promise.all([
685
505
  NativePluginFolders.in(app.cwdPath, `apps/${app.name}`),
686
- ...scanInfo.getLibs().map((lib) => NativePluginFolders.in(path5.join(root, "libs", lib), `libs/${lib}`))
506
+ ...scanInfo.getLibs().map((lib) => NativePluginFolders.in(path4.join(root, "libs", lib), `libs/${lib}`))
687
507
  ]);
688
508
  return NativePluginFolders.select(own ?? [], fromLibs.flat());
689
509
  }
690
510
  }
691
511
 
512
+ // pkgs/@akanjs/devkit/mobile/nativePublicEnv.ts
513
+ import { createHash } from "crypto";
514
+ class NativePublicEnv {
515
+ static derivedKeys = [
516
+ "AKAN_PUBLIC_APP_NAME",
517
+ "AKAN_PUBLIC_BASE_PATHS",
518
+ "AKAN_PUBLIC_API_PREFIX",
519
+ "AKAN_PUBLIC_WS_PREFIX",
520
+ "AKAN_PUBLIC_RENDER_ENV",
521
+ "AKAN_PUBLIC_DEFAULT_LOCALE",
522
+ "AKAN_PUBLIC_LOCALES"
523
+ ];
524
+ static #keyPattern = /^AKAN_PUBLIC_[A-Z0-9_]{1,128}$/;
525
+ static #secretWords = ["SECRET", "TOKEN", "PASSWORD", "PRIVATE", "CREDENTIAL"];
526
+ static #maxKeys = 256;
527
+ static #maxBytes = 64 * 1024;
528
+ static of(env) {
529
+ return Object.fromEntries(Object.keys(env).filter((key) => key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key)).sort().map((key) => [key, String(env[key])]));
530
+ }
531
+ static hash(publicEnv) {
532
+ return createHash("sha256").update(JSON.stringify(NativePublicEnv.of(publicEnv))).digest("hex");
533
+ }
534
+ static problems(publicEnv) {
535
+ const keys = Object.keys(publicEnv);
536
+ const problems = keys.flatMap((key) => {
537
+ const value = publicEnv[key];
538
+ if (!NativePublicEnv.#keyPattern.test(key))
539
+ return [`${key} is not an AKAN_PUBLIC_[A-Z0-9_] name`];
540
+ const word = NativePublicEnv.#secretWords.find((secret) => key.includes(secret));
541
+ if (word)
542
+ return [`${key} names a ${word.toLowerCase()}, which a page bundle must not carry`];
543
+ if (typeof value !== "string")
544
+ return [`${key} is not a string`];
545
+ if (/[\u0000-\u001f\u007f]/.test(value))
546
+ return [`${key} holds a control character`];
547
+ return [];
548
+ });
549
+ if (keys.length > NativePublicEnv.#maxKeys)
550
+ problems.push(`${keys.length} keys, more than ${NativePublicEnv.#maxKeys}`);
551
+ const bytes = Buffer.byteLength(JSON.stringify(publicEnv));
552
+ if (bytes > NativePublicEnv.#maxBytes)
553
+ problems.push(`${bytes} bytes, more than ${NativePublicEnv.#maxBytes}`);
554
+ return problems;
555
+ }
556
+ static diff(a, b) {
557
+ return [...new Set([...Object.keys(a), ...Object.keys(b)])].filter((key) => a[key] !== b[key]).sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
558
+ }
559
+ static async built(app) {
560
+ const file = Bun.file(CsrArtifactBuilder.publicEnvPath(app));
561
+ if (!await file.exists())
562
+ return null;
563
+ return NativePublicEnv.of(await file.json());
564
+ }
565
+ static async recordedIn(bundleFile) {
566
+ const file = Bun.file(bundleFile);
567
+ if (!await file.exists())
568
+ return null;
569
+ const bundle = await file.json();
570
+ return bundle.web?.publicEnv ?? null;
571
+ }
572
+ static async load(file) {
573
+ const source = Bun.file(file);
574
+ if (!await source.exists())
575
+ throw new Error(`--public-env ${file} does not exist.`);
576
+ const json = await source.json().catch(() => null);
577
+ if (!json || typeof json !== "object" || Array.isArray(json))
578
+ throw new Error(`--public-env ${file} is not a JSON object of AKAN_PUBLIC_* values or a bundle.json.`);
579
+ const isBundle = json.schema === 1 && typeof json.web === "object";
580
+ const map = isBundle ? json.web.publicEnv : json;
581
+ if (!map)
582
+ throw new Error(`--public-env ${file} is a bundle.json from a build that recorded no public env.`);
583
+ const derived = Object.keys(map).filter((key) => NativePublicEnv.derivedKeys.includes(key));
584
+ if (derived.length)
585
+ throw new Error(`--public-env ${file} sets ${derived.join(", ")}, which akan.config.ts decides.`);
586
+ const problems = NativePublicEnv.problems(map);
587
+ if (problems.length)
588
+ throw new Error(`--public-env ${file} is refused:
589
+ - ${problems.join(`
590
+ - `)}`);
591
+ return NativePublicEnv.of(map);
592
+ }
593
+ static pin(publicEnv) {
594
+ for (const key of Object.keys(process.env))
595
+ if (key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key))
596
+ delete process.env[key];
597
+ Object.assign(process.env, publicEnv);
598
+ WorkspaceExecutor.publicEnvPin = publicEnv;
599
+ }
600
+ static async problemAgainst(shipped, bundle) {
601
+ const [before, now] = await Promise.all([NativePublicEnv.recordedIn(shipped), NativePublicEnv.recordedIn(bundle)]);
602
+ if (!before)
603
+ return null;
604
+ const differs = NativePublicEnv.diff(before, now ?? {});
605
+ if (!differs.length)
606
+ return null;
607
+ return `public env ${differs.join(", ")} differs from the store build's (pass --public-env ${shipped} to repeat it)`;
608
+ }
609
+ }
610
+
692
611
  // pkgs/@akanjs/devkit/mobile/nativeWebDir.ts
693
- import { cp, mkdir as mkdir2, rm as rm3, stat } from "fs/promises";
694
- import path6 from "path";
612
+ import { cp, mkdir as mkdir2, rm as rm2, stat } from "fs/promises";
613
+ import path5 from "path";
614
+ import { trimSlashes as trimSlashes2 } from "akanjs/common";
695
615
 
696
616
  class NativeWebDir {
697
617
  dir;
@@ -702,13 +622,13 @@ class NativeWebDir {
702
622
  static injectTarget(html, target) {
703
623
  if (html.includes(NativeWebDir.#targetTag))
704
624
  return html;
705
- const basePath = target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
625
+ const basePath = target.basePath ? trimSlashes2(target.basePath) : "";
706
626
  const script = `${NativeWebDir.#targetTag}${JSON.stringify({
707
627
  name: target.name,
708
628
  basePath,
709
629
  indexPath: target.indexPath
710
630
  })};</script>`;
711
- const head = /<head(?:\s[^>]*)?>/i.exec(html);
631
+ const head = /<head(?:\s[^<>]*)?>/i.exec(html);
712
632
  if (!head)
713
633
  return `${script}
714
634
  ${html}`;
@@ -718,7 +638,7 @@ ${script}${html.slice(at)}`;
718
638
  }
719
639
  static #targetTag = "<script>window.__AKAN_MOBILE_TARGET__=";
720
640
  async clear() {
721
- await rm3(this.dir, { recursive: true, force: true });
641
+ await rm2(this.dir, { recursive: true, force: true });
722
642
  await mkdir2(this.dir, { recursive: true });
723
643
  }
724
644
  async assemble(target, { html, publicDir, fontsDir }) {
@@ -728,12 +648,12 @@ ${script}${html.slice(at)}`;
728
648
  if (publicDir && await NativeWebDir.#isDir(publicDir))
729
649
  await cp(publicDir, this.dir, { recursive: true });
730
650
  if (fontsDir && await NativeWebDir.#isDir(fontsDir))
731
- await cp(fontsDir, path6.join(this.dir, "_akan", "fonts"), { recursive: true });
732
- if (await NativeWebDir.#isDir(path6.join(this.dir, NativeWebDir.reservedEntry)))
651
+ await cp(fontsDir, path5.join(this.dir, "_akan", "fonts"), { recursive: true });
652
+ if (await NativeWebDir.#isDir(path5.join(this.dir, NativeWebDir.reservedEntry)))
733
653
  throw new Error(`public/${NativeWebDir.reservedEntry} is reserved for the native runtime.`);
734
- await Bun.write(path6.join(this.dir, "index.html"), NativeWebDir.injectTarget(await Bun.file(html).text(), target));
654
+ await Bun.write(path5.join(this.dir, "index.html"), NativeWebDir.injectTarget(await Bun.file(html).text(), target));
735
655
  const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: this.dir, onlyFiles: true, dot: true }));
736
- return files.map((file) => file.split(path6.sep).join("/")).sort();
656
+ return files.map((file) => file.split(path5.sep).join("/")).sort();
737
657
  }
738
658
  static async#isDir(dir) {
739
659
  try {
@@ -746,7 +666,7 @@ ${script}${html.slice(at)}`;
746
666
 
747
667
  // pkgs/@akanjs/devkit/mobile/serverBundleSecrets.ts
748
668
  import { readdir as readdir2, readFile, stat as stat2 } from "fs/promises";
749
- import path7 from "path";
669
+ import path6 from "path";
750
670
  class ServerBundleSecrets {
751
671
  workspaceRoot;
752
672
  appDir;
@@ -758,8 +678,8 @@ class ServerBundleSecrets {
758
678
  this.appDir = appDir;
759
679
  }
760
680
  async envFiles() {
761
- const dirs = [path7.join(this.appDir, "env"), ...(await this.#libDirs()).map((lib) => path7.join(lib, "env"))];
762
- const files = await Promise.all(dirs.map(async (dir) => (await readdir2(dir).catch(() => [])).filter((name) => /^env\.server\.[A-Za-z0-9.-]+\.ts$/.test(name) && !/\.(type|example)\.ts$/.test(name)).map((name) => path7.join(dir, name))));
681
+ const dirs = [path6.join(this.appDir, "env"), ...(await this.#libDirs()).map((lib) => path6.join(lib, "env"))];
682
+ const files = await Promise.all(dirs.map(async (dir) => (await readdir2(dir).catch(() => [])).filter((name) => /^env\.server\.[A-Za-z0-9.-]+\.ts$/.test(name) && !/\.(type|example)\.ts$/.test(name)).map((name) => path6.join(dir, name))));
763
683
  return files.flat();
764
684
  }
765
685
  async assertClean(bundleDir) {
@@ -767,7 +687,7 @@ class ServerBundleSecrets {
767
687
  const secrets = [];
768
688
  for (const file of await this.envFiles()) {
769
689
  try {
770
- const value = await ServerEnvProfile.evaluate(file, path7.dirname(path7.dirname(file)));
690
+ const value = await ServerEnvProfile.evaluate(file, path6.dirname(path6.dirname(file)));
771
691
  secrets.push(...ServerEnvProfile.secretValues(value).map((secret) => ({ file, ...secret })));
772
692
  } catch {
773
693
  unread.push(ServerBundleSecrets.#relative(this.workspaceRoot, file));
@@ -807,8 +727,8 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
807
727
  return coded;
808
728
  }
809
729
  async#libDirs() {
810
- const libsDir = path7.join(this.workspaceRoot, "libs");
811
- return (await readdir2(libsDir).catch(() => [])).map((lib) => path7.join(libsDir, lib));
730
+ const libsDir = path6.join(this.workspaceRoot, "libs");
731
+ return (await readdir2(libsDir).catch(() => [])).map((lib) => path6.join(libsDir, lib));
812
732
  }
813
733
  static #needles(value) {
814
734
  const escaped = JSON.stringify(value).slice(1, -1);
@@ -821,12 +741,12 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
821
741
  return /^export default (["'`])([\s\S]*)\1;?\s*$/.exec(code)?.[2] ?? value;
822
742
  }
823
743
  static #relative(from, to) {
824
- return path7.relative(from, to).split(path7.sep).join("/");
744
+ return path6.relative(from, to).split(path6.sep).join("/");
825
745
  }
826
746
  static async#files(dir) {
827
747
  const files = [];
828
748
  for (const entry of await readdir2(dir, { withFileTypes: true })) {
829
- const file = path7.join(dir, entry.name);
749
+ const file = path6.join(dir, entry.name);
830
750
  if (entry.isDirectory()) {
831
751
  if (entry.name !== "node_modules")
832
752
  files.push(...await ServerBundleSecrets.#files(file));
@@ -840,7 +760,7 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
840
760
  for (const entry of await readdir2(dir, { withFileTypes: true }).catch(() => [])) {
841
761
  if (entry.name.startsWith("."))
842
762
  continue;
843
- const file = path7.join(dir, entry.name);
763
+ const file = path6.join(dir, entry.name);
844
764
  if (entry.isDirectory()) {
845
765
  if (!ServerBundleSecrets.#notSource.has(entry.name))
846
766
  files.push(...await ServerBundleSecrets.#sources(file));
@@ -851,6 +771,88 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
851
771
  }
852
772
  }
853
773
 
774
+ // pkgs/@akanjs/devkit/mobile/updatePackFolder.ts
775
+ import { mkdtemp, rm as rm3 } from "fs/promises";
776
+ import os from "os";
777
+ import path7 from "path";
778
+ class UpdatePackFolder {
779
+ static packFile = "pack.json";
780
+ static compatFile = "compat.json";
781
+ static bundleFile = "bundle.json";
782
+ static templateFile = "manifest.template.json";
783
+ static filesDir = "files";
784
+ static #records = [
785
+ UpdatePackFolder.templateFile,
786
+ UpdatePackFolder.bundleFile,
787
+ UpdatePackFolder.packFile,
788
+ UpdatePackFolder.compatFile
789
+ ];
790
+ static #sha256 = /^[0-9a-f]{64}$/;
791
+ static async clear(dir) {
792
+ await Promise.all([UpdatePackFolder.packFile, UpdatePackFolder.compatFile].map((name) => rm3(path7.join(dir, name), { force: true })));
793
+ }
794
+ static async read(dir) {
795
+ const file = Bun.file(path7.join(dir, UpdatePackFolder.packFile));
796
+ if (!await file.exists())
797
+ throw new Error(`${dir} holds no ${UpdatePackFolder.packFile}: give the folder \`akan pack-update\` wrote.`);
798
+ const pack = await file.json();
799
+ if (pack.schema !== 1)
800
+ throw new Error(`${dir}/${UpdatePackFolder.packFile} is schema ${pack.schema}, and this akan reads schema 1.`);
801
+ return { ...pack, out: path7.resolve(dir) };
802
+ }
803
+ static async write({ out, ...record }) {
804
+ await Bun.write(path7.join(out, UpdatePackFolder.packFile), `${JSON.stringify(record, null, 2)}
805
+ `);
806
+ }
807
+ static async writeCompat(pack, compat) {
808
+ pack.compat = compat;
809
+ await Bun.write(path7.join(pack.out, UpdatePackFolder.compatFile), `${JSON.stringify(compat, null, 2)}
810
+ `);
811
+ }
812
+ static async readBundle(file) {
813
+ return await Bun.file(file).json();
814
+ }
815
+ static storeBuildVerdict({ out, compat }, then) {
816
+ if (!compat || compat.compatible)
817
+ return null;
818
+ return new ExitCodeError(`The pack in ${out} needs a new store build; it differs from the one ${compat.against} came from:
819
+ - ${compat.problems.join(`
820
+ - `)}${then ? `
821
+ ${then}` : ""}`, 2);
822
+ }
823
+ static async withArchive(pack, use) {
824
+ const { templateFile, filesDir } = UpdatePackFolder;
825
+ const template = Bun.file(path7.join(pack.out, templateFile));
826
+ if (!await template.exists())
827
+ throw new Error(`${pack.out} holds no ${templateFile}: give the folder \`akan pack-update\` wrote.`);
828
+ const { files = [] } = await template.json();
829
+ const hashes = [...new Set(files.map((file) => file.sha256))];
830
+ if (!hashes.every((hash) => typeof hash === "string" && UpdatePackFolder.#sha256.test(hash)))
831
+ throw new Error(`${pack.out}/${templateFile} lists a file whose sha256 is not 64 hex digits.`);
832
+ const present = await Promise.all(UpdatePackFolder.#records.map((name) => Bun.file(path7.join(pack.out, name)).exists()));
833
+ const entries = [
834
+ ...UpdatePackFolder.#records.filter((_, idx) => present[idx]),
835
+ ...hashes.map((hash) => `${filesDir}/${hash}`)
836
+ ];
837
+ const dir = await mkdtemp(path7.join(os.tmpdir(), "akan-pack-"));
838
+ try {
839
+ const tar = Bun.spawn(["tar", "-czf", "pack.tgz", "-C", pack.out, "-T", "-"], {
840
+ cwd: dir,
841
+ stdin: new Blob([entries.map((entry) => `./${entry}
842
+ `).join("")]),
843
+ stdout: "ignore",
844
+ stderr: "pipe"
845
+ });
846
+ const [code, stderr] = await Promise.all([tar.exited, new Response(tar.stderr).text()]);
847
+ if (code !== 0)
848
+ throw new Error(`tar of ${pack.out} failed: ${stderr.trim()}`);
849
+ return await use(path7.join(dir, "pack.tgz"));
850
+ } finally {
851
+ await rm3(dir, { recursive: true, force: true });
852
+ }
853
+ }
854
+ }
855
+
854
856
  // pkgs/@akanjs/devkit/mobile/nativeApp.ts
855
857
  class NativeApp {
856
858
  app;