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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-kyprmy06.js → CodeAgent-rkjyxgec.js} +2 -2
  3. package/{CodeTui-21mtze3j.js → CodeTui-h7pyj6xa.js} +1 -1
  4. package/TunnelShare-661cxz5e.js +14 -0
  5. package/{agent.command-f3gsc1gd.js → agent.command-3qxf4xcj.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-5s780vnz.js → application.command-m1kcd7jf.js} +54 -42
  8. package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-dj0xkrep.js} +26 -33
  9. package/buildBatch.proc.js +24 -15
  10. package/{cloud.command-yj6xb2a7.js → cloud.command-9cp1t0f5.js} +60 -67
  11. package/cloud.script-dgstqt1m.js +24 -0
  12. package/{code.command-3wkn4ja5.js → code.command-0ad6gw2a.js} +6 -6
  13. package/{context.command-9jjn3gcw.js → context.command-0qx447mb.js} +17 -13
  14. package/context.runner-1vsjztjg.js +25 -0
  15. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  16. package/{guideline.command-dtczrqpf.js → guideline.command-cv5bmrx2.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +3 -3
  18. package/guidelines/conventions/conventions.instruction.md +457 -1071
  19. package/guidelines/cssRule/cssRule.instruction.md +28 -7
  20. package/guidelines/fieldRule/fieldRule.instruction.md +62 -0
  21. package/guidelines/framework/framework.instruction.md +4 -7
  22. package/guidelines/mcpRule/mcpRule.instruction.md +43 -20
  23. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  24. package/guidelines/modelStore/modelStore.instruction.md +9 -0
  25. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  26. package/guidelines/queryRule/queryRule.instruction.md +44 -11
  27. package/guidelines/runtimeRule/runtimeRule.instruction.md +113 -17
  28. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  29. package/guidelines/transportRule/transportRule.instruction.md +135 -13
  30. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  31. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +68 -7
  32. package/incrementalBuilder.proc.js +10 -7
  33. package/index-192ha4xh.js +1173 -0
  34. package/{index-c76k723e.js → index-1p98exm2.js} +2 -2
  35. package/{index-rwq3ekqc.js → index-1xj2t7cb.js} +7 -7
  36. package/{index-rtct7d43.js → index-20bxxyvn.js} +80 -80
  37. package/{index-qt22xstj.js → index-35hc95h5.js} +5 -3
  38. package/{index-8r4d7cmt.js → index-4mhy6qme.js} +4 -4
  39. package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
  40. package/{index-2mx692qq.js → index-5edzv8fn.js} +5 -5
  41. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  42. package/index-62md4dwy.js +260 -0
  43. package/index-665zp9rs.js +271 -0
  44. package/{index-2etcv0sc.js → index-86jt2a0e.js} +1 -1
  45. package/{index-krq7btbt.js → index-8fp3b47h.js} +419 -418
  46. package/{index-c2yrsxdh.js → index-9z426msb.js} +109 -328
  47. package/{index-ctanya41.js → index-awbdfcf2.js} +4 -4
  48. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  49. package/{index-rc5rwyps.js → index-bspm21y1.js} +71 -60
  50. package/index-cpb3nvma.js +667 -0
  51. package/{index-jx4bzvwg.js → index-d47ypebv.js} +97 -55
  52. package/{index-ae5pwhgs.js → index-d72hfmdm.js} +15 -13
  53. package/{index-e0cvze1s.js → index-fmzcs8v6.js} +4 -4
  54. package/{index-8ggztp8k.js → index-fyq1z6kt.js} +68 -201
  55. package/{index-1w2qaee2.js → index-g1mbppa9.js} +331 -259
  56. package/{index-gnyf1fqy.js → index-j237xtew.js} +6 -6
  57. package/{index-fm46xrf1.js → index-j4mkhz93.js} +3 -3
  58. package/{index-6jjtnhkw.js → index-jw1zm273.js} +74 -34
  59. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  60. package/index-mdjc93e1.js +33 -0
  61. package/{index-c1x0be5j.js → index-pydbnfez.js} +2 -2
  62. package/{index-56d2g8ms.js → index-qwa5k1j8.js} +1 -1
  63. package/{index-v29746vb.js → index-tamvyvj3.js} +3 -3
  64. package/{index-05g3jayg.js → index-trpasv9q.js} +260 -259
  65. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  66. package/{index-55mynnqs.js → index-va06nr10.js} +1434 -1173
  67. package/index-vk0pa0mn.js +240 -0
  68. package/{index-aprt7pkz.js → index-w44cy4vs.js} +2271 -3369
  69. package/index.js +22 -22
  70. package/{library.command-gaga5bmq.js → library.command-erp2f56n.js} +3 -3
  71. package/{localRegistry.command-vsreyy42.js → localRegistry.command-y176p558.js} +15 -11
  72. package/{module.command-7vpf6db4.js → module.command-gs2r615e.js} +5 -5
  73. package/{package.command-twmvvf69.js → package.command-j10r76ax.js} +4 -4
  74. package/package.json +21 -21
  75. package/{page.command-5asq7fdf.js → page.command-4h4a8pnh.js} +3 -3
  76. package/{pipeline.command-d0v38572.js → pipeline.command-a2nrk26m.js} +221 -220
  77. package/{primitive.command-dyrs2fm8.js → primitive.command-w6t0asqz.js} +6 -6
  78. package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-f534enqj.js} +1 -1
  79. package/{quality.command-6emsqpbg.js → quality.command-77x7vbbx.js} +4 -4
  80. package/{repair.command-8t6aegv5.js → repair.command-y6sstev7.js} +5 -5
  81. package/{scalar.command-bnggy1x7.js → scalar.command-20b8cy0k.js} +4 -4
  82. package/{subspace.command-md2teygq.js → subspace.command-5c7yasd7.js} +424 -364
  83. package/templates/appSample/lib/task/Task.Util.tsx +7 -7
  84. package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
  85. package/templates/appSample/ui/QuantityControl.tsx +2 -2
  86. package/templates/crudSinglePage/page.tsx +1 -1
  87. package/templates/lib/cnst.ts +0 -1
  88. package/templates/workspaceRoot/.gitignore.template +23 -52
  89. package/templates/workspaceRoot/biome.json.template +1 -1
  90. package/{tunnel.command-jx3tdm2d.js → tunnel.command-2kw8vt9g.js} +8 -4
  91. package/{workflow.command-jx0wag5r.js → workflow.command-2djwmpqf.js} +10 -10
  92. package/{workspace.command-yxd7rr7g.js → workspace.command-6qfgq4b4.js} +38 -34
  93. package/TunnelShare-q4jq08qg.js +0 -9
  94. package/cloud.script-h6pvcpa2.js +0 -20
  95. package/context.runner-atcrzz5b.js +0 -21
  96. package/index-6w0rbr9d.js +0 -68
  97. package/index-n68jx79w.js +0 -120
  98. package/index-pgj2rt78.js +0 -404
@@ -2,16 +2,19 @@
2
2
  import {
3
3
  binPlatforms,
4
4
  ServerEnvProfile,
5
- Executor
6
- } from "./index-55mynnqs.js";
5
+ Executor,
6
+ WorkspaceExecutor
7
+ } from "./index-va06nr10.js";
7
8
  import {
8
9
  resolveAppId,
9
10
  targetHtmlFilename
10
- } from "./index-q958tqqg.js";
11
-
12
- // pkgs/@akanjs/devkit/mobile/nativeApp.ts
13
- import { rm as rm3 } from "fs/promises";
14
- import path7 from "path";
11
+ } from "./index-m9jj544v.js";
12
+ import {
13
+ ExitCodeError
14
+ } from "./index-jw1zm273.js";
15
+ import {
16
+ CsrArtifactBuilder
17
+ } from "./index-665zp9rs.js";
15
18
 
16
19
  // pkgs/@akanjs/devkit/mobile/desktopBin.ts
17
20
  import { chmod, mkdir, rename, rm, writeFile } from "fs/promises";
@@ -138,6 +141,7 @@ import path2 from "path";
138
141
 
139
142
  class NativeApi {
140
143
  static supportedMajor = 0;
144
+ static minimumVersion = "0.12.0";
141
145
  static entryOf(appDir) {
142
146
  const workspaceEntry = NativeApi.#resolve("@akanjs/native/api", appDir);
143
147
  if (workspaceEntry)
@@ -153,8 +157,8 @@ class NativeApi {
153
157
  throw new Error(`The installed akanjs has no native build at ${entry}; update akanjs.`);
154
158
  const api = await import(entry);
155
159
  const major = Number(api.API_VERSION.split(".")[0]);
156
- if (major !== NativeApi.supportedMajor)
157
- throw new Error(`The native build API is ${api.API_VERSION}, and this akan CLI speaks ${NativeApi.supportedMajor}.x; install matching versions of akanjs and @akanjs/cli.`);
160
+ if (major !== NativeApi.supportedMajor || Bun.semver.order(api.API_VERSION, NativeApi.minimumVersion) < 0)
161
+ throw new Error(`The native build API is ${api.API_VERSION}, and this akan CLI speaks ${NativeApi.minimumVersion} or a later ${NativeApi.supportedMajor}.x; install matching versions of akanjs and @akanjs/cli.`);
158
162
  return api;
159
163
  }
160
164
  static #resolve(specifier, from) {
@@ -166,6 +170,10 @@ class NativeApi {
166
170
  }
167
171
  }
168
172
 
173
+ // pkgs/@akanjs/devkit/mobile/nativeApp.ts
174
+ import path8 from "path";
175
+ import { toError } from "akanjs/common";
176
+
169
177
  // pkgs/@akanjs/devkit/mobile/nativeAppLine.ts
170
178
  class NativeAppLine {
171
179
  static #page = /^\[page(\+)?(?:#(\d+))? (trace|verbose|debug|log|info|warn|error)\] ?(.*)$/;
@@ -224,6 +232,7 @@ class NativeAppLine {
224
232
  // pkgs/@akanjs/devkit/mobile/nativeConfig.ts
225
233
  import { readFileSync } from "fs";
226
234
  import path3 from "path";
235
+ import { trimSlashes } from "akanjs/common";
227
236
 
228
237
  // pkgs/@akanjs/devkit/mobile/usageDescriptions.ts
229
238
  import { capitalize } from "akanjs/common";
@@ -425,7 +434,7 @@ class NativeConfig {
425
434
  return url && keys ? { ...rest, url, ...keys, ...channel ? { channel } : {} } : undefined;
426
435
  }
427
436
  static fileNameOf(name) {
428
- return name.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "app";
437
+ return /[^-.](?:.*[^-.])?/.exec(name.replace(/[^A-Za-z0-9._-]/g, "-"))?.[0] ?? "app";
429
438
  }
430
439
  static #pluginId(appPath, spec) {
431
440
  if (NativeConfig.#isId(spec))
@@ -448,7 +457,7 @@ class NativeConfig {
448
457
  return Object.assign({}, ...contributed, own);
449
458
  }
450
459
  static #deepLinks(target, locales) {
451
- const basePath = target.basePath?.replace(/^\/+|\/+$/g, "");
460
+ const basePath = target.basePath ? trimSlashes(target.basePath) : "";
452
461
  const pathPrefixes = basePath ? locales.map((locale) => `/${locale}/${basePath}`) : undefined;
453
462
  return {
454
463
  ...target.deepLinks?.schemes?.length ? { schemes: target.deepLinks.schemes } : {},
@@ -500,9 +509,109 @@ class NativePluginFolders {
500
509
  }
501
510
  }
502
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
+
503
611
  // pkgs/@akanjs/devkit/mobile/nativeWebDir.ts
504
612
  import { cp, mkdir as mkdir2, rm as rm2, stat } from "fs/promises";
505
613
  import path5 from "path";
614
+ import { trimSlashes as trimSlashes2 } from "akanjs/common";
506
615
 
507
616
  class NativeWebDir {
508
617
  dir;
@@ -513,13 +622,13 @@ class NativeWebDir {
513
622
  static injectTarget(html, target) {
514
623
  if (html.includes(NativeWebDir.#targetTag))
515
624
  return html;
516
- const basePath = target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
625
+ const basePath = target.basePath ? trimSlashes2(target.basePath) : "";
517
626
  const script = `${NativeWebDir.#targetTag}${JSON.stringify({
518
627
  name: target.name,
519
628
  basePath,
520
629
  indexPath: target.indexPath
521
630
  })};</script>`;
522
- const head = /<head(?:\s[^>]*)?>/i.exec(html);
631
+ const head = /<head(?:\s[^<>]*)?>/i.exec(html);
523
632
  if (!head)
524
633
  return `${script}
525
634
  ${html}`;
@@ -562,14 +671,14 @@ class ServerBundleSecrets {
562
671
  workspaceRoot;
563
672
  appDir;
564
673
  static #largestFile = 64 * 1024 * 1024;
674
+ static #printer = new Bun.Transpiler({ loader: "js", target: "bun" });
675
+ static #notSource = new Set(["node_modules", "env", "private", "public", "dist"]);
565
676
  constructor(workspaceRoot, appDir) {
566
677
  this.workspaceRoot = workspaceRoot;
567
678
  this.appDir = appDir;
568
679
  }
569
680
  async envFiles() {
570
- const libsDir = path6.join(this.workspaceRoot, "libs");
571
- const libs = await readdir2(libsDir).catch(() => []);
572
- const dirs = [path6.join(this.appDir, "env"), ...libs.map((lib) => path6.join(libsDir, lib, "env"))];
681
+ const dirs = [path6.join(this.appDir, "env"), ...(await this.#libDirs()).map((lib) => path6.join(lib, "env"))];
573
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))));
574
683
  return files.flat();
575
684
  }
@@ -584,24 +693,52 @@ class ServerBundleSecrets {
584
693
  unread.push(ServerBundleSecrets.#relative(this.workspaceRoot, file));
585
694
  }
586
695
  }
696
+ if (!secrets.length)
697
+ return unread;
698
+ const sought = secrets.map((secret) => ({ secret, needles: ServerBundleSecrets.#needles(secret.value) }));
587
699
  const found = [];
588
700
  for (const bundled of await ServerBundleSecrets.#files(bundleDir)) {
589
701
  const text = await readFile(bundled, "latin1");
590
- for (const secret of secrets)
591
- if (ServerBundleSecrets.#needles(secret.value).some((needle) => text.includes(needle)))
592
- found.push(`${ServerBundleSecrets.#relative(bundleDir, bundled)} holds ${ServerBundleSecrets.#relative(this.workspaceRoot, secret.file)} ${secret.path}`);
702
+ for (const { secret, needles } of sought)
703
+ if (needles.some((needle) => text.includes(needle)))
704
+ found.push({ bundled, secret });
593
705
  }
594
- if (found.length)
595
- throw new Error(`The server this app carries holds secret values from the workspace's env files, so it is not signed or sent anywhere:
596
- ${[...new Set(found)].join(`
706
+ const coded = found.length ? await this.#coded(found.map(({ secret }) => secret.value)) : new Set;
707
+ const leaks = found.filter(({ secret }) => !coded.has(secret.value)).map(({ bundled, secret }) => `${ServerBundleSecrets.#relative(bundleDir, bundled)} holds ${ServerBundleSecrets.#relative(this.workspaceRoot, secret.file)} ${secret.path}`);
708
+ if (leaks.length)
709
+ throw new Error(`The server this app carries holds secret values of the workspace's env files, so the release stops here:
710
+ ${[...new Set(leaks)].join(`
597
711
  `)}
598
- Build its server with a profile (native.desktop.server.profile), whose bundle carries no secret.`);
712
+ Build its server with a profile (native.desktop.server.profile), whose bundle leaves the env files' secrets out. A value a profile build still holds sits in code or a file the server carries: take it out there.`);
599
713
  return unread;
600
714
  }
715
+ async#coded(values) {
716
+ const plain = [...new Set(values)].filter((value) => !ServerEnvProfile.distinctive(value));
717
+ const coded = new Set;
718
+ if (!plain.length)
719
+ return coded;
720
+ for (const dir of [this.appDir, ...await this.#libDirs()])
721
+ for (const file of await ServerBundleSecrets.#sources(dir)) {
722
+ const text = await readFile(file, "utf8");
723
+ for (const value of plain)
724
+ if (text.includes(value))
725
+ coded.add(value);
726
+ }
727
+ return coded;
728
+ }
729
+ async#libDirs() {
730
+ const libsDir = path6.join(this.workspaceRoot, "libs");
731
+ return (await readdir2(libsDir).catch(() => [])).map((lib) => path6.join(libsDir, lib));
732
+ }
601
733
  static #needles(value) {
602
734
  const escaped = JSON.stringify(value).slice(1, -1);
603
735
  const line = value.split(/\r?\n/).find((part) => part.length >= 32);
604
- return [...new Set([value, escaped, ...line ? [line] : []])];
736
+ const forms = [value, escaped, ServerBundleSecrets.#printed(value), ...line ? [line] : []];
737
+ return [...new Set(forms.map((form) => Buffer.from(form, "utf8").toString("latin1")))];
738
+ }
739
+ static #printed(value) {
740
+ const code = ServerBundleSecrets.#printer.transformSync(`export default ${JSON.stringify(value)};`);
741
+ return /^export default (["'`])([\s\S]*)\1;?\s*$/.exec(code)?.[2] ?? value;
605
742
  }
606
743
  static #relative(from, to) {
607
744
  return path6.relative(from, to).split(path6.sep).join("/");
@@ -618,6 +755,102 @@ Build its server with a profile (native.desktop.server.profile), whose bundle ca
618
755
  }
619
756
  return files;
620
757
  }
758
+ static async#sources(dir) {
759
+ const files = [];
760
+ for (const entry of await readdir2(dir, { withFileTypes: true }).catch(() => [])) {
761
+ if (entry.name.startsWith("."))
762
+ continue;
763
+ const file = path6.join(dir, entry.name);
764
+ if (entry.isDirectory()) {
765
+ if (!ServerBundleSecrets.#notSource.has(entry.name))
766
+ files.push(...await ServerBundleSecrets.#sources(file));
767
+ } else if (/\.(?:[cm]?[jt]sx?|json)$/.test(entry.name) && !/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(entry.name))
768
+ files.push(file);
769
+ }
770
+ return files;
771
+ }
772
+ }
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
+ }
621
854
  }
622
855
 
623
856
  // pkgs/@akanjs/devkit/mobile/nativeApp.ts
@@ -632,14 +865,14 @@ class NativeApp {
632
865
  this.app = app;
633
866
  this.target = target;
634
867
  this.env = env;
635
- this.targetRoot = path7.join(app.workspace.workspaceRoot, "dist", "native", app.name, target.name);
636
- this.web = new NativeWebDir(path7.join(this.targetRoot, "web"));
868
+ this.targetRoot = path8.join(app.workspace.workspaceRoot, "dist", "native", app.name, target.name);
869
+ this.web = new NativeWebDir(path8.join(this.targetRoot, "web"));
637
870
  }
638
871
  outDir(platform) {
639
- return path7.join(this.targetRoot, "build", platform);
872
+ return path8.join(this.targetRoot, "build", platform);
640
873
  }
641
874
  devOutDir(platform) {
642
- return path7.join(this.targetRoot, "dev", platform);
875
+ return path8.join(this.targetRoot, "dev", platform);
643
876
  }
644
877
  indexPath(platform) {
645
878
  const { config } = this.target;
@@ -649,13 +882,13 @@ class NativeApp {
649
882
  async assembleWeb(platform) {
650
883
  const dist = this.app.dist.cwdPath;
651
884
  return await this.web.assemble({ ...this.target.config, indexPath: this.indexPath(platform) }, {
652
- html: path7.join(dist, "csr", targetHtmlFilename(this.target.config)),
653
- publicDir: path7.join(dist, "public"),
654
- fontsDir: path7.join(dist, ".akan", "artifact", "fonts")
885
+ html: path8.join(dist, "csr", targetHtmlFilename(this.target.config)),
886
+ publicDir: path8.join(dist, "public"),
887
+ fontsDir: path8.join(dist, ".akan", "artifact", "fonts")
655
888
  });
656
889
  }
657
890
  get binDir() {
658
- return path7.join(this.targetRoot, "bin");
891
+ return path8.join(this.targetRoot, "bin");
659
892
  }
660
893
  async config({
661
894
  server,
@@ -739,16 +972,17 @@ class NativeApp {
739
972
  NativeApp.#assertServerPlatform(platform, server);
740
973
  NativeApp.assertInstaller(platform, installer);
741
974
  await this.assembleWeb(platform);
975
+ const publicEnv = await this.#publicEnv();
742
976
  const { api, config } = await this.prepare(platform, server, arch);
743
977
  const distribution = await this.#distribution(api, platform, profile, { sign, notarize }, server);
744
- return await api.build({
978
+ return await this.#recorded(this.outDir(platform), publicEnv, api.build({
745
979
  ...this.#task(platform, config, { env: await this.#runtimeEnv(server) }),
746
980
  profile,
747
981
  ...arch ? { arch } : {},
748
982
  ...platform === "windows" ? { windows: { ...installer ? { installer } : {}, ...distribution.windows } } : {},
749
983
  ...platform === "macos" ? { macos: { ...distribution.macos, ...installer ? { dmg: true } : {} } } : {},
750
984
  ...platform === "linux" && installer ? { linux: { appImage: true } } : {}
751
- });
985
+ }));
752
986
  }
753
987
  async run(platform, { device, teamId, profile = "debug", server } = {}) {
754
988
  NativeApp.#assertServerPlatform(platform, server);
@@ -804,22 +1038,25 @@ class NativeApp {
804
1038
  }
805
1039
  async releaseIos({ teamId, adHoc = false } = {}) {
806
1040
  await this.assembleWeb("ios");
1041
+ const publicEnv = await this.#publicEnv();
807
1042
  const { api, config } = await this.prepare("ios");
808
- return await api.release({
1043
+ return await this.#recorded(this.outDir("ios"), publicEnv, api.release({
809
1044
  ...this.#task("ios", config, { env: await this.#runtimeEnv() }),
810
1045
  platform: "ios",
811
1046
  signing: { ...teamId ? { teamId } : {}, ...adHoc ? { distribution: "ad-hoc" } : {} }
812
- });
1047
+ }));
813
1048
  }
814
1049
  get updatesDir() {
815
- return path7.join(this.targetRoot, "updates");
1050
+ return path8.join(this.targetRoot, "updates");
816
1051
  }
817
1052
  async publishUpdate(platform, {
818
1053
  channel,
819
1054
  server,
820
1055
  arch,
821
1056
  sign = false,
822
- notarize = false
1057
+ notarize = false,
1058
+ revoke,
1059
+ resetSequence
823
1060
  } = {}) {
824
1061
  NativeApp.#assertServerPlatform(platform, server);
825
1062
  this.#assertUpdates();
@@ -833,25 +1070,35 @@ class NativeApp {
833
1070
  ...channel ? { channel } : {},
834
1071
  ...arch ? { arch } : {},
835
1072
  ...distribution.macos ? { macos: distribution.macos } : {},
836
- ...distribution.windows ? { windows: distribution.windows } : {}
1073
+ ...distribution.windows ? { windows: distribution.windows } : {},
1074
+ ...revoke?.length ? { revoke } : {},
1075
+ ...resetSequence ? { resetSequence } : {}
837
1076
  });
838
1077
  }
839
1078
  async#distribution(api, platform, profile, request, server) {
840
1079
  const distribution = api.requestedDistribution(platform, profile, request);
1080
+ if (server && profile === "release")
1081
+ await this.#assertServerClean(server);
841
1082
  if (!distribution.notice) {
842
1083
  if (distribution.unused.length && (platform === "macos" || platform === "windows"))
843
1084
  this.app.logger.info(`${distribution.unused.join(", ")} set: this build is not signed without --sign.`);
844
1085
  return distribution;
845
1086
  }
846
- if (server) {
847
- const unread = await new ServerBundleSecrets(this.app.workspace.workspaceRoot, this.app.cwdPath).assertClean(server.dir);
848
- if (unread.length)
849
- this.app.logger.warn(`Not searched for secrets, as they could not be evaluated: ${unread.join(", ")}.`);
850
- }
851
1087
  this.app.logger.warn(distribution.notice);
852
1088
  return distribution;
853
1089
  }
854
- async assertPublishable(platform, { channel, server = false, arch } = {}) {
1090
+ async#assertServerClean(server) {
1091
+ const unread = await new ServerBundleSecrets(this.app.workspace.workspaceRoot, this.app.cwdPath).assertClean(server.dir);
1092
+ if (unread.length)
1093
+ throw new Error(`The server this app carries was not searched for the secret values of ${unread.join(", ")}, which could not be evaluated with no app env set, so the release stops here.`);
1094
+ }
1095
+ async assertPublishable(platform, {
1096
+ channel,
1097
+ server = false,
1098
+ arch,
1099
+ revoke,
1100
+ resetSequence
1101
+ } = {}) {
855
1102
  this.#assertUpdates();
856
1103
  const [api, { config }] = await Promise.all([
857
1104
  NativeApi.load(this.app.cwdPath),
@@ -866,10 +1113,12 @@ class NativeApp {
866
1113
  out: this.updatesDir,
867
1114
  server,
868
1115
  ...channel ? { channel } : {},
869
- ...arch ? { arch } : {}
1116
+ ...arch ? { arch } : {},
1117
+ ...revoke?.length ? { revoke } : {},
1118
+ ...resetSequence ? { resetSequence } : {}
870
1119
  });
871
1120
  } catch (error) {
872
- throw new Error(`Native target '${this.target.name}': ${error instanceof Error ? error.message : String(error)}`, {
1121
+ throw new Error(`Native target '${this.target.name}': ${toError(error).message}`, {
873
1122
  cause: error
874
1123
  });
875
1124
  }
@@ -888,24 +1137,26 @@ class NativeApp {
888
1137
  async releaseAndroid({ formats = ["aab"] } = {}) {
889
1138
  const signing = NativeApp.androidSigning();
890
1139
  await this.assembleWeb("android");
1140
+ const publicEnv = await this.#publicEnv();
891
1141
  const { api, config } = await this.prepare("android");
892
- return await api.release({
1142
+ return await this.#recorded(this.outDir("android"), publicEnv, api.release({
893
1143
  ...this.#task("android", config, { env: await this.#runtimeEnv() }),
894
1144
  platform: "android",
895
1145
  signing,
896
1146
  formats
897
- });
1147
+ }));
898
1148
  }
899
1149
  async packUpdate(platform, { out } = {}) {
900
1150
  await this.assembleWeb(platform);
1151
+ const publicEnv = await this.#publicEnv();
901
1152
  const { api, config } = await this.prepare(platform);
902
1153
  if (!config.updates)
903
1154
  throw new Error(`Native target '${this.target.name}' takes no updates: add native.updates: { url, publicKey } to akan.config.ts first.`);
904
- const dir = path7.resolve(out ?? path7.join(this.updatesDir, platform));
905
- await Promise.all(["pack.json", "compat.json"].map((name) => rm3(path7.join(dir, name), { force: true })));
906
- const packed = await api.packUpdate({ ...this.#task(platform, config), platform, out: dir });
1155
+ const dir = path8.resolve(out ?? path8.join(this.updatesDir, platform));
1156
+ await UpdatePackFolder.clear(dir);
1157
+ const packed = await this.#recorded(dir, publicEnv, api.packUpdate({ ...this.#task(platform, config), platform, out: dir }));
907
1158
  const channel = config.updates.channel ?? "production";
908
- const bundle = await Bun.file(path7.join(packed.out, "bundle.json")).json();
1159
+ const bundle = await UpdatePackFolder.readBundle(path8.join(packed.out, UpdatePackFolder.bundleFile));
909
1160
  const pack = {
910
1161
  schema: 1,
911
1162
  app: this.app.name,
@@ -917,24 +1168,39 @@ class NativeApp {
917
1168
  appVersion: packed.manifest.version,
918
1169
  nativeApi: packed.manifest.nativeApi ?? "",
919
1170
  webHash: bundle.web.hash,
1171
+ ...bundle.web.publicEnvHash ? { publicEnvHash: bundle.web.publicEnvHash } : {},
920
1172
  files: packed.manifest.files.length,
921
1173
  size: packed.manifest.files.reduce((sum, file) => sum + file.size, 0),
922
1174
  out: packed.out
923
1175
  };
924
1176
  return { ...packed, channel, pack };
925
1177
  }
926
- static async writePack(pack) {
927
- await Bun.write(path7.join(pack.out, "pack.json"), `${JSON.stringify(pack, null, 2)}
1178
+ async#publicEnv() {
1179
+ const publicEnv = await NativePublicEnv.built(this.app);
1180
+ if (!publicEnv)
1181
+ return null;
1182
+ const problems = NativePublicEnv.problems(publicEnv);
1183
+ if (!problems.length)
1184
+ return publicEnv;
1185
+ const message = `The web build inlines public env an update cannot carry:
1186
+ - ${problems.join(`
1187
+ - `)}`;
1188
+ if (this.target.config.updates)
1189
+ throw new Error(message);
1190
+ this.app.logger.warn(`${message}
1191
+ bundle.json records none, so no update can be checked against this build.`);
1192
+ return null;
1193
+ }
1194
+ async#recorded(dir, publicEnv, build) {
1195
+ const result = await build;
1196
+ const file = path8.join(dir, UpdatePackFolder.bundleFile);
1197
+ if (!publicEnv || !await Bun.file(file).exists())
1198
+ return result;
1199
+ const bundle = await UpdatePackFolder.readBundle(file);
1200
+ const web = { ...bundle.web, publicEnv, publicEnvHash: NativePublicEnv.hash(publicEnv) };
1201
+ await Bun.write(file, `${JSON.stringify({ ...bundle, web }, null, 2)}
928
1202
  `);
929
- }
930
- static async readPack(dir) {
931
- const file = Bun.file(path7.join(dir, "pack.json"));
932
- if (!await file.exists())
933
- throw new Error(`${dir} holds no pack.json: give the folder \`akan pack-update\` wrote.`);
934
- const pack = await file.json();
935
- if (pack.schema !== 1)
936
- throw new Error(`${dir}/pack.json is schema ${pack.schema}, and this akan reads schema 1.`);
937
- return { ...pack, out: path7.resolve(dir) };
1203
+ return result;
938
1204
  }
939
1205
  static async compareBundles(appDir, shipped, bundle) {
940
1206
  return (await NativeApi.load(appDir)).compareBundles(shipped, bundle);
@@ -990,198 +1256,4 @@ class NativeApp {
990
1256
  }
991
1257
  }
992
1258
 
993
- // pkgs/@akanjs/devkit/mobile/placeholderAppId.ts
994
- var PLACEHOLDER_APP_IDS = [
995
- "com.myapp.app",
996
- "com.myorg.myapp",
997
- "com.example.app",
998
- "com.example.myapp"
999
- ];
1000
- var placeholderAppIdSegment = /^(example|examples|myorg|myapp|mycompany|myorganization|changeme|todo|sample|test)$/;
1001
- var isPlaceholderAppId = (appId) => {
1002
- const normalized = appId?.trim().toLowerCase() ?? "";
1003
- if (!normalized)
1004
- return true;
1005
- if (PLACEHOLDER_APP_IDS.includes(normalized))
1006
- return true;
1007
- return normalized.split(".").some((segment) => placeholderAppIdSegment.test(segment));
1008
- };
1009
- // pkgs/@akanjs/devkit/mobile/desktopServerStage.ts
1010
- import { cp as cp2, mkdir as mkdir3, readdir as readdir3, rm as rm4, writeFile as writeFile2 } from "fs/promises";
1011
- import path8 from "path";
1012
- class DesktopServerStage {
1013
- app;
1014
- static uncarried = new Set([
1015
- "Dockerfile",
1016
- "package.json",
1017
- "rscWorker.js",
1018
- "console.js",
1019
- "console-runtime.js",
1020
- "csr",
1021
- "public",
1022
- ".akan"
1023
- ]);
1024
- static rscRenderer = "react-server-dom-webpack";
1025
- dir;
1026
- constructor(app) {
1027
- this.app = app;
1028
- this.dir = path8.join(app.cwdPath, ".akan", "desktop", "server");
1029
- }
1030
- static assertCarriable(config) {
1031
- if (!config.database.modes.includes("single"))
1032
- throw new Error(`desktop.server in native puts the server in the app, where only database mode single runs (no Redis or Postgres); apps/${config.app.name}/akan.config.ts declares ${config.database.modes.join(", ")}.`);
1033
- }
1034
- static env(config, environment, { modules = [], env = {} } = {}) {
1035
- const { PORT: _port, AKAN_LOG_TO_FILE: _fileLog, ...image } = config.getProductionEnv(environment);
1036
- return {
1037
- ...image,
1038
- ...env,
1039
- AKAN_PUBLIC_OPERATION_MODE: "edge",
1040
- AKAN_DATABASE_MODE: "single",
1041
- AKAN_DATABASE_MODES: "single",
1042
- AKAN_SSR: "false",
1043
- AKAN_CSR: "false",
1044
- AKAN_MCP: "false",
1045
- AKAN_SHUTDOWN_TIMEOUT_MS: "1000",
1046
- ...modules.length ? { AKAN_MODULES: modules.join(",") } : {}
1047
- };
1048
- }
1049
- static imageStepsNotice(config, carried) {
1050
- const steps = typeof config.docker === "string" ? null : config.docker.preRuns.length + config.docker.postRuns.length;
1051
- if (steps === 0)
1052
- return null;
1053
- const image = steps === null ? `apps/${config.app.name}/akan.config.ts writes its own Dockerfile` : `The image runs ${steps} docker step${steps === 1 ? "" : "s"} from the app and its libs`;
1054
- return carried.length ? {
1055
- level: "info",
1056
- message: `${image}; the desktop app's server runs none of them and carries only bin: ${carried.join(", ")}.`
1057
- } : {
1058
- level: "warn",
1059
- message: `${image}, and a desktop app's server runs none of them: declare in bin the executables its code spawns, or those calls fail on the user's computer.`
1060
- };
1061
- }
1062
- static packageJson(config, built, omit = []) {
1063
- const otherDrivers = new Set(config.database.modes.filter((mode) => mode !== "single").flatMap((mode) => config.getDatabaseModeRuntimePackages(mode)));
1064
- const dependencies = Object.entries(built.dependencies ?? {}).filter(([name]) => !omit.includes(name) && (config.externalLibs.includes(name) || name !== DesktopServerStage.rscRenderer && !otherDrivers.has(name)));
1065
- return { ...built, dependencies: Object.fromEntries(dependencies) };
1066
- }
1067
- static async dependentsOf(nodeModules, name) {
1068
- const dependents = new Set;
1069
- for await (const manifest of new Bun.Glob("**/package.json").scan({ cwd: nodeModules, onlyFiles: true })) {
1070
- const json = await Bun.file(path8.join(nodeModules, manifest)).json().catch(() => null);
1071
- if (!json?.name || json.name === name)
1072
- continue;
1073
- const declared = { ...json.dependencies, ...json.optionalDependencies, ...json.peerDependencies };
1074
- if (name in declared)
1075
- dependents.add(json.name);
1076
- }
1077
- return [...dependents].sort();
1078
- }
1079
- static installArgs(arch) {
1080
- return ["install", "--production", "--prefer-offline", ...arch && arch !== process.arch ? [`--cpu=${arch}`] : []];
1081
- }
1082
- async install(arch, omit = []) {
1083
- const config = await this.app.getConfig();
1084
- DesktopServerStage.assertCarriable(config);
1085
- await rm4(this.dir, { recursive: true, force: true });
1086
- await mkdir3(this.dir, { recursive: true });
1087
- const built = config.getProductionPackageJson();
1088
- for (const name of omit.filter((name) => !(name in (built.dependencies ?? {}))))
1089
- this.app.logger.warn(`desktop.server.omit names ${name}, which the server does not depend on directly.`);
1090
- await writeFile2(path8.join(this.dir, "package.json"), JSON.stringify(DesktopServerStage.packageJson(config, built, omit), null, 2));
1091
- try {
1092
- await this.app.spawn(process.execPath, DesktopServerStage.installArgs(arch), { cwd: this.dir });
1093
- } catch (error) {
1094
- throw new Error(`The desktop app's server could not install its packages in ${this.dir}: it needs the npm registry, or a Bun cache that already holds every one of them.
1095
- ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1096
- }
1097
- await rm4(path8.join(this.dir, "node_modules", ".bin"), { recursive: true, force: true });
1098
- await this.#assertOmitted(omit);
1099
- }
1100
- async#assertOmitted(omit) {
1101
- const nodeModules = path8.join(this.dir, "node_modules");
1102
- const kept = [];
1103
- for (const name of omit) {
1104
- if (!await Bun.file(path8.join(nodeModules, name, "package.json")).exists())
1105
- continue;
1106
- const dependents = await DesktopServerStage.dependentsOf(nodeModules, name);
1107
- kept.push(`${name} (needed by ${dependents.join(", ") || "a package that names it"})`);
1108
- }
1109
- if (kept.length)
1110
- throw new Error(`desktop.server.omit leaves out packages another dependency still installs: ${kept.join("; ")}. Omit those too, or keep the package.`);
1111
- }
1112
- async prepare(environment, run = {}) {
1113
- const config = await this.app.getConfig();
1114
- const dist = this.app.dist.cwdPath;
1115
- await mkdir3(this.dir, { recursive: true });
1116
- for (const name of await readdir3(dist))
1117
- if (!DesktopServerStage.uncarried.has(name))
1118
- await cp2(path8.join(dist, name), path8.join(this.dir, name), { recursive: true });
1119
- const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? await this.app.scan({ write: false });
1120
- const carried = [...DesktopBin.select(config, scanInfo.getLibs()).keys()];
1121
- const notice = DesktopServerStage.imageStepsNotice(config, carried);
1122
- if (notice)
1123
- this.app.logger[notice.level](notice.message);
1124
- return {
1125
- dir: this.dir,
1126
- entry: "main.js",
1127
- env: DesktopServerStage.env(config, environment, run),
1128
- ...run.restart !== undefined ? { restart: run.restart } : {},
1129
- ...run.watchdog ? { watchdog: run.watchdog } : {}
1130
- };
1131
- }
1132
- }
1133
- // pkgs/@akanjs/devkit/mobile/updateUploader.ts
1134
- import path9 from "path";
1135
-
1136
- class UpdateUploader {
1137
- bucket;
1138
- prefix;
1139
- static #manifest = /\.json(?:\.sig)?$/;
1140
- static parseTarget(target) {
1141
- const match = /^s3:\/\/([^/]+)\/?(.*)$/.exec(target.trim());
1142
- if (!match?.[1])
1143
- throw new Error(`--upload takes s3://<bucket>/<prefix>, not ${JSON.stringify(target)}.`);
1144
- return { bucket: match[1], prefix: (match[2] ?? "").replace(/\/+$/, "") };
1145
- }
1146
- static order(files) {
1147
- const manifests = files.filter((file) => UpdateUploader.#manifest.test(file));
1148
- return {
1149
- releases: files.filter((file) => !UpdateUploader.#manifest.test(file)).sort((a, b) => a.localeCompare(b)),
1150
- manifests: manifests.sort((a, b) => Number(a.endsWith(".json")) - Number(b.endsWith(".json")) || a.localeCompare(b))
1151
- };
1152
- }
1153
- constructor(bucket, prefix) {
1154
- this.bucket = bucket;
1155
- this.prefix = prefix;
1156
- }
1157
- static forTarget(target) {
1158
- const { bucket, prefix } = UpdateUploader.parseTarget(target);
1159
- return new UpdateUploader(new Bun.S3Client({ bucket }), prefix);
1160
- }
1161
- async upload(dir) {
1162
- const files = [];
1163
- for await (const file of new Bun.Glob("**/*").scan({ cwd: dir, onlyFiles: true })) {
1164
- if (!file.endsWith(".tmp"))
1165
- files.push(file.split(path9.sep).join("/"));
1166
- }
1167
- const { releases, manifests } = UpdateUploader.order(files);
1168
- const keyOf = (file) => this.prefix ? `${this.prefix}/${file}` : file;
1169
- let uploaded = 0;
1170
- let skipped = 0;
1171
- for (const file of releases) {
1172
- if (await this.bucket.exists(keyOf(file))) {
1173
- skipped++;
1174
- continue;
1175
- }
1176
- await this.bucket.write(keyOf(file), Bun.file(path9.join(dir, file)));
1177
- uploaded++;
1178
- }
1179
- for (const file of manifests) {
1180
- const type = file.endsWith(".json") ? "application/json" : "text/plain";
1181
- await this.bucket.write(keyOf(file), Bun.file(path9.join(dir, file)), { type });
1182
- uploaded++;
1183
- }
1184
- return { uploaded, skipped };
1185
- }
1186
- }
1187
- export { DesktopServerStage, NativeApp, isPlaceholderAppId, UpdateUploader };
1259
+ export { DesktopBin, NativePublicEnv, UpdatePackFolder, NativeApp };