@akanjs/cli 2.3.5 → 2.3.6-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.
@@ -2,7 +2,7 @@
2
2
  var __require = import.meta.require;
3
3
 
4
4
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
5
- import path38 from "path";
5
+ import path40 from "path";
6
6
 
7
7
  // pkgs/@akanjs/devkit/aiEditor.ts
8
8
  import { input, select } from "@inquirer/prompts";
@@ -3940,6 +3940,36 @@ var createTunnel = async (service, { app, environment, port = service === "postg
3940
3940
  return `localhost:${port}`;
3941
3941
  };
3942
3942
 
3943
+ // pkgs/@akanjs/devkit/incrementalBuilder/devWatchBatch.ts
3944
+ var prepareDevWatchBatch = ({
3945
+ generation,
3946
+ batch,
3947
+ indexSync,
3948
+ changePlanner
3949
+ }) => {
3950
+ const files = [...new Set([...batch.files, ...indexSync.changedFiles])].sort();
3951
+ const kindSet = new Set(batch.kinds);
3952
+ if (indexSync.changedFiles.length > 0)
3953
+ kindSet.add("code");
3954
+ const kinds = [...kindSet];
3955
+ const expandedBatch = { files, kinds: kindSet };
3956
+ const devPlan = changePlanner.plan({
3957
+ generation,
3958
+ files,
3959
+ kinds,
3960
+ generatedFiles: indexSync.changedFiles
3961
+ });
3962
+ if (indexSync.errors.length > 0 && !devPlan.actions.includes("report-error")) {
3963
+ devPlan.actions = [...devPlan.actions, "report-error"].sort();
3964
+ }
3965
+ return {
3966
+ files,
3967
+ kinds,
3968
+ expandedBatch,
3969
+ event: { type: "invalidate", kinds, files, generation, devPlan },
3970
+ hasSyncErrors: indexSync.errors.length > 0
3971
+ };
3972
+ };
3943
3973
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.host.ts
3944
3974
  import path8 from "path";
3945
3975
  import { Logger as Logger4 } from "akanjs/common";
@@ -3948,7 +3978,8 @@ var builderMsgTypeSet = new Set([
3948
3978
  "builder-ready",
3949
3979
  "invalidate",
3950
3980
  "css-updated",
3951
- "pages-updated"
3981
+ "pages-updated",
3982
+ "build-status"
3952
3983
  ]);
3953
3984
 
3954
3985
  class IncrementalBuilderHost {
@@ -4091,12 +4122,79 @@ var BUILDER_READY_TIMEOUT_MS = 15000;
4091
4122
  var BUILDER_START_MAX_ATTEMPTS = 3;
4092
4123
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
4093
4124
  var NON_SOURCE_EXT_RE = /\.(css|scss|sass|less|json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav|html)$/i;
4125
+ var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
4126
+ var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
4127
+ var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
4094
4128
  var GRAPH_IMPORT_KINDS = new Set([
4095
4129
  "import-statement",
4096
4130
  "require-call",
4097
4131
  "require-resolve",
4098
4132
  "dynamic-import"
4099
4133
  ]);
4134
+ var shouldRestartBackendByDevPlan = (message) => {
4135
+ if (!message.devPlan)
4136
+ return null;
4137
+ if (message.devPlan.actions.includes("report-error"))
4138
+ return false;
4139
+ if (message.devPlan.actions.includes("restart-builder"))
4140
+ return false;
4141
+ return message.devPlan.actions.includes("restart-backend");
4142
+ };
4143
+ var shouldRestartBuilderByDevPlan = (message) => message.devPlan?.actions.includes("restart-builder") ?? false;
4144
+ var shouldRestartDevHostByDevPlan = (message) => message.devPlan?.actions.includes("restart-dev-host") ?? message.kinds.includes("config");
4145
+ var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
4146
+ var generationValue = (generation) => generation ?? -1;
4147
+ var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
4148
+ const abs = path9.resolve(file);
4149
+ const ext = path9.extname(abs).toLowerCase();
4150
+ if (!SOURCE_EXTS.has(ext))
4151
+ return false;
4152
+ const rel = path9.relative(path9.resolve(workspaceRoot), abs);
4153
+ if (!rel || rel.startsWith("..") || path9.isAbsolute(rel))
4154
+ return false;
4155
+ const parts = rel.split(path9.sep).filter(Boolean);
4156
+ const [scope] = parts;
4157
+ if (scope !== "apps" && scope !== "libs" && scope !== "pkgs")
4158
+ return false;
4159
+ const base = path9.basename(abs);
4160
+ return parts.includes("srvkit") || parts.includes("common") || SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) || SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES.has(base) || base === "main.ts" || base === "server.ts";
4161
+ };
4162
+ var shouldMarkBuildPhaseRecovered = (previousByPhase, status) => {
4163
+ const previous = previousByPhase.get(status.phase);
4164
+ return Boolean(previous && status.ok && !previous.ok && generationValue(status.generation) >= previous.generation);
4165
+ };
4166
+ var createBackendBuildStatus = ({
4167
+ generation,
4168
+ ok,
4169
+ files = [],
4170
+ message
4171
+ }) => ({
4172
+ generation,
4173
+ phase: "backend",
4174
+ ok,
4175
+ files,
4176
+ message
4177
+ });
4178
+ var backendRestartReasonFromMessage = (message) => {
4179
+ const roleSet = new Set;
4180
+ for (const role of message.devPlan?.roles ?? []) {
4181
+ if (role === "server" || role === "shared" || role === "barrel" || role === "config")
4182
+ roleSet.add(role);
4183
+ }
4184
+ return {
4185
+ generation: message.devPlan?.generation ?? message.generation,
4186
+ files: [...new Set(message.files)].sort(),
4187
+ roles: RESTART_ROLE_ORDER.filter((role) => roleSet.has(role))
4188
+ };
4189
+ };
4190
+ var mergeBackendRestartReasons = (current, next) => ({
4191
+ generation: generationValue(next.generation) >= generationValue(current?.generation) ? next.generation : current?.generation,
4192
+ files: [...new Set([...current?.files ?? [], ...next.files])].sort(),
4193
+ roles: RESTART_ROLE_ORDER.filter((role) => current?.roles.includes(role) || next.roles.includes(role))
4194
+ });
4195
+ var shouldReplaceLastGoodMessage = (current, next) => !current || generationValue(next.data.generation) >= generationValue(current.data.generation);
4196
+ var shouldQueueBuildStatusReplay = (backendReady, pendingReplayCount) => !backendReady || pendingReplayCount > 0;
4197
+ var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
4100
4198
 
4101
4199
  class BackendImportGraph {
4102
4200
  #app;
@@ -4107,6 +4205,7 @@ class BackendImportGraph {
4107
4205
  #jsxTranspiler = new Bun.Transpiler({ loader: "jsx" });
4108
4206
  #files = new Set;
4109
4207
  #ready = false;
4208
+ #lastRefreshSucceeded = false;
4110
4209
  constructor(app, logger) {
4111
4210
  this.#app = app;
4112
4211
  this.#logger = logger;
@@ -4114,6 +4213,9 @@ class BackendImportGraph {
4114
4213
  get ready() {
4115
4214
  return this.#ready;
4116
4215
  }
4216
+ get lastRefreshSucceeded() {
4217
+ return this.#lastRefreshSucceeded;
4218
+ }
4117
4219
  has(file) {
4118
4220
  return this.#files.has(path9.resolve(file));
4119
4221
  }
@@ -4122,10 +4224,12 @@ class BackendImportGraph {
4122
4224
  const files = await this.#build();
4123
4225
  this.#files = files;
4124
4226
  this.#ready = true;
4227
+ this.#lastRefreshSucceeded = true;
4125
4228
  this.#logger.verbose(`[backend-graph] scanned ${files.size} files`);
4126
4229
  return true;
4127
4230
  } catch (err) {
4128
4231
  this.#ready = this.#files.size > 0;
4232
+ this.#lastRefreshSucceeded = false;
4129
4233
  this.#logger.warn(`[backend-graph] scan failed; ${this.#ready ? "using previous graph" : "using fallback rules"}: ${err instanceof Error ? err.message : String(err)}`);
4130
4234
  return this.#ready;
4131
4235
  }
@@ -4212,9 +4316,13 @@ class AkanAppHost {
4212
4316
  #restartTimer = null;
4213
4317
  #backendRecoveryTimer = null;
4214
4318
  #backendRecoveryAttempts = 0;
4215
- #restartFiles = new Set;
4216
- #latestPagesUpdated = null;
4217
- #latestCssUpdated = null;
4319
+ #backendLifecycleState = "stopped";
4320
+ #pendingRestartReason = null;
4321
+ #backendStartStatus = null;
4322
+ #backendBuildStatusGeneration = 0;
4323
+ #lastGoodFrontend = {};
4324
+ #buildStatusByPhase = new Map;
4325
+ #pendingBuildStatusReplay = [];
4218
4326
  #builderMessageQueue = Promise.resolve();
4219
4327
  #backendGraph;
4220
4328
  constructor(app, { env, withInk = false }) {
@@ -4259,7 +4367,9 @@ class AkanAppHost {
4259
4367
  return "localhost";
4260
4368
  return await createTunnel(type, { app: this.app, environment });
4261
4369
  }
4262
- #startBackend() {
4370
+ #startBackend(startStatus = null) {
4371
+ this.#backendStartStatus = startStatus;
4372
+ this.#setBackendLifecycleState("starting");
4263
4373
  this.#backendReady = false;
4264
4374
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
4265
4375
  cwd: this.app.workspace.workspaceRoot,
@@ -4271,6 +4381,8 @@ class AkanAppHost {
4271
4381
  if (msg.type === "backend-ready") {
4272
4382
  this.#backendReady = true;
4273
4383
  this.#backendRecoveryAttempts = 0;
4384
+ this.#setBackendLifecycleState("ready", `pid=${msg.pid}`);
4385
+ this.#recordBackendReadyStatus();
4274
4386
  this.logger.verbose(`backend ready pid=${msg.pid}`);
4275
4387
  this.#replayBuilderState();
4276
4388
  return;
@@ -4293,9 +4405,53 @@ class AkanAppHost {
4293
4405
  this.#backend = backend;
4294
4406
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
4295
4407
  }
4408
+ #nextBackendBuildStatusGeneration(generation) {
4409
+ if (typeof generation === "number") {
4410
+ this.#backendBuildStatusGeneration = Math.max(this.#backendBuildStatusGeneration, generation);
4411
+ return generation;
4412
+ }
4413
+ this.#backendBuildStatusGeneration += 1;
4414
+ return this.#backendBuildStatusGeneration;
4415
+ }
4416
+ #recordBackendBuildStatus({
4417
+ generation,
4418
+ ok,
4419
+ files,
4420
+ message
4421
+ }) {
4422
+ const status = createBackendBuildStatus({
4423
+ generation: this.#nextBackendBuildStatusGeneration(generation),
4424
+ ok,
4425
+ files,
4426
+ message
4427
+ });
4428
+ this.#recordBuildStatus(status);
4429
+ return status;
4430
+ }
4431
+ #recordBackendReadyStatus() {
4432
+ const previous = this.#buildStatusByPhase.get("backend");
4433
+ const startStatus = this.#backendStartStatus;
4434
+ if (startStatus || previous?.ok === false) {
4435
+ const status = this.#recordBackendBuildStatus({
4436
+ generation: startStatus?.generation ?? previous?.generation,
4437
+ ok: true,
4438
+ files: startStatus?.files ?? previous?.files ?? [],
4439
+ message: "Backend ready"
4440
+ });
4441
+ this.#sendOrQueueBuildStatus(status);
4442
+ }
4443
+ this.#backendStartStatus = null;
4444
+ }
4445
+ #setBackendLifecycleState(next, detail) {
4446
+ if (this.#backendLifecycleState === next && !detail)
4447
+ return;
4448
+ const prev = this.#backendLifecycleState;
4449
+ this.#backendLifecycleState = next;
4450
+ this.logger.verbose(`[backend-lifecycle] ${prev} -> ${next}${detail ? ` ${detail}` : ""}`);
4451
+ }
4296
4452
  #sendToBackend(message) {
4297
4453
  if (!this.#backend || !this.#backendReady) {
4298
- if (message.type === "css-updated" || message.type === "pages-updated") {
4454
+ if (message.type === "css-updated" || message.type === "pages-updated" || message.type === "build-status") {
4299
4455
  this.logger.verbose(`backend is not ready; will replay ${message.type}`);
4300
4456
  return;
4301
4457
  }
@@ -4315,6 +4471,7 @@ class AkanAppHost {
4315
4471
  const backend = this.#backend;
4316
4472
  this.#plannedBackendStops.add(backend);
4317
4473
  this.#backendReady = false;
4474
+ this.#setBackendLifecycleState("stopping", `pid=${backend.pid}`);
4318
4475
  this.logger.verbose(`stopping backend pid=${backend.pid}`);
4319
4476
  try {
4320
4477
  backend.kill("SIGTERM");
@@ -4330,11 +4487,13 @@ class AkanAppHost {
4330
4487
  } finally {
4331
4488
  if (this.#backend === backend)
4332
4489
  this.#backend = null;
4490
+ this.#setBackendLifecycleState("stopped", `pid=${backend.pid}`);
4333
4491
  }
4334
4492
  }
4335
- #scheduleBackendRestart(files) {
4336
- for (const file of files)
4337
- this.#restartFiles.add(file);
4493
+ #scheduleBackendRestart(reason) {
4494
+ this.#pendingRestartReason = mergeBackendRestartReasons(this.#pendingRestartReason, reason);
4495
+ const pending = this.#pendingRestartReason;
4496
+ this.#setBackendLifecycleState("restart-pending", `generation=${pending.generation ?? "(unknown)"} files=${pending.files.length} roles=${pending.roles.join(",") || "(none)"}`);
4338
4497
  if (this.#backendRecoveryTimer) {
4339
4498
  clearTimeout(this.#backendRecoveryTimer);
4340
4499
  this.#backendRecoveryTimer = null;
@@ -4343,23 +4502,31 @@ class AkanAppHost {
4343
4502
  clearTimeout(this.#restartTimer);
4344
4503
  this.#restartTimer = setTimeout(() => {
4345
4504
  this.#restartTimer = null;
4346
- const changed = [...this.#restartFiles];
4347
- this.#restartFiles.clear();
4348
- this.#restartBackend(changed);
4505
+ const next = this.#pendingRestartReason;
4506
+ this.#pendingRestartReason = null;
4507
+ if (next)
4508
+ this.#restartBackend(next);
4349
4509
  }, BACKEND_RESTART_DEBOUNCE_MS);
4350
4510
  }
4351
- async#restartBackend(files) {
4352
- this.logger.verbose(`[backend-reload] restarting backend for ${files.length} file(s)`);
4511
+ async#restartBackend(reason) {
4512
+ this.logger.verbose(`[backend-reload] restarting backend generation=${reason.generation ?? "(unknown)"} files=${reason.files.length} roles=${reason.roles.join(",") || "(none)"}`);
4353
4513
  this.#backendRecoveryAttempts = 0;
4354
4514
  await Promise.all([this.#stopBackend(), this.#backendGraph.refresh()]);
4355
- this.#startBackend();
4515
+ this.#startBackend({ generation: reason.generation, files: reason.files });
4356
4516
  }
4357
4517
  #scheduleBackendRecovery(reason) {
4358
4518
  if (this.#backendRecoveryTimer || this.#backend)
4359
4519
  return;
4520
+ this.#setBackendLifecycleState("recovering", reason);
4360
4521
  const attempt = this.#backendRecoveryAttempts;
4361
4522
  const delay = Math.min(BACKEND_RECOVERY_BASE_DELAY_MS * 2 ** attempt, BACKEND_RECOVERY_MAX_DELAY_MS);
4362
4523
  this.#backendRecoveryAttempts = attempt + 1;
4524
+ const failureStatus = this.#recordBackendBuildStatus({
4525
+ ok: false,
4526
+ files: [],
4527
+ message: `Backend exited unexpectedly (${reason}); restarting in ${delay}ms`
4528
+ });
4529
+ this.#sendOrQueueBuildStatus(failureStatus);
4363
4530
  this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
4364
4531
  this.#backendRecoveryTimer = setTimeout(() => {
4365
4532
  this.#backendRecoveryTimer = null;
@@ -4367,7 +4534,7 @@ class AkanAppHost {
4367
4534
  return;
4368
4535
  this.#backendGraph.refresh().finally(() => {
4369
4536
  if (!this.#backend)
4370
- this.#startBackend();
4537
+ this.#startBackend({ generation: failureStatus.generation, files: failureStatus.files });
4371
4538
  });
4372
4539
  }, delay);
4373
4540
  }
@@ -4377,10 +4544,15 @@ class AkanAppHost {
4377
4544
  });
4378
4545
  }
4379
4546
  async#handleBuilderMessage(message) {
4547
+ if (message.type === "build-status") {
4548
+ this.#recordBuildStatus(message.data);
4549
+ this.#sendOrQueueBuildStatus(message.data);
4550
+ return;
4551
+ }
4380
4552
  if (message.type === "pages-updated")
4381
- this.#latestPagesUpdated = message;
4553
+ this.#recordLastGood(message);
4382
4554
  if (message.type === "css-updated")
4383
- this.#latestCssUpdated = message;
4555
+ this.#recordLastGood(message);
4384
4556
  if (message.type === "invalidate") {
4385
4557
  await this.#handleInvalidate(message);
4386
4558
  return;
@@ -4388,26 +4560,140 @@ class AkanAppHost {
4388
4560
  this.#sendToBackend(message);
4389
4561
  }
4390
4562
  async#handleInvalidate(message) {
4563
+ if (shouldRestartBuilderByDevPlan(message)) {
4564
+ try {
4565
+ await this.#restartDevChildren(message);
4566
+ } catch (err) {
4567
+ this.#recordDevHostRestartFailure(message, err);
4568
+ }
4569
+ return;
4570
+ }
4571
+ if (shouldRestartDevHostByDevPlan(message)) {
4572
+ this.#recordDevHostRestartRequired(message);
4573
+ return;
4574
+ }
4391
4575
  if (await this.#shouldRestartBackend(message)) {
4392
- this.#scheduleBackendRestart(message.files);
4576
+ this.#scheduleBackendRestart(backendRestartReasonFromMessage(message));
4393
4577
  return;
4394
4578
  }
4395
4579
  this.#sendToBackend(message);
4396
4580
  }
4581
+ async#restartDevChildren(message) {
4582
+ const generation = message.devPlan?.generation ?? message.generation;
4583
+ this.logger.warn(`[dev-host] recycling builder/backend for runtime metadata generation=${generation ?? "(unknown)"} files=${message.files.length}`);
4584
+ if (this.#restartTimer) {
4585
+ clearTimeout(this.#restartTimer);
4586
+ this.#restartTimer = null;
4587
+ }
4588
+ if (this.#backendRecoveryTimer) {
4589
+ clearTimeout(this.#backendRecoveryTimer);
4590
+ this.#backendRecoveryTimer = null;
4591
+ }
4592
+ this.#pendingRestartReason = null;
4593
+ this.#lastGoodFrontend = {};
4594
+ this.#buildStatusByPhase.clear();
4595
+ this.#pendingBuildStatusReplay = [];
4596
+ await this.#stopBackend();
4597
+ this.#stopBuilder();
4598
+ await this.#backendGraph.refresh();
4599
+ await this.#startBuilder();
4600
+ this.#startBackend({ generation, files: message.files });
4601
+ }
4602
+ #recordLastGood(message) {
4603
+ if (message.type === "pages-updated") {
4604
+ if (!shouldReplaceLastGoodMessage(this.#lastGoodFrontend.pages, message))
4605
+ return;
4606
+ this.#lastGoodFrontend.pages = message;
4607
+ this.logger.verbose(`[last-good] pages generation=${message.data.generation ?? "(unknown)"} buildId=${message.data.buildId}`);
4608
+ return;
4609
+ }
4610
+ if (!shouldReplaceLastGoodMessage(this.#lastGoodFrontend.css, message))
4611
+ return;
4612
+ this.#lastGoodFrontend.css = message;
4613
+ this.logger.verbose(`[last-good] css generation=${message.data.generation ?? "(unknown)"} assets=${Object.keys(message.data.cssAssets).length}`);
4614
+ }
4615
+ #recordDevHostRestartRequired(message) {
4616
+ const generation = message.devPlan?.generation ?? message.generation;
4617
+ const detail = `generation=${generation ?? "(unknown)"} files=${message.files.length}`;
4618
+ this.logger.warn(`[dev-host] config change requires a manual restart until controlled dev-host restart is implemented (${detail})`);
4619
+ if (typeof generation === "number") {
4620
+ const status = {
4621
+ generation,
4622
+ phase: "scan",
4623
+ ok: false,
4624
+ files: message.files,
4625
+ message: "Config change requires restarting `akan start` to apply."
4626
+ };
4627
+ this.#recordBuildStatus(status);
4628
+ this.#sendOrQueueBuildStatus(status);
4629
+ }
4630
+ }
4631
+ #recordDevHostRestartFailure(message, err) {
4632
+ const generation = message.devPlan?.generation ?? message.generation ?? this.#nextBackendBuildStatusGeneration();
4633
+ const detail = err instanceof Error ? err.message : String(err);
4634
+ this.logger.warn(`[dev-host] runtime metadata restart failed generation=${generation}: ${detail}`);
4635
+ const status = {
4636
+ generation,
4637
+ phase: "scan",
4638
+ ok: false,
4639
+ files: message.files,
4640
+ message: `Runtime metadata change requires restarting \`akan start\` to apply: ${detail}`
4641
+ };
4642
+ this.#recordBuildStatus(status);
4643
+ this.#sendOrQueueBuildStatus(status);
4644
+ }
4645
+ #recordBuildStatus(status) {
4646
+ const recovered = shouldMarkBuildPhaseRecovered(this.#buildStatusByPhase, status);
4647
+ this.#buildStatusByPhase.set(status.phase, status);
4648
+ const label = `[build-status] generation=${status.generation} phase=${status.phase} ok=${status.ok} files=${status.files.length}`;
4649
+ if (status.ok)
4650
+ this.logger.verbose(`${label}${recovered ? " recovered=1" : ""}`);
4651
+ else
4652
+ this.logger.warn(`${label}${status.message ? ` message=${status.message}` : ""}`);
4653
+ }
4654
+ #sendOrQueueBuildStatus(status) {
4655
+ if (!this.#backend || shouldQueueBuildStatusReplay(this.#backendReady, this.#pendingBuildStatusReplay.length)) {
4656
+ this.#pendingBuildStatusReplay.push(status);
4657
+ this.logger.verbose(`backend is not ready; will replay build-status generation=${status.generation} phase=${status.phase}`);
4658
+ return;
4659
+ }
4660
+ this.#sendToBackend({ type: "build-status", data: status });
4661
+ }
4397
4662
  #replayBuilderState() {
4398
4663
  if (!this.#backendReady)
4399
4664
  return;
4400
- if (this.#latestCssUpdated)
4401
- this.#sendToBackend(this.#latestCssUpdated);
4402
- if (this.#latestPagesUpdated)
4403
- this.#sendToBackend(this.#latestPagesUpdated);
4665
+ if (this.#lastGoodFrontend.css)
4666
+ this.#sendToBackend(this.#lastGoodFrontend.css);
4667
+ if (this.#lastGoodFrontend.pages)
4668
+ this.#sendToBackend(this.#lastGoodFrontend.pages);
4669
+ const queuedStatuses = this.#pendingBuildStatusReplay.splice(0);
4670
+ for (const status of buildStatusReplaySequence(queuedStatuses, this.#buildStatusByPhase)) {
4671
+ this.#sendToBackend({ type: "build-status", data: status });
4672
+ }
4404
4673
  }
4405
4674
  async#shouldRestartBackend(message) {
4406
4675
  if (message.kinds.length === 1 && message.kinds[0] === "css")
4407
4676
  return false;
4408
- if (!this.#backendGraph.ready && message.kinds.includes("code"))
4677
+ if (message.devPlan) {
4678
+ const { generation, roles, actions, reasonByFile } = message.devPlan;
4679
+ this.logger.verbose(`[dev-plan] generation=${generation} roles=${roles.join(",") || "(none)"} actions=${actions.join(",") || "(none)"} reasons=${Object.keys(reasonByFile).length}`);
4680
+ const shouldRestart = shouldRestartBackendByDevPlan(message) ?? false;
4681
+ if (shouldRestart && message.kinds.includes("code"))
4682
+ await this.#backendGraph.refresh();
4683
+ return shouldRestart;
4684
+ }
4685
+ if (message.kinds.includes("code"))
4409
4686
  await this.#backendGraph.refresh();
4410
- return message.files.some((file) => this.#isBackendFile(file));
4687
+ if (message.files.some((file) => this.#isBackendFile(file)))
4688
+ return true;
4689
+ if (!this.#backendGraph.lastRefreshSucceeded) {
4690
+ const fallbackFiles = message.files.filter((file) => isLegacyBackendFallbackFile(file, this.app.workspace.workspaceRoot));
4691
+ if (fallbackFiles.length > 0) {
4692
+ this.logger.warn(`[backend-graph] using path-role fallback for legacy invalidate; restart files=${fallbackFiles.length}`);
4693
+ return true;
4694
+ }
4695
+ }
4696
+ return false;
4411
4697
  }
4412
4698
  #isBackendFile(file) {
4413
4699
  return this.#backendGraph.has(file);
@@ -4763,8 +5049,8 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
4763
5049
  }
4764
5050
  }
4765
5051
  // pkgs/@akanjs/devkit/applicationBuildRunner.ts
4766
- import { mkdir as mkdir7, rm as rm2 } from "fs/promises";
4767
- import path33 from "path";
5052
+ import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
5053
+ import path35 from "path";
4768
5054
 
4769
5055
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
4770
5056
  import path20 from "path";
@@ -7232,9 +7518,306 @@ function getPageKeyBasePath(pageKey, basePaths) {
7232
7518
  const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
7233
7519
  return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
7234
7520
  }
7235
- // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
7236
- import { mkdir as mkdir6 } from "fs/promises";
7521
+ // pkgs/@akanjs/devkit/frontendBuild/devChangePlanner.ts
7237
7522
  import path25 from "path";
7523
+ var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
7524
+ var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
7525
+ var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit"]);
7526
+ var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
7527
+ var SHARED_SUFFIXES2 = [".constant.ts", ".dictionary.ts", ".signal.ts"];
7528
+ var SERVER_SUFFIXES2 = [".service.ts", ".document.ts"];
7529
+ var RUNTIME_METADATA_BASENAMES2 = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
7530
+
7531
+ class DevChangePlanner {
7532
+ #workspaceRoot;
7533
+ constructor({ workspaceRoot }) {
7534
+ this.#workspaceRoot = path25.resolve(workspaceRoot);
7535
+ }
7536
+ plan({ generation, files, kinds, generatedFiles: generatedFiles2 = [] }) {
7537
+ const fileList = uniqueResolved([...files, ...generatedFiles2]);
7538
+ const generatedSet = new Set(generatedFiles2.map((file) => path25.resolve(file)));
7539
+ const kindSet = new Set(kinds);
7540
+ const roles = new Set;
7541
+ const actions = new Set;
7542
+ const reasonByFile = {};
7543
+ for (const kind of kindSet) {
7544
+ if (kind === "css") {
7545
+ roles.add("css");
7546
+ actions.add("rebuild-css");
7547
+ }
7548
+ if (kind === "config") {
7549
+ roles.add("config");
7550
+ actions.add("restart-dev-host");
7551
+ }
7552
+ }
7553
+ for (const file of fileList) {
7554
+ const reasons = new Set;
7555
+ const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path25.resolve(file)), reasons });
7556
+ for (const role of fileRoles)
7557
+ roles.add(role);
7558
+ if (reasons.has("runtime-metadata"))
7559
+ actions.add("restart-builder");
7560
+ if (reasons.size > 0)
7561
+ reasonByFile[path25.resolve(file)] = [...reasons].sort();
7562
+ }
7563
+ if (roles.has("barrel"))
7564
+ actions.add("sync-generated");
7565
+ if (roles.has("server") || roles.has("shared"))
7566
+ actions.add("restart-backend");
7567
+ if (roles.has("client") || roles.has("shared"))
7568
+ actions.add("rebuild-client");
7569
+ if (roles.has("css"))
7570
+ actions.add("rebuild-css");
7571
+ return {
7572
+ generation,
7573
+ files: fileList,
7574
+ generatedFiles: uniqueResolved(generatedFiles2),
7575
+ roles: [...roles].sort(),
7576
+ actions: [...actions].sort(),
7577
+ reasonByFile
7578
+ };
7579
+ }
7580
+ #rolesForFile(file, { isGenerated, reasons }) {
7581
+ const roles = new Set;
7582
+ const abs = path25.resolve(file);
7583
+ const base = path25.basename(abs);
7584
+ const ext = path25.extname(abs).toLowerCase();
7585
+ const isSource = SOURCE_EXTS4.has(ext);
7586
+ const rel = path25.relative(this.#workspaceRoot, abs);
7587
+ const parts = rel.split(path25.sep).filter(Boolean);
7588
+ if (CONFIG_BASENAMES.has(base)) {
7589
+ roles.add("config");
7590
+ reasons.add("config-file");
7591
+ }
7592
+ if (ext === ".css") {
7593
+ roles.add("css");
7594
+ reasons.add("css-file");
7595
+ }
7596
+ if (isGenerated || isSource && this.#isBarrelFacetChild(parts)) {
7597
+ roles.add("barrel");
7598
+ reasons.add(isGenerated ? "generated-index" : "barrel-facet-child");
7599
+ }
7600
+ if (isSource && this.#isServerBiased(abs, parts)) {
7601
+ roles.add("server");
7602
+ reasons.add("server-path");
7603
+ }
7604
+ if (isSource && this.#isClientBiased(abs, parts)) {
7605
+ roles.add("client");
7606
+ reasons.add("client-path");
7607
+ }
7608
+ if (isSource && this.#isSharedBiased(abs, parts)) {
7609
+ roles.add("shared");
7610
+ reasons.add("shared-path");
7611
+ }
7612
+ if (isSource && this.#isRuntimeMetadataFile(parts, base)) {
7613
+ reasons.add("runtime-metadata");
7614
+ }
7615
+ if (roles.has("server") && roles.has("client")) {
7616
+ roles.delete("server");
7617
+ roles.delete("client");
7618
+ roles.add("shared");
7619
+ reasons.add("server-client-overlap");
7620
+ }
7621
+ if (roles.size === 0 && SOURCE_EXTS4.has(ext) && this.#isWorkspaceSource(rel)) {
7622
+ roles.add("shared");
7623
+ reasons.add("workspace-source-fallback");
7624
+ }
7625
+ return roles;
7626
+ }
7627
+ #isServerBiased(abs, parts) {
7628
+ const base = path25.basename(abs);
7629
+ return parts.includes("srvkit") || SERVER_SUFFIXES2.some((suffix) => base.endsWith(suffix)) || base === "main.ts" || base === "server.ts";
7630
+ }
7631
+ #isClientBiased(abs, parts) {
7632
+ const base = path25.basename(abs);
7633
+ return parts.includes("ui") || parts.includes("webkit") || parts.includes("page") || CLIENT_SUFFIXES.some((suffix) => base.endsWith(suffix));
7634
+ }
7635
+ #isSharedBiased(abs, parts) {
7636
+ const base = path25.basename(abs);
7637
+ return parts.includes("common") || SHARED_SUFFIXES2.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES2.has(base);
7638
+ }
7639
+ #isRuntimeMetadataFile(parts, base) {
7640
+ const parent = parts.at(-2);
7641
+ if (parent === "lib" && RUNTIME_METADATA_BASENAMES2.has(base))
7642
+ return true;
7643
+ const libIndex = parts.lastIndexOf("lib");
7644
+ if (libIndex < 0 || parts.length <= libIndex + 1)
7645
+ return false;
7646
+ return base.endsWith(".dictionary.ts") || base.endsWith(".signal.ts");
7647
+ }
7648
+ #isBarrelFacetChild(parts) {
7649
+ if (parts.length < 4)
7650
+ return false;
7651
+ const [scope, , facet, child] = parts;
7652
+ if (scope !== "apps" && scope !== "libs")
7653
+ return false;
7654
+ if (!facet || !BARREL_FACETS.has(facet))
7655
+ return false;
7656
+ if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
7657
+ return false;
7658
+ return true;
7659
+ }
7660
+ #isWorkspaceSource(rel) {
7661
+ if (!rel || rel.startsWith("..") || path25.isAbsolute(rel))
7662
+ return false;
7663
+ const [scope] = rel.split(path25.sep);
7664
+ return scope === "apps" || scope === "libs" || scope === "pkgs";
7665
+ }
7666
+ }
7667
+ var uniqueResolved = (files) => [...new Set(files.map((file) => path25.resolve(file)))].sort();
7668
+ // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
7669
+ import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm2, stat as stat3, writeFile } from "fs/promises";
7670
+ import path26 from "path";
7671
+ var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
7672
+ var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
7673
+ var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
7674
+ var MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"];
7675
+ var SERVICE_UI_TYPES = ["Util", "Zone"];
7676
+ var SCALAR_UI_TYPES = ["Template", "Unit"];
7677
+
7678
+ class DevGeneratedIndexSync {
7679
+ #workspaceRoot;
7680
+ constructor({ workspaceRoot }) {
7681
+ this.#workspaceRoot = path26.resolve(workspaceRoot);
7682
+ }
7683
+ async syncForBatch(files) {
7684
+ const indexPaths = new Set;
7685
+ const errors = [];
7686
+ for (const file of files) {
7687
+ const facetIndex = this.#facetIndexFor(file);
7688
+ if (facetIndex)
7689
+ indexPaths.add(facetIndex);
7690
+ const moduleIndex = await this.#moduleIndexForDirectoryEvent(file).catch((err) => {
7691
+ errors.push(`[generated-index] module detection failed for ${file}: ${formatError(err)}`);
7692
+ return null;
7693
+ });
7694
+ if (moduleIndex)
7695
+ indexPaths.add(moduleIndex);
7696
+ }
7697
+ const changedFiles = [];
7698
+ for (const indexPath of [...indexPaths].sort()) {
7699
+ try {
7700
+ const changed = await this.#syncIndex(indexPath);
7701
+ if (changed)
7702
+ changedFiles.push(indexPath);
7703
+ } catch (err) {
7704
+ errors.push(`[generated-index] sync failed for ${indexPath}: ${formatError(err)}`);
7705
+ }
7706
+ }
7707
+ return { changedFiles, errors };
7708
+ }
7709
+ #facetIndexFor(file) {
7710
+ const abs = path26.resolve(file);
7711
+ const rel = path26.relative(this.#workspaceRoot, abs);
7712
+ if (rel.startsWith("..") || path26.isAbsolute(rel))
7713
+ return null;
7714
+ const parts = rel.split(path26.sep).filter(Boolean);
7715
+ if (parts.length < 4)
7716
+ return null;
7717
+ const [scope, project, facet, child] = parts;
7718
+ if (scope !== "apps" && scope !== "libs" || !project || !facet || !BARREL_FACETS2.has(facet))
7719
+ return null;
7720
+ if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
7721
+ return null;
7722
+ return path26.join(this.#workspaceRoot, scope, project, facet, "index.ts");
7723
+ }
7724
+ async#moduleIndexForDirectoryEvent(file) {
7725
+ const abs = path26.resolve(file);
7726
+ const rel = path26.relative(this.#workspaceRoot, abs);
7727
+ if (rel.startsWith("..") || path26.isAbsolute(rel))
7728
+ return null;
7729
+ const parts = rel.split(path26.sep).filter(Boolean);
7730
+ if (parts.length !== 4 && parts.length !== 5)
7731
+ return null;
7732
+ const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
7733
+ if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
7734
+ return null;
7735
+ const isExistingDirectory = await stat3(abs).then((s) => s.isDirectory()).catch(() => false);
7736
+ const looksLikeDeletedDirectory = !path26.extname(abs);
7737
+ if (!isExistingDirectory && !looksLikeDeletedDirectory)
7738
+ return null;
7739
+ if (moduleSegment === "__scalar" && scalarSegment) {
7740
+ return path26.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
7741
+ }
7742
+ if (!moduleSegment || moduleSegment === "__scalar")
7743
+ return null;
7744
+ return path26.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
7745
+ }
7746
+ async#syncIndex(indexPath) {
7747
+ const dir = path26.dirname(indexPath);
7748
+ const content = await this.#contentForIndex(indexPath);
7749
+ if (content === null) {
7750
+ if (!await exists(indexPath))
7751
+ return false;
7752
+ await rm2(indexPath, { force: true });
7753
+ return true;
7754
+ }
7755
+ const current = await readFile(indexPath, "utf8").catch(() => null);
7756
+ if (current === content)
7757
+ return false;
7758
+ await mkdir6(dir, { recursive: true });
7759
+ await writeFile(indexPath, content);
7760
+ return true;
7761
+ }
7762
+ async#contentForIndex(indexPath) {
7763
+ const parts = path26.relative(this.#workspaceRoot, indexPath).split(path26.sep).filter(Boolean);
7764
+ const facet = parts.at(-2);
7765
+ if (facet && BARREL_FACETS2.has(facet))
7766
+ return this.#facetContent(path26.dirname(indexPath));
7767
+ return this.#moduleContent(path26.dirname(indexPath));
7768
+ }
7769
+ async#facetContent(dir) {
7770
+ const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
7771
+ const exportNames = entries.flatMap((entry) => {
7772
+ const name = entry.name;
7773
+ if (name.startsWith("."))
7774
+ return [];
7775
+ if (entry.isDirectory())
7776
+ return [name];
7777
+ if (!entry.isFile())
7778
+ return [];
7779
+ if (!FACET_SOURCE_FILE_RE.test(name) || FACET_EXCLUDED_FILE_RE.test(name))
7780
+ return [];
7781
+ return [name.replace(FACET_SOURCE_FILE_RE, "")];
7782
+ }).sort();
7783
+ if (exportNames.length === 0)
7784
+ return null;
7785
+ return `${exportNames.map((name) => `export * from "./${name}";`).join(`
7786
+ `)}
7787
+ `;
7788
+ }
7789
+ async#moduleContent(dir) {
7790
+ const rel = path26.relative(this.#workspaceRoot, dir);
7791
+ const parts = rel.split(path26.sep).filter(Boolean);
7792
+ const moduleSegment = parts.at(-1);
7793
+ if (!moduleSegment)
7794
+ return null;
7795
+ const isScalar = parts.at(-2) === "__scalar";
7796
+ const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
7797
+ if (!rawModel)
7798
+ return null;
7799
+ const modelName = capitalize3(rawModel);
7800
+ const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
7801
+ const fileTypes = [];
7802
+ for (const type of allowedTypes) {
7803
+ if (await exists(path26.join(dir, `${modelName}.${type}.tsx`)))
7804
+ fileTypes.push(type);
7805
+ }
7806
+ if (fileTypes.length === 0)
7807
+ return null;
7808
+ return `
7809
+ ${fileTypes.map((type) => `import * as ${type} from "./${modelName}.${type}";`).join(`
7810
+ `)}
7811
+
7812
+ export const ${modelName} = { ${fileTypes.join(", ")} };`;
7813
+ }
7814
+ }
7815
+ var exists = async (file) => stat3(file).then(() => true).catch(() => false);
7816
+ var capitalize3 = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
7817
+ var formatError = (err) => err instanceof Error ? err.message : String(err);
7818
+ // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
7819
+ import { mkdir as mkdir7 } from "fs/promises";
7820
+ import path27 from "path";
7238
7821
  import {
7239
7822
  generateFontFace,
7240
7823
  getMetricsForFamily,
@@ -7258,7 +7841,7 @@ class FontOptimizer {
7258
7841
  constructor(app, command = "start") {
7259
7842
  this.#app = app;
7260
7843
  this.#command = command;
7261
- this.#artifactRoot = path25.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
7844
+ this.#artifactRoot = path27.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
7262
7845
  }
7263
7846
  async optimize() {
7264
7847
  const fonts = await this.discoverFonts();
@@ -7277,7 +7860,7 @@ class FontOptimizer {
7277
7860
  const pageKeys = await this.#app.getPageKeys();
7278
7861
  const fonts = [];
7279
7862
  await Promise.all(pageKeys.map(async (key) => {
7280
- const filePath = path25.resolve(this.#app.cwdPath, "page", key);
7863
+ const filePath = path27.resolve(this.#app.cwdPath, "page", key);
7281
7864
  const file = Bun.file(filePath);
7282
7865
  if (!await file.exists())
7283
7866
  return;
@@ -7293,8 +7876,8 @@ class FontOptimizer {
7293
7876
  this.#app.logger.warn(`[font] source not found: ${face.src}`);
7294
7877
  continue;
7295
7878
  }
7296
- const outputPath = path25.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
7297
- await mkdir6(path25.dirname(outputPath), { recursive: true });
7879
+ const outputPath = path27.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
7880
+ await mkdir7(path27.dirname(outputPath), { recursive: true });
7298
7881
  const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
7299
7882
  const outputBuffer = font.subset === false ? await this.#convertToWoff2(sourceBuffer, sourcePath) : await subsetFont(sourceBuffer, await this.#getSubsetText(font), { targetFormat: "woff2" });
7300
7883
  await Bun.write(outputPath, outputBuffer);
@@ -7441,8 +8024,8 @@ class FontOptimizer {
7441
8024
  return null;
7442
8025
  const rel = src.replace(/^\//, "");
7443
8026
  const candidates = [
7444
- this.#command === "build" ? path25.join(this.#app.dist.cwdPath, "public", rel) : null,
7445
- path25.join(this.#app.cwdPath, "public", rel),
8027
+ this.#command === "build" ? path27.join(this.#app.dist.cwdPath, "public", rel) : null,
8028
+ path27.join(this.#app.cwdPath, "public", rel),
7446
8029
  this.#resolveWorkspacePublicPath(rel)
7447
8030
  ].filter(Boolean);
7448
8031
  for (const candidate of candidates) {
@@ -7470,7 +8053,7 @@ class FontOptimizer {
7470
8053
  return "woff2";
7471
8054
  if (signature === "OTTO")
7472
8055
  return "otf";
7473
- const ext = path25.extname(sourcePath).slice(1).toLowerCase();
8056
+ const ext = path27.extname(sourcePath).slice(1).toLowerCase();
7474
8057
  if (ext === "otf" || ext === "woff" || ext === "woff2")
7475
8058
  return ext;
7476
8059
  return "ttf";
@@ -7479,7 +8062,7 @@ class FontOptimizer {
7479
8062
  const [root, dep, ...rest] = rel.split("/");
7480
8063
  if (root !== "libs" || !dep || rest.length === 0)
7481
8064
  return null;
7482
- return path25.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
8065
+ return path27.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
7483
8066
  }
7484
8067
  async#getSubsetText(font) {
7485
8068
  const parts = new Set;
@@ -7489,7 +8072,7 @@ class FontOptimizer {
7489
8072
  if (font.subsetText)
7490
8073
  parts.add(font.subsetText);
7491
8074
  for (const filePath of font.subsetFiles ?? []) {
7492
- const abs = path25.isAbsolute(filePath) ? filePath : path25.join(this.#app.cwdPath, filePath);
8075
+ const abs = path27.isAbsolute(filePath) ? filePath : path27.join(this.#app.cwdPath, filePath);
7493
8076
  const file = Bun.file(abs);
7494
8077
  if (await file.exists())
7495
8078
  parts.add(await file.text());
@@ -7508,7 +8091,7 @@ class FontOptimizer {
7508
8091
  return "";
7509
8092
  }
7510
8093
  async#collectAutoSubsetText() {
7511
- const roots = ["page", "ui"].map((dir) => path25.join(this.#app.cwdPath, dir));
8094
+ const roots = ["page", "ui"].map((dir) => path27.join(this.#app.cwdPath, dir));
7512
8095
  const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
7513
8096
  const parts = [];
7514
8097
  await Promise.all(roots.map(async (root) => {
@@ -7622,43 +8205,43 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
7622
8205
  }
7623
8206
  }
7624
8207
  // pkgs/@akanjs/devkit/frontendBuild/hmrChangeClassifier.ts
7625
- import path26 from "path";
7626
- var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
8208
+ import path28 from "path";
8209
+ var SOURCE_EXTS5 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
7627
8210
  var CSS_EXTS = new Set([".css"]);
7628
- var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
8211
+ var CONFIG_BASENAMES2 = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
7629
8212
 
7630
8213
  class HmrChangeClassifier {
7631
8214
  classify(abs) {
7632
8215
  if (this.#isUninteresting(abs))
7633
8216
  return "ignore";
7634
- const base = path26.basename(abs);
7635
- if (CONFIG_BASENAMES.has(base))
8217
+ const base = path28.basename(abs);
8218
+ if (CONFIG_BASENAMES2.has(base))
7636
8219
  return "config";
7637
- const ext = path26.extname(abs).toLowerCase();
8220
+ const ext = path28.extname(abs).toLowerCase();
7638
8221
  if (CSS_EXTS.has(ext))
7639
8222
  return "css";
7640
- if (SOURCE_EXTS4.has(ext))
8223
+ if (SOURCE_EXTS5.has(ext))
7641
8224
  return "code";
7642
8225
  return "ignore";
7643
8226
  }
7644
8227
  #isUninteresting(abs) {
7645
- const base = path26.basename(abs);
8228
+ const base = path28.basename(abs);
7646
8229
  if (!base)
7647
8230
  return true;
7648
8231
  if (base.startsWith("."))
7649
8232
  return true;
7650
8233
  if (base.endsWith("~") || base.endsWith(".swp") || base.endsWith(".swx") || base.endsWith(".tmp"))
7651
8234
  return true;
7652
- if (abs.includes(`${path26.sep}node_modules${path26.sep}`))
8235
+ if (abs.includes(`${path28.sep}node_modules${path28.sep}`))
7653
8236
  return true;
7654
- if (abs.includes(`${path26.sep}.akan${path26.sep}`))
8237
+ if (abs.includes(`${path28.sep}.akan${path28.sep}`))
7655
8238
  return true;
7656
8239
  return false;
7657
8240
  }
7658
8241
  }
7659
8242
  // pkgs/@akanjs/devkit/frontendBuild/hmrWatcher.ts
7660
8243
  import fs4 from "fs";
7661
- import path27 from "path";
8244
+ import path29 from "path";
7662
8245
  class HmrWatcher {
7663
8246
  #roots;
7664
8247
  #debounceMs;
@@ -7671,7 +8254,7 @@ class HmrWatcher {
7671
8254
  #stopped = false;
7672
8255
  #flushing = false;
7673
8256
  constructor(opts) {
7674
- this.#roots = [...new Set(opts.roots.map((r) => path27.resolve(r)))];
8257
+ this.#roots = [...new Set(opts.roots.map((r) => path29.resolve(r)))];
7675
8258
  this.#debounceMs = opts.debounceMs ?? 80;
7676
8259
  this.#onBatch = opts.onBatch;
7677
8260
  this.#logger = opts.logger;
@@ -7682,7 +8265,7 @@ class HmrWatcher {
7682
8265
  const w = fs4.watch(root, { recursive: true, persistent: false }, (_event, filename) => {
7683
8266
  if (!filename)
7684
8267
  return;
7685
- const abs = path27.resolve(root, filename.toString());
8268
+ const abs = path29.resolve(root, filename.toString());
7686
8269
  this.#queue(abs);
7687
8270
  });
7688
8271
  this.#watchers.push(w);
@@ -7740,10 +8323,10 @@ class HmrWatcher {
7740
8323
  }
7741
8324
  }
7742
8325
  // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
7743
- import path29 from "path";
8326
+ import path31 from "path";
7744
8327
 
7745
8328
  // pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
7746
- import path28 from "path";
8329
+ import path30 from "path";
7747
8330
  var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
7748
8331
  var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
7749
8332
  var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
@@ -7786,7 +8369,7 @@ async function createExternalizeFrameworkPlugin(options) {
7786
8369
  const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
7787
8370
  if (!replPath)
7788
8371
  continue;
7789
- const candidate = path28.resolve(workspaceRoot, replPath + suffix);
8372
+ const candidate = path30.resolve(workspaceRoot, replPath + suffix);
7790
8373
  const hit = await firstExisting(candidate);
7791
8374
  if (hit)
7792
8375
  return hit;
@@ -7798,8 +8381,8 @@ async function createExternalizeFrameworkPlugin(options) {
7798
8381
  if (spec === pkg)
7799
8382
  continue;
7800
8383
  const suffix = spec.slice(pkg.length + 1);
7801
- const pkgDir = path28.dirname(path28.resolve(workspaceRoot, entryFile));
7802
- const candidate = path28.join(pkgDir, suffix);
8384
+ const pkgDir = path30.dirname(path30.resolve(workspaceRoot, entryFile));
8385
+ const candidate = path30.join(pkgDir, suffix);
7803
8386
  const hit = await firstExisting(candidate);
7804
8387
  if (hit)
7805
8388
  return hit;
@@ -7849,7 +8432,7 @@ async function firstExisting(basePath2) {
7849
8432
  return candidate;
7850
8433
  }
7851
8434
  for (const ext of CANDIDATE_EXTS3) {
7852
- const candidate = path28.join(basePath2, `index${ext}`);
8435
+ const candidate = path30.join(basePath2, `index${ext}`);
7853
8436
  if (await Bun.file(candidate).exists())
7854
8437
  return candidate;
7855
8438
  }
@@ -7940,11 +8523,11 @@ class PagesBundleBuilder {
7940
8523
  const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
7941
8524
  if (!entryArtifact)
7942
8525
  throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
7943
- const bundlePath = path29.resolve(entryArtifact.path);
8526
+ const bundlePath = path31.resolve(entryArtifact.path);
7944
8527
  const buildId = Date.now();
7945
8528
  const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
7946
8529
  const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
7947
- this.#app.verbose(`[PagesBundleBuilder] ${path29.basename(bundlePath)} emitted in ${Date.now() - this.#started}ms splitting=${this.#splitting} entry=${entryArtifact.size} bytes outputs=${result.outputs.length} chunks=${chunkCount} total=${outputBytes} bytes`);
8530
+ this.#app.verbose(`[PagesBundleBuilder] ${path31.basename(bundlePath)} emitted in ${Date.now() - this.#started}ms splitting=${this.#splitting} entry=${entryArtifact.size} bytes outputs=${result.outputs.length} chunks=${chunkCount} total=${outputBytes} bytes`);
7948
8531
  return {
7949
8532
  bundlePath,
7950
8533
  buildId,
@@ -8026,11 +8609,11 @@ function loaderFor4(absPath) {
8026
8609
  }
8027
8610
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
8028
8611
  import fs5 from "fs";
8029
- import path30 from "path";
8612
+ import path32 from "path";
8030
8613
  var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
8031
8614
  var MIN_COMPRESS_BYTES = 1024;
8032
8615
  async function precompressArtifacts(app) {
8033
- const roots = [path30.join(app.dist.cwdPath, ".akan/artifact/client")];
8616
+ const roots = [path32.join(app.dist.cwdPath, ".akan/artifact/client")];
8034
8617
  const result = { files: 0, inputBytes: 0, outputBytes: 0 };
8035
8618
  await Promise.all(roots.map((root) => precompressRoot(root, result)));
8036
8619
  if (result.files > 0) {
@@ -8056,7 +8639,7 @@ async function precompressRoot(root, result) {
8056
8639
  async function shouldPrecompress(filePath) {
8057
8640
  if (filePath.endsWith(".gz"))
8058
8641
  return false;
8059
- if (!COMPRESSIBLE_EXTS.has(path30.extname(filePath).toLowerCase()))
8642
+ if (!COMPRESSIBLE_EXTS.has(path32.extname(filePath).toLowerCase()))
8060
8643
  return false;
8061
8644
  const file = Bun.file(filePath);
8062
8645
  if (!await file.exists())
@@ -8074,7 +8657,7 @@ function formatBytes(bytes) {
8074
8657
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
8075
8658
  }
8076
8659
  // pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
8077
- import path31 from "path";
8660
+ import path33 from "path";
8078
8661
  import { optimize } from "@tailwindcss/node";
8079
8662
  function prepareCssAsset(command, basePath2, cssText) {
8080
8663
  return optimize(cssText, { file: `${basePath2 || "root"}.css`, minify: command === "build" }).code;
@@ -8090,7 +8673,7 @@ class SsrBaseArtifactBuilder {
8090
8673
  this.#app = app;
8091
8674
  this.#command = command;
8092
8675
  this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
8093
- this.#absArtifactDir = path31.resolve(this.#artifactDir);
8676
+ this.#absArtifactDir = path33.resolve(this.#artifactDir);
8094
8677
  }
8095
8678
  async build() {
8096
8679
  const akanConfig2 = await this.#app.getConfig();
@@ -8112,7 +8695,7 @@ class SsrBaseArtifactBuilder {
8112
8695
  rscRuntimeSsrManifest,
8113
8696
  vendorMap,
8114
8697
  cssAssets,
8115
- pagesBundlePath: this.#command === "build" ? path31.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
8698
+ pagesBundlePath: this.#command === "build" ? path33.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
8116
8699
  pagesBundleBuildId: pagesBundle.buildId,
8117
8700
  domains: [...akanConfig2.domains],
8118
8701
  subRoutes: Object.fromEntries(Array.from(akanConfig2.subRoutes.entries()).map(([basePath2, domains]) => [basePath2, [...domains]])),
@@ -8121,7 +8704,7 @@ class SsrBaseArtifactBuilder {
8121
8704
  i18n: akanConfig2.i18n,
8122
8705
  imageConfig: akanConfig2.images
8123
8706
  };
8124
- await Bun.write(path31.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
8707
+ await Bun.write(path33.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
8125
8708
  `);
8126
8709
  this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
8127
8710
  return { artifact, seedIndex, cssCompiler, optimizedFonts };
@@ -8169,15 +8752,15 @@ class SsrBaseArtifactBuilder {
8169
8752
  async#resolveAkanServerPath() {
8170
8753
  const candidates = [];
8171
8754
  try {
8172
- candidates.push(path31.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
8755
+ candidates.push(path33.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
8173
8756
  } catch {}
8174
- candidates.push(path31.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path31.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
8757
+ candidates.push(path33.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path33.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
8175
8758
  try {
8176
- candidates.push(path31.dirname(Bun.resolveSync("akanjs/server", path31.dirname(Bun.main))));
8759
+ candidates.push(path33.dirname(Bun.resolveSync("akanjs/server", path33.dirname(Bun.main))));
8177
8760
  } catch {}
8178
- candidates.push(path31.join(path31.dirname(Bun.main), "node_modules/akanjs/server"), path31.join(path31.dirname(Bun.main), "../../akanjs/server"), path31.resolve(import.meta.dir, "../../server"), path31.resolve(import.meta.dir, "../server"));
8761
+ candidates.push(path33.join(path33.dirname(Bun.main), "node_modules/akanjs/server"), path33.join(path33.dirname(Bun.main), "../../akanjs/server"), path33.resolve(import.meta.dir, "../../server"), path33.resolve(import.meta.dir, "../server"));
8179
8762
  for (const candidate of candidates) {
8180
- if (await Bun.file(path31.join(candidate, "rscClient.tsx")).exists())
8763
+ if (await Bun.file(path33.join(candidate, "rscClient.tsx")).exists())
8181
8764
  return candidate;
8182
8765
  }
8183
8766
  throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
@@ -8206,14 +8789,14 @@ ${preparedCssText}`).toString(36);
8206
8789
  `styles/${cssAssetName}-${cssHash}.css`,
8207
8790
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
8208
8791
  ];
8209
- await Bun.write(path31.join(this.#absArtifactDir, cssRelPath), preparedCssText);
8792
+ await Bun.write(path33.join(this.#absArtifactDir, cssRelPath), preparedCssText);
8210
8793
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath2} -> ${cssRelPath}`);
8211
8794
  return [basePath2, { cssUrl, cssRelPath }];
8212
8795
  }
8213
8796
  }
8214
8797
  // pkgs/@akanjs/devkit/frontendBuild/watchRootResolver.ts
8215
8798
  import fs6 from "fs";
8216
- import path32 from "path";
8799
+ import path34 from "path";
8217
8800
 
8218
8801
  class WatchRootResolver {
8219
8802
  #app;
@@ -8223,15 +8806,15 @@ class WatchRootResolver {
8223
8806
  async resolve() {
8224
8807
  const tsconfig = await this.#app.getTsConfig();
8225
8808
  const set = new Set;
8226
- set.add(path32.resolve(`${this.#app.cwdPath}/page`));
8809
+ set.add(path34.resolve(`${this.#app.cwdPath}/page`));
8227
8810
  for (const targets of Object.values(tsconfig.compilerOptions.paths ?? {})) {
8228
8811
  for (const target of targets) {
8229
8812
  if (!target)
8230
8813
  continue;
8231
- if (path32.isAbsolute(target))
8814
+ if (path34.isAbsolute(target))
8232
8815
  continue;
8233
8816
  const cleaned = target.replace(/\/?\*+.*$/, "").replace(/\/[^/]+\.[^/]+$/, "");
8234
- const resolved = path32.resolve(this.#app.workspace.workspaceRoot, cleaned);
8817
+ const resolved = path34.resolve(this.#app.workspace.workspaceRoot, cleaned);
8235
8818
  if (fs6.existsSync(resolved))
8236
8819
  set.add(resolved);
8237
8820
  }
@@ -8298,7 +8881,7 @@ class ApplicationBuildRunner {
8298
8881
  phases: this.#phases,
8299
8882
  durationMs: Date.now() - this.#startedAt,
8300
8883
  outputDir: this.#app.dist.cwdPath,
8301
- artifactDir: path33.join(this.#app.dist.cwdPath, ".akan/artifact")
8884
+ artifactDir: path35.join(this.#app.dist.cwdPath, ".akan/artifact")
8302
8885
  };
8303
8886
  }
8304
8887
  async typecheck(options = {}) {
@@ -8306,7 +8889,7 @@ class ApplicationBuildRunner {
8306
8889
  await this.#app.getPageKeys({ refresh: true });
8307
8890
  const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig({ incremental });
8308
8891
  if (clean)
8309
- await rm2(path33.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
8892
+ await rm3(path35.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
8310
8893
  await this.#checkProjectInChildProcess(tsconfigPath);
8311
8894
  }
8312
8895
  async#runPhase(id, label, task, summarize, options = {}) {
@@ -8378,7 +8961,7 @@ class ApplicationBuildRunner {
8378
8961
  };
8379
8962
  }
8380
8963
  async#writeConsoleShim() {
8381
- await Bun.write(path33.join(this.#app.dist.cwdPath, "console.js"), `import { cnst, db, dict, option, server, sig, srv } from "./server.js";
8964
+ await Bun.write(path35.join(this.#app.dist.cwdPath, "console.js"), `import { cnst, db, dict, option, server, sig, srv } from "./server.js";
8382
8965
  import { assertAkanConsoleAllowed, startAkanConsole } from "./console-runtime.js";
8383
8966
 
8384
8967
  const run = async () => {
@@ -8401,14 +8984,14 @@ void run().catch((error) => {
8401
8984
  try {
8402
8985
  return Bun.resolveSync("akanjs/server/rsc-worker", import.meta.dir);
8403
8986
  } catch {
8404
- return path33.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/rscWorker.tsx");
8987
+ return path35.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/rscWorker.tsx");
8405
8988
  }
8406
8989
  }
8407
8990
  #resolveConsoleRuntimeBuildEntry() {
8408
8991
  try {
8409
- return path33.join(path33.dirname(Bun.resolveSync("akanjs/server", import.meta.dir)), "console.ts");
8992
+ return path35.join(path35.dirname(Bun.resolveSync("akanjs/server", import.meta.dir)), "console.ts");
8410
8993
  } catch {
8411
- return path33.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/console.ts");
8994
+ return path35.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/console.ts");
8412
8995
  }
8413
8996
  }
8414
8997
  async#buildCsr() {
@@ -8425,8 +9008,8 @@ void run().catch((error) => {
8425
9008
  return { base, allRoutes };
8426
9009
  }
8427
9010
  async#writeTypecheckTsconfig({ incremental = true } = {}) {
8428
- const typecheckDir = path33.join(this.#app.cwdPath, ".akan", "typecheck");
8429
- await mkdir7(typecheckDir, { recursive: true });
9011
+ const typecheckDir = path35.join(this.#app.cwdPath, ".akan", "typecheck");
9012
+ await mkdir8(typecheckDir, { recursive: true });
8430
9013
  const tsconfig = {
8431
9014
  extends: "../../tsconfig.json",
8432
9015
  compilerOptions: {
@@ -8444,7 +9027,7 @@ void run().catch((error) => {
8444
9027
  ],
8445
9028
  references: []
8446
9029
  };
8447
- const tsconfigPath = path33.join(typecheckDir, "tsconfig.json");
9030
+ const tsconfigPath = path35.join(typecheckDir, "tsconfig.json");
8448
9031
  await Bun.write(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
8449
9032
  `);
8450
9033
  return { typecheckDir, tsconfigPath };
@@ -8470,10 +9053,10 @@ void run().catch((error) => {
8470
9053
  }
8471
9054
  async#resolveTypecheckWorkerEntry() {
8472
9055
  const candidates = [
8473
- path33.join(this.#app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
8474
- path33.join(this.#app.workspace.workspaceRoot, "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
8475
- path33.join(import.meta.dir, "typecheck.proc.js"),
8476
- path33.join(import.meta.dir, "typecheck.proc.ts")
9056
+ path35.join(this.#app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
9057
+ path35.join(this.#app.workspace.workspaceRoot, "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
9058
+ path35.join(import.meta.dir, "typecheck.proc.js"),
9059
+ path35.join(import.meta.dir, "typecheck.proc.ts")
8477
9060
  ];
8478
9061
  for (const candidate of candidates)
8479
9062
  if (await Bun.file(candidate).exists())
@@ -8506,7 +9089,7 @@ void run().catch((error) => {
8506
9089
  }
8507
9090
  }
8508
9091
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
8509
- import { cp, mkdir as mkdir8, rm as rm3 } from "fs/promises";
9092
+ import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
8510
9093
 
8511
9094
  // pkgs/@akanjs/devkit/uploadRelease.ts
8512
9095
  import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
@@ -8612,16 +9195,16 @@ class ApplicationReleasePackager {
8612
9195
  const platformVersion = akanConfig2.mobile.version;
8613
9196
  const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
8614
9197
  if (await FileSys.dirExists(buildRoot))
8615
- await rm3(buildRoot, { recursive: true, force: true });
8616
- await mkdir8(buildRoot, { recursive: true });
9198
+ await rm4(buildRoot, { recursive: true, force: true });
9199
+ await mkdir9(buildRoot, { recursive: true });
8617
9200
  if (rebuild || !await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))
8618
9201
  await this.#build();
8619
9202
  const buildVersion = `${platformVersion}-${buildNum}`;
8620
9203
  const buildPath = `${buildRoot}/${buildVersion}`;
8621
- await mkdir8(buildPath, { recursive: true });
9204
+ await mkdir9(buildPath, { recursive: true });
8622
9205
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
8623
9206
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
8624
- await rm3(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
9207
+ await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
8625
9208
  await this.#app.workspace.spawn("tar", [
8626
9209
  "-zcf",
8627
9210
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-release.tar.gz`,
@@ -8641,7 +9224,7 @@ class ApplicationReleasePackager {
8641
9224
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
8642
9225
  "./csr"
8643
9226
  ]);
8644
- await rm3("./csr", { recursive: true, force: true });
9227
+ await rm4("./csr", { recursive: true, force: true });
8645
9228
  }
8646
9229
  async#writeSourceArchive({ readme }) {
8647
9230
  const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
@@ -8649,13 +9232,13 @@ class ApplicationReleasePackager {
8649
9232
  await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
8650
9233
  const libDeps = ["social", "shared", "platform", "util"];
8651
9234
  await Promise.all(libDeps.map((lib) => cp(`${this.#app.workspace.cwdPath}/libs/${lib}`, `${sourceRoot}/libs/${lib}`, { recursive: true })));
8652
- await Promise.all([".next", "ios", "android", "public/libs"].map(async (path34) => {
8653
- const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path34}`;
9235
+ await Promise.all([".next", "ios", "android", "public/libs"].map(async (path36) => {
9236
+ const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path36}`;
8654
9237
  if (await FileSys.dirExists(targetPath))
8655
- await rm3(targetPath, { recursive: true, force: true });
9238
+ await rm4(targetPath, { recursive: true, force: true });
8656
9239
  }));
8657
9240
  const syncPaths = [".husky", ".gitignore", "package.json"];
8658
- await Promise.all(syncPaths.map((path34) => cp(`${this.#app.workspace.cwdPath}/${path34}`, `${sourceRoot}/${path34}`, { recursive: true })));
9241
+ await Promise.all(syncPaths.map((path36) => cp(`${this.#app.workspace.cwdPath}/${path36}`, `${sourceRoot}/${path36}`, { recursive: true })));
8659
9242
  await this.#writeSourceTsconfig(sourceRoot, libDeps);
8660
9243
  await Bun.write(`${sourceRoot}/README.md`, readme);
8661
9244
  await this.#app.workspace.spawn("tar", [
@@ -8671,11 +9254,11 @@ class ApplicationReleasePackager {
8671
9254
  const maxRetry = 3;
8672
9255
  for (let i = 0;i < maxRetry; i++) {
8673
9256
  try {
8674
- await rm3(sourceRoot, { recursive: true, force: true });
9257
+ await rm4(sourceRoot, { recursive: true, force: true });
8675
9258
  } catch {}
8676
9259
  }
8677
9260
  }
8678
- await mkdir8(sourceRoot, { recursive: true });
9261
+ await mkdir9(sourceRoot, { recursive: true });
8679
9262
  }
8680
9263
  async#writeSourceTsconfig(sourceRoot, libDeps) {
8681
9264
  const tsconfig = await this.#app.workspace.readJson("tsconfig.json");
@@ -8746,20 +9329,20 @@ class ApplicationReleasePackager {
8746
9329
  }
8747
9330
  }
8748
9331
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
8749
- import path34 from "path";
9332
+ import path36 from "path";
8750
9333
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
8751
9334
  async function resolveSignalTestPreloadPath(target) {
8752
9335
  const candidates = [];
8753
9336
  const addResolvedPackageCandidate = (basePath2) => {
8754
9337
  try {
8755
- candidates.push(path34.join(path34.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
9338
+ candidates.push(path36.join(path36.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
8756
9339
  } catch {}
8757
9340
  };
8758
9341
  addResolvedPackageCandidate(target.cwdPath);
8759
9342
  addResolvedPackageCandidate(process.cwd());
8760
- addResolvedPackageCandidate(path34.dirname(Bun.main));
9343
+ addResolvedPackageCandidate(path36.dirname(Bun.main));
8761
9344
  addResolvedPackageCandidate(import.meta.dir);
8762
- candidates.push(path34.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path34.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path34.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path34.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path34.join(path34.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path34.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
9345
+ candidates.push(path36.join(target.cwdPath, "../../node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path36.join(target.cwdPath, "../../pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path36.join(process.cwd(), "node_modules/akanjs", SIGNAL_TEST_PRELOAD_PATH), path36.join(process.cwd(), "pkgs/akanjs", SIGNAL_TEST_PRELOAD_PATH), path36.join(path36.dirname(Bun.main), "../../akanjs", SIGNAL_TEST_PRELOAD_PATH), path36.resolve(import.meta.dir, "../../akanjs", SIGNAL_TEST_PRELOAD_PATH));
8763
9346
  for (const candidate of [...new Set(candidates)]) {
8764
9347
  if (await Bun.file(candidate).exists())
8765
9348
  return candidate;
@@ -8768,8 +9351,8 @@ async function resolveSignalTestPreloadPath(target) {
8768
9351
  }
8769
9352
  // pkgs/@akanjs/devkit/builder.ts
8770
9353
  import { existsSync as existsSync2 } from "fs";
8771
- import { mkdir as mkdir9 } from "fs/promises";
8772
- import path35 from "path";
9354
+ import { mkdir as mkdir10 } from "fs/promises";
9355
+ import path37 from "path";
8773
9356
  var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
8774
9357
  var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
8775
9358
  var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
@@ -8786,14 +9369,14 @@ class Builder {
8786
9369
  #globEntrypoints(cwd, pattern) {
8787
9370
  const glob = new Bun.Glob(pattern);
8788
9371
  return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
8789
- const segments = relativePath.split(path35.sep);
9372
+ const segments = relativePath.split(path37.sep);
8790
9373
  return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
8791
- }).map((rel) => path35.join(cwd, rel));
9374
+ }).map((rel) => path37.join(cwd, rel));
8792
9375
  }
8793
9376
  #globFiles(cwd, pattern = "**/*.*") {
8794
9377
  const glob = new Bun.Glob(pattern);
8795
9378
  return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
8796
- const segments = relativePath.split(path35.sep);
9379
+ const segments = relativePath.split(path37.sep);
8797
9380
  return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
8798
9381
  });
8799
9382
  }
@@ -8801,17 +9384,17 @@ class Builder {
8801
9384
  const out = [];
8802
9385
  for (const p of additionalEntryPoints) {
8803
9386
  if (p.includes("*")) {
8804
- const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path35.sep}`) ? p.slice(cwd.length + 1) : p;
9387
+ const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path37.sep}`) ? p.slice(cwd.length + 1) : p;
8805
9388
  out.push(...this.#globEntrypoints(cwd, rel));
8806
9389
  } else
8807
- out.push(path35.isAbsolute(p) ? p : path35.join(cwd, p));
9390
+ out.push(path37.isAbsolute(p) ? p : path37.join(cwd, p));
8808
9391
  }
8809
9392
  return out;
8810
9393
  }
8811
9394
  #getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
8812
9395
  const cwd = this.#executor.cwdPath;
8813
9396
  const entrypoints = [
8814
- ...bundle ? [path35.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
9397
+ ...bundle ? [path37.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
8815
9398
  ...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
8816
9399
  ];
8817
9400
  return {
@@ -8832,9 +9415,9 @@ class Builder {
8832
9415
  for (const relativePath of this.#globFiles(cwd)) {
8833
9416
  if (relativePath === "package.json")
8834
9417
  continue;
8835
- const sourcePath = path35.join(cwd, relativePath);
8836
- const targetPath = path35.join(this.#distExecutor.cwdPath, relativePath);
8837
- await mkdir9(path35.dirname(targetPath), { recursive: true });
9418
+ const sourcePath = path37.join(cwd, relativePath);
9419
+ const targetPath = path37.join(this.#distExecutor.cwdPath, relativePath);
9420
+ await mkdir10(path37.dirname(targetPath), { recursive: true });
8838
9421
  await Bun.write(targetPath, Bun.file(sourcePath));
8839
9422
  }
8840
9423
  }
@@ -8845,13 +9428,13 @@ class Builder {
8845
9428
  return withoutFormatDir;
8846
9429
  if (!hasDotSlash && withoutFormatDir === publishedPath)
8847
9430
  return publishedPath;
8848
- const parsed = path35.posix.parse(withoutFormatDir);
9431
+ const parsed = path37.posix.parse(withoutFormatDir);
8849
9432
  if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
8850
9433
  return withoutFormatDir;
8851
- const withoutExt = path35.posix.join(parsed.dir, parsed.name);
9434
+ const withoutExt = path37.posix.join(parsed.dir, parsed.name);
8852
9435
  const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
8853
9436
  const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
8854
- const matchedSource = sourceCandidates.find((candidate) => existsSync2(path35.join(this.#executor.cwdPath, candidate)));
9437
+ const matchedSource = sourceCandidates.find((candidate) => existsSync2(path37.join(this.#executor.cwdPath, candidate)));
8855
9438
  if (!matchedSource)
8856
9439
  return withoutFormatDir;
8857
9440
  return hasDotSlash ? `./${matchedSource}` : matchedSource;
@@ -8900,10 +9483,10 @@ class Builder {
8900
9483
  }
8901
9484
  }
8902
9485
  // pkgs/@akanjs/devkit/capacitorApp.ts
8903
- import { cp as cp2, mkdir as mkdir10, rm as rm4 } from "fs/promises";
8904
- import path36 from "path";
9486
+ import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
9487
+ import path38 from "path";
8905
9488
  import { MobileProject } from "@trapezedev/project";
8906
- import { capitalize as capitalize3 } from "akanjs/common";
9489
+ import { capitalize as capitalize4 } from "akanjs/common";
8907
9490
 
8908
9491
  // pkgs/@akanjs/devkit/fileEditor.ts
8909
9492
  class FileEditor {
@@ -9096,10 +9679,10 @@ class CapacitorApp {
9096
9679
  constructor(app, target) {
9097
9680
  this.app = app;
9098
9681
  this.target = target;
9099
- this.targetRootPath = path36.posix.join(".akan", "mobile", this.target.name);
9100
- this.targetRoot = path36.join(this.app.cwdPath, this.targetRootPath);
9101
- this.targetWebRoot = path36.join(this.targetRoot, "www");
9102
- this.targetAssetRoot = path36.join(this.targetRoot, "assets");
9682
+ this.targetRootPath = path38.posix.join(".akan", "mobile", this.target.name);
9683
+ this.targetRoot = path38.join(this.app.cwdPath, this.targetRootPath);
9684
+ this.targetWebRoot = path38.join(this.targetRoot, "www");
9685
+ this.targetAssetRoot = path38.join(this.targetRoot, "assets");
9103
9686
  this.project = new MobileProject(this.app.cwdPath, {
9104
9687
  android: { path: this.androidRootPath },
9105
9688
  ios: { path: this.iosProjectPath }
@@ -9111,13 +9694,13 @@ class CapacitorApp {
9111
9694
  env = "debug",
9112
9695
  regenerate = false
9113
9696
  } = {}) {
9114
- await mkdir10(this.targetRoot, { recursive: true });
9697
+ await mkdir11(this.targetRoot, { recursive: true });
9115
9698
  await this.#writeCapacitorConfig();
9116
9699
  if (regenerate) {
9117
9700
  if (!platform || platform === "ios")
9118
- await rm4(path36.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
9701
+ await rm5(path38.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
9119
9702
  if (!platform || platform === "android")
9120
- await rm4(path36.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
9703
+ await rm5(path38.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
9121
9704
  }
9122
9705
  const project = this.project;
9123
9706
  await this.project.load();
@@ -9181,7 +9764,7 @@ class CapacitorApp {
9181
9764
  await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
9182
9765
  }
9183
9766
  async#updateAndroidBuildTypes() {
9184
- const appGradle = await FileEditor.create(path36.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
9767
+ const appGradle = await FileEditor.create(path38.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
9185
9768
  const buildTypesBlock = `
9186
9769
  debug {
9187
9770
  applicationIdSuffix ".debug"
@@ -9224,7 +9807,7 @@ class CapacitorApp {
9224
9807
  const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
9225
9808
  await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
9226
9809
  stdio: "inherit",
9227
- cwd: path36.join(this.app.cwdPath, this.androidRootPath),
9810
+ cwd: path38.join(this.app.cwdPath, this.androidRootPath),
9228
9811
  env: await this.#commandEnv("release", env)
9229
9812
  });
9230
9813
  }
@@ -9232,10 +9815,10 @@ class CapacitorApp {
9232
9815
  await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
9233
9816
  }
9234
9817
  async#ensureAndroidAssetsDir() {
9235
- await mkdir10(path36.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
9818
+ await mkdir11(path38.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
9236
9819
  }
9237
9820
  async#ensureAndroidDebugKeystore() {
9238
- const keystorePath = path36.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
9821
+ const keystorePath = path38.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
9239
9822
  if (await Bun.file(keystorePath).exists())
9240
9823
  return;
9241
9824
  await this.#spawn("keytool", [
@@ -9281,12 +9864,12 @@ class CapacitorApp {
9281
9864
  await this.#prepareAndroid({ operation: "release", env: "main" });
9282
9865
  }
9283
9866
  async prepareWww() {
9284
- const htmlSource = path36.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
9867
+ const htmlSource = path38.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
9285
9868
  if (!await Bun.file(htmlSource).exists())
9286
9869
  throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
9287
- await rm4(this.targetWebRoot, { recursive: true, force: true });
9288
- await mkdir10(this.targetWebRoot, { recursive: true });
9289
- await Bun.write(path36.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
9870
+ await rm5(this.targetWebRoot, { recursive: true, force: true });
9871
+ await mkdir11(this.targetWebRoot, { recursive: true });
9872
+ await Bun.write(path38.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
9290
9873
  }
9291
9874
  #injectMobileTargetMeta(html) {
9292
9875
  const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
@@ -9297,8 +9880,8 @@ class CapacitorApp {
9297
9880
  </head>`);
9298
9881
  }
9299
9882
  async#writeCapacitorConfig() {
9300
- await mkdir10(this.targetRoot, { recursive: true });
9301
- const appInfoPath = path36.relative(this.app.cwdPath, path36.join(this.app.cwdPath, "akan.app.json")).split(path36.sep).join("/");
9883
+ await mkdir11(this.targetRoot, { recursive: true });
9884
+ const appInfoPath = path38.relative(this.app.cwdPath, path38.join(this.app.cwdPath, "akan.app.json")).split(path38.sep).join("/");
9302
9885
  const content = `import type { AppScanResult } from "akanjs";
9303
9886
  import { withBase } from "${process.env.USE_AKANJS_PKGS === "true" ? "../../pkgs/" : ""}akanjs/capacitor.base.config";
9304
9887
  import appInfo from "${appInfoPath.startsWith(".") ? appInfoPath : `./${appInfoPath}`}";
@@ -9319,18 +9902,18 @@ export default withBase(
9319
9902
  appInfo as AppScanResult,
9320
9903
  );
9321
9904
  `;
9322
- await Bun.write(path36.join(this.app.cwdPath, "capacitor.config.ts"), content);
9905
+ await Bun.write(path38.join(this.app.cwdPath, "capacitor.config.ts"), content);
9323
9906
  }
9324
9907
  async#prepareTargetAssets() {
9325
9908
  if (!this.target.assets)
9326
9909
  return;
9327
- await mkdir10(this.targetAssetRoot, { recursive: true });
9910
+ await mkdir11(this.targetAssetRoot, { recursive: true });
9328
9911
  if (this.target.assets.icon)
9329
- await cp2(path36.join(this.app.cwdPath, this.target.assets.icon), path36.join(this.targetAssetRoot, "icon.png"), {
9912
+ await cp2(path38.join(this.app.cwdPath, this.target.assets.icon), path38.join(this.targetAssetRoot, "icon.png"), {
9330
9913
  force: true
9331
9914
  });
9332
9915
  if (this.target.assets.splash)
9333
- await cp2(path36.join(this.app.cwdPath, this.target.assets.splash), path36.join(this.targetAssetRoot, "splash.png"), {
9916
+ await cp2(path38.join(this.app.cwdPath, this.target.assets.splash), path38.join(this.targetAssetRoot, "splash.png"), {
9334
9917
  force: true
9335
9918
  });
9336
9919
  }
@@ -9338,11 +9921,11 @@ export default withBase(
9338
9921
  const files = this.target.files?.[platform];
9339
9922
  if (!files)
9340
9923
  return;
9341
- const platformRoot = path36.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
9924
+ const platformRoot = path38.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
9342
9925
  await Promise.all(Object.entries(files).map(async ([to, from]) => {
9343
- const targetPath = path36.join(platformRoot, to);
9344
- await mkdir10(path36.dirname(targetPath), { recursive: true });
9345
- await cp2(path36.join(this.app.cwdPath, from), targetPath, { force: true });
9926
+ const targetPath = path38.join(platformRoot, to);
9927
+ await mkdir11(path38.dirname(targetPath), { recursive: true });
9928
+ await cp2(path38.join(this.app.cwdPath, from), targetPath, { force: true });
9346
9929
  }));
9347
9930
  }
9348
9931
  async#generateAssets({ operation, env }) {
@@ -9352,7 +9935,7 @@ export default withBase(
9352
9935
  "@capacitor/assets",
9353
9936
  "generate",
9354
9937
  "--assetPath",
9355
- path36.posix.join(this.targetRootPath, "assets"),
9938
+ path38.posix.join(this.targetRootPath, "assets"),
9356
9939
  "--iosProject",
9357
9940
  this.iosProjectPath,
9358
9941
  "--androidProject",
@@ -9454,7 +10037,7 @@ export default withBase(
9454
10037
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
9455
10038
  }
9456
10039
  async#setPermissionInIos(permissions) {
9457
- const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize3(key)}`, value]));
10040
+ const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize4(key)}`, value]));
9458
10041
  await Promise.all([
9459
10042
  this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
9460
10043
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
@@ -9539,15 +10122,15 @@ var Pkg = createInternalArgToken("Pkg");
9539
10122
  var Module = createInternalArgToken("Module");
9540
10123
  var Workspace = createInternalArgToken("Workspace");
9541
10124
  // pkgs/@akanjs/devkit/commandDecorators/command.ts
9542
- import path37 from "path";
10125
+ import path39 from "path";
9543
10126
  import { confirm, input as input2, select as select2 } from "@inquirer/prompts";
9544
10127
  import { Logger as Logger10 } from "akanjs/common";
9545
10128
  import chalk6 from "chalk";
9546
10129
  import { program } from "commander";
9547
10130
 
9548
10131
  // pkgs/@akanjs/devkit/commandDecorators/dependencyBuilder.ts
9549
- var capitalize4 = (value) => value.slice(0, 1).toUpperCase() + value.slice(1);
9550
- var createDependencyKey = (refName, kind) => `${refName}${capitalize4(kind)}`;
10132
+ var capitalize5 = (value) => value.slice(0, 1).toUpperCase() + value.slice(1);
10133
+ var createDependencyKey = (refName, kind) => `${refName}${capitalize5(kind)}`;
9551
10134
 
9552
10135
  class CommandContainer {
9553
10136
  static #instances = new Map;
@@ -10039,7 +10622,7 @@ var runCommands = async (...commands) => {
10039
10622
  process.exit(1);
10040
10623
  });
10041
10624
  const __dirname2 = getDirname(import.meta.url);
10042
- const packageJsonCandidates = [`${path37.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
10625
+ const packageJsonCandidates = [`${path39.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
10043
10626
  let cliPackageJson = null;
10044
10627
  for (const packageJsonPath of packageJsonCandidates) {
10045
10628
  if (!await FileSys.fileExists(packageJsonPath))
@@ -10317,8 +10900,8 @@ var scanModuleSpecifiers = (source, filePath, includeExports) => {
10317
10900
  return importSpecifiers;
10318
10901
  };
10319
10902
  var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
10320
- const configFile = ts7.readConfigFile(tsConfigPath, (path38) => {
10321
- return ts7.sys.readFile(path38);
10903
+ const configFile = ts7.readConfigFile(tsConfigPath, (path40) => {
10904
+ return ts7.sys.readFile(path40);
10322
10905
  });
10323
10906
  return ts7.parseJsonConfigFileContent(configFile.config, ts7.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
10324
10907
  };
@@ -10560,7 +11143,6 @@ import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime"
10560
11143
  "use client";
10561
11144
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
10562
11145
  import { Logger as Logger11 } from "akanjs/common";
10563
-
10564
11146
  class IncrementalBuilder {
10565
11147
  #logger = new Logger11("IncrementalBuilder");
10566
11148
  #app;
@@ -10569,6 +11151,8 @@ class IncrementalBuilder {
10569
11151
  #cssCompiler;
10570
11152
  #optimizedFonts;
10571
11153
  #discovery;
11154
+ #changePlanner;
11155
+ #generatedIndexSync;
10572
11156
  #generation = 0;
10573
11157
  #workQueue = Promise.resolve();
10574
11158
  #cssRebuildQueue = Promise.resolve();
@@ -10581,6 +11165,8 @@ class IncrementalBuilder {
10581
11165
  this.#cssCompiler = options.cssCompiler;
10582
11166
  this.#optimizedFonts = options.optimizedFonts;
10583
11167
  this.#discovery = options.discovery;
11168
+ this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
11169
+ this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
10584
11170
  }
10585
11171
  async handleBuildRoute(msg) {
10586
11172
  return this.#enqueueWork(`build-route:${msg.routeId}`, async () => this.#handleBuildRoute(msg));
@@ -10596,6 +11182,7 @@ class IncrementalBuilder {
10596
11182
  discovery: this.#discovery
10597
11183
  }).build();
10598
11184
  this.#logger.verbose(`build-route ok routeId=${msg.routeId} newEntries=${delta.newEntries.length}`);
11185
+ this.#sendBuildStatus("route", { generation: msg.generation, ok: true, files: msg.seeds });
10599
11186
  return {
10600
11187
  type: "build-route-res",
10601
11188
  id: msg.id,
@@ -10614,9 +11201,24 @@ class IncrementalBuilder {
10614
11201
  } catch (err) {
10615
11202
  const errMsg = err instanceof Error ? err.message : String(err);
10616
11203
  this.#logger.error(`build-route failed routeId=${msg.routeId}: ${errMsg}`);
11204
+ this.#sendBuildStatus("route", { generation: msg.generation, ok: false, files: msg.seeds, message: errMsg });
10617
11205
  return { type: "build-route-res", id: msg.id, ok: false, error: errMsg };
10618
11206
  }
10619
11207
  }
11208
+ #sendBuildStatus(phase, { generation, ok, files, message }) {
11209
+ if (typeof generation !== "number")
11210
+ return;
11211
+ process.send?.({
11212
+ type: "build-status",
11213
+ data: {
11214
+ generation,
11215
+ phase,
11216
+ ok,
11217
+ files: files ?? [],
11218
+ message
11219
+ }
11220
+ });
11221
+ }
10620
11222
  async#enqueueWork(label, fn) {
10621
11223
  const started = Date.now();
10622
11224
  const run = this.#workQueue.then(fn, fn);
@@ -10632,10 +11234,10 @@ class IncrementalBuilder {
10632
11234
  }
10633
11235
  }
10634
11236
  batchTouchesPagesTree(appDir, batch) {
10635
- const absAppDir = path38.resolve(appDir);
11237
+ const absAppDir = path40.resolve(appDir);
10636
11238
  for (const f of batch.files) {
10637
- const abs = path38.resolve(f);
10638
- if (!abs.startsWith(`${absAppDir}${path38.sep}`) && abs !== absAppDir)
11239
+ const abs = path40.resolve(f);
11240
+ if (!abs.startsWith(`${absAppDir}${path40.sep}`) && abs !== absAppDir)
10639
11241
  continue;
10640
11242
  if (/\.(tsx|ts|jsx|js)$/.test(abs))
10641
11243
  return true;
@@ -10643,15 +11245,15 @@ class IncrementalBuilder {
10643
11245
  return false;
10644
11246
  }
10645
11247
  async batchMayChangePageKeys(appDir, batch) {
10646
- const absAppDir = path38.resolve(appDir);
10647
- const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path38.normalize(key)));
11248
+ const absAppDir = path40.resolve(appDir);
11249
+ const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path40.normalize(key)));
10648
11250
  for (const f of batch.files) {
10649
- const abs = path38.resolve(f);
10650
- if (!abs.startsWith(`${absAppDir}${path38.sep}`) && abs !== absAppDir)
11251
+ const abs = path40.resolve(f);
11252
+ if (!abs.startsWith(`${absAppDir}${path40.sep}`) && abs !== absAppDir)
10651
11253
  continue;
10652
11254
  if (!/\.(tsx|ts|jsx|js)$/.test(abs))
10653
11255
  continue;
10654
- const rel = path38.normalize(path38.relative(absAppDir, abs));
11256
+ const rel = path40.normalize(path40.relative(absAppDir, abs));
10655
11257
  if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
10656
11258
  return true;
10657
11259
  }
@@ -10679,7 +11281,7 @@ class IncrementalBuilder {
10679
11281
  ${cssText}`).toString(36);
10680
11282
  const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
10681
11283
  const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
10682
- await Bun.write(path38.join(artifactDir, cssRelPath), cssText);
11284
+ await Bun.write(path40.join(artifactDir, cssRelPath), cssText);
10683
11285
  cssAssetEntries.push([basePath2, { cssUrl, cssRelPath }]);
10684
11286
  cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
10685
11287
  })()
@@ -10719,9 +11321,17 @@ ${cssText}`).toString(36);
10719
11321
  generation: next.generation,
10720
11322
  changedFiles: next.changedFiles
10721
11323
  });
11324
+ this.#sendBuildStatus("css", { generation: next.generation, ok: true, files: next.changedFiles });
10722
11325
  this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
10723
11326
  }).catch((err) => {
10724
- this.#logger.error(`css-rebuild failed: ${err instanceof Error ? err.message : err}`);
11327
+ const message = err instanceof Error ? err.message : String(err);
11328
+ this.#logger.error(`css-rebuild failed: ${message}`);
11329
+ this.#sendBuildStatus("css", {
11330
+ generation: next.generation,
11331
+ ok: false,
11332
+ files: next.changedFiles,
11333
+ message
11334
+ });
10725
11335
  });
10726
11336
  }, 150);
10727
11337
  }
@@ -10739,10 +11349,10 @@ ${cssText}`).toString(36);
10739
11349
  if (changedFiles.length === 0)
10740
11350
  return false;
10741
11351
  return changedFiles.some((file) => {
10742
- const normalized = path38.resolve(file);
11352
+ const normalized = path40.resolve(file);
10743
11353
  if (/\.(woff2?|ttf|otf)$/i.test(normalized))
10744
11354
  return true;
10745
- return this.#optimizedFonts.files.some((fontFile) => path38.resolve(fontFile) === normalized);
11355
+ return this.#optimizedFonts.files.some((fontFile) => path40.resolve(fontFile) === normalized);
10746
11356
  });
10747
11357
  }
10748
11358
  async installWatcher() {
@@ -10759,38 +11369,58 @@ ${cssText}`).toString(36);
10759
11369
  this.#logger.verbose(`watching ${roots.length} roots`);
10760
11370
  }
10761
11371
  async#handleWatchBatch(appDir, artifactDir, batch) {
10762
- const kinds = [...batch.kinds];
10763
- if (kinds.length === 0)
11372
+ const rawKinds = new Set(batch.kinds);
11373
+ if (rawKinds.size === 0)
10764
11374
  return;
10765
11375
  const generation = ++this.#generation;
10766
- this.#logger.verbose(`[hmr] batch generation=${generation} kinds=${kinds.join(",")} files=${batch.files.length}`);
11376
+ const indexSync = await this.#generatedIndexSync.syncForBatch(batch.files);
11377
+ const { files, kinds, expandedBatch, event, hasSyncErrors } = prepareDevWatchBatch({
11378
+ generation,
11379
+ batch,
11380
+ indexSync,
11381
+ changePlanner: this.#changePlanner
11382
+ });
11383
+ const devPlan = event.devPlan;
11384
+ this.#logger.verbose(`[hmr] batch generation=${generation} kinds=${kinds.join(",")} files=${files.length} generated=${indexSync.changedFiles.length} roles=${devPlan.roles.join(",") || "(none)"} actions=${devPlan.actions.join(",") || "(none)"}`);
11385
+ for (const error of indexSync.errors)
11386
+ this.#logger.error(error);
10767
11387
  if (kinds.includes("code")) {
10768
11388
  const started = Date.now();
10769
11389
  if (kinds.includes("config"))
10770
11390
  this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
10771
11391
  else
10772
- this.#discovery.invalidate?.(batch.files);
11392
+ this.#discovery.invalidate?.(files);
10773
11393
  this.#logger.verbose(`client-entry-discovery ${kinds.includes("config") ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
10774
11394
  }
10775
- if (kinds.includes("code") && await this.batchMayChangePageKeys(appDir, batch)) {
11395
+ if (hasSyncErrors) {
11396
+ this.#sendBuildStatus("barrel", { generation, ok: false, files, message: indexSync.errors.join(`
11397
+ `) });
11398
+ process.send?.(event);
11399
+ return;
11400
+ }
11401
+ if (indexSync.changedFiles.length > 0)
11402
+ this.#sendBuildStatus("barrel", { generation, ok: true, files });
11403
+ if (kinds.includes("code") && await this.batchMayChangePageKeys(appDir, expandedBatch)) {
10776
11404
  const started = Date.now();
10777
11405
  await this.#app.getPageKeys({ refresh: true });
10778
11406
  this.#logger.verbose(`pageKeys updated, app pageKeys are refreshed (${Date.now() - started}ms)`);
10779
- } else if (kinds.includes("code") && this.batchTouchesPagesTree(appDir, batch)) {
11407
+ } else if (kinds.includes("code") && this.batchTouchesPagesTree(appDir, expandedBatch)) {
10780
11408
  this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
10781
11409
  }
10782
11410
  if (kinds.includes("code") && this.#shouldRebuildCsr()) {
10783
11411
  try {
10784
11412
  const started = Date.now();
10785
11413
  await new CsrArtifactBuilder(this.#app).build();
11414
+ this.#sendBuildStatus("csr", { generation, ok: true, files });
10786
11415
  this.#logger.verbose(`csr-rebundle ok (${Date.now() - started}ms)`);
10787
11416
  } catch (err) {
10788
- this.#logger.error(`csr-rebundle failed: ${err instanceof Error ? err.message : err}`);
11417
+ const message = err instanceof Error ? err.message : String(err);
11418
+ this.#logger.error(`csr-rebundle failed: ${message}`);
11419
+ this.#sendBuildStatus("csr", { generation, ok: false, files, message });
10789
11420
  }
10790
11421
  } else if (kinds.includes("code")) {
10791
11422
  this.#logger.verbose(`csr-rebundle skipped; set AKAN_DEV_CSR_REBUILD=1 to enable per-save CSR rebuilds`);
10792
11423
  }
10793
- const event = { type: "invalidate", kinds, files: batch.files, generation };
10794
11424
  process.send?.(event);
10795
11425
  if (kinds.includes("code")) {
10796
11426
  try {
@@ -10798,15 +11428,18 @@ ${cssText}`).toString(36);
10798
11428
  const next = await new PagesBundleBuilder(this.#app).build();
10799
11429
  process.send?.({
10800
11430
  type: "pages-updated",
10801
- data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles: batch.files }
11431
+ data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles: files }
10802
11432
  });
11433
+ this.#sendBuildStatus("pages", { generation, ok: true, files });
10803
11434
  this.#logger.verbose(`pages-rebundle ok buildId=${next.buildId} (${Date.now() - started}ms)`);
10804
11435
  } catch (err) {
10805
- this.#logger.error(`pages-rebundle failed: ${err instanceof Error ? err.message : err}`);
11436
+ const message = err instanceof Error ? err.message : String(err);
11437
+ this.#logger.error(`pages-rebundle failed: ${message}`);
11438
+ this.#sendBuildStatus("pages", { generation, ok: false, files, message });
10806
11439
  }
10807
11440
  }
10808
11441
  if (kinds.includes("code") || kinds.includes("css")) {
10809
- this.scheduleCssRebuild(artifactDir, { refresh: true, generation, changedFiles: batch.files });
11442
+ this.scheduleCssRebuild(artifactDir, { refresh: true, generation, changedFiles: files });
10810
11443
  this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
10811
11444
  }
10812
11445
  }