@akanjs/devkit 1.0.20 → 2.1.0-rc.1

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 (195) hide show
  1. package/README.ko.md +65 -0
  2. package/README.md +62 -6
  3. package/aiEditor.ts +304 -0
  4. package/akanApp/akanApp.host.ts +393 -0
  5. package/akanApp/index.ts +1 -0
  6. package/akanConfig/akanConfig.test.ts +236 -0
  7. package/akanConfig/akanConfig.ts +384 -0
  8. package/akanConfig/index.ts +2 -0
  9. package/akanConfig/types.ts +23 -0
  10. package/applicationBuildReporter.ts +69 -0
  11. package/applicationBuildRunner.ts +302 -0
  12. package/applicationReleasePackager.ts +206 -0
  13. package/artifact/implicitRootLayout.ts +155 -0
  14. package/artifact/index.ts +1 -0
  15. package/artifact/routeSeedIndex.test.ts +98 -0
  16. package/artifact/routeSeedIndex.ts +130 -0
  17. package/auth.ts +41 -0
  18. package/builder.ts +164 -0
  19. package/capacitor.base.config.ts +88 -0
  20. package/capacitorApp.ts +440 -0
  21. package/commandDecorators/argMeta.ts +102 -0
  22. package/commandDecorators/command.ts +351 -0
  23. package/commandDecorators/commandBuilder.ts +224 -0
  24. package/commandDecorators/commandDecorators.test.ts +212 -0
  25. package/commandDecorators/commandMeta.ts +7 -0
  26. package/commandDecorators/dependencyBuilder.ts +100 -0
  27. package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
  28. package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
  29. package/commandDecorators/targetMeta.ts +31 -0
  30. package/commandDecorators/types.ts +10 -0
  31. package/constants.ts +25 -0
  32. package/createTunnel.ts +36 -0
  33. package/dependencyScanner.ts +357 -0
  34. package/devkitUtils.test.ts +259 -0
  35. package/executors.test.ts +315 -0
  36. package/executors.ts +1390 -0
  37. package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
  38. package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
  39. package/fileSys.ts +39 -0
  40. package/frontendBuild/allRoutesBuilder.ts +103 -0
  41. package/frontendBuild/buildRouteClient.test.ts +190 -0
  42. package/frontendBuild/clientBuildTypes.ts +114 -0
  43. package/frontendBuild/clientEntriesBundler.ts +303 -0
  44. package/frontendBuild/clientEntryDiscovery.ts +199 -0
  45. package/frontendBuild/csrArtifactBuilder.ts +237 -0
  46. package/frontendBuild/cssCompiler.ts +286 -0
  47. package/frontendBuild/cssImportResolver.ts +116 -0
  48. package/frontendBuild/fontOptimizer.ts +427 -0
  49. package/frontendBuild/frontendBuild.test.ts +204 -0
  50. package/frontendBuild/hmrChangeClassifier.ts +28 -0
  51. package/frontendBuild/hmrWatcher.ts +102 -0
  52. package/frontendBuild/index.ts +18 -0
  53. package/frontendBuild/pagesBundleBuilder.ts +137 -0
  54. package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
  55. package/frontendBuild/precompressArtifacts.ts +59 -0
  56. package/frontendBuild/routeClientBuilder.ts +290 -0
  57. package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
  58. package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
  59. package/frontendBuild/vendorSpecifiers.ts +16 -0
  60. package/frontendBuild/watchRootResolver.ts +28 -0
  61. package/getCredentials.ts +19 -0
  62. package/getDirname.ts +3 -0
  63. package/getModelFileData.ts +59 -0
  64. package/getRelatedCnsts.ts +313 -0
  65. package/guideline.ts +19 -0
  66. package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
  67. package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
  68. package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
  69. package/incrementalBuilder/index.ts +1 -0
  70. package/{esm/src/index.js → index.ts} +28 -15
  71. package/lint/no-deep-internal-import.grit +25 -0
  72. package/lint/no-import-client-functions.grit +32 -0
  73. package/lint/no-import-external-library.grit +21 -0
  74. package/lint/no-js-private-class-method.grit +42 -0
  75. package/lint/no-use-client-in-server.grit +7 -0
  76. package/lint/non-scalar-props-restricted.grit +13 -0
  77. package/linter.ts +271 -0
  78. package/mobile/index.ts +1 -0
  79. package/mobile/mobileTarget.test.ts +53 -0
  80. package/mobile/mobileTarget.ts +88 -0
  81. package/package.json +48 -31
  82. package/prompter.ts +72 -0
  83. package/scanInfo.ts +606 -0
  84. package/selectModel.ts +11 -0
  85. package/{esm/src/spinner.js → spinner.ts} +22 -28
  86. package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
  87. package/sshTunnel.ts +152 -0
  88. package/{esm/src/streamAi.js → streamAi.ts} +18 -12
  89. package/transforms/barrelAnalyzer.ts +278 -0
  90. package/transforms/barrelImportsPlugin.ts +504 -0
  91. package/transforms/externalizeFrameworkPlugin.ts +185 -0
  92. package/transforms/index.ts +5 -0
  93. package/transforms/rscUseClientTransform.ts +59 -0
  94. package/transforms/transforms.test.ts +208 -0
  95. package/transforms/useClientBundlePlugin.ts +47 -0
  96. package/tsconfig.json +37 -0
  97. package/typeChecker.ts +264 -0
  98. package/types.ts +44 -0
  99. package/ui/MultiScrollList.tsx +242 -0
  100. package/ui/ScrollList.tsx +107 -0
  101. package/ui/index.ts +2 -0
  102. package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
  103. package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
  104. package/cjs/index.js +0 -21
  105. package/cjs/src/aiEditor.js +0 -311
  106. package/cjs/src/auth.js +0 -72
  107. package/cjs/src/builder.js +0 -114
  108. package/cjs/src/capacitorApp.js +0 -313
  109. package/cjs/src/commandDecorators/argMeta.js +0 -88
  110. package/cjs/src/commandDecorators/command.js +0 -324
  111. package/cjs/src/commandDecorators/commandMeta.js +0 -30
  112. package/cjs/src/commandDecorators/helpFormatter.js +0 -211
  113. package/cjs/src/commandDecorators/index.js +0 -31
  114. package/cjs/src/commandDecorators/targetMeta.js +0 -57
  115. package/cjs/src/commandDecorators/types.js +0 -15
  116. package/cjs/src/constants.js +0 -46
  117. package/cjs/src/createTunnel.js +0 -49
  118. package/cjs/src/dependencyScanner.js +0 -220
  119. package/cjs/src/executors.js +0 -964
  120. package/cjs/src/extractDeps.js +0 -103
  121. package/cjs/src/fileEditor.js +0 -120
  122. package/cjs/src/getCredentials.js +0 -44
  123. package/cjs/src/getDirname.js +0 -38
  124. package/cjs/src/getModelFileData.js +0 -66
  125. package/cjs/src/getRelatedCnsts.js +0 -260
  126. package/cjs/src/guideline.js +0 -15
  127. package/cjs/src/index.js +0 -65
  128. package/cjs/src/linter.js +0 -238
  129. package/cjs/src/prompter.js +0 -85
  130. package/cjs/src/scanInfo.js +0 -491
  131. package/cjs/src/selectModel.js +0 -46
  132. package/cjs/src/spinner.js +0 -93
  133. package/cjs/src/streamAi.js +0 -62
  134. package/cjs/src/typeChecker.js +0 -207
  135. package/cjs/src/types.js +0 -15
  136. package/cjs/src/uploadRelease.js +0 -112
  137. package/cjs/src/useStdoutDimensions.js +0 -43
  138. package/esm/index.js +0 -1
  139. package/esm/src/aiEditor.js +0 -282
  140. package/esm/src/auth.js +0 -42
  141. package/esm/src/builder.js +0 -81
  142. package/esm/src/commandDecorators/argMeta.js +0 -54
  143. package/esm/src/commandDecorators/command.js +0 -290
  144. package/esm/src/commandDecorators/commandMeta.js +0 -7
  145. package/esm/src/commandDecorators/targetMeta.js +0 -33
  146. package/esm/src/commandDecorators/types.js +0 -0
  147. package/esm/src/constants.js +0 -17
  148. package/esm/src/createTunnel.js +0 -26
  149. package/esm/src/dependencyScanner.js +0 -187
  150. package/esm/src/executors.js +0 -928
  151. package/esm/src/getCredentials.js +0 -11
  152. package/esm/src/getDirname.js +0 -5
  153. package/esm/src/getModelFileData.js +0 -33
  154. package/esm/src/getRelatedCnsts.js +0 -221
  155. package/esm/src/guideline.js +0 -0
  156. package/esm/src/linter.js +0 -205
  157. package/esm/src/prompter.js +0 -51
  158. package/esm/src/scanInfo.js +0 -455
  159. package/esm/src/selectModel.js +0 -13
  160. package/esm/src/typeChecker.js +0 -174
  161. package/esm/src/types.js +0 -0
  162. package/index.d.ts +0 -1
  163. package/src/aiEditor.d.ts +0 -50
  164. package/src/auth.d.ts +0 -9
  165. package/src/builder.d.ts +0 -18
  166. package/src/capacitorApp.d.ts +0 -39
  167. package/src/commandDecorators/argMeta.d.ts +0 -67
  168. package/src/commandDecorators/command.d.ts +0 -2
  169. package/src/commandDecorators/commandMeta.d.ts +0 -2
  170. package/src/commandDecorators/helpFormatter.d.ts +0 -3
  171. package/src/commandDecorators/index.d.ts +0 -6
  172. package/src/commandDecorators/targetMeta.d.ts +0 -19
  173. package/src/commandDecorators/types.d.ts +0 -1
  174. package/src/constants.d.ts +0 -26
  175. package/src/createTunnel.d.ts +0 -8
  176. package/src/dependencyScanner.d.ts +0 -23
  177. package/src/executors.d.ts +0 -296
  178. package/src/extractDeps.d.ts +0 -7
  179. package/src/fileEditor.d.ts +0 -16
  180. package/src/getCredentials.d.ts +0 -12
  181. package/src/getDirname.d.ts +0 -1
  182. package/src/getModelFileData.d.ts +0 -16
  183. package/src/getRelatedCnsts.d.ts +0 -53
  184. package/src/guideline.d.ts +0 -19
  185. package/src/index.d.ts +0 -23
  186. package/src/linter.d.ts +0 -109
  187. package/src/prompter.d.ts +0 -14
  188. package/src/scanInfo.d.ts +0 -82
  189. package/src/selectModel.d.ts +0 -1
  190. package/src/spinner.d.ts +0 -20
  191. package/src/streamAi.d.ts +0 -6
  192. package/src/typeChecker.d.ts +0 -52
  193. package/src/types.d.ts +0 -31
  194. package/src/uploadRelease.d.ts +0 -10
  195. package/src/useStdoutDimensions.d.ts +0 -1
@@ -0,0 +1,212 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+ import { App, getArgMetas, Lib, Module, Pkg, Sys, Workspace } from "./argMeta";
3
+ import { command } from "./commandBuilder";
4
+ import {
5
+ assertUniqueDependencies,
6
+ CommandContainer,
7
+ getDependencyKey,
8
+ injectDependencies,
9
+ runner,
10
+ script,
11
+ } from "./dependencyBuilder";
12
+ import { formatCommandHelp, formatHelp } from "./helpFormatter";
13
+ import { getTargetMetas } from "./targetMeta";
14
+
15
+ const ansiPattern = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
16
+ const stripAnsi = (value: string) => value.replace(ansiPattern, "");
17
+
18
+ describe("command helper metadata", () => {
19
+ afterEach(() => {
20
+ CommandContainer.clear();
21
+ });
22
+
23
+ test("records targets, args, options, and internal executor tokens", () => {
24
+ class ExampleRunner extends runner("example") {
25
+ run() {
26
+ return "ok";
27
+ }
28
+ }
29
+
30
+ class ExampleScript extends script("example", [ExampleRunner]) {
31
+ callRunner() {
32
+ return this.exampleRunner.run();
33
+ }
34
+ }
35
+
36
+ class ExampleCommand extends command("example", [ExampleScript], ({ public: publicTarget, dev }) => ({
37
+ createModule: publicTarget({ short: true, desc: "Create a module" })
38
+ .arg("moduleName", String, { desc: "module name", example: "user" })
39
+ .option("count", Number, { desc: "count", default: 1 })
40
+ .option("force", Boolean, { desc: "force", default: false })
41
+ .with(Workspace, App, Lib, Sys, Pkg, Module)
42
+ .exec(function (moduleName, count, force, workspace, app, lib, sys, pkg, module) {
43
+ expect(this.exampleScript.callRunner()).toBe("ok");
44
+ return { moduleName, count, force, workspace, app, lib, sys, pkg, module };
45
+ }),
46
+ devOnlyTask: dev({ devOnly: true, desc: "Hidden task" })
47
+ .arg("targetName", String)
48
+ .exec((targetName) => targetName),
49
+ })) {}
50
+
51
+ const targetMetas = getTargetMetas(ExampleCommand);
52
+ expect(targetMetas.map((target) => target.key)).toEqual(["createModule", "devOnlyTask"]);
53
+ expect(targetMetas[0]?.targetOption).toEqual({
54
+ runsOnWorkspaceRoot: true,
55
+ short: true,
56
+ desc: "Create a module",
57
+ type: "public",
58
+ });
59
+ expect(targetMetas[1]?.targetOption).toEqual({
60
+ runsOnWorkspaceRoot: true,
61
+ devOnly: true,
62
+ desc: "Hidden task",
63
+ type: "dev",
64
+ });
65
+
66
+ const [allArgMetas, optionMetas, internalArgMetas] = getArgMetas(ExampleCommand, "createModule");
67
+ expect(allArgMetas.map((arg) => [arg.type, arg.idx])).toEqual([
68
+ ["Argument", 0],
69
+ ["Option", 1],
70
+ ["Option", 2],
71
+ ["Workspace", 3],
72
+ ["App", 4],
73
+ ["Lib", 5],
74
+ ["Sys", 6],
75
+ ["Pkg", 7],
76
+ ["Module", 8],
77
+ ]);
78
+ expect(optionMetas.map((arg) => arg.name)).toEqual(["count", "force"]);
79
+ const internalArgTypes = internalArgMetas.map((arg) => arg.type as string);
80
+ expect(internalArgTypes).toEqual(["Argument", "Workspace", "App", "Lib", "Sys", "Pkg", "Module"]);
81
+
82
+ const commandInstance = CommandContainer.get(ExampleCommand);
83
+ const result = targetMetas[0]?.handler.call(
84
+ commandInstance,
85
+ "profile",
86
+ 2,
87
+ true,
88
+ { name: "workspace" },
89
+ { name: "app" },
90
+ { name: "lib" },
91
+ { name: "sys" },
92
+ { name: "pkg" },
93
+ { name: "module" },
94
+ );
95
+ expect(result).toMatchObject({ moduleName: "profile", count: 2, force: true });
96
+ });
97
+
98
+ test("formats global and command help from helper metadata", () => {
99
+ class HelpCommand extends command("help", ({ public: publicTarget, dev }) => ({
100
+ buildApp: publicTarget({ desc: "Build app" })
101
+ .with(App)
102
+ .option("write", Boolean, { desc: "write generated files", default: true })
103
+ .option("mode", String, {
104
+ desc: "build mode",
105
+ enum: [
106
+ { label: "Fast", value: "fast" },
107
+ { label: "Full", value: "full" },
108
+ ],
109
+ })
110
+ .exec(() => undefined),
111
+ generateModule: publicTarget({ desc: "Generate module" })
112
+ .with(Module)
113
+ .arg("modelName", String, { desc: "model name" })
114
+ .exec(() => undefined),
115
+ hiddenTask: dev({ devOnly: true, desc: "Hidden task" }).exec(() => undefined),
116
+ })) {}
117
+
118
+ const globalHelp = stripAnsi(formatHelp([HelpCommand], "1.2.3"));
119
+ expect(globalHelp).toContain("Version: 1.2.3");
120
+ expect(globalHelp).toContain("build-app [app]");
121
+ expect(globalHelp).toContain("generate-module [sys:module] [modelName]");
122
+ expect(globalHelp).not.toContain("hidden-task");
123
+
124
+ const commandHelp = stripAnsi(formatCommandHelp(HelpCommand, "buildApp"));
125
+ expect(commandHelp).toContain("akan build-app [app]");
126
+ expect(commandHelp).toContain("--write");
127
+ expect(commandHelp).toContain("[default: true]");
128
+ expect(commandHelp).toContain("Fast, Full");
129
+ });
130
+ });
131
+
132
+ describe("command helper dependency injection", () => {
133
+ afterEach(() => {
134
+ CommandContainer.clear();
135
+ });
136
+
137
+ test("creates stable dependency keys and singleton injected instances", () => {
138
+ class StableRunner extends runner("stable") {
139
+ readonly id = Math.random();
140
+ }
141
+
142
+ class StableScript extends script("stable", [StableRunner]) {}
143
+
144
+ expect(StableRunner.refName).toBe("stable");
145
+ expect(StableRunner.dependencyKind).toBe("runner");
146
+ expect(StableRunner.dependencyKey).toBe("stableRunner");
147
+ expect(StableScript.dependencyKind).toBe("script");
148
+ expect(StableScript.dependencyKey).toBe("stableScript");
149
+ expect(getDependencyKey(StableScript)).toBe("stableScript");
150
+
151
+ const first = CommandContainer.get(StableScript);
152
+ const second = CommandContainer.get(StableScript);
153
+ expect(first).toBe(second);
154
+ expect(first.stableRunner).toBe(CommandContainer.get(StableRunner));
155
+ expect(Object.keys(first)).not.toContain("stableRunner");
156
+
157
+ expect(() => {
158
+ first.stableRunner = new StableRunner();
159
+ }).toThrow();
160
+ });
161
+
162
+ test("rejects duplicate dependencies by class or generated key", () => {
163
+ class OneRunner extends runner("duplicate") {}
164
+ class AnotherRunner extends runner("duplicate") {}
165
+
166
+ expect(() => assertUniqueDependencies([OneRunner, OneRunner])).toThrow("Duplicate command dependency class");
167
+ expect(() => assertUniqueDependencies([OneRunner, AnotherRunner])).toThrow(
168
+ 'Duplicate command dependency key "duplicateRunner"',
169
+ );
170
+ expect(() => script("broken", [OneRunner, AnotherRunner])).toThrow(
171
+ 'Duplicate command dependency key "duplicateRunner"',
172
+ );
173
+ });
174
+
175
+ test("injectDependencies detects circular command dependencies", () => {
176
+ class CircularA {
177
+ static readonly refName = "circularA";
178
+ static readonly dependencyKind = "script";
179
+ static readonly dependencyKey = "circularAScript";
180
+
181
+ constructor() {
182
+ CommandContainer.get(CircularB);
183
+ }
184
+ }
185
+
186
+ class CircularB {
187
+ static readonly refName = "circularB";
188
+ static readonly dependencyKind = "script";
189
+ static readonly dependencyKey = "circularBScript";
190
+
191
+ constructor() {
192
+ CommandContainer.get(CircularA);
193
+ }
194
+ }
195
+
196
+ expect(() => CommandContainer.get(CircularA)).toThrow("Circular command dependency");
197
+ });
198
+
199
+ test("injects explicit dependency lists into arbitrary objects", () => {
200
+ class DirectRunner extends runner("direct") {
201
+ value = "runner";
202
+ }
203
+
204
+ const target = {};
205
+ const injected = injectDependencies(target, [DirectRunner]);
206
+ expect(injected.directRunner.value).toBe("runner");
207
+
208
+ const descriptor = Object.getOwnPropertyDescriptor(injected, "directRunner");
209
+ expect(descriptor?.enumerable).toBe(false);
210
+ expect(descriptor?.writable).toBe(false);
211
+ });
212
+ });
@@ -0,0 +1,7 @@
1
+ import type { Cls } from "./types";
2
+
3
+ export const Commands = () => {
4
+ return (target: Cls) => {
5
+ // not implemented yet
6
+ };
7
+ };
@@ -0,0 +1,100 @@
1
+ import type { DependencyCls, DependencyKey, DependencyKind } from "./types";
2
+
3
+ type UnionToIntersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (
4
+ value: infer Intersection,
5
+ ) => void
6
+ ? Intersection
7
+ : never;
8
+
9
+ type DependencyToField<Dep> = Dep extends DependencyCls<infer Instance, infer Key> ? { [K in Key]: Instance } : never;
10
+
11
+ export type DependencyInstanceMap<Deps extends readonly DependencyCls[]> = [Deps[number]] extends [never]
12
+ ? Record<PropertyKey, never>
13
+ : UnionToIntersection<DependencyToField<Deps[number]>>;
14
+
15
+ const capitalize = (value: string) => value.slice(0, 1).toUpperCase() + value.slice(1);
16
+
17
+ const createDependencyKey = <RefName extends string, Kind extends DependencyKind>(
18
+ refName: RefName,
19
+ kind: Kind,
20
+ ): DependencyKey<RefName, Kind> => `${refName}${capitalize(kind)}` as DependencyKey<RefName, Kind>;
21
+
22
+ export class CommandContainer {
23
+ static #instances = new Map<DependencyCls, unknown>();
24
+ static #resolving = new Set<DependencyCls>();
25
+
26
+ static get<T>(dep: DependencyCls<T>): T {
27
+ const instance = CommandContainer.#instances.get(dep);
28
+ if (instance) return instance as T;
29
+ if (CommandContainer.#resolving.has(dep)) throw new Error(`Circular command dependency: ${dep.name}`);
30
+ CommandContainer.#resolving.add(dep);
31
+ try {
32
+ const nextInstance = new dep();
33
+ CommandContainer.#instances.set(dep, nextInstance);
34
+ return nextInstance;
35
+ } finally {
36
+ CommandContainer.#resolving.delete(dep);
37
+ }
38
+ }
39
+
40
+ static clear() {
41
+ CommandContainer.#instances.clear();
42
+ CommandContainer.#resolving.clear();
43
+ }
44
+ }
45
+
46
+ export const getDependencyKey = (dep: DependencyCls) => dep.dependencyKey;
47
+
48
+ export const assertUniqueDependencies = (deps: readonly DependencyCls[]) => {
49
+ const keys = new Map<string, DependencyCls>();
50
+ const classes = new Set<DependencyCls>();
51
+ for (const dep of deps) {
52
+ if (classes.has(dep)) throw new Error(`Duplicate command dependency class: ${dep.name}`);
53
+ classes.add(dep);
54
+ const key = getDependencyKey(dep);
55
+ const existing = keys.get(key);
56
+ if (existing) throw new Error(`Duplicate command dependency key "${key}": ${existing.name}, ${dep.name}`);
57
+ keys.set(key, dep);
58
+ }
59
+ };
60
+
61
+ export const injectDependencies = <Deps extends readonly DependencyCls[]>(target: object, deps: Deps) => {
62
+ assertUniqueDependencies(deps);
63
+ for (const dep of deps) {
64
+ Object.defineProperty(target, getDependencyKey(dep), {
65
+ configurable: true,
66
+ enumerable: false,
67
+ value: CommandContainer.get(dep),
68
+ writable: false,
69
+ });
70
+ }
71
+ return target as typeof target & DependencyInstanceMap<Deps>;
72
+ };
73
+
74
+ export const runner = <RefName extends string>(refName: RefName) => {
75
+ class RunnerBase {
76
+ static readonly refName = refName;
77
+ static readonly dependencyKind = "runner";
78
+ static readonly dependencyKey = createDependencyKey(refName, "runner");
79
+ }
80
+
81
+ return RunnerBase as unknown as DependencyCls<InstanceType<typeof RunnerBase>, DependencyKey<RefName, "runner">>;
82
+ };
83
+
84
+ export const script = <RefName extends string, Deps extends readonly DependencyCls[]>(
85
+ refName: RefName,
86
+ deps: Deps = [] as unknown as Deps,
87
+ ) => {
88
+ assertUniqueDependencies(deps);
89
+ class ScriptBase {
90
+ static readonly refName = refName;
91
+ static readonly dependencyKind = "script";
92
+ static readonly dependencyKey = createDependencyKey(refName, "script");
93
+
94
+ constructor() {
95
+ injectDependencies(this, deps);
96
+ }
97
+ }
98
+
99
+ return ScriptBase as unknown as DependencyCls<DependencyInstanceMap<Deps>, DependencyKey<RefName, "script">>;
100
+ };
@@ -1,75 +1,103 @@
1
1
  import chalk from "chalk";
2
- import { getArgMetas } from "./argMeta";
3
- import { getTargetMetas } from "./targetMeta";
4
- const camelToKebabCase = (str) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
5
- const groupCommands = (commands) => {
6
- const groups = /* @__PURE__ */ new Map();
2
+
3
+ import { type EnumChoice, getArgMetas } from "./argMeta";
4
+ import { type CommandCls, getTargetMetas } from "./targetMeta";
5
+
6
+ const camelToKebabCase = (str: string) => str.replace(/([A-Z])/g, "-$1").toLowerCase();
7
+ const formatChoice = (choice: EnumChoice) => (typeof choice === "object" ? choice.label : choice.toString());
8
+
9
+ const groupCommands = (commands: CommandCls[]) => {
10
+ const groups = new Map<string, { name: string; commands: { key: string; args: string[]; desc?: string }[] }>();
11
+
7
12
  for (const command of commands) {
8
13
  const className = command.name.replace("Command", "");
9
14
  const groupName = className;
15
+
10
16
  if (!groups.has(groupName)) {
11
17
  groups.set(groupName, { name: groupName, commands: [] });
12
18
  }
19
+
13
20
  const targetMetas = getTargetMetas(command);
14
21
  for (const targetMeta of targetMetas) {
15
- if (targetMeta.targetOption.devOnly)
16
- continue;
22
+ // Skip devOnly commands in help
23
+ if (targetMeta.targetOption.devOnly) continue;
24
+
17
25
  const [allArgMetas] = getArgMetas(command, targetMeta.key);
18
- const args = allArgMetas.filter((arg) => arg.type !== "Option").map((arg) => {
19
- if (arg.type === "Workspace")
20
- return "";
21
- if (arg.type === "Module")
22
- return "[sys:module]";
23
- if (arg.type === "Argument") {
24
- return `[${arg.name}]`;
25
- }
26
- return `[${arg.type.toLowerCase()}]`;
27
- }).filter(Boolean);
26
+ const args = allArgMetas
27
+ .filter((arg) => arg.type !== "Option")
28
+ .map((arg) => {
29
+ if (arg.type === "Workspace") return "";
30
+ if (arg.type === "Module") return "[sys:module]";
31
+ if (arg.type === "Argument") {
32
+ return `[${arg.name}]`;
33
+ }
34
+ return `[${arg.type.toLowerCase()}]`;
35
+ })
36
+ .filter(Boolean);
37
+
28
38
  const group = groups.get(groupName);
29
39
  if (group) {
30
40
  group.commands.push({
31
41
  key: camelToKebabCase(targetMeta.key),
32
42
  args,
33
- desc: targetMeta.targetOption.desc
43
+ desc: targetMeta.targetOption.desc,
34
44
  });
35
45
  }
36
46
  }
37
47
  }
48
+
38
49
  return groups;
39
50
  };
40
- const formatHelp = (commands, version) => {
51
+
52
+ export const formatHelp = (commands: CommandCls[], version: string) => {
41
53
  const groups = groupCommands(commands);
42
- const lines = [];
54
+ const lines: string[] = [];
55
+
56
+ // Header
43
57
  lines.push("");
44
- lines.push(chalk.bold.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
58
+ lines.push(chalk.bold.cyan(" ╔═══════════════════════════════════════════════════╗"));
45
59
  lines.push(
46
- chalk.bold.cyan(" \u2551") + chalk.bold.white(" Akan.js Framework CLI ") + chalk.bold.cyan(" \u2551")
60
+ chalk.bold.cyan(" ") +
61
+ chalk.bold.white(" Akan.js Framework CLI ") +
62
+ chalk.bold.cyan(" ║"),
47
63
  );
48
- lines.push(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
64
+ lines.push(chalk.bold.cyan(" ╚═══════════════════════════════════════════════════╝"));
49
65
  lines.push("");
50
66
  lines.push(chalk.gray(` Version: ${version}`));
51
67
  lines.push("");
68
+
69
+ // Usage
52
70
  lines.push(chalk.bold.yellow(" USAGE"));
53
71
  lines.push("");
54
72
  lines.push(chalk.gray(" $ ") + chalk.white("akan") + chalk.gray(" <command> [options]"));
55
73
  lines.push("");
74
+
75
+ // Commands by category
56
76
  lines.push(chalk.bold.yellow(" COMMANDS"));
57
77
  lines.push("");
78
+
58
79
  for (const [groupName, group] of groups) {
59
- if (group.commands.length === 0)
60
- continue;
80
+ // Skip empty groups (all commands are devOnly)
81
+ if (group.commands.length === 0) continue;
82
+
61
83
  lines.push(chalk.bold.magenta(` ${groupName}`));
62
84
  lines.push("");
85
+
63
86
  for (const cmd of group.commands) {
64
87
  const cmdName = chalk.green(cmd.key);
65
88
  const cmdArgs = cmd.args.length > 0 ? chalk.gray(` ${cmd.args.join(" ")}`) : "";
89
+
90
+ // Format description: wrap if too long
66
91
  if (cmd.desc) {
67
92
  const maxLineLength = 70;
68
93
  const cmdPrefix = ` ${cmdName}${cmdArgs}`;
69
94
  const indent = " ";
95
+
70
96
  if (cmdPrefix.length + cmd.desc.length + 3 < maxLineLength) {
97
+ // Single line
71
98
  lines.push(`${cmdPrefix} ${chalk.gray(cmd.desc)}`);
72
99
  } else {
100
+ // Multi-line
73
101
  lines.push(cmdPrefix);
74
102
  lines.push(`${indent}${chalk.gray(cmd.desc)}`);
75
103
  }
@@ -79,12 +107,16 @@ const formatHelp = (commands, version) => {
79
107
  }
80
108
  lines.push("");
81
109
  }
110
+
111
+ // Global Options
82
112
  lines.push(chalk.bold.yellow(" OPTIONS"));
83
113
  lines.push("");
84
114
  lines.push(` ${chalk.green("-v, --verbose")} ${chalk.gray("Enable verbose output")}`);
85
115
  lines.push(` ${chalk.green("-h, --help")} ${chalk.gray("Display this help message")}`);
86
116
  lines.push(` ${chalk.green("-V, --version")} ${chalk.gray("Output version number")}`);
87
117
  lines.push("");
118
+
119
+ // Examples
88
120
  lines.push(chalk.bold.yellow(" EXAMPLES"));
89
121
  lines.push("");
90
122
  lines.push(chalk.gray(" # Create a new workspace"));
@@ -96,48 +128,63 @@ const formatHelp = (commands, version) => {
96
128
  lines.push(chalk.gray(" # Create a new module"));
97
129
  lines.push(chalk.white(" $ akan create-module userProfile"));
98
130
  lines.push("");
131
+
132
+ // Footer
99
133
  lines.push(chalk.gray(" Documentation: ") + chalk.cyan("https://akanjs.com/docs"));
100
134
  lines.push(chalk.gray(" Report issues: ") + chalk.cyan("https://github.com/akan-team/akanjs/issues"));
101
135
  lines.push("");
136
+
102
137
  return lines.join("\n");
103
138
  };
104
- const formatCommandHelp = (command, key) => {
139
+
140
+ export const formatCommandHelp = (command: CommandCls, key: string) => {
105
141
  const [allArgMetas, argMetas] = getArgMetas(command, key);
106
142
  const kebabKey = camelToKebabCase(key);
107
- const lines = [];
143
+ const lines: string[] = [];
144
+
108
145
  const targetMetas = getTargetMetas(command);
109
146
  const targetMeta = targetMetas.find((t) => t.key === key);
110
147
  const commandDesc = targetMeta?.targetOption.desc;
148
+
111
149
  lines.push("");
112
150
  lines.push(chalk.bold.cyan(` Command: ${kebabKey}`));
113
151
  if (commandDesc) {
114
152
  lines.push(chalk.gray(` ${commandDesc}`));
115
153
  }
116
154
  lines.push("");
117
- const args = allArgMetas.filter((arg) => arg.type !== "Option").map((arg) => {
118
- if (arg.type === "Workspace")
119
- return "";
120
- if (arg.type === "Module")
121
- return "[sys:module]";
122
- if (arg.type === "Argument") {
123
- return `[${camelToKebabCase(arg.name)}]`;
124
- }
125
- return `[${arg.type.toLowerCase()}]`;
126
- }).filter(Boolean).join(" ");
155
+
156
+ // Usage
157
+ const args = allArgMetas
158
+ .filter((arg) => arg.type !== "Option")
159
+ .map((arg) => {
160
+ if (arg.type === "Workspace") return "";
161
+ if (arg.type === "Module") return "[sys:module]";
162
+ if (arg.type === "Argument") {
163
+ return `[${camelToKebabCase(arg.name)}]`;
164
+ }
165
+ return `[${arg.type.toLowerCase()}]`;
166
+ })
167
+ .filter(Boolean)
168
+ .join(" ");
169
+
127
170
  lines.push(chalk.bold.yellow(" USAGE"));
128
171
  lines.push("");
129
172
  lines.push(chalk.gray(" $ ") + chalk.white(`akan ${kebabKey}`) + (args ? chalk.gray(` ${args}`) : ""));
130
173
  lines.push("");
174
+
175
+ // Arguments
131
176
  const nonOptionArgs = allArgMetas.filter((arg) => arg.type !== "Option");
132
177
  if (nonOptionArgs.length > 0) {
133
178
  lines.push(chalk.bold.yellow(" ARGUMENTS"));
134
179
  lines.push("");
180
+
135
181
  for (const arg of nonOptionArgs) {
136
- if (arg.type === "Workspace")
137
- continue;
138
- let argName;
139
- let argDesc;
182
+ if (arg.type === "Workspace") continue;
183
+
184
+ let argName: string;
185
+ let argDesc: string;
140
186
  let example = "";
187
+
141
188
  if (arg.type === "Argument") {
142
189
  argName = camelToKebabCase(arg.name);
143
190
  argDesc = arg.argsOption.desc ?? "";
@@ -149,29 +196,35 @@ const formatCommandHelp = (command, key) => {
149
196
  argName = arg.type.toLowerCase();
150
197
  argDesc = `${arg.type} name in this workspace`;
151
198
  }
199
+
152
200
  lines.push(` ${chalk.green(argName)} ${chalk.gray(argDesc)}${example}`);
153
201
  }
202
+
154
203
  lines.push("");
155
204
  }
205
+
206
+ // Options
156
207
  const optionArgs = argMetas.filter((a) => a.type === "Option");
157
208
  if (optionArgs.length > 0) {
158
209
  lines.push(chalk.bold.yellow(" OPTIONS"));
159
210
  lines.push("");
211
+
160
212
  for (const arg of optionArgs) {
161
213
  const opt = arg.argsOption;
162
214
  const flag = opt.flag ? `-${opt.flag}, ` : "";
163
215
  const kebabName = camelToKebabCase(arg.name);
164
216
  const optName = `${flag}--${kebabName}`;
165
217
  const optDesc = opt.desc ?? "";
166
- const defaultVal = opt.default !== void 0 ? chalk.gray(` [default: ${String(opt.default)}]`) : "";
167
- const choices = opt.enum ? chalk.gray(` (${opt.enum.join(", ")})`) : "";
218
+ const defaultVal = opt.default !== undefined ? chalk.gray(` [default: ${String(opt.default)}]`) : "";
219
+ const choices = opt.enum
220
+ ? chalk.gray(
221
+ typeof opt.enum === "function" ? " ([dynamic choices])" : ` (${opt.enum.map(formatChoice).join(", ")})`,
222
+ )
223
+ : "";
168
224
  lines.push(` ${chalk.green(optName)} ${chalk.gray(optDesc)}${defaultVal}${choices}`);
169
225
  }
170
226
  lines.push("");
171
227
  }
228
+
172
229
  return lines.join("\n");
173
230
  };
174
- export {
175
- formatCommandHelp,
176
- formatHelp
177
- };
@@ -1,6 +1,8 @@
1
1
  export * from "./argMeta";
2
+ export * from "./command";
3
+ export * from "./commandBuilder";
2
4
  export * from "./commandMeta";
5
+ export * from "./dependencyBuilder";
6
+ export * from "./helpFormatter";
3
7
  export * from "./targetMeta";
4
8
  export * from "./types";
5
- export * from "./helpFormatter";
6
- export * from "./command";
@@ -0,0 +1,31 @@
1
+ import type { ArgMeta, InternalArgMeta } from "./argMeta";
2
+ import type { DependencyCls } from "./types";
3
+
4
+ export const COMMAND_META: unique symbol = Symbol("akan.command.meta");
5
+
6
+ export interface TargetMeta {
7
+ key: string;
8
+ targetOption: TargetOption;
9
+ args: (ArgMeta | InternalArgMeta)[];
10
+ handler: (this: unknown, ...args: unknown[]) => unknown | Promise<unknown>;
11
+ }
12
+
13
+ export interface CommandStatics {
14
+ [COMMAND_META]: Map<string, TargetMeta>;
15
+ }
16
+
17
+ export type CommandCls<Instance = unknown, Key extends string = string> = DependencyCls<Instance, Key> & CommandStatics;
18
+
19
+ export const getTargetMetas = (command: CommandCls): TargetMeta[] => {
20
+ const targetMetaMap = command[COMMAND_META];
21
+ if (!targetMetaMap) throw new Error(`TargetMeta is not defined for ${command.name}`);
22
+ return [...targetMetaMap.values()];
23
+ };
24
+
25
+ export interface TargetOption {
26
+ type: "public" | "cloud" | "dev";
27
+ short?: string | true;
28
+ devOnly?: boolean;
29
+ desc?: string;
30
+ runsOnWorkspaceRoot?: boolean;
31
+ }
@@ -0,0 +1,10 @@
1
+ export type Cls<T = unknown> = new (...args: unknown[]) => T;
2
+
3
+ export type DependencyKind = "command" | "script" | "runner";
4
+ export type DependencyKey<RefName extends string, Kind extends DependencyKind> = `${RefName}${Capitalize<Kind>}`;
5
+
6
+ export type DependencyCls<T = unknown, Key extends string = string> = (new () => T) & {
7
+ readonly dependencyKey: Key;
8
+ readonly dependencyKind: DependencyKind;
9
+ readonly refName: string;
10
+ };
package/constants.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { SupportedLlmModel } from "./aiEditor";
2
+
3
+ export const basePath = `${Bun.env.HOME ?? Bun.env.USERPROFILE}/.akan`;
4
+ export const configPath = `${basePath}/config.json`;
5
+ export const akanCloudHost =
6
+ process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? "http://localhost" : "https://cloud.akanjs.com";
7
+ export const akanCloudUrl = `${akanCloudHost}${process.env.AKAN_PUBLIC_OPERATION_MODE === "local" ? ":8282" : ""}/메ㅑ`;
8
+
9
+ export interface HostConfig {
10
+ auth?: {
11
+ token: string;
12
+ self: { id: string; nickname: string };
13
+ };
14
+ }
15
+ export const defaultHostConfig: HostConfig = {};
16
+ export interface AkanGlobalConfig {
17
+ cloudHost: {
18
+ [key: string]: HostConfig;
19
+ };
20
+ llm: {
21
+ model: SupportedLlmModel;
22
+ apiKey: string;
23
+ } | null;
24
+ }
25
+ export const defaultAkanGlobalConfig: AkanGlobalConfig = { cloudHost: {}, llm: null };