@akanjs/devkit 2.1.1-rc.1 → 2.1.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.
- package/aiEditor.test.ts +68 -0
- package/aiEditor.ts +183 -57
- package/cloud/cloudApi.ts +83 -50
- package/cloud/constants.ts +48 -0
- package/cloud/globalConfig.ts +109 -0
- package/cloud/index.ts +2 -0
- package/executors.ts +748 -164
- package/index.ts +2 -3
- package/linter.ts +308 -97
- package/package.json +2 -2
- package/prompter.ts +17 -4
- package/typecheck/typecheck.proc.ts +21 -0
- package/auth.ts +0 -41
- package/constants.ts +0 -32
package/executors.ts
CHANGED
|
@@ -8,7 +8,12 @@ import {
|
|
|
8
8
|
spawn,
|
|
9
9
|
} from "node:child_process";
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
copyFile,
|
|
13
|
+
mkdir,
|
|
14
|
+
readdir as readDirEntries,
|
|
15
|
+
stat,
|
|
16
|
+
} from "node:fs/promises";
|
|
12
17
|
import path from "node:path";
|
|
13
18
|
import {
|
|
14
19
|
capitalize,
|
|
@@ -21,7 +26,12 @@ import {
|
|
|
21
26
|
import { $ } from "bun";
|
|
22
27
|
import chalk from "chalk";
|
|
23
28
|
import ts from "typescript";
|
|
24
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
AkanAppConfig,
|
|
31
|
+
AkanLibConfig,
|
|
32
|
+
decreaseBuildNum,
|
|
33
|
+
increaseBuildNum,
|
|
34
|
+
} from "./akanConfig";
|
|
25
35
|
import { FileSys } from "./fileSys";
|
|
26
36
|
import { getDirname } from "./getDirname";
|
|
27
37
|
import { Linter } from "./linter";
|
|
@@ -64,7 +74,8 @@ const staticTemplateFileExtensions = new Set([
|
|
|
64
74
|
".xml",
|
|
65
75
|
]);
|
|
66
76
|
|
|
67
|
-
const formatCommandArg = (value: string) =>
|
|
77
|
+
const formatCommandArg = (value: string) =>
|
|
78
|
+
/^[\w@%+=:,./-]+$/.test(value) ? value : JSON.stringify(value);
|
|
68
79
|
|
|
69
80
|
const formatCommandForDisplay = (command: string, args: string[] = []) =>
|
|
70
81
|
[command, ...args].map(formatCommandArg).join(" ");
|
|
@@ -100,11 +111,21 @@ export class CommandExecutionError extends Error {
|
|
|
100
111
|
cause,
|
|
101
112
|
}: CommandExecutionErrorOptions) {
|
|
102
113
|
const displayCommand = formatCommandForDisplay(command, args);
|
|
103
|
-
const status = signal
|
|
114
|
+
const status = signal
|
|
115
|
+
? `signal: ${signal}`
|
|
116
|
+
: `exit code: ${code ?? "unknown"}`;
|
|
104
117
|
const output = (stderr || stdout).trim();
|
|
105
|
-
super(
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
super(
|
|
119
|
+
[
|
|
120
|
+
`Command failed: ${displayCommand}`,
|
|
121
|
+
`cwd: ${cwd}`,
|
|
122
|
+
status,
|
|
123
|
+
output ? `\n${output}` : "",
|
|
124
|
+
].join("\n"),
|
|
125
|
+
{
|
|
126
|
+
cause,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
108
129
|
this.name = "CommandExecutionError";
|
|
109
130
|
this.command = command;
|
|
110
131
|
this.args = args;
|
|
@@ -138,12 +159,17 @@ const parseEnvFile = (envPath: string): Record<string, string> => {
|
|
|
138
159
|
for (const line of content.split(/\r?\n/)) {
|
|
139
160
|
const trimmed = line.trim();
|
|
140
161
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
141
|
-
const normalized = trimmed.startsWith("export ")
|
|
162
|
+
const normalized = trimmed.startsWith("export ")
|
|
163
|
+
? trimmed.slice("export ".length).trim()
|
|
164
|
+
: trimmed;
|
|
142
165
|
const separatorIndex = normalized.indexOf("=");
|
|
143
166
|
if (separatorIndex <= 0) continue;
|
|
144
167
|
const key = normalized.slice(0, separatorIndex).trim();
|
|
145
168
|
let value = normalized.slice(separatorIndex + 1).trim();
|
|
146
|
-
if (
|
|
169
|
+
if (
|
|
170
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
171
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
172
|
+
) {
|
|
147
173
|
value = value.slice(1, -1);
|
|
148
174
|
}
|
|
149
175
|
env[key] = value;
|
|
@@ -151,7 +177,13 @@ const parseEnvFile = (envPath: string): Record<string, string> => {
|
|
|
151
177
|
return env;
|
|
152
178
|
};
|
|
153
179
|
|
|
154
|
-
const PAGE_ROUTE_EXPORTS = new Set([
|
|
180
|
+
const PAGE_ROUTE_EXPORTS = new Set([
|
|
181
|
+
"default",
|
|
182
|
+
"pageConfig",
|
|
183
|
+
"head",
|
|
184
|
+
"generateHead",
|
|
185
|
+
"Loading",
|
|
186
|
+
]);
|
|
155
187
|
const ROOT_LAYOUT_EXPORTS = new Set([
|
|
156
188
|
"default",
|
|
157
189
|
"head",
|
|
@@ -164,7 +196,12 @@ const ROOT_LAYOUT_EXPORTS = new Set([
|
|
|
164
196
|
"gaTrackingId",
|
|
165
197
|
"Loading",
|
|
166
198
|
]);
|
|
167
|
-
const LAYOUT_ROUTE_EXPORTS = new Set([
|
|
199
|
+
const LAYOUT_ROUTE_EXPORTS = new Set([
|
|
200
|
+
"default",
|
|
201
|
+
"head",
|
|
202
|
+
"generateHead",
|
|
203
|
+
"Loading",
|
|
204
|
+
]);
|
|
168
205
|
|
|
169
206
|
function validateRouteSourceExports(
|
|
170
207
|
source: string,
|
|
@@ -172,23 +209,42 @@ function validateRouteSourceExports(
|
|
|
172
209
|
kind: "page" | "layout",
|
|
173
210
|
options: { rootLayout?: boolean } = {},
|
|
174
211
|
) {
|
|
175
|
-
const sourceFile = ts.createSourceFile(
|
|
212
|
+
const sourceFile = ts.createSourceFile(
|
|
213
|
+
filePath,
|
|
214
|
+
source,
|
|
215
|
+
ts.ScriptTarget.Latest,
|
|
216
|
+
true,
|
|
217
|
+
ts.ScriptKind.TSX,
|
|
218
|
+
);
|
|
176
219
|
const allowed =
|
|
177
|
-
kind === "page"
|
|
220
|
+
kind === "page"
|
|
221
|
+
? PAGE_ROUTE_EXPORTS
|
|
222
|
+
: options.rootLayout
|
|
223
|
+
? ROOT_LAYOUT_EXPORTS
|
|
224
|
+
: LAYOUT_ROUTE_EXPORTS;
|
|
178
225
|
const exported = new Set<string>();
|
|
179
226
|
const assertExport = (name: string) => {
|
|
180
227
|
if (!allowed.has(name)) {
|
|
181
|
-
throw new Error(
|
|
228
|
+
throw new Error(
|
|
229
|
+
`[route-convention] unsupported export "${name}" in ${filePath}`,
|
|
230
|
+
);
|
|
182
231
|
}
|
|
183
232
|
exported.add(name);
|
|
184
233
|
};
|
|
185
234
|
|
|
186
235
|
for (const statement of sourceFile.statements) {
|
|
187
|
-
if (
|
|
236
|
+
if (
|
|
237
|
+
ts.isInterfaceDeclaration(statement) ||
|
|
238
|
+
ts.isTypeAliasDeclaration(statement)
|
|
239
|
+
)
|
|
240
|
+
continue;
|
|
188
241
|
if (ts.isExportDeclaration(statement)) {
|
|
189
242
|
if (statement.isTypeOnly) continue;
|
|
190
243
|
const clause = statement.exportClause;
|
|
191
|
-
if (!clause)
|
|
244
|
+
if (!clause)
|
|
245
|
+
throw new Error(
|
|
246
|
+
`[route-convention] export * is not allowed in route modules: ${filePath}`,
|
|
247
|
+
);
|
|
192
248
|
if (ts.isNamedExports(clause)) {
|
|
193
249
|
for (const element of clause.elements) {
|
|
194
250
|
if (element.isTypeOnly) continue;
|
|
@@ -197,17 +253,22 @@ function validateRouteSourceExports(
|
|
|
197
253
|
}
|
|
198
254
|
continue;
|
|
199
255
|
}
|
|
200
|
-
const modifiers = ts.canHaveModifiers(statement)
|
|
201
|
-
|
|
256
|
+
const modifiers = ts.canHaveModifiers(statement)
|
|
257
|
+
? ts.getModifiers(statement)
|
|
258
|
+
: undefined;
|
|
259
|
+
const isExported =
|
|
260
|
+
modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
202
261
|
if (!isExported) continue;
|
|
203
|
-
const isDefault =
|
|
262
|
+
const isDefault =
|
|
263
|
+
modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
|
|
204
264
|
if (isDefault) {
|
|
205
265
|
assertExport("default");
|
|
206
266
|
continue;
|
|
207
267
|
}
|
|
208
268
|
if (ts.isVariableStatement(statement)) {
|
|
209
269
|
for (const declaration of statement.declarationList.declarations) {
|
|
210
|
-
if (ts.isIdentifier(declaration.name))
|
|
270
|
+
if (ts.isIdentifier(declaration.name))
|
|
271
|
+
assertExport(declaration.name.text);
|
|
211
272
|
}
|
|
212
273
|
continue;
|
|
213
274
|
}
|
|
@@ -217,7 +278,9 @@ function validateRouteSourceExports(
|
|
|
217
278
|
}
|
|
218
279
|
}
|
|
219
280
|
if (exported.has("head") && exported.has("generateHead")) {
|
|
220
|
-
throw new Error(
|
|
281
|
+
throw new Error(
|
|
282
|
+
`[route-convention] head and generateHead cannot both be exported in ${filePath}`,
|
|
283
|
+
);
|
|
221
284
|
}
|
|
222
285
|
}
|
|
223
286
|
|
|
@@ -261,16 +324,40 @@ export class Executor {
|
|
|
261
324
|
});
|
|
262
325
|
return new Promise((resolve, reject) => {
|
|
263
326
|
proc.on("error", (error) => {
|
|
264
|
-
reject(
|
|
327
|
+
reject(
|
|
328
|
+
new CommandExecutionError({
|
|
329
|
+
command,
|
|
330
|
+
cwd,
|
|
331
|
+
code: null,
|
|
332
|
+
signal: null,
|
|
333
|
+
stdout,
|
|
334
|
+
stderr,
|
|
335
|
+
cause: error,
|
|
336
|
+
}),
|
|
337
|
+
);
|
|
265
338
|
});
|
|
266
339
|
proc.on("exit", (code, signal) => {
|
|
267
|
-
if (!!code || signal)
|
|
340
|
+
if (!!code || signal)
|
|
341
|
+
reject(
|
|
342
|
+
new CommandExecutionError({
|
|
343
|
+
command,
|
|
344
|
+
cwd,
|
|
345
|
+
code,
|
|
346
|
+
signal,
|
|
347
|
+
stdout,
|
|
348
|
+
stderr,
|
|
349
|
+
}),
|
|
350
|
+
);
|
|
268
351
|
else resolve({ code, signal });
|
|
269
352
|
});
|
|
270
353
|
});
|
|
271
354
|
}
|
|
272
355
|
|
|
273
|
-
spawn(
|
|
356
|
+
spawn(
|
|
357
|
+
command: string,
|
|
358
|
+
args: string[] = [],
|
|
359
|
+
options: SpawnOptions = {},
|
|
360
|
+
): Promise<string> {
|
|
274
361
|
const cwd = options.cwd?.toString() ?? this.cwdPath;
|
|
275
362
|
const proc = spawn(command, args, {
|
|
276
363
|
cwd: this.cwdPath,
|
|
@@ -293,17 +380,40 @@ export class Executor {
|
|
|
293
380
|
return new Promise((resolve, reject) => {
|
|
294
381
|
proc.on("error", (error) => {
|
|
295
382
|
reject(
|
|
296
|
-
new CommandExecutionError({
|
|
383
|
+
new CommandExecutionError({
|
|
384
|
+
command,
|
|
385
|
+
args,
|
|
386
|
+
cwd,
|
|
387
|
+
code: null,
|
|
388
|
+
signal: null,
|
|
389
|
+
stdout,
|
|
390
|
+
stderr,
|
|
391
|
+
cause: error,
|
|
392
|
+
}),
|
|
297
393
|
);
|
|
298
394
|
});
|
|
299
395
|
proc.on("close", (code, signal) => {
|
|
300
396
|
if (code !== 0 || signal)
|
|
301
|
-
reject(
|
|
397
|
+
reject(
|
|
398
|
+
new CommandExecutionError({
|
|
399
|
+
command,
|
|
400
|
+
args,
|
|
401
|
+
cwd,
|
|
402
|
+
code,
|
|
403
|
+
signal,
|
|
404
|
+
stdout,
|
|
405
|
+
stderr,
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
302
408
|
else resolve(stdout);
|
|
303
409
|
});
|
|
304
410
|
});
|
|
305
411
|
}
|
|
306
|
-
spawnSync(
|
|
412
|
+
spawnSync(
|
|
413
|
+
command: string,
|
|
414
|
+
args: string[] = [],
|
|
415
|
+
options: SpawnOptions = {},
|
|
416
|
+
): ChildProcess {
|
|
307
417
|
const proc = spawn(command, args, {
|
|
308
418
|
cwd: this.cwdPath,
|
|
309
419
|
// stdio: "inherit",
|
|
@@ -345,7 +455,17 @@ export class Executor {
|
|
|
345
455
|
});
|
|
346
456
|
proc.on("exit", (code, signal) => {
|
|
347
457
|
if (!!code || signal)
|
|
348
|
-
reject(
|
|
458
|
+
reject(
|
|
459
|
+
new CommandExecutionError({
|
|
460
|
+
command: modulePath,
|
|
461
|
+
args,
|
|
462
|
+
cwd,
|
|
463
|
+
code,
|
|
464
|
+
signal,
|
|
465
|
+
stdout,
|
|
466
|
+
stderr,
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
349
469
|
else resolve({ code, signal });
|
|
350
470
|
});
|
|
351
471
|
});
|
|
@@ -374,7 +494,8 @@ export class Executor {
|
|
|
374
494
|
}
|
|
375
495
|
async mkdir(dirPath: string) {
|
|
376
496
|
const writePath = this.getPath(dirPath);
|
|
377
|
-
if (!(await FileSys.dirExists(writePath)))
|
|
497
|
+
if (!(await FileSys.dirExists(writePath)))
|
|
498
|
+
await mkdir(writePath, { recursive: true });
|
|
378
499
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
379
500
|
return this;
|
|
380
501
|
}
|
|
@@ -387,16 +508,27 @@ export class Executor {
|
|
|
387
508
|
return [];
|
|
388
509
|
}
|
|
389
510
|
}
|
|
390
|
-
async getAllFiles(
|
|
511
|
+
async getAllFiles(
|
|
512
|
+
pattern = "**/*",
|
|
513
|
+
{ cwd }: { cwd?: string } = {},
|
|
514
|
+
): Promise<string[]> {
|
|
391
515
|
const glob = new Bun.Glob(pattern);
|
|
392
|
-
return Array.from(
|
|
516
|
+
return Array.from(
|
|
517
|
+
glob.scanSync({ cwd: cwd ?? this.cwdPath, onlyFiles: true }),
|
|
518
|
+
);
|
|
393
519
|
}
|
|
394
|
-
async getFilesAndDirs(
|
|
520
|
+
async getFilesAndDirs(
|
|
521
|
+
dirPath: string,
|
|
522
|
+
): Promise<{ files: string[]; dirs: string[] }> {
|
|
395
523
|
const fullDirPath = this.getPath(dirPath);
|
|
396
524
|
const fileGlob = new Bun.Glob("*");
|
|
397
|
-
const files = Array.from(
|
|
525
|
+
const files = Array.from(
|
|
526
|
+
fileGlob.scanSync({ cwd: fullDirPath, onlyFiles: true }),
|
|
527
|
+
);
|
|
398
528
|
const dirGlob = new Bun.Glob("*");
|
|
399
|
-
const allEntries = Array.from(
|
|
529
|
+
const allEntries = Array.from(
|
|
530
|
+
dirGlob.scanSync({ cwd: fullDirPath, onlyFiles: false }),
|
|
531
|
+
);
|
|
400
532
|
const dirs = allEntries.filter((entry) => !files.includes(entry));
|
|
401
533
|
return { files, dirs };
|
|
402
534
|
}
|
|
@@ -424,7 +556,8 @@ export class Executor {
|
|
|
424
556
|
const writePath = this.getPath(filePath);
|
|
425
557
|
const dir = path.dirname(writePath);
|
|
426
558
|
if (!(await FileSys.dirExists(dir))) await mkdir(dir, { recursive: true });
|
|
427
|
-
let contentStr =
|
|
559
|
+
let contentStr =
|
|
560
|
+
typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
428
561
|
|
|
429
562
|
if (await FileSys.fileExists(writePath)) {
|
|
430
563
|
const currentContent = await FileSys.readText(writePath);
|
|
@@ -433,7 +566,8 @@ export class Executor {
|
|
|
433
566
|
contentStr = currentContent;
|
|
434
567
|
} else {
|
|
435
568
|
await FileSys.writeText(writePath, contentStr);
|
|
436
|
-
if (Logger.isVerbose())
|
|
569
|
+
if (Logger.isVerbose())
|
|
570
|
+
this.logger.rawLog(chalk.yellow(`File Update: ${filePath}`));
|
|
437
571
|
}
|
|
438
572
|
} else {
|
|
439
573
|
await FileSys.writeText(writePath, contentStr);
|
|
@@ -445,7 +579,9 @@ export class Executor {
|
|
|
445
579
|
await this.writeFile(filePath, content);
|
|
446
580
|
}
|
|
447
581
|
async getLocalFile(targetPath: string) {
|
|
448
|
-
const filePath = path.isAbsolute(targetPath)
|
|
582
|
+
const filePath = path.isAbsolute(targetPath)
|
|
583
|
+
? targetPath
|
|
584
|
+
: targetPath.replace(this.cwdPath, "");
|
|
449
585
|
const content = await this.readFile(filePath);
|
|
450
586
|
return { filePath, content };
|
|
451
587
|
}
|
|
@@ -462,7 +598,8 @@ export class Executor {
|
|
|
462
598
|
const dest = this.getPath(destPath);
|
|
463
599
|
if (!(await FileSys.exists(src))) return;
|
|
464
600
|
const isDirectory = (await stat(src)).isDirectory();
|
|
465
|
-
if (!(await FileSys.exists(dest)) && isDirectory)
|
|
601
|
+
if (!(await FileSys.exists(dest)) && isDirectory)
|
|
602
|
+
await mkdir(dest, { recursive: true });
|
|
466
603
|
await $`cp -r ${src}${isDirectory ? "/." : ""} ${dest}`;
|
|
467
604
|
}
|
|
468
605
|
log(msg: string) {
|
|
@@ -477,12 +614,22 @@ export class Executor {
|
|
|
477
614
|
this.logger.debug(msg);
|
|
478
615
|
return this;
|
|
479
616
|
}
|
|
480
|
-
spinning(
|
|
617
|
+
spinning(
|
|
618
|
+
msg: string,
|
|
619
|
+
{
|
|
620
|
+
prefix = `${this.emoji}${this.name}`,
|
|
621
|
+
indent = 0,
|
|
622
|
+
enableSpin = !Executor.verbose,
|
|
623
|
+
} = {},
|
|
624
|
+
) {
|
|
481
625
|
return new Spinner(msg, { prefix, indent, enableSpin }).start();
|
|
482
626
|
}
|
|
483
627
|
|
|
484
628
|
#tsconfig: TsConfigJson | null = null;
|
|
485
|
-
async getTsConfig(
|
|
629
|
+
async getTsConfig(
|
|
630
|
+
pathname = "tsconfig.json",
|
|
631
|
+
{ refresh }: { refresh?: boolean } = {},
|
|
632
|
+
): Promise<TsConfigJson> {
|
|
486
633
|
if (this.#tsconfig && !refresh) return this.#tsconfig;
|
|
487
634
|
const tsconfig = (await this.readJson(pathname)) as TsConfigJson;
|
|
488
635
|
if (tsconfig.extends) {
|
|
@@ -490,7 +637,10 @@ export class Executor {
|
|
|
490
637
|
const result = {
|
|
491
638
|
...extendsTsconfig,
|
|
492
639
|
...tsconfig,
|
|
493
|
-
compilerOptions: {
|
|
640
|
+
compilerOptions: {
|
|
641
|
+
...extendsTsconfig.compilerOptions,
|
|
642
|
+
...tsconfig.compilerOptions,
|
|
643
|
+
},
|
|
494
644
|
} as TsConfigJson;
|
|
495
645
|
this.#tsconfig = result;
|
|
496
646
|
return result;
|
|
@@ -504,7 +654,11 @@ export class Executor {
|
|
|
504
654
|
}
|
|
505
655
|
|
|
506
656
|
#packageJson: PackageJson | null = null;
|
|
507
|
-
async getPackageJson({
|
|
657
|
+
async getPackageJson({
|
|
658
|
+
refresh,
|
|
659
|
+
}: {
|
|
660
|
+
refresh?: boolean;
|
|
661
|
+
} = {}): Promise<PackageJson> {
|
|
508
662
|
if (this.#packageJson && !refresh) return this.#packageJson;
|
|
509
663
|
const packageJson = (await this.readJson("package.json")) as PackageJson;
|
|
510
664
|
this.#packageJson = packageJson;
|
|
@@ -551,37 +705,55 @@ export class Executor {
|
|
|
551
705
|
};
|
|
552
706
|
const result = await getContent.default(scanInfo ?? null, dict, options);
|
|
553
707
|
if (result === null) return null;
|
|
554
|
-
const filename =
|
|
708
|
+
const filename =
|
|
709
|
+
typeof result === "object"
|
|
710
|
+
? result.filename
|
|
711
|
+
: path.basename(targetPath).replace(".js", ".ts");
|
|
555
712
|
const content = typeof result === "object" ? result.content : result;
|
|
556
713
|
const dirname = path.dirname(targetPath);
|
|
557
714
|
const convertedTargetPath = Object.entries(dict).reduce(
|
|
558
|
-
(path, [key, value]) =>
|
|
715
|
+
(path, [key, value]) =>
|
|
716
|
+
path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
559
717
|
`${dirname}/${filename}`,
|
|
560
718
|
);
|
|
561
|
-
this.logger.verbose(
|
|
719
|
+
this.logger.verbose(
|
|
720
|
+
`Apply template ${templatePath} to ${convertedTargetPath}`,
|
|
721
|
+
);
|
|
562
722
|
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
563
723
|
} else if (targetPath.endsWith(".template")) {
|
|
564
724
|
const content = await FileSys.readText(templatePath);
|
|
565
725
|
const convertedTargetPath = Object.entries(dict).reduce(
|
|
566
|
-
(path, [key, value]) =>
|
|
726
|
+
(path, [key, value]) =>
|
|
727
|
+
path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
567
728
|
targetPath.slice(0, -9),
|
|
568
729
|
);
|
|
569
730
|
const convertedContent = Object.entries(dict).reduce(
|
|
570
|
-
(data, [key, value]) =>
|
|
731
|
+
(data, [key, value]) =>
|
|
732
|
+
data.replace(new RegExp(`<%= ${key} %>`, "g"), value),
|
|
571
733
|
content,
|
|
572
734
|
);
|
|
573
|
-
this.logger.verbose(
|
|
574
|
-
|
|
575
|
-
|
|
735
|
+
this.logger.verbose(
|
|
736
|
+
`Apply template ${templatePath} to ${convertedTargetPath}`,
|
|
737
|
+
);
|
|
738
|
+
return this.writeFile(convertedTargetPath, convertedContent, {
|
|
739
|
+
overwrite,
|
|
740
|
+
});
|
|
741
|
+
} else if (
|
|
742
|
+
staticTemplateFileExtensions.has(path.extname(targetPath).toLowerCase())
|
|
743
|
+
) {
|
|
576
744
|
const convertedTargetPath = Object.entries(dict).reduce(
|
|
577
|
-
(path, [key, value]) =>
|
|
745
|
+
(path, [key, value]) =>
|
|
746
|
+
path.replace(new RegExp(`__${key}__`, "g"), value),
|
|
578
747
|
targetPath,
|
|
579
748
|
);
|
|
580
749
|
const writePath = this.getPath(convertedTargetPath);
|
|
581
750
|
const dirname = path.dirname(writePath);
|
|
582
|
-
if (!(await FileSys.dirExists(dirname)))
|
|
751
|
+
if (!(await FileSys.dirExists(dirname)))
|
|
752
|
+
await mkdir(dirname, { recursive: true });
|
|
583
753
|
await copyFile(templatePath, writePath);
|
|
584
|
-
this.logger.verbose(
|
|
754
|
+
this.logger.verbose(
|
|
755
|
+
`Apply template ${templatePath} to ${convertedTargetPath}`,
|
|
756
|
+
);
|
|
585
757
|
return { filePath: writePath, content: "" };
|
|
586
758
|
} else return null;
|
|
587
759
|
}
|
|
@@ -606,7 +778,12 @@ export class Executor {
|
|
|
606
778
|
if ((await stat(prefixTemplatePath)).isFile()) {
|
|
607
779
|
const filename = path.basename(prefixTemplatePath);
|
|
608
780
|
const fileContent = await this.#applyTemplateFile(
|
|
609
|
-
{
|
|
781
|
+
{
|
|
782
|
+
templatePath: prefixTemplatePath,
|
|
783
|
+
targetPath: path.join(basePath, filename),
|
|
784
|
+
scanInfo,
|
|
785
|
+
overwrite,
|
|
786
|
+
},
|
|
610
787
|
dict,
|
|
611
788
|
options,
|
|
612
789
|
);
|
|
@@ -619,7 +796,12 @@ export class Executor {
|
|
|
619
796
|
const subpath = path.join(templatePath, subdir);
|
|
620
797
|
if ((await stat(subpath)).isFile()) {
|
|
621
798
|
const fileContent = await this.#applyTemplateFile(
|
|
622
|
-
{
|
|
799
|
+
{
|
|
800
|
+
templatePath: subpath,
|
|
801
|
+
targetPath: path.join(basePath, subdir),
|
|
802
|
+
scanInfo,
|
|
803
|
+
overwrite,
|
|
804
|
+
},
|
|
623
805
|
dict,
|
|
624
806
|
options,
|
|
625
807
|
);
|
|
@@ -659,7 +841,10 @@ export class Executor {
|
|
|
659
841
|
const dict = {
|
|
660
842
|
...(options.dict ?? {}),
|
|
661
843
|
...Object.fromEntries(
|
|
662
|
-
Object.entries(options.dict ?? {}).map(([key, value]) => [
|
|
844
|
+
Object.entries(options.dict ?? {}).map(([key, value]) => [
|
|
845
|
+
capitalize(key),
|
|
846
|
+
capitalize(value),
|
|
847
|
+
]),
|
|
663
848
|
),
|
|
664
849
|
};
|
|
665
850
|
return this._applyTemplate({ ...options, dict });
|
|
@@ -671,10 +856,69 @@ export class Executor {
|
|
|
671
856
|
typeCheck(filePath: string) {
|
|
672
857
|
const path = this.getPath(filePath);
|
|
673
858
|
const typeChecker = this.getTypeChecker();
|
|
674
|
-
const { fileDiagnostics, fileErrors, fileWarnings } =
|
|
859
|
+
const { fileDiagnostics, fileErrors, fileWarnings } =
|
|
860
|
+
typeChecker.check(path);
|
|
675
861
|
const message = typeChecker.formatDiagnostics(fileDiagnostics);
|
|
676
862
|
return { fileDiagnostics, fileErrors, fileWarnings, message };
|
|
677
863
|
}
|
|
864
|
+
async typeCheckAsync(filePath: string) {
|
|
865
|
+
const path = this.getPath(filePath);
|
|
866
|
+
const entry = await this.#resolveTypecheckWorkerEntry();
|
|
867
|
+
const proc = Bun.spawn([process.execPath, entry], {
|
|
868
|
+
cwd: this.cwdPath,
|
|
869
|
+
env: {
|
|
870
|
+
...process.env,
|
|
871
|
+
AKAN_TYPECHECK_CWD: this.cwdPath,
|
|
872
|
+
AKAN_TYPECHECK_FILE: path,
|
|
873
|
+
},
|
|
874
|
+
stdout: "pipe",
|
|
875
|
+
stderr: "pipe",
|
|
876
|
+
});
|
|
877
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
878
|
+
new Response(proc.stdout).text(),
|
|
879
|
+
new Response(proc.stderr).text(),
|
|
880
|
+
proc.exited,
|
|
881
|
+
]);
|
|
882
|
+
if (exitCode !== 0)
|
|
883
|
+
throw new Error(
|
|
884
|
+
(stderr || stdout).trim() ||
|
|
885
|
+
`Typecheck failed with exit code ${exitCode}`,
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
const result = JSON.parse(stdout) as {
|
|
889
|
+
fileDiagnosticsCount: number;
|
|
890
|
+
fileErrorsCount: number;
|
|
891
|
+
fileWarningsCount: number;
|
|
892
|
+
message: string;
|
|
893
|
+
};
|
|
894
|
+
return {
|
|
895
|
+
fileDiagnostics: Array.from({ length: result.fileDiagnosticsCount }),
|
|
896
|
+
fileErrors: Array.from({ length: result.fileErrorsCount }),
|
|
897
|
+
fileWarnings: Array.from({ length: result.fileWarningsCount }),
|
|
898
|
+
message: result.message,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
async #resolveTypecheckWorkerEntry() {
|
|
902
|
+
const dirname = getDirname(import.meta.url);
|
|
903
|
+
const candidates = [
|
|
904
|
+
path.join(
|
|
905
|
+
process.cwd(),
|
|
906
|
+
"pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts",
|
|
907
|
+
),
|
|
908
|
+
path.join(
|
|
909
|
+
process.cwd(),
|
|
910
|
+
"node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts",
|
|
911
|
+
),
|
|
912
|
+
path.join(dirname, "typecheck/typecheck.proc.ts"),
|
|
913
|
+
path.join(dirname, "typecheck.proc.js"),
|
|
914
|
+
path.join(dirname, "typecheck.proc.ts"),
|
|
915
|
+
];
|
|
916
|
+
for (const candidate of candidates)
|
|
917
|
+
if (await Bun.file(candidate).exists()) return candidate;
|
|
918
|
+
throw new Error(
|
|
919
|
+
`[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`,
|
|
920
|
+
);
|
|
921
|
+
}
|
|
678
922
|
getLinter() {
|
|
679
923
|
this.linter ??= new Linter(this.cwdPath);
|
|
680
924
|
return this.linter;
|
|
@@ -690,7 +934,10 @@ export class Executor {
|
|
|
690
934
|
}> {
|
|
691
935
|
const path = this.getPath(filePath);
|
|
692
936
|
const linter = this.getLinter();
|
|
693
|
-
const { results, errors, warnings } = await linter.lint(path, {
|
|
937
|
+
const { results, errors, warnings } = await linter.lint(path, {
|
|
938
|
+
fix,
|
|
939
|
+
dryRun,
|
|
940
|
+
});
|
|
694
941
|
const message = linter.formatLintResults(results);
|
|
695
942
|
return { results, message, errors, warnings };
|
|
696
943
|
}
|
|
@@ -718,14 +965,20 @@ export class WorkspaceExecutor extends Executor {
|
|
|
718
965
|
workspaceRoot?: string;
|
|
719
966
|
repoName?: string;
|
|
720
967
|
} = {}) {
|
|
721
|
-
return
|
|
968
|
+
return (
|
|
969
|
+
WorkspaceExecutor.#execs.get(repoName) ??
|
|
970
|
+
new WorkspaceExecutor({ workspaceRoot, repoName })
|
|
971
|
+
);
|
|
722
972
|
}
|
|
723
973
|
static getBaseDevEnv(envPath?: string) {
|
|
724
974
|
// Bun auto-loads .env, so we use process.env directly
|
|
725
|
-
const sourceEnv = envPath
|
|
975
|
+
const sourceEnv = envPath
|
|
976
|
+
? { ...process.env, ...parseEnvFile(envPath) }
|
|
977
|
+
: process.env;
|
|
726
978
|
|
|
727
979
|
const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
|
|
728
980
|
const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
|
|
981
|
+
const workspaceId = sourceEnv.AKAN_WORKSPACE_ID;
|
|
729
982
|
|
|
730
983
|
const repoName = sourceEnv.AKAN_PUBLIC_REPO_NAME;
|
|
731
984
|
if (!repoName) throw new Error("AKAN_PUBLIC_REPO_NAME is not set");
|
|
@@ -743,29 +996,63 @@ export class WorkspaceExecutor extends Executor {
|
|
|
743
996
|
| "local"
|
|
744
997
|
| undefined;
|
|
745
998
|
if (!env) throw new Error("AKAN_PUBLIC_ENV is not set");
|
|
746
|
-
return {
|
|
999
|
+
return {
|
|
1000
|
+
...(appName ? { appName } : {}),
|
|
1001
|
+
workspaceRoot,
|
|
1002
|
+
repoName,
|
|
1003
|
+
serveDomain,
|
|
1004
|
+
env,
|
|
1005
|
+
portOffset,
|
|
1006
|
+
workspaceId,
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
getWorkspaceId<AllowEmpty extends boolean = false>({
|
|
1010
|
+
allowEmpty,
|
|
1011
|
+
}: {
|
|
1012
|
+
allowEmpty?: AllowEmpty;
|
|
1013
|
+
} = {}): AllowEmpty extends true ? string | undefined : string {
|
|
1014
|
+
const { workspaceId } = WorkspaceExecutor.getBaseDevEnv();
|
|
1015
|
+
if (!workspaceId && !allowEmpty)
|
|
1016
|
+
throw new Error("Workspace ID is not found");
|
|
1017
|
+
return workspaceId as AllowEmpty extends true ? string | undefined : string;
|
|
747
1018
|
}
|
|
748
1019
|
async scan(): Promise<WorkspaceInfo> {
|
|
749
1020
|
return await WorkspaceInfo.fromExecutor(this);
|
|
750
1021
|
}
|
|
751
1022
|
async getApps() {
|
|
752
1023
|
if (!(await FileSys.dirExists(`${this.workspaceRoot}/apps`))) return [];
|
|
753
|
-
return await this.#getDirHasFile(
|
|
1024
|
+
return await this.#getDirHasFile(
|
|
1025
|
+
`${this.workspaceRoot}/apps`,
|
|
1026
|
+
"akan.config.ts",
|
|
1027
|
+
);
|
|
754
1028
|
}
|
|
755
1029
|
async getLibs() {
|
|
756
1030
|
if (!(await FileSys.dirExists(`${this.workspaceRoot}/libs`))) return [];
|
|
757
|
-
return await this.#getDirHasFile(
|
|
1031
|
+
return await this.#getDirHasFile(
|
|
1032
|
+
`${this.workspaceRoot}/libs`,
|
|
1033
|
+
"akan.config.ts",
|
|
1034
|
+
);
|
|
758
1035
|
}
|
|
759
1036
|
async getSyss() {
|
|
760
|
-
const [appNames, libNames] = await Promise.all([
|
|
1037
|
+
const [appNames, libNames] = await Promise.all([
|
|
1038
|
+
this.getApps(),
|
|
1039
|
+
this.getLibs(),
|
|
1040
|
+
]);
|
|
761
1041
|
return [appNames, libNames] as [string[], string[]];
|
|
762
1042
|
}
|
|
763
1043
|
async getPkgs() {
|
|
764
1044
|
if (!(await FileSys.dirExists(`${this.workspaceRoot}/pkgs`))) return [];
|
|
765
|
-
return await this.#getDirHasFile(
|
|
1045
|
+
return await this.#getDirHasFile(
|
|
1046
|
+
`${this.workspaceRoot}/pkgs`,
|
|
1047
|
+
"package.json",
|
|
1048
|
+
);
|
|
766
1049
|
}
|
|
767
1050
|
async getExecs() {
|
|
768
|
-
const [appNames, libNames, pkgNames] = await Promise.all([
|
|
1051
|
+
const [appNames, libNames, pkgNames] = await Promise.all([
|
|
1052
|
+
this.getApps(),
|
|
1053
|
+
this.getLibs(),
|
|
1054
|
+
this.getPkgs(),
|
|
1055
|
+
]);
|
|
769
1056
|
return [appNames, libNames, pkgNames] as [string[], string[], string[]];
|
|
770
1057
|
}
|
|
771
1058
|
async setPkgTsPaths(name: string) {
|
|
@@ -774,7 +1061,11 @@ export class WorkspaceExecutor extends Executor {
|
|
|
774
1061
|
rootTsConfig.compilerOptions.paths[name] = [`./pkgs/${name}/index.ts`];
|
|
775
1062
|
rootTsConfig.compilerOptions.paths[`${name}/*`] = [`./pkgs/${name}/*`];
|
|
776
1063
|
if (rootTsConfig.references) {
|
|
777
|
-
if (
|
|
1064
|
+
if (
|
|
1065
|
+
!rootTsConfig.references.some(
|
|
1066
|
+
(ref) => ref.path === `./pkgs/${name}/tsconfig.json`,
|
|
1067
|
+
)
|
|
1068
|
+
)
|
|
778
1069
|
rootTsConfig.references.push({ path: `./pkgs/${name}/tsconfig.json` });
|
|
779
1070
|
}
|
|
780
1071
|
await this.writeJson("tsconfig.json", rootTsConfig);
|
|
@@ -782,11 +1073,14 @@ export class WorkspaceExecutor extends Executor {
|
|
|
782
1073
|
}
|
|
783
1074
|
async unsetPkgTsPaths(name: string) {
|
|
784
1075
|
const rootTsConfig = (await this.readJson("tsconfig.json")) as TsConfigJson;
|
|
785
|
-
const filteredKeys = Object.keys(
|
|
786
|
-
|
|
787
|
-
);
|
|
1076
|
+
const filteredKeys = Object.keys(
|
|
1077
|
+
rootTsConfig.compilerOptions.paths ?? {},
|
|
1078
|
+
).filter((key) => key !== name && key !== `${name}/*`);
|
|
788
1079
|
rootTsConfig.compilerOptions.paths = Object.fromEntries(
|
|
789
|
-
filteredKeys.map((key) => [
|
|
1080
|
+
filteredKeys.map((key) => [
|
|
1081
|
+
key,
|
|
1082
|
+
rootTsConfig.compilerOptions.paths?.[key] ?? [],
|
|
1083
|
+
]),
|
|
790
1084
|
);
|
|
791
1085
|
if (rootTsConfig.references) {
|
|
792
1086
|
rootTsConfig.references = rootTsConfig.references.filter(
|
|
@@ -798,7 +1092,12 @@ export class WorkspaceExecutor extends Executor {
|
|
|
798
1092
|
}
|
|
799
1093
|
async getDirInModule(basePath: string, name: string) {
|
|
800
1094
|
const AVOID_DIRS = ["__lib", "__scalar", `_`, `_${name}`];
|
|
801
|
-
const getDirs = async (
|
|
1095
|
+
const getDirs = async (
|
|
1096
|
+
dirname: string,
|
|
1097
|
+
maxDepth = 3,
|
|
1098
|
+
results: string[] = [],
|
|
1099
|
+
prefix = "",
|
|
1100
|
+
) => {
|
|
802
1101
|
const dirs = await this.readdir(dirname);
|
|
803
1102
|
await Promise.all(
|
|
804
1103
|
dirs.map(async (dir) => {
|
|
@@ -806,7 +1105,8 @@ export class WorkspaceExecutor extends Executor {
|
|
|
806
1105
|
const dirPath = path.join(dirname, dir);
|
|
807
1106
|
if ((await stat(dirPath)).isDirectory()) {
|
|
808
1107
|
results.push(`${prefix}${dir}`);
|
|
809
|
-
if (maxDepth > 0)
|
|
1108
|
+
if (maxDepth > 0)
|
|
1109
|
+
await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
|
|
810
1110
|
}
|
|
811
1111
|
}),
|
|
812
1112
|
);
|
|
@@ -814,23 +1114,34 @@ export class WorkspaceExecutor extends Executor {
|
|
|
814
1114
|
};
|
|
815
1115
|
return await getDirs(basePath);
|
|
816
1116
|
}
|
|
817
|
-
async commit(
|
|
1117
|
+
async commit(
|
|
1118
|
+
message: string,
|
|
1119
|
+
{ init = false, add = true }: { init?: boolean; add?: boolean } = {},
|
|
1120
|
+
) {
|
|
818
1121
|
if (init) await this.exec(`git init --quiet`);
|
|
819
1122
|
if (add) await this.exec(`git add .`);
|
|
820
1123
|
await this.exec(`git commit --quiet -m "${message}"`);
|
|
821
1124
|
}
|
|
822
1125
|
async #getDirHasFile(basePath: string, targetFilename: string) {
|
|
823
1126
|
const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
|
|
824
|
-
const getDirs = async (
|
|
1127
|
+
const getDirs = async (
|
|
1128
|
+
dirname: string,
|
|
1129
|
+
maxDepth = 3,
|
|
1130
|
+
results: string[] = [],
|
|
1131
|
+
prefix = "",
|
|
1132
|
+
) => {
|
|
825
1133
|
const dirs = await this.readdir(dirname);
|
|
826
1134
|
await Promise.all(
|
|
827
1135
|
dirs.map(async (dir) => {
|
|
828
1136
|
if (AVOID_DIRS.includes(dir)) return;
|
|
829
1137
|
const dirPath = path.join(dirname, dir);
|
|
830
1138
|
if ((await stat(dirPath)).isDirectory()) {
|
|
831
|
-
const hasTargetFile = await FileSys.fileExists(
|
|
1139
|
+
const hasTargetFile = await FileSys.fileExists(
|
|
1140
|
+
path.join(dirPath, targetFilename),
|
|
1141
|
+
);
|
|
832
1142
|
if (hasTargetFile) results.push(`${prefix}${dir}`);
|
|
833
|
-
if (maxDepth > 0)
|
|
1143
|
+
if (maxDepth > 0)
|
|
1144
|
+
await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
|
|
834
1145
|
}
|
|
835
1146
|
}),
|
|
836
1147
|
);
|
|
@@ -843,10 +1154,18 @@ export class WorkspaceExecutor extends Executor {
|
|
|
843
1154
|
const [appNames, libNames] = await this.getSyss();
|
|
844
1155
|
const scalarConstantExampleFiles = [
|
|
845
1156
|
...(
|
|
846
|
-
await Promise.all(
|
|
1157
|
+
await Promise.all(
|
|
1158
|
+
appNames.map((appName) =>
|
|
1159
|
+
AppExecutor.from(this, appName).getScalarConstantFiles(),
|
|
1160
|
+
),
|
|
1161
|
+
)
|
|
847
1162
|
).flat(),
|
|
848
1163
|
...(
|
|
849
|
-
await Promise.all(
|
|
1164
|
+
await Promise.all(
|
|
1165
|
+
libNames.map((libName) =>
|
|
1166
|
+
LibExecutor.from(this, libName).getScalarConstantFiles(),
|
|
1167
|
+
),
|
|
1168
|
+
)
|
|
850
1169
|
).flat(),
|
|
851
1170
|
];
|
|
852
1171
|
return scalarConstantExampleFiles;
|
|
@@ -854,24 +1173,60 @@ export class WorkspaceExecutor extends Executor {
|
|
|
854
1173
|
async getConstantFiles() {
|
|
855
1174
|
const [appNames, libNames] = await this.getSyss();
|
|
856
1175
|
const moduleConstantExampleFiles = [
|
|
857
|
-
...(
|
|
858
|
-
|
|
1176
|
+
...(
|
|
1177
|
+
await Promise.all(
|
|
1178
|
+
appNames.map((appName) =>
|
|
1179
|
+
AppExecutor.from(this, appName).getConstantFiles(),
|
|
1180
|
+
),
|
|
1181
|
+
)
|
|
1182
|
+
).flat(),
|
|
1183
|
+
...(
|
|
1184
|
+
await Promise.all(
|
|
1185
|
+
libNames.map((libName) =>
|
|
1186
|
+
LibExecutor.from(this, libName).getConstantFiles(),
|
|
1187
|
+
),
|
|
1188
|
+
)
|
|
1189
|
+
).flat(),
|
|
859
1190
|
];
|
|
860
1191
|
return moduleConstantExampleFiles;
|
|
861
1192
|
}
|
|
862
1193
|
async getDictionaryFiles() {
|
|
863
1194
|
const [appNames, libNames] = await this.getSyss();
|
|
864
1195
|
const moduleDictionaryExampleFiles = [
|
|
865
|
-
...(
|
|
866
|
-
|
|
1196
|
+
...(
|
|
1197
|
+
await Promise.all(
|
|
1198
|
+
appNames.map((appName) =>
|
|
1199
|
+
AppExecutor.from(this, appName).getDictionaryFiles(),
|
|
1200
|
+
),
|
|
1201
|
+
)
|
|
1202
|
+
).flat(),
|
|
1203
|
+
...(
|
|
1204
|
+
await Promise.all(
|
|
1205
|
+
libNames.map((libName) =>
|
|
1206
|
+
LibExecutor.from(this, libName).getDictionaryFiles(),
|
|
1207
|
+
),
|
|
1208
|
+
)
|
|
1209
|
+
).flat(),
|
|
867
1210
|
];
|
|
868
1211
|
return moduleDictionaryExampleFiles;
|
|
869
1212
|
}
|
|
870
1213
|
async getViewFiles() {
|
|
871
1214
|
const [appNames, libNames] = await this.getSyss();
|
|
872
1215
|
const viewExampleFiles = [
|
|
873
|
-
...(
|
|
874
|
-
|
|
1216
|
+
...(
|
|
1217
|
+
await Promise.all(
|
|
1218
|
+
appNames.map((appName) =>
|
|
1219
|
+
AppExecutor.from(this, appName).getViewsSourceCode(),
|
|
1220
|
+
),
|
|
1221
|
+
)
|
|
1222
|
+
).flat(),
|
|
1223
|
+
...(
|
|
1224
|
+
await Promise.all(
|
|
1225
|
+
libNames.map((libName) =>
|
|
1226
|
+
LibExecutor.from(this, libName).getViewsSourceCode(),
|
|
1227
|
+
),
|
|
1228
|
+
)
|
|
1229
|
+
).flat(),
|
|
875
1230
|
];
|
|
876
1231
|
return viewExampleFiles;
|
|
877
1232
|
}
|
|
@@ -890,7 +1245,11 @@ export class SysExecutor extends Executor {
|
|
|
890
1245
|
override name: string;
|
|
891
1246
|
type: "app" | "lib";
|
|
892
1247
|
override emoji: string;
|
|
893
|
-
constructor({
|
|
1248
|
+
constructor({
|
|
1249
|
+
workspace = WorkspaceExecutor.fromRoot(),
|
|
1250
|
+
name,
|
|
1251
|
+
type,
|
|
1252
|
+
}: SysExecutorOptions) {
|
|
894
1253
|
super(name, `${workspace.workspaceRoot}/${type}s/${name}`);
|
|
895
1254
|
this.workspace = workspace;
|
|
896
1255
|
this.name = name;
|
|
@@ -907,7 +1266,8 @@ export class SysExecutor extends Executor {
|
|
|
907
1266
|
return this.#akanConfig;
|
|
908
1267
|
}
|
|
909
1268
|
async getModules() {
|
|
910
|
-
const path =
|
|
1269
|
+
const path =
|
|
1270
|
+
this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
|
|
911
1271
|
return await this.workspace.getDirInModule(path, this.name);
|
|
912
1272
|
}
|
|
913
1273
|
|
|
@@ -919,17 +1279,26 @@ export class SysExecutor extends Executor {
|
|
|
919
1279
|
allowEmpty,
|
|
920
1280
|
}: {
|
|
921
1281
|
allowEmpty?: AllowEmpty;
|
|
922
|
-
} = {}): AllowEmpty extends true
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1282
|
+
} = {}): AllowEmpty extends true
|
|
1283
|
+
? AppInfo | LibInfo | null
|
|
1284
|
+
: AppInfo | LibInfo {
|
|
1285
|
+
if (!this.hasScanInfo() && !allowEmpty)
|
|
1286
|
+
throw new Error("Scan info is not available");
|
|
1287
|
+
return this.#scanInfo as AllowEmpty extends true
|
|
1288
|
+
? AppInfo | LibInfo | null
|
|
1289
|
+
: AppInfo | LibInfo;
|
|
1290
|
+
}
|
|
1291
|
+
#getScanTemplateTasks(
|
|
1292
|
+
scanInfo: AppInfo | LibInfo,
|
|
1293
|
+
): (Promise<FileContent[]> | null)[] {
|
|
927
1294
|
return [
|
|
928
1295
|
this._applyTemplate({ basePath: "env", template: "env", scanInfo }),
|
|
929
1296
|
this._applyTemplate({ basePath: "lib", template: "lib", scanInfo }),
|
|
930
1297
|
this._applyTemplate({ basePath: ".", template: "server.ts", scanInfo }),
|
|
931
1298
|
this._applyTemplate({ basePath: ".", template: "client.ts", scanInfo }),
|
|
932
|
-
this.type === "lib"
|
|
1299
|
+
this.type === "lib"
|
|
1300
|
+
? this._applyTemplate({ basePath: ".", template: "index.ts", scanInfo })
|
|
1301
|
+
: null,
|
|
933
1302
|
...scanFacetDirs.map((facet) =>
|
|
934
1303
|
this._applyTemplate({
|
|
935
1304
|
basePath: facet,
|
|
@@ -976,8 +1345,12 @@ export class SysExecutor extends Executor {
|
|
|
976
1345
|
if (this.#scanInfo && !refresh) return this.#scanInfo;
|
|
977
1346
|
const scanInfo =
|
|
978
1347
|
this.type === "app"
|
|
979
|
-
? await AppInfo.fromExecutor(this as unknown as AppExecutor, {
|
|
980
|
-
|
|
1348
|
+
? await AppInfo.fromExecutor(this as unknown as AppExecutor, {
|
|
1349
|
+
refresh,
|
|
1350
|
+
})
|
|
1351
|
+
: await LibInfo.fromExecutor(this as unknown as LibExecutor, {
|
|
1352
|
+
refresh,
|
|
1353
|
+
});
|
|
981
1354
|
if (write) {
|
|
982
1355
|
await Promise.all(this.#getScanTemplateTasks(scanInfo));
|
|
983
1356
|
await this.writeJson(`akan.${this.type}.json`, scanInfo.getScanResult());
|
|
@@ -986,7 +1359,11 @@ export class SysExecutor extends Executor {
|
|
|
986
1359
|
if (writeLib) {
|
|
987
1360
|
const libInfos = [...scanInfo.getLibInfos().values()];
|
|
988
1361
|
await this.#updateDependencies(scanInfo);
|
|
989
|
-
await Promise.all(
|
|
1362
|
+
await Promise.all(
|
|
1363
|
+
libInfos.flatMap((libInfo) =>
|
|
1364
|
+
libInfo.exec.#getScanTemplateTasks(libInfo),
|
|
1365
|
+
),
|
|
1366
|
+
);
|
|
990
1367
|
}
|
|
991
1368
|
}
|
|
992
1369
|
this.#scanInfo = scanInfo;
|
|
@@ -1003,7 +1380,9 @@ export class SysExecutor extends Executor {
|
|
|
1003
1380
|
...libPackageJson,
|
|
1004
1381
|
dependencies: {
|
|
1005
1382
|
...Object.fromEntries(
|
|
1006
|
-
Object.entries(libPackageJson.dependencies ?? {}).filter(
|
|
1383
|
+
Object.entries(libPackageJson.dependencies ?? {}).filter(
|
|
1384
|
+
([dep]) => !devDependencySet.has(dep),
|
|
1385
|
+
),
|
|
1007
1386
|
),
|
|
1008
1387
|
...(Object.fromEntries(
|
|
1009
1388
|
dependencies
|
|
@@ -1014,82 +1393,134 @@ export class SysExecutor extends Executor {
|
|
|
1014
1393
|
},
|
|
1015
1394
|
devDependencies: {
|
|
1016
1395
|
...Object.fromEntries(
|
|
1017
|
-
Object.entries(libPackageJson.devDependencies ?? {}).filter(
|
|
1396
|
+
Object.entries(libPackageJson.devDependencies ?? {}).filter(
|
|
1397
|
+
([dep]) => !dependencySet.has(dep),
|
|
1398
|
+
),
|
|
1018
1399
|
),
|
|
1019
1400
|
...(Object.fromEntries(
|
|
1020
1401
|
devDependencies
|
|
1021
|
-
.filter(
|
|
1402
|
+
.filter(
|
|
1403
|
+
(dep) =>
|
|
1404
|
+
rootPackageJson.dependencies?.[dep] ||
|
|
1405
|
+
rootPackageJson.devDependencies?.[dep],
|
|
1406
|
+
)
|
|
1022
1407
|
.sort()
|
|
1023
|
-
.map((dep) => [
|
|
1408
|
+
.map((dep) => [
|
|
1409
|
+
dep,
|
|
1410
|
+
rootPackageJson.devDependencies?.[dep] ??
|
|
1411
|
+
rootPackageJson.dependencies?.[dep],
|
|
1412
|
+
]),
|
|
1024
1413
|
) as Record<string, string>),
|
|
1025
1414
|
},
|
|
1026
1415
|
};
|
|
1027
1416
|
await this.setPackageJson(libPkgJsonWithDeps);
|
|
1028
1417
|
}
|
|
1029
1418
|
override async getLocalFile(targetPath: string) {
|
|
1030
|
-
const filePath = path.isAbsolute(targetPath)
|
|
1419
|
+
const filePath = path.isAbsolute(targetPath)
|
|
1420
|
+
? targetPath
|
|
1421
|
+
: `${this.type}s/${this.name}/${targetPath}`;
|
|
1031
1422
|
const content = await this.workspace.readFile(filePath);
|
|
1032
1423
|
return { filePath, content };
|
|
1033
1424
|
}
|
|
1034
1425
|
|
|
1035
1426
|
async getDatabaseModules() {
|
|
1036
1427
|
const databaseModules = (await this.readdir("lib"))
|
|
1037
|
-
.filter(
|
|
1038
|
-
|
|
1428
|
+
.filter(
|
|
1429
|
+
(name) =>
|
|
1430
|
+
!name.startsWith("_") &&
|
|
1431
|
+
!name.startsWith("__") &&
|
|
1432
|
+
!name.endsWith(".ts"),
|
|
1433
|
+
)
|
|
1434
|
+
.filter((name) =>
|
|
1435
|
+
Bun.file(`${this.cwdPath}/lib/${name}/${name}.constant.ts`).exists(),
|
|
1436
|
+
);
|
|
1039
1437
|
return databaseModules;
|
|
1040
1438
|
}
|
|
1041
1439
|
|
|
1042
1440
|
async getServiceModules() {
|
|
1043
1441
|
const serviceModules = (await this.readdir("lib"))
|
|
1044
1442
|
.filter((name) => name.startsWith("_") && !name.startsWith("__"))
|
|
1045
|
-
.filter((name) =>
|
|
1443
|
+
.filter((name) =>
|
|
1444
|
+
Bun.file(`${this.cwdPath}/lib/${name}/${name}.service.ts`).exists(),
|
|
1445
|
+
);
|
|
1046
1446
|
return serviceModules;
|
|
1047
1447
|
}
|
|
1048
1448
|
|
|
1049
1449
|
async getScalarModules() {
|
|
1050
1450
|
const scalarModules = (await this.readdir("lib/__scalar"))
|
|
1051
1451
|
.filter((name) => !name.startsWith("_"))
|
|
1052
|
-
.filter((name) =>
|
|
1452
|
+
.filter((name) =>
|
|
1453
|
+
Bun.file(
|
|
1454
|
+
`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`,
|
|
1455
|
+
).exists(),
|
|
1456
|
+
);
|
|
1053
1457
|
return scalarModules;
|
|
1054
1458
|
}
|
|
1055
1459
|
|
|
1056
1460
|
async getViewComponents() {
|
|
1057
1461
|
const viewComponents = (await this.readdir("lib"))
|
|
1058
|
-
.filter(
|
|
1059
|
-
|
|
1462
|
+
.filter(
|
|
1463
|
+
(name) =>
|
|
1464
|
+
!name.startsWith("_") &&
|
|
1465
|
+
!name.startsWith("__") &&
|
|
1466
|
+
!name.endsWith(".ts"),
|
|
1467
|
+
)
|
|
1468
|
+
.filter((name) =>
|
|
1469
|
+
Bun.file(`${this.cwdPath}/lib/${name}/${name}.View.tsx`).exists(),
|
|
1470
|
+
);
|
|
1060
1471
|
return viewComponents;
|
|
1061
1472
|
}
|
|
1062
1473
|
|
|
1063
1474
|
async getUnitComponents() {
|
|
1064
1475
|
const unitComponents = (await this.readdir("lib"))
|
|
1065
|
-
.filter(
|
|
1066
|
-
|
|
1476
|
+
.filter(
|
|
1477
|
+
(name) =>
|
|
1478
|
+
!name.startsWith("_") &&
|
|
1479
|
+
!name.startsWith("__") &&
|
|
1480
|
+
!name.endsWith(".ts"),
|
|
1481
|
+
)
|
|
1482
|
+
.filter((name) =>
|
|
1483
|
+
Bun.file(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`).exists(),
|
|
1484
|
+
);
|
|
1067
1485
|
return unitComponents;
|
|
1068
1486
|
}
|
|
1069
1487
|
async getTemplateComponents() {
|
|
1070
1488
|
const templateComponents = (await this.readdir("lib"))
|
|
1071
|
-
.filter(
|
|
1072
|
-
|
|
1489
|
+
.filter(
|
|
1490
|
+
(name) =>
|
|
1491
|
+
!name.startsWith("_") &&
|
|
1492
|
+
!name.startsWith("__") &&
|
|
1493
|
+
!name.endsWith(".ts"),
|
|
1494
|
+
)
|
|
1495
|
+
.filter((name) =>
|
|
1496
|
+
Bun.file(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`).exists(),
|
|
1497
|
+
);
|
|
1073
1498
|
return templateComponents;
|
|
1074
1499
|
}
|
|
1075
1500
|
|
|
1076
1501
|
async getViewsSourceCode() {
|
|
1077
1502
|
const viewComponents = await this.getViewComponents();
|
|
1078
1503
|
return Promise.all(
|
|
1079
|
-
viewComponents.map((viewComponent) =>
|
|
1504
|
+
viewComponents.map((viewComponent) =>
|
|
1505
|
+
this.getLocalFile(`lib/${viewComponent}/${viewComponent}.View.tsx`),
|
|
1506
|
+
),
|
|
1080
1507
|
);
|
|
1081
1508
|
}
|
|
1082
1509
|
async getUnitsSourceCode() {
|
|
1083
1510
|
const unitComponents = await this.getUnitComponents();
|
|
1084
1511
|
return Promise.all(
|
|
1085
|
-
unitComponents.map((unitComponent) =>
|
|
1512
|
+
unitComponents.map((unitComponent) =>
|
|
1513
|
+
this.getLocalFile(`lib/${unitComponent}/${unitComponent}.Unit.tsx`),
|
|
1514
|
+
),
|
|
1086
1515
|
);
|
|
1087
1516
|
}
|
|
1088
1517
|
async getTemplatesSourceCode() {
|
|
1089
1518
|
const templateComponents = await this.getTemplateComponents();
|
|
1090
1519
|
return Promise.all(
|
|
1091
1520
|
templateComponents.map((templateComponent) =>
|
|
1092
|
-
this.getLocalFile(
|
|
1521
|
+
this.getLocalFile(
|
|
1522
|
+
`lib/${templateComponent}/${templateComponent}.Template.tsx`,
|
|
1523
|
+
),
|
|
1093
1524
|
),
|
|
1094
1525
|
);
|
|
1095
1526
|
}
|
|
@@ -1098,7 +1529,9 @@ export class SysExecutor extends Executor {
|
|
|
1098
1529
|
const scalarModules = await this.getScalarModules();
|
|
1099
1530
|
return Promise.all(
|
|
1100
1531
|
scalarModules.map((scalarModule) =>
|
|
1101
|
-
this.getLocalFile(
|
|
1532
|
+
this.getLocalFile(
|
|
1533
|
+
`lib/__scalar/${scalarModule}/${scalarModule}.constant.ts`,
|
|
1534
|
+
),
|
|
1102
1535
|
),
|
|
1103
1536
|
);
|
|
1104
1537
|
}
|
|
@@ -1106,13 +1539,19 @@ export class SysExecutor extends Executor {
|
|
|
1106
1539
|
async getScalarDictionaryFiles() {
|
|
1107
1540
|
const scalarModules = await this.getScalarModules();
|
|
1108
1541
|
return Promise.all(
|
|
1109
|
-
scalarModules.map((scalarModule) =>
|
|
1542
|
+
scalarModules.map((scalarModule) =>
|
|
1543
|
+
this.getLocalFile(`lib/${scalarModule}/${scalarModule}.dictionary.ts`),
|
|
1544
|
+
),
|
|
1110
1545
|
);
|
|
1111
1546
|
}
|
|
1112
1547
|
|
|
1113
1548
|
async getConstantFiles() {
|
|
1114
1549
|
const modules = await this.getModules();
|
|
1115
|
-
return Promise.all(
|
|
1550
|
+
return Promise.all(
|
|
1551
|
+
modules.map((module) =>
|
|
1552
|
+
this.getLocalFile(`lib/${module}/${module}.constant.ts`),
|
|
1553
|
+
),
|
|
1554
|
+
);
|
|
1116
1555
|
}
|
|
1117
1556
|
async getConstantFilesWithLibs() {
|
|
1118
1557
|
const scanInfo =
|
|
@@ -1128,11 +1567,19 @@ export class SysExecutor extends Executor {
|
|
|
1128
1567
|
...(await LibExecutor.from(this, lib).getScalarConstantFiles()),
|
|
1129
1568
|
]),
|
|
1130
1569
|
);
|
|
1131
|
-
return [
|
|
1570
|
+
return [
|
|
1571
|
+
...sysContantFiles,
|
|
1572
|
+
...sysScalarConstantFiles,
|
|
1573
|
+
...libConstantFiles.flat(),
|
|
1574
|
+
];
|
|
1132
1575
|
}
|
|
1133
1576
|
async getDictionaryFiles() {
|
|
1134
1577
|
const modules = await this.getModules();
|
|
1135
|
-
return Promise.all(
|
|
1578
|
+
return Promise.all(
|
|
1579
|
+
modules.map((module) =>
|
|
1580
|
+
this.getLocalFile(`lib/${module}/${module}.dictionary.ts`),
|
|
1581
|
+
),
|
|
1582
|
+
);
|
|
1136
1583
|
}
|
|
1137
1584
|
override async applyTemplate(options: {
|
|
1138
1585
|
basePath: string;
|
|
@@ -1143,11 +1590,18 @@ export class SysExecutor extends Executor {
|
|
|
1143
1590
|
const dict = {
|
|
1144
1591
|
...(options.dict ?? {}),
|
|
1145
1592
|
...Object.fromEntries(
|
|
1146
|
-
Object.entries(options.dict ?? {}).map(([key, value]) => [
|
|
1593
|
+
Object.entries(options.dict ?? {}).map(([key, value]) => [
|
|
1594
|
+
capitalize(key),
|
|
1595
|
+
capitalize(value),
|
|
1596
|
+
]),
|
|
1147
1597
|
),
|
|
1148
1598
|
};
|
|
1149
1599
|
const scanInfo = await this.scan();
|
|
1150
|
-
const fileContents = await this._applyTemplate({
|
|
1600
|
+
const fileContents = await this._applyTemplate({
|
|
1601
|
+
...options,
|
|
1602
|
+
scanInfo,
|
|
1603
|
+
dict,
|
|
1604
|
+
});
|
|
1151
1605
|
await this.scan();
|
|
1152
1606
|
return fileContents;
|
|
1153
1607
|
}
|
|
@@ -1162,13 +1616,17 @@ export class AppExecutor extends SysExecutor {
|
|
|
1162
1616
|
override emoji = execEmoji.app;
|
|
1163
1617
|
constructor({ workspace, name }: AppExecutorOptions) {
|
|
1164
1618
|
super({ workspace, name, type: "app" });
|
|
1165
|
-
this.dist = new Executor(
|
|
1619
|
+
this.dist = new Executor(
|
|
1620
|
+
`dist/${name}`,
|
|
1621
|
+
`${this.workspace.workspaceRoot}/dist/apps/${name}`,
|
|
1622
|
+
);
|
|
1166
1623
|
}
|
|
1167
1624
|
static #execs = new Map<string, AppExecutor>();
|
|
1168
1625
|
static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
|
|
1169
1626
|
const exec = AppExecutor.#execs.get(name);
|
|
1170
1627
|
if (exec) return exec;
|
|
1171
|
-
else if (executor instanceof WorkspaceExecutor)
|
|
1628
|
+
else if (executor instanceof WorkspaceExecutor)
|
|
1629
|
+
return new AppExecutor({ workspace: executor, name });
|
|
1172
1630
|
else return new AppExecutor({ workspace: executor.workspace, name });
|
|
1173
1631
|
}
|
|
1174
1632
|
getEnv() {
|
|
@@ -1178,7 +1636,9 @@ export class AppExecutor extends SysExecutor {
|
|
|
1178
1636
|
const basePort = 8282;
|
|
1179
1637
|
const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
|
|
1180
1638
|
const PORT = basePort ? (basePort + portOffset).toString() : undefined;
|
|
1181
|
-
const AKAN_PUBLIC_SERVER_PORT = portOffset
|
|
1639
|
+
const AKAN_PUBLIC_SERVER_PORT = portOffset
|
|
1640
|
+
? (8282 + portOffset).toString()
|
|
1641
|
+
: undefined;
|
|
1182
1642
|
return {
|
|
1183
1643
|
...process.env,
|
|
1184
1644
|
AKAN_PUBLIC_APP_NAME: this.name,
|
|
@@ -1191,15 +1651,22 @@ export class AppExecutor extends SysExecutor {
|
|
|
1191
1651
|
}
|
|
1192
1652
|
async prepareCommand(type: "build" | "start") {
|
|
1193
1653
|
const akanConfig = await this.getConfig();
|
|
1194
|
-
const databaseMode =
|
|
1654
|
+
const databaseMode =
|
|
1655
|
+
process.env.AKAN_DATABASE_MODE ??
|
|
1656
|
+
akanConfig.defaultDatabaseMode ??
|
|
1657
|
+
"single";
|
|
1195
1658
|
const routeEnv = {
|
|
1196
1659
|
AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
|
|
1197
1660
|
AKAN_DATABASE_MODE: databaseMode,
|
|
1198
1661
|
};
|
|
1199
1662
|
Object.assign(process.env, routeEnv);
|
|
1200
1663
|
if (type === "build") {
|
|
1201
|
-
if (await this.exists(this.dist.cwdPath))
|
|
1202
|
-
|
|
1664
|
+
if (await this.exists(this.dist.cwdPath))
|
|
1665
|
+
await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
|
|
1666
|
+
await Promise.all([
|
|
1667
|
+
this.dist.mkdir("private"),
|
|
1668
|
+
this.dist.mkdir("public"),
|
|
1669
|
+
]);
|
|
1203
1670
|
await Promise.all([
|
|
1204
1671
|
this.cp("private", `${this.dist.cwdPath}/private`),
|
|
1205
1672
|
this.cp("public", `${this.dist.cwdPath}/public`),
|
|
@@ -1237,7 +1704,11 @@ export class AppExecutor extends SysExecutor {
|
|
|
1237
1704
|
}
|
|
1238
1705
|
|
|
1239
1706
|
#pageKeys: string[] | null = null;
|
|
1240
|
-
async getPageKeys({
|
|
1707
|
+
async getPageKeys({
|
|
1708
|
+
refresh,
|
|
1709
|
+
}: {
|
|
1710
|
+
refresh?: boolean;
|
|
1711
|
+
} = {}): Promise<string[]> {
|
|
1241
1712
|
if (this.#pageKeys && !refresh) return this.#pageKeys;
|
|
1242
1713
|
const akanConfig = await this.getConfig();
|
|
1243
1714
|
const glob = new Bun.Glob("**/*");
|
|
@@ -1247,7 +1718,11 @@ export class AppExecutor extends SysExecutor {
|
|
|
1247
1718
|
this.#pageKeys = [];
|
|
1248
1719
|
return this.#pageKeys;
|
|
1249
1720
|
}
|
|
1250
|
-
for await (const rel of glob.scan({
|
|
1721
|
+
for await (const rel of glob.scan({
|
|
1722
|
+
cwd: pageDir,
|
|
1723
|
+
absolute: false,
|
|
1724
|
+
onlyFiles: true,
|
|
1725
|
+
})) {
|
|
1251
1726
|
const segments = rel.split(path.sep);
|
|
1252
1727
|
if (segments.some((s) => s === "node_modules")) continue;
|
|
1253
1728
|
const posix = segments.join("/");
|
|
@@ -1255,13 +1730,24 @@ export class AppExecutor extends SysExecutor {
|
|
|
1255
1730
|
validatePageSourceFile(posix, { filePath: absPath });
|
|
1256
1731
|
if (!isRouteSourceFile(posix)) continue;
|
|
1257
1732
|
const key = `./${posix}`;
|
|
1258
|
-
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
1733
|
+
validateSubRoutePageKey(key, akanConfig.basePaths, {
|
|
1734
|
+
appName: this.name,
|
|
1735
|
+
filePath: absPath,
|
|
1736
|
+
});
|
|
1259
1737
|
const parsed = parseRouteModuleKey(key);
|
|
1260
1738
|
if (parsed.isInternalRootLayout) {
|
|
1261
|
-
throw new Error(
|
|
1739
|
+
throw new Error(
|
|
1740
|
+
`[route-convention] __root_layout is reserved for Akan.js generated root layout: ${absPath}`,
|
|
1741
|
+
);
|
|
1262
1742
|
}
|
|
1263
|
-
const isRootLayout =
|
|
1264
|
-
|
|
1743
|
+
const isRootLayout =
|
|
1744
|
+
parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
|
|
1745
|
+
validateRouteSourceExports(
|
|
1746
|
+
await Bun.file(absPath).text(),
|
|
1747
|
+
absPath,
|
|
1748
|
+
parsed.kind,
|
|
1749
|
+
{ rootLayout: isRootLayout },
|
|
1750
|
+
);
|
|
1265
1751
|
pageKeys.push(key);
|
|
1266
1752
|
}
|
|
1267
1753
|
pageKeys.sort();
|
|
@@ -1277,28 +1763,61 @@ export class AppExecutor extends SysExecutor {
|
|
|
1277
1763
|
const projectAssetsPath = `${this.cwdPath}/private`;
|
|
1278
1764
|
const projectPublicLibPath = `${projectPublicPath}/libs`;
|
|
1279
1765
|
const projectAssetsLibPath = `${projectAssetsPath}/libs`;
|
|
1280
|
-
await Promise.all([
|
|
1766
|
+
await Promise.all([
|
|
1767
|
+
this.removeDir(projectPublicLibPath),
|
|
1768
|
+
this.removeDir(projectAssetsLibPath),
|
|
1769
|
+
]);
|
|
1281
1770
|
const targetPublicDeps = [] as string[];
|
|
1282
1771
|
for (const dep of libDeps) {
|
|
1283
|
-
if (
|
|
1772
|
+
if (
|
|
1773
|
+
await this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/public`)
|
|
1774
|
+
)
|
|
1775
|
+
targetPublicDeps.push(dep);
|
|
1284
1776
|
}
|
|
1285
1777
|
const targetAssetsDeps = [] as string[];
|
|
1286
1778
|
for (const dep of libDeps) {
|
|
1287
|
-
if (
|
|
1779
|
+
if (
|
|
1780
|
+
await this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/private`)
|
|
1781
|
+
)
|
|
1782
|
+
targetAssetsDeps.push(dep);
|
|
1288
1783
|
}
|
|
1289
|
-
await Promise.all(
|
|
1290
|
-
|
|
1784
|
+
await Promise.all(
|
|
1785
|
+
targetPublicDeps.map((dep) =>
|
|
1786
|
+
this.mkdir(`${projectPublicLibPath}/${dep}`),
|
|
1787
|
+
),
|
|
1788
|
+
);
|
|
1789
|
+
await Promise.all(
|
|
1790
|
+
targetAssetsDeps.map((dep) =>
|
|
1791
|
+
this.mkdir(`${projectAssetsLibPath}/${dep}`),
|
|
1792
|
+
),
|
|
1793
|
+
);
|
|
1291
1794
|
await Promise.all([
|
|
1292
1795
|
...targetPublicDeps.map((dep) =>
|
|
1293
|
-
this.cp(
|
|
1796
|
+
this.cp(
|
|
1797
|
+
`${this.workspace.workspaceRoot}/libs/${dep}/public`,
|
|
1798
|
+
`${projectPublicLibPath}/${dep}`,
|
|
1799
|
+
),
|
|
1294
1800
|
),
|
|
1295
1801
|
...targetAssetsDeps.map((dep) =>
|
|
1296
|
-
this.cp(
|
|
1802
|
+
this.cp(
|
|
1803
|
+
`${this.workspace.workspaceRoot}/libs/${dep}/private`,
|
|
1804
|
+
`${projectAssetsLibPath}/${dep}`,
|
|
1805
|
+
),
|
|
1297
1806
|
),
|
|
1298
1807
|
]);
|
|
1299
1808
|
}
|
|
1300
|
-
async scanSync({
|
|
1301
|
-
|
|
1809
|
+
async scanSync({
|
|
1810
|
+
refresh = false,
|
|
1811
|
+
write = true,
|
|
1812
|
+
}: {
|
|
1813
|
+
refresh?: boolean;
|
|
1814
|
+
write?: boolean;
|
|
1815
|
+
} = {}) {
|
|
1816
|
+
const scanInfo = (await this.scan({
|
|
1817
|
+
refresh,
|
|
1818
|
+
write,
|
|
1819
|
+
writeLib: write,
|
|
1820
|
+
})) as AppInfo;
|
|
1302
1821
|
if (write) await this.syncAssets(scanInfo.getScanResult().libDeps);
|
|
1303
1822
|
return scanInfo;
|
|
1304
1823
|
}
|
|
@@ -1318,13 +1837,17 @@ export class LibExecutor extends SysExecutor {
|
|
|
1318
1837
|
override emoji = execEmoji.lib;
|
|
1319
1838
|
constructor({ workspace, name }: LibExecutorOptions) {
|
|
1320
1839
|
super({ workspace, name, type: "lib" });
|
|
1321
|
-
this.dist = new Executor(
|
|
1840
|
+
this.dist = new Executor(
|
|
1841
|
+
`dist/${name}`,
|
|
1842
|
+
`${this.workspace.workspaceRoot}/dist/libs/${name}`,
|
|
1843
|
+
);
|
|
1322
1844
|
}
|
|
1323
1845
|
static #execs = new Map<string, LibExecutor>();
|
|
1324
1846
|
static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
|
|
1325
1847
|
const exec = LibExecutor.#execs.get(name);
|
|
1326
1848
|
if (exec) return exec;
|
|
1327
|
-
else if (executor instanceof WorkspaceExecutor)
|
|
1849
|
+
else if (executor instanceof WorkspaceExecutor)
|
|
1850
|
+
return new LibExecutor({ workspace: executor, name });
|
|
1328
1851
|
else return new LibExecutor({ workspace: executor.workspace, name });
|
|
1329
1852
|
}
|
|
1330
1853
|
|
|
@@ -1345,14 +1868,21 @@ export class PkgExecutor extends Executor {
|
|
|
1345
1868
|
override name: string;
|
|
1346
1869
|
dist: Executor;
|
|
1347
1870
|
override emoji = execEmoji.pkg;
|
|
1348
|
-
constructor({
|
|
1871
|
+
constructor({
|
|
1872
|
+
workspace = WorkspaceExecutor.fromRoot(),
|
|
1873
|
+
name,
|
|
1874
|
+
}: PkgExecutorOptions) {
|
|
1349
1875
|
super(name, `${workspace.workspaceRoot}/pkgs/${name}`);
|
|
1350
1876
|
this.workspace = workspace;
|
|
1351
1877
|
this.name = name;
|
|
1352
|
-
this.dist = new Executor(
|
|
1878
|
+
this.dist = new Executor(
|
|
1879
|
+
`dist/${name}`,
|
|
1880
|
+
`${this.workspace.workspaceRoot}/dist/pkgs/${name}`,
|
|
1881
|
+
);
|
|
1353
1882
|
}
|
|
1354
1883
|
static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
|
|
1355
|
-
if (executor instanceof WorkspaceExecutor)
|
|
1884
|
+
if (executor instanceof WorkspaceExecutor)
|
|
1885
|
+
return new PkgExecutor({ workspace: executor, name });
|
|
1356
1886
|
return new PkgExecutor({ workspace: executor.workspace, name });
|
|
1357
1887
|
}
|
|
1358
1888
|
|
|
@@ -1364,16 +1894,28 @@ export class PkgExecutor extends Executor {
|
|
|
1364
1894
|
this.#scanInfo = scanInfo;
|
|
1365
1895
|
return scanInfo;
|
|
1366
1896
|
}
|
|
1367
|
-
async #getDependencyVersion(
|
|
1368
|
-
|
|
1897
|
+
async #getDependencyVersion(
|
|
1898
|
+
rootPackageJson: PackageJson,
|
|
1899
|
+
dep: string,
|
|
1900
|
+
): Promise<string | undefined> {
|
|
1901
|
+
const rootDeps = {
|
|
1902
|
+
...rootPackageJson.dependencies,
|
|
1903
|
+
...rootPackageJson.devDependencies,
|
|
1904
|
+
};
|
|
1369
1905
|
const rootVersion = rootDeps[dep];
|
|
1370
1906
|
if (rootVersion) return rootVersion;
|
|
1371
1907
|
|
|
1372
1908
|
try {
|
|
1373
1909
|
const packageJsonPath = `pkgs/${dep}/package.json`;
|
|
1374
|
-
if (
|
|
1910
|
+
if (
|
|
1911
|
+
!(await Bun.file(
|
|
1912
|
+
path.join(this.workspace.workspaceRoot, packageJsonPath),
|
|
1913
|
+
).exists())
|
|
1914
|
+
)
|
|
1915
|
+
return undefined;
|
|
1375
1916
|
const packageJson = await this.workspace.readJson(packageJsonPath);
|
|
1376
|
-
if ((packageJson as PackageJson).name === dep)
|
|
1917
|
+
if ((packageJson as PackageJson).name === dep)
|
|
1918
|
+
return (packageJson as PackageJson).version;
|
|
1377
1919
|
} catch {
|
|
1378
1920
|
return undefined;
|
|
1379
1921
|
}
|
|
@@ -1384,7 +1926,9 @@ export class PkgExecutor extends Executor {
|
|
|
1384
1926
|
devDependencies: string[] = [],
|
|
1385
1927
|
): Promise<Pick<PackageJson, "dependencies" | "devDependencies">> {
|
|
1386
1928
|
const dependencyNames = [...new Set(dependencies)].sort();
|
|
1387
|
-
const devDependencyNames = [...new Set(devDependencies)]
|
|
1929
|
+
const devDependencyNames = [...new Set(devDependencies)]
|
|
1930
|
+
.filter((dep) => !dependencyNames.includes(dep))
|
|
1931
|
+
.sort();
|
|
1388
1932
|
const dependencyVersions = new Map<string, string>();
|
|
1389
1933
|
const missingDeps: string[] = [];
|
|
1390
1934
|
for (const dep of [...dependencyNames, ...devDependencyNames]) {
|
|
@@ -1393,26 +1937,40 @@ export class PkgExecutor extends Executor {
|
|
|
1393
1937
|
else missingDeps.push(dep);
|
|
1394
1938
|
}
|
|
1395
1939
|
if (missingDeps.length > 0)
|
|
1396
|
-
throw new Error(
|
|
1940
|
+
throw new Error(
|
|
1941
|
+
`Missing dependency versions in root package.json: ${missingDeps.join(", ")}`,
|
|
1942
|
+
);
|
|
1397
1943
|
|
|
1398
1944
|
const toDependencyEntries = (names: string[]) =>
|
|
1399
1945
|
names.map((dep) => {
|
|
1400
1946
|
const version = dependencyVersions.get(dep);
|
|
1401
|
-
if (!version)
|
|
1947
|
+
if (!version)
|
|
1948
|
+
throw new Error(
|
|
1949
|
+
`Missing dependency versions in root package.json: ${dep}`,
|
|
1950
|
+
);
|
|
1402
1951
|
return [dep, version] as const;
|
|
1403
1952
|
});
|
|
1404
1953
|
|
|
1405
1954
|
return {
|
|
1406
1955
|
dependencies: Object.fromEntries(toDependencyEntries(dependencyNames)),
|
|
1407
|
-
devDependencies: Object.fromEntries(
|
|
1956
|
+
devDependencies: Object.fromEntries(
|
|
1957
|
+
toDependencyEntries(devDependencyNames),
|
|
1958
|
+
),
|
|
1408
1959
|
};
|
|
1409
1960
|
}
|
|
1410
1961
|
async updatePackageJsonDependencies(
|
|
1411
1962
|
dependencies: string[] = [],
|
|
1412
1963
|
devDependencies: string[] = [],
|
|
1413
1964
|
): Promise<PackageJson> {
|
|
1414
|
-
const [rootPackageJson, pkgJson] = await Promise.all([
|
|
1415
|
-
|
|
1965
|
+
const [rootPackageJson, pkgJson] = await Promise.all([
|
|
1966
|
+
this.workspace.getPackageJson(),
|
|
1967
|
+
this.getPackageJson(),
|
|
1968
|
+
]);
|
|
1969
|
+
const dependencyMaps = await this.#toDependencyMap(
|
|
1970
|
+
rootPackageJson,
|
|
1971
|
+
dependencies,
|
|
1972
|
+
devDependencies,
|
|
1973
|
+
);
|
|
1416
1974
|
const newPkgJson = {
|
|
1417
1975
|
...pkgJson,
|
|
1418
1976
|
...dependencyMaps,
|
|
@@ -1420,17 +1978,37 @@ export class PkgExecutor extends Executor {
|
|
|
1420
1978
|
await this.writeJson("package.json", newPkgJson);
|
|
1421
1979
|
return newPkgJson;
|
|
1422
1980
|
}
|
|
1423
|
-
async generateDistPackageJson(
|
|
1424
|
-
|
|
1425
|
-
|
|
1981
|
+
async generateDistPackageJson(
|
|
1982
|
+
dependencies: string[] = [],
|
|
1983
|
+
devDependencies: string[] = [],
|
|
1984
|
+
): Promise<PackageJson> {
|
|
1985
|
+
const [rootPackageJson, pkgJson] = await Promise.all([
|
|
1986
|
+
this.workspace.getPackageJson(),
|
|
1987
|
+
this.getPackageJson(),
|
|
1988
|
+
]);
|
|
1989
|
+
const dependencyMaps = await this.#toDependencyMap(
|
|
1990
|
+
rootPackageJson,
|
|
1991
|
+
dependencies,
|
|
1992
|
+
devDependencies,
|
|
1993
|
+
);
|
|
1426
1994
|
const distPkgJson: PackageJson = {
|
|
1427
1995
|
...pkgJson,
|
|
1428
1996
|
type: "module",
|
|
1429
|
-
exports: {
|
|
1997
|
+
exports: {
|
|
1998
|
+
...pkgJson.exports,
|
|
1999
|
+
".": {
|
|
2000
|
+
import: "./index.ts",
|
|
2001
|
+
types: "./index.ts",
|
|
2002
|
+
default: "./index.ts",
|
|
2003
|
+
},
|
|
2004
|
+
},
|
|
1430
2005
|
engines: { bun: ">=1.3.13" },
|
|
1431
2006
|
...dependencyMaps,
|
|
1432
2007
|
};
|
|
1433
|
-
await Promise.all([
|
|
2008
|
+
await Promise.all([
|
|
2009
|
+
this.dist.writeJson("package.json", distPkgJson),
|
|
2010
|
+
this.writeJson("package.json", distPkgJson),
|
|
2011
|
+
]);
|
|
1434
2012
|
return distPkgJson;
|
|
1435
2013
|
}
|
|
1436
2014
|
async build(): Promise<void> {
|
|
@@ -1443,7 +2021,10 @@ export class PkgExecutor extends Executor {
|
|
|
1443
2021
|
await this.cp(`${this.cwdPath}/dist`, this.dist.cwdPath);
|
|
1444
2022
|
}
|
|
1445
2023
|
async generateTsconfigJson(): Promise<TsConfigJson> {
|
|
1446
|
-
const [rootTsconfig, pkgTsconfig] = await Promise.all([
|
|
2024
|
+
const [rootTsconfig, pkgTsconfig] = await Promise.all([
|
|
2025
|
+
this.workspace.getTsConfig(),
|
|
2026
|
+
this.getTsConfig(),
|
|
2027
|
+
]);
|
|
1447
2028
|
const tsconfig: TsConfigJson = {
|
|
1448
2029
|
...rootTsconfig,
|
|
1449
2030
|
...pkgTsconfig,
|
|
@@ -1466,7 +2047,10 @@ export class ModuleExecutor extends Executor {
|
|
|
1466
2047
|
sys: SysExecutor;
|
|
1467
2048
|
override emoji = execEmoji.module;
|
|
1468
2049
|
constructor({ sys, name }: ModuleExecutorOptions) {
|
|
1469
|
-
super(
|
|
2050
|
+
super(
|
|
2051
|
+
name,
|
|
2052
|
+
`${sys.workspace.workspaceRoot}/${sys.type}s/${sys.name}/lib/${name}`,
|
|
2053
|
+
);
|
|
1470
2054
|
this.sys = sys;
|
|
1471
2055
|
}
|
|
1472
2056
|
static from(sysExecutor: SysExecutor, name: string) {
|