@akanjs/cli 2.1.0 → 2.1.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.
Files changed (32) hide show
  1. package/README.ko.md +63 -0
  2. package/README.md +62 -0
  3. package/guidelines/componentRule/componentRule.generate.json +4 -10
  4. package/guidelines/cssRule/cssRule.generate.json +4 -10
  5. package/guidelines/docPageRule/docPageRule.generate.json +4 -10
  6. package/guidelines/docPageRule/docPageRule.instruction.md +1 -1
  7. package/guidelines/docSyncRule/docSyncRule.generate.json +4 -10
  8. package/guidelines/enumConstant/enumConstant.generate.json +4 -10
  9. package/guidelines/fieldRule/fieldRule.generate.json +4 -10
  10. package/guidelines/framework/framework.generate.json +4 -10
  11. package/guidelines/modelConstant/modelConstant.generate.json +4 -10
  12. package/guidelines/modelDictionary/modelDictionary.generate.json +4 -10
  13. package/guidelines/modelDocument/modelDocument.generate.json +4 -10
  14. package/guidelines/modelService/modelService.generate.json +4 -10
  15. package/guidelines/modelSignal/modelSignal.generate.json +4 -10
  16. package/guidelines/modelStore/modelStore.generate.json +4 -10
  17. package/guidelines/modelTemplate/modelTemplate.generate.json +4 -10
  18. package/guidelines/modelUnit/modelUnit.generate.json +4 -10
  19. package/guidelines/modelUtil/modelUtil.generate.json +4 -10
  20. package/guidelines/modelView/modelView.generate.json +4 -10
  21. package/guidelines/modelZone/modelZone.generate.json +4 -10
  22. package/guidelines/moduleCodegen/moduleCodegen.generate.json +4 -10
  23. package/guidelines/moduleOverview/moduleOverview.generate.json +4 -10
  24. package/guidelines/scalarConstant/scalarConstant.generate.json +4 -10
  25. package/guidelines/scalarDictionary/scalarDictionary.generate.json +4 -10
  26. package/guidelines/scalarModule/scalarModule.generate.json +4 -10
  27. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +4 -10
  28. package/guidelines/utilUiUsage/utilUiUsage.generate.json +4 -10
  29. package/incrementalBuilder.proc.js +131 -18
  30. package/index.js +201 -26
  31. package/package.json +3 -2
  32. package/templates/libRoot/lib/___libName__/__libName__.signal.ts +15 -0
@@ -24,9 +24,12 @@ import { mkdir } from "fs/promises";
24
24
  var basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
25
25
  var configPath = `${basePath}/config.json`;
26
26
  var akanCloudHost = process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? "http://localhost" : "https://cloud.akanjs.com";
27
- var akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""}/\uBA54\u3151`;
27
+ var akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""}/api`;
28
28
  var defaultHostConfig = {};
29
- var defaultAkanGlobalConfig = { cloudHost: {}, llm: null };
29
+ var defaultAkanGlobalConfig = {
30
+ cloudHost: {},
31
+ llm: null
32
+ };
30
33
 
31
34
  // pkgs/@akanjs/devkit/fileSys.ts
32
35
  import { stat } from "fs/promises";
@@ -2140,7 +2143,6 @@ class Executor {
2140
2143
  this.logger = new Logger4(name);
2141
2144
  this.logs = [];
2142
2145
  this.cwdPath = cwdPath;
2143
- //! TODO: 테스트 확인 필요
2144
2146
  }
2145
2147
  #stdout(data) {
2146
2148
  if (Executor.verbose)
@@ -3982,6 +3984,16 @@ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
3982
3984
  }
3983
3985
  return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
3984
3986
  }
3987
+ function hasAncestorRootBoundary(boundary, boundaries) {
3988
+ return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
3989
+ }
3990
+ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
3991
+ const rootLayoutKey = pageKeys.find((key) => {
3992
+ const segments = getRootBoundarySegments(key);
3993
+ return segments !== null && segments.length === 0;
3994
+ });
3995
+ return rootLayoutKey ? path10.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
3996
+ }
3985
3997
  function routePrefixForSegments(segments) {
3986
3998
  const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
3987
3999
  return visible[0] ?? null;
@@ -3998,21 +4010,27 @@ async function writeGeneratedRootLayoutFile(opts) {
3998
4010
  await mkdir3(path10.dirname(absPath), { recursive: true });
3999
4011
  const sourceRel = opts.boundary.sourceAbsPath ? path10.relative(path10.dirname(absPath), opts.boundary.sourceAbsPath).split(path10.sep).join("/") : null;
4000
4012
  const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
4013
+ const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
4014
+ const inheritedSourceRel = inheritedSourceAbsPath ? path10.relative(path10.dirname(absPath), inheritedSourceAbsPath).split(path10.sep).join("/") : null;
4015
+ const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
4001
4016
  const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
4002
4017
  void st;
4003
4018
  ` : `import "@apps/${opts.appName}/client";
4019
+ `;
4020
+ const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
4021
+ ` : `const inheritedLayout = {};
4004
4022
  `;
4005
4023
  const prefix = routePrefixForSegments(opts.boundary.segments);
4006
4024
  const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
4007
4025
  ` : `const UserLayout = ({ children }) => children;
4008
4026
  const userLayout = {};
4009
4027
  `;
4010
- const source = `import type { LayoutProps, PageProps } from "akanjs/client";
4028
+ const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
4011
4029
  import { loadFonts } from "akanjs/client";
4012
4030
  import { System } from "akanjs/ui";
4013
4031
  import { env } from "@apps/${opts.appName}/env/env.client";
4014
- ${clientImport}${userImport}
4015
- const userFonts = userLayout.fonts ?? [];
4032
+ ${clientImport}${inheritedImport}${userImport}
4033
+ const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
4016
4034
  const defaultFonts = userFonts.filter((font) => font.default);
4017
4035
  if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
4018
4036
  const defaultFont = defaultFonts[0];
@@ -4020,7 +4038,9 @@ const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${d
4020
4038
 
4021
4039
  export async function generateHead(props: PageProps) {
4022
4040
  if (userLayout.generateHead) return userLayout.generateHead(props);
4023
- return userLayout.head;
4041
+ if (userLayout.head !== undefined) return userLayout.head;
4042
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
4043
+ return inheritedLayout.head;
4024
4044
  }
4025
4045
 
4026
4046
  export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
@@ -4030,19 +4050,31 @@ export default function GeneratedLayout({ children, params, searchParams }: Layo
4030
4050
  appName=${JSON.stringify(opts.appName)}
4031
4051
  ${prefix ? `prefix=${JSON.stringify(prefix)}
4032
4052
  ` : ""}params={params}
4033
- manifest={userLayout.manifest}
4053
+ manifest={userLayout.manifest ?? inheritedLayout.manifest}
4034
4054
  env={env}
4035
- theme={userLayout.theme}
4055
+ theme={userLayout.theme ?? inheritedLayout.theme}
4036
4056
  fonts={loadFonts(userFonts)}
4037
4057
  className={defaultFontClassName}
4038
- gaTrackingId={userLayout.gaTrackingId}
4039
- layoutStyle={userLayout.layoutStyle}
4040
- reconnect={userLayout.reconnect ?? false}
4058
+ gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
4059
+ layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
4060
+ reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
4041
4061
  >
4042
4062
  <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>
4043
4063
  </System.Provider>
4044
4064
  );
4045
4065
  }
4066
+ ` : `import type { LayoutProps, PageProps } from "akanjs/client";
4067
+ ${inheritedImport}${userImport}
4068
+ export async function generateHead(props: PageProps) {
4069
+ if (userLayout.generateHead) return userLayout.generateHead(props);
4070
+ if (userLayout.head !== undefined) return userLayout.head;
4071
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
4072
+ return inheritedLayout.head;
4073
+ }
4074
+
4075
+ export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
4076
+ return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
4077
+ }
4046
4078
  `;
4047
4079
  await Bun.write(absPath, source);
4048
4080
  return absPath;
@@ -4051,6 +4083,8 @@ async function resolveSsrPageEntries(opts) {
4051
4083
  const absPageDir = path10.resolve(opts.appCwdPath, "page");
4052
4084
  const hasSt = await appHasStModule(opts.appCwdPath);
4053
4085
  const basePaths = opts.basePaths ?? [];
4086
+ const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
4087
+ const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
4054
4088
  const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
4055
4089
  const segments = getRootBoundarySegments(key);
4056
4090
  return segments !== null && isRootBoundarySegments(segments, basePaths);
@@ -4059,15 +4093,17 @@ async function resolveSsrPageEntries(opts) {
4059
4093
  key,
4060
4094
  moduleAbsPath: path10.resolve(absPageDir, key)
4061
4095
  }));
4062
- const generated = await Promise.all(findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths).map(async (boundary) => ({
4096
+ const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
4063
4097
  key: implicitRootLayoutKey(boundary.segments),
4064
4098
  moduleAbsPath: await writeGeneratedRootLayoutFile({
4065
4099
  appCwdPath: opts.appCwdPath,
4066
4100
  appName: opts.appName,
4067
4101
  boundary,
4068
- includeStInit: hasSt && boundary.segments.length === 0
4102
+ rootSourceAbsPath,
4103
+ includeStInit: hasSt && boundary.segments.length === 0,
4104
+ includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
4069
4105
  }),
4070
- seedAbsPaths: boundary.sourceAbsPath ? [boundary.sourceAbsPath] : []
4106
+ seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
4071
4107
  })));
4072
4108
  const entries = [...base, ...generated];
4073
4109
  entries.sort((a, b) => a.key.localeCompare(b.key));
@@ -7042,6 +7078,13 @@ function formatBytes(bytes) {
7042
7078
  }
7043
7079
  // pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
7044
7080
  import path30 from "path";
7081
+ import { optimize } from "@tailwindcss/node";
7082
+ function prepareCssAsset(command, basePath2, cssText) {
7083
+ if (command !== "build")
7084
+ return cssText;
7085
+ return optimize(cssText, { file: `${basePath2 || "root"}.css`, minify: true }).code;
7086
+ }
7087
+
7045
7088
  class SsrBaseArtifactBuilder {
7046
7089
  #app;
7047
7090
  #command;
@@ -7133,14 +7176,15 @@ class SsrBaseArtifactBuilder {
7133
7176
  }
7134
7177
  async#writeCssAsset(basePath2, cssText) {
7135
7178
  const cssAssetName = basePath2 || "root";
7179
+ const preparedCssText = await prepareCssAsset(this.#command, basePath2, cssText);
7136
7180
  const cssHash = Bun.hash(`${basePath2}
7137
- ${cssText}`).toString(36);
7181
+ ${preparedCssText}`).toString(36);
7138
7182
  const [cssRelPath, cssUrl] = [
7139
7183
  `styles/${cssAssetName}-${cssHash}.css`,
7140
7184
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
7141
7185
  ];
7142
- await Bun.write(path30.join(this.#absArtifactDir, cssRelPath), cssText);
7143
- this.#app.verbose(`[base-artifact] wrote ${cssText.length} bytes of CSS for ${basePath2} -> ${cssRelPath}`);
7186
+ await Bun.write(path30.join(this.#absArtifactDir, cssRelPath), preparedCssText);
7187
+ this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath2} -> ${cssRelPath}`);
7144
7188
  return [basePath2, { cssUrl, cssRelPath }];
7145
7189
  }
7146
7190
  }
@@ -9407,6 +9451,75 @@ import { Box as Box2, Newline, Text as Text2, useInput as useInput2 } from "ink"
9407
9451
  import { useEffect as useEffect3, useState as useState3 } from "react";
9408
9452
  import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
9409
9453
  "use client";
9454
+ // pkgs/@akanjs/devkit/cloud/cloudApi.ts
9455
+ class HttpClient2 {
9456
+ baseUrl;
9457
+ constructor(baseUrl) {
9458
+ this.baseUrl = baseUrl;
9459
+ }
9460
+ async get(url, { headers } = {}) {
9461
+ const response = await fetch(`${this.baseUrl}${url}`, {
9462
+ headers: { "Content-Type": "application/json", ...headers }
9463
+ });
9464
+ return response.json();
9465
+ }
9466
+ async post(url, data, { headers } = {}) {
9467
+ const isFormData = data instanceof FormData;
9468
+ const response = await fetch(`${this.baseUrl}${url}`, {
9469
+ method: "POST",
9470
+ body: isFormData ? data : JSON.stringify(data),
9471
+ headers: isFormData ? headers : { "Content-Type": "application/json", ...headers }
9472
+ });
9473
+ return response.json();
9474
+ }
9475
+ }
9476
+
9477
+ class CloudApi {
9478
+ api;
9479
+ #accessToken = null;
9480
+ constructor(host, { accessToken } = {}) {
9481
+ this.api = new HttpClient2(`${host}/api`);
9482
+ this.#accessToken = accessToken ?? null;
9483
+ }
9484
+ async uploadEnv(devProjectId, fileStream) {
9485
+ const formData = new FormData;
9486
+ formData.append("devProjectId", devProjectId);
9487
+ formData.append("fileStream", await new Response(fileStream).blob());
9488
+ const response = await this.api.post(`/uploadEnv/${devProjectId}`, formData);
9489
+ return response.success;
9490
+ }
9491
+ async downloadEnv(devProjectId) {
9492
+ const response = await this.api.get(`/downloadEnv/${devProjectId}`);
9493
+ return response.success;
9494
+ }
9495
+ async getRemoteAuthToken(remoteId) {
9496
+ if (this.#needRefreshToken())
9497
+ return await this.refreshAuthToken();
9498
+ else if (this.#accessToken)
9499
+ return this.#accessToken;
9500
+ const accessToken = await this.api.get(`/getRemoteAuthToken/${remoteId}`);
9501
+ this.#accessToken = {
9502
+ jwt: accessToken.jwt,
9503
+ refreshToken: accessToken.refreshToken,
9504
+ expiresAt: new Date(accessToken.expiresAt)
9505
+ };
9506
+ return accessToken;
9507
+ }
9508
+ async refreshAuthToken() {
9509
+ const response = await this.api.post(`/refreshRemoteAuthToken`, {
9510
+ refreshToken: this.#accessToken?.refreshToken
9511
+ });
9512
+ this.#accessToken = {
9513
+ jwt: response.jwt,
9514
+ refreshToken: response.refreshToken,
9515
+ expiresAt: new Date(response.expiresAt)
9516
+ };
9517
+ return response;
9518
+ }
9519
+ #needRefreshToken() {
9520
+ return !!(this.#accessToken?.expiresAt && this.#accessToken.expiresAt.getTime() < Date.now() - 1000 * 60 * 60);
9521
+ }
9522
+ }
9410
9523
  // pkgs/@akanjs/devkit/incrementalBuilder/incrementalBuilder.proc.ts
9411
9524
  import { Logger as Logger12 } from "akanjs/common";
9412
9525
 
package/index.js CHANGED
@@ -22,9 +22,12 @@ import { mkdir } from "fs/promises";
22
22
  var basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
23
23
  var configPath = `${basePath}/config.json`;
24
24
  var akanCloudHost = process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? "http://localhost" : "https://cloud.akanjs.com";
25
- var akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""}/\uBA54\u3151`;
25
+ var akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""}/api`;
26
26
  var defaultHostConfig = {};
27
- var defaultAkanGlobalConfig = { cloudHost: {}, llm: null };
27
+ var defaultAkanGlobalConfig = {
28
+ cloudHost: {},
29
+ llm: null
30
+ };
28
31
 
29
32
  // pkgs/@akanjs/devkit/fileSys.ts
30
33
  import { stat } from "fs/promises";
@@ -2138,7 +2141,6 @@ class Executor {
2138
2141
  this.logger = new Logger4(name);
2139
2142
  this.logs = [];
2140
2143
  this.cwdPath = cwdPath;
2141
- //! TODO: 테스트 확인 필요
2142
2144
  }
2143
2145
  #stdout(data) {
2144
2146
  if (Executor.verbose)
@@ -3980,6 +3982,16 @@ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
3980
3982
  }
3981
3983
  return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
3982
3984
  }
3985
+ function hasAncestorRootBoundary(boundary, boundaries) {
3986
+ return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
3987
+ }
3988
+ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
3989
+ const rootLayoutKey = pageKeys.find((key) => {
3990
+ const segments = getRootBoundarySegments(key);
3991
+ return segments !== null && segments.length === 0;
3992
+ });
3993
+ return rootLayoutKey ? path10.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
3994
+ }
3983
3995
  function routePrefixForSegments(segments) {
3984
3996
  const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
3985
3997
  return visible[0] ?? null;
@@ -3996,21 +4008,27 @@ async function writeGeneratedRootLayoutFile(opts) {
3996
4008
  await mkdir3(path10.dirname(absPath), { recursive: true });
3997
4009
  const sourceRel = opts.boundary.sourceAbsPath ? path10.relative(path10.dirname(absPath), opts.boundary.sourceAbsPath).split(path10.sep).join("/") : null;
3998
4010
  const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
4011
+ const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
4012
+ const inheritedSourceRel = inheritedSourceAbsPath ? path10.relative(path10.dirname(absPath), inheritedSourceAbsPath).split(path10.sep).join("/") : null;
4013
+ const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
3999
4014
  const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
4000
4015
  void st;
4001
4016
  ` : `import "@apps/${opts.appName}/client";
4017
+ `;
4018
+ const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
4019
+ ` : `const inheritedLayout = {};
4002
4020
  `;
4003
4021
  const prefix = routePrefixForSegments(opts.boundary.segments);
4004
4022
  const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
4005
4023
  ` : `const UserLayout = ({ children }) => children;
4006
4024
  const userLayout = {};
4007
4025
  `;
4008
- const source = `import type { LayoutProps, PageProps } from "akanjs/client";
4026
+ const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
4009
4027
  import { loadFonts } from "akanjs/client";
4010
4028
  import { System } from "akanjs/ui";
4011
4029
  import { env } from "@apps/${opts.appName}/env/env.client";
4012
- ${clientImport}${userImport}
4013
- const userFonts = userLayout.fonts ?? [];
4030
+ ${clientImport}${inheritedImport}${userImport}
4031
+ const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
4014
4032
  const defaultFonts = userFonts.filter((font) => font.default);
4015
4033
  if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
4016
4034
  const defaultFont = defaultFonts[0];
@@ -4018,7 +4036,9 @@ const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${d
4018
4036
 
4019
4037
  export async function generateHead(props: PageProps) {
4020
4038
  if (userLayout.generateHead) return userLayout.generateHead(props);
4021
- return userLayout.head;
4039
+ if (userLayout.head !== undefined) return userLayout.head;
4040
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
4041
+ return inheritedLayout.head;
4022
4042
  }
4023
4043
 
4024
4044
  export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
@@ -4028,19 +4048,31 @@ export default function GeneratedLayout({ children, params, searchParams }: Layo
4028
4048
  appName=${JSON.stringify(opts.appName)}
4029
4049
  ${prefix ? `prefix=${JSON.stringify(prefix)}
4030
4050
  ` : ""}params={params}
4031
- manifest={userLayout.manifest}
4051
+ manifest={userLayout.manifest ?? inheritedLayout.manifest}
4032
4052
  env={env}
4033
- theme={userLayout.theme}
4053
+ theme={userLayout.theme ?? inheritedLayout.theme}
4034
4054
  fonts={loadFonts(userFonts)}
4035
4055
  className={defaultFontClassName}
4036
- gaTrackingId={userLayout.gaTrackingId}
4037
- layoutStyle={userLayout.layoutStyle}
4038
- reconnect={userLayout.reconnect ?? false}
4056
+ gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
4057
+ layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
4058
+ reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
4039
4059
  >
4040
4060
  <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>
4041
4061
  </System.Provider>
4042
4062
  );
4043
4063
  }
4064
+ ` : `import type { LayoutProps, PageProps } from "akanjs/client";
4065
+ ${inheritedImport}${userImport}
4066
+ export async function generateHead(props: PageProps) {
4067
+ if (userLayout.generateHead) return userLayout.generateHead(props);
4068
+ if (userLayout.head !== undefined) return userLayout.head;
4069
+ if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
4070
+ return inheritedLayout.head;
4071
+ }
4072
+
4073
+ export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
4074
+ return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
4075
+ }
4044
4076
  `;
4045
4077
  await Bun.write(absPath, source);
4046
4078
  return absPath;
@@ -4049,6 +4081,8 @@ async function resolveSsrPageEntries(opts) {
4049
4081
  const absPageDir = path10.resolve(opts.appCwdPath, "page");
4050
4082
  const hasSt = await appHasStModule(opts.appCwdPath);
4051
4083
  const basePaths = opts.basePaths ?? [];
4084
+ const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
4085
+ const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
4052
4086
  const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
4053
4087
  const segments = getRootBoundarySegments(key);
4054
4088
  return segments !== null && isRootBoundarySegments(segments, basePaths);
@@ -4057,15 +4091,17 @@ async function resolveSsrPageEntries(opts) {
4057
4091
  key,
4058
4092
  moduleAbsPath: path10.resolve(absPageDir, key)
4059
4093
  }));
4060
- const generated = await Promise.all(findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths).map(async (boundary) => ({
4094
+ const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
4061
4095
  key: implicitRootLayoutKey(boundary.segments),
4062
4096
  moduleAbsPath: await writeGeneratedRootLayoutFile({
4063
4097
  appCwdPath: opts.appCwdPath,
4064
4098
  appName: opts.appName,
4065
4099
  boundary,
4066
- includeStInit: hasSt && boundary.segments.length === 0
4100
+ rootSourceAbsPath,
4101
+ includeStInit: hasSt && boundary.segments.length === 0,
4102
+ includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
4067
4103
  }),
4068
- seedAbsPaths: boundary.sourceAbsPath ? [boundary.sourceAbsPath] : []
4104
+ seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
4069
4105
  })));
4070
4106
  const entries = [...base, ...generated];
4071
4107
  entries.sort((a, b) => a.key.localeCompare(b.key));
@@ -7040,6 +7076,13 @@ function formatBytes(bytes) {
7040
7076
  }
7041
7077
  // pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
7042
7078
  import path30 from "path";
7079
+ import { optimize } from "@tailwindcss/node";
7080
+ function prepareCssAsset(command, basePath2, cssText) {
7081
+ if (command !== "build")
7082
+ return cssText;
7083
+ return optimize(cssText, { file: `${basePath2 || "root"}.css`, minify: true }).code;
7084
+ }
7085
+
7043
7086
  class SsrBaseArtifactBuilder {
7044
7087
  #app;
7045
7088
  #command;
@@ -7131,14 +7174,15 @@ class SsrBaseArtifactBuilder {
7131
7174
  }
7132
7175
  async#writeCssAsset(basePath2, cssText) {
7133
7176
  const cssAssetName = basePath2 || "root";
7177
+ const preparedCssText = await prepareCssAsset(this.#command, basePath2, cssText);
7134
7178
  const cssHash = Bun.hash(`${basePath2}
7135
- ${cssText}`).toString(36);
7179
+ ${preparedCssText}`).toString(36);
7136
7180
  const [cssRelPath, cssUrl] = [
7137
7181
  `styles/${cssAssetName}-${cssHash}.css`,
7138
7182
  `/_akan/styles/${cssAssetName}-${cssHash}.css`
7139
7183
  ];
7140
- await Bun.write(path30.join(this.#absArtifactDir, cssRelPath), cssText);
7141
- this.#app.verbose(`[base-artifact] wrote ${cssText.length} bytes of CSS for ${basePath2} -> ${cssRelPath}`);
7184
+ await Bun.write(path30.join(this.#absArtifactDir, cssRelPath), preparedCssText);
7185
+ this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath2} -> ${cssRelPath}`);
7142
7186
  return [basePath2, { cssUrl, cssRelPath }];
7143
7187
  }
7144
7188
  }
@@ -9405,6 +9449,75 @@ import { Box as Box2, Newline, Text as Text2, useInput as useInput2 } from "ink"
9405
9449
  import { useEffect as useEffect3, useState as useState3 } from "react";
9406
9450
  import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
9407
9451
  "use client";
9452
+ // pkgs/@akanjs/devkit/cloud/cloudApi.ts
9453
+ class HttpClient2 {
9454
+ baseUrl;
9455
+ constructor(baseUrl) {
9456
+ this.baseUrl = baseUrl;
9457
+ }
9458
+ async get(url, { headers } = {}) {
9459
+ const response = await fetch(`${this.baseUrl}${url}`, {
9460
+ headers: { "Content-Type": "application/json", ...headers }
9461
+ });
9462
+ return response.json();
9463
+ }
9464
+ async post(url, data, { headers } = {}) {
9465
+ const isFormData = data instanceof FormData;
9466
+ const response = await fetch(`${this.baseUrl}${url}`, {
9467
+ method: "POST",
9468
+ body: isFormData ? data : JSON.stringify(data),
9469
+ headers: isFormData ? headers : { "Content-Type": "application/json", ...headers }
9470
+ });
9471
+ return response.json();
9472
+ }
9473
+ }
9474
+
9475
+ class CloudApi {
9476
+ api;
9477
+ #accessToken = null;
9478
+ constructor(host, { accessToken } = {}) {
9479
+ this.api = new HttpClient2(`${host}/api`);
9480
+ this.#accessToken = accessToken ?? null;
9481
+ }
9482
+ async uploadEnv(devProjectId, fileStream) {
9483
+ const formData = new FormData;
9484
+ formData.append("devProjectId", devProjectId);
9485
+ formData.append("fileStream", await new Response(fileStream).blob());
9486
+ const response = await this.api.post(`/uploadEnv/${devProjectId}`, formData);
9487
+ return response.success;
9488
+ }
9489
+ async downloadEnv(devProjectId) {
9490
+ const response = await this.api.get(`/downloadEnv/${devProjectId}`);
9491
+ return response.success;
9492
+ }
9493
+ async getRemoteAuthToken(remoteId) {
9494
+ if (this.#needRefreshToken())
9495
+ return await this.refreshAuthToken();
9496
+ else if (this.#accessToken)
9497
+ return this.#accessToken;
9498
+ const accessToken = await this.api.get(`/getRemoteAuthToken/${remoteId}`);
9499
+ this.#accessToken = {
9500
+ jwt: accessToken.jwt,
9501
+ refreshToken: accessToken.refreshToken,
9502
+ expiresAt: new Date(accessToken.expiresAt)
9503
+ };
9504
+ return accessToken;
9505
+ }
9506
+ async refreshAuthToken() {
9507
+ const response = await this.api.post(`/refreshRemoteAuthToken`, {
9508
+ refreshToken: this.#accessToken?.refreshToken
9509
+ });
9510
+ this.#accessToken = {
9511
+ jwt: response.jwt,
9512
+ refreshToken: response.refreshToken,
9513
+ expiresAt: new Date(response.expiresAt)
9514
+ };
9515
+ return response;
9516
+ }
9517
+ #needRefreshToken() {
9518
+ return !!(this.#accessToken?.expiresAt && this.#accessToken.expiresAt.getTime() < Date.now() - 1000 * 60 * 60);
9519
+ }
9520
+ }
9408
9521
  // pkgs/@akanjs/cli/application/application.command.ts
9409
9522
  import { select as select6 } from "@inquirer/prompts";
9410
9523
 
@@ -10238,6 +10351,10 @@ class PackageRunner extends runner("package") {
10238
10351
  pkg.generateTsconfigJson()
10239
10352
  ]);
10240
10353
  }
10354
+ await this.#copyPackageReadmes(pkg);
10355
+ }
10356
+ async#copyPackageReadmes(pkg) {
10357
+ await Promise.all(["README.md", "README.ko.md"].map((fileName) => pkg.cp(fileName, `${pkg.dist.cwdPath}/${fileName}`)));
10241
10358
  }
10242
10359
  async updateWorskpaceRootPackageJson(workspace, rootPackageJson) {
10243
10360
  const templatePath = "pkgs/@akanjs/cli/templates/workspaceRoot/package.json.template";
@@ -10310,7 +10427,12 @@ async function getLatestPackageVersion(packageName, tag = "latest", registryUrl)
10310
10427
 
10311
10428
  // pkgs/@akanjs/cli/cloud/cloud.runner.ts
10312
10429
  class CloudRunner extends runner("cloud") {
10313
- #akanFrameworkPackages = new Set(["akanjs", "@akanjs/devkit", "@akanjs/cli", "create-akan-workspace"]);
10430
+ #akanFrameworkPackages = new Set([
10431
+ "akanjs",
10432
+ "@akanjs/devkit",
10433
+ "@akanjs/cli",
10434
+ "create-akan-workspace"
10435
+ ]);
10314
10436
  #getRegistryArgs(registryUrl) {
10315
10437
  return registryUrl ? ["--registry", getNpmRegistryUrl(registryUrl)] : [];
10316
10438
  }
@@ -10445,7 +10567,13 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10445
10567
  }
10446
10568
  await Promise.all(akanPkgs.map(async (library) => {
10447
10569
  Logger15.info(`Publishing ${library}@${nextVersion} to ${registry ?? "npm"}...`);
10448
- await workspace.spawn("npm", ["publish", "--tag", tag, ...this.#getRegistryArgs(registry), ...this.#getLocalRegistryAuthArgs(registry)], {
10570
+ await workspace.spawn("npm", [
10571
+ "publish",
10572
+ "--tag",
10573
+ tag,
10574
+ ...this.#getRegistryArgs(registry),
10575
+ ...this.#getLocalRegistryAuthArgs(registry)
10576
+ ], {
10449
10577
  cwd: path38.join(workspace.workspaceRoot, "dist/pkgs", library),
10450
10578
  env: this.#getRegistryEnv(registry),
10451
10579
  stdio: "inherit"
@@ -10462,7 +10590,14 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10462
10590
  await workspace.spawn("bun", ["update", "-g", "akanjs", "--latest", `--tag=${tag}`, ...registryArgs], { env });
10463
10591
  else
10464
10592
  await Promise.all([
10465
- workspace.spawn("bun", ["update", "-g", "akanjs", "--latest", `--tag=${tag}`, ...registryArgs], { env }),
10593
+ workspace.spawn("bun", [
10594
+ "update",
10595
+ "-g",
10596
+ "akanjs",
10597
+ "--latest",
10598
+ `--tag=${tag}`,
10599
+ ...registryArgs
10600
+ ], { env }),
10466
10601
  this.#updateAkanPkgs(workspace, tag, registry)
10467
10602
  ]);
10468
10603
  }
@@ -10486,7 +10621,12 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10486
10621
  }
10487
10622
  #normalizeAkanPackageJson(packageJson, packageName, version) {
10488
10623
  const normalized = { ...packageJson, version };
10489
- for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
10624
+ for (const field of [
10625
+ "dependencies",
10626
+ "devDependencies",
10627
+ "peerDependencies",
10628
+ "optionalDependencies"
10629
+ ]) {
10490
10630
  const dependencies = normalized[field];
10491
10631
  if (!dependencies)
10492
10632
  continue;
@@ -10497,10 +10637,28 @@ ${chalk7.green("\u27A4")} Authentication Required`));
10497
10637
  }
10498
10638
  return normalized;
10499
10639
  }
10640
+ async downloadEnv(workspace) {
10641
+ const repoName = workspace.repoName;
10642
+ const config = await getHostConfig();
10643
+ const self = config.auth ? await getSelf(config.auth.token) : null;
10644
+ if (!self)
10645
+ throw new Error("Not logged in");
10646
+ const res = await fetch(`${akanCloudUrl}/api/akasys/akasys/${repoName}`, {
10647
+ headers: { Authorization: `Bearer ${config.auth?.token}` }
10648
+ });
10649
+ const env = await res.json();
10650
+ Logger15.info(`Downloading environment variables from cloud...`);
10651
+ Logger15.info(`Environment variables: ${JSON.stringify(env.env, null, 2)}`);
10652
+ }
10653
+ async uploadEnv(workspace) {}
10500
10654
  }
10501
10655
 
10502
10656
  // pkgs/@akanjs/cli/cloud/cloud.script.ts
10503
- class CloudScript extends script("cloud", [CloudRunner, ApplicationScript, PackageScript]) {
10657
+ class CloudScript extends script("cloud", [
10658
+ CloudRunner,
10659
+ ApplicationScript,
10660
+ PackageScript
10661
+ ]) {
10504
10662
  async login(workspace) {
10505
10663
  await this.cloudRunner.login();
10506
10664
  }
@@ -10517,6 +10675,12 @@ class CloudScript extends script("cloud", [CloudRunner, ApplicationScript, Packa
10517
10675
  const session = new AiSession("general", { workspace, isContinued: true });
10518
10676
  await session.ask(question);
10519
10677
  }
10678
+ async downloadEnv(workspace) {
10679
+ await this.cloudRunner.downloadEnv(workspace);
10680
+ }
10681
+ async uploadEnv(workspace) {
10682
+ await this.cloudRunner.uploadEnv(workspace);
10683
+ }
10520
10684
  async deployAkan(workspace, { test = true, registryUrl } = {}) {
10521
10685
  const akanPkgs = await this.cloudRunner.getAkanPkgs(workspace);
10522
10686
  await this.packageScript.updateWorskpaceRootPackageJson(workspace);
@@ -10557,7 +10721,10 @@ class CloudCommand extends command("cloud", [CloudScript], ({ public: target })
10557
10721
  ask: target({ desc: "Ask AI assistant a question about your project" }).option("question", String, { ask: "question to ask" }).with(Workspace).exec(async function(question, workspace) {
10558
10722
  await this.cloudScript.ask(question, workspace);
10559
10723
  }),
10560
- deployAkan: target({ devOnly: true, desc: "Deploy Akan.js framework to cloud (internal use)" }).option("test", Boolean, { desc: "test the deployment", default: true }).option("registry", String, {
10724
+ deployAkan: target({
10725
+ devOnly: true,
10726
+ desc: "Deploy Akan.js framework to cloud (internal use)"
10727
+ }).option("test", Boolean, { desc: "test the deployment", default: true }).option("registry", String, {
10561
10728
  desc: "registry target for publishing Akan packages",
10562
10729
  ask: "Select a registry target",
10563
10730
  enum: [
@@ -10582,7 +10749,15 @@ class CloudCommand extends command("cloud", [CloudScript], ({ public: target })
10582
10749
  { label: "local", value: "local" }
10583
10750
  ]
10584
10751
  }).exec(async function(workspace, tag, registry) {
10585
- await this.cloudScript.update(workspace, tag, { registryUrl: resolveRegistryUrl(registry) });
10752
+ await this.cloudScript.update(workspace, tag, {
10753
+ registryUrl: resolveRegistryUrl(registry)
10754
+ });
10755
+ }),
10756
+ downloadEnv: target({ desc: "Download environment variables from cloud" }).with(Workspace).exec(async function(workspace) {
10757
+ await this.cloudScript.downloadEnv(workspace);
10758
+ }),
10759
+ uploadEnv: target({ desc: "Upload environment variables to cloud" }).with(Workspace).exec(async function(workspace) {
10760
+ await this.cloudScript.uploadEnv(workspace);
10586
10761
  })
10587
10762
  })) {
10588
10763
  }
@@ -10603,7 +10778,7 @@ class GuidelinePrompt extends Prompter {
10603
10778
  return page;
10604
10779
  if (page.endsWith(".tsx"))
10605
10780
  return page;
10606
- return `apps/angelo/page${page}`;
10781
+ return `apps/akan/page${page}`;
10607
10782
  }
10608
10783
  async#getScanFilePaths(matchPattern, { avoidDirs = ["node_modules", ".next"], filterText } = {}) {
10609
10784
  const glob = new Bun.Glob(matchPattern);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.1.0",
3
+ "version": "2.1.1-rc.0",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -33,8 +33,9 @@
33
33
  "@langchain/core": "^1.1.47",
34
34
  "@langchain/deepseek": "^1.0.26",
35
35
  "@langchain/openai": "^1.4.6",
36
+ "@tailwindcss/node": "^4.3.0",
36
37
  "@trapezedev/project": "^7.1.4",
37
- "akanjs": "2.1.0",
38
+ "akanjs": "2.1.1-rc.0",
38
39
  "chalk": "^5.6.2",
39
40
  "commander": "^14.0.3",
40
41
  "daisyui": "^5.5.20",