@akanjs/cli 3.0.2 → 3.1.0-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/.build-stamp +1 -1
- package/{CodeAgent-szbjv4gv.js → CodeAgent-q7a4srt6.js} +2 -2
- package/{CodeTui-5nw86ab6.js → CodeTui-ak23dxc9.js} +1 -1
- package/{TunnelShare-cwh9ezsm.js → TunnelShare-21mm6dr5.js} +2 -2
- package/{agent.command-np1dffkb.js → agent.command-f3gsc1gd.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-gk69v1xs.js → application.command-3v2d53gd.js} +37 -15
- package/{applicationBuildRunner-wx09n9g6.js → applicationBuildRunner-1yaqsrdq.js} +115 -103
- package/buildBatch.proc.js +16 -5
- package/cloud.command-kk2rwkvw.js +187 -0
- package/cloud.script-r7eyfey4.js +20 -0
- package/{code.command-hmtx14b2.js → code.command-30sca09d.js} +6 -6
- package/commandManifest.json +1 -1
- package/{context.command-6aqvdx43.js → context.command-k7eqacq6.js} +13 -13
- package/context.runner-hqd1ppqe.js +21 -0
- package/{guideline.command-axvf68gc.js → guideline.command-dtczrqpf.js} +2 -2
- package/guidelines/agentRule/agentRule.instruction.md +10 -2
- package/guidelines/conventions/conventions.instruction.md +68 -25
- package/guidelines/cssRule/cssRule.instruction.md +19 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +67 -12
- package/guidelines/queryRule/queryRule.instruction.md +11 -1
- package/guidelines/runtimeRule/runtimeRule.instruction.md +97 -16
- package/guidelines/ssrRule/ssrRule.instruction.md +10 -7
- package/guidelines/transportRule/transportRule.instruction.md +19 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +6 -6
- package/incrementalBuilder.proc.js +24 -3
- package/{index-h9ht6s60.js → index-05g3jayg.js} +1 -1
- package/{index-j38qkvkk.js → index-0bm04n5g.js} +12 -10
- package/{index-g38nhv87.js → index-2etcv0sc.js} +1 -1
- package/{index-zyw622y1.js → index-2mx692qq.js} +3 -3
- package/{index-jb0hmt3k.js → index-2t4tqd1c.js} +3 -3
- package/{index-50wb2a26.js → index-55mynnqs.js} +354 -129
- package/{index-q6ft9fkc.js → index-6jjtnhkw.js} +8 -3
- package/index-6w0rbr9d.js +68 -0
- package/{index-2wc41k5w.js → index-7bymavqk.js} +2 -2
- package/{index-gtjgnv36.js → index-aprt7pkz.js} +6 -9
- package/{index-1bjkfvm9.js → index-c1x0be5j.js} +2 -2
- package/{index-z750gk1j.js → index-c2yrsxdh.js} +13 -7
- package/{index-cb6yc5mb.js → index-c76k723e.js} +2 -2
- package/{index-6keyf4sg.js → index-ctanya41.js} +4 -4
- package/{index-f4e08ptp.js → index-e0cvze1s.js} +4 -4
- package/{index-hp3stk1m.js → index-fm46xrf1.js} +3 -3
- package/{index-3xmzr1p1.js → index-gnyf1fqy.js} +2 -2
- package/{index-zwd84m0w.js → index-jyv387qw.js} +4 -4
- package/{index-jk9xnck6.js → index-kjvrq3nc.js} +231 -133
- package/{index-xwprn9nr.js → index-nz15k0b6.js} +1 -1
- package/{index-nwd6qzvv.js → index-p9ystjrp.js} +138 -55
- package/index-q958tqqg.js +77 -0
- package/{index-50jjwg8y.js → index-qt22xstj.js} +1 -1
- package/{index-94s080qm.js → index-r2tpeb3n.js} +127 -1
- package/{index-bsv901fd.js → index-rwq3ekqc.js} +7 -7
- package/{index-t91a7339.js → index-tn4rpqp1.js} +96 -12
- package/{index-1gs9g12n.js → index-v29746vb.js} +4 -4
- package/index-ww9bhp67.js +62 -0
- package/{index-hxnnfy57.js → index-y8q31011.js} +180 -8
- package/index.js +22 -21
- package/{library.command-9m2rdwwz.js → library.command-gaga5bmq.js} +3 -3
- package/{localRegistry.command-kda81jyk.js → localRegistry.command-9e84e32q.js} +9 -9
- package/{module.command-q63dffya.js → module.command-7vpf6db4.js} +5 -5
- package/{package.command-y2jh0m99.js → package.command-twmvvf69.js} +3 -3
- package/package.json +2 -2
- package/{page.command-f3e4cwhh.js → page.command-5asq7fdf.js} +3 -3
- package/pipeline.command-d0v38572.js +1334 -0
- package/{primitive.command-r4kq75qj.js → primitive.command-dyrs2fm8.js} +6 -6
- package/projectTypecheck-cvtkrzp2.js +7 -0
- package/{quality.command-f7gzh26z.js → quality.command-6emsqpbg.js} +5 -4
- package/{repair.command-0qmyr6qa.js → repair.command-8t6aegv5.js} +5 -5
- package/{scalar.command-kddn7av4.js → scalar.command-bnggy1x7.js} +4 -4
- package/{subspace.command-ft65qvc4.js → subspace.command-f02j5tge.js} +158 -91
- package/templates/appSample/page/task/[taskId]/_index.tsx +2 -2
- package/templates/appSample/page/task/[taskId]/edit.tsx +2 -2
- package/templates/appSample/page/task/_index.tsx +2 -2
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -2
- package/templates/crudPages/[__model__Id]/page.tsx +3 -3
- package/templates/crudPages/page.tsx +2 -2
- package/templates/crudSinglePage/page.tsx +3 -3
- package/templates/lib/cnst.ts +2 -2
- package/templates/lib/sig.ts +3 -3
- package/{tunnel.command-b3ymdk4f.js → tunnel.command-9x8nfn75.js} +16 -13
- package/{workflow.command-z1srcdfw.js → workflow.command-jx0wag5r.js} +10 -10
- package/{workspace.command-vp6vmx3z.js → workspace.command-cmj6mqyk.js} +57 -27
- package/cloud.command-tw06613r.js +0 -95
- package/cloud.script-64gzba8r.js +0 -20
- package/context.runner-hnkvv3z3.js +0 -21
|
@@ -4,13 +4,94 @@ import {
|
|
|
4
4
|
FileSys2
|
|
5
5
|
} from "./index-52w0fvh8.js";
|
|
6
6
|
|
|
7
|
+
// pkgs/@akanjs/devkit/transforms/serverEnvProfile.ts
|
|
8
|
+
import path from "path";
|
|
9
|
+
var secretKey = /password|passwd|passphrase|^pass$|token|jwt|authorization|cookie|secret|api[-_]?key|private[-_]?key|access[-_]?key|auth[-_]?key|hash[-_]?key|webhook|cred|iap[-_]?verify|service[-_]?account/i;
|
|
10
|
+
|
|
11
|
+
class ServerEnvProfile {
|
|
12
|
+
static namePattern = /^[a-z][a-zA-Z0-9-]*$/;
|
|
13
|
+
static fileName(environment, profile) {
|
|
14
|
+
return `env.server.${environment}.${profile}.ts`;
|
|
15
|
+
}
|
|
16
|
+
static profileFileOf(file, profile) {
|
|
17
|
+
return file.replace(/\.ts$/, `.${profile}.ts`);
|
|
18
|
+
}
|
|
19
|
+
static strip(value, at = "", underSecret = false) {
|
|
20
|
+
if (typeof value === "string")
|
|
21
|
+
return underSecret && value.trim() ? { value: undefined, stripped: [at] } : { value, stripped: [] };
|
|
22
|
+
if (!value || typeof value !== "object")
|
|
23
|
+
return { value, stripped: [] };
|
|
24
|
+
const stripped = [];
|
|
25
|
+
const strip = (key, child) => {
|
|
26
|
+
const result = ServerEnvProfile.strip(child, at ? `${at}.${key}` : key, underSecret || secretKey.test(key));
|
|
27
|
+
stripped.push(...result.stripped);
|
|
28
|
+
return result.value;
|
|
29
|
+
};
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return { value: value.map((child, idx) => strip(String(idx), child)), stripped };
|
|
32
|
+
const kept = Object.fromEntries(Object.entries(value).map(([key, child]) => [key, strip(key, child)]).filter(([, child]) => child !== undefined));
|
|
33
|
+
return { value: kept, stripped };
|
|
34
|
+
}
|
|
35
|
+
static secretValues(value, at = "", underSecret = false) {
|
|
36
|
+
if (typeof value === "string") {
|
|
37
|
+
const named = secretKey.test(at.split(".").at(-1) ?? "");
|
|
38
|
+
const distinct = value.length >= 16 && !/^https?:\/\//.test(value) && !/^[a-z]+(?:[._-][a-z]+)*$/.test(value);
|
|
39
|
+
return underSecret && value.length >= 8 && (named || distinct) ? [{ path: at, value }] : [];
|
|
40
|
+
}
|
|
41
|
+
if (!value || typeof value !== "object")
|
|
42
|
+
return [];
|
|
43
|
+
return Object.entries(value).flatMap(([key, child]) => ServerEnvProfile.secretValues(child, at ? `${at}.${key}` : key, underSecret || secretKey.test(key)));
|
|
44
|
+
}
|
|
45
|
+
static secretPaths(value, at = "", underSecret = false) {
|
|
46
|
+
if (typeof value === "string")
|
|
47
|
+
return underSecret && value.trim() ? [at] : [];
|
|
48
|
+
if (!value || typeof value !== "object")
|
|
49
|
+
return [];
|
|
50
|
+
return Object.entries(value).flatMap(([key, child]) => ServerEnvProfile.secretPaths(child, at ? `${at}.${key}` : key, underSecret || secretKey.test(key)));
|
|
51
|
+
}
|
|
52
|
+
static async evaluate(file, cwd) {
|
|
53
|
+
const script = [
|
|
54
|
+
"const { env } = await import(process.env.AKAN_PROFILE_ENV_FILE);",
|
|
55
|
+
'const plain = (_, value) => (typeof value === "function" || typeof value === "symbol" ? undefined : typeof value === "bigint" ? String(value) : value);',
|
|
56
|
+
"process.stdout.write(JSON.stringify(env ?? null, plain));"
|
|
57
|
+
].join(`
|
|
58
|
+
`);
|
|
59
|
+
const child = Bun.spawn([process.execPath, "--eval", script], {
|
|
60
|
+
cwd,
|
|
61
|
+
env: { PATH: process.env.PATH ?? "", HOME: process.env.HOME ?? "", AKAN_PROFILE_ENV_FILE: file },
|
|
62
|
+
stdout: "pipe",
|
|
63
|
+
stderr: "pipe"
|
|
64
|
+
});
|
|
65
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
66
|
+
new Response(child.stdout).text(),
|
|
67
|
+
new Response(child.stderr).text(),
|
|
68
|
+
child.exited
|
|
69
|
+
]);
|
|
70
|
+
if (exitCode !== 0)
|
|
71
|
+
throw new Error(`${path.basename(file)} could not be evaluated to check it for secrets:
|
|
72
|
+
${stderr.trim()}`);
|
|
73
|
+
return JSON.parse(stdout || "null");
|
|
74
|
+
}
|
|
75
|
+
static async assertShippable({ envDir, environment, profile }) {
|
|
76
|
+
if (!ServerEnvProfile.namePattern.test(profile))
|
|
77
|
+
throw new Error(`The server env profile "${profile}" must be a lowercase-first name of letters, digits and dashes.`);
|
|
78
|
+
const name = ServerEnvProfile.fileName(environment, profile);
|
|
79
|
+
const file = path.join(envDir, name);
|
|
80
|
+
if (!await Bun.file(file).exists())
|
|
81
|
+
throw new Error(`The server env profile "${profile}" bundles env/${name} in place of env/env.server.${environment}.ts, and there is no such file.`);
|
|
82
|
+
const secrets = ServerEnvProfile.secretPaths(await ServerEnvProfile.evaluate(file, path.dirname(envDir)));
|
|
83
|
+
if (secrets.length)
|
|
84
|
+
throw new Error(`env/${name} holds secret-named values, which a profile's bundle carries to every computer it runs on: ${secrets.join(", ")}. Leave them out, or read them from the process env of the server that needs them.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
7
88
|
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
8
89
|
import { readdir, rm } from "fs/promises";
|
|
9
|
-
import
|
|
90
|
+
import path3 from "path";
|
|
10
91
|
|
|
11
92
|
// pkgs/@akanjs/devkit/transforms/asyncDefaultExportDetector.ts
|
|
12
93
|
import fs from "fs";
|
|
13
|
-
import
|
|
94
|
+
import path2 from "path";
|
|
14
95
|
|
|
15
96
|
class AsyncDefaultExportDetector {
|
|
16
97
|
static #typescriptLoad;
|
|
@@ -21,7 +102,7 @@ class AsyncDefaultExportDetector {
|
|
|
21
102
|
static async detect(moduleAbsPath) {
|
|
22
103
|
try {
|
|
23
104
|
const typescript = await AsyncDefaultExportDetector.#loadTypescript();
|
|
24
|
-
const source = fs.readFileSync(
|
|
105
|
+
const source = fs.readFileSync(path2.resolve(moduleAbsPath), "utf8");
|
|
25
106
|
const sourceFile = typescript.createSourceFile(moduleAbsPath, source, typescript.ScriptTarget.Latest, true, /\.[tj]sx$/.test(moduleAbsPath) ? typescript.ScriptKind.TSX : typescript.ScriptKind.TS);
|
|
26
107
|
return new AsyncDefaultExportDetector(typescript).detectInSourceFile(sourceFile);
|
|
27
108
|
} catch {
|
|
@@ -85,17 +166,17 @@ class AsyncDefaultExportDetector {
|
|
|
85
166
|
|
|
86
167
|
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
87
168
|
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
88
|
-
var IMPLICIT_LAYOUT_DIR =
|
|
89
|
-
var IMPLICIT_DICT_DIR =
|
|
90
|
-
var IMPLICIT_OVERRIDES_DIR =
|
|
169
|
+
var IMPLICIT_LAYOUT_DIR = path3.join(".akan", "generated", "root-layouts");
|
|
170
|
+
var IMPLICIT_DICT_DIR = path3.join(".akan", "generated", "dict");
|
|
171
|
+
var IMPLICIT_OVERRIDES_DIR = path3.join(".akan", "generated", "overrides");
|
|
91
172
|
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
92
173
|
var importSpecifier = (fromAbsPath, toAbsPath) => {
|
|
93
|
-
const rel =
|
|
174
|
+
const rel = path3.relative(path3.dirname(fromAbsPath), toAbsPath).split(path3.sep).join("/");
|
|
94
175
|
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
95
176
|
};
|
|
96
177
|
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
97
178
|
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
98
|
-
const absPath =
|
|
179
|
+
const absPath = path3.join(path3.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
99
180
|
const source = `"use client";
|
|
100
181
|
import { UiOverrideProvider } from "akanjs/ui";
|
|
101
182
|
import { createElement, type ReactNode } from "react";
|
|
@@ -111,7 +192,7 @@ export default function AkanUiOverridesLayout({ children }: { children?: ReactNo
|
|
|
111
192
|
async function pruneGeneratedFiles(dirAbsPath, keepAbsPaths) {
|
|
112
193
|
const keep = new Set(keepAbsPaths);
|
|
113
194
|
const names = await readdir(dirAbsPath).catch(() => []);
|
|
114
|
-
await Promise.all(names.map((name) =>
|
|
195
|
+
await Promise.all(names.map((name) => path3.join(dirAbsPath, name)).filter((absPath) => !keep.has(absPath)).map((absPath) => rm(absPath, { force: true })));
|
|
115
196
|
}
|
|
116
197
|
function getRootBoundarySegments(key) {
|
|
117
198
|
const match = LAYOUT_KEY_RE.exec(key);
|
|
@@ -127,7 +208,7 @@ function implicitRootLayoutKey(segments) {
|
|
|
127
208
|
}
|
|
128
209
|
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
129
210
|
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
130
|
-
return
|
|
211
|
+
return path3.join(path3.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
131
212
|
}
|
|
132
213
|
function isRootBoundarySegments(segments, basePaths) {
|
|
133
214
|
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
@@ -150,7 +231,7 @@ function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
|
150
231
|
const id = segments.join("/");
|
|
151
232
|
boundaries.set(id, {
|
|
152
233
|
sourceKey: key,
|
|
153
|
-
sourceAbsPath:
|
|
234
|
+
sourceAbsPath: path3.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
154
235
|
segments
|
|
155
236
|
});
|
|
156
237
|
}
|
|
@@ -166,16 +247,16 @@ function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
|
166
247
|
const segments = getRootBoundarySegments(key);
|
|
167
248
|
return segments !== null && segments.length === 0;
|
|
168
249
|
});
|
|
169
|
-
return rootLayoutKey ?
|
|
250
|
+
return rootLayoutKey ? path3.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
170
251
|
}
|
|
171
252
|
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
172
|
-
const envPath =
|
|
253
|
+
const envPath = path3.join(appCwdPath, "env", "env.client.ts");
|
|
173
254
|
if (!await Bun.file(envPath).exists()) {
|
|
174
255
|
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
175
256
|
}
|
|
176
257
|
}
|
|
177
258
|
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
178
|
-
const absPath =
|
|
259
|
+
const absPath = path3.join(path3.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
179
260
|
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
180
261
|
|
|
181
262
|
export const allDictionary = getAllDictionary();
|
|
@@ -194,13 +275,13 @@ async function writeGeneratedRootLayoutFile(opts) {
|
|
|
194
275
|
void st;
|
|
195
276
|
` : `import "@apps/${opts.appName}/client";
|
|
196
277
|
`;
|
|
197
|
-
const inheritedLabel = inheritedSourceAbsPath ?
|
|
278
|
+
const inheritedLabel = inheritedSourceAbsPath ? path3.relative(opts.appCwdPath, inheritedSourceAbsPath) : "";
|
|
198
279
|
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedModule from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
199
280
|
const inheritedLayout = resolveRouteModule(inheritedModule as never, ${JSON.stringify(inheritedLabel)}).module as LayoutModule;
|
|
200
281
|
` : `const inheritedLayout: LayoutModule = {};
|
|
201
282
|
`;
|
|
202
283
|
const prefix = opts.boundary.segments.find((segment) => !/^\(.+\)$/.test(segment)) ?? null;
|
|
203
|
-
const userLabel = opts.boundary.sourceAbsPath ?
|
|
284
|
+
const userLabel = opts.boundary.sourceAbsPath ? path3.relative(opts.appCwdPath, opts.boundary.sourceAbsPath) : "";
|
|
204
285
|
const userImport = sourceSpecifier ? `import * as userModule from ${JSON.stringify(sourceSpecifier)};
|
|
205
286
|
const userLayout = resolveRouteModule(userModule as never, ${JSON.stringify(userLabel)}).module as LayoutModule;
|
|
206
287
|
const UserLayout = userLayout.default as (props: LayoutProps) => ReactNode | Promise<ReactNode>;
|
|
@@ -217,11 +298,12 @@ const userLayout: LayoutModule = {};
|
|
|
217
298
|
` : "";
|
|
218
299
|
const layoutChild = isAsyncUserLayout ? "{layout}" : userLayoutElement;
|
|
219
300
|
const layoutReturn = isAsyncUserLayout ? "layout" : userLayoutElement;
|
|
301
|
+
const headLayouts = opts.includeSystemProvider ? "[inheritedLayout, userLayout]" : "[userLayout]";
|
|
220
302
|
const layoutExports = `export async function generateHead(props: PageProps) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return
|
|
303
|
+
const heads = await Promise.all(
|
|
304
|
+
${headLayouts}.map((layout) => (layout.generateHead ? layout.generateHead(props) : layout.head)),
|
|
305
|
+
);
|
|
306
|
+
return RouteHead.mergeNodes(heads);
|
|
225
307
|
}
|
|
226
308
|
|
|
227
309
|
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
@@ -231,7 +313,7 @@ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
|
231
313
|
${layoutSignature}({ children, params, searchParams }: LayoutProps) {
|
|
232
314
|
${layoutBinding} return `;
|
|
233
315
|
const source = opts.includeSystemProvider ? `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
234
|
-
import { loadFonts, resolveRouteModule } from "akanjs/client";
|
|
316
|
+
import { loadFonts, resolveRouteModule, RouteHead } from "akanjs/client";
|
|
235
317
|
import type { ReactNode } from "react";
|
|
236
318
|
import { System } from "akanjs/ui";
|
|
237
319
|
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
@@ -243,6 +325,8 @@ if (defaultFonts.length > 1) throw new Error("[route-convention] only one defaul
|
|
|
243
325
|
const defaultFont = defaultFonts[0];
|
|
244
326
|
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
245
327
|
export const theme = userLayout.theme ?? inheritedLayout.theme;
|
|
328
|
+
export const viewport = userLayout.viewport ?? inheritedLayout.viewport;
|
|
329
|
+
export const manifest = userLayout.manifest ?? inheritedLayout.manifest;
|
|
246
330
|
|
|
247
331
|
${layoutExports}(
|
|
248
332
|
<System.Provider
|
|
@@ -250,9 +334,10 @@ ${layoutExports}(
|
|
|
250
334
|
appName=${JSON.stringify(opts.appName)}
|
|
251
335
|
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
252
336
|
` : ""}params={params}
|
|
253
|
-
manifest={
|
|
337
|
+
manifest={manifest}
|
|
254
338
|
env={env}
|
|
255
339
|
theme={theme}
|
|
340
|
+
viewport={viewport}
|
|
256
341
|
fonts={loadFonts(userFonts)}
|
|
257
342
|
className={defaultFontClassName}
|
|
258
343
|
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
@@ -265,7 +350,7 @@ ${layoutExports}(
|
|
|
265
350
|
);
|
|
266
351
|
}
|
|
267
352
|
` : `import type { LayoutModule, LayoutProps, PageProps } from "akanjs/client";
|
|
268
|
-
import { resolveRouteModule } from "akanjs/client";
|
|
353
|
+
import { resolveRouteModule, RouteHead } from "akanjs/client";
|
|
269
354
|
import type { ReactNode } from "react";
|
|
270
355
|
${inheritedImport}${userImport}
|
|
271
356
|
${layoutExports}${layoutReturn};
|
|
@@ -275,8 +360,8 @@ ${layoutExports}${layoutReturn};
|
|
|
275
360
|
return absPath;
|
|
276
361
|
}
|
|
277
362
|
async function resolveSsrPageEntries(opts) {
|
|
278
|
-
const absPageDir =
|
|
279
|
-
const hasSt = await Bun.file(
|
|
363
|
+
const absPageDir = path3.resolve(opts.appCwdPath, "page");
|
|
364
|
+
const hasSt = await Bun.file(path3.join(opts.appCwdPath, "lib", "st.ts")).exists();
|
|
280
365
|
const basePaths = opts.basePaths ?? [];
|
|
281
366
|
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
282
367
|
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
@@ -285,7 +370,7 @@ async function resolveSsrPageEntries(opts) {
|
|
|
285
370
|
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
286
371
|
}));
|
|
287
372
|
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
288
|
-
const userAbsPath =
|
|
373
|
+
const userAbsPath = path3.resolve(absPageDir, key);
|
|
289
374
|
if (OVERRIDES_KEY_RE.test(key)) {
|
|
290
375
|
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
291
376
|
appCwdPath: opts.appCwdPath,
|
|
@@ -308,10 +393,10 @@ async function resolveSsrPageEntries(opts) {
|
|
|
308
393
|
}),
|
|
309
394
|
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
310
395
|
})));
|
|
311
|
-
const appCwdAbsPath =
|
|
396
|
+
const appCwdAbsPath = path3.resolve(opts.appCwdPath);
|
|
312
397
|
await Promise.all([
|
|
313
|
-
pruneGeneratedFiles(
|
|
314
|
-
pruneGeneratedFiles(
|
|
398
|
+
pruneGeneratedFiles(path3.join(appCwdAbsPath, IMPLICIT_OVERRIDES_DIR), base.filter(({ key }) => OVERRIDES_KEY_RE.test(key)).map(({ moduleAbsPath }) => moduleAbsPath)),
|
|
399
|
+
pruneGeneratedFiles(path3.join(appCwdAbsPath, IMPLICIT_LAYOUT_DIR), generated.map(({ moduleAbsPath }) => moduleAbsPath))
|
|
315
400
|
]);
|
|
316
401
|
return [...base, ...generated].sort((a, b) => a.key.localeCompare(b.key));
|
|
317
402
|
}
|
|
@@ -322,7 +407,7 @@ async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
|
322
407
|
|
|
323
408
|
// pkgs/@akanjs/devkit/codegenLock.ts
|
|
324
409
|
import { mkdir, open, readFile, rm as rm2, stat } from "fs/promises";
|
|
325
|
-
import
|
|
410
|
+
import path4 from "path";
|
|
326
411
|
import { Logger } from "akanjs/common";
|
|
327
412
|
|
|
328
413
|
class CodegenLock {
|
|
@@ -333,7 +418,7 @@ class CodegenLock {
|
|
|
333
418
|
static #logger = new Logger("CodegenLock");
|
|
334
419
|
static #queue = Promise.resolve();
|
|
335
420
|
static pathIn(workspaceRoot) {
|
|
336
|
-
return
|
|
421
|
+
return path4.join(workspaceRoot, "local", ".akan", CodegenLock.fileName);
|
|
337
422
|
}
|
|
338
423
|
static async run(workspaceRoot, label, fn) {
|
|
339
424
|
const ahead = CodegenLock.#queue;
|
|
@@ -363,7 +448,7 @@ class CodegenLock {
|
|
|
363
448
|
}
|
|
364
449
|
static async#withFileLock(workspaceRoot, label, fn) {
|
|
365
450
|
const lockPath = CodegenLock.pathIn(workspaceRoot);
|
|
366
|
-
await mkdir(
|
|
451
|
+
await mkdir(path4.dirname(lockPath), { recursive: true }).catch(() => {
|
|
367
452
|
return;
|
|
368
453
|
});
|
|
369
454
|
const held = await CodegenLock.#acquire(lockPath, label);
|
|
@@ -517,7 +602,7 @@ import {
|
|
|
517
602
|
stat as stat2,
|
|
518
603
|
symlink
|
|
519
604
|
} from "fs/promises";
|
|
520
|
-
import
|
|
605
|
+
import path12 from "path";
|
|
521
606
|
import { pathToFileURL } from "url";
|
|
522
607
|
import {
|
|
523
608
|
capitalize,
|
|
@@ -670,10 +755,10 @@ ${CLAUDE_COMMENT_RULE}
|
|
|
670
755
|
`;
|
|
671
756
|
|
|
672
757
|
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
673
|
-
import
|
|
758
|
+
import path5 from "path";
|
|
674
759
|
|
|
675
760
|
// pkgs/@akanjs/devkit/akanConfig/types.ts
|
|
676
|
-
import { archs, binPlatforms } from "akanjs";
|
|
761
|
+
import { archs, binPlatforms, defaultAppPipeline } from "akanjs";
|
|
677
762
|
|
|
678
763
|
// pkgs/@akanjs/devkit/akanConfig/akanBin.ts
|
|
679
764
|
class AkanBin {
|
|
@@ -721,7 +806,7 @@ class AkanBin {
|
|
|
721
806
|
if (!archive)
|
|
722
807
|
throw new Error(`${at}: file picks the executable out of an archive, and this source is none`);
|
|
723
808
|
const parts = typeof file === "string" ? file.split(/[\\/]/).filter((part) => part && part !== ".") : [];
|
|
724
|
-
if (!parts.length ||
|
|
809
|
+
if (!parts.length || path5.isAbsolute(file) || parts.includes(".."))
|
|
725
810
|
throw new Error(`${at}: file must be a path inside the archive (got ${JSON.stringify(file)})`);
|
|
726
811
|
return { ...resolved, file: parts.join("/") };
|
|
727
812
|
}
|
|
@@ -737,12 +822,12 @@ class AkanBin {
|
|
|
737
822
|
throw new Error(`${at}: path must name a file`);
|
|
738
823
|
if (sha256 !== undefined)
|
|
739
824
|
throw new Error(`${at}: sha256 checks a download, and a path source downloads nothing`);
|
|
740
|
-
return { path:
|
|
825
|
+
return { path: path5.resolve(baseDir, localPath) };
|
|
741
826
|
}
|
|
742
827
|
}
|
|
743
828
|
// pkgs/@akanjs/devkit/akanConfig/akanConfig.ts
|
|
744
829
|
import fs2 from "fs";
|
|
745
|
-
import
|
|
830
|
+
import path6 from "path";
|
|
746
831
|
import { fileURLToPath } from "url";
|
|
747
832
|
import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
|
|
748
833
|
import { resolveAkanI18nConfig } from "akanjs/common";
|
|
@@ -938,8 +1023,8 @@ class AkanAppConfig {
|
|
|
938
1023
|
if (basePath && !this.basePaths.has(basePath))
|
|
939
1024
|
throw new Error(`Native target '${name}' uses unknown basePath '${basePath}' in ${configPath}`);
|
|
940
1025
|
const { updates } = merged;
|
|
941
|
-
if (updates && (!updates.url || !updates.publicKey))
|
|
942
|
-
throw new Error(`${where}.updates in ${configPath} has no url or
|
|
1026
|
+
if (updates && (!updates.url || !(updates.publicKey || updates.publicKeys?.length)))
|
|
1027
|
+
throw new Error(`${where}.updates in ${configPath} has no url or publicKeys${named ? "; give them in native.updates or the target's own" : ""}.`);
|
|
943
1028
|
AkanAppConfig.#assertFiles(own ?? {}, where, configPath);
|
|
944
1029
|
const {
|
|
945
1030
|
basePath: _basePath,
|
|
@@ -980,12 +1065,64 @@ class AkanAppConfig {
|
|
|
980
1065
|
if (server === undefined || typeof server === "boolean")
|
|
981
1066
|
return desktop;
|
|
982
1067
|
if (!AkanAppConfig.#isPlainObject(server))
|
|
983
|
-
throw new Error(`${where} in ${configPath} must be true or { omit
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1068
|
+
throw new Error(`${where} in ${configPath} must be true or { omit, profile, modules }.`);
|
|
1069
|
+
const names = (list, key, what) => {
|
|
1070
|
+
if (list === undefined)
|
|
1071
|
+
return [];
|
|
1072
|
+
if (!Array.isArray(list) || list.some((name) => typeof name !== "string" || !name.trim()))
|
|
1073
|
+
throw new Error(`${where}.${key} in ${configPath} must be a list of ${what}.`);
|
|
1074
|
+
return [...new Set(list.map((name) => name.trim()))].sort();
|
|
1075
|
+
};
|
|
1076
|
+
const { profile } = server;
|
|
1077
|
+
if (profile !== undefined && (typeof profile !== "string" || !ServerEnvProfile.namePattern.test(profile)))
|
|
1078
|
+
throw new Error(`${where}.profile in ${configPath} names env/env.server.<env>.<profile>.ts: a lowercase-first name of letters, digits and dashes.`);
|
|
1079
|
+
const modules = names(server.modules, "modules", "module names or @<operationMode> selectors");
|
|
1080
|
+
const env = AkanAppConfig.#desktopServerEnv(server.env, where, configPath);
|
|
1081
|
+
const { restart } = server;
|
|
1082
|
+
if (restart !== undefined && restart !== "forever" && !(Number.isInteger(restart) && restart > 0))
|
|
1083
|
+
throw new Error(`${where}.restart in ${configPath} must be "forever" or a count of crashes in a row from 1.`);
|
|
1084
|
+
const watchdog = AkanAppConfig.#desktopServerWatchdog(server.watchdog, where, configPath);
|
|
1085
|
+
return {
|
|
1086
|
+
...desktop,
|
|
1087
|
+
server: {
|
|
1088
|
+
omit: names(server.omit, "omit", "package names"),
|
|
1089
|
+
...profile ? { profile } : {},
|
|
1090
|
+
...modules.length ? { modules } : {},
|
|
1091
|
+
...Object.keys(env).length ? { env } : {},
|
|
1092
|
+
...restart !== undefined ? { restart } : {},
|
|
1093
|
+
...watchdog ? { watchdog } : {}
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
static #stagedServerEnv = new Set([
|
|
1098
|
+
"AKAN_PUBLIC_OPERATION_MODE",
|
|
1099
|
+
"AKAN_DATABASE_MODE",
|
|
1100
|
+
"AKAN_DATABASE_MODES",
|
|
1101
|
+
"AKAN_SSR",
|
|
1102
|
+
"AKAN_CSR",
|
|
1103
|
+
"AKAN_MODULES"
|
|
1104
|
+
]);
|
|
1105
|
+
static #desktopServerEnv(env, where, configPath) {
|
|
1106
|
+
if (env === undefined)
|
|
1107
|
+
return {};
|
|
1108
|
+
if (!AkanAppConfig.#isPlainObject(env) || Object.values(env).some((value) => typeof value !== "string"))
|
|
1109
|
+
throw new Error(`${where}.env in ${configPath} must be an object of strings.`);
|
|
1110
|
+
const staged = Object.keys(env).filter((key) => AkanAppConfig.#stagedServerEnv.has(key.toUpperCase()));
|
|
1111
|
+
if (staged.length)
|
|
1112
|
+
throw new Error(`${where}.env in ${configPath} sets ${staged.join(", ")}, which the carried server's stage owns (modules: desktop.server.modules).`);
|
|
1113
|
+
const secrets = ServerEnvProfile.secretPaths(env);
|
|
1114
|
+
if (secrets.length)
|
|
1115
|
+
throw new Error(`${where}.env in ${configPath} holds secret-named values (${secrets.join(", ")}), and the app carries its env where anyone on the computer reads it. Leave them out.`);
|
|
1116
|
+
return env;
|
|
1117
|
+
}
|
|
1118
|
+
static #desktopServerWatchdog(watchdog, where, configPath) {
|
|
1119
|
+
if (watchdog === undefined || watchdog === false)
|
|
1120
|
+
return null;
|
|
1121
|
+
const { intervalMs = 1e4, failures = 3 } = watchdog === true ? {} : watchdog;
|
|
1122
|
+
const whole = (value) => Number.isInteger(value) && value > 0;
|
|
1123
|
+
if (watchdog !== true && !AkanAppConfig.#isPlainObject(watchdog) || !whole(intervalMs) || !whole(failures))
|
|
1124
|
+
throw new Error(`${where}.watchdog in ${configPath} must be true or { intervalMs, failures }, whole numbers from 1.`);
|
|
1125
|
+
return { intervalMs, failures };
|
|
989
1126
|
}
|
|
990
1127
|
static #platformIndexPath(section) {
|
|
991
1128
|
const { indexPath: rawIndexPath, ...rest } = section;
|
|
@@ -996,7 +1133,7 @@ class AkanAppConfig {
|
|
|
996
1133
|
const merge = (kept, value, top, key) => {
|
|
997
1134
|
if (!AkanAppConfig.#isPlainObject(kept) || !AkanAppConfig.#isPlainObject(value))
|
|
998
1135
|
return value;
|
|
999
|
-
if (top && (key === "icon" || key === "splash"))
|
|
1136
|
+
if (top && (key === "icon" || key === "splash" || key === "backgroundColor"))
|
|
1000
1137
|
return value;
|
|
1001
1138
|
const out = { ...kept };
|
|
1002
1139
|
for (const [inner, next] of Object.entries(value))
|
|
@@ -1024,10 +1161,12 @@ class AkanAppConfig {
|
|
|
1024
1161
|
"buildNum",
|
|
1025
1162
|
"icon",
|
|
1026
1163
|
"splash",
|
|
1164
|
+
"backgroundColor",
|
|
1027
1165
|
"permissions",
|
|
1028
1166
|
"plugins",
|
|
1029
1167
|
"deepLinks",
|
|
1030
1168
|
"updates",
|
|
1169
|
+
"devtools",
|
|
1031
1170
|
"ios",
|
|
1032
1171
|
"android",
|
|
1033
1172
|
"desktop"
|
|
@@ -1044,8 +1183,18 @@ class AkanAppConfig {
|
|
|
1044
1183
|
"activity",
|
|
1045
1184
|
"files"
|
|
1046
1185
|
],
|
|
1047
|
-
desktop: [
|
|
1048
|
-
|
|
1186
|
+
desktop: [
|
|
1187
|
+
"indexPath",
|
|
1188
|
+
"server",
|
|
1189
|
+
"recovery",
|
|
1190
|
+
"window",
|
|
1191
|
+
"screenCapture",
|
|
1192
|
+
"webview2",
|
|
1193
|
+
"publisher",
|
|
1194
|
+
"entitlements",
|
|
1195
|
+
"dmg"
|
|
1196
|
+
],
|
|
1197
|
+
desktopServer: ["omit", "profile", "modules", "env", "restart", "watchdog"],
|
|
1049
1198
|
dmg: ["background", "background2x", "window", "iconSize", "textSize", "app", "applications"],
|
|
1050
1199
|
deepLinks: ["schemes", "domains"]
|
|
1051
1200
|
};
|
|
@@ -1195,12 +1344,18 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
1195
1344
|
static async from(app, { bustImportCache = false } = {}) {
|
|
1196
1345
|
const [configImp, baseDevEnv, libs, rootPackageJson] = await Promise.all([
|
|
1197
1346
|
AkanAppConfig.importConfigModule(app.cwdPath, { bustImportCache }),
|
|
1198
|
-
WorkspaceExecutor.getBaseDevEnv(
|
|
1347
|
+
WorkspaceExecutor.getBaseDevEnv(path6.join(app.workspace.workspaceRoot, ".env")),
|
|
1199
1348
|
app.workspace.getLibs(),
|
|
1200
1349
|
app.workspace.getPackageJson()
|
|
1201
1350
|
]);
|
|
1202
1351
|
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
1203
|
-
const {
|
|
1352
|
+
const {
|
|
1353
|
+
plugins,
|
|
1354
|
+
pipeline: _pipeline,
|
|
1355
|
+
...config
|
|
1356
|
+
} = resolved ?? {};
|
|
1357
|
+
if ("subspace" in config)
|
|
1358
|
+
throw new Error(`apps/${app.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`);
|
|
1204
1359
|
const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
|
|
1205
1360
|
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
|
|
1206
1361
|
}
|
|
@@ -1263,6 +1418,9 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
1263
1418
|
return libs.filter((lib) => !rootDependencies[lib]).map((lib) => `${lib}@${this.#resolveProductionDependencyVersion(lib)}`);
|
|
1264
1419
|
}
|
|
1265
1420
|
get akanVersion() {
|
|
1421
|
+
return AkanAppConfig.akanVersion;
|
|
1422
|
+
}
|
|
1423
|
+
static get akanVersion() {
|
|
1266
1424
|
return getAkanPackageJson().version;
|
|
1267
1425
|
}
|
|
1268
1426
|
getProductionPackageJson(data = {}) {
|
|
@@ -1284,11 +1442,11 @@ var akanPackageJson = null;
|
|
|
1284
1442
|
function getAkanPackageJson() {
|
|
1285
1443
|
if (akanPackageJson)
|
|
1286
1444
|
return akanPackageJson;
|
|
1287
|
-
const sourceDir =
|
|
1445
|
+
const sourceDir = path6.dirname(fileURLToPath(import.meta.url));
|
|
1288
1446
|
const packageJsonPaths = [
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1447
|
+
path6.join(sourceDir, "../../../akanjs/package.json"),
|
|
1448
|
+
path6.join(process.cwd(), "pkgs/akanjs/package.json"),
|
|
1449
|
+
path6.join(path6.dirname(Bun.main), "node_modules/akanjs/package.json")
|
|
1292
1450
|
];
|
|
1293
1451
|
try {
|
|
1294
1452
|
packageJsonPaths.unshift(Bun.resolveSync("akanjs/package.json", sourceDir));
|
|
@@ -1342,26 +1500,81 @@ class AkanLibConfig {
|
|
|
1342
1500
|
const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
|
|
1343
1501
|
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
1344
1502
|
const { plugins, ...config } = resolved ?? {};
|
|
1503
|
+
if ("pipeline" in config)
|
|
1504
|
+
throw new Error(`libs/${lib.name}/akan.config.ts declares \`pipeline\`, which a lib does not take: pipelines belong to apps and the workspace root, and a lib's build-time work goes in an akan build plugin.`);
|
|
1505
|
+
if ("subspace" in config)
|
|
1506
|
+
throw new Error(`libs/${lib.name}/akan.config.ts declares \`subspace\`, a workspace setting: it belongs in the workspace root akan.config.ts.`);
|
|
1345
1507
|
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
1346
1508
|
}
|
|
1347
1509
|
}
|
|
1348
1510
|
//! need to refactor
|
|
1349
1511
|
var shiftBuildNum = async (app, delta) => {
|
|
1350
1512
|
const { buildNum } = (await AkanAppConfig.from(app)).native;
|
|
1351
|
-
const akanConfigPath =
|
|
1513
|
+
const akanConfigPath = path6.join(app.cwdPath, "akan.config.ts");
|
|
1352
1514
|
const akanConfig = fs2.readFileSync(akanConfigPath, "utf8");
|
|
1353
1515
|
fs2.writeFileSync(akanConfigPath, akanConfig.replace(`buildNum: ${buildNum}`, `buildNum: ${buildNum + delta}`));
|
|
1354
1516
|
};
|
|
1355
1517
|
var increaseBuildNum = async (app) => await shiftBuildNum(app, 1);
|
|
1356
1518
|
var decreaseBuildNum = async (app) => await shiftBuildNum(app, -1);
|
|
1519
|
+
// pkgs/@akanjs/devkit/akanConfig/akanWorkspaceConfig.ts
|
|
1520
|
+
import path7 from "path";
|
|
1521
|
+
class AkanWorkspaceConfig {
|
|
1522
|
+
static fileName = "akan.config.ts";
|
|
1523
|
+
static #keys = ["subspace", "pipeline", "cloud"];
|
|
1524
|
+
static #loopbackHosts = ["localhost", "127.0.0.1", "[::1]"];
|
|
1525
|
+
static async from(workspace, { bustImportCache = false } = {}) {
|
|
1526
|
+
const file = path7.join(workspace.workspaceRoot, AkanWorkspaceConfig.fileName);
|
|
1527
|
+
if (!await FileSys2.fileExists(file))
|
|
1528
|
+
return new AkanWorkspaceConfig(null, {});
|
|
1529
|
+
const input = await AkanAppConfig.importConfigModule(workspace.workspaceRoot, { bustImportCache });
|
|
1530
|
+
return new AkanWorkspaceConfig(file, input);
|
|
1531
|
+
}
|
|
1532
|
+
file;
|
|
1533
|
+
subspace;
|
|
1534
|
+
pipeline;
|
|
1535
|
+
cloudHost;
|
|
1536
|
+
constructor(file, input) {
|
|
1537
|
+
this.file = file;
|
|
1538
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
1539
|
+
throw new Error("The workspace root akan.config.ts must default-export a WorkspaceConfig object.");
|
|
1540
|
+
for (const key of Object.keys(input))
|
|
1541
|
+
if (!AkanWorkspaceConfig.#keys.includes(key))
|
|
1542
|
+
throw new Error(`The workspace root akan.config.ts declares \`${key}\`, which it does not take: it holds \`subspace\`, \`pipeline\` and \`cloud\`, and app or lib settings belong in apps/<app>/akan.config.ts or libs/<lib>/akan.config.ts.`);
|
|
1543
|
+
const config = input;
|
|
1544
|
+
this.subspace = config.subspace ?? null;
|
|
1545
|
+
this.pipeline = config.pipeline ?? null;
|
|
1546
|
+
this.cloudHost = AkanWorkspaceConfig.#cloudHost(config.cloud);
|
|
1547
|
+
}
|
|
1548
|
+
static #cloudHost(cloud) {
|
|
1549
|
+
if (cloud === undefined)
|
|
1550
|
+
return null;
|
|
1551
|
+
const refuse = (why) => {
|
|
1552
|
+
throw new Error(`The workspace root akan.config.ts has cloud ${why}.`);
|
|
1553
|
+
};
|
|
1554
|
+
if (typeof cloud !== "object" || cloud === null || Array.isArray(cloud))
|
|
1555
|
+
return refuse("that is not an object");
|
|
1556
|
+
const unknown = Object.keys(cloud).filter((key) => key !== "host");
|
|
1557
|
+
if (unknown.length)
|
|
1558
|
+
return refuse(`keys it does not take: ${unknown.join(", ")} (only \`host\`)`);
|
|
1559
|
+
const { host } = cloud;
|
|
1560
|
+
if (host === undefined)
|
|
1561
|
+
return null;
|
|
1562
|
+
const url = typeof host === "string" && URL.canParse(host) ? new URL(host) : null;
|
|
1563
|
+
if (!url || url.search || url.hash || url.username || url.password)
|
|
1564
|
+
return refuse(`.host ${JSON.stringify(host)}, which is not a URL like "https://cloud.example.com"`);
|
|
1565
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && AkanWorkspaceConfig.#loopbackHosts.includes(url.hostname)))
|
|
1566
|
+
return refuse(`.host ${JSON.stringify(host)}: https only, or http on this computer`);
|
|
1567
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1357
1570
|
// pkgs/@akanjs/devkit/jsonFormatter.ts
|
|
1358
|
-
import
|
|
1571
|
+
import path8 from "path";
|
|
1359
1572
|
|
|
1360
1573
|
class JsonFormatter {
|
|
1361
1574
|
static lineWidth = 120;
|
|
1362
1575
|
static stringify(value, filePath) {
|
|
1363
1576
|
const json = JSON.stringify(value, null, 2);
|
|
1364
|
-
if (
|
|
1577
|
+
if (path8.basename(filePath) === "package.json")
|
|
1365
1578
|
return `${json}
|
|
1366
1579
|
`;
|
|
1367
1580
|
return `${JsonFormatter.#print(JSON.parse(json), 0, { column: 0, trailing: 0 })}
|
|
@@ -1432,7 +1645,7 @@ ${" ".repeat(depth)}]`;
|
|
|
1432
1645
|
// pkgs/@akanjs/devkit/linter.ts
|
|
1433
1646
|
import { spawn } from "child_process";
|
|
1434
1647
|
import { existsSync, readFileSync } from "fs";
|
|
1435
|
-
import
|
|
1648
|
+
import path9 from "path";
|
|
1436
1649
|
import chalk from "chalk";
|
|
1437
1650
|
var parseBiomeReport = (output) => {
|
|
1438
1651
|
for (let start = output.indexOf("{");start !== -1; start = output.indexOf("{", start + 1)) {
|
|
@@ -1498,13 +1711,13 @@ class Linter {
|
|
|
1498
1711
|
#biomeBin;
|
|
1499
1712
|
constructor(cwdPath) {
|
|
1500
1713
|
this.lintRoot = this.#findBiomeRootPath(cwdPath);
|
|
1501
|
-
this.configPath = Linter.#configPathIn(this.lintRoot) ??
|
|
1502
|
-
const localBiomeBin =
|
|
1714
|
+
this.configPath = Linter.#configPathIn(this.lintRoot) ?? path9.join(this.lintRoot, "biome.json");
|
|
1715
|
+
const localBiomeBin = path9.join(this.lintRoot, "node_modules", ".bin", Linter.biomeBinName);
|
|
1503
1716
|
this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
|
|
1504
1717
|
}
|
|
1505
1718
|
static #configPathIn(dir) {
|
|
1506
1719
|
for (const fileName of BIOME_CONFIG_FILES) {
|
|
1507
|
-
const configPath =
|
|
1720
|
+
const configPath = path9.join(dir, fileName);
|
|
1508
1721
|
if (existsSync(configPath))
|
|
1509
1722
|
return configPath;
|
|
1510
1723
|
}
|
|
@@ -1513,19 +1726,19 @@ class Linter {
|
|
|
1513
1726
|
#findBiomeRootPath(dir) {
|
|
1514
1727
|
if (Linter.#configPathIn(dir))
|
|
1515
1728
|
return dir;
|
|
1516
|
-
const parentDir =
|
|
1729
|
+
const parentDir = path9.dirname(dir);
|
|
1517
1730
|
if (parentDir === dir)
|
|
1518
1731
|
throw new Error(`biome.json not found from ${dir}`);
|
|
1519
1732
|
return this.#findBiomeRootPath(parentDir);
|
|
1520
1733
|
}
|
|
1521
1734
|
#toBiomePath(filePath) {
|
|
1522
|
-
const relativePath =
|
|
1523
|
-
if (!relativePath.startsWith("..") && !
|
|
1735
|
+
const relativePath = path9.relative(this.lintRoot, filePath);
|
|
1736
|
+
if (!relativePath.startsWith("..") && !path9.isAbsolute(relativePath))
|
|
1524
1737
|
return relativePath;
|
|
1525
1738
|
return filePath;
|
|
1526
1739
|
}
|
|
1527
1740
|
#resolveFilePath(filePath) {
|
|
1528
|
-
return
|
|
1741
|
+
return path9.isAbsolute(filePath) ? filePath : path9.join(this.lintRoot, filePath);
|
|
1529
1742
|
}
|
|
1530
1743
|
async#runBiome(args, input) {
|
|
1531
1744
|
return await new Promise((resolve, reject) => {
|
|
@@ -1550,7 +1763,7 @@ class Linter {
|
|
|
1550
1763
|
const diagnosticPath = diagnostic.location?.path;
|
|
1551
1764
|
if (!diagnosticPath)
|
|
1552
1765
|
return fallbackFilePath;
|
|
1553
|
-
return
|
|
1766
|
+
return path9.isAbsolute(diagnosticPath) ? diagnosticPath : path9.join(this.lintRoot, diagnosticPath);
|
|
1554
1767
|
}
|
|
1555
1768
|
#createLintMessage(diagnostic) {
|
|
1556
1769
|
const start = diagnostic.location?.start;
|
|
@@ -1886,7 +2099,7 @@ class ManifestDependencies {
|
|
|
1886
2099
|
|
|
1887
2100
|
// pkgs/@akanjs/devkit/repoIdentity.ts
|
|
1888
2101
|
import { execFileSync } from "child_process";
|
|
1889
|
-
import
|
|
2102
|
+
import path10 from "path";
|
|
1890
2103
|
var resolved = new Map;
|
|
1891
2104
|
var readRemoteName = (workspaceRoot) => {
|
|
1892
2105
|
try {
|
|
@@ -1904,13 +2117,13 @@ var resolveRepoName = (workspaceRoot) => {
|
|
|
1904
2117
|
const cached = resolved.get(workspaceRoot);
|
|
1905
2118
|
if (cached)
|
|
1906
2119
|
return cached;
|
|
1907
|
-
const repoName = readRemoteName(workspaceRoot) ??
|
|
2120
|
+
const repoName = readRemoteName(workspaceRoot) ?? path10.basename(workspaceRoot);
|
|
1908
2121
|
resolved.set(workspaceRoot, repoName);
|
|
1909
2122
|
return repoName;
|
|
1910
2123
|
};
|
|
1911
2124
|
|
|
1912
2125
|
// pkgs/@akanjs/devkit/scanInfo.ts
|
|
1913
|
-
import
|
|
2126
|
+
import path11 from "path";
|
|
1914
2127
|
|
|
1915
2128
|
// pkgs/@akanjs/devkit/workspaceLayout.ts
|
|
1916
2129
|
var appRootAllowedFiles = new Set([
|
|
@@ -1981,8 +2194,11 @@ var libFacetRootAllowedFiles = new Set([
|
|
|
1981
2194
|
"useClient.ts",
|
|
1982
2195
|
"useServer.ts"
|
|
1983
2196
|
]);
|
|
1984
|
-
var
|
|
1985
|
-
var
|
|
2197
|
+
var isAllowedLibFacetRootFile = (filename) => libFacetRootAllowedFiles.has(filename);
|
|
2198
|
+
var testSuffix = /\.(test|spec)(\.tsx?)$/;
|
|
2199
|
+
var libFacetRootHintOf = (filename) => testSuffix.test(filename) ? "a test beside the generated barrels; move it into the module folder it covers " + "(lib/<model>/<model>.signal.test.ts), or into srvkit/ when it drives several modules" : null;
|
|
2200
|
+
var moduleTestTargetOf = (filename) => testSuffix.test(filename) ? filename.replace(testSuffix, "$2") : null;
|
|
2201
|
+
var moduleTestHint = "a module test is named after the one module file it covers (<model>.<role>.test.ts); add a describe block to " + "that file's suite instead, or move a suite that drives several modules into srvkit/";
|
|
1986
2202
|
var barrelFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
|
|
1987
2203
|
var barrelShadowingIndexFiles = new Set(["index.tsx", "index.jsx", "index.mts"]);
|
|
1988
2204
|
var facetIndexShadowOf = (sysRelativePath) => {
|
|
@@ -2029,9 +2245,9 @@ var moduleUiFileTypes = {
|
|
|
2029
2245
|
scalar: new Set(["Template", "Unit"])
|
|
2030
2246
|
};
|
|
2031
2247
|
var createDependencyScanner = async (exec) => (await import("./dependencyScanner-06z2dmrr.js")).TypeScriptDependencyScanner.from(exec);
|
|
2032
|
-
var getScanPath = (exec, relativePath) =>
|
|
2248
|
+
var getScanPath = (exec, relativePath) => path11.posix.join(`${exec.type}s`, exec.name, relativePath.split(path11.sep).join("/"));
|
|
2033
2249
|
var getModuleNameFromPath = (kind, modulePath) => {
|
|
2034
|
-
const dirname =
|
|
2250
|
+
const dirname = path11.basename(modulePath);
|
|
2035
2251
|
return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
|
|
2036
2252
|
};
|
|
2037
2253
|
async function assertScanConvention(exec, libRoot) {
|
|
@@ -2052,24 +2268,24 @@ async function assertScanConvention(exec, libRoot) {
|
|
|
2052
2268
|
for (const filename of await exec.readdir(facet)) {
|
|
2053
2269
|
const shadow = facetIndexShadowOf(`${facet}/${filename}`);
|
|
2054
2270
|
if (shadow)
|
|
2055
|
-
addViolation(
|
|
2271
|
+
addViolation(path11.join(facet, filename), shadow);
|
|
2056
2272
|
}
|
|
2057
2273
|
if (exec.type === "lib")
|
|
2058
2274
|
for (const { relativePath, reason } of await exec.getPageConventionViolations())
|
|
2059
2275
|
addViolation(relativePath, reason);
|
|
2060
2276
|
for (const filename of libRoot.files)
|
|
2061
2277
|
if (!isAllowedLibFacetRootFile(filename))
|
|
2062
|
-
addViolation(
|
|
2278
|
+
addViolation(path11.join("lib", filename), libFacetRootHintOf(filename) ?? "unsupported lib facet root file");
|
|
2063
2279
|
for (const dirname of libRoot.dirs)
|
|
2064
2280
|
if (dirname.startsWith("__") && !internalLibDirs.has(dirname))
|
|
2065
|
-
addViolation(
|
|
2281
|
+
addViolation(path11.join("lib", dirname), "unsupported internal lib folder");
|
|
2066
2282
|
const databaseDirs = libRoot.dirs.filter((dirname) => !dirname.startsWith("_"));
|
|
2067
2283
|
const serviceDirs = libRoot.dirs.filter((dirname) => dirname.startsWith("_") && !dirname.startsWith("__"));
|
|
2068
2284
|
const scalarDirs = await exec.readdir("lib/__scalar");
|
|
2069
2285
|
await Promise.all([
|
|
2070
|
-
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database",
|
|
2071
|
-
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service",
|
|
2072
|
-
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar",
|
|
2286
|
+
...databaseDirs.map((dirname) => validateModuleFiles(exec, violations, "database", path11.join("lib", dirname))),
|
|
2287
|
+
...serviceDirs.map((dirname) => validateModuleFiles(exec, violations, "service", path11.join("lib", dirname))),
|
|
2288
|
+
...scalarDirs.map((dirname) => validateModuleFiles(exec, violations, "scalar", path11.join("lib/__scalar", dirname)))
|
|
2073
2289
|
]);
|
|
2074
2290
|
if (violations.length > 0) {
|
|
2075
2291
|
throw new Error(`[scan-convention]
|
|
@@ -2081,19 +2297,21 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
2081
2297
|
const { files, dirs } = await exec.getFilesAndDirs(modulePath);
|
|
2082
2298
|
const moduleName = getModuleNameFromPath(kind, modulePath);
|
|
2083
2299
|
dirs.forEach((dirname) => {
|
|
2084
|
-
violations.push(`${getScanPath(exec,
|
|
2300
|
+
violations.push(`${getScanPath(exec, path11.join(modulePath, dirname))}: unsupported module folder`);
|
|
2085
2301
|
});
|
|
2086
2302
|
const uiModuleName = moduleName[0].toUpperCase() + moduleName.slice(1);
|
|
2087
2303
|
for (const filename of files) {
|
|
2088
|
-
const scanPath = getScanPath(exec,
|
|
2089
|
-
if (filename === "index.ts" || filename === "index.tsx"
|
|
2304
|
+
const scanPath = getScanPath(exec, path11.join(modulePath, filename));
|
|
2305
|
+
if (filename === "index.ts" || filename === "index.tsx")
|
|
2090
2306
|
continue;
|
|
2091
2307
|
if (filename === `${moduleName}.abstract.md`)
|
|
2092
2308
|
continue;
|
|
2093
|
-
const
|
|
2094
|
-
const
|
|
2309
|
+
const testTarget = moduleTestTargetOf(filename);
|
|
2310
|
+
const sourceName = testTarget ?? filename;
|
|
2311
|
+
const uiMatch = sourceName.match(/^([A-Z][A-Za-z0-9]+)\.([A-Z][A-Za-z0-9]*)\.tsx$/);
|
|
2312
|
+
const match = uiMatch ?? sourceName.match(/^([a-z][a-zA-Z0-9]*)\.([a-z][a-z0-9]*)\.ts$/);
|
|
2095
2313
|
if (!match) {
|
|
2096
|
-
violations.push(`${scanPath}: unsupported module file`);
|
|
2314
|
+
violations.push(`${scanPath}: ${testTarget ? moduleTestHint : "unsupported module file"}`);
|
|
2097
2315
|
continue;
|
|
2098
2316
|
}
|
|
2099
2317
|
const [, fileModuleName, fileType] = match;
|
|
@@ -2101,7 +2319,7 @@ async function validateModuleFiles(exec, violations, kind, modulePath) {
|
|
|
2101
2319
|
if (fileModuleName !== expectedName)
|
|
2102
2320
|
violations.push(`${scanPath}: module name mismatch: expected '${expectedName}', got '${fileModuleName}'`);
|
|
2103
2321
|
if (!(uiMatch ? moduleUiFileTypes : moduleNonUiFileTypes)[kind].has(fileType))
|
|
2104
|
-
violations.push(`${scanPath}: unsupported ${kind}${uiMatch ? " UI" : ""} file`);
|
|
2322
|
+
violations.push(`${scanPath}: ${testTarget ? moduleTestHint : `unsupported ${kind}${uiMatch ? " UI" : ""} file`}`);
|
|
2105
2323
|
}
|
|
2106
2324
|
}
|
|
2107
2325
|
|
|
@@ -2158,16 +2376,16 @@ class ScanInfo {
|
|
|
2158
2376
|
}
|
|
2159
2377
|
};
|
|
2160
2378
|
await Promise.all([
|
|
2161
|
-
...databaseDirs.map((name) => collect(
|
|
2162
|
-
...serviceDirs.map((dirname) => collect(
|
|
2163
|
-
...scalarDirs.map((name) => collect(
|
|
2379
|
+
...databaseDirs.map((name) => collect(path11.join("lib", name), name, "databases")),
|
|
2380
|
+
...serviceDirs.map((dirname) => collect(path11.join("lib", dirname), dirname.slice(1), "services")),
|
|
2381
|
+
...scalarDirs.map((name) => collect(path11.join("lib/__scalar", name), name, "scalars"))
|
|
2164
2382
|
]);
|
|
2165
2383
|
const routes = exec.type === "lib" ? [] : await exec.getPageKeys();
|
|
2166
2384
|
const common = {
|
|
2167
2385
|
name: exec.name,
|
|
2168
2386
|
type: exec.type,
|
|
2169
2387
|
repoName: exec.workspace.repoName,
|
|
2170
|
-
serveDomain: WorkspaceExecutor.getBaseDevEnv(
|
|
2388
|
+
serveDomain: WorkspaceExecutor.getBaseDevEnv(path11.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
|
|
2171
2389
|
files,
|
|
2172
2390
|
libDeps,
|
|
2173
2391
|
pkgDeps,
|
|
@@ -2549,10 +2767,10 @@ class Executor {
|
|
|
2549
2767
|
return this.#settle(proc, target, { redStderr: true }).then(({ code, signal }) => ({ code, signal }));
|
|
2550
2768
|
}
|
|
2551
2769
|
getPath(filePath) {
|
|
2552
|
-
if (
|
|
2770
|
+
if (path12.isAbsolute(filePath))
|
|
2553
2771
|
return filePath;
|
|
2554
2772
|
if (filePath.startsWith("."))
|
|
2555
|
-
return
|
|
2773
|
+
return path12.join(this.cwdPath, filePath);
|
|
2556
2774
|
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
2557
2775
|
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
2558
2776
|
let overlapLength = 0;
|
|
@@ -2566,7 +2784,7 @@ class Executor {
|
|
|
2566
2784
|
if (isOverlap)
|
|
2567
2785
|
overlapLength = i;
|
|
2568
2786
|
}
|
|
2569
|
-
return
|
|
2787
|
+
return path12.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
2570
2788
|
}
|
|
2571
2789
|
async mkdir(dirPath) {
|
|
2572
2790
|
const writePath = this.getPath(dirPath);
|
|
@@ -2607,7 +2825,7 @@ class Executor {
|
|
|
2607
2825
|
return this;
|
|
2608
2826
|
}
|
|
2609
2827
|
async removeDir(dirPath) {
|
|
2610
|
-
const readPath =
|
|
2828
|
+
const readPath = path12.join(this.getPath(dirPath), ".");
|
|
2611
2829
|
if (await FileSys2.entryExists(readPath))
|
|
2612
2830
|
await rm3(readPath, { recursive: true, force: true });
|
|
2613
2831
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
@@ -2615,7 +2833,7 @@ class Executor {
|
|
|
2615
2833
|
}
|
|
2616
2834
|
async writeFile(filePath, content, { overwrite = true, silent = false } = {}) {
|
|
2617
2835
|
const writePath = this.getPath(filePath);
|
|
2618
|
-
const dir =
|
|
2836
|
+
const dir = path12.dirname(writePath);
|
|
2619
2837
|
if (!await FileSys2.dirExists(dir))
|
|
2620
2838
|
await mkdir2(dir, { recursive: true });
|
|
2621
2839
|
let contentStr = typeof content === "string" ? content : JsonFormatter.stringify(content, writePath);
|
|
@@ -2640,7 +2858,7 @@ class Executor {
|
|
|
2640
2858
|
await this.writeFile(filePath, content);
|
|
2641
2859
|
}
|
|
2642
2860
|
async getLocalFile(targetPath) {
|
|
2643
|
-
const filePath =
|
|
2861
|
+
const filePath = path12.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
2644
2862
|
const content = await this.readFile(filePath);
|
|
2645
2863
|
return { filePath, content };
|
|
2646
2864
|
}
|
|
@@ -2727,9 +2945,9 @@ class Executor {
|
|
|
2727
2945
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
2728
2946
|
if (result === null)
|
|
2729
2947
|
return null;
|
|
2730
|
-
const filename = typeof result === "object" ? result.filename :
|
|
2948
|
+
const filename = typeof result === "object" ? result.filename : path12.basename(targetPath).replace(".js", ".ts");
|
|
2731
2949
|
const content = typeof result === "object" ? result.content : result;
|
|
2732
|
-
const convertedTargetPath = convertPath(`${
|
|
2950
|
+
const convertedTargetPath = convertPath(`${path12.dirname(targetPath)}/${filename}`);
|
|
2733
2951
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2734
2952
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
2735
2953
|
} else if (targetPath.endsWith(".template")) {
|
|
@@ -2738,10 +2956,10 @@ class Executor {
|
|
|
2738
2956
|
const convertedContent = Object.entries(dict).reduce((data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value), content);
|
|
2739
2957
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
2740
2958
|
return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
|
|
2741
|
-
} else if (staticTemplateFileExtensions.has(
|
|
2959
|
+
} else if (staticTemplateFileExtensions.has(path12.extname(targetPath).toLowerCase())) {
|
|
2742
2960
|
const convertedTargetPath = convertPath(targetPath);
|
|
2743
2961
|
const writePath = this.getPath(convertedTargetPath);
|
|
2744
|
-
const dirname =
|
|
2962
|
+
const dirname = path12.dirname(writePath);
|
|
2745
2963
|
if (!await FileSys2.dirExists(dirname))
|
|
2746
2964
|
await mkdir2(dirname, { recursive: true });
|
|
2747
2965
|
await copyFile(templatePath, writePath);
|
|
@@ -2763,7 +2981,7 @@ class Executor {
|
|
|
2763
2981
|
if ((await stat2(templatePath)).isFile()) {
|
|
2764
2982
|
const fileContent = await this.#applyTemplateFile({
|
|
2765
2983
|
templatePath,
|
|
2766
|
-
targetPath:
|
|
2984
|
+
targetPath: path12.join(basePath, path12.basename(templatePath)),
|
|
2767
2985
|
scanInfo,
|
|
2768
2986
|
overwrite
|
|
2769
2987
|
}, dict, options);
|
|
@@ -2771,19 +2989,19 @@ class Executor {
|
|
|
2771
2989
|
} else {
|
|
2772
2990
|
const subdirs = await readDirEntries(templatePath);
|
|
2773
2991
|
const fileContents = (await Promise.all(subdirs.map(async (subdir) => {
|
|
2774
|
-
const subpath =
|
|
2992
|
+
const subpath = path12.join(templatePath, subdir);
|
|
2775
2993
|
if ((await stat2(subpath)).isFile()) {
|
|
2776
2994
|
const fileContent = await this.#applyTemplateFile({
|
|
2777
2995
|
templatePath: subpath,
|
|
2778
|
-
targetPath:
|
|
2996
|
+
targetPath: path12.join(basePath, subdir),
|
|
2779
2997
|
scanInfo,
|
|
2780
2998
|
overwrite
|
|
2781
2999
|
}, dict, options);
|
|
2782
3000
|
return fileContent ? [fileContent] : [];
|
|
2783
3001
|
} else
|
|
2784
3002
|
return await this._applyTemplate({
|
|
2785
|
-
basePath:
|
|
2786
|
-
template:
|
|
3003
|
+
basePath: path12.join(basePath, subdir),
|
|
3004
|
+
template: path12.join(template, subdir),
|
|
2787
3005
|
scanInfo,
|
|
2788
3006
|
dict,
|
|
2789
3007
|
overwrite,
|
|
@@ -2795,8 +3013,8 @@ class Executor {
|
|
|
2795
3013
|
}
|
|
2796
3014
|
async#resolveTemplateRoot() {
|
|
2797
3015
|
const candidates = [
|
|
2798
|
-
|
|
2799
|
-
|
|
3016
|
+
path12.resolve(getDirname2(import.meta.url), "templates"),
|
|
3017
|
+
path12.resolve(getDirname2(import.meta.url), "../cli/templates")
|
|
2800
3018
|
];
|
|
2801
3019
|
for (const candidate of candidates) {
|
|
2802
3020
|
if (await FileSys2.dirExists(candidate))
|
|
@@ -2864,11 +3082,11 @@ class Executor {
|
|
|
2864
3082
|
async#resolveTypecheckWorkerEntry() {
|
|
2865
3083
|
const dirname = getDirname2(import.meta.url);
|
|
2866
3084
|
const candidates = [
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
3085
|
+
path12.join(process.cwd(), "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
3086
|
+
path12.join(process.cwd(), "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts"),
|
|
3087
|
+
path12.join(dirname, "typecheck/typecheck.proc.ts"),
|
|
3088
|
+
path12.join(dirname, "typecheck.proc.js"),
|
|
3089
|
+
path12.join(dirname, "typecheck.proc.ts")
|
|
2872
3090
|
];
|
|
2873
3091
|
for (const candidate of candidates)
|
|
2874
3092
|
if (await Bun.file(candidate).exists())
|
|
@@ -2942,6 +3160,13 @@ class WorkspaceExecutor extends Executor {
|
|
|
2942
3160
|
async scan() {
|
|
2943
3161
|
return await WorkspaceInfo.fromExecutor(this);
|
|
2944
3162
|
}
|
|
3163
|
+
#akanConfig = null;
|
|
3164
|
+
async getConfig({ refresh } = {}) {
|
|
3165
|
+
if (this.#akanConfig && !refresh)
|
|
3166
|
+
return this.#akanConfig;
|
|
3167
|
+
this.#akanConfig = await AkanWorkspaceConfig.from(this, { bustImportCache: refresh });
|
|
3168
|
+
return this.#akanConfig;
|
|
3169
|
+
}
|
|
2945
3170
|
async getApps() {
|
|
2946
3171
|
return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
|
|
2947
3172
|
}
|
|
@@ -2988,7 +3213,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
2988
3213
|
await Promise.all(dirs.map(async (dir) => {
|
|
2989
3214
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
2990
3215
|
return;
|
|
2991
|
-
const dirPath =
|
|
3216
|
+
const dirPath = path12.join(dirname, dir);
|
|
2992
3217
|
if ((await stat2(dirPath)).isDirectory()) {
|
|
2993
3218
|
results.push(`${prefix}${dir}`);
|
|
2994
3219
|
if (maxDepth > 0)
|
|
@@ -3025,9 +3250,9 @@ class WorkspaceExecutor extends Executor {
|
|
|
3025
3250
|
await Promise.all(dirs.map(async (dir) => {
|
|
3026
3251
|
if (AVOID_DIRS.includes(dir))
|
|
3027
3252
|
return;
|
|
3028
|
-
const dirPath =
|
|
3253
|
+
const dirPath = path12.join(dirname, dir);
|
|
3029
3254
|
if (await FileSys2.dirExists(dirPath)) {
|
|
3030
|
-
const hasTargetFile = await FileSys2.fileExists(
|
|
3255
|
+
const hasTargetFile = await FileSys2.fileExists(path12.join(dirPath, targetFilename));
|
|
3031
3256
|
if (hasTargetFile)
|
|
3032
3257
|
results.push(`${prefix}${dir}`);
|
|
3033
3258
|
if (maxDepth > 0)
|
|
@@ -3086,7 +3311,7 @@ class SysExecutor extends Executor {
|
|
|
3086
3311
|
return [];
|
|
3087
3312
|
const violations = [];
|
|
3088
3313
|
for (const entry of await this.getAllFiles("**/*", { cwd: this.getPath("page") })) {
|
|
3089
|
-
const relativePath = entry.split(
|
|
3314
|
+
const relativePath = entry.split(path12.sep).join("/");
|
|
3090
3315
|
const reason = getPageSourceFileViolation(relativePath);
|
|
3091
3316
|
if (reason)
|
|
3092
3317
|
violations.push({ relativePath: `page/${relativePath}`, reason });
|
|
@@ -3181,7 +3406,7 @@ class SysExecutor extends Executor {
|
|
|
3181
3406
|
await this.setPackageJson({ ...packageJson, dependencies, devDependencies });
|
|
3182
3407
|
}
|
|
3183
3408
|
async getLocalFile(targetPath) {
|
|
3184
|
-
const filePath =
|
|
3409
|
+
const filePath = path12.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
3185
3410
|
const content = await this.workspace.readFile(filePath);
|
|
3186
3411
|
return { filePath, content };
|
|
3187
3412
|
}
|
|
@@ -3248,7 +3473,7 @@ class SysExecutor extends Executor {
|
|
|
3248
3473
|
}
|
|
3249
3474
|
async applyTemplate(options) {
|
|
3250
3475
|
const scanInfo = await this.scan();
|
|
3251
|
-
const fileContents = await
|
|
3476
|
+
const fileContents = await super.applyTemplate({ ...options, scanInfo });
|
|
3252
3477
|
await this.scan();
|
|
3253
3478
|
return fileContents;
|
|
3254
3479
|
}
|
|
@@ -3269,19 +3494,19 @@ class AppExecutor extends SysExecutor {
|
|
|
3269
3494
|
static from(executor, name) {
|
|
3270
3495
|
return new AppExecutor({ workspace: executor instanceof WorkspaceExecutor ? executor : executor.workspace, name });
|
|
3271
3496
|
}
|
|
3272
|
-
static #keptOnStart = [
|
|
3273
|
-
static #legacyNative =
|
|
3497
|
+
static #keptOnStart = [path12.join(".akan", "cache", "bin")];
|
|
3498
|
+
static #legacyNative = path12.join(".akan", "native");
|
|
3274
3499
|
async#removeDevOutput(dir = ".akan") {
|
|
3275
3500
|
const entries = await readDirEntries(this.getPath(dir)).catch(() => []);
|
|
3276
3501
|
await Promise.all(entries.map(async (name) => {
|
|
3277
|
-
const entry =
|
|
3502
|
+
const entry = path12.join(dir, name);
|
|
3278
3503
|
if (entry === AppExecutor.#legacyNative) {
|
|
3279
3504
|
this.logger.warn(`apps/${this.name}/.akan/native holds native builds from before they moved to dist/native/${this.name}: upload any release waiting in its updates folders, then delete it.`);
|
|
3280
3505
|
return;
|
|
3281
3506
|
}
|
|
3282
3507
|
if (AppExecutor.#keptOnStart.includes(entry))
|
|
3283
3508
|
return;
|
|
3284
|
-
if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${
|
|
3509
|
+
if (AppExecutor.#keptOnStart.some((kept) => kept.startsWith(`${entry}${path12.sep}`)))
|
|
3285
3510
|
return await this.#removeDevOutput(entry);
|
|
3286
3511
|
await this.removeDir(entry);
|
|
3287
3512
|
}));
|
|
@@ -3405,13 +3630,13 @@ class AppExecutor extends SysExecutor {
|
|
|
3405
3630
|
absolute: false,
|
|
3406
3631
|
onlyFiles: true
|
|
3407
3632
|
})) {
|
|
3408
|
-
const segments = rel.split(
|
|
3633
|
+
const segments = rel.split(path12.sep);
|
|
3409
3634
|
if (segments.some((s) => s === "node_modules"))
|
|
3410
3635
|
continue;
|
|
3411
3636
|
const posix = `${root.keyPrefix}${segments.join("/")}`;
|
|
3412
3637
|
if (!root.keyPrefix && libKeyPrefixes.some((prefix) => posix.startsWith(prefix)))
|
|
3413
3638
|
continue;
|
|
3414
|
-
const absPath =
|
|
3639
|
+
const absPath = path12.join(root.dir, ...segments);
|
|
3415
3640
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
3416
3641
|
if (!isRouteSourceFile(posix))
|
|
3417
3642
|
continue;
|
|
@@ -3552,7 +3777,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3552
3777
|
static async#linkLibAsset(targetPath, linkPath) {
|
|
3553
3778
|
const isWindows = process.platform === "win32";
|
|
3554
3779
|
try {
|
|
3555
|
-
const target = isWindows ? targetPath :
|
|
3780
|
+
const target = isWindows ? targetPath : path12.relative(path12.dirname(linkPath), targetPath);
|
|
3556
3781
|
await symlink(target, linkPath, isWindows ? "junction" : "dir");
|
|
3557
3782
|
} catch (error) {
|
|
3558
3783
|
if (!isWindows)
|
|
@@ -3595,7 +3820,7 @@ class AppExecutor extends SysExecutor {
|
|
|
3595
3820
|
await this.writeFile(rel, content, opts);
|
|
3596
3821
|
},
|
|
3597
3822
|
readEnvClient: async () => {
|
|
3598
|
-
const envClientPath =
|
|
3823
|
+
const envClientPath = path12.join(this.cwdPath, "env", "env.client.ts");
|
|
3599
3824
|
if (!await FileSys2.fileExists(envClientPath))
|
|
3600
3825
|
return null;
|
|
3601
3826
|
try {
|
|
@@ -3689,7 +3914,7 @@ class PkgExecutor extends Executor {
|
|
|
3689
3914
|
return overrideVersion;
|
|
3690
3915
|
try {
|
|
3691
3916
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
3692
|
-
if (!await Bun.file(
|
|
3917
|
+
if (!await Bun.file(path12.join(this.workspace.workspaceRoot, packageJsonPath)).exists())
|
|
3693
3918
|
return;
|
|
3694
3919
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
3695
3920
|
if (packageJson.name === dep)
|
|
@@ -3782,4 +4007,4 @@ class ModuleExecutor extends Executor {
|
|
|
3782
4007
|
}
|
|
3783
4008
|
}
|
|
3784
4009
|
|
|
3785
|
-
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, LibSource, formatLibStatuses, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, barrelFacetDirs, facetIndexShadowOf, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|
|
4010
|
+
export { AGENT_BLOCK_START, AGENT_BLOCK_END, stampBlockVersion, extractBlockVersion, readDevkitVersion, upsertAgentBlock, extractAgentBlock, renderRecipeEntries, collectScopeRecipeSources, renderScopeAgentBlock, CLAUDE_COMMENT_RULE, binPlatforms, ServerEnvProfile, AkanAppConfig, AkanWorkspaceConfig, AsyncDefaultExportDetector, resolveSsrPageEntriesForApp, CodegenLock, Linter, LibSource, formatLibStatuses, rootAllowedFiles, rootAllowedDirs, isAllowedLibFacetRootFile, libFacetRootHintOf, barrelFacetDirs, facetIndexShadowOf, rootEntryHintOf, isScannedRootEntry, AppInfo, Spinner, Executor, WorkspaceExecutor, SysExecutor, AppExecutor, LibExecutor, PkgExecutor, ModuleExecutor };
|