akanjs 2.0.5 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/cli/application/application.runner.ts +1 -1
  2. package/cli/build.ts +2 -1
  3. package/cli/cloud/cloud.runner.ts +7 -8
  4. package/cli/index.js +176 -43
  5. package/cli/library/library.runner.ts +2 -2
  6. package/cli/module/module.runner.ts +2 -2
  7. package/cli/npmRegistry.ts +13 -0
  8. package/cli/openBrowser.ts +15 -0
  9. package/cli/pluralizeName.ts +5 -0
  10. package/cli/scalar/scalar.prompt.ts +2 -2
  11. package/cli/scalar/scalar.runner.ts +2 -2
  12. package/cli/semver.ts +18 -0
  13. package/cli/templates/lib/sig.ts +2 -2
  14. package/cli/workspace/workspace.runner.ts +3 -3
  15. package/client/cookie.ts +10 -15
  16. package/common/index.ts +1 -0
  17. package/common/jwtDecode.ts +17 -0
  18. package/devkit/akanApp/akanApp.host.ts +46 -9
  19. package/devkit/akanConfig/akanConfig.ts +2 -1
  20. package/devkit/incrementalBuilder/incrementalBuilder.host.ts +83 -9
  21. package/document/dataLoader.ts +140 -6
  22. package/document/database.ts +1 -1
  23. package/package.json +7 -13
  24. package/server/akanApp.ts +197 -32
  25. package/server/di/diLifecycle.ts +1 -1
  26. package/server/proxy/localeWebProxy.ts +29 -12
  27. package/service/serviceModule.ts +1 -6
  28. package/signal/base.signal.ts +1 -1
  29. package/signal/signalRegistry.ts +35 -10
  30. package/types/cli/npmRegistry.d.ts +1 -0
  31. package/types/cli/openBrowser.d.ts +1 -0
  32. package/types/cli/pluralizeName.d.ts +1 -0
  33. package/types/cli/semver.d.ts +1 -0
  34. package/types/client/cookie.d.ts +6 -1
  35. package/types/common/index.d.ts +1 -0
  36. package/types/common/jwtDecode.d.ts +2 -0
  37. package/types/devkit/incrementalBuilder/incrementalBuilder.host.d.ts +9 -5
  38. package/types/document/dataLoader.d.ts +21 -2
  39. package/types/document/database.d.ts +1 -1
  40. package/types/service/serviceModule.d.ts +1 -1
  41. package/types/signal/signalRegistry.d.ts +25 -4
  42. package/ui/Signal/Doc.tsx +2 -3
@@ -25,8 +25,8 @@ import {
25
25
  type TypecheckOptions,
26
26
  type Workspace,
27
27
  } from "akanjs/devkit";
28
- import openBrowser from "open";
29
28
  import ora from "ora";
29
+ import { openBrowser } from "../openBrowser";
30
30
 
31
31
  export class ApplicationRunner extends runner("application") {
32
32
  async createApplication(appName: string, workspace: Workspace, libs: string[] = []) {
package/cli/build.ts CHANGED
@@ -7,13 +7,14 @@ const OUT_DIR = process.env.DIST_DIR ?? `${WORKSPACE_ROOT}/dist/pkgs/akanjs`;
7
7
 
8
8
  const build = async () => {
9
9
  try {
10
+ const packageJson = await Bun.file(`${PACKAGE_DIR}/package.json`).json();
10
11
  await $`rm -rf ${OUT_DIR}/cli`;
11
12
  await Bun.build({
12
13
  entrypoints: [`${CLI_DIR}/index.ts`],
13
14
  splitting: false,
14
15
  target: "bun",
15
16
  outdir: `${OUT_DIR}/cli`,
16
- external: Object.keys((await Bun.file(`${PACKAGE_DIR}/package.json`).json()).dependencies ?? {}),
17
+ external: Object.keys({ ...packageJson.dependencies, ...packageJson.peerDependencies }),
17
18
  });
18
19
  await $`cp -R ${CLI_DIR}/templates ${OUT_DIR}/cli/templates`;
19
20
  } catch (error) {
@@ -11,10 +11,9 @@ import {
11
11
  type Workspace,
12
12
  } from "akanjs/devkit";
13
13
  import chalk from "chalk";
14
- import latestVersion from "latest-version";
15
- import open from "open";
16
14
  import * as QRcode from "qrcode";
17
- import { v4 as uuidv4 } from "uuid";
15
+ import { getLatestPackageVersion } from "../npmRegistry";
16
+ import { openBrowser } from "../openBrowser";
18
17
 
19
18
  export class CloudRunner extends runner("cloud") {
20
19
  async login() {
@@ -24,7 +23,7 @@ export class CloudRunner extends runner("cloud") {
24
23
  Logger.rawLog(chalk.green(`\n✓ Already logged in akan cloud as ${self.nickname}\n`));
25
24
  return true;
26
25
  }
27
- const remoteId = uuidv4();
26
+ const remoteId = crypto.randomUUID();
28
27
  const signinUrl = `${akanCloudUrl}/signin?remoteId=${remoteId}`;
29
28
 
30
29
  Logger.rawLog(chalk.bold(`\n${chalk.green("➤")} Authentication Required`));
@@ -39,7 +38,7 @@ export class CloudRunner extends runner("cloud") {
39
38
  });
40
39
  });
41
40
  Logger.rawLog(qrcode);
42
- await open(signinUrl);
41
+ await openBrowser(signinUrl);
43
42
  Logger.rawLog(chalk.dim("Opening browser..."));
44
43
  } catch {
45
44
  Logger.rawLog(chalk.yellow("Could not open browser. Please visit the URL manually."));
@@ -96,13 +95,13 @@ export class CloudRunner extends runner("cloud") {
96
95
  const tag = isOfficialRelease ? "latest" : (patchVersion.split("-").at(1) ?? "dev");
97
96
  const getNextVersion = async (prefix: string, tag: string) => {
98
97
  try {
99
- const latestPublishedVersion = await latestVersion("akanjs", { version: tag });
98
+ const latestPublishedVersion = await getLatestPackageVersion("akanjs", tag);
100
99
  const latestPatch = latestPublishedVersion.startsWith(prefix)
101
100
  ? parseInt(latestPublishedVersion.split(".").at(-1) ?? "-1")
102
101
  : -1;
103
102
  const nextVersion = `${prefix}.${latestPatch + 1}`;
104
103
  return { nextVersion, latestPublishedVersion };
105
- } catch (e) {
104
+ } catch {
106
105
  return { nextVersion: `${prefix}.0`, latestPublishedVersion: null };
107
106
  }
108
107
  };
@@ -147,7 +146,7 @@ export class CloudRunner extends runner("cloud") {
147
146
  ]);
148
147
  }
149
148
  async #updateAkanPkgs(workspace: Workspace, tag: string = "latest") {
150
- const latestPublishedVersion = await latestVersion("akanjs", { version: tag });
149
+ const latestPublishedVersion = await getLatestPackageVersion("akanjs", tag);
151
150
  const rootPackageJson = await workspace.getPackageJson();
152
151
  if (!rootPackageJson.dependencies) throw new Error("No dependencies found in package.json");
153
152
  if (rootPackageJson.dependencies.akanjs) rootPackageJson.dependencies.akanjs = latestPublishedVersion;
package/cli/index.js CHANGED
@@ -2203,7 +2203,7 @@ var require_source_map_support = __commonJS((exports, module) => {
2203
2203
  });
2204
2204
 
2205
2205
  var require_typescript = __commonJS((exports, module) => {
2206
- var __dirname = "/Users/kangminseon/github/Ieading-flight-guidance/node_modules/typescript/lib", __filename = "/Users/kangminseon/github/Ieading-flight-guidance/node_modules/typescript/lib/typescript.js";
2206
+ var __dirname = "/Users/kangminseon/Desktop/Mintaka_Github/bunkan/node_modules/typescript/lib", __filename = "/Users/kangminseon/Desktop/Mintaka_Github/bunkan/node_modules/typescript/lib/typescript.js";
2207
2207
 
2208
2208
  var ts = {};
2209
2209
  ((module2) => {
@@ -176299,6 +176299,8 @@ var builderMsgTypeSet = new Set([
176299
176299
  ]);
176300
176300
 
176301
176301
  class IncrementalBuilderHost {
176302
+ static #restartBaseDelayMs = 1000;
176303
+ static #restartMaxDelayMs = 30000;
176302
176304
  logger = new Logger("IncrementalBuilderHost");
176303
176305
  entry;
176304
176306
  env;
@@ -176306,48 +176308,112 @@ class IncrementalBuilderHost {
176306
176308
  ready = false;
176307
176309
  #onMessage;
176308
176310
  #proc = null;
176311
+ #status = "stopped";
176312
+ #restartAttempts = 0;
176313
+ #restartTimer = null;
176314
+ #manualStop = false;
176315
+ #startOptions = {};
176309
176316
  constructor({ app, entry, env, onMessage }) {
176310
176317
  this.app = app;
176311
176318
  this.entry = entry;
176312
176319
  this.env = env;
176313
176320
  this.#onMessage = onMessage;
176314
176321
  }
176315
- start({ onExit, onReady }) {
176322
+ get status() {
176323
+ return this.#status;
176324
+ }
176325
+ start(options = {}) {
176316
176326
  if (this.#proc)
176317
176327
  this.stop();
176318
- this.#proc = Bun.spawn(["bun", this.entry], {
176328
+ this.#manualStop = false;
176329
+ this.#startOptions = options;
176330
+ this.#spawn(false);
176331
+ return this;
176332
+ }
176333
+ #spawn(isRestart) {
176334
+ this.#status = isRestart ? "restarting" : "starting";
176335
+ this.ready = false;
176336
+ let proc;
176337
+ proc = Bun.spawn(["bun", this.entry], {
176319
176338
  cwd: this.app.cwdPath,
176320
176339
  env: { ...this.env, AKAN_WATCH: "1" },
176321
176340
  stdio: ["ignore", "inherit", "inherit"],
176322
176341
  ipc: (msg) => {
176342
+ if (this.#proc !== proc)
176343
+ return;
176323
176344
  if (!msg || typeof msg !== "object")
176324
176345
  return;
176325
176346
  if (builderMsgTypeSet.has(msg.type))
176326
176347
  this.#onMessage(msg);
176327
176348
  if (msg.type === "builder-ready" && !this.ready) {
176328
176349
  this.ready = true;
176329
- onReady?.();
176350
+ this.#status = "ready";
176351
+ this.#restartAttempts = 0;
176352
+ if (isRestart)
176353
+ this.#startOptions.onRestartReady?.();
176354
+ else
176355
+ this.#startOptions.onReady?.();
176330
176356
  }
176331
176357
  },
176332
176358
  serialization: "advanced",
176333
176359
  onExit: () => {
176334
- onExit?.();
176360
+ if (this.#proc !== proc)
176361
+ return;
176362
+ this.#proc = null;
176363
+ const wasReady = this.ready;
176364
+ this.ready = false;
176365
+ if (this.#manualStop || this.#status === "stopped")
176366
+ return;
176367
+ if (!wasReady) {
176368
+ this.#status = "stopped";
176369
+ this.#startOptions.onExit?.();
176370
+ return;
176371
+ }
176372
+ this.#scheduleRestart();
176335
176373
  }
176336
176374
  });
176337
- this.logger.verbose(`builder spawned pid=${this.#proc.pid} entry=${this.entry}`);
176338
- return this;
176375
+ this.#proc = proc;
176376
+ this.logger.verbose(`builder spawned pid=${proc.pid} entry=${this.entry}${isRestart ? " restart=1" : ""}`);
176377
+ }
176378
+ #scheduleRestart() {
176379
+ if (this.#manualStop || this.#restartTimer)
176380
+ return;
176381
+ this.#status = "restarting";
176382
+ const attempt = this.#restartAttempts;
176383
+ const delay = Math.min(IncrementalBuilderHost.#restartBaseDelayMs * 2 ** attempt, IncrementalBuilderHost.#restartMaxDelayMs);
176384
+ this.#restartAttempts = attempt + 1;
176385
+ this.logger.warn(`builder exited after ready; restarting in ${delay}ms (attempt ${this.#restartAttempts})`);
176386
+ this.#restartTimer = setTimeout(() => {
176387
+ this.#restartTimer = null;
176388
+ if (this.#manualStop)
176389
+ return;
176390
+ this.#spawn(true);
176391
+ }, delay);
176339
176392
  }
176340
176393
  send(message) {
176341
- if (this.#proc)
176394
+ if (!this.#proc || this.#status !== "ready") {
176395
+ this.logger.warn(`incrementalBuilderHost is ${this.#status}; cannot send ${message.type}`);
176396
+ return false;
176397
+ }
176398
+ try {
176342
176399
  this.#proc.send(message);
176343
- else
176344
- this.logger.warn("incrementalBuilderHost is not running");
176400
+ return true;
176401
+ } catch (error) {
176402
+ this.logger.warn(`failed to send ${message.type} to builder: ${error instanceof Error ? error.message : String(error)}`);
176403
+ return false;
176404
+ }
176345
176405
  }
176346
176406
  stop() {
176407
+ this.#manualStop = true;
176408
+ if (this.#restartTimer) {
176409
+ clearTimeout(this.#restartTimer);
176410
+ this.#restartTimer = null;
176411
+ }
176347
176412
  if (this.#proc)
176348
176413
  this.#proc.kill();
176349
176414
  this.#proc = null;
176350
176415
  this.ready = false;
176416
+ this.#status = "stopped";
176351
176417
  }
176352
176418
  static async create(app, env, onMessage) {
176353
176419
  const candidates = [
@@ -176365,6 +176431,8 @@ class IncrementalBuilderHost {
176365
176431
  var backendMsgTypeSet = new Set(["build-route"]);
176366
176432
  var BACKEND_RESTART_DEBOUNCE_MS = 120;
176367
176433
  var BACKEND_GRACEFUL_TIMEOUT_MS = 3000;
176434
+ var BACKEND_RECOVERY_BASE_DELAY_MS = 1000;
176435
+ var BACKEND_RECOVERY_MAX_DELAY_MS = 30000;
176368
176436
  var BUILDER_READY_TIMEOUT_MS = 15000;
176369
176437
  var BUILDER_START_MAX_ATTEMPTS = 3;
176370
176438
  var SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
@@ -176488,6 +176556,8 @@ class AkanAppHost {
176488
176556
  #backendReady = false;
176489
176557
  #plannedBackendStops = new WeakSet;
176490
176558
  #restartTimer = null;
176559
+ #backendRecoveryTimer = null;
176560
+ #backendRecoveryAttempts = 0;
176491
176561
  #restartFiles = new Set;
176492
176562
  #latestPagesUpdated = null;
176493
176563
  #latestCssUpdated = null;
@@ -176518,6 +176588,10 @@ class AkanAppHost {
176518
176588
  clearTimeout(this.#restartTimer);
176519
176589
  this.#restartTimer = null;
176520
176590
  }
176591
+ if (this.#backendRecoveryTimer) {
176592
+ clearTimeout(this.#backendRecoveryTimer);
176593
+ this.#backendRecoveryTimer = null;
176594
+ }
176521
176595
  await this.#stopBackend();
176522
176596
  this.#stopBuilder();
176523
176597
  return this;
@@ -176542,6 +176616,7 @@ class AkanAppHost {
176542
176616
  return;
176543
176617
  if (msg.type === "backend-ready") {
176544
176618
  this.#backendReady = true;
176619
+ this.#backendRecoveryAttempts = 0;
176545
176620
  this.logger.verbose(`backend ready pid=${msg.pid}`);
176546
176621
  this.#replayBuilderState();
176547
176622
  return;
@@ -176558,7 +176633,7 @@ class AkanAppHost {
176558
176633
  this.#plannedBackendStops.delete(backend);
176559
176634
  return;
176560
176635
  }
176561
- this.#stopBuilder();
176636
+ this.#scheduleBackendRecovery("backend-exit");
176562
176637
  }
176563
176638
  });
176564
176639
  this.#backend = backend;
@@ -176606,6 +176681,10 @@ class AkanAppHost {
176606
176681
  #scheduleBackendRestart(files) {
176607
176682
  for (const file of files)
176608
176683
  this.#restartFiles.add(file);
176684
+ if (this.#backendRecoveryTimer) {
176685
+ clearTimeout(this.#backendRecoveryTimer);
176686
+ this.#backendRecoveryTimer = null;
176687
+ }
176609
176688
  if (this.#restartTimer)
176610
176689
  clearTimeout(this.#restartTimer);
176611
176690
  this.#restartTimer = setTimeout(() => {
@@ -176617,9 +176696,27 @@ class AkanAppHost {
176617
176696
  }
176618
176697
  async#restartBackend(files) {
176619
176698
  this.logger.verbose(`[backend-reload] restarting backend for ${files.length} file(s)`);
176699
+ this.#backendRecoveryAttempts = 0;
176620
176700
  await Promise.all([this.#stopBackend(), this.#backendGraph.refresh()]);
176621
176701
  this.#startBackend();
176622
176702
  }
176703
+ #scheduleBackendRecovery(reason) {
176704
+ if (this.#backendRecoveryTimer || this.#backend)
176705
+ return;
176706
+ const attempt = this.#backendRecoveryAttempts;
176707
+ const delay = Math.min(BACKEND_RECOVERY_BASE_DELAY_MS * 2 ** attempt, BACKEND_RECOVERY_MAX_DELAY_MS);
176708
+ this.#backendRecoveryAttempts = attempt + 1;
176709
+ this.logger.warn(`[backend-recovery] backend exited unexpectedly (${reason}); restarting in ${delay}ms (attempt ${this.#backendRecoveryAttempts})`);
176710
+ this.#backendRecoveryTimer = setTimeout(() => {
176711
+ this.#backendRecoveryTimer = null;
176712
+ if (this.#backend)
176713
+ return;
176714
+ this.#backendGraph.refresh().finally(() => {
176715
+ if (!this.#backend)
176716
+ this.#startBackend();
176717
+ });
176718
+ }, delay);
176719
+ }
176623
176720
  #enqueueBuilderMessage(message) {
176624
176721
  this.#builderMessageQueue = this.#builderMessageQueue.then(() => this.#handleBuilderMessage(message)).catch((err) => {
176625
176722
  this.logger.warn(`failed to handle builder message: ${err instanceof Error ? err.message : String(err)}`);
@@ -176688,7 +176785,6 @@ class AkanAppHost {
176688
176785
  if (!this.#builder)
176689
176786
  throw new Error("Builder Not Found");
176690
176787
  let settled = false;
176691
- let ready = false;
176692
176788
  const settle = (fn) => {
176693
176789
  if (settled)
176694
176790
  return;
@@ -176701,24 +176797,31 @@ class AkanAppHost {
176701
176797
  }, BUILDER_READY_TIMEOUT_MS);
176702
176798
  this.#builder.start({
176703
176799
  onExit: () => {
176704
- if (settled && ready) {
176705
- this.#stopBackend();
176706
- return;
176707
- }
176708
176800
  settle(() => reject(new Error(`[cli] builder exited before emitting builder-ready (attempt ${attempt})`)));
176709
176801
  },
176710
176802
  onReady: () => {
176711
- ready = true;
176712
176803
  settle(resolve);
176804
+ },
176805
+ onRestartReady: () => {
176806
+ this.logger.verbose("[builder-recovery] builder ready after restart; replaying latest state");
176807
+ this.#replayBuilderState();
176713
176808
  }
176714
176809
  });
176715
176810
  });
176716
176811
  }
176717
176812
  #sendToBuilder(message) {
176718
- if (this.#builder)
176719
- this.#builder.send(message);
176720
- else
176721
- this.logger.warn("akanAppHost is not running");
176813
+ if (this.#builder?.send(message))
176814
+ return;
176815
+ if (message.type === "build-route") {
176816
+ this.#sendToBackend({
176817
+ type: "build-route-res",
176818
+ id: message.id,
176819
+ ok: false,
176820
+ error: `builder is ${this.#builder?.status ?? "stopped"}; reload after the builder is ready`
176821
+ });
176822
+ return;
176823
+ }
176824
+ this.logger.warn("akanAppHost builder is not running");
176722
176825
  }
176723
176826
  #stopBuilder() {
176724
176827
  if (!this.#builder)
@@ -182080,7 +182183,20 @@ var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
182080
182183
 
182081
182184
  import { select as select6 } from "@inquirer/prompts";
182082
182185
 
182083
- import { compareVersions } from "compare-versions";
182186
+ function parseVersion(version) {
182187
+ return version.replace(/^[^\d]*/, "").split(/[.-]/).map((part) => Number.parseInt(part, 10)).map((part) => Number.isFinite(part) ? part : 0);
182188
+ }
182189
+ function compareSemver(a, b) {
182190
+ const left = parseVersion(a);
182191
+ const right = parseVersion(b);
182192
+ const length = Math.max(left.length, right.length);
182193
+ for (let i = 0;i < length; i++) {
182194
+ const diff = (left[i] ?? 0) - (right[i] ?? 0);
182195
+ if (diff !== 0)
182196
+ return diff > 0 ? 1 : -1;
182197
+ }
182198
+ return 0;
182199
+ }
182084
182200
 
182085
182201
  class LibraryRunner extends runner("library") {
182086
182202
  async createLibrary(libName, workspace) {
@@ -182124,7 +182240,7 @@ class LibraryRunner extends runner("library") {
182124
182240
  const allDependencies = Object.fromEntries(Object.keys({ ...libDependencies, ...rootDependencies }).map((dep) => {
182125
182241
  const libVersion = libDependencies[dep] ?? "0.0.0";
182126
182242
  const rootVersion = rootDependencies[dep] ?? "0.0.0";
182127
- const newerVersion = compareVersions(rootVersion, libVersion) > 0 ? rootVersion : libVersion;
182243
+ const newerVersion = compareSemver(rootVersion, libVersion) > 0 ? rootVersion : libVersion;
182128
182244
  return [dep, newerVersion];
182129
182245
  }));
182130
182246
  Object.keys(allDependencies).sort().forEach((dep) => {
@@ -182180,9 +182296,17 @@ import { StringOutputParser } from "@langchain/core/output_parsers";
182180
182296
  import { PromptTemplate as PromptTemplate2 } from "@langchain/core/prompts";
182181
182297
  import { RunnableSequence as RunnableSequence2 } from "@langchain/core/runnables";
182182
182298
  import { ChatOpenAI as ChatOpenAI3 } from "@langchain/openai";
182183
- import openBrowser from "open";
182184
182299
  import ora3 from "ora";
182185
182300
 
182301
+ import { spawn as spawn2 } from "child_process";
182302
+ function openBrowser(url) {
182303
+ const command3 = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
182304
+ const child = spawn2(command3[0], command3.slice(1), { detached: true, stdio: "ignore" });
182305
+ child.on("error", () => {});
182306
+ child.unref();
182307
+ return Promise.resolve();
182308
+ }
182309
+
182186
182310
  class ApplicationRunner extends runner("application") {
182187
182311
  async createApplication(appName, workspace, libs = []) {
182188
182312
  await workspace.applyTemplate({
@@ -182877,10 +183001,19 @@ class PackageScript extends script("package", [PackageRunner]) {
182877
183001
 
182878
183002
  import { confirm as confirm3 } from "@inquirer/prompts";
182879
183003
  import chalk7 from "chalk";
182880
- import latestVersion from "latest-version";
182881
- import open from "open";
182882
183004
  import * as QRcode from "qrcode";
182883
- import { v4 as uuidv4 } from "uuid";
183005
+
183006
+ async function getLatestPackageVersion(packageName, tag = "latest") {
183007
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace(/^%40/, "@")}`;
183008
+ const res = await fetch(url);
183009
+ if (!res.ok)
183010
+ throw new Error(`Failed to fetch ${packageName} metadata from npm registry`);
183011
+ const metadata = await res.json();
183012
+ const version = metadata["dist-tags"]?.[tag];
183013
+ if (!version)
183014
+ throw new Error(`No npm dist-tag "${tag}" found for ${packageName}`);
183015
+ return version;
183016
+ }
182884
183017
 
182885
183018
  class CloudRunner extends runner("cloud") {
182886
183019
  async login() {
@@ -182892,7 +183025,7 @@ class CloudRunner extends runner("cloud") {
182892
183025
  `));
182893
183026
  return true;
182894
183027
  }
182895
- const remoteId = uuidv4();
183028
+ const remoteId = crypto.randomUUID();
182896
183029
  const signinUrl = `${akanCloudUrl}/signin?remoteId=${remoteId}`;
182897
183030
  Logger.rawLog(chalk7.bold(`
182898
183031
  ${chalk7.green("\u27A4")} Authentication Required`));
@@ -182908,7 +183041,7 @@ ${chalk7.green("\u27A4")} Authentication Required`));
182908
183041
  });
182909
183042
  });
182910
183043
  Logger.rawLog(qrcode);
182911
- await open(signinUrl);
183044
+ await openBrowser(signinUrl);
182912
183045
  Logger.rawLog(chalk7.dim("Opening browser..."));
182913
183046
  } catch {
182914
183047
  Logger.rawLog(chalk7.yellow("Could not open browser. Please visit the URL manually."));
@@ -182969,11 +183102,11 @@ ${chalk7.green("\u27A4")} Authentication Required`));
182969
183102
  const tag = isOfficialRelease ? "latest" : patchVersion.split("-").at(1) ?? "dev";
182970
183103
  const getNextVersion = async (prefix, tag2) => {
182971
183104
  try {
182972
- const latestPublishedVersion2 = await latestVersion("akanjs", { version: tag2 });
183105
+ const latestPublishedVersion2 = await getLatestPackageVersion("akanjs", tag2);
182973
183106
  const latestPatch = latestPublishedVersion2.startsWith(prefix) ? parseInt(latestPublishedVersion2.split(".").at(-1) ?? "-1") : -1;
182974
183107
  const nextVersion2 = `${prefix}.${latestPatch + 1}`;
182975
183108
  return { nextVersion: nextVersion2, latestPublishedVersion: latestPublishedVersion2 };
182976
- } catch (e) {
183109
+ } catch {
182977
183110
  return { nextVersion: `${prefix}.0`, latestPublishedVersion: null };
182978
183111
  }
182979
183112
  };
@@ -183012,7 +183145,7 @@ ${chalk7.green("\u27A4")} Authentication Required`));
183012
183145
  ]);
183013
183146
  }
183014
183147
  async#updateAkanPkgs(workspace, tag = "latest") {
183015
- const latestPublishedVersion = await latestVersion("akanjs", { version: tag });
183148
+ const latestPublishedVersion = await getLatestPackageVersion("akanjs", tag);
183016
183149
  const rootPackageJson = await workspace.getPackageJson();
183017
183150
  if (!rootPackageJson.dependencies)
183018
183151
  throw new Error("No dependencies found in package.json");
@@ -183685,7 +183818,13 @@ var requestUnit = ({
183685
183818
 
183686
183819
  `;
183687
183820
 
183688
- import pluralize from "pluralize";
183821
+ function pluralizeName(name) {
183822
+ if (/[^aeiou]y$/i.test(name))
183823
+ return `${name.slice(0, -1)}ies`;
183824
+ if (/(s|x|z|ch|sh)$/i.test(name))
183825
+ return `${name}es`;
183826
+ return `${name}s`;
183827
+ }
183689
183828
 
183690
183829
  class ModuleRunner extends runner("module") {
183691
183830
  async createModule(workspace, sysType, sysName, moduleName, description) {}
@@ -183706,7 +183845,7 @@ class ModuleRunner extends runner("module") {
183706
183845
  };
183707
183846
  }
183708
183847
  async createModuleTemplate(module) {
183709
- const names = pluralize(module.name);
183848
+ const names = pluralizeName(module.name);
183710
183849
  await module.applyTemplate({
183711
183850
  basePath: `.`,
183712
183851
  template: "module",
@@ -183916,11 +184055,7 @@ class PageCommand extends command("page", [PageScript], ({ public: target }) =>
183916
184055
  })) {
183917
184056
  }
183918
184057
 
183919
- import pluralize3 from "pluralize";
183920
-
183921
184058
  import { input as input5 } from "@inquirer/prompts";
183922
- import pluralize2 from "pluralize";
183923
-
183924
184059
  class ScalarPrompt extends Prompter {
183925
184060
  sys;
183926
184061
  name;
@@ -183940,7 +184075,7 @@ class ScalarPrompt extends Prompter {
183940
184075
  await this.sys.applyTemplate({
183941
184076
  basePath: "./lib/__scalar",
183942
184077
  template: "__scalar",
183943
- dict: { model: this.name, models: pluralize2(this.name), sysName: this.sys.name }
184078
+ dict: { model: this.name, models: pluralizeName(this.name), sysName: this.sys.name }
183944
184079
  });
183945
184080
  const boilerplate = await this.sys.readFile(`lib/__scalar/${this.name}/${this.name}.constant.ts`);
183946
184081
  return await this.#requestConstant({
@@ -184044,7 +184179,7 @@ class ScalarRunner extends runner("scalar") {
184044
184179
  await sys3.applyTemplate({
184045
184180
  basePath: "./lib/__scalar",
184046
184181
  template: "__scalar",
184047
- dict: { model: scalarName, models: pluralize3(scalarName), sysName: sys3.name },
184182
+ dict: { model: scalarName, models: pluralizeName(scalarName), sysName: sys3.name },
184048
184183
  overwrite: false
184049
184184
  });
184050
184185
  }
@@ -184100,15 +184235,13 @@ class ScalarCommand extends command("scalar", [ScalarScript], ({ public: target
184100
184235
  import path38 from "path";
184101
184236
 
184102
184237
  import path37 from "path";
184103
- import latestVersion2 from "latest-version";
184104
-
184105
184238
  class WorkspaceRunner extends runner("workspace") {
184106
184239
  async createWorkspace(repoName, appName, { dirname: dirname3 = ".", init = true, akanVersion }) {
184107
184240
  const cwdPath = process.cwd();
184108
184241
  const workspaceRoot = path37.join(cwdPath, dirname3, repoName);
184109
184242
  const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot, repoName });
184110
184243
  const templateSpinner = workspace.spinning(`Creating workspace template files in ${dirname3}/${repoName}...`);
184111
- const latestTypesBunVersion = await latestVersion2("@types/bun");
184244
+ const latestTypesBunVersion = await getLatestPackageVersion("@types/bun");
184112
184245
  await workspace.applyTemplate({
184113
184246
  basePath: ".",
184114
184247
  template: "workspaceRoot",
@@ -1,5 +1,5 @@
1
1
  import { type Lib, LibExecutor, runner, type Workspace } from "akanjs/devkit";
2
- import { compareVersions } from "compare-versions";
2
+ import { compareSemver } from "../semver";
3
3
 
4
4
  export class LibraryRunner extends runner("library") {
5
5
  async createLibrary(libName: string, workspace: Workspace) {
@@ -44,7 +44,7 @@ export class LibraryRunner extends runner("library") {
44
44
  Object.keys({ ...libDependencies, ...rootDependencies }).map((dep) => {
45
45
  const libVersion = libDependencies[dep] ?? "0.0.0";
46
46
  const rootVersion = rootDependencies[dep] ?? "0.0.0";
47
- const newerVersion = compareVersions(rootVersion, libVersion) > 0 ? rootVersion : libVersion;
47
+ const newerVersion = compareSemver(rootVersion, libVersion) > 0 ? rootVersion : libVersion;
48
48
  return [dep, newerVersion];
49
49
  }),
50
50
  );
@@ -1,6 +1,6 @@
1
1
  import { capitalize } from "akanjs/common";
2
2
  import { type Module, runner, type Workspace } from "akanjs/devkit";
3
- import pluralize from "pluralize";
3
+ import { pluralizeName } from "../pluralizeName";
4
4
 
5
5
  export class ModuleRunner extends runner("module") {
6
6
  async createModule(
@@ -30,7 +30,7 @@ export class ModuleRunner extends runner("module") {
30
30
  }
31
31
 
32
32
  async createModuleTemplate(module: Module) {
33
- const names = pluralize(module.name);
33
+ const names = pluralizeName(module.name);
34
34
  await module.applyTemplate({
35
35
  basePath: `.`,
36
36
  template: "module",
@@ -0,0 +1,13 @@
1
+ interface NpmPackageMetadata {
2
+ "dist-tags"?: Record<string, string>;
3
+ }
4
+
5
+ export async function getLatestPackageVersion(packageName: string, tag = "latest"): Promise<string> {
6
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace(/^%40/, "@")}`;
7
+ const res = await fetch(url);
8
+ if (!res.ok) throw new Error(`Failed to fetch ${packageName} metadata from npm registry`);
9
+ const metadata = (await res.json()) as NpmPackageMetadata;
10
+ const version = metadata["dist-tags"]?.[tag];
11
+ if (!version) throw new Error(`No npm dist-tag "${tag}" found for ${packageName}`);
12
+ return version;
13
+ }
@@ -0,0 +1,15 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ export function openBrowser(url: string): Promise<void> {
4
+ const command =
5
+ process.platform === "darwin"
6
+ ? ["open", url]
7
+ : process.platform === "win32"
8
+ ? ["cmd", "/c", "start", "", url]
9
+ : ["xdg-open", url];
10
+ const child = spawn(command[0], command.slice(1), { detached: true, stdio: "ignore" });
11
+ child.on("error", () => {
12
+ });
13
+ child.unref();
14
+ return Promise.resolve();
15
+ }
@@ -0,0 +1,5 @@
1
+ export function pluralizeName(name: string): string {
2
+ if (/[^aeiou]y$/i.test(name)) return `${name.slice(0, -1)}ies`;
3
+ if (/(s|x|z|ch|sh)$/i.test(name)) return `${name}es`;
4
+ return `${name}s`;
5
+ }
@@ -1,6 +1,6 @@
1
1
  import { input } from "@inquirer/prompts";
2
2
  import { type FileContent, Prompter, type Sys } from "akanjs/devkit";
3
- import pluralize from "pluralize";
3
+ import { pluralizeName } from "../pluralizeName";
4
4
 
5
5
  export class ScalarPrompt extends Prompter {
6
6
  constructor(
@@ -20,7 +20,7 @@ export class ScalarPrompt extends Prompter {
20
20
  await this.sys.applyTemplate({
21
21
  basePath: "./lib/__scalar",
22
22
  template: "__scalar",
23
- dict: { model: this.name, models: pluralize(this.name), sysName: this.sys.name },
23
+ dict: { model: this.name, models: pluralizeName(this.name), sysName: this.sys.name },
24
24
  });
25
25
  const boilerplate = await this.sys.readFile(`lib/__scalar/${this.name}/${this.name}.constant.ts`);
26
26
  return await this.#requestConstant({
@@ -1,5 +1,5 @@
1
1
  import { AiSession, runner, type Sys } from "akanjs/devkit";
2
- import pluralize from "pluralize";
2
+ import { pluralizeName } from "../pluralizeName";
3
3
 
4
4
  import { ScalarPrompt } from "./scalar.prompt";
5
5
 
@@ -8,7 +8,7 @@ export class ScalarRunner extends runner("scalar") {
8
8
  await sys.applyTemplate({
9
9
  basePath: "./lib/__scalar",
10
10
  template: "__scalar",
11
- dict: { model: scalarName, models: pluralize(scalarName), sysName: sys.name },
11
+ dict: { model: scalarName, models: pluralizeName(scalarName), sysName: sys.name },
12
12
  overwrite: false,
13
13
  });
14
14
  }
package/cli/semver.ts ADDED
@@ -0,0 +1,18 @@
1
+ function parseVersion(version: string): number[] {
2
+ return version
3
+ .replace(/^[^\d]*/, "")
4
+ .split(/[.-]/)
5
+ .map((part) => Number.parseInt(part, 10))
6
+ .map((part) => (Number.isFinite(part) ? part : 0));
7
+ }
8
+
9
+ export function compareSemver(a: string, b: string): number {
10
+ const left = parseVersion(a);
11
+ const right = parseVersion(b);
12
+ const length = Math.max(left.length, right.length);
13
+ for (let i = 0; i < length; i++) {
14
+ const diff = (left[i] ?? 0) - (right[i] ?? 0);
15
+ if (diff !== 0) return diff > 0 ? 1 : -1;
16
+ }
17
+ return 0;
18
+ }