@akanjs/cli 2.4.1-rc.4 → 2.4.1-rc.5

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.
package/.build-stamp CHANGED
@@ -1 +1 @@
1
- cf0119c268ffd25d9a5f294b48dd17139368114c7beb2e14d958c18a47442b5f
1
+ 97b051f1849d61848768f6c43d1dbcc90953864ce31504f1f376a61adb4481fb
@@ -8,7 +8,8 @@ import {
8
8
  CsrArtifactBuilder,
9
9
  CssCompiler,
10
10
  FontOptimizer,
11
- PagesBundleBuilder
11
+ PagesBundleBuilder,
12
+ SsrBaseArtifactBuilder
12
13
  } from "./index-khzzttv1.js";
13
14
  import"./index-0fn1r7gg.js";
14
15
  import"./index-6pz1j0zj.js";
@@ -29,6 +30,8 @@ class BuildBatch {
29
30
  this.#result = { generation: request.generation, errors: {} };
30
31
  }
31
32
  async run() {
33
+ if (this.#request.needs.includes("base"))
34
+ await this.#buildBase();
32
35
  if (this.#request.needs.includes("csr"))
33
36
  await this.#buildCsr();
34
37
  if (this.#request.needs.includes("pages"))
@@ -52,6 +55,19 @@ class BuildBatch {
52
55
  }
53
56
  });
54
57
  }
58
+ async#buildBase() {
59
+ const started = Date.now();
60
+ try {
61
+ const { artifact, optimizedFonts } = await new SsrBaseArtifactBuilder(this.#app).build();
62
+ this.#result.artifact = artifact;
63
+ this.#result.optimizedFonts = optimizedFonts;
64
+ this.#logger.verbose(`base-artifact ok buildId=${artifact.pagesBundleBuildId} (${Date.now() - started}ms)`);
65
+ } catch (err) {
66
+ const message = err instanceof Error ? err.message : String(err);
67
+ this.#logger.error(`base-artifact failed: ${message}`);
68
+ this.#result.errors.base = message;
69
+ }
70
+ }
55
71
  async#buildCsr() {
56
72
  const started = Date.now();
57
73
  try {
@@ -12,8 +12,7 @@ import {
12
12
  DevChangePlanner,
13
13
  DevGeneratedIndexSync,
14
14
  GraphClientEntryDiscovery,
15
- RouteClientBuilder,
16
- SsrBaseArtifactBuilder
15
+ RouteClientBuilder
17
16
  } from "./index-khzzttv1.js";
18
17
  import {
19
18
  HmrWatcher,
@@ -92,20 +91,36 @@ class BuildBatchRunner {
92
91
  }
93
92
  }
94
93
 
95
- // pkgs/@akanjs/devkit/incrementalBuilder/builderReply.ts
96
- class BuilderReply {
94
+ // pkgs/@akanjs/devkit/incrementalBuilder/builderChannel.ts
95
+ class BuilderChannel {
97
96
  static #flushTimeoutMs = 5000;
98
- static async send(res) {
97
+ static #flushing = new Set;
98
+ static send(message) {
99
99
  const send = process.send;
100
100
  if (!send)
101
- return;
102
- await new Promise((resolve) => {
103
- const timer = setTimeout(resolve, BuilderReply.#flushTimeoutMs);
104
- send.call(process, res, undefined, undefined, () => {
101
+ return Promise.resolve();
102
+ const flushed = new Promise((resolve) => {
103
+ const timer = setTimeout(resolve, BuilderChannel.#flushTimeoutMs);
104
+ send.call(process, message, undefined, undefined, () => {
105
105
  clearTimeout(timer);
106
106
  resolve();
107
107
  });
108
108
  });
109
+ BuilderChannel.#flushing.add(flushed);
110
+ flushed.then(() => BuilderChannel.#flushing.delete(flushed));
111
+ return flushed;
112
+ }
113
+ static emit(message) {
114
+ BuilderChannel.send(message);
115
+ }
116
+ static async drain() {
117
+ let flushed = 0;
118
+ while (BuilderChannel.#flushing.size) {
119
+ const pending = [...BuilderChannel.#flushing];
120
+ flushed += pending.length;
121
+ await Promise.all(pending);
122
+ }
123
+ return flushed;
109
124
  }
110
125
  }
111
126
 
@@ -150,7 +165,7 @@ class IncrementalBuilder {
150
165
  return `${this.#app.cwdPath}/.akan/artifact`;
151
166
  }
152
167
  async handleBuildRoute(msg) {
153
- await this.#enqueueWork(`build-route:${msg.routeId}`, async () => BuilderReply.send(await this.#handleBuildRoute(msg)));
168
+ await this.#enqueueWork(`build-route:${msg.routeId}`, async () => BuilderChannel.send(await this.#handleBuildRoute(msg)));
154
169
  }
155
170
  async#handleBuildRoute(msg) {
156
171
  try {
@@ -189,7 +204,7 @@ class IncrementalBuilder {
189
204
  #sendBuildStatus(phase, { generation, ok, files, message }) {
190
205
  if (typeof generation !== "number")
191
206
  return;
192
- process.send?.({
207
+ BuilderChannel.emit({
193
208
  type: "build-status",
194
209
  data: {
195
210
  generation,
@@ -224,7 +239,7 @@ class IncrementalBuilder {
224
239
  #reportMetrics() {
225
240
  if (!this.#idle || this.#shuttingDown)
226
241
  return;
227
- process.send?.({
242
+ BuilderChannel.emit({
228
243
  type: "builder-metrics",
229
244
  data: { rssBytes: process.memoryUsage.rss(), generation: this.#generation, workCount: this.#workCount }
230
245
  });
@@ -246,7 +261,8 @@ class IncrementalBuilder {
246
261
  await this.#cssRebuildQueue.catch(() => {
247
262
  return;
248
263
  });
249
- this.#logger.info(`drained in ${Date.now() - started}ms; exiting for recycle`);
264
+ const flushed = await BuilderChannel.drain();
265
+ this.#logger.info(`drained in ${Date.now() - started}ms${flushed ? ` after flushing ${flushed} ipc write(s)` : ""}; exiting for recycle`);
250
266
  process.exit(0);
251
267
  }
252
268
  get shuttingDown() {
@@ -357,7 +373,7 @@ class IncrementalBuilder {
357
373
  if (hasSyncErrors) {
358
374
  this.#sendBuildStatus("barrel", { generation, ok: false, files, message: indexSync.errors.join(`
359
375
  `) });
360
- process.send?.(event);
376
+ BuilderChannel.emit(event);
361
377
  return;
362
378
  }
363
379
  if (indexSync.changedFiles.length > 0)
@@ -382,7 +398,7 @@ class IncrementalBuilder {
382
398
  needs.push("pages");
383
399
  needs.push("css");
384
400
  }
385
- process.send?.(event);
401
+ BuilderChannel.emit(event);
386
402
  if (needs.length > 0)
387
403
  await this.#runBatch({ generation, needs, changedFiles: files });
388
404
  else if (kinds.includes("css")) {
@@ -396,14 +412,15 @@ class IncrementalBuilder {
396
412
  changedFiles
397
413
  }) {
398
414
  const started = Date.now();
399
- const result = await this.#batchRunner.run(await this.#batchRequest({ generation, needs, changedFiles }), (msg) => process.send?.(msg));
415
+ const result = await this.#batchRunner.run(await this.#batchRequest({ generation, needs, changedFiles }), (msg) => BuilderChannel.emit(msg));
400
416
  if (result.optimizedFonts)
401
417
  this.#optimizedFonts = result.optimizedFonts;
402
418
  if (result.cssAssets)
403
419
  this.#artifact = { ...this.#artifact, cssAssets: result.cssAssets };
404
420
  if (result.crashed) {
405
421
  for (const need of needs)
406
- this.#sendBuildStatus(need, { generation, ok: false, files: changedFiles, message: result.errors[need] });
422
+ if (need !== "base")
423
+ this.#sendBuildStatus(need, { generation, ok: false, files: changedFiles, message: result.errors[need] });
407
424
  }
408
425
  if (needs.includes("css"))
409
426
  this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
@@ -430,7 +447,7 @@ class IncrementalBuilder {
430
447
  async boot() {
431
448
  if (this.#watch)
432
449
  await this.installWatcher();
433
- process.send?.({ type: "builder-ready" });
450
+ BuilderChannel.emit({ type: "builder-ready" });
434
451
  this.#logger.verbose(`ready (watch=${this.#watch})`);
435
452
  }
436
453
  async announceRecoveredState(changedFiles) {
@@ -442,7 +459,7 @@ class IncrementalBuilder {
442
459
  async announceBootState() {
443
460
  const generation = ++this.#generation;
444
461
  const reason = "builder-recycle";
445
- process.send?.({
462
+ await BuilderChannel.send({
446
463
  type: "pages-updated",
447
464
  data: {
448
465
  bundlePath: this.#artifact.pagesBundlePath,
@@ -457,7 +474,7 @@ class IncrementalBuilder {
457
474
  cssUrl,
458
475
  Buffer.from(await Bun.file(path2.join(this.#artifactDir, cssRelPath)).arrayBuffer()).toString("base64")
459
476
  ])));
460
- process.send?.({
477
+ await BuilderChannel.send({
461
478
  type: "css-updated",
462
479
  data: { cssAssets, cssBase64ByUrl, generation, changedFiles: [], reason }
463
480
  });
@@ -470,12 +487,12 @@ class IncrementalBuilder {
470
487
  const error = result.errors.csr;
471
488
  if (error) {
472
489
  this.#logger.error(`csr-build failed: ${error}`);
473
- await BuilderReply.send({ type: "build-csr-res", id: msg.id, ok: false, error });
490
+ await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: false, error });
474
491
  return;
475
492
  }
476
493
  this.#csrActive = true;
477
494
  this.#logger.info(`csr-build ok on demand (${Date.now() - started}ms); rebuilding CSR on every save now`);
478
- await BuilderReply.send({ type: "build-csr-res", id: msg.id, ok: true });
495
+ await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: true });
479
496
  });
480
497
  }
481
498
  #shouldRebuildCsr() {
@@ -484,10 +501,25 @@ class IncrementalBuilder {
484
501
  static #csrArmedByEnv() {
485
502
  return process.env.AKAN_DEV_CSR_REBUILD === "1";
486
503
  }
487
- static async#buildBootDeps(app) {
488
- const { artifact, optimizedFonts } = await new SsrBaseArtifactBuilder(app).build();
504
+ static async#buildBootDeps(app, runner) {
505
+ const result = await runner.run({
506
+ appName: app.name,
507
+ workspaceRoot: app.workspace.workspaceRoot,
508
+ repoName: app.workspace.repoName,
509
+ generation: 0,
510
+ needs: ["base"],
511
+ changedFiles: [],
512
+ pageKeys: null,
513
+ optimizedFonts: null,
514
+ cssAssets: null,
515
+ artifactDir: path2.resolve(`${app.cwdPath}/.akan/artifact`)
516
+ });
517
+ if (result.errors.base)
518
+ throw new Error(result.errors.base);
519
+ if (!result.artifact || !result.optimizedFonts)
520
+ throw new Error("boot build reported success without an artifact; the build worker likely died");
489
521
  const discovery = await GraphClientEntryDiscovery.create(app);
490
- return { artifact, optimizedFonts, discovery };
522
+ return { artifact: result.artifact, optimizedFonts: result.optimizedFonts, discovery };
491
523
  }
492
524
  async rearmCsrFromEnv() {
493
525
  if (!IncrementalBuilder.#csrArmedByEnv())
@@ -501,18 +533,18 @@ class IncrementalBuilder {
501
533
  this.#logger.verbose("csr-rearm ok; this session had CSR armed before the builder restarted");
502
534
  });
503
535
  }
504
- static #recoverBoot(app, bootError, logger) {
536
+ static #recoverBoot(app, bootError, logger, runner) {
505
537
  const firstMessage = bootError instanceof Error ? bootError.message : String(bootError);
506
538
  logger.error(`boot build failed; entering degraded watch mode until the error is fixed: ${firstMessage}`);
507
539
  let generation = 0;
508
540
  const sendFailure = (files, message) => {
509
- process.send?.({
541
+ BuilderChannel.emit({
510
542
  type: "build-status",
511
543
  data: { generation, phase: "pages", ok: false, files, message: `Boot build failed: ${message}` }
512
544
  });
513
545
  };
514
546
  sendFailure([], firstMessage);
515
- process.send?.({ type: "builder-ready" });
547
+ BuilderChannel.emit({ type: "builder-ready" });
516
548
  return new Promise((resolve, reject) => {
517
549
  (async () => {
518
550
  const roots = await new WatchRootResolver(app).resolve();
@@ -525,7 +557,7 @@ class IncrementalBuilder {
525
557
  try {
526
558
  if (new Set(batch.kinds).has("config"))
527
559
  await app.getConfig({ refresh: true });
528
- const deps = await IncrementalBuilder.#buildBootDeps(app);
560
+ const deps = await IncrementalBuilder.#buildBootDeps(app, runner);
529
561
  const builder = new IncrementalBuilder({ app, watch: true, initialGeneration: generation, ...deps });
530
562
  watcher.stop();
531
563
  logger.info(`boot build recovered generation=${generation}`);
@@ -567,7 +599,7 @@ class IncrementalBuilder {
567
599
  if (msg.type === "build-route") {
568
600
  const error = builder?.shuttingDown ? recyclingError : bootingError;
569
601
  if (!builder || builder.shuttingDown) {
570
- process.send?.({ type: "build-route-res", id: msg.id, ok: false, error });
602
+ BuilderChannel.emit({ type: "build-route-res", id: msg.id, ok: false, error });
571
603
  return;
572
604
  }
573
605
  builder.handleBuildRoute(msg);
@@ -576,7 +608,7 @@ class IncrementalBuilder {
576
608
  if (msg.type === "build-csr") {
577
609
  const error = builder?.shuttingDown ? recyclingError : bootingError;
578
610
  if (!builder || builder.shuttingDown) {
579
- process.send?.({ type: "build-csr-res", id: msg.id, ok: false, error });
611
+ BuilderChannel.emit({ type: "build-csr-res", id: msg.id, ok: false, error });
580
612
  return;
581
613
  }
582
614
  builder.handleBuildCsr(msg);
@@ -587,12 +619,13 @@ class IncrementalBuilder {
587
619
  process.exit(0);
588
620
  });
589
621
  let recoveredFiles = null;
622
+ const bootRunner = new BuildBatchRunner({ workspaceRoot, cwd: app.cwdPath });
590
623
  try {
591
- builder = new IncrementalBuilder({ app, watch, ...await IncrementalBuilder.#buildBootDeps(app) });
624
+ builder = new IncrementalBuilder({ app, watch, ...await IncrementalBuilder.#buildBootDeps(app, bootRunner) });
592
625
  } catch (err) {
593
626
  if (!watch)
594
627
  throw err;
595
- const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger);
628
+ const recovered = await IncrementalBuilder.#recoverBoot(app, err, logger, bootRunner);
596
629
  builder = recovered.builder;
597
630
  recoveredFiles = recovered.changedFiles;
598
631
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.4.1-rc.4",
3
+ "version": "2.4.1-rc.5",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -34,7 +34,7 @@
34
34
  "@langchain/openai": "^1.4.6",
35
35
  "@tailwindcss/node": "^4.3.0",
36
36
  "@trapezedev/project": "^7.1.4",
37
- "akanjs": "2.4.1-rc.4",
37
+ "akanjs": "2.4.1-rc.5",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "daisyui": "5.5.23",