@akanjs/cli 3.0.2 → 3.1.0-rc.1

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 (87) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-szbjv4gv.js → CodeAgent-kyprmy06.js} +2 -2
  3. package/{CodeTui-5nw86ab6.js → CodeTui-21mtze3j.js} +1 -1
  4. package/{TunnelShare-cwh9ezsm.js → TunnelShare-q4jq08qg.js} +2 -2
  5. package/{agent.command-np1dffkb.js → agent.command-f3gsc1gd.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-gk69v1xs.js → application.command-5s780vnz.js} +37 -15
  8. package/{applicationBuildRunner-wx09n9g6.js → applicationBuildRunner-1yaqsrdq.js} +115 -103
  9. package/buildBatch.proc.js +16 -5
  10. package/cloud.command-yj6xb2a7.js +192 -0
  11. package/cloud.script-h6pvcpa2.js +20 -0
  12. package/{code.command-hmtx14b2.js → code.command-3wkn4ja5.js} +6 -6
  13. package/commandManifest.json +1 -1
  14. package/{context.command-6aqvdx43.js → context.command-9jjn3gcw.js} +13 -13
  15. package/context.runner-atcrzz5b.js +21 -0
  16. package/{guideline.command-axvf68gc.js → guideline.command-dtczrqpf.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +10 -2
  18. package/guidelines/conventions/conventions.instruction.md +70 -25
  19. package/guidelines/cssRule/cssRule.instruction.md +19 -0
  20. package/guidelines/mcpRule/mcpRule.instruction.md +72 -12
  21. package/guidelines/queryRule/queryRule.instruction.md +11 -1
  22. package/guidelines/runtimeRule/runtimeRule.instruction.md +97 -16
  23. package/guidelines/ssrRule/ssrRule.instruction.md +10 -7
  24. package/guidelines/transportRule/transportRule.instruction.md +19 -0
  25. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +6 -6
  26. package/incrementalBuilder.proc.js +24 -3
  27. package/{index-h9ht6s60.js → index-05g3jayg.js} +1 -1
  28. package/{index-jk9xnck6.js → index-1w2qaee2.js} +240 -133
  29. package/{index-g38nhv87.js → index-2etcv0sc.js} +1 -1
  30. package/{index-zyw622y1.js → index-2mx692qq.js} +3 -3
  31. package/{index-50wb2a26.js → index-55mynnqs.js} +354 -129
  32. package/{index-2wc41k5w.js → index-56d2g8ms.js} +2 -2
  33. package/{index-q6ft9fkc.js → index-6jjtnhkw.js} +8 -3
  34. package/index-6w0rbr9d.js +68 -0
  35. package/{index-hxnnfy57.js → index-8ggztp8k.js} +204 -8
  36. package/{index-zwd84m0w.js → index-8r4d7cmt.js} +4 -4
  37. package/{index-j38qkvkk.js → index-ae5pwhgs.js} +12 -10
  38. package/{index-gtjgnv36.js → index-aprt7pkz.js} +6 -9
  39. package/{index-1bjkfvm9.js → index-c1x0be5j.js} +2 -2
  40. package/{index-z750gk1j.js → index-c2yrsxdh.js} +13 -7
  41. package/{index-cb6yc5mb.js → index-c76k723e.js} +2 -2
  42. package/{index-6keyf4sg.js → index-ctanya41.js} +4 -4
  43. package/{index-f4e08ptp.js → index-e0cvze1s.js} +4 -4
  44. package/{index-hp3stk1m.js → index-fm46xrf1.js} +3 -3
  45. package/{index-3xmzr1p1.js → index-gnyf1fqy.js} +2 -2
  46. package/{index-nwd6qzvv.js → index-jx4bzvwg.js} +138 -55
  47. package/{index-jb0hmt3k.js → index-krq7btbt.js} +3 -3
  48. package/{index-94s080qm.js → index-pgj2rt78.js} +139 -1
  49. package/index-q958tqqg.js +77 -0
  50. package/{index-50jjwg8y.js → index-qt22xstj.js} +1 -1
  51. package/index-rc5rwyps.js +201 -0
  52. package/{index-xwprn9nr.js → index-rtct7d43.js} +1 -1
  53. package/{index-bsv901fd.js → index-rwq3ekqc.js} +7 -7
  54. package/{index-1gs9g12n.js → index-v29746vb.js} +4 -4
  55. package/index-ww9bhp67.js +62 -0
  56. package/index.js +22 -21
  57. package/{library.command-9m2rdwwz.js → library.command-gaga5bmq.js} +3 -3
  58. package/{localRegistry.command-kda81jyk.js → localRegistry.command-vsreyy42.js} +9 -9
  59. package/{module.command-q63dffya.js → module.command-7vpf6db4.js} +5 -5
  60. package/{package.command-y2jh0m99.js → package.command-twmvvf69.js} +3 -3
  61. package/package.json +2 -2
  62. package/{page.command-f3e4cwhh.js → page.command-5asq7fdf.js} +3 -3
  63. package/pipeline.command-d0v38572.js +1334 -0
  64. package/{primitive.command-r4kq75qj.js → primitive.command-dyrs2fm8.js} +6 -6
  65. package/projectTypecheck-cvtkrzp2.js +7 -0
  66. package/{quality.command-f7gzh26z.js → quality.command-6emsqpbg.js} +5 -4
  67. package/{repair.command-0qmyr6qa.js → repair.command-8t6aegv5.js} +5 -5
  68. package/{scalar.command-kddn7av4.js → scalar.command-bnggy1x7.js} +4 -4
  69. package/{subspace.command-ft65qvc4.js → subspace.command-md2teygq.js} +158 -91
  70. package/templates/app/env/env.client.type.ts +2 -3
  71. package/templates/app/page/styles.css.template +8 -0
  72. package/templates/appSample/page/task/[taskId]/_index.tsx +2 -2
  73. package/templates/appSample/page/task/[taskId]/edit.tsx +2 -2
  74. package/templates/appSample/page/task/_index.tsx +2 -2
  75. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -2
  76. package/templates/crudPages/[__model__Id]/page.tsx +3 -3
  77. package/templates/crudPages/page.tsx +2 -2
  78. package/templates/crudSinglePage/page.tsx +3 -3
  79. package/templates/lib/cnst.ts +2 -2
  80. package/templates/lib/sig.ts +3 -3
  81. package/{tunnel.command-b3ymdk4f.js → tunnel.command-jx3tdm2d.js} +16 -13
  82. package/{workflow.command-z1srcdfw.js → workflow.command-jx0wag5r.js} +10 -10
  83. package/{workspace.command-vp6vmx3z.js → workspace.command-yxd7rr7g.js} +57 -27
  84. package/cloud.command-tw06613r.js +0 -95
  85. package/cloud.script-64gzba8r.js +0 -20
  86. package/context.runner-hnkvv3z3.js +0 -21
  87. package/index-t91a7339.js +0 -104
@@ -1,86 +1,17 @@
1
1
  // @bun
2
2
  import {
3
3
  binPlatforms,
4
+ ServerEnvProfile,
4
5
  Executor
5
- } from "./index-50wb2a26.js";
6
-
7
- // pkgs/@akanjs/devkit/mobile/mobileTarget.ts
8
- var trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
9
- var getMobileTargets = async (app) => {
10
- const config = await app.getConfig();
11
- return Object.entries(config.native.targets).map(([name, target]) => ({ name, config: target }));
12
- };
13
- var isTemplateOnly = ({ basePaths, native }) => basePaths.size > 0 && Object.values(native.targets).every((target) => !target.basePath);
14
- var getMobileTargetChoices = async (app) => {
15
- const config = await app.getConfig();
16
- const targetNames = Object.keys(config.native.targets);
17
- if (targetNames.length > 0 && !isTemplateOnly(config))
18
- return targetNames;
19
- return [...config.basePaths];
20
- };
21
- var resolveMobileTargetByBasePath = (targets, basePath) => {
22
- const normalizedBasePath = trimSlashes(basePath);
23
- const byBasePath = targets.find(({ config }) => config.basePath !== undefined && trimSlashes(config.basePath) === normalizedBasePath);
24
- if (byBasePath)
25
- return byBasePath;
26
- const [template] = targets;
27
- if (!template)
28
- return;
29
- return {
30
- name: normalizedBasePath,
31
- config: { ...template.config, name: normalizedBasePath, basePath: normalizedBasePath }
32
- };
33
- };
34
- var resolveMobileTargets = async (app, selection) => {
35
- const config = await app.getConfig();
36
- const targets = await getMobileTargets(app);
37
- if (targets.length === 0)
38
- throw new Error(`No native targets configured for ${app.name}`);
39
- if (!selection && targets.length === 1 && !isTemplateOnly(config))
40
- return targets;
41
- if (!selection) {
42
- const choices = await getMobileTargetChoices(app);
43
- if (choices.length === 1)
44
- return resolveMobileTargets(app, choices[0]);
45
- throw new Error(`Multiple native targets found for ${app.name}. Pass --target <${choices.join("|")}|all>.`);
46
- }
47
- if (selection === "all") {
48
- if (Object.keys(config.native.targets).length > 1)
49
- return targets;
50
- const basePaths = [...config.basePaths];
51
- if (basePaths.length > 1 || isTemplateOnly(config)) {
52
- return basePaths.flatMap((basePath) => {
53
- const resolved = resolveMobileTargetByBasePath(targets, basePath);
54
- return resolved ? [resolved] : [];
55
- });
56
- }
57
- return targets;
58
- }
59
- const target = targets.find((candidate) => candidate.name === selection);
60
- if (target)
61
- return [target];
62
- const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
63
- if (basePathTarget && config.basePaths.has(trimSlashes(selection)))
64
- return [basePathTarget];
65
- const choices = await getMobileTargetChoices(app);
66
- throw new Error(`Native target '${selection}' was not found. Available: ${choices.join(", ")}`);
67
- };
68
- var resolveAppId = (appId, platform) => {
69
- if (typeof appId === "string")
70
- return appId;
71
- const id = appId[platform] ?? appId.default;
72
- if (!id)
73
- throw new Error(`native.appId names no id for ${platform}; give it ${platform} or default.`);
74
- return id;
75
- };
76
- var appIdsOf = (appId) => typeof appId === "string" ? [appId] : Object.values(appId).filter((id) => !!id);
77
- var targetHtmlFilename = (target) => {
78
- const basePath = trimSlashes(target.basePath ?? "");
79
- return basePath ? `${basePath}.html` : "index.html";
80
- };
6
+ } from "./index-55mynnqs.js";
7
+ import {
8
+ resolveAppId,
9
+ targetHtmlFilename
10
+ } from "./index-q958tqqg.js";
81
11
 
82
12
  // pkgs/@akanjs/devkit/mobile/nativeApp.ts
83
- import path6 from "path";
13
+ import { rm as rm3 } from "fs/promises";
14
+ import path7 from "path";
84
15
 
85
16
  // pkgs/@akanjs/devkit/mobile/desktopBin.ts
86
17
  import { chmod, mkdir, rename, rm, writeFile } from "fs/promises";
@@ -417,11 +348,19 @@ class NativeConfig {
417
348
  const desktop = NativeConfig.#compact({
418
349
  recovery: target.desktop?.recovery,
419
350
  screenCapture: target.desktop?.screenCapture,
351
+ webview2: target.desktop?.webview2,
352
+ publisher: target.desktop?.publisher,
420
353
  window: NativeConfig.#compact({ ...target.desktop?.window }),
421
354
  server: desktopServer,
422
355
  bin: desktopBin,
423
356
  dmg: NativeConfig.#dmg(target.desktop?.dmg, abs)
424
357
  });
358
+ const background = target.backgroundColor;
359
+ const shell = NativeConfig.#compact({
360
+ backgroundColor: typeof background === "string" ? background : background?.light,
361
+ backgroundColorDark: typeof background === "string" ? undefined : background?.dark,
362
+ devtools: target.devtools ? true : undefined
363
+ });
425
364
  const config = {
426
365
  app: {
427
366
  id: resolveAppId(target.appId, platform),
@@ -457,7 +396,8 @@ class NativeConfig {
457
396
  keyboard: { resize: "none" },
458
397
  ...desktop ? { desktop } : {},
459
398
  ...target.icon ? { icon: NativeConfig.#icon(target.icon, abs) } : {},
460
- ...target.splash ? { splash: NativeConfig.#splash(target.splash, abs) } : {}
399
+ ...target.splash ? { splash: NativeConfig.#splash(target.splash, abs) } : {},
400
+ ...shell ? { shell } : {}
461
401
  };
462
402
  return { config, warnings };
463
403
  }
@@ -480,8 +420,9 @@ class NativeConfig {
480
420
  };
481
421
  }
482
422
  static #updates(target, env) {
483
- const { url, publicKey, channel = env, ...rest } = target.updates ?? {};
484
- return url && publicKey ? { ...rest, url, publicKey, ...channel ? { channel } : {} } : undefined;
423
+ const { url, publicKey, publicKeys, channel = env, ...rest } = target.updates ?? {};
424
+ const keys = publicKeys ? { publicKeys: [...publicKeys] } : publicKey ? { publicKey } : null;
425
+ return url && keys ? { ...rest, url, ...keys, ...channel ? { channel } : {} } : undefined;
485
426
  }
486
427
  static fileNameOf(name) {
487
428
  return name.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "app";
@@ -614,6 +555,71 @@ ${script}${html.slice(at)}`;
614
555
  }
615
556
  }
616
557
 
558
+ // pkgs/@akanjs/devkit/mobile/serverBundleSecrets.ts
559
+ import { readdir as readdir2, readFile, stat as stat2 } from "fs/promises";
560
+ import path6 from "path";
561
+ class ServerBundleSecrets {
562
+ workspaceRoot;
563
+ appDir;
564
+ static #largestFile = 64 * 1024 * 1024;
565
+ constructor(workspaceRoot, appDir) {
566
+ this.workspaceRoot = workspaceRoot;
567
+ this.appDir = appDir;
568
+ }
569
+ 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"))];
573
+ 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
+ return files.flat();
575
+ }
576
+ async assertClean(bundleDir) {
577
+ const unread = [];
578
+ const secrets = [];
579
+ for (const file of await this.envFiles()) {
580
+ try {
581
+ const value = await ServerEnvProfile.evaluate(file, path6.dirname(path6.dirname(file)));
582
+ secrets.push(...ServerEnvProfile.secretValues(value).map((secret) => ({ file, ...secret })));
583
+ } catch {
584
+ unread.push(ServerBundleSecrets.#relative(this.workspaceRoot, file));
585
+ }
586
+ }
587
+ const found = [];
588
+ for (const bundled of await ServerBundleSecrets.#files(bundleDir)) {
589
+ 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}`);
593
+ }
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(`
597
+ `)}
598
+ Build its server with a profile (native.desktop.server.profile), whose bundle carries no secret.`);
599
+ return unread;
600
+ }
601
+ static #needles(value) {
602
+ const escaped = JSON.stringify(value).slice(1, -1);
603
+ const line = value.split(/\r?\n/).find((part) => part.length >= 32);
604
+ return [...new Set([value, escaped, ...line ? [line] : []])];
605
+ }
606
+ static #relative(from, to) {
607
+ return path6.relative(from, to).split(path6.sep).join("/");
608
+ }
609
+ static async#files(dir) {
610
+ const files = [];
611
+ for (const entry of await readdir2(dir, { withFileTypes: true })) {
612
+ const file = path6.join(dir, entry.name);
613
+ if (entry.isDirectory()) {
614
+ if (entry.name !== "node_modules")
615
+ files.push(...await ServerBundleSecrets.#files(file));
616
+ } else if (entry.isFile() && (await stat2(file)).size <= ServerBundleSecrets.#largestFile)
617
+ files.push(file);
618
+ }
619
+ return files;
620
+ }
621
+ }
622
+
617
623
  // pkgs/@akanjs/devkit/mobile/nativeApp.ts
618
624
  class NativeApp {
619
625
  app;
@@ -626,14 +632,14 @@ class NativeApp {
626
632
  this.app = app;
627
633
  this.target = target;
628
634
  this.env = env;
629
- this.targetRoot = path6.join(app.workspace.workspaceRoot, "dist", "native", app.name, target.name);
630
- this.web = new NativeWebDir(path6.join(this.targetRoot, "web"));
635
+ this.targetRoot = path7.join(app.workspace.workspaceRoot, "dist", "native", app.name, target.name);
636
+ this.web = new NativeWebDir(path7.join(this.targetRoot, "web"));
631
637
  }
632
638
  outDir(platform) {
633
- return path6.join(this.targetRoot, "build", platform);
639
+ return path7.join(this.targetRoot, "build", platform);
634
640
  }
635
641
  devOutDir(platform) {
636
- return path6.join(this.targetRoot, "dev", platform);
642
+ return path7.join(this.targetRoot, "dev", platform);
637
643
  }
638
644
  indexPath(platform) {
639
645
  const { config } = this.target;
@@ -643,13 +649,13 @@ class NativeApp {
643
649
  async assembleWeb(platform) {
644
650
  const dist = this.app.dist.cwdPath;
645
651
  return await this.web.assemble({ ...this.target.config, indexPath: this.indexPath(platform) }, {
646
- html: path6.join(dist, "csr", targetHtmlFilename(this.target.config)),
647
- publicDir: path6.join(dist, "public"),
648
- fontsDir: path6.join(dist, ".akan", "artifact", "fonts")
652
+ html: path7.join(dist, "csr", targetHtmlFilename(this.target.config)),
653
+ publicDir: path7.join(dist, "public"),
654
+ fontsDir: path7.join(dist, ".akan", "artifact", "fonts")
649
655
  });
650
656
  }
651
657
  get binDir() {
652
- return path6.join(this.targetRoot, "bin");
658
+ return path7.join(this.targetRoot, "bin");
653
659
  }
654
660
  async config({
655
661
  server,
@@ -729,27 +735,18 @@ class NativeApp {
729
735
  return {};
730
736
  return { PUBLIC_AKAN_SERVER_URL: `http://localhost:${await this.app.getDevPort()}` };
731
737
  }
732
- async build(platform, { profile = "release", server, installer = false, arch } = {}) {
738
+ async build(platform, { profile = "release", server, installer = false, arch, sign = false, notarize = false } = {}) {
733
739
  NativeApp.#assertServerPlatform(platform, server);
734
740
  NativeApp.assertInstaller(platform, installer);
735
741
  await this.assembleWeb(platform);
736
742
  const { api, config } = await this.prepare(platform, server, arch);
743
+ const distribution = await this.#distribution(api, platform, profile, { sign, notarize }, server);
737
744
  return await api.build({
738
745
  ...this.#task(platform, config, { env: await this.#runtimeEnv(server) }),
739
746
  profile,
740
747
  ...arch ? { arch } : {},
741
- ...platform === "windows" ? {
742
- windows: {
743
- ...installer ? { installer } : {},
744
- ...profile === "release" ? NativeApp.#windowsSigning(api) : {}
745
- }
746
- } : {},
747
- ...platform === "macos" ? {
748
- macos: {
749
- ...profile === "release" ? api.macosDistributionFromEnv(process.env) : {},
750
- ...installer ? { dmg: true } : {}
751
- }
752
- } : {},
748
+ ...platform === "windows" ? { windows: { ...installer ? { installer } : {}, ...distribution.windows } } : {},
749
+ ...platform === "macos" ? { macos: { ...distribution.macos, ...installer ? { dmg: true } : {} } } : {},
753
750
  ...platform === "linux" && installer ? { linux: { appImage: true } } : {}
754
751
  });
755
752
  }
@@ -815,23 +812,46 @@ class NativeApp {
815
812
  });
816
813
  }
817
814
  get updatesDir() {
818
- return path6.join(this.targetRoot, "updates");
815
+ return path7.join(this.targetRoot, "updates");
819
816
  }
820
- async publishUpdate(platform, { channel, server } = {}) {
817
+ async publishUpdate(platform, {
818
+ channel,
819
+ server,
820
+ arch,
821
+ sign = false,
822
+ notarize = false
823
+ } = {}) {
821
824
  NativeApp.#assertServerPlatform(platform, server);
822
825
  this.#assertUpdates();
823
826
  await this.assembleWeb(platform);
824
- const { api, config } = await this.prepare(platform, server);
827
+ const { api, config } = await this.prepare(platform, server, arch);
828
+ const distribution = await this.#distribution(api, platform, "release", { sign, notarize }, server);
825
829
  return await api.publishUpdate({
826
830
  ...this.#task(platform, config, { env: await this.#runtimeEnv(server) }),
827
831
  platform,
828
832
  out: this.updatesDir,
829
833
  ...channel ? { channel } : {},
830
- ...platform === "macos" ? { macos: api.macosDistributionFromEnv(process.env) } : {},
831
- ...platform === "windows" ? { windows: NativeApp.#windowsSigning(api) } : {}
834
+ ...arch ? { arch } : {},
835
+ ...distribution.macos ? { macos: distribution.macos } : {},
836
+ ...distribution.windows ? { windows: distribution.windows } : {}
832
837
  });
833
838
  }
834
- async assertPublishable(platform, { channel, server = false } = {}) {
839
+ async#distribution(api, platform, profile, request, server) {
840
+ const distribution = api.requestedDistribution(platform, profile, request);
841
+ if (!distribution.notice) {
842
+ if (distribution.unused.length && (platform === "macos" || platform === "windows"))
843
+ this.app.logger.info(`${distribution.unused.join(", ")} set: this build is not signed without --sign.`);
844
+ return distribution;
845
+ }
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
+ this.app.logger.warn(distribution.notice);
852
+ return distribution;
853
+ }
854
+ async assertPublishable(platform, { channel, server = false, arch } = {}) {
835
855
  this.#assertUpdates();
836
856
  const [api, { config }] = await Promise.all([
837
857
  NativeApi.load(this.app.cwdPath),
@@ -845,7 +865,8 @@ class NativeApp {
845
865
  platform,
846
866
  out: this.updatesDir,
847
867
  server,
848
- ...channel ? { channel } : {}
868
+ ...channel ? { channel } : {},
869
+ ...arch ? { arch } : {}
849
870
  });
850
871
  } catch (error) {
851
872
  throw new Error(`Native target '${this.target.name}': ${error instanceof Error ? error.message : String(error)}`, {
@@ -880,12 +901,40 @@ class NativeApp {
880
901
  const { api, config } = await this.prepare(platform);
881
902
  if (!config.updates)
882
903
  throw new Error(`Native target '${this.target.name}' takes no updates: add native.updates: { url, publicKey } to akan.config.ts first.`);
883
- const packed = await api.packUpdate({
884
- ...this.#task(platform, config),
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 });
907
+ const channel = config.updates.channel ?? "production";
908
+ const bundle = await Bun.file(path7.join(packed.out, "bundle.json")).json();
909
+ const pack = {
910
+ schema: 1,
911
+ app: this.app.name,
912
+ target: this.target.name,
885
913
  platform,
886
- out: out ?? path6.join(this.updatesDir, platform)
887
- });
888
- return { ...packed, channel: config.updates.channel ?? "production" };
914
+ env: this.env ?? "main",
915
+ channel,
916
+ appId: packed.manifest.app,
917
+ appVersion: packed.manifest.version,
918
+ nativeApi: packed.manifest.nativeApi ?? "",
919
+ webHash: bundle.web.hash,
920
+ files: packed.manifest.files.length,
921
+ size: packed.manifest.files.reduce((sum, file) => sum + file.size, 0),
922
+ out: packed.out
923
+ };
924
+ return { ...packed, channel, pack };
925
+ }
926
+ static async writePack(pack) {
927
+ await Bun.write(path7.join(pack.out, "pack.json"), `${JSON.stringify(pack, null, 2)}
928
+ `);
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) };
889
938
  }
890
939
  static async compareBundles(appDir, shipped, bundle) {
891
940
  return (await NativeApi.load(appDir)).compareBundles(shipped, bundle);
@@ -916,10 +965,6 @@ class NativeApp {
916
965
  const home = [lang, basePath, indexPath].flatMap((part) => (part ?? "").split("/")).filter((segment) => segment.length > 0);
917
966
  return `/${home.join("/")}?${params}`;
918
967
  }
919
- static #windowsSigning(api) {
920
- const signing = api.windowsSigningFromEnv(process.env);
921
- return signing ? { signing } : {};
922
- }
923
968
  static assertInstaller(platform, installer) {
924
969
  if (installer && (platform === "ios" || platform === "android"))
925
970
  throw new Error(`An installer is built for a desktop app (Windows, macOS, Linux), not for ${platform}.`);
@@ -962,8 +1007,8 @@ var isPlaceholderAppId = (appId) => {
962
1007
  return normalized.split(".").some((segment) => placeholderAppIdSegment.test(segment));
963
1008
  };
964
1009
  // pkgs/@akanjs/devkit/mobile/desktopServerStage.ts
965
- import { cp as cp2, mkdir as mkdir3, readdir as readdir2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
966
- import path7 from "path";
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";
967
1012
  class DesktopServerStage {
968
1013
  app;
969
1014
  static uncarried = new Set([
@@ -980,23 +1025,25 @@ class DesktopServerStage {
980
1025
  dir;
981
1026
  constructor(app) {
982
1027
  this.app = app;
983
- this.dir = path7.join(app.cwdPath, ".akan", "desktop", "server");
1028
+ this.dir = path8.join(app.cwdPath, ".akan", "desktop", "server");
984
1029
  }
985
1030
  static assertCarriable(config) {
986
1031
  if (!config.database.modes.includes("single"))
987
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(", ")}.`);
988
1033
  }
989
- static env(config, environment) {
1034
+ static env(config, environment, { modules = [], env = {} } = {}) {
990
1035
  const { PORT: _port, AKAN_LOG_TO_FILE: _fileLog, ...image } = config.getProductionEnv(environment);
991
1036
  return {
992
1037
  ...image,
1038
+ ...env,
993
1039
  AKAN_PUBLIC_OPERATION_MODE: "edge",
994
1040
  AKAN_DATABASE_MODE: "single",
995
1041
  AKAN_DATABASE_MODES: "single",
996
1042
  AKAN_SSR: "false",
997
1043
  AKAN_CSR: "false",
998
1044
  AKAN_MCP: "false",
999
- AKAN_SHUTDOWN_TIMEOUT_MS: "1000"
1045
+ AKAN_SHUTDOWN_TIMEOUT_MS: "1000",
1046
+ ...modules.length ? { AKAN_MODULES: modules.join(",") } : {}
1000
1047
  };
1001
1048
  }
1002
1049
  static imageStepsNotice(config, carried) {
@@ -1020,7 +1067,7 @@ class DesktopServerStage {
1020
1067
  static async dependentsOf(nodeModules, name) {
1021
1068
  const dependents = new Set;
1022
1069
  for await (const manifest of new Bun.Glob("**/package.json").scan({ cwd: nodeModules, onlyFiles: true })) {
1023
- const json = await Bun.file(path7.join(nodeModules, manifest)).json().catch(() => null);
1070
+ const json = await Bun.file(path8.join(nodeModules, manifest)).json().catch(() => null);
1024
1071
  if (!json?.name || json.name === name)
1025
1072
  continue;
1026
1073
  const declared = { ...json.dependencies, ...json.optionalDependencies, ...json.peerDependencies };
@@ -1035,26 +1082,26 @@ class DesktopServerStage {
1035
1082
  async install(arch, omit = []) {
1036
1083
  const config = await this.app.getConfig();
1037
1084
  DesktopServerStage.assertCarriable(config);
1038
- await rm3(this.dir, { recursive: true, force: true });
1085
+ await rm4(this.dir, { recursive: true, force: true });
1039
1086
  await mkdir3(this.dir, { recursive: true });
1040
1087
  const built = config.getProductionPackageJson();
1041
1088
  for (const name of omit.filter((name) => !(name in (built.dependencies ?? {}))))
1042
1089
  this.app.logger.warn(`desktop.server.omit names ${name}, which the server does not depend on directly.`);
1043
- await writeFile2(path7.join(this.dir, "package.json"), JSON.stringify(DesktopServerStage.packageJson(config, built, omit), null, 2));
1090
+ await writeFile2(path8.join(this.dir, "package.json"), JSON.stringify(DesktopServerStage.packageJson(config, built, omit), null, 2));
1044
1091
  try {
1045
1092
  await this.app.spawn(process.execPath, DesktopServerStage.installArgs(arch), { cwd: this.dir });
1046
1093
  } catch (error) {
1047
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.
1048
1095
  ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1049
1096
  }
1050
- await rm3(path7.join(this.dir, "node_modules", ".bin"), { recursive: true, force: true });
1097
+ await rm4(path8.join(this.dir, "node_modules", ".bin"), { recursive: true, force: true });
1051
1098
  await this.#assertOmitted(omit);
1052
1099
  }
1053
1100
  async#assertOmitted(omit) {
1054
- const nodeModules = path7.join(this.dir, "node_modules");
1101
+ const nodeModules = path8.join(this.dir, "node_modules");
1055
1102
  const kept = [];
1056
1103
  for (const name of omit) {
1057
- if (!await Bun.file(path7.join(nodeModules, name, "package.json")).exists())
1104
+ if (!await Bun.file(path8.join(nodeModules, name, "package.json")).exists())
1058
1105
  continue;
1059
1106
  const dependents = await DesktopServerStage.dependentsOf(nodeModules, name);
1060
1107
  kept.push(`${name} (needed by ${dependents.join(", ") || "a package that names it"})`);
@@ -1062,19 +1109,79 @@ ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1062
1109
  if (kept.length)
1063
1110
  throw new Error(`desktop.server.omit leaves out packages another dependency still installs: ${kept.join("; ")}. Omit those too, or keep the package.`);
1064
1111
  }
1065
- async prepare(environment) {
1112
+ async prepare(environment, run = {}) {
1066
1113
  const config = await this.app.getConfig();
1067
1114
  const dist = this.app.dist.cwdPath;
1068
1115
  await mkdir3(this.dir, { recursive: true });
1069
- for (const name of await readdir2(dist))
1116
+ for (const name of await readdir3(dist))
1070
1117
  if (!DesktopServerStage.uncarried.has(name))
1071
- await cp2(path7.join(dist, name), path7.join(this.dir, name), { recursive: true });
1118
+ await cp2(path8.join(dist, name), path8.join(this.dir, name), { recursive: true });
1072
1119
  const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? await this.app.scan({ write: false });
1073
1120
  const carried = [...DesktopBin.select(config, scanInfo.getLibs()).keys()];
1074
1121
  const notice = DesktopServerStage.imageStepsNotice(config, carried);
1075
1122
  if (notice)
1076
1123
  this.app.logger[notice.level](notice.message);
1077
- return { dir: this.dir, entry: "main.js", env: DesktopServerStage.env(config, environment) };
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 };
1078
1185
  }
1079
1186
  }
1080
- export { DesktopServerStage, getMobileTargets, getMobileTargetChoices, resolveMobileTargets, resolveAppId, appIdsOf, NativeApp, isPlaceholderAppId };
1187
+ export { DesktopServerStage, NativeApp, isPlaceholderAppId, UpdateUploader };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-q6ft9fkc.js";
5
+ } from "./index-6jjtnhkw.js";
6
6
 
7
7
  // pkgs/@akanjs/cli/page/page.runner.ts
8
8
  class PageRunner extends runner("page") {
@@ -10,7 +10,7 @@ import {
10
10
  isScannedRootEntry,
11
11
  AppExecutor,
12
12
  LibExecutor
13
- } from "./index-50wb2a26.js";
13
+ } from "./index-55mynnqs.js";
14
14
  import {
15
15
  FileSys2
16
16
  } from "./index-52w0fvh8.js";
@@ -23,7 +23,7 @@ import {
23
23
  workflowPathsForPlan,
24
24
  workflowRunArtifactPath,
25
25
  workflowSyncDir
26
- } from "./index-h9ht6s60.js";
26
+ } from "./index-05g3jayg.js";
27
27
 
28
28
  // pkgs/@akanjs/devkit/akanContext.ts
29
29
  import { readdir } from "fs/promises";
@@ -131,7 +131,7 @@ var generatedFiles = [
131
131
  var validationCommands = [
132
132
  "akan sync <app-or-lib>",
133
133
  "akan lint <app-or-lib-or-pkg>",
134
- "akan typecheck <app-name>",
134
+ "akan typecheck <app-or-lib>",
135
135
  "akan test <app-or-lib-or-pkg>",
136
136
  "akan build <app-name>",
137
137
  "akan doctor --strict --format json",