@akanjs/cli 2.3.5 → 2.3.6-rc.0

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,76 @@ 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
+ return message.devPlan.actions.includes("restart-backend");
4140
+ };
4141
+ var shouldRestartDevHostByDevPlan = (message) => message.devPlan?.actions.includes("restart-dev-host") ?? message.kinds.includes("config");
4142
+ var RESTART_ROLE_ORDER = ["server", "shared", "barrel", "config"];
4143
+ var generationValue = (generation) => generation ?? -1;
4144
+ var isLegacyBackendFallbackFile = (file, workspaceRoot) => {
4145
+ const abs = path9.resolve(file);
4146
+ const ext = path9.extname(abs).toLowerCase();
4147
+ if (!SOURCE_EXTS.has(ext))
4148
+ return false;
4149
+ const rel = path9.relative(path9.resolve(workspaceRoot), abs);
4150
+ if (!rel || rel.startsWith("..") || path9.isAbsolute(rel))
4151
+ return false;
4152
+ const parts = rel.split(path9.sep).filter(Boolean);
4153
+ const [scope] = parts;
4154
+ if (scope !== "apps" && scope !== "libs" && scope !== "pkgs")
4155
+ return false;
4156
+ const base = path9.basename(abs);
4157
+ 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";
4158
+ };
4159
+ var shouldMarkBuildPhaseRecovered = (previousByPhase, status) => {
4160
+ const previous = previousByPhase.get(status.phase);
4161
+ return Boolean(previous && status.ok && !previous.ok && generationValue(status.generation) >= previous.generation);
4162
+ };
4163
+ var createBackendBuildStatus = ({
4164
+ generation,
4165
+ ok,
4166
+ files = [],
4167
+ message
4168
+ }) => ({
4169
+ generation,
4170
+ phase: "backend",
4171
+ ok,
4172
+ files,
4173
+ message
4174
+ });
4175
+ var backendRestartReasonFromMessage = (message) => {
4176
+ const roleSet = new Set;
4177
+ for (const role of message.devPlan?.roles ?? []) {
4178
+ if (role === "server" || role === "shared" || role === "barrel" || role === "config")
4179
+ roleSet.add(role);
4180
+ }
4181
+ return {
4182
+ generation: message.devPlan?.generation ?? message.generation,
4183
+ files: [...new Set(message.files)].sort(),
4184
+ roles: RESTART_ROLE_ORDER.filter((role) => roleSet.has(role))
4185
+ };
4186
+ };
4187
+ var mergeBackendRestartReasons = (current, next) => ({
4188
+ generation: generationValue(next.generation) >= generationValue(current?.generation) ? next.generation : current?.generation,
4189
+ files: [...new Set([...current?.files ?? [], ...next.files])].sort(),
4190
+ roles: RESTART_ROLE_ORDER.filter((role) => current?.roles.includes(role) || next.roles.includes(role))
4191
+ });
4192
+ var shouldReplaceLastGoodMessage = (current, next) => !current || generationValue(next.data.generation) >= generationValue(current.data.generation);
4193
+ var shouldQueueBuildStatusReplay = (backendReady, pendingReplayCount) => !backendReady || pendingReplayCount > 0;
4194
+ var buildStatusReplaySequence = (pendingReplay, latestByPhase) => [...pendingReplay, ...latestByPhase.values()];
4100
4195
 
4101
4196
  class BackendImportGraph {
4102
4197
  #app;
@@ -4107,6 +4202,7 @@ class BackendImportGraph {
4107
4202
  #jsxTranspiler = new Bun.Transpiler({ loader: "jsx" });
4108
4203
  #files = new Set;
4109
4204
  #ready = false;
4205
+ #lastRefreshSucceeded = false;
4110
4206
  constructor(app, logger) {
4111
4207
  this.#app = app;
4112
4208
  this.#logger = logger;
@@ -4114,6 +4210,9 @@ class BackendImportGraph {
4114
4210
  get ready() {
4115
4211
  return this.#ready;
4116
4212
  }
4213
+ get lastRefreshSucceeded() {
4214
+ return this.#lastRefreshSucceeded;
4215
+ }
4117
4216
  has(file) {
4118
4217
  return this.#files.has(path9.resolve(file));
4119
4218
  }
@@ -4122,10 +4221,12 @@ class BackendImportGraph {
4122
4221
  const files = await this.#build();
4123
4222
  this.#files = files;
4124
4223
  this.#ready = true;
4224
+ this.#lastRefreshSucceeded = true;
4125
4225
  this.#logger.verbose(`[backend-graph] scanned ${files.size} files`);
4126
4226
  return true;
4127
4227
  } catch (err) {
4128
4228
  this.#ready = this.#files.size > 0;
4229
+ this.#lastRefreshSucceeded = false;
4129
4230
  this.#logger.warn(`[backend-graph] scan failed; ${this.#ready ? "using previous graph" : "using fallback rules"}: ${err instanceof Error ? err.message : String(err)}`);
4130
4231
  return this.#ready;
4131
4232
  }
@@ -4212,9 +4313,13 @@ class AkanAppHost {
4212
4313
  #restartTimer = null;
4213
4314
  #backendRecoveryTimer = null;
4214
4315
  #backendRecoveryAttempts = 0;
4215
- #restartFiles = new Set;
4216
- #latestPagesUpdated = null;
4217
- #latestCssUpdated = null;
4316
+ #backendLifecycleState = "stopped";
4317
+ #pendingRestartReason = null;
4318
+ #backendStartStatus = null;
4319
+ #backendBuildStatusGeneration = 0;
4320
+ #lastGoodFrontend = {};
4321
+ #buildStatusByPhase = new Map;
4322
+ #pendingBuildStatusReplay = [];
4218
4323
  #builderMessageQueue = Promise.resolve();
4219
4324
  #backendGraph;
4220
4325
  constructor(app, { env, withInk = false }) {
@@ -4259,7 +4364,9 @@ class AkanAppHost {
4259
4364
  return "localhost";
4260
4365
  return await createTunnel(type, { app: this.app, environment });
4261
4366
  }
4262
- #startBackend() {
4367
+ #startBackend(startStatus = null) {
4368
+ this.#backendStartStatus = startStatus;
4369
+ this.#setBackendLifecycleState("starting");
4263
4370
  this.#backendReady = false;
4264
4371
  const backend = Bun.spawn(["bun", `apps/${this.app.name}/main.ts`], {
4265
4372
  cwd: this.app.workspace.workspaceRoot,
@@ -4271,6 +4378,8 @@ class AkanAppHost {
4271
4378
  if (msg.type === "backend-ready") {
4272
4379
  this.#backendReady = true;
4273
4380
  this.#backendRecoveryAttempts = 0;
4381
+ this.#setBackendLifecycleState("ready", `pid=${msg.pid}`);
4382
+ this.#recordBackendReadyStatus();
4274
4383
  this.logger.verbose(`backend ready pid=${msg.pid}`);
4275
4384
  this.#replayBuilderState();
4276
4385
  return;
@@ -4293,9 +4402,53 @@ class AkanAppHost {
4293
4402
  this.#backend = backend;
4294
4403
  this.logger.verbose(`backend spawned pid=${backend.pid}`);
4295
4404
  }
4405
+ #nextBackendBuildStatusGeneration(generation) {
4406
+ if (typeof generation === "number") {
4407
+ this.#backendBuildStatusGeneration = Math.max(this.#backendBuildStatusGeneration, generation);
4408
+ return generation;
4409
+ }
4410
+ this.#backendBuildStatusGeneration += 1;
4411
+ return this.#backendBuildStatusGeneration;
4412
+ }
4413
+ #recordBackendBuildStatus({
4414
+ generation,
4415
+ ok,
4416
+ files,
4417
+ message
4418
+ }) {
4419
+ const status = createBackendBuildStatus({
4420
+ generation: this.#nextBackendBuildStatusGeneration(generation),
4421
+ ok,
4422
+ files,
4423
+ message
4424
+ });
4425
+ this.#recordBuildStatus(status);
4426
+ return status;
4427
+ }
4428
+ #recordBackendReadyStatus() {
4429
+ const previous = this.#buildStatusByPhase.get("backend");
4430
+ const startStatus = this.#backendStartStatus;
4431
+ if (startStatus || previous?.ok === false) {
4432
+ const status = this.#recordBackendBuildStatus({
4433
+ generation: startStatus?.generation ?? previous?.generation,
4434
+ ok: true,
4435
+ files: startStatus?.files ?? previous?.files ?? [],
4436
+ message: "Backend ready"
4437
+ });
4438
+ this.#sendOrQueueBuildStatus(status);
4439
+ }
4440
+ this.#backendStartStatus = null;
4441
+ }
4442
+ #setBackendLifecycleState(next, detail) {
4443
+ if (this.#backendLifecycleState === next && !detail)
4444
+ return;
4445
+ const prev = this.#backendLifecycleState;
4446
+ this.#backendLifecycleState = next;
4447
+ this.logger.verbose(`[backend-lifecycle] ${prev} -> ${next}${detail ? ` ${detail}` : ""}`);
4448
+ }
4296
4449
  #sendToBackend(message) {
4297
4450
  if (!this.#backend || !this.#backendReady) {
4298
- if (message.type === "css-updated" || message.type === "pages-updated") {
4451
+ if (message.type === "css-updated" || message.type === "pages-updated" || message.type === "build-status") {
4299
4452
  this.logger.verbose(`backend is not ready; will replay ${message.type}`);
4300
4453
  return;
4301
4454
  }
@@ -4315,6 +4468,7 @@ class AkanAppHost {
4315
4468
  const backend = this.#backend;
4316
4469
  this.#plannedBackendStops.add(backend);
4317
4470
  this.#backendReady = false;
4471
+ this.#setBackendLifecycleState("stopping", `pid=${backend.pid}`);
4318
4472
  this.logger.verbose(`stopping backend pid=${backend.pid}`);
4319
4473
  try {
4320
4474
  backend.kill("SIGTERM");
@@ -4330,11 +4484,13 @@ class AkanAppHost {
4330
4484
  } finally {
4331
4485
  if (this.#backend === backend)
4332
4486
  this.#backend = null;
4487
+ this.#setBackendLifecycleState("stopped", `pid=${backend.pid}`);
4333
4488
  }
4334
4489
  }
4335
- #scheduleBackendRestart(files) {
4336
- for (const file of files)
4337
- this.#restartFiles.add(file);
4490
+ #scheduleBackendRestart(reason) {
4491
+ this.#pendingRestartReason = mergeBackendRestartReasons(this.#pendingRestartReason, reason);
4492
+ const pending = this.#pendingRestartReason;
4493
+ this.#setBackendLifecycleState("restart-pending", `generation=${pending.generation ?? "(unknown)"} files=${pending.files.length} roles=${pending.roles.join(",") || "(none)"}`);
4338
4494
  if (this.#backendRecoveryTimer) {
4339
4495
  clearTimeout(this.#backendRecoveryTimer);
4340
4496
  this.#backendRecoveryTimer = null;
@@ -4343,23 +4499,31 @@ class AkanAppHost {
4343
4499
  clearTimeout(this.#restartTimer);
4344
4500
  this.#restartTimer = setTimeout(() => {
4345
4501
  this.#restartTimer = null;
4346
- const changed = [...this.#restartFiles];
4347
- this.#restartFiles.clear();
4348
- this.#restartBackend(changed);
4502
+ const next = this.#pendingRestartReason;
4503
+ this.#pendingRestartReason = null;
4504
+ if (next)
4505
+ this.#restartBackend(next);
4349
4506
  }, BACKEND_RESTART_DEBOUNCE_MS);
4350
4507
  }
4351
- async#restartBackend(files) {
4352
- this.logger.verbose(`[backend-reload] restarting backend for ${files.length} file(s)`);
4508
+ async#restartBackend(reason) {
4509
+ this.logger.verbose(`[backend-reload] restarting backend generation=${reason.generation ?? "(unknown)"} files=${reason.files.length} roles=${reason.roles.join(",") || "(none)"}`);
4353
4510
  this.#backendRecoveryAttempts = 0;
4354
4511
  await Promise.all([this.#stopBackend(), this.#backendGraph.refresh()]);
4355
- this.#startBackend();
4512
+ this.#startBackend({ generation: reason.generation, files: reason.files });
4356
4513
  }
4357
4514
  #scheduleBackendRecovery(reason) {
4358
4515
  if (this.#backendRecoveryTimer || this.#backend)
4359
4516
  return;
4517
+ this.#setBackendLifecycleState("recovering", reason);
4360
4518
  const attempt = this.#backendRecoveryAttempts;
4361
4519
  const delay = Math.min(BACKEND_RECOVERY_BASE_DELAY_MS * 2 ** attempt, BACKEND_RECOVERY_MAX_DELAY_MS);
4362
4520
  this.#backendRecoveryAttempts = attempt + 1;
4521
+ const failureStatus = this.#recordBackendBuildStatus({
4522
+ ok: false,
4523
+ files: [],
4524
+ message: `Backend exited unexpectedly (${reason}); restarting in ${delay}ms`
4525
+ });
4526
+ this.#sendOrQueueBuildStatus(failureStatus);
4363
4527
  this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
4364
4528
  this.#backendRecoveryTimer = setTimeout(() => {
4365
4529
  this.#backendRecoveryTimer = null;
@@ -4367,7 +4531,7 @@ class AkanAppHost {
4367
4531
  return;
4368
4532
  this.#backendGraph.refresh().finally(() => {
4369
4533
  if (!this.#backend)
4370
- this.#startBackend();
4534
+ this.#startBackend({ generation: failureStatus.generation, files: failureStatus.files });
4371
4535
  });
4372
4536
  }, delay);
4373
4537
  }
@@ -4377,10 +4541,15 @@ class AkanAppHost {
4377
4541
  });
4378
4542
  }
4379
4543
  async#handleBuilderMessage(message) {
4544
+ if (message.type === "build-status") {
4545
+ this.#recordBuildStatus(message.data);
4546
+ this.#sendOrQueueBuildStatus(message.data);
4547
+ return;
4548
+ }
4380
4549
  if (message.type === "pages-updated")
4381
- this.#latestPagesUpdated = message;
4550
+ this.#recordLastGood(message);
4382
4551
  if (message.type === "css-updated")
4383
- this.#latestCssUpdated = message;
4552
+ this.#recordLastGood(message);
4384
4553
  if (message.type === "invalidate") {
4385
4554
  await this.#handleInvalidate(message);
4386
4555
  return;
@@ -4388,26 +4557,97 @@ class AkanAppHost {
4388
4557
  this.#sendToBackend(message);
4389
4558
  }
4390
4559
  async#handleInvalidate(message) {
4560
+ if (shouldRestartDevHostByDevPlan(message)) {
4561
+ this.#recordDevHostRestartRequired(message);
4562
+ return;
4563
+ }
4391
4564
  if (await this.#shouldRestartBackend(message)) {
4392
- this.#scheduleBackendRestart(message.files);
4565
+ this.#scheduleBackendRestart(backendRestartReasonFromMessage(message));
4393
4566
  return;
4394
4567
  }
4395
4568
  this.#sendToBackend(message);
4396
4569
  }
4570
+ #recordLastGood(message) {
4571
+ if (message.type === "pages-updated") {
4572
+ if (!shouldReplaceLastGoodMessage(this.#lastGoodFrontend.pages, message))
4573
+ return;
4574
+ this.#lastGoodFrontend.pages = message;
4575
+ this.logger.verbose(`[last-good] pages generation=${message.data.generation ?? "(unknown)"} buildId=${message.data.buildId}`);
4576
+ return;
4577
+ }
4578
+ if (!shouldReplaceLastGoodMessage(this.#lastGoodFrontend.css, message))
4579
+ return;
4580
+ this.#lastGoodFrontend.css = message;
4581
+ this.logger.verbose(`[last-good] css generation=${message.data.generation ?? "(unknown)"} assets=${Object.keys(message.data.cssAssets).length}`);
4582
+ }
4583
+ #recordDevHostRestartRequired(message) {
4584
+ const generation = message.devPlan?.generation ?? message.generation;
4585
+ const detail = `generation=${generation ?? "(unknown)"} files=${message.files.length}`;
4586
+ this.logger.warn(`[dev-host] config change requires a manual restart until controlled dev-host restart is implemented (${detail})`);
4587
+ if (typeof generation === "number") {
4588
+ const status = {
4589
+ generation,
4590
+ phase: "scan",
4591
+ ok: false,
4592
+ files: message.files,
4593
+ message: "Config change requires restarting `akan start` to apply."
4594
+ };
4595
+ this.#recordBuildStatus(status);
4596
+ this.#sendOrQueueBuildStatus(status);
4597
+ }
4598
+ }
4599
+ #recordBuildStatus(status) {
4600
+ const recovered = shouldMarkBuildPhaseRecovered(this.#buildStatusByPhase, status);
4601
+ this.#buildStatusByPhase.set(status.phase, status);
4602
+ const label = `[build-status] generation=${status.generation} phase=${status.phase} ok=${status.ok} files=${status.files.length}`;
4603
+ if (status.ok)
4604
+ this.logger.verbose(`${label}${recovered ? " recovered=1" : ""}`);
4605
+ else
4606
+ this.logger.warn(`${label}${status.message ? ` message=${status.message}` : ""}`);
4607
+ }
4608
+ #sendOrQueueBuildStatus(status) {
4609
+ if (!this.#backend || shouldQueueBuildStatusReplay(this.#backendReady, this.#pendingBuildStatusReplay.length)) {
4610
+ this.#pendingBuildStatusReplay.push(status);
4611
+ this.logger.verbose(`backend is not ready; will replay build-status generation=${status.generation} phase=${status.phase}`);
4612
+ return;
4613
+ }
4614
+ this.#sendToBackend({ type: "build-status", data: status });
4615
+ }
4397
4616
  #replayBuilderState() {
4398
4617
  if (!this.#backendReady)
4399
4618
  return;
4400
- if (this.#latestCssUpdated)
4401
- this.#sendToBackend(this.#latestCssUpdated);
4402
- if (this.#latestPagesUpdated)
4403
- this.#sendToBackend(this.#latestPagesUpdated);
4619
+ if (this.#lastGoodFrontend.css)
4620
+ this.#sendToBackend(this.#lastGoodFrontend.css);
4621
+ if (this.#lastGoodFrontend.pages)
4622
+ this.#sendToBackend(this.#lastGoodFrontend.pages);
4623
+ const queuedStatuses = this.#pendingBuildStatusReplay.splice(0);
4624
+ for (const status of buildStatusReplaySequence(queuedStatuses, this.#buildStatusByPhase)) {
4625
+ this.#sendToBackend({ type: "build-status", data: status });
4626
+ }
4404
4627
  }
4405
4628
  async#shouldRestartBackend(message) {
4406
4629
  if (message.kinds.length === 1 && message.kinds[0] === "css")
4407
4630
  return false;
4408
- if (!this.#backendGraph.ready && message.kinds.includes("code"))
4631
+ if (message.devPlan) {
4632
+ const { generation, roles, actions, reasonByFile } = message.devPlan;
4633
+ this.logger.verbose(`[dev-plan] generation=${generation} roles=${roles.join(",") || "(none)"} actions=${actions.join(",") || "(none)"} reasons=${Object.keys(reasonByFile).length}`);
4634
+ const shouldRestart = shouldRestartBackendByDevPlan(message) ?? false;
4635
+ if (shouldRestart && message.kinds.includes("code"))
4636
+ await this.#backendGraph.refresh();
4637
+ return shouldRestart;
4638
+ }
4639
+ if (message.kinds.includes("code"))
4409
4640
  await this.#backendGraph.refresh();
4410
- return message.files.some((file) => this.#isBackendFile(file));
4641
+ if (message.files.some((file) => this.#isBackendFile(file)))
4642
+ return true;
4643
+ if (!this.#backendGraph.lastRefreshSucceeded) {
4644
+ const fallbackFiles = message.files.filter((file) => isLegacyBackendFallbackFile(file, this.app.workspace.workspaceRoot));
4645
+ if (fallbackFiles.length > 0) {
4646
+ this.logger.warn(`[backend-graph] using path-role fallback for legacy invalidate; restart files=${fallbackFiles.length}`);
4647
+ return true;
4648
+ }
4649
+ }
4650
+ return false;
4411
4651
  }
4412
4652
  #isBackendFile(file) {
4413
4653
  return this.#backendGraph.has(file);
@@ -4763,8 +5003,8 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
4763
5003
  }
4764
5004
  }
4765
5005
  // pkgs/@akanjs/devkit/applicationBuildRunner.ts
4766
- import { mkdir as mkdir7, rm as rm2 } from "fs/promises";
4767
- import path33 from "path";
5006
+ import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
5007
+ import path35 from "path";
4768
5008
 
4769
5009
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
4770
5010
  import path20 from "path";
@@ -7232,9 +7472,292 @@ function getPageKeyBasePath(pageKey, basePaths) {
7232
7472
  const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
7233
7473
  return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
7234
7474
  }
7235
- // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
7236
- import { mkdir as mkdir6 } from "fs/promises";
7475
+ // pkgs/@akanjs/devkit/frontendBuild/devChangePlanner.ts
7237
7476
  import path25 from "path";
7477
+ var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
7478
+ var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
7479
+ var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit"]);
7480
+ var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
7481
+ var SHARED_SUFFIXES2 = [".constant.ts", ".dictionary.ts", ".signal.ts"];
7482
+ var SERVER_SUFFIXES2 = [".service.ts", ".document.ts"];
7483
+ var RUNTIME_METADATA_BASENAMES2 = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
7484
+
7485
+ class DevChangePlanner {
7486
+ #workspaceRoot;
7487
+ constructor({ workspaceRoot }) {
7488
+ this.#workspaceRoot = path25.resolve(workspaceRoot);
7489
+ }
7490
+ plan({ generation, files, kinds, generatedFiles: generatedFiles2 = [] }) {
7491
+ const fileList = uniqueResolved([...files, ...generatedFiles2]);
7492
+ const generatedSet = new Set(generatedFiles2.map((file) => path25.resolve(file)));
7493
+ const kindSet = new Set(kinds);
7494
+ const roles = new Set;
7495
+ const actions = new Set;
7496
+ const reasonByFile = {};
7497
+ for (const kind of kindSet) {
7498
+ if (kind === "css") {
7499
+ roles.add("css");
7500
+ actions.add("rebuild-css");
7501
+ }
7502
+ if (kind === "config") {
7503
+ roles.add("config");
7504
+ actions.add("restart-dev-host");
7505
+ }
7506
+ }
7507
+ for (const file of fileList) {
7508
+ const reasons = new Set;
7509
+ const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path25.resolve(file)), reasons });
7510
+ for (const role of fileRoles)
7511
+ roles.add(role);
7512
+ if (reasons.size > 0)
7513
+ reasonByFile[path25.resolve(file)] = [...reasons].sort();
7514
+ }
7515
+ if (roles.has("barrel"))
7516
+ actions.add("sync-generated");
7517
+ if (roles.has("server") || roles.has("shared"))
7518
+ actions.add("restart-backend");
7519
+ if (roles.has("client") || roles.has("shared"))
7520
+ actions.add("rebuild-client");
7521
+ if (roles.has("css"))
7522
+ actions.add("rebuild-css");
7523
+ return {
7524
+ generation,
7525
+ files: fileList,
7526
+ generatedFiles: uniqueResolved(generatedFiles2),
7527
+ roles: [...roles].sort(),
7528
+ actions: [...actions].sort(),
7529
+ reasonByFile
7530
+ };
7531
+ }
7532
+ #rolesForFile(file, { isGenerated, reasons }) {
7533
+ const roles = new Set;
7534
+ const abs = path25.resolve(file);
7535
+ const base = path25.basename(abs);
7536
+ const ext = path25.extname(abs).toLowerCase();
7537
+ const isSource = SOURCE_EXTS4.has(ext);
7538
+ const rel = path25.relative(this.#workspaceRoot, abs);
7539
+ const parts = rel.split(path25.sep).filter(Boolean);
7540
+ if (CONFIG_BASENAMES.has(base)) {
7541
+ roles.add("config");
7542
+ reasons.add("config-file");
7543
+ }
7544
+ if (ext === ".css") {
7545
+ roles.add("css");
7546
+ reasons.add("css-file");
7547
+ }
7548
+ if (isGenerated || isSource && this.#isBarrelFacetChild(parts)) {
7549
+ roles.add("barrel");
7550
+ reasons.add(isGenerated ? "generated-index" : "barrel-facet-child");
7551
+ }
7552
+ if (isSource && this.#isServerBiased(abs, parts)) {
7553
+ roles.add("server");
7554
+ reasons.add("server-path");
7555
+ }
7556
+ if (isSource && this.#isClientBiased(abs, parts)) {
7557
+ roles.add("client");
7558
+ reasons.add("client-path");
7559
+ }
7560
+ if (isSource && this.#isSharedBiased(abs, parts)) {
7561
+ roles.add("shared");
7562
+ reasons.add("shared-path");
7563
+ }
7564
+ if (roles.has("server") && roles.has("client")) {
7565
+ roles.delete("server");
7566
+ roles.delete("client");
7567
+ roles.add("shared");
7568
+ reasons.add("server-client-overlap");
7569
+ }
7570
+ if (roles.size === 0 && SOURCE_EXTS4.has(ext) && this.#isWorkspaceSource(rel)) {
7571
+ roles.add("shared");
7572
+ reasons.add("workspace-source-fallback");
7573
+ }
7574
+ return roles;
7575
+ }
7576
+ #isServerBiased(abs, parts) {
7577
+ const base = path25.basename(abs);
7578
+ return parts.includes("srvkit") || SERVER_SUFFIXES2.some((suffix) => base.endsWith(suffix)) || base === "main.ts" || base === "server.ts";
7579
+ }
7580
+ #isClientBiased(abs, parts) {
7581
+ const base = path25.basename(abs);
7582
+ return parts.includes("ui") || parts.includes("webkit") || parts.includes("page") || CLIENT_SUFFIXES.some((suffix) => base.endsWith(suffix));
7583
+ }
7584
+ #isSharedBiased(abs, parts) {
7585
+ const base = path25.basename(abs);
7586
+ return parts.includes("common") || SHARED_SUFFIXES2.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES2.has(base);
7587
+ }
7588
+ #isBarrelFacetChild(parts) {
7589
+ if (parts.length < 4)
7590
+ return false;
7591
+ const [scope, , facet, child] = parts;
7592
+ if (scope !== "apps" && scope !== "libs")
7593
+ return false;
7594
+ if (!facet || !BARREL_FACETS.has(facet))
7595
+ return false;
7596
+ if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
7597
+ return false;
7598
+ return true;
7599
+ }
7600
+ #isWorkspaceSource(rel) {
7601
+ if (!rel || rel.startsWith("..") || path25.isAbsolute(rel))
7602
+ return false;
7603
+ const [scope] = rel.split(path25.sep);
7604
+ return scope === "apps" || scope === "libs" || scope === "pkgs";
7605
+ }
7606
+ }
7607
+ var uniqueResolved = (files) => [...new Set(files.map((file) => path25.resolve(file)))].sort();
7608
+ // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
7609
+ import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm2, stat as stat3, writeFile } from "fs/promises";
7610
+ import path26 from "path";
7611
+ var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
7612
+ var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
7613
+ var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
7614
+ var MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"];
7615
+ var SERVICE_UI_TYPES = ["Util", "Zone"];
7616
+ var SCALAR_UI_TYPES = ["Template", "Unit"];
7617
+
7618
+ class DevGeneratedIndexSync {
7619
+ #workspaceRoot;
7620
+ constructor({ workspaceRoot }) {
7621
+ this.#workspaceRoot = path26.resolve(workspaceRoot);
7622
+ }
7623
+ async syncForBatch(files) {
7624
+ const indexPaths = new Set;
7625
+ const errors = [];
7626
+ for (const file of files) {
7627
+ const facetIndex = this.#facetIndexFor(file);
7628
+ if (facetIndex)
7629
+ indexPaths.add(facetIndex);
7630
+ const moduleIndex = await this.#moduleIndexForDirectoryEvent(file).catch((err) => {
7631
+ errors.push(`[generated-index] module detection failed for ${file}: ${formatError(err)}`);
7632
+ return null;
7633
+ });
7634
+ if (moduleIndex)
7635
+ indexPaths.add(moduleIndex);
7636
+ }
7637
+ const changedFiles = [];
7638
+ for (const indexPath of [...indexPaths].sort()) {
7639
+ try {
7640
+ const changed = await this.#syncIndex(indexPath);
7641
+ if (changed)
7642
+ changedFiles.push(indexPath);
7643
+ } catch (err) {
7644
+ errors.push(`[generated-index] sync failed for ${indexPath}: ${formatError(err)}`);
7645
+ }
7646
+ }
7647
+ return { changedFiles, errors };
7648
+ }
7649
+ #facetIndexFor(file) {
7650
+ const abs = path26.resolve(file);
7651
+ const rel = path26.relative(this.#workspaceRoot, abs);
7652
+ if (rel.startsWith("..") || path26.isAbsolute(rel))
7653
+ return null;
7654
+ const parts = rel.split(path26.sep).filter(Boolean);
7655
+ if (parts.length < 4)
7656
+ return null;
7657
+ const [scope, project, facet, child] = parts;
7658
+ if (scope !== "apps" && scope !== "libs" || !project || !facet || !BARREL_FACETS2.has(facet))
7659
+ return null;
7660
+ if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
7661
+ return null;
7662
+ return path26.join(this.#workspaceRoot, scope, project, facet, "index.ts");
7663
+ }
7664
+ async#moduleIndexForDirectoryEvent(file) {
7665
+ const abs = path26.resolve(file);
7666
+ const rel = path26.relative(this.#workspaceRoot, abs);
7667
+ if (rel.startsWith("..") || path26.isAbsolute(rel))
7668
+ return null;
7669
+ const parts = rel.split(path26.sep).filter(Boolean);
7670
+ if (parts.length !== 4 && parts.length !== 5)
7671
+ return null;
7672
+ const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
7673
+ if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
7674
+ return null;
7675
+ const isExistingDirectory = await stat3(abs).then((s) => s.isDirectory()).catch(() => false);
7676
+ const looksLikeDeletedDirectory = !path26.extname(abs);
7677
+ if (!isExistingDirectory && !looksLikeDeletedDirectory)
7678
+ return null;
7679
+ if (moduleSegment === "__scalar" && scalarSegment) {
7680
+ return path26.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
7681
+ }
7682
+ if (!moduleSegment || moduleSegment === "__scalar")
7683
+ return null;
7684
+ return path26.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
7685
+ }
7686
+ async#syncIndex(indexPath) {
7687
+ const dir = path26.dirname(indexPath);
7688
+ const content = await this.#contentForIndex(indexPath);
7689
+ if (content === null) {
7690
+ if (!await exists(indexPath))
7691
+ return false;
7692
+ await rm2(indexPath, { force: true });
7693
+ return true;
7694
+ }
7695
+ const current = await readFile(indexPath, "utf8").catch(() => null);
7696
+ if (current === content)
7697
+ return false;
7698
+ await mkdir6(dir, { recursive: true });
7699
+ await writeFile(indexPath, content);
7700
+ return true;
7701
+ }
7702
+ async#contentForIndex(indexPath) {
7703
+ const parts = path26.relative(this.#workspaceRoot, indexPath).split(path26.sep).filter(Boolean);
7704
+ const facet = parts.at(-2);
7705
+ if (facet && BARREL_FACETS2.has(facet))
7706
+ return this.#facetContent(path26.dirname(indexPath));
7707
+ return this.#moduleContent(path26.dirname(indexPath));
7708
+ }
7709
+ async#facetContent(dir) {
7710
+ const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
7711
+ const exportNames = entries.flatMap((entry) => {
7712
+ const name = entry.name;
7713
+ if (name.startsWith("."))
7714
+ return [];
7715
+ if (entry.isDirectory())
7716
+ return [name];
7717
+ if (!entry.isFile())
7718
+ return [];
7719
+ if (!FACET_SOURCE_FILE_RE.test(name) || FACET_EXCLUDED_FILE_RE.test(name))
7720
+ return [];
7721
+ return [name.replace(FACET_SOURCE_FILE_RE, "")];
7722
+ }).sort();
7723
+ if (exportNames.length === 0)
7724
+ return null;
7725
+ return `${exportNames.map((name) => `export * from "./${name}";`).join(`
7726
+ `)}
7727
+ `;
7728
+ }
7729
+ async#moduleContent(dir) {
7730
+ const rel = path26.relative(this.#workspaceRoot, dir);
7731
+ const parts = rel.split(path26.sep).filter(Boolean);
7732
+ const moduleSegment = parts.at(-1);
7733
+ if (!moduleSegment)
7734
+ return null;
7735
+ const isScalar = parts.at(-2) === "__scalar";
7736
+ const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
7737
+ if (!rawModel)
7738
+ return null;
7739
+ const modelName = capitalize3(rawModel);
7740
+ const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
7741
+ const fileTypes = [];
7742
+ for (const type of allowedTypes) {
7743
+ if (await exists(path26.join(dir, `${modelName}.${type}.tsx`)))
7744
+ fileTypes.push(type);
7745
+ }
7746
+ if (fileTypes.length === 0)
7747
+ return null;
7748
+ return `
7749
+ ${fileTypes.map((type) => `import * as ${type} from "./${modelName}.${type}";`).join(`
7750
+ `)}
7751
+
7752
+ export const ${modelName} = { ${fileTypes.join(", ")} };`;
7753
+ }
7754
+ }
7755
+ var exists = async (file) => stat3(file).then(() => true).catch(() => false);
7756
+ var capitalize3 = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
7757
+ var formatError = (err) => err instanceof Error ? err.message : String(err);
7758
+ // pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
7759
+ import { mkdir as mkdir7 } from "fs/promises";
7760
+ import path27 from "path";
7238
7761
  import {
7239
7762
  generateFontFace,
7240
7763
  getMetricsForFamily,
@@ -7258,7 +7781,7 @@ class FontOptimizer {
7258
7781
  constructor(app, command = "start") {
7259
7782
  this.#app = app;
7260
7783
  this.#command = command;
7261
- this.#artifactRoot = path25.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
7784
+ this.#artifactRoot = path27.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
7262
7785
  }
7263
7786
  async optimize() {
7264
7787
  const fonts = await this.discoverFonts();
@@ -7277,7 +7800,7 @@ class FontOptimizer {
7277
7800
  const pageKeys = await this.#app.getPageKeys();
7278
7801
  const fonts = [];
7279
7802
  await Promise.all(pageKeys.map(async (key) => {
7280
- const filePath = path25.resolve(this.#app.cwdPath, "page", key);
7803
+ const filePath = path27.resolve(this.#app.cwdPath, "page", key);
7281
7804
  const file = Bun.file(filePath);
7282
7805
  if (!await file.exists())
7283
7806
  return;
@@ -7293,8 +7816,8 @@ class FontOptimizer {
7293
7816
  this.#app.logger.warn(`[font] source not found: ${face.src}`);
7294
7817
  continue;
7295
7818
  }
7296
- const outputPath = path25.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
7297
- await mkdir6(path25.dirname(outputPath), { recursive: true });
7819
+ const outputPath = path27.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
7820
+ await mkdir7(path27.dirname(outputPath), { recursive: true });
7298
7821
  const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
7299
7822
  const outputBuffer = font.subset === false ? await this.#convertToWoff2(sourceBuffer, sourcePath) : await subsetFont(sourceBuffer, await this.#getSubsetText(font), { targetFormat: "woff2" });
7300
7823
  await Bun.write(outputPath, outputBuffer);
@@ -7441,8 +7964,8 @@ class FontOptimizer {
7441
7964
  return null;
7442
7965
  const rel = src.replace(/^\//, "");
7443
7966
  const candidates = [
7444
- this.#command === "build" ? path25.join(this.#app.dist.cwdPath, "public", rel) : null,
7445
- path25.join(this.#app.cwdPath, "public", rel),
7967
+ this.#command === "build" ? path27.join(this.#app.dist.cwdPath, "public", rel) : null,
7968
+ path27.join(this.#app.cwdPath, "public", rel),
7446
7969
  this.#resolveWorkspacePublicPath(rel)
7447
7970
  ].filter(Boolean);
7448
7971
  for (const candidate of candidates) {
@@ -7470,7 +7993,7 @@ class FontOptimizer {
7470
7993
  return "woff2";
7471
7994
  if (signature === "OTTO")
7472
7995
  return "otf";
7473
- const ext = path25.extname(sourcePath).slice(1).toLowerCase();
7996
+ const ext = path27.extname(sourcePath).slice(1).toLowerCase();
7474
7997
  if (ext === "otf" || ext === "woff" || ext === "woff2")
7475
7998
  return ext;
7476
7999
  return "ttf";
@@ -7479,7 +8002,7 @@ class FontOptimizer {
7479
8002
  const [root, dep, ...rest] = rel.split("/");
7480
8003
  if (root !== "libs" || !dep || rest.length === 0)
7481
8004
  return null;
7482
- return path25.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
8005
+ return path27.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
7483
8006
  }
7484
8007
  async#getSubsetText(font) {
7485
8008
  const parts = new Set;
@@ -7489,7 +8012,7 @@ class FontOptimizer {
7489
8012
  if (font.subsetText)
7490
8013
  parts.add(font.subsetText);
7491
8014
  for (const filePath of font.subsetFiles ?? []) {
7492
- const abs = path25.isAbsolute(filePath) ? filePath : path25.join(this.#app.cwdPath, filePath);
8015
+ const abs = path27.isAbsolute(filePath) ? filePath : path27.join(this.#app.cwdPath, filePath);
7493
8016
  const file = Bun.file(abs);
7494
8017
  if (await file.exists())
7495
8018
  parts.add(await file.text());
@@ -7508,7 +8031,7 @@ class FontOptimizer {
7508
8031
  return "";
7509
8032
  }
7510
8033
  async#collectAutoSubsetText() {
7511
- const roots = ["page", "ui"].map((dir) => path25.join(this.#app.cwdPath, dir));
8034
+ const roots = ["page", "ui"].map((dir) => path27.join(this.#app.cwdPath, dir));
7512
8035
  const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
7513
8036
  const parts = [];
7514
8037
  await Promise.all(roots.map(async (root) => {
@@ -7622,43 +8145,43 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
7622
8145
  }
7623
8146
  }
7624
8147
  // pkgs/@akanjs/devkit/frontendBuild/hmrChangeClassifier.ts
7625
- import path26 from "path";
7626
- var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
8148
+ import path28 from "path";
8149
+ var SOURCE_EXTS5 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
7627
8150
  var CSS_EXTS = new Set([".css"]);
7628
- var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
8151
+ var CONFIG_BASENAMES2 = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
7629
8152
 
7630
8153
  class HmrChangeClassifier {
7631
8154
  classify(abs) {
7632
8155
  if (this.#isUninteresting(abs))
7633
8156
  return "ignore";
7634
- const base = path26.basename(abs);
7635
- if (CONFIG_BASENAMES.has(base))
8157
+ const base = path28.basename(abs);
8158
+ if (CONFIG_BASENAMES2.has(base))
7636
8159
  return "config";
7637
- const ext = path26.extname(abs).toLowerCase();
8160
+ const ext = path28.extname(abs).toLowerCase();
7638
8161
  if (CSS_EXTS.has(ext))
7639
8162
  return "css";
7640
- if (SOURCE_EXTS4.has(ext))
8163
+ if (SOURCE_EXTS5.has(ext))
7641
8164
  return "code";
7642
8165
  return "ignore";
7643
8166
  }
7644
8167
  #isUninteresting(abs) {
7645
- const base = path26.basename(abs);
8168
+ const base = path28.basename(abs);
7646
8169
  if (!base)
7647
8170
  return true;
7648
8171
  if (base.startsWith("."))
7649
8172
  return true;
7650
8173
  if (base.endsWith("~") || base.endsWith(".swp") || base.endsWith(".swx") || base.endsWith(".tmp"))
7651
8174
  return true;
7652
- if (abs.includes(`${path26.sep}node_modules${path26.sep}`))
8175
+ if (abs.includes(`${path28.sep}node_modules${path28.sep}`))
7653
8176
  return true;
7654
- if (abs.includes(`${path26.sep}.akan${path26.sep}`))
8177
+ if (abs.includes(`${path28.sep}.akan${path28.sep}`))
7655
8178
  return true;
7656
8179
  return false;
7657
8180
  }
7658
8181
  }
7659
8182
  // pkgs/@akanjs/devkit/frontendBuild/hmrWatcher.ts
7660
8183
  import fs4 from "fs";
7661
- import path27 from "path";
8184
+ import path29 from "path";
7662
8185
  class HmrWatcher {
7663
8186
  #roots;
7664
8187
  #debounceMs;
@@ -7671,7 +8194,7 @@ class HmrWatcher {
7671
8194
  #stopped = false;
7672
8195
  #flushing = false;
7673
8196
  constructor(opts) {
7674
- this.#roots = [...new Set(opts.roots.map((r) => path27.resolve(r)))];
8197
+ this.#roots = [...new Set(opts.roots.map((r) => path29.resolve(r)))];
7675
8198
  this.#debounceMs = opts.debounceMs ?? 80;
7676
8199
  this.#onBatch = opts.onBatch;
7677
8200
  this.#logger = opts.logger;
@@ -7682,7 +8205,7 @@ class HmrWatcher {
7682
8205
  const w = fs4.watch(root, { recursive: true, persistent: false }, (_event, filename) => {
7683
8206
  if (!filename)
7684
8207
  return;
7685
- const abs = path27.resolve(root, filename.toString());
8208
+ const abs = path29.resolve(root, filename.toString());
7686
8209
  this.#queue(abs);
7687
8210
  });
7688
8211
  this.#watchers.push(w);
@@ -7740,10 +8263,10 @@ class HmrWatcher {
7740
8263
  }
7741
8264
  }
7742
8265
  // pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
7743
- import path29 from "path";
8266
+ import path31 from "path";
7744
8267
 
7745
8268
  // pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
7746
- import path28 from "path";
8269
+ import path30 from "path";
7747
8270
  var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
7748
8271
  var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
7749
8272
  var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
@@ -7786,7 +8309,7 @@ async function createExternalizeFrameworkPlugin(options) {
7786
8309
  const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
7787
8310
  if (!replPath)
7788
8311
  continue;
7789
- const candidate = path28.resolve(workspaceRoot, replPath + suffix);
8312
+ const candidate = path30.resolve(workspaceRoot, replPath + suffix);
7790
8313
  const hit = await firstExisting(candidate);
7791
8314
  if (hit)
7792
8315
  return hit;
@@ -7798,8 +8321,8 @@ async function createExternalizeFrameworkPlugin(options) {
7798
8321
  if (spec === pkg)
7799
8322
  continue;
7800
8323
  const suffix = spec.slice(pkg.length + 1);
7801
- const pkgDir = path28.dirname(path28.resolve(workspaceRoot, entryFile));
7802
- const candidate = path28.join(pkgDir, suffix);
8324
+ const pkgDir = path30.dirname(path30.resolve(workspaceRoot, entryFile));
8325
+ const candidate = path30.join(pkgDir, suffix);
7803
8326
  const hit = await firstExisting(candidate);
7804
8327
  if (hit)
7805
8328
  return hit;
@@ -7849,7 +8372,7 @@ async function firstExisting(basePath2) {
7849
8372
  return candidate;
7850
8373
  }
7851
8374
  for (const ext of CANDIDATE_EXTS3) {
7852
- const candidate = path28.join(basePath2, `index${ext}`);
8375
+ const candidate = path30.join(basePath2, `index${ext}`);
7853
8376
  if (await Bun.file(candidate).exists())
7854
8377
  return candidate;
7855
8378
  }
@@ -7940,11 +8463,11 @@ class PagesBundleBuilder {
7940
8463
  const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
7941
8464
  if (!entryArtifact)
7942
8465
  throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
7943
- const bundlePath = path29.resolve(entryArtifact.path);
8466
+ const bundlePath = path31.resolve(entryArtifact.path);
7944
8467
  const buildId = Date.now();
7945
8468
  const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
7946
8469
  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`);
8470
+ 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
8471
  return {
7949
8472
  bundlePath,
7950
8473
  buildId,
@@ -8026,11 +8549,11 @@ function loaderFor4(absPath) {
8026
8549
  }
8027
8550
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
8028
8551
  import fs5 from "fs";
8029
- import path30 from "path";
8552
+ import path32 from "path";
8030
8553
  var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
8031
8554
  var MIN_COMPRESS_BYTES = 1024;
8032
8555
  async function precompressArtifacts(app) {
8033
- const roots = [path30.join(app.dist.cwdPath, ".akan/artifact/client")];
8556
+ const roots = [path32.join(app.dist.cwdPath, ".akan/artifact/client")];
8034
8557
  const result = { files: 0, inputBytes: 0, outputBytes: 0 };
8035
8558
  await Promise.all(roots.map((root) => precompressRoot(root, result)));
8036
8559
  if (result.files > 0) {
@@ -8056,7 +8579,7 @@ async function precompressRoot(root, result) {
8056
8579
  async function shouldPrecompress(filePath) {
8057
8580
  if (filePath.endsWith(".gz"))
8058
8581
  return false;
8059
- if (!COMPRESSIBLE_EXTS.has(path30.extname(filePath).toLowerCase()))
8582
+ if (!COMPRESSIBLE_EXTS.has(path32.extname(filePath).toLowerCase()))
8060
8583
  return false;
8061
8584
  const file = Bun.file(filePath);
8062
8585
  if (!await file.exists())
@@ -8074,7 +8597,7 @@ function formatBytes(bytes) {
8074
8597
  return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
8075
8598
  }
8076
8599
  // pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
8077
- import path31 from "path";
8600
+ import path33 from "path";
8078
8601
  import { optimize } from "@tailwindcss/node";
8079
8602
  function prepareCssAsset(command, basePath2, cssText) {
8080
8603
  return optimize(cssText, { file: `${basePath2 || "root"}.css`, minify: command === "build" }).code;
@@ -8090,7 +8613,7 @@ class SsrBaseArtifactBuilder {
8090
8613
  this.#app = app;
8091
8614
  this.#command = command;
8092
8615
  this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
8093
- this.#absArtifactDir = path31.resolve(this.#artifactDir);
8616
+ this.#absArtifactDir = path33.resolve(this.#artifactDir);
8094
8617
  }
8095
8618
  async build() {
8096
8619
  const akanConfig2 = await this.#app.getConfig();
@@ -8112,7 +8635,7 @@ class SsrBaseArtifactBuilder {
8112
8635
  rscRuntimeSsrManifest,
8113
8636
  vendorMap,
8114
8637
  cssAssets,
8115
- pagesBundlePath: this.#command === "build" ? path31.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
8638
+ pagesBundlePath: this.#command === "build" ? path33.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
8116
8639
  pagesBundleBuildId: pagesBundle.buildId,
8117
8640
  domains: [...akanConfig2.domains],
8118
8641
  subRoutes: Object.fromEntries(Array.from(akanConfig2.subRoutes.entries()).map(([basePath2, domains]) => [basePath2, [...domains]])),
@@ -8121,7 +8644,7 @@ class SsrBaseArtifactBuilder {
8121
8644
  i18n: akanConfig2.i18n,
8122
8645
  imageConfig: akanConfig2.images
8123
8646
  };
8124
- await Bun.write(path31.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
8647
+ await Bun.write(path33.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
8125
8648
  `);
8126
8649
  this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
8127
8650
  return { artifact, seedIndex, cssCompiler, optimizedFonts };
@@ -8169,15 +8692,15 @@ class SsrBaseArtifactBuilder {
8169
8692
  async#resolveAkanServerPath() {
8170
8693
  const candidates = [];
8171
8694
  try {
8172
- candidates.push(path31.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
8695
+ candidates.push(path33.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
8173
8696
  } catch {}
8174
- candidates.push(path31.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path31.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
8697
+ candidates.push(path33.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path33.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
8175
8698
  try {
8176
- candidates.push(path31.dirname(Bun.resolveSync("akanjs/server", path31.dirname(Bun.main))));
8699
+ candidates.push(path33.dirname(Bun.resolveSync("akanjs/server", path33.dirname(Bun.main))));
8177
8700
  } 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"));
8701
+ 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
8702
  for (const candidate of candidates) {
8180
- if (await Bun.file(path31.join(candidate, "rscClient.tsx")).exists())
8703
+ if (await Bun.file(path33.join(candidate, "rscClient.tsx")).exists())
8181
8704
  return candidate;
8182
8705
  }
8183
8706
  throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
@@ -8206,14 +8729,14 @@ ${preparedCssText}`).toString(36);
8206
8729
  `styles/${cssAssetName}-${cssHash}.css`,
8207
8730
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
8208
8731
  ];
8209
- await Bun.write(path31.join(this.#absArtifactDir, cssRelPath), preparedCssText);
8732
+ await Bun.write(path33.join(this.#absArtifactDir, cssRelPath), preparedCssText);
8210
8733
  this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath2} -> ${cssRelPath}`);
8211
8734
  return [basePath2, { cssUrl, cssRelPath }];
8212
8735
  }
8213
8736
  }
8214
8737
  // pkgs/@akanjs/devkit/frontendBuild/watchRootResolver.ts
8215
8738
  import fs6 from "fs";
8216
- import path32 from "path";
8739
+ import path34 from "path";
8217
8740
 
8218
8741
  class WatchRootResolver {
8219
8742
  #app;
@@ -8223,15 +8746,15 @@ class WatchRootResolver {
8223
8746
  async resolve() {
8224
8747
  const tsconfig = await this.#app.getTsConfig();
8225
8748
  const set = new Set;
8226
- set.add(path32.resolve(`${this.#app.cwdPath}/page`));
8749
+ set.add(path34.resolve(`${this.#app.cwdPath}/page`));
8227
8750
  for (const targets of Object.values(tsconfig.compilerOptions.paths ?? {})) {
8228
8751
  for (const target of targets) {
8229
8752
  if (!target)
8230
8753
  continue;
8231
- if (path32.isAbsolute(target))
8754
+ if (path34.isAbsolute(target))
8232
8755
  continue;
8233
8756
  const cleaned = target.replace(/\/?\*+.*$/, "").replace(/\/[^/]+\.[^/]+$/, "");
8234
- const resolved = path32.resolve(this.#app.workspace.workspaceRoot, cleaned);
8757
+ const resolved = path34.resolve(this.#app.workspace.workspaceRoot, cleaned);
8235
8758
  if (fs6.existsSync(resolved))
8236
8759
  set.add(resolved);
8237
8760
  }
@@ -8298,7 +8821,7 @@ class ApplicationBuildRunner {
8298
8821
  phases: this.#phases,
8299
8822
  durationMs: Date.now() - this.#startedAt,
8300
8823
  outputDir: this.#app.dist.cwdPath,
8301
- artifactDir: path33.join(this.#app.dist.cwdPath, ".akan/artifact")
8824
+ artifactDir: path35.join(this.#app.dist.cwdPath, ".akan/artifact")
8302
8825
  };
8303
8826
  }
8304
8827
  async typecheck(options = {}) {
@@ -8306,7 +8829,7 @@ class ApplicationBuildRunner {
8306
8829
  await this.#app.getPageKeys({ refresh: true });
8307
8830
  const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig({ incremental });
8308
8831
  if (clean)
8309
- await rm2(path33.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
8832
+ await rm3(path35.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
8310
8833
  await this.#checkProjectInChildProcess(tsconfigPath);
8311
8834
  }
8312
8835
  async#runPhase(id, label, task, summarize, options = {}) {
@@ -8378,7 +8901,7 @@ class ApplicationBuildRunner {
8378
8901
  };
8379
8902
  }
8380
8903
  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";
8904
+ await Bun.write(path35.join(this.#app.dist.cwdPath, "console.js"), `import { cnst, db, dict, option, server, sig, srv } from "./server.js";
8382
8905
  import { assertAkanConsoleAllowed, startAkanConsole } from "./console-runtime.js";
8383
8906
 
8384
8907
  const run = async () => {
@@ -8401,14 +8924,14 @@ void run().catch((error) => {
8401
8924
  try {
8402
8925
  return Bun.resolveSync("akanjs/server/rsc-worker", import.meta.dir);
8403
8926
  } catch {
8404
- return path33.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/rscWorker.tsx");
8927
+ return path35.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/rscWorker.tsx");
8405
8928
  }
8406
8929
  }
8407
8930
  #resolveConsoleRuntimeBuildEntry() {
8408
8931
  try {
8409
- return path33.join(path33.dirname(Bun.resolveSync("akanjs/server", import.meta.dir)), "console.ts");
8932
+ return path35.join(path35.dirname(Bun.resolveSync("akanjs/server", import.meta.dir)), "console.ts");
8410
8933
  } catch {
8411
- return path33.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/console.ts");
8934
+ return path35.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server/console.ts");
8412
8935
  }
8413
8936
  }
8414
8937
  async#buildCsr() {
@@ -8425,8 +8948,8 @@ void run().catch((error) => {
8425
8948
  return { base, allRoutes };
8426
8949
  }
8427
8950
  async#writeTypecheckTsconfig({ incremental = true } = {}) {
8428
- const typecheckDir = path33.join(this.#app.cwdPath, ".akan", "typecheck");
8429
- await mkdir7(typecheckDir, { recursive: true });
8951
+ const typecheckDir = path35.join(this.#app.cwdPath, ".akan", "typecheck");
8952
+ await mkdir8(typecheckDir, { recursive: true });
8430
8953
  const tsconfig = {
8431
8954
  extends: "../../tsconfig.json",
8432
8955
  compilerOptions: {
@@ -8444,7 +8967,7 @@ void run().catch((error) => {
8444
8967
  ],
8445
8968
  references: []
8446
8969
  };
8447
- const tsconfigPath = path33.join(typecheckDir, "tsconfig.json");
8970
+ const tsconfigPath = path35.join(typecheckDir, "tsconfig.json");
8448
8971
  await Bun.write(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
8449
8972
  `);
8450
8973
  return { typecheckDir, tsconfigPath };
@@ -8470,10 +8993,10 @@ void run().catch((error) => {
8470
8993
  }
8471
8994
  async#resolveTypecheckWorkerEntry() {
8472
8995
  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")
8996
+ path35.join(this.#app.workspace.workspaceRoot, "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
8997
+ path35.join(this.#app.workspace.workspaceRoot, "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
8998
+ path35.join(import.meta.dir, "typecheck.proc.js"),
8999
+ path35.join(import.meta.dir, "typecheck.proc.ts")
8477
9000
  ];
8478
9001
  for (const candidate of candidates)
8479
9002
  if (await Bun.file(candidate).exists())
@@ -8506,7 +9029,7 @@ void run().catch((error) => {
8506
9029
  }
8507
9030
  }
8508
9031
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
8509
- import { cp, mkdir as mkdir8, rm as rm3 } from "fs/promises";
9032
+ import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
8510
9033
 
8511
9034
  // pkgs/@akanjs/devkit/uploadRelease.ts
8512
9035
  import { HttpClient as HttpClient2, Logger as Logger9 } from "akanjs/common";
@@ -8612,16 +9135,16 @@ class ApplicationReleasePackager {
8612
9135
  const platformVersion = akanConfig2.mobile.version;
8613
9136
  const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
8614
9137
  if (await FileSys.dirExists(buildRoot))
8615
- await rm3(buildRoot, { recursive: true, force: true });
8616
- await mkdir8(buildRoot, { recursive: true });
9138
+ await rm4(buildRoot, { recursive: true, force: true });
9139
+ await mkdir9(buildRoot, { recursive: true });
8617
9140
  if (rebuild || !await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))
8618
9141
  await this.#build();
8619
9142
  const buildVersion = `${platformVersion}-${buildNum}`;
8620
9143
  const buildPath = `${buildRoot}/${buildVersion}`;
8621
- await mkdir8(buildPath, { recursive: true });
9144
+ await mkdir9(buildPath, { recursive: true });
8622
9145
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
8623
9146
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
8624
- await rm3(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
9147
+ await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
8625
9148
  await this.#app.workspace.spawn("tar", [
8626
9149
  "-zcf",
8627
9150
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-release.tar.gz`,
@@ -8641,7 +9164,7 @@ class ApplicationReleasePackager {
8641
9164
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
8642
9165
  "./csr"
8643
9166
  ]);
8644
- await rm3("./csr", { recursive: true, force: true });
9167
+ await rm4("./csr", { recursive: true, force: true });
8645
9168
  }
8646
9169
  async#writeSourceArchive({ readme }) {
8647
9170
  const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
@@ -8649,13 +9172,13 @@ class ApplicationReleasePackager {
8649
9172
  await cp(this.#app.dist.cwdPath, `${sourceRoot}/apps/${this.#app.name}`, { recursive: true });
8650
9173
  const libDeps = ["social", "shared", "platform", "util"];
8651
9174
  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}`;
9175
+ await Promise.all([".next", "ios", "android", "public/libs"].map(async (path36) => {
9176
+ const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path36}`;
8654
9177
  if (await FileSys.dirExists(targetPath))
8655
- await rm3(targetPath, { recursive: true, force: true });
9178
+ await rm4(targetPath, { recursive: true, force: true });
8656
9179
  }));
8657
9180
  const syncPaths = [".husky", ".gitignore", "package.json"];
8658
- await Promise.all(syncPaths.map((path34) => cp(`${this.#app.workspace.cwdPath}/${path34}`, `${sourceRoot}/${path34}`, { recursive: true })));
9181
+ await Promise.all(syncPaths.map((path36) => cp(`${this.#app.workspace.cwdPath}/${path36}`, `${sourceRoot}/${path36}`, { recursive: true })));
8659
9182
  await this.#writeSourceTsconfig(sourceRoot, libDeps);
8660
9183
  await Bun.write(`${sourceRoot}/README.md`, readme);
8661
9184
  await this.#app.workspace.spawn("tar", [
@@ -8671,11 +9194,11 @@ class ApplicationReleasePackager {
8671
9194
  const maxRetry = 3;
8672
9195
  for (let i = 0;i < maxRetry; i++) {
8673
9196
  try {
8674
- await rm3(sourceRoot, { recursive: true, force: true });
9197
+ await rm4(sourceRoot, { recursive: true, force: true });
8675
9198
  } catch {}
8676
9199
  }
8677
9200
  }
8678
- await mkdir8(sourceRoot, { recursive: true });
9201
+ await mkdir9(sourceRoot, { recursive: true });
8679
9202
  }
8680
9203
  async#writeSourceTsconfig(sourceRoot, libDeps) {
8681
9204
  const tsconfig = await this.#app.workspace.readJson("tsconfig.json");
@@ -8746,20 +9269,20 @@ class ApplicationReleasePackager {
8746
9269
  }
8747
9270
  }
8748
9271
  // pkgs/@akanjs/devkit/applicationTestPreload.ts
8749
- import path34 from "path";
9272
+ import path36 from "path";
8750
9273
  var SIGNAL_TEST_PRELOAD_PATH = "test/signalTest.preload.ts";
8751
9274
  async function resolveSignalTestPreloadPath(target) {
8752
9275
  const candidates = [];
8753
9276
  const addResolvedPackageCandidate = (basePath2) => {
8754
9277
  try {
8755
- candidates.push(path34.join(path34.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
9278
+ candidates.push(path36.join(path36.dirname(Bun.resolveSync("akanjs/package.json", basePath2)), SIGNAL_TEST_PRELOAD_PATH));
8756
9279
  } catch {}
8757
9280
  };
8758
9281
  addResolvedPackageCandidate(target.cwdPath);
8759
9282
  addResolvedPackageCandidate(process.cwd());
8760
- addResolvedPackageCandidate(path34.dirname(Bun.main));
9283
+ addResolvedPackageCandidate(path36.dirname(Bun.main));
8761
9284
  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));
9285
+ 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
9286
  for (const candidate of [...new Set(candidates)]) {
8764
9287
  if (await Bun.file(candidate).exists())
8765
9288
  return candidate;
@@ -8768,8 +9291,8 @@ async function resolveSignalTestPreloadPath(target) {
8768
9291
  }
8769
9292
  // pkgs/@akanjs/devkit/builder.ts
8770
9293
  import { existsSync as existsSync2 } from "fs";
8771
- import { mkdir as mkdir9 } from "fs/promises";
8772
- import path35 from "path";
9294
+ import { mkdir as mkdir10 } from "fs/promises";
9295
+ import path37 from "path";
8773
9296
  var SKIP_ENTRY_DIR_SET = new Set(["node_modules", "dist", "build", ".git", ".next"]);
8774
9297
  var assetExtensions = [".css", ".md", ".js", ".png", ".ico", ".svg", ".json", ".template"];
8775
9298
  var assetLoader = Object.fromEntries(assetExtensions.map((ext) => [ext, "file"]));
@@ -8786,14 +9309,14 @@ class Builder {
8786
9309
  #globEntrypoints(cwd, pattern) {
8787
9310
  const glob = new Bun.Glob(pattern);
8788
9311
  return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
8789
- const segments = relativePath.split(path35.sep);
9312
+ const segments = relativePath.split(path37.sep);
8790
9313
  return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
8791
- }).map((rel) => path35.join(cwd, rel));
9314
+ }).map((rel) => path37.join(cwd, rel));
8792
9315
  }
8793
9316
  #globFiles(cwd, pattern = "**/*.*") {
8794
9317
  const glob = new Bun.Glob(pattern);
8795
9318
  return Array.from(glob.scanSync({ cwd, onlyFiles: true })).filter((relativePath) => {
8796
- const segments = relativePath.split(path35.sep);
9319
+ const segments = relativePath.split(path37.sep);
8797
9320
  return !segments.some((segment) => SKIP_ENTRY_DIR_SET.has(segment));
8798
9321
  });
8799
9322
  }
@@ -8801,17 +9324,17 @@ class Builder {
8801
9324
  const out = [];
8802
9325
  for (const p of additionalEntryPoints) {
8803
9326
  if (p.includes("*")) {
8804
- const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path35.sep}`) ? p.slice(cwd.length + 1) : p;
9327
+ const rel = p.startsWith(`${cwd}/`) || p.startsWith(`${cwd}${path37.sep}`) ? p.slice(cwd.length + 1) : p;
8805
9328
  out.push(...this.#globEntrypoints(cwd, rel));
8806
9329
  } else
8807
- out.push(path35.isAbsolute(p) ? p : path35.join(cwd, p));
9330
+ out.push(path37.isAbsolute(p) ? p : path37.join(cwd, p));
8808
9331
  }
8809
9332
  return out;
8810
9333
  }
8811
9334
  #getBuildOptions({ bundle = false, additionalEntryPoints = [] } = {}) {
8812
9335
  const cwd = this.#executor.cwdPath;
8813
9336
  const entrypoints = [
8814
- ...bundle ? [path35.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
9337
+ ...bundle ? [path37.join(cwd, "index.ts")] : this.#globEntrypoints(cwd, "**/*.{ts,tsx}"),
8815
9338
  ...this.#resolveAdditionalEntrypoints(cwd, additionalEntryPoints)
8816
9339
  ];
8817
9340
  return {
@@ -8832,9 +9355,9 @@ class Builder {
8832
9355
  for (const relativePath of this.#globFiles(cwd)) {
8833
9356
  if (relativePath === "package.json")
8834
9357
  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 });
9358
+ const sourcePath = path37.join(cwd, relativePath);
9359
+ const targetPath = path37.join(this.#distExecutor.cwdPath, relativePath);
9360
+ await mkdir10(path37.dirname(targetPath), { recursive: true });
8838
9361
  await Bun.write(targetPath, Bun.file(sourcePath));
8839
9362
  }
8840
9363
  }
@@ -8845,13 +9368,13 @@ class Builder {
8845
9368
  return withoutFormatDir;
8846
9369
  if (!hasDotSlash && withoutFormatDir === publishedPath)
8847
9370
  return publishedPath;
8848
- const parsed = path35.posix.parse(withoutFormatDir);
9371
+ const parsed = path37.posix.parse(withoutFormatDir);
8849
9372
  if (![".js", ".mjs", ".cjs"].includes(parsed.ext))
8850
9373
  return withoutFormatDir;
8851
- const withoutExt = path35.posix.join(parsed.dir, parsed.name);
9374
+ const withoutExt = path37.posix.join(parsed.dir, parsed.name);
8852
9375
  const sourcePath = withoutExt.startsWith("./") ? withoutExt.slice(2) : withoutExt;
8853
9376
  const sourceCandidates = [`${sourcePath}.ts`, `${sourcePath}.tsx`];
8854
- const matchedSource = sourceCandidates.find((candidate) => existsSync2(path35.join(this.#executor.cwdPath, candidate)));
9377
+ const matchedSource = sourceCandidates.find((candidate) => existsSync2(path37.join(this.#executor.cwdPath, candidate)));
8855
9378
  if (!matchedSource)
8856
9379
  return withoutFormatDir;
8857
9380
  return hasDotSlash ? `./${matchedSource}` : matchedSource;
@@ -8900,10 +9423,10 @@ class Builder {
8900
9423
  }
8901
9424
  }
8902
9425
  // pkgs/@akanjs/devkit/capacitorApp.ts
8903
- import { cp as cp2, mkdir as mkdir10, rm as rm4 } from "fs/promises";
8904
- import path36 from "path";
9426
+ import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
9427
+ import path38 from "path";
8905
9428
  import { MobileProject } from "@trapezedev/project";
8906
- import { capitalize as capitalize3 } from "akanjs/common";
9429
+ import { capitalize as capitalize4 } from "akanjs/common";
8907
9430
 
8908
9431
  // pkgs/@akanjs/devkit/fileEditor.ts
8909
9432
  class FileEditor {
@@ -9096,10 +9619,10 @@ class CapacitorApp {
9096
9619
  constructor(app, target) {
9097
9620
  this.app = app;
9098
9621
  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");
9622
+ this.targetRootPath = path38.posix.join(".akan", "mobile", this.target.name);
9623
+ this.targetRoot = path38.join(this.app.cwdPath, this.targetRootPath);
9624
+ this.targetWebRoot = path38.join(this.targetRoot, "www");
9625
+ this.targetAssetRoot = path38.join(this.targetRoot, "assets");
9103
9626
  this.project = new MobileProject(this.app.cwdPath, {
9104
9627
  android: { path: this.androidRootPath },
9105
9628
  ios: { path: this.iosProjectPath }
@@ -9111,13 +9634,13 @@ class CapacitorApp {
9111
9634
  env = "debug",
9112
9635
  regenerate = false
9113
9636
  } = {}) {
9114
- await mkdir10(this.targetRoot, { recursive: true });
9637
+ await mkdir11(this.targetRoot, { recursive: true });
9115
9638
  await this.#writeCapacitorConfig();
9116
9639
  if (regenerate) {
9117
9640
  if (!platform || platform === "ios")
9118
- await rm4(path36.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
9641
+ await rm5(path38.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
9119
9642
  if (!platform || platform === "android")
9120
- await rm4(path36.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
9643
+ await rm5(path38.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
9121
9644
  }
9122
9645
  const project = this.project;
9123
9646
  await this.project.load();
@@ -9181,7 +9704,7 @@ class CapacitorApp {
9181
9704
  await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
9182
9705
  }
9183
9706
  async#updateAndroidBuildTypes() {
9184
- const appGradle = await FileEditor.create(path36.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
9707
+ const appGradle = await FileEditor.create(path38.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
9185
9708
  const buildTypesBlock = `
9186
9709
  debug {
9187
9710
  applicationIdSuffix ".debug"
@@ -9224,7 +9747,7 @@ class CapacitorApp {
9224
9747
  const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
9225
9748
  await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
9226
9749
  stdio: "inherit",
9227
- cwd: path36.join(this.app.cwdPath, this.androidRootPath),
9750
+ cwd: path38.join(this.app.cwdPath, this.androidRootPath),
9228
9751
  env: await this.#commandEnv("release", env)
9229
9752
  });
9230
9753
  }
@@ -9232,10 +9755,10 @@ class CapacitorApp {
9232
9755
  await this.#spawnMobile("npx", ["cap", "open", "android"], { operation: "local", env: "local" });
9233
9756
  }
9234
9757
  async#ensureAndroidAssetsDir() {
9235
- await mkdir10(path36.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
9758
+ await mkdir11(path38.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
9236
9759
  }
9237
9760
  async#ensureAndroidDebugKeystore() {
9238
- const keystorePath = path36.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
9761
+ const keystorePath = path38.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
9239
9762
  if (await Bun.file(keystorePath).exists())
9240
9763
  return;
9241
9764
  await this.#spawn("keytool", [
@@ -9281,12 +9804,12 @@ class CapacitorApp {
9281
9804
  await this.#prepareAndroid({ operation: "release", env: "main" });
9282
9805
  }
9283
9806
  async prepareWww() {
9284
- const htmlSource = path36.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
9807
+ const htmlSource = path38.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
9285
9808
  if (!await Bun.file(htmlSource).exists())
9286
9809
  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()));
9810
+ await rm5(this.targetWebRoot, { recursive: true, force: true });
9811
+ await mkdir11(this.targetWebRoot, { recursive: true });
9812
+ await Bun.write(path38.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
9290
9813
  }
9291
9814
  #injectMobileTargetMeta(html) {
9292
9815
  const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
@@ -9297,8 +9820,8 @@ class CapacitorApp {
9297
9820
  </head>`);
9298
9821
  }
9299
9822
  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("/");
9823
+ await mkdir11(this.targetRoot, { recursive: true });
9824
+ const appInfoPath = path38.relative(this.app.cwdPath, path38.join(this.app.cwdPath, "akan.app.json")).split(path38.sep).join("/");
9302
9825
  const content = `import type { AppScanResult } from "akanjs";
9303
9826
  import { withBase } from "${process.env.USE_AKANJS_PKGS === "true" ? "../../pkgs/" : ""}akanjs/capacitor.base.config";
9304
9827
  import appInfo from "${appInfoPath.startsWith(".") ? appInfoPath : `./${appInfoPath}`}";
@@ -9319,18 +9842,18 @@ export default withBase(
9319
9842
  appInfo as AppScanResult,
9320
9843
  );
9321
9844
  `;
9322
- await Bun.write(path36.join(this.app.cwdPath, "capacitor.config.ts"), content);
9845
+ await Bun.write(path38.join(this.app.cwdPath, "capacitor.config.ts"), content);
9323
9846
  }
9324
9847
  async#prepareTargetAssets() {
9325
9848
  if (!this.target.assets)
9326
9849
  return;
9327
- await mkdir10(this.targetAssetRoot, { recursive: true });
9850
+ await mkdir11(this.targetAssetRoot, { recursive: true });
9328
9851
  if (this.target.assets.icon)
9329
- await cp2(path36.join(this.app.cwdPath, this.target.assets.icon), path36.join(this.targetAssetRoot, "icon.png"), {
9852
+ await cp2(path38.join(this.app.cwdPath, this.target.assets.icon), path38.join(this.targetAssetRoot, "icon.png"), {
9330
9853
  force: true
9331
9854
  });
9332
9855
  if (this.target.assets.splash)
9333
- await cp2(path36.join(this.app.cwdPath, this.target.assets.splash), path36.join(this.targetAssetRoot, "splash.png"), {
9856
+ await cp2(path38.join(this.app.cwdPath, this.target.assets.splash), path38.join(this.targetAssetRoot, "splash.png"), {
9334
9857
  force: true
9335
9858
  });
9336
9859
  }
@@ -9338,11 +9861,11 @@ export default withBase(
9338
9861
  const files = this.target.files?.[platform];
9339
9862
  if (!files)
9340
9863
  return;
9341
- const platformRoot = path36.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
9864
+ const platformRoot = path38.join(this.app.cwdPath, platform === "ios" ? this.iosRootPath : this.androidRootPath);
9342
9865
  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 });
9866
+ const targetPath = path38.join(platformRoot, to);
9867
+ await mkdir11(path38.dirname(targetPath), { recursive: true });
9868
+ await cp2(path38.join(this.app.cwdPath, from), targetPath, { force: true });
9346
9869
  }));
9347
9870
  }
9348
9871
  async#generateAssets({ operation, env }) {
@@ -9352,7 +9875,7 @@ export default withBase(
9352
9875
  "@capacitor/assets",
9353
9876
  "generate",
9354
9877
  "--assetPath",
9355
- path36.posix.join(this.targetRootPath, "assets"),
9878
+ path38.posix.join(this.targetRootPath, "assets"),
9356
9879
  "--iosProject",
9357
9880
  this.iosProjectPath,
9358
9881
  "--androidProject",
@@ -9454,7 +9977,7 @@ export default withBase(
9454
9977
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
9455
9978
  }
9456
9979
  async#setPermissionInIos(permissions) {
9457
- const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize3(key)}`, value]));
9980
+ const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize4(key)}`, value]));
9458
9981
  await Promise.all([
9459
9982
  this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
9460
9983
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
@@ -9539,15 +10062,15 @@ var Pkg = createInternalArgToken("Pkg");
9539
10062
  var Module = createInternalArgToken("Module");
9540
10063
  var Workspace = createInternalArgToken("Workspace");
9541
10064
  // pkgs/@akanjs/devkit/commandDecorators/command.ts
9542
- import path37 from "path";
10065
+ import path39 from "path";
9543
10066
  import { confirm, input as input2, select as select2 } from "@inquirer/prompts";
9544
10067
  import { Logger as Logger10 } from "akanjs/common";
9545
10068
  import chalk6 from "chalk";
9546
10069
  import { program } from "commander";
9547
10070
 
9548
10071
  // 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)}`;
10072
+ var capitalize5 = (value) => value.slice(0, 1).toUpperCase() + value.slice(1);
10073
+ var createDependencyKey = (refName, kind) => `${refName}${capitalize5(kind)}`;
9551
10074
 
9552
10075
  class CommandContainer {
9553
10076
  static #instances = new Map;
@@ -10039,7 +10562,7 @@ var runCommands = async (...commands) => {
10039
10562
  process.exit(1);
10040
10563
  });
10041
10564
  const __dirname2 = getDirname(import.meta.url);
10042
- const packageJsonCandidates = [`${path37.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
10565
+ const packageJsonCandidates = [`${path39.dirname(Bun.main)}/package.json`, `${__dirname2}/../package.json`];
10043
10566
  let cliPackageJson = null;
10044
10567
  for (const packageJsonPath of packageJsonCandidates) {
10045
10568
  if (!await FileSys.fileExists(packageJsonPath))
@@ -10317,8 +10840,8 @@ var scanModuleSpecifiers = (source, filePath, includeExports) => {
10317
10840
  return importSpecifiers;
10318
10841
  };
10319
10842
  var parseTsConfig = (tsConfigPath = "./tsconfig.json") => {
10320
- const configFile = ts7.readConfigFile(tsConfigPath, (path38) => {
10321
- return ts7.sys.readFile(path38);
10843
+ const configFile = ts7.readConfigFile(tsConfigPath, (path40) => {
10844
+ return ts7.sys.readFile(path40);
10322
10845
  });
10323
10846
  return ts7.parseJsonConfigFileContent(configFile.config, ts7.sys, realpathSync(tsConfigPath).replace(/[^/\\]+$/, ""));
10324
10847
  };
@@ -10560,7 +11083,6 @@ import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime"
10560
11083
  "use client";
10561
11084
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
10562
11085
  import { Logger as Logger11 } from "akanjs/common";
10563
-
10564
11086
  class IncrementalBuilder {
10565
11087
  #logger = new Logger11("IncrementalBuilder");
10566
11088
  #app;
@@ -10569,6 +11091,8 @@ class IncrementalBuilder {
10569
11091
  #cssCompiler;
10570
11092
  #optimizedFonts;
10571
11093
  #discovery;
11094
+ #changePlanner;
11095
+ #generatedIndexSync;
10572
11096
  #generation = 0;
10573
11097
  #workQueue = Promise.resolve();
10574
11098
  #cssRebuildQueue = Promise.resolve();
@@ -10581,6 +11105,8 @@ class IncrementalBuilder {
10581
11105
  this.#cssCompiler = options.cssCompiler;
10582
11106
  this.#optimizedFonts = options.optimizedFonts;
10583
11107
  this.#discovery = options.discovery;
11108
+ this.#changePlanner = new DevChangePlanner({ workspaceRoot: options.app.workspace.workspaceRoot });
11109
+ this.#generatedIndexSync = new DevGeneratedIndexSync({ workspaceRoot: options.app.workspace.workspaceRoot });
10584
11110
  }
10585
11111
  async handleBuildRoute(msg) {
10586
11112
  return this.#enqueueWork(`build-route:${msg.routeId}`, async () => this.#handleBuildRoute(msg));
@@ -10596,6 +11122,7 @@ class IncrementalBuilder {
10596
11122
  discovery: this.#discovery
10597
11123
  }).build();
10598
11124
  this.#logger.verbose(`build-route ok routeId=${msg.routeId} newEntries=${delta.newEntries.length}`);
11125
+ this.#sendBuildStatus("route", { generation: msg.generation, ok: true, files: msg.seeds });
10599
11126
  return {
10600
11127
  type: "build-route-res",
10601
11128
  id: msg.id,
@@ -10614,9 +11141,24 @@ class IncrementalBuilder {
10614
11141
  } catch (err) {
10615
11142
  const errMsg = err instanceof Error ? err.message : String(err);
10616
11143
  this.#logger.error(`build-route failed routeId=${msg.routeId}: ${errMsg}`);
11144
+ this.#sendBuildStatus("route", { generation: msg.generation, ok: false, files: msg.seeds, message: errMsg });
10617
11145
  return { type: "build-route-res", id: msg.id, ok: false, error: errMsg };
10618
11146
  }
10619
11147
  }
11148
+ #sendBuildStatus(phase, { generation, ok, files, message }) {
11149
+ if (typeof generation !== "number")
11150
+ return;
11151
+ process.send?.({
11152
+ type: "build-status",
11153
+ data: {
11154
+ generation,
11155
+ phase,
11156
+ ok,
11157
+ files: files ?? [],
11158
+ message
11159
+ }
11160
+ });
11161
+ }
10620
11162
  async#enqueueWork(label, fn) {
10621
11163
  const started = Date.now();
10622
11164
  const run = this.#workQueue.then(fn, fn);
@@ -10632,10 +11174,10 @@ class IncrementalBuilder {
10632
11174
  }
10633
11175
  }
10634
11176
  batchTouchesPagesTree(appDir, batch) {
10635
- const absAppDir = path38.resolve(appDir);
11177
+ const absAppDir = path40.resolve(appDir);
10636
11178
  for (const f of batch.files) {
10637
- const abs = path38.resolve(f);
10638
- if (!abs.startsWith(`${absAppDir}${path38.sep}`) && abs !== absAppDir)
11179
+ const abs = path40.resolve(f);
11180
+ if (!abs.startsWith(`${absAppDir}${path40.sep}`) && abs !== absAppDir)
10639
11181
  continue;
10640
11182
  if (/\.(tsx|ts|jsx|js)$/.test(abs))
10641
11183
  return true;
@@ -10643,15 +11185,15 @@ class IncrementalBuilder {
10643
11185
  return false;
10644
11186
  }
10645
11187
  async batchMayChangePageKeys(appDir, batch) {
10646
- const absAppDir = path38.resolve(appDir);
10647
- const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path38.normalize(key)));
11188
+ const absAppDir = path40.resolve(appDir);
11189
+ const pageKeys = new Set((await this.#app.getPageKeys()).map((key) => path40.normalize(key)));
10648
11190
  for (const f of batch.files) {
10649
- const abs = path38.resolve(f);
10650
- if (!abs.startsWith(`${absAppDir}${path38.sep}`) && abs !== absAppDir)
11191
+ const abs = path40.resolve(f);
11192
+ if (!abs.startsWith(`${absAppDir}${path40.sep}`) && abs !== absAppDir)
10651
11193
  continue;
10652
11194
  if (!/\.(tsx|ts|jsx|js)$/.test(abs))
10653
11195
  continue;
10654
- const rel = path38.normalize(path38.relative(absAppDir, abs));
11196
+ const rel = path40.normalize(path40.relative(absAppDir, abs));
10655
11197
  if (!await Bun.file(abs).exists() || !pageKeys.has(rel))
10656
11198
  return true;
10657
11199
  }
@@ -10679,7 +11221,7 @@ class IncrementalBuilder {
10679
11221
  ${cssText}`).toString(36);
10680
11222
  const cssRelPath = `styles/${cssAssetName}-${cssHash}.css`;
10681
11223
  const cssUrl = `/_akan/styles/${cssAssetName}-${cssHash}.css`;
10682
- await Bun.write(path38.join(artifactDir, cssRelPath), cssText);
11224
+ await Bun.write(path40.join(artifactDir, cssRelPath), cssText);
10683
11225
  cssAssetEntries.push([basePath2, { cssUrl, cssRelPath }]);
10684
11226
  cssBase64ByUrl[cssUrl] = Buffer.from(new TextEncoder().encode(cssText)).toString("base64");
10685
11227
  })()
@@ -10719,9 +11261,17 @@ ${cssText}`).toString(36);
10719
11261
  generation: next.generation,
10720
11262
  changedFiles: next.changedFiles
10721
11263
  });
11264
+ this.#sendBuildStatus("css", { generation: next.generation, ok: true, files: next.changedFiles });
10722
11265
  this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
10723
11266
  }).catch((err) => {
10724
- this.#logger.error(`css-rebuild failed: ${err instanceof Error ? err.message : err}`);
11267
+ const message = err instanceof Error ? err.message : String(err);
11268
+ this.#logger.error(`css-rebuild failed: ${message}`);
11269
+ this.#sendBuildStatus("css", {
11270
+ generation: next.generation,
11271
+ ok: false,
11272
+ files: next.changedFiles,
11273
+ message
11274
+ });
10725
11275
  });
10726
11276
  }, 150);
10727
11277
  }
@@ -10739,10 +11289,10 @@ ${cssText}`).toString(36);
10739
11289
  if (changedFiles.length === 0)
10740
11290
  return false;
10741
11291
  return changedFiles.some((file) => {
10742
- const normalized = path38.resolve(file);
11292
+ const normalized = path40.resolve(file);
10743
11293
  if (/\.(woff2?|ttf|otf)$/i.test(normalized))
10744
11294
  return true;
10745
- return this.#optimizedFonts.files.some((fontFile) => path38.resolve(fontFile) === normalized);
11295
+ return this.#optimizedFonts.files.some((fontFile) => path40.resolve(fontFile) === normalized);
10746
11296
  });
10747
11297
  }
10748
11298
  async installWatcher() {
@@ -10759,38 +11309,58 @@ ${cssText}`).toString(36);
10759
11309
  this.#logger.verbose(`watching ${roots.length} roots`);
10760
11310
  }
10761
11311
  async#handleWatchBatch(appDir, artifactDir, batch) {
10762
- const kinds = [...batch.kinds];
10763
- if (kinds.length === 0)
11312
+ const rawKinds = new Set(batch.kinds);
11313
+ if (rawKinds.size === 0)
10764
11314
  return;
10765
11315
  const generation = ++this.#generation;
10766
- this.#logger.verbose(`[hmr] batch generation=${generation} kinds=${kinds.join(",")} files=${batch.files.length}`);
11316
+ const indexSync = await this.#generatedIndexSync.syncForBatch(batch.files);
11317
+ const { files, kinds, expandedBatch, event, hasSyncErrors } = prepareDevWatchBatch({
11318
+ generation,
11319
+ batch,
11320
+ indexSync,
11321
+ changePlanner: this.#changePlanner
11322
+ });
11323
+ const devPlan = event.devPlan;
11324
+ 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)"}`);
11325
+ for (const error of indexSync.errors)
11326
+ this.#logger.error(error);
10767
11327
  if (kinds.includes("code")) {
10768
11328
  const started = Date.now();
10769
11329
  if (kinds.includes("config"))
10770
11330
  this.#discovery = await GraphClientEntryDiscovery.create(this.#app);
10771
11331
  else
10772
- this.#discovery.invalidate?.(batch.files);
11332
+ this.#discovery.invalidate?.(files);
10773
11333
  this.#logger.verbose(`client-entry-discovery ${kinds.includes("config") ? "refreshed" : "invalidated"} (${Date.now() - started}ms)`);
10774
11334
  }
10775
- if (kinds.includes("code") && await this.batchMayChangePageKeys(appDir, batch)) {
11335
+ if (hasSyncErrors) {
11336
+ this.#sendBuildStatus("barrel", { generation, ok: false, files, message: indexSync.errors.join(`
11337
+ `) });
11338
+ process.send?.(event);
11339
+ return;
11340
+ }
11341
+ if (indexSync.changedFiles.length > 0)
11342
+ this.#sendBuildStatus("barrel", { generation, ok: true, files });
11343
+ if (kinds.includes("code") && await this.batchMayChangePageKeys(appDir, expandedBatch)) {
10776
11344
  const started = Date.now();
10777
11345
  await this.#app.getPageKeys({ refresh: true });
10778
11346
  this.#logger.verbose(`pageKeys updated, app pageKeys are refreshed (${Date.now() - started}ms)`);
10779
- } else if (kinds.includes("code") && this.batchTouchesPagesTree(appDir, batch)) {
11347
+ } else if (kinds.includes("code") && this.batchTouchesPagesTree(appDir, expandedBatch)) {
10780
11348
  this.#logger.verbose("pageKeys refresh skipped; changed page source cannot add/remove a route key");
10781
11349
  }
10782
11350
  if (kinds.includes("code") && this.#shouldRebuildCsr()) {
10783
11351
  try {
10784
11352
  const started = Date.now();
10785
11353
  await new CsrArtifactBuilder(this.#app).build();
11354
+ this.#sendBuildStatus("csr", { generation, ok: true, files });
10786
11355
  this.#logger.verbose(`csr-rebundle ok (${Date.now() - started}ms)`);
10787
11356
  } catch (err) {
10788
- this.#logger.error(`csr-rebundle failed: ${err instanceof Error ? err.message : err}`);
11357
+ const message = err instanceof Error ? err.message : String(err);
11358
+ this.#logger.error(`csr-rebundle failed: ${message}`);
11359
+ this.#sendBuildStatus("csr", { generation, ok: false, files, message });
10789
11360
  }
10790
11361
  } else if (kinds.includes("code")) {
10791
11362
  this.#logger.verbose(`csr-rebundle skipped; set AKAN_DEV_CSR_REBUILD=1 to enable per-save CSR rebuilds`);
10792
11363
  }
10793
- const event = { type: "invalidate", kinds, files: batch.files, generation };
10794
11364
  process.send?.(event);
10795
11365
  if (kinds.includes("code")) {
10796
11366
  try {
@@ -10798,15 +11368,18 @@ ${cssText}`).toString(36);
10798
11368
  const next = await new PagesBundleBuilder(this.#app).build();
10799
11369
  process.send?.({
10800
11370
  type: "pages-updated",
10801
- data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles: batch.files }
11371
+ data: { bundlePath: next.bundlePath, buildId: next.buildId, generation, changedFiles: files }
10802
11372
  });
11373
+ this.#sendBuildStatus("pages", { generation, ok: true, files });
10803
11374
  this.#logger.verbose(`pages-rebundle ok buildId=${next.buildId} (${Date.now() - started}ms)`);
10804
11375
  } catch (err) {
10805
- this.#logger.error(`pages-rebundle failed: ${err instanceof Error ? err.message : err}`);
11376
+ const message = err instanceof Error ? err.message : String(err);
11377
+ this.#logger.error(`pages-rebundle failed: ${message}`);
11378
+ this.#sendBuildStatus("pages", { generation, ok: false, files, message });
10806
11379
  }
10807
11380
  }
10808
11381
  if (kinds.includes("code") || kinds.includes("css")) {
10809
- this.scheduleCssRebuild(artifactDir, { refresh: true, generation, changedFiles: batch.files });
11382
+ this.scheduleCssRebuild(artifactDir, { refresh: true, generation, changedFiles: files });
10810
11383
  this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
10811
11384
  }
10812
11385
  }