@akanjs/cli 2.3.0 → 2.3.1-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.
package/README.ko.md CHANGED
@@ -44,8 +44,17 @@ akan create-scalar <scalar-name>
44
44
  akan build-package akanjs
45
45
  akan build-package @akanjs/cli
46
46
  akan build-package @akanjs/devkit
47
+ akan build-package create-akan-workspace
48
+ akan verify-akan-publish-packages
49
+ akan smoke-registry --test=true --tag=rc
47
50
  ```
48
51
 
52
+ Akan framework 패키지는 반드시 `dist/pkgs/*` 산출물 기준으로 publish합니다. `verify-akan-publish-packages`는
53
+ 빌드된 패키지에 `npm pack --dry-run --json`을 실행하고, `deploy-akan` 또는 local registry smoke 전에 필요한
54
+ metadata를 검증합니다.
55
+ 저장소 릴리즈에서는 root `akan` bootstrap script가 CLI dist를 덮어쓰지 않도록
56
+ `bun run release:build-packages && bun run release:verify-packages`를 우선 사용합니다.
57
+
49
58
  ## 패키지 경계
50
59
 
51
60
  - 애플리케이션과 런타임 코드는 `akanjs`를 사용합니다.
package/README.md CHANGED
@@ -58,8 +58,16 @@ Package maintenance commands are also exposed through the same executable:
58
58
  akan build-package akanjs
59
59
  akan build-package @akanjs/cli
60
60
  akan build-package @akanjs/devkit
61
+ akan build-package create-akan-workspace
62
+ akan verify-akan-publish-packages
63
+ akan smoke-registry --test=true --tag=rc
61
64
  ```
62
65
 
66
+ Publish Akan framework packages from `dist/pkgs/*` only. `verify-akan-publish-packages` runs `npm pack --dry-run --json`
67
+ against the built packages and checks metadata that must be correct before `deploy-akan` or local registry smoke.
68
+ For repository releases, prefer `bun run release:build-packages && bun run release:verify-packages` so the CLI package
69
+ artifact is built last and is not overwritten by the root `akan` bootstrap script.
70
+
63
71
  ## Package Boundary
64
72
 
65
73
  - Use `akanjs` from application and runtime code.
@@ -2487,11 +2487,21 @@ var parseEnvFile = (envPath) => {
2487
2487
  }
2488
2488
  return env;
2489
2489
  };
2490
- var PAGE_ROUTE_EXPORTS = new Set(["default", "pageConfig", "head", "generateHead", "Loading"]);
2490
+ var PAGE_ROUTE_EXPORTS = new Set([
2491
+ "default",
2492
+ "pageConfig",
2493
+ "head",
2494
+ "metadata",
2495
+ "generateHead",
2496
+ "generateMetadata",
2497
+ "Loading"
2498
+ ]);
2491
2499
  var ROOT_LAYOUT_EXPORTS = new Set([
2492
2500
  "default",
2493
2501
  "head",
2502
+ "metadata",
2494
2503
  "generateHead",
2504
+ "generateMetadata",
2495
2505
  "fonts",
2496
2506
  "manifest",
2497
2507
  "theme",
@@ -2502,7 +2512,16 @@ var ROOT_LAYOUT_EXPORTS = new Set([
2502
2512
  "NotFound",
2503
2513
  "Error"
2504
2514
  ]);
2505
- var LAYOUT_ROUTE_EXPORTS = new Set(["default", "head", "generateHead", "Loading", "NotFound", "Error"]);
2515
+ var LAYOUT_ROUTE_EXPORTS = new Set([
2516
+ "default",
2517
+ "head",
2518
+ "metadata",
2519
+ "generateHead",
2520
+ "generateMetadata",
2521
+ "Loading",
2522
+ "NotFound",
2523
+ "Error"
2524
+ ]);
2506
2525
  function validateRouteSourceExports(source, filePath, kind, options = {}) {
2507
2526
  const sourceFile = ts3.createSourceFile(filePath, source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
2508
2527
  const allowed = kind === "page" ? PAGE_ROUTE_EXPORTS : options.rootLayout ? ROOT_LAYOUT_EXPORTS : LAYOUT_ROUTE_EXPORTS;
@@ -2555,6 +2574,12 @@ function validateRouteSourceExports(source, filePath, kind, options = {}) {
2555
2574
  if (exported.has("head") && exported.has("generateHead")) {
2556
2575
  throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
2557
2576
  }
2577
+ if (!options.rootLayout && (exported.has("head") || exported.has("generateHead")) && (exported.has("metadata") || exported.has("generateMetadata"))) {
2578
+ throw new Error(`[route-convention] head/generateHead and metadata/generateMetadata cannot both be exported in ${filePath}`);
2579
+ }
2580
+ if (exported.has("metadata") && exported.has("generateMetadata")) {
2581
+ throw new Error(`[route-convention] metadata and generateMetadata cannot both be exported in ${filePath}`);
2582
+ }
2558
2583
  }
2559
2584
 
2560
2585
  class Executor {
@@ -7897,6 +7922,7 @@ class PagesBundleBuilder {
7897
7922
  define: this.#define(),
7898
7923
  plugins: [
7899
7924
  PagesBundleBuilder.createPagesEntryPlugin(entrySource),
7925
+ PagesBundleBuilder.createServerCssStubPlugin(),
7900
7926
  await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig2.externalLibs }),
7901
7927
  akanConfig2.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
7902
7928
  pipeAfter: (source, args) => transformUseClient(source, {
@@ -7955,6 +7981,17 @@ class PagesBundleBuilder {
7955
7981
  }
7956
7982
  };
7957
7983
  }
7984
+ static createServerCssStubPlugin() {
7985
+ return {
7986
+ name: "akan-server-css-stub",
7987
+ setup(build) {
7988
+ build.onLoad({ filter: /\.css$/ }, () => ({
7989
+ contents: "",
7990
+ loader: "js"
7991
+ }));
7992
+ }
7993
+ };
7994
+ }
7958
7995
  }
7959
7996
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
7960
7997
  import fs5 from "fs";
@@ -8026,7 +8063,7 @@ class SsrBaseArtifactBuilder {
8026
8063
  }
8027
8064
  async build() {
8028
8065
  const akanConfig2 = await this.#app.getConfig();
8029
- const { rscClientUrl, vendorMap } = await this.#buildRuntimeClientEntries();
8066
+ const { rscClientUrl, rscRuntimeClientManifest, rscRuntimeSsrManifest, vendorMap } = await this.#buildRuntimeClientEntries();
8030
8067
  const pageKeys = await this.#app.getPageKeys();
8031
8068
  this.#app.verbose(`[base-artifact] discovered ${pageKeys.length} route files under ${this.#app.cwdPath}/page`);
8032
8069
  const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
@@ -8040,6 +8077,8 @@ class SsrBaseArtifactBuilder {
8040
8077
  this.#app.verbose(`[base-artifact] route seed index -> ${seedIndexPath} entries=${seedIndex.entries.length} globalLayouts=${seedIndex.globalLayoutFiles.length}`);
8041
8078
  const artifact = {
8042
8079
  rscClientUrl,
8080
+ rscRuntimeClientManifest,
8081
+ rscRuntimeSsrManifest,
8043
8082
  vendorMap,
8044
8083
  cssAssets,
8045
8084
  pagesBundlePath: this.#command === "build" ? path31.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
@@ -8059,16 +8098,42 @@ class SsrBaseArtifactBuilder {
8059
8098
  async#buildRuntimeClientEntries() {
8060
8099
  const akanServerPath = await this.#resolveAkanServerPath();
8061
8100
  const rscClientEntry = `${akanServerPath}/rscClient.tsx`;
8101
+ const rscSegmentOutletEntry = `${akanServerPath}/rscSegmentOutlet.tsx`;
8062
8102
  const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
8063
8103
  specifier,
8064
8104
  absPath: `${akanServerPath}/vendor/${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`
8065
8105
  }));
8066
- const entries = [rscClientEntry, ...vendorEntries.map((v) => v.absPath)];
8106
+ const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
8067
8107
  const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
8108
+ const ssrBundle = await new ClientEntriesBundler({
8109
+ app: this.#app,
8110
+ entries: [rscSegmentOutletEntry],
8111
+ ...RouteClientBuilder.resolveSsrClientExternalOptions(this.#command),
8112
+ outputSubdir: "client-ssr",
8113
+ command: this.#command
8114
+ }).bundle();
8068
8115
  const rscClientUrl = clientBundle.entryUrlsByAbsPath.get(rscClientEntry) ?? "";
8116
+ const rscRuntimeSsrManifest = {
8117
+ moduleLoading: null,
8118
+ moduleMap: Object.fromEntries(Object.entries(clientBundle.manifest).map(([key, row]) => {
8119
+ const ssrOutput = ssrBundle.entryOutputAbsByAbsPath.get(rscSegmentOutletEntry);
8120
+ if (!ssrOutput || key !== `${clientBundle.clientReferenceIdByAbsPath.get(rscSegmentOutletEntry)}#${row.name}`) {
8121
+ return null;
8122
+ }
8123
+ return [
8124
+ row.id,
8125
+ { [row.name]: { id: ssrOutput, chunks: [ssrOutput, ssrOutput], name: row.name, async: true } }
8126
+ ];
8127
+ }).filter((entry) => Boolean(entry)))
8128
+ };
8069
8129
  const vendorMap = Object.fromEntries(vendorEntries.map(({ specifier, absPath }) => [specifier, clientBundle.entryUrlsByAbsPath.get(absPath) ?? ""]));
8070
8130
  this.#app.verbose(`[base-artifact] rscClientUrl=${rscClientUrl} vendors=${Object.keys(vendorMap).length}`);
8071
- return { rscClientUrl, vendorMap };
8131
+ return {
8132
+ rscClientUrl,
8133
+ rscRuntimeClientManifest: clientBundle.manifest,
8134
+ rscRuntimeSsrManifest,
8135
+ vendorMap
8136
+ };
8072
8137
  }
8073
8138
  async#resolveAkanServerPath() {
8074
8139
  const candidates = [];
@@ -10004,7 +10069,8 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
10004
10069
  return formatCommandHelp(command, targetMeta.key);
10005
10070
  };
10006
10071
  programCommand.action(async (...args) => {
10007
- Logger10.rawLog();
10072
+ if (!targetMeta.targetOption.stdio)
10073
+ Logger10.rawLog();
10008
10074
  const cmdArgs = args.slice(0, args.length - 2);
10009
10075
  const opt = args[args.length - 2];
10010
10076
  const commandArgs = [];
@@ -10028,7 +10094,8 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
10028
10094
  const cmd = CommandContainer.get(command);
10029
10095
  try {
10030
10096
  await targetMeta.handler.call(cmd, ...commandArgs);
10031
- Logger10.rawLog();
10097
+ if (!targetMeta.targetOption.stdio)
10098
+ Logger10.rawLog();
10032
10099
  } catch (e) {
10033
10100
  printCliError(e);
10034
10101
  throw e;
package/index.js CHANGED
@@ -2485,11 +2485,21 @@ var parseEnvFile = (envPath) => {
2485
2485
  }
2486
2486
  return env;
2487
2487
  };
2488
- var PAGE_ROUTE_EXPORTS = new Set(["default", "pageConfig", "head", "generateHead", "Loading"]);
2488
+ var PAGE_ROUTE_EXPORTS = new Set([
2489
+ "default",
2490
+ "pageConfig",
2491
+ "head",
2492
+ "metadata",
2493
+ "generateHead",
2494
+ "generateMetadata",
2495
+ "Loading"
2496
+ ]);
2489
2497
  var ROOT_LAYOUT_EXPORTS = new Set([
2490
2498
  "default",
2491
2499
  "head",
2500
+ "metadata",
2492
2501
  "generateHead",
2502
+ "generateMetadata",
2493
2503
  "fonts",
2494
2504
  "manifest",
2495
2505
  "theme",
@@ -2500,7 +2510,16 @@ var ROOT_LAYOUT_EXPORTS = new Set([
2500
2510
  "NotFound",
2501
2511
  "Error"
2502
2512
  ]);
2503
- var LAYOUT_ROUTE_EXPORTS = new Set(["default", "head", "generateHead", "Loading", "NotFound", "Error"]);
2513
+ var LAYOUT_ROUTE_EXPORTS = new Set([
2514
+ "default",
2515
+ "head",
2516
+ "metadata",
2517
+ "generateHead",
2518
+ "generateMetadata",
2519
+ "Loading",
2520
+ "NotFound",
2521
+ "Error"
2522
+ ]);
2504
2523
  function validateRouteSourceExports(source, filePath, kind, options = {}) {
2505
2524
  const sourceFile = ts3.createSourceFile(filePath, source, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TSX);
2506
2525
  const allowed = kind === "page" ? PAGE_ROUTE_EXPORTS : options.rootLayout ? ROOT_LAYOUT_EXPORTS : LAYOUT_ROUTE_EXPORTS;
@@ -2553,6 +2572,12 @@ function validateRouteSourceExports(source, filePath, kind, options = {}) {
2553
2572
  if (exported.has("head") && exported.has("generateHead")) {
2554
2573
  throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
2555
2574
  }
2575
+ if (!options.rootLayout && (exported.has("head") || exported.has("generateHead")) && (exported.has("metadata") || exported.has("generateMetadata"))) {
2576
+ throw new Error(`[route-convention] head/generateHead and metadata/generateMetadata cannot both be exported in ${filePath}`);
2577
+ }
2578
+ if (exported.has("metadata") && exported.has("generateMetadata")) {
2579
+ throw new Error(`[route-convention] metadata and generateMetadata cannot both be exported in ${filePath}`);
2580
+ }
2556
2581
  }
2557
2582
 
2558
2583
  class Executor {
@@ -7895,6 +7920,7 @@ class PagesBundleBuilder {
7895
7920
  define: this.#define(),
7896
7921
  plugins: [
7897
7922
  PagesBundleBuilder.createPagesEntryPlugin(entrySource),
7923
+ PagesBundleBuilder.createServerCssStubPlugin(),
7898
7924
  await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig2.externalLibs }),
7899
7925
  akanConfig2.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
7900
7926
  pipeAfter: (source, args) => transformUseClient(source, {
@@ -7953,6 +7979,17 @@ class PagesBundleBuilder {
7953
7979
  }
7954
7980
  };
7955
7981
  }
7982
+ static createServerCssStubPlugin() {
7983
+ return {
7984
+ name: "akan-server-css-stub",
7985
+ setup(build) {
7986
+ build.onLoad({ filter: /\.css$/ }, () => ({
7987
+ contents: "",
7988
+ loader: "js"
7989
+ }));
7990
+ }
7991
+ };
7992
+ }
7956
7993
  }
7957
7994
  // pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
7958
7995
  import fs5 from "fs";
@@ -8024,7 +8061,7 @@ class SsrBaseArtifactBuilder {
8024
8061
  }
8025
8062
  async build() {
8026
8063
  const akanConfig2 = await this.#app.getConfig();
8027
- const { rscClientUrl, vendorMap } = await this.#buildRuntimeClientEntries();
8064
+ const { rscClientUrl, rscRuntimeClientManifest, rscRuntimeSsrManifest, vendorMap } = await this.#buildRuntimeClientEntries();
8028
8065
  const pageKeys = await this.#app.getPageKeys();
8029
8066
  this.#app.verbose(`[base-artifact] discovered ${pageKeys.length} route files under ${this.#app.cwdPath}/page`);
8030
8067
  const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
@@ -8038,6 +8075,8 @@ class SsrBaseArtifactBuilder {
8038
8075
  this.#app.verbose(`[base-artifact] route seed index -> ${seedIndexPath} entries=${seedIndex.entries.length} globalLayouts=${seedIndex.globalLayoutFiles.length}`);
8039
8076
  const artifact = {
8040
8077
  rscClientUrl,
8078
+ rscRuntimeClientManifest,
8079
+ rscRuntimeSsrManifest,
8041
8080
  vendorMap,
8042
8081
  cssAssets,
8043
8082
  pagesBundlePath: this.#command === "build" ? path31.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
@@ -8057,16 +8096,42 @@ class SsrBaseArtifactBuilder {
8057
8096
  async#buildRuntimeClientEntries() {
8058
8097
  const akanServerPath = await this.#resolveAkanServerPath();
8059
8098
  const rscClientEntry = `${akanServerPath}/rscClient.tsx`;
8099
+ const rscSegmentOutletEntry = `${akanServerPath}/rscSegmentOutlet.tsx`;
8060
8100
  const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
8061
8101
  specifier,
8062
8102
  absPath: `${akanServerPath}/vendor/${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`
8063
8103
  }));
8064
- const entries = [rscClientEntry, ...vendorEntries.map((v) => v.absPath)];
8104
+ const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
8065
8105
  const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
8106
+ const ssrBundle = await new ClientEntriesBundler({
8107
+ app: this.#app,
8108
+ entries: [rscSegmentOutletEntry],
8109
+ ...RouteClientBuilder.resolveSsrClientExternalOptions(this.#command),
8110
+ outputSubdir: "client-ssr",
8111
+ command: this.#command
8112
+ }).bundle();
8066
8113
  const rscClientUrl = clientBundle.entryUrlsByAbsPath.get(rscClientEntry) ?? "";
8114
+ const rscRuntimeSsrManifest = {
8115
+ moduleLoading: null,
8116
+ moduleMap: Object.fromEntries(Object.entries(clientBundle.manifest).map(([key, row]) => {
8117
+ const ssrOutput = ssrBundle.entryOutputAbsByAbsPath.get(rscSegmentOutletEntry);
8118
+ if (!ssrOutput || key !== `${clientBundle.clientReferenceIdByAbsPath.get(rscSegmentOutletEntry)}#${row.name}`) {
8119
+ return null;
8120
+ }
8121
+ return [
8122
+ row.id,
8123
+ { [row.name]: { id: ssrOutput, chunks: [ssrOutput, ssrOutput], name: row.name, async: true } }
8124
+ ];
8125
+ }).filter((entry) => Boolean(entry)))
8126
+ };
8067
8127
  const vendorMap = Object.fromEntries(vendorEntries.map(({ specifier, absPath }) => [specifier, clientBundle.entryUrlsByAbsPath.get(absPath) ?? ""]));
8068
8128
  this.#app.verbose(`[base-artifact] rscClientUrl=${rscClientUrl} vendors=${Object.keys(vendorMap).length}`);
8069
- return { rscClientUrl, vendorMap };
8129
+ return {
8130
+ rscClientUrl,
8131
+ rscRuntimeClientManifest: clientBundle.manifest,
8132
+ rscRuntimeSsrManifest,
8133
+ vendorMap
8134
+ };
8070
8135
  }
8071
8136
  async#resolveAkanServerPath() {
8072
8137
  const candidates = [];
@@ -10002,7 +10067,8 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
10002
10067
  return formatCommandHelp(command, targetMeta.key);
10003
10068
  };
10004
10069
  programCommand.action(async (...args) => {
10005
- Logger10.rawLog();
10070
+ if (!targetMeta.targetOption.stdio)
10071
+ Logger10.rawLog();
10006
10072
  const cmdArgs = args.slice(0, args.length - 2);
10007
10073
  const opt = args[args.length - 2];
10008
10074
  const commandArgs = [];
@@ -10026,7 +10092,8 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
10026
10092
  const cmd = CommandContainer.get(command);
10027
10093
  try {
10028
10094
  await targetMeta.handler.call(cmd, ...commandArgs);
10029
- Logger10.rawLog();
10095
+ if (!targetMeta.targetOption.stdio)
10096
+ Logger10.rawLog();
10030
10097
  } catch (e) {
10031
10098
  printCliError(e);
10032
10099
  throw e;
@@ -11420,6 +11487,7 @@ import { Logger as Logger14 } from "akanjs/common";
11420
11487
  var {$: $2 } = globalThis.Bun;
11421
11488
 
11422
11489
  class PackageRunner extends runner("package") {
11490
+ static publishableAkanPackages = ["akanjs", "@akanjs/cli", "@akanjs/devkit", "create-akan-workspace"];
11423
11491
  async version(workspace, { log = true } = {}) {
11424
11492
  const pkgJson = process.env.USE_AKANJS_PKGS === "true" ? await FileSys.readJson(`${workspace?.workspaceRoot ?? process.cwd()}/pkgs/akanjs/package.json`) : await this.#getInstalledPackageJson();
11425
11493
  const version = pkgJson.name === "akanjs" ? pkgJson.version : pkgJson.dependencies?.akanjs ?? pkgJson.version;
@@ -11490,7 +11558,13 @@ class PackageRunner extends runner("package") {
11490
11558
  await pkg.updatePackageJsonDependencies(packageRuntimeDeps, packageRuntimeDevDeps);
11491
11559
  const hasBuildFile = await Bun.file(`${pkg.cwdPath}/build.ts`).exists();
11492
11560
  if (hasBuildFile) {
11493
- await pkg.workspace.spawn(process.execPath, [`${pkg.cwdPath}/build.ts`], { env: process.env, stdio: "inherit" });
11561
+ await pkg.workspace.spawn(process.execPath, [`${pkg.cwdPath}/build.ts`], {
11562
+ env: {
11563
+ ...process.env,
11564
+ ...pkg.name === "akanjs" ? { AKAN_BUILD_DECLARATION_DIAGNOSTICS: "error" } : {}
11565
+ },
11566
+ stdio: "inherit"
11567
+ });
11494
11568
  } else {
11495
11569
  await $2`cp -r ${pkg.cwdPath}/. ${pkg.dist.cwdPath}`;
11496
11570
  await Promise.all([
@@ -11500,6 +11574,55 @@ class PackageRunner extends runner("package") {
11500
11574
  }
11501
11575
  await this.#copyPackageReadmes(pkg);
11502
11576
  }
11577
+ async verifyDistPackage(pkg) {
11578
+ const distPackageJsonPath = `${pkg.dist.cwdPath}/package.json`;
11579
+ if (!await Bun.file(distPackageJsonPath).exists()) {
11580
+ throw new Error(`[package] dist package not found for ${pkg.name}. Run build-package first.`);
11581
+ }
11582
+ const pkgJson = await FileSys.readJson(distPackageJsonPath);
11583
+ if (pkgJson.name !== pkg.name) {
11584
+ throw new Error(`[package] dist package name mismatch: expected ${pkg.name}, got ${pkgJson.name ?? "(missing)"}`);
11585
+ }
11586
+ if (!pkgJson.version)
11587
+ throw new Error(`[package] dist package version is missing for ${pkg.name}`);
11588
+ if (!pkgJson.publishConfig || pkgJson.publishConfig.access !== "public") {
11589
+ throw new Error(`[package] ${pkg.name} must publish with publishConfig.access=public`);
11590
+ }
11591
+ if (!await Bun.file(`${pkg.dist.cwdPath}/README.md`).exists()) {
11592
+ throw new Error(`[package] README.md is missing from dist package ${pkg.name}`);
11593
+ }
11594
+ if (!await Bun.file(`${pkg.dist.cwdPath}/README.ko.md`).exists()) {
11595
+ throw new Error(`[package] README.ko.md is missing from dist package ${pkg.name}`);
11596
+ }
11597
+ const binEntries = typeof pkgJson.bin === "string" ? [pkgJson.bin] : Object.values(pkgJson.bin ?? {});
11598
+ if (binEntries.some((binPath) => binPath.endsWith(".ts"))) {
11599
+ throw new Error(`[package] ${pkg.name} dist bin entries must not point at TypeScript sources`);
11600
+ }
11601
+ if (pkg.name === "akanjs") {
11602
+ const exports = pkgJson.exports;
11603
+ const rootExport = exports?.["."];
11604
+ if (!rootExport?.types?.startsWith("./types/")) {
11605
+ throw new Error("[package] akanjs dist exports must point type declarations at ./types");
11606
+ }
11607
+ }
11608
+ const packOutput = await pkg.workspace.spawn("npm", ["pack", "--dry-run", "--json", pkg.dist.cwdPath], {
11609
+ cwd: pkg.workspace.workspaceRoot
11610
+ });
11611
+ const [packResult] = JSON.parse(packOutput);
11612
+ return {
11613
+ name: pkg.name,
11614
+ version: pkgJson.version,
11615
+ files: packResult?.files?.length ?? 0,
11616
+ size: packResult?.size ?? 0
11617
+ };
11618
+ }
11619
+ async verifyAkanPublishPackages(workspace) {
11620
+ const results = [];
11621
+ for (const pkgName of PackageRunner.publishableAkanPackages) {
11622
+ results.push(await this.verifyDistPackage(PkgExecutor.from(workspace, pkgName)));
11623
+ }
11624
+ return results;
11625
+ }
11503
11626
  async#copyPackageReadmes(pkg) {
11504
11627
  await Promise.all(["README.md", "README.ko.md"].map((fileName) => pkg.cp(fileName, `${pkg.dist.cwdPath}/${fileName}`)));
11505
11628
  }
@@ -11543,6 +11666,18 @@ class PackageScript extends script("package", [PackageRunner]) {
11543
11666
  if (spinner2)
11544
11667
  spinner2.succeed("Package built");
11545
11668
  }
11669
+ async verifyDistPackage(pkg) {
11670
+ const spinner2 = pkg.spinning("Verifying dist package...");
11671
+ const result = await this.packageRunner.verifyDistPackage(pkg);
11672
+ spinner2.succeed(`Package verified (${result.files} files, ${result.size} bytes packed)`);
11673
+ return result;
11674
+ }
11675
+ async verifyAkanPublishPackages(workspace) {
11676
+ const spinner2 = workspace.spinning("Verifying Akan publish packages...");
11677
+ const results = await this.packageRunner.verifyAkanPublishPackages(workspace);
11678
+ spinner2.succeed(`Akan publish packages verified (${results.length} packages)`);
11679
+ return results;
11680
+ }
11546
11681
  async updateWorskpaceRootPackageJson(workspace) {
11547
11682
  const rootPackageJson = await workspace.getPackageJson();
11548
11683
  await this.packageRunner.updateWorskpaceRootPackageJson(workspace, rootPackageJson);
@@ -11988,6 +12123,7 @@ class CloudScript extends script("cloud", [CloudRunner, ApplicationScript, Packa
11988
12123
  await this.applicationScript.test(pkg);
11989
12124
  for (const pkg of pkgs)
11990
12125
  await this.packageScript.buildPackage(pkg);
12126
+ await this.packageScript.verifyAkanPublishPackages(workspace);
11991
12127
  await this.cloudRunner.deployAkan(workspace, akanPkgs, { registryUrl });
11992
12128
  }
11993
12129
  async update(workspace, tag = "latest", { registryUrl } = {}) {
@@ -12094,6 +12230,13 @@ var resourceList = [
12094
12230
  { uri: "akan://workspace/apps", name: "Workspace apps", mimeType: "application/json" },
12095
12231
  { uri: "akan://workspace/modules", name: "Workspace modules", mimeType: "application/json" }
12096
12232
  ];
12233
+ var cursorMcpConfigPath = ".cursor/mcp.json";
12234
+ var cursorWorkspaceFolder = "$" + "{workspaceFolder}";
12235
+ var akanCursorMcpServer = {
12236
+ type: "stdio",
12237
+ command: "bash",
12238
+ args: ["-lc", `cd "${cursorWorkspaceFolder}" && akan mcp`]
12239
+ };
12097
12240
 
12098
12241
  class ContextRunner extends runner("context") {
12099
12242
  async getContext(workspace, {
@@ -12117,20 +12260,44 @@ class ContextRunner extends runner("context") {
12117
12260
  async getGuidelineResource(name) {
12118
12261
  return await Prompter.getInstruction(name);
12119
12262
  }
12263
+ async installMcp(workspace, target, { force = false } = {}) {
12264
+ if (target !== "cursor")
12265
+ throw new Error(`Unknown MCP install target: ${target}. Use cursor.`);
12266
+ const existing = await workspace.exists(cursorMcpConfigPath) ? await workspace.readJson(cursorMcpConfigPath) : {};
12267
+ const mcpServers = existing.mcpServers ?? {};
12268
+ const currentAkanServer = mcpServers.akan;
12269
+ if (currentAkanServer && !force && JSON.stringify(currentAkanServer) !== JSON.stringify(akanCursorMcpServer)) {
12270
+ throw new Error(`${cursorMcpConfigPath} already has an "akan" MCP server. Re-run with --force to overwrite it.`);
12271
+ }
12272
+ const nextConfig = {
12273
+ ...existing,
12274
+ mcpServers: {
12275
+ ...mcpServers,
12276
+ akan: akanCursorMcpServer
12277
+ }
12278
+ };
12279
+ await workspace.writeFile(cursorMcpConfigPath, `${JSON.stringify(nextConfig, null, 2)}
12280
+ `);
12281
+ return cursorMcpConfigPath;
12282
+ }
12120
12283
  async runMcp(workspace) {
12121
12284
  const decoder = new TextDecoder;
12122
12285
  let buffer = "";
12123
- const respond = (id, result) => {
12124
- const payload = JSON.stringify({ jsonrpc: "2.0", id, result });
12125
- process.stdout.write(`Content-Length: ${Buffer.byteLength(payload)}\r
12286
+ const writeMessage = (message, framing) => {
12287
+ const payload = JSON.stringify(message);
12288
+ if (framing === "newline")
12289
+ process.stdout.write(`${payload}
12290
+ `);
12291
+ else
12292
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(payload)}\r
12126
12293
  \r
12127
12294
  ${payload}`);
12128
12295
  };
12129
- const respondError = (id, code, message) => {
12130
- const payload = JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
12131
- process.stdout.write(`Content-Length: ${Buffer.byteLength(payload)}\r
12132
- \r
12133
- ${payload}`);
12296
+ const respond = (id, result, framing) => {
12297
+ writeMessage({ jsonrpc: "2.0", id, result }, framing);
12298
+ };
12299
+ const respondError = (id, code, message, framing) => {
12300
+ writeMessage({ jsonrpc: "2.0", id, error: { code, message } }, framing);
12134
12301
  };
12135
12302
  const readResource = async (uri) => {
12136
12303
  const context = await AkanContextAnalyzer.analyze(workspace);
@@ -12155,14 +12322,14 @@ ${payload}`);
12155
12322
  }
12156
12323
  throw new Error(`Unknown resource: ${uri}`);
12157
12324
  };
12158
- const handle = async (request) => {
12325
+ const handle = async (request, framing) => {
12159
12326
  const params = request.params ?? {};
12160
12327
  if (request.method === "initialize") {
12161
12328
  respond(request.id, {
12162
12329
  protocolVersion: "2024-11-05",
12163
12330
  capabilities: { tools: {}, resources: {} },
12164
12331
  serverInfo: { name: "akan", version: process.env.AKAN_VERSION ?? "0.0.0" }
12165
- });
12332
+ }, framing);
12166
12333
  } else if (request.method === "tools/list") {
12167
12334
  respond(request.id, {
12168
12335
  tools: [
@@ -12181,9 +12348,12 @@ ${payload}`);
12181
12348
  name: "explain_command",
12182
12349
  inputSchema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] }
12183
12350
  },
12184
- { name: "doctor_workspace", inputSchema: { type: "object", properties: { strict: { type: "boolean" } } } }
12351
+ {
12352
+ name: "doctor_workspace",
12353
+ inputSchema: { type: "object", properties: { strict: { type: "boolean" } } }
12354
+ }
12185
12355
  ]
12186
- });
12356
+ }, framing);
12187
12357
  } else if (request.method === "tools/call") {
12188
12358
  const name = params.name;
12189
12359
  const args = params.arguments ?? {};
@@ -12210,36 +12380,64 @@ ${payload}`);
12210
12380
  })();
12211
12381
  respond(request.id, {
12212
12382
  content: [{ type: "text", text: typeof result === "string" ? result : jsonText(result) }]
12213
- });
12383
+ }, framing);
12214
12384
  } else if (request.method === "resources/list") {
12215
- respond(request.id, { resources: resourceList });
12385
+ respond(request.id, { resources: resourceList }, framing);
12216
12386
  } else if (request.method === "resources/read") {
12217
- respond(request.id, { contents: [await readResource(params.uri)] });
12387
+ respond(request.id, { contents: [await readResource(params.uri)] }, framing);
12218
12388
  } else if (!request.method.endsWith("/initialized")) {
12219
- respondError(request.id, -32601, `Unknown method: ${request.method}`);
12389
+ respondError(request.id, -32601, `Unknown method: ${request.method}`, framing);
12220
12390
  }
12221
12391
  };
12222
- const parse = async () => {
12223
- while (true) {
12224
- const headerEnd = buffer.indexOf(`\r
12392
+ const parseContentLengthMessage = async () => {
12393
+ const headerEnd = buffer.indexOf(`\r
12225
12394
  \r
12226
12395
  `);
12227
- if (headerEnd < 0)
12396
+ if (headerEnd < 0)
12397
+ return false;
12398
+ const header = buffer.slice(0, headerEnd);
12399
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
12400
+ if (!match) {
12401
+ buffer = buffer.slice(headerEnd + 4);
12402
+ return true;
12403
+ }
12404
+ const length = Number(match[1]);
12405
+ const bodyStart = headerEnd + 4;
12406
+ const bodyEnd = bodyStart + length;
12407
+ if (buffer.length < bodyEnd)
12408
+ return false;
12409
+ const body = buffer.slice(bodyStart, bodyEnd);
12410
+ buffer = buffer.slice(bodyEnd);
12411
+ await handle(JSON.parse(body), "content-length");
12412
+ return true;
12413
+ };
12414
+ const parseLineMessage = async () => {
12415
+ const lineEnd = buffer.indexOf(`
12416
+ `);
12417
+ if (lineEnd < 0)
12418
+ return false;
12419
+ const line = buffer.slice(0, lineEnd).trim();
12420
+ buffer = buffer.slice(lineEnd + 1);
12421
+ if (!line)
12422
+ return true;
12423
+ await handle(JSON.parse(line), "newline");
12424
+ return true;
12425
+ };
12426
+ const parse = async () => {
12427
+ while (true) {
12428
+ buffer = buffer.trimStart();
12429
+ if (/^Content-Length:/i.test(buffer)) {
12430
+ if (await parseContentLengthMessage())
12431
+ continue;
12228
12432
  return;
12229
- const header = buffer.slice(0, headerEnd);
12230
- const match = /Content-Length:\s*(\d+)/i.exec(header);
12231
- if (!match) {
12232
- buffer = buffer.slice(headerEnd + 4);
12233
- continue;
12234
12433
  }
12235
- const length = Number(match[1]);
12236
- const bodyStart = headerEnd + 4;
12237
- const bodyEnd = bodyStart + length;
12238
- if (buffer.length < bodyEnd)
12239
- return;
12240
- const body = buffer.slice(bodyStart, bodyEnd);
12241
- buffer = buffer.slice(bodyEnd);
12242
- await handle(JSON.parse(body));
12434
+ if (buffer.includes(`\r
12435
+ \r
12436
+ `) && await parseContentLengthMessage())
12437
+ continue;
12438
+ if (await parseLineMessage())
12439
+ continue;
12440
+ return;
12243
12441
  }
12244
12442
  };
12245
12443
  for await (const chunk of Bun.stdin.stream()) {
@@ -12267,6 +12465,13 @@ class ContextScript extends script("context", [ContextRunner]) {
12267
12465
  async doctor(workspace, options = {}) {
12268
12466
  Logger17.rawLog(await this.contextRunner.doctor(workspace, options));
12269
12467
  }
12468
+ async mcpInstall(workspace, target, { force = false } = {}) {
12469
+ if (target && target !== "cursor")
12470
+ throw new Error(`Unknown MCP install target: ${target}. Use cursor.`);
12471
+ const written = await this.contextRunner.installMcp(workspace, "cursor", { force });
12472
+ Logger17.rawLog(`Akan MCP server installed for Cursor:
12473
+ - ${written}`);
12474
+ }
12270
12475
  async mcp(workspace) {
12271
12476
  await this.contextRunner.runMcp(workspace);
12272
12477
  }
@@ -12288,7 +12493,10 @@ class ContextCommand extends command("context", [ContextScript], ({ public: targ
12288
12493
  }).option("strict", Boolean, { desc: "treat recommended conventions as errors", default: false }).with(Workspace).exec(async function(format, strict, workspace) {
12289
12494
  await this.contextScript.doctor(workspace, { format, strict });
12290
12495
  }),
12291
- mcp: target({ desc: "Start the read-only Akan MCP server over stdio" }).with(Workspace).exec(async function(workspace) {
12496
+ mcpInstall: target({ desc: "Install the Akan MCP server config for Cursor" }).arg("target", String, { desc: "cursor", nullable: true }).option("force", Boolean, { desc: "overwrite an existing Akan MCP server entry", default: false }).with(Workspace).exec(async function(targetName, force, workspace) {
12497
+ await this.contextScript.mcpInstall(workspace, targetName, { force });
12498
+ }),
12499
+ mcp: target({ desc: "Start the read-only Akan MCP server over stdio", stdio: true }).with(Workspace).exec(async function(workspace) {
12292
12500
  await this.contextScript.mcp(workspace);
12293
12501
  })
12294
12502
  })) {
@@ -12754,6 +12962,7 @@ class LocalRegistryScript extends script("localRegistry", [
12754
12962
  await this.applicationScript.test(pkg);
12755
12963
  for (const pkg of pkgs)
12756
12964
  await this.packageScript.buildPackage(pkg, { showSpinner: false });
12965
+ await this.packageScript.verifyAkanPublishPackages(workspace);
12757
12966
  }
12758
12967
  }
12759
12968
 
@@ -13376,6 +13585,12 @@ class PackageCommand extends command("package", [PackageScript], ({ public: targ
13376
13585
  }),
13377
13586
  buildPackage: target({ desc: "Build a package for distribution" }).with(Pkg).exec(async function(pkg) {
13378
13587
  await this.packageScript.buildPackage(pkg);
13588
+ }),
13589
+ verifyDistPackage: target({ desc: "Verify a built dist package with npm pack dry-run" }).with(Pkg).exec(async function(pkg) {
13590
+ await this.packageScript.verifyDistPackage(pkg);
13591
+ }),
13592
+ verifyAkanPublishPackages: target({ devOnly: true, desc: "Verify all Akan publish dist packages" }).with(Workspace).exec(async function(workspace) {
13593
+ await this.packageScript.verifyAkanPublishPackages(workspace);
13379
13594
  })
13380
13595
  })) {
13381
13596
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.3.0",
3
+ "version": "2.3.1-rc.0",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -35,7 +35,7 @@
35
35
  "@langchain/openai": "^1.4.6",
36
36
  "@tailwindcss/node": "^4.3.0",
37
37
  "@trapezedev/project": "^7.1.4",
38
- "akanjs": "2.3.0",
38
+ "akanjs": "2.3.1-rc.0",
39
39
  "chalk": "^5.6.2",
40
40
  "commander": "^14.0.3",
41
41
  "daisyui": "^5.5.20",
@@ -47,7 +47,7 @@
47
47
  "js-yaml": "^4.1.1",
48
48
  "ora": "^9.4.0",
49
49
  "qrcode": "^1.5.4",
50
- "react": "19.2.6",
50
+ "react": "19.2.7",
51
51
  "ssh2": "^1.17.0",
52
52
  "subset-font": "^2.5.0",
53
53
  "tailwind-scrollbar": "^4.0.2",