@akanjs/devkit 0.0.143 → 0.0.145
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/cjs/src/aiEditor.js +152 -16
- package/cjs/src/builder.js +1 -2
- package/cjs/src/commandDecorators/command.js +0 -1
- package/cjs/src/executors.js +151 -60
- package/cjs/src/guideline.js +15 -0
- package/cjs/src/index.js +5 -1
- package/cjs/src/linter.js +238 -0
- package/cjs/src/prompter.js +78 -0
- package/cjs/src/typeChecker.js +203 -0
- package/cjs/src/uploadRelease.js +59 -33
- package/esm/src/aiEditor.js +158 -17
- package/esm/src/builder.js +1 -2
- package/esm/src/commandDecorators/command.js +0 -1
- package/esm/src/executors.js +152 -61
- package/esm/src/guideline.js +0 -0
- package/esm/src/index.js +2 -0
- package/esm/src/linter.js +205 -0
- package/esm/src/prompter.js +45 -0
- package/esm/src/typeChecker.js +170 -0
- package/esm/src/uploadRelease.js +59 -33
- package/package.json +3 -1
- package/src/aiEditor.d.ts +23 -4
- package/src/executors.d.ts +63 -23
- package/src/guideline.d.ts +19 -0
- package/src/index.d.ts +2 -0
- package/src/linter.d.ts +109 -0
- package/src/prompter.d.ts +13 -0
- package/src/typeChecker.d.ts +49 -0
- package/src/types.d.ts +4 -0
- package/src/uploadRelease.d.ts +1 -1
package/esm/src/executors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Logger } from "@akanjs/common";
|
|
1
|
+
import { capitalize, Logger } from "@akanjs/common";
|
|
2
2
|
import {
|
|
3
3
|
getAppConfig,
|
|
4
4
|
getDefaultFileScan,
|
|
@@ -11,7 +11,9 @@ import fs from "fs";
|
|
|
11
11
|
import fsPromise from "fs/promises";
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { TypeScriptDependencyScanner } from "./dependencyScanner";
|
|
14
|
+
import { Linter } from "./linter";
|
|
14
15
|
import { Spinner } from "./spinner";
|
|
16
|
+
import { TypeChecker } from "./typeChecker";
|
|
15
17
|
const execEmoji = {
|
|
16
18
|
workspace: "\u{1F3E0}",
|
|
17
19
|
app: "\u{1F680}",
|
|
@@ -31,6 +33,8 @@ class Executor {
|
|
|
31
33
|
logger;
|
|
32
34
|
cwdPath;
|
|
33
35
|
emoji = execEmoji.default;
|
|
36
|
+
typeChecker = null;
|
|
37
|
+
linter = null;
|
|
34
38
|
constructor(name, cwdPath) {
|
|
35
39
|
this.name = name;
|
|
36
40
|
this.logger = new Logger(name);
|
|
@@ -65,28 +69,32 @@ class Executor {
|
|
|
65
69
|
spawn(command, args = [], options = {}) {
|
|
66
70
|
const proc = spawn(command, args, {
|
|
67
71
|
cwd: this.cwdPath,
|
|
68
|
-
stdio: "inherit",
|
|
72
|
+
// stdio: "inherit",
|
|
69
73
|
...options
|
|
70
74
|
});
|
|
75
|
+
let stdout = "";
|
|
76
|
+
proc.stdout?.on("data", (data) => {
|
|
77
|
+
stdout += data;
|
|
78
|
+
});
|
|
71
79
|
proc.stdout?.on("data", (data) => {
|
|
72
80
|
this.#stdout(data);
|
|
73
81
|
});
|
|
74
82
|
proc.stderr?.on("data", (data) => {
|
|
75
|
-
this.#
|
|
83
|
+
this.#stdout(data);
|
|
76
84
|
});
|
|
77
85
|
return new Promise((resolve, reject) => {
|
|
78
86
|
proc.on("exit", (code, signal) => {
|
|
79
87
|
if (!!code || signal)
|
|
80
|
-
reject({ code, signal });
|
|
88
|
+
reject({ code, signal, stdout });
|
|
81
89
|
else
|
|
82
|
-
resolve(
|
|
90
|
+
resolve(stdout);
|
|
83
91
|
});
|
|
84
92
|
});
|
|
85
93
|
}
|
|
86
94
|
fork(modulePath, args = [], options = {}) {
|
|
87
95
|
const proc = fork(modulePath, args, {
|
|
88
96
|
cwd: this.cwdPath,
|
|
89
|
-
stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
97
|
+
// stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
90
98
|
...options
|
|
91
99
|
});
|
|
92
100
|
proc.stdout?.on("data", (data) => {
|
|
@@ -104,45 +112,70 @@ class Executor {
|
|
|
104
112
|
});
|
|
105
113
|
});
|
|
106
114
|
}
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
getPath(filePath) {
|
|
116
|
+
if (path.isAbsolute(filePath))
|
|
117
|
+
return filePath;
|
|
118
|
+
const baseParts = this.cwdPath.split("/").filter(Boolean);
|
|
119
|
+
const targetParts = filePath.split("/").filter(Boolean);
|
|
120
|
+
let overlapLength = 0;
|
|
121
|
+
for (let i = 1; i <= Math.min(baseParts.length, targetParts.length); i++) {
|
|
122
|
+
let isOverlap = true;
|
|
123
|
+
for (let j = 0; j < i; j++)
|
|
124
|
+
if (baseParts[baseParts.length - i + j] !== targetParts[j]) {
|
|
125
|
+
isOverlap = false;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
if (isOverlap)
|
|
129
|
+
overlapLength = i;
|
|
130
|
+
}
|
|
131
|
+
const result = overlapLength > 0 ? `/${[...baseParts, ...targetParts.slice(overlapLength)].join("/")}` : `${this.cwdPath}/${filePath}`;
|
|
132
|
+
return result.replace(/\/+/g, "/");
|
|
109
133
|
}
|
|
110
134
|
mkdir(dirPath) {
|
|
111
|
-
const writePath = this
|
|
135
|
+
const writePath = this.getPath(dirPath);
|
|
112
136
|
if (!fs.existsSync(writePath))
|
|
113
137
|
fs.mkdirSync(writePath, { recursive: true });
|
|
114
138
|
this.logger.verbose(`Make directory ${writePath}`);
|
|
115
139
|
return this;
|
|
116
140
|
}
|
|
141
|
+
async readdir(dirPath) {
|
|
142
|
+
const readPath = this.getPath(dirPath);
|
|
143
|
+
try {
|
|
144
|
+
return await fsPromise.readdir(readPath);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
117
149
|
exists(filePath) {
|
|
118
|
-
const readPath = this
|
|
150
|
+
const readPath = this.getPath(filePath);
|
|
119
151
|
return fs.existsSync(readPath);
|
|
120
152
|
}
|
|
121
153
|
remove(filePath) {
|
|
122
|
-
const readPath = this
|
|
154
|
+
const readPath = this.getPath(filePath);
|
|
123
155
|
if (fs.existsSync(readPath))
|
|
124
156
|
fs.unlinkSync(readPath);
|
|
125
157
|
this.logger.verbose(`Remove file ${readPath}`);
|
|
126
158
|
return this;
|
|
127
159
|
}
|
|
128
160
|
removeDir(dirPath) {
|
|
129
|
-
const readPath = this
|
|
161
|
+
const readPath = this.getPath(dirPath);
|
|
130
162
|
if (fs.existsSync(readPath))
|
|
131
163
|
fs.rmSync(readPath, { recursive: true });
|
|
132
164
|
this.logger.verbose(`Remove directory ${readPath}`);
|
|
133
165
|
return this;
|
|
134
166
|
}
|
|
135
167
|
writeFile(filePath, content, { overwrite = true } = {}) {
|
|
136
|
-
const writePath = this
|
|
168
|
+
const writePath = this.getPath(filePath);
|
|
137
169
|
const dir = path.dirname(writePath);
|
|
138
170
|
if (!fs.existsSync(dir))
|
|
139
171
|
fs.mkdirSync(dir, { recursive: true });
|
|
140
|
-
|
|
172
|
+
let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
141
173
|
if (fs.existsSync(writePath)) {
|
|
142
174
|
const currentContent = fs.readFileSync(writePath, "utf8");
|
|
143
|
-
if (currentContent === contentStr || !overwrite)
|
|
175
|
+
if (currentContent === contentStr || !overwrite) {
|
|
144
176
|
this.logger.verbose(`File ${writePath} is unchanged`);
|
|
145
|
-
|
|
177
|
+
contentStr = fs.readFileSync(writePath, "utf-8");
|
|
178
|
+
} else {
|
|
146
179
|
fs.writeFileSync(writePath, contentStr, "utf8");
|
|
147
180
|
this.logger.verbose(`File ${writePath} is changed`);
|
|
148
181
|
}
|
|
@@ -150,28 +183,28 @@ class Executor {
|
|
|
150
183
|
fs.writeFileSync(writePath, contentStr, "utf8");
|
|
151
184
|
this.logger.verbose(`File ${writePath} is created`);
|
|
152
185
|
}
|
|
153
|
-
return
|
|
186
|
+
return { filePath: writePath, content: contentStr };
|
|
154
187
|
}
|
|
155
188
|
writeJson(filePath, content) {
|
|
156
189
|
this.writeFile(filePath, JSON.stringify(content, null, 2) + "\n");
|
|
157
190
|
return this;
|
|
158
191
|
}
|
|
159
|
-
getLocalFile(
|
|
160
|
-
const
|
|
161
|
-
const content = this.readFile(
|
|
162
|
-
return {
|
|
192
|
+
getLocalFile(targetPath) {
|
|
193
|
+
const filePath = path.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
|
|
194
|
+
const content = this.readFile(filePath);
|
|
195
|
+
return { filePath, content };
|
|
163
196
|
}
|
|
164
197
|
readFile(filePath) {
|
|
165
|
-
const readPath = this
|
|
198
|
+
const readPath = this.getPath(filePath);
|
|
166
199
|
return fs.readFileSync(readPath, "utf8");
|
|
167
200
|
}
|
|
168
201
|
readJson(filePath) {
|
|
169
|
-
const readPath = this
|
|
202
|
+
const readPath = this.getPath(filePath);
|
|
170
203
|
return JSON.parse(fs.readFileSync(readPath, "utf8"));
|
|
171
204
|
}
|
|
172
205
|
async cp(srcPath, destPath) {
|
|
173
|
-
const src = this
|
|
174
|
-
const dest = this
|
|
206
|
+
const src = this.getPath(srcPath);
|
|
207
|
+
const dest = this.getPath(destPath);
|
|
175
208
|
await fsPromise.cp(src, dest, { recursive: true });
|
|
176
209
|
}
|
|
177
210
|
log(msg) {
|
|
@@ -207,7 +240,7 @@ class Executor {
|
|
|
207
240
|
const getContent = require(templatePath);
|
|
208
241
|
const result = getContent.default(scanResult ?? null, dict);
|
|
209
242
|
if (result === null)
|
|
210
|
-
return;
|
|
243
|
+
return null;
|
|
211
244
|
const filename = typeof result === "object" ? result.filename : path.basename(targetPath).replace(".js", ".ts");
|
|
212
245
|
const content = typeof result === "object" ? result.content : result;
|
|
213
246
|
const dirname = path.dirname(targetPath);
|
|
@@ -216,7 +249,7 @@ class Executor {
|
|
|
216
249
|
`${dirname}/${filename}`
|
|
217
250
|
);
|
|
218
251
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
219
|
-
this.writeFile(convertedTargetPath, content, { overwrite });
|
|
252
|
+
return this.writeFile(convertedTargetPath, content, { overwrite });
|
|
220
253
|
} else if (targetPath.endsWith(".template")) {
|
|
221
254
|
const content = await fsPromise.readFile(templatePath, "utf8");
|
|
222
255
|
const convertedTargetPath = Object.entries(dict).reduce(
|
|
@@ -228,10 +261,11 @@ class Executor {
|
|
|
228
261
|
content
|
|
229
262
|
);
|
|
230
263
|
this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
|
|
231
|
-
this.writeFile(convertedTargetPath, convertedContent, { overwrite });
|
|
232
|
-
}
|
|
264
|
+
return this.writeFile(convertedTargetPath, convertedContent, { overwrite });
|
|
265
|
+
} else
|
|
266
|
+
return null;
|
|
233
267
|
}
|
|
234
|
-
async
|
|
268
|
+
async _applyTemplate({
|
|
235
269
|
basePath,
|
|
236
270
|
template,
|
|
237
271
|
scanResult,
|
|
@@ -241,22 +275,24 @@ class Executor {
|
|
|
241
275
|
const templatePath = `${__dirname}/src/templates${template ? `/${template}` : ""}`.replace(".ts", ".js");
|
|
242
276
|
if (fs.statSync(templatePath).isFile()) {
|
|
243
277
|
const filename = path.basename(templatePath);
|
|
244
|
-
await this.#applyTemplateFile(
|
|
278
|
+
const fileContent = await this.#applyTemplateFile(
|
|
245
279
|
{ templatePath, targetPath: path.join(basePath, filename), scanResult, overwrite },
|
|
246
280
|
dict
|
|
247
281
|
);
|
|
282
|
+
return fileContent ? [fileContent] : [];
|
|
248
283
|
} else {
|
|
249
|
-
const subdirs = await
|
|
250
|
-
await Promise.all(
|
|
284
|
+
const subdirs = await this.readdir(templatePath);
|
|
285
|
+
const fileContents = (await Promise.all(
|
|
251
286
|
subdirs.map(async (subdir) => {
|
|
252
287
|
const subpath = path.join(templatePath, subdir);
|
|
253
|
-
if (fs.statSync(subpath).isFile())
|
|
254
|
-
await this.#applyTemplateFile(
|
|
288
|
+
if (fs.statSync(subpath).isFile()) {
|
|
289
|
+
const fileContent = await this.#applyTemplateFile(
|
|
255
290
|
{ templatePath: subpath, targetPath: path.join(basePath, subdir), scanResult, overwrite },
|
|
256
291
|
dict
|
|
257
292
|
);
|
|
258
|
-
|
|
259
|
-
|
|
293
|
+
return fileContent ? [fileContent] : [];
|
|
294
|
+
} else
|
|
295
|
+
return await this._applyTemplate({
|
|
260
296
|
basePath: path.join(basePath, subdir),
|
|
261
297
|
template: path.join(template, subdir),
|
|
262
298
|
scanResult,
|
|
@@ -264,9 +300,41 @@ class Executor {
|
|
|
264
300
|
overwrite
|
|
265
301
|
});
|
|
266
302
|
})
|
|
267
|
-
);
|
|
303
|
+
)).flat();
|
|
304
|
+
return fileContents;
|
|
268
305
|
}
|
|
269
306
|
}
|
|
307
|
+
async applyTemplate(options) {
|
|
308
|
+
const dict = {
|
|
309
|
+
...options.dict ?? {},
|
|
310
|
+
...Object.fromEntries(
|
|
311
|
+
Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)])
|
|
312
|
+
)
|
|
313
|
+
};
|
|
314
|
+
return this._applyTemplate({ ...options, dict });
|
|
315
|
+
}
|
|
316
|
+
getTypeChecker() {
|
|
317
|
+
this.typeChecker ??= new TypeChecker(this);
|
|
318
|
+
return this.typeChecker;
|
|
319
|
+
}
|
|
320
|
+
typeCheck(filePath) {
|
|
321
|
+
const path2 = this.getPath(filePath);
|
|
322
|
+
const typeChecker = this.getTypeChecker();
|
|
323
|
+
const { diagnostics, errors, warnings } = typeChecker.check(path2);
|
|
324
|
+
const message = typeChecker.formatDiagnostics(diagnostics);
|
|
325
|
+
return { diagnostics, errors, warnings, message };
|
|
326
|
+
}
|
|
327
|
+
getLinter() {
|
|
328
|
+
this.linter ??= new Linter(this.cwdPath);
|
|
329
|
+
return this.linter;
|
|
330
|
+
}
|
|
331
|
+
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
332
|
+
const path2 = this.getPath(filePath);
|
|
333
|
+
const linter = this.getLinter();
|
|
334
|
+
const { results, errors, warnings } = await linter.lint(path2, { fix, dryRun });
|
|
335
|
+
const message = linter.formatLintResults(results);
|
|
336
|
+
return { results, message, errors, warnings };
|
|
337
|
+
}
|
|
270
338
|
}
|
|
271
339
|
class WorkspaceExecutor extends Executor {
|
|
272
340
|
workspaceRoot;
|
|
@@ -379,7 +447,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
379
447
|
async getDirInModule(basePath, name) {
|
|
380
448
|
const AVOID_DIRS = ["__lib", "__scalar", `_`, `_${name}`];
|
|
381
449
|
const getDirs = async (dirname, maxDepth = 3, results = [], prefix = "") => {
|
|
382
|
-
const dirs = await
|
|
450
|
+
const dirs = await this.readdir(dirname);
|
|
383
451
|
await Promise.all(
|
|
384
452
|
dirs.map(async (dir) => {
|
|
385
453
|
if (dir.includes("_") || AVOID_DIRS.includes(dir))
|
|
@@ -406,7 +474,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
406
474
|
async #getDirHasFile(basePath, targetFilename) {
|
|
407
475
|
const AVOID_DIRS = ["node_modules", "dist", "public", "./next"];
|
|
408
476
|
const getDirs = async (dirname, maxDepth = 3, results = [], prefix = "") => {
|
|
409
|
-
const dirs = await
|
|
477
|
+
const dirs = await this.readdir(dirname);
|
|
410
478
|
await Promise.all(
|
|
411
479
|
dirs.map(async (dir) => {
|
|
412
480
|
if (AVOID_DIRS.includes(dir))
|
|
@@ -504,14 +572,14 @@ class SysExecutor extends Executor {
|
|
|
504
572
|
if (!fs.existsSync(`${this.cwdPath}/lib/__scalar`))
|
|
505
573
|
fs.mkdirSync(`${this.cwdPath}/lib/__scalar`, { recursive: true });
|
|
506
574
|
const files = getDefaultFileScan();
|
|
507
|
-
const dirnames = (await
|
|
575
|
+
const dirnames = (await this.readdir("lib")).filter(
|
|
508
576
|
(name) => fs.lstatSync(`${this.cwdPath}/lib/${name}`).isDirectory()
|
|
509
577
|
);
|
|
510
578
|
const databaseDirs = dirnames.filter((name) => !name.startsWith("_"));
|
|
511
579
|
const serviceDirs = dirnames.filter((name) => name.startsWith("_") && !name.startsWith("__"));
|
|
512
580
|
await Promise.all(
|
|
513
581
|
databaseDirs.map(async (name) => {
|
|
514
|
-
const filenames = await
|
|
582
|
+
const filenames = await this.readdir(path.join("lib", name));
|
|
515
583
|
filenames.forEach((filename) => {
|
|
516
584
|
if (filename.endsWith(".constant.ts"))
|
|
517
585
|
files.constants.databases.push(name);
|
|
@@ -533,7 +601,7 @@ class SysExecutor extends Executor {
|
|
|
533
601
|
await Promise.all(
|
|
534
602
|
serviceDirs.map(async (dirname) => {
|
|
535
603
|
const name = dirname.slice(1);
|
|
536
|
-
const filenames = await
|
|
604
|
+
const filenames = await this.readdir(path.join("lib", dirname));
|
|
537
605
|
filenames.forEach((filename) => {
|
|
538
606
|
if (filename.endsWith(".dictionary.ts"))
|
|
539
607
|
files.dictionary.services.push(name);
|
|
@@ -548,12 +616,10 @@ class SysExecutor extends Executor {
|
|
|
548
616
|
});
|
|
549
617
|
})
|
|
550
618
|
);
|
|
551
|
-
const scalarDirs = (await
|
|
552
|
-
(name) => !name.startsWith("_")
|
|
553
|
-
);
|
|
619
|
+
const scalarDirs = (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_"));
|
|
554
620
|
await Promise.all(
|
|
555
621
|
scalarDirs.map(async (name) => {
|
|
556
|
-
const filenames = await
|
|
622
|
+
const filenames = await this.readdir(path.join("lib/__scalar", name));
|
|
557
623
|
filenames.forEach((filename) => {
|
|
558
624
|
if (filename.endsWith(".constant.ts"))
|
|
559
625
|
files.constants.scalars.push(name);
|
|
@@ -600,11 +666,11 @@ class SysExecutor extends Executor {
|
|
|
600
666
|
dependencies: [...npmDepSet].filter((dep) => !dep.startsWith("@akanjs")),
|
|
601
667
|
libs: Object.fromEntries(akanConfig.libs.map((libName) => [libName, libScanResults[libName]]))
|
|
602
668
|
};
|
|
603
|
-
await this.
|
|
604
|
-
await this.
|
|
605
|
-
await this.
|
|
669
|
+
await this._applyTemplate({ basePath: "lib", template: "lib", scanResult });
|
|
670
|
+
await this._applyTemplate({ basePath: ".", template: "server.ts", scanResult });
|
|
671
|
+
await this._applyTemplate({ basePath: ".", template: "client.ts", scanResult });
|
|
606
672
|
if (this.type === "lib")
|
|
607
|
-
await this.
|
|
673
|
+
await this._applyTemplate({ basePath: ".", template: "index.ts", scanResult });
|
|
608
674
|
this.writeJson(`akan.${this.type}.json`, scanResult);
|
|
609
675
|
if (this.type === "app")
|
|
610
676
|
return scanResult;
|
|
@@ -627,33 +693,33 @@ class SysExecutor extends Executor {
|
|
|
627
693
|
this.writeJson("package.json", libPkgJsonWithDeps);
|
|
628
694
|
return scanResult;
|
|
629
695
|
}
|
|
630
|
-
getLocalFile(
|
|
631
|
-
const
|
|
632
|
-
const content = this.workspace.readFile(
|
|
633
|
-
return {
|
|
696
|
+
getLocalFile(targetPath) {
|
|
697
|
+
const filePath = path.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
|
|
698
|
+
const content = this.workspace.readFile(filePath);
|
|
699
|
+
return { filePath, content };
|
|
634
700
|
}
|
|
635
701
|
async getDatabaseModules() {
|
|
636
|
-
const databaseModules = (await
|
|
702
|
+
const databaseModules = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => fs.existsSync(`${this.cwdPath}/lib/${name}/${name}.constant.ts`));
|
|
637
703
|
return databaseModules;
|
|
638
704
|
}
|
|
639
705
|
async getServiceModules() {
|
|
640
|
-
const serviceModules = (await
|
|
706
|
+
const serviceModules = (await this.readdir("lib")).filter((name) => name.startsWith("_") && !name.startsWith("__")).filter((name) => fs.existsSync(`${this.cwdPath}/lib/${name}/${name}.service.ts`));
|
|
641
707
|
return serviceModules;
|
|
642
708
|
}
|
|
643
709
|
async getScalarModules() {
|
|
644
|
-
const scalarModules = (await
|
|
710
|
+
const scalarModules = (await this.readdir("lib/__scalar")).filter((name) => !name.startsWith("_")).filter((name) => fs.existsSync(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`));
|
|
645
711
|
return scalarModules;
|
|
646
712
|
}
|
|
647
713
|
async getViewComponents() {
|
|
648
|
-
const viewComponents = (await
|
|
714
|
+
const viewComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => fs.existsSync(`${this.cwdPath}/lib/${name}/${name}.View.tsx`));
|
|
649
715
|
return viewComponents;
|
|
650
716
|
}
|
|
651
717
|
async getUnitComponents() {
|
|
652
|
-
const unitComponents = (await
|
|
718
|
+
const unitComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => fs.existsSync(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`));
|
|
653
719
|
return unitComponents;
|
|
654
720
|
}
|
|
655
721
|
async getTemplateComponents() {
|
|
656
|
-
const templateComponents = (await
|
|
722
|
+
const templateComponents = (await this.readdir("lib")).filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts")).filter((name) => fs.existsSync(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`));
|
|
657
723
|
return templateComponents;
|
|
658
724
|
}
|
|
659
725
|
async getViewsSourceCode() {
|
|
@@ -684,10 +750,35 @@ class SysExecutor extends Executor {
|
|
|
684
750
|
const modules = await this.getModules();
|
|
685
751
|
return modules.map((module) => this.getLocalFile(`lib/${module}/${module}.constant.ts`));
|
|
686
752
|
}
|
|
753
|
+
async getConstantFilesWithLibs() {
|
|
754
|
+
const config = await this.getConfig();
|
|
755
|
+
const sysContantFiles = await this.getConstantFiles();
|
|
756
|
+
const sysScalarConstantFiles = await this.getScalarConstantFiles();
|
|
757
|
+
const libConstantFiles = await Promise.all(
|
|
758
|
+
config.libs.map(async (lib) => [
|
|
759
|
+
...await LibExecutor.from(this, lib).getConstantFiles(),
|
|
760
|
+
...await LibExecutor.from(this, lib).getScalarConstantFiles()
|
|
761
|
+
])
|
|
762
|
+
);
|
|
763
|
+
return [...sysContantFiles, ...sysScalarConstantFiles, ...libConstantFiles.flat()];
|
|
764
|
+
}
|
|
687
765
|
async getDictionaryFiles() {
|
|
688
766
|
const modules = await this.getModules();
|
|
689
767
|
return modules.map((module) => this.getLocalFile(`lib/${module}/${module}.dictionary.ts`));
|
|
690
768
|
}
|
|
769
|
+
async applyTemplate(options) {
|
|
770
|
+
const dict = {
|
|
771
|
+
...options.dict ?? {},
|
|
772
|
+
...Object.fromEntries(
|
|
773
|
+
Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)])
|
|
774
|
+
)
|
|
775
|
+
};
|
|
776
|
+
const akanConfig = await this.getConfig();
|
|
777
|
+
const scanResult = await this.scan({ akanConfig });
|
|
778
|
+
const fileContents = await this._applyTemplate({ ...options, scanResult, dict });
|
|
779
|
+
await this.scan({ akanConfig });
|
|
780
|
+
return fileContents;
|
|
781
|
+
}
|
|
691
782
|
setTsPaths() {
|
|
692
783
|
this.workspace.setTsPaths(this.type, this.name);
|
|
693
784
|
return this;
|
|
File without changes
|
package/esm/src/index.js
CHANGED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Logger } from "@akanjs/common";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { ESLint } from "eslint";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
class Linter {
|
|
7
|
+
#logger = new Logger("Linter");
|
|
8
|
+
#eslint;
|
|
9
|
+
lintRoot;
|
|
10
|
+
constructor(cwdPath) {
|
|
11
|
+
this.lintRoot = this.#findEslintRootPath(cwdPath);
|
|
12
|
+
this.#eslint = new ESLint({ cwd: this.lintRoot, errorOnUnmatchedPattern: false });
|
|
13
|
+
}
|
|
14
|
+
#findEslintRootPath(dir) {
|
|
15
|
+
const configPath = path.join(dir, "eslint.config.ts");
|
|
16
|
+
if (fs.existsSync(configPath))
|
|
17
|
+
return dir;
|
|
18
|
+
const parentDir = path.dirname(dir);
|
|
19
|
+
return this.#findEslintRootPath(parentDir);
|
|
20
|
+
}
|
|
21
|
+
async lint(filePath, { fix = false, dryRun = false } = {}) {
|
|
22
|
+
if (fix)
|
|
23
|
+
return await this.fixFile(filePath, dryRun);
|
|
24
|
+
return await this.lintFile(filePath);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Lint a single file using ESLint
|
|
28
|
+
* @param filePath - Path to the file to lint
|
|
29
|
+
* @returns Array of ESLint results
|
|
30
|
+
*/
|
|
31
|
+
async lintFile(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath))
|
|
33
|
+
throw new Error(`File not found: ${filePath}`);
|
|
34
|
+
const isIgnored = await this.#eslint.isPathIgnored(filePath);
|
|
35
|
+
if (isIgnored) {
|
|
36
|
+
this.#logger.warn(`File ${filePath} is ignored by ESLint configuration`);
|
|
37
|
+
return { fixed: false, results: [], errors: [], warnings: [] };
|
|
38
|
+
}
|
|
39
|
+
const results = await this.#eslint.lintFiles([filePath]);
|
|
40
|
+
const errors = results.flatMap((result) => result.messages.filter((message) => message.severity === 2));
|
|
41
|
+
const warnings = results.flatMap((result) => result.messages.filter((message) => message.severity === 1));
|
|
42
|
+
return { fixed: false, results, errors, warnings };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Format lint results for console output
|
|
46
|
+
* @param results - Array of ESLint results
|
|
47
|
+
* @returns Formatted string
|
|
48
|
+
*/
|
|
49
|
+
formatLintResults(results) {
|
|
50
|
+
if (results.length === 0)
|
|
51
|
+
return "No files to lint";
|
|
52
|
+
const output = [];
|
|
53
|
+
let totalErrors = 0;
|
|
54
|
+
let totalWarnings = 0;
|
|
55
|
+
results.forEach((result) => {
|
|
56
|
+
totalErrors += result.errorCount;
|
|
57
|
+
totalWarnings += result.warningCount;
|
|
58
|
+
if (result.messages.length > 0) {
|
|
59
|
+
output.push(`
|
|
60
|
+
${chalk.cyan(result.filePath)}`);
|
|
61
|
+
let sourceLines = [];
|
|
62
|
+
if (fs.existsSync(result.filePath)) {
|
|
63
|
+
try {
|
|
64
|
+
const sourceContent = fs.readFileSync(result.filePath, "utf8");
|
|
65
|
+
sourceLines = sourceContent.split("\n");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
result.messages.forEach((message) => {
|
|
70
|
+
const type = message.severity === 2 ? "error" : "warning";
|
|
71
|
+
const typeColor = message.severity === 2 ? chalk.red : chalk.yellow;
|
|
72
|
+
const icon = message.severity === 2 ? "\u274C" : "\u26A0\uFE0F";
|
|
73
|
+
const ruleInfo = message.ruleId ? chalk.dim(` (${message.ruleId})`) : "";
|
|
74
|
+
output.push(`
|
|
75
|
+
${icon} ${typeColor(type)}: ${message.message}${ruleInfo}`);
|
|
76
|
+
output.push(` ${chalk.gray("at")} ${result.filePath}:${chalk.bold(`${message.line}:${message.column}`)}`);
|
|
77
|
+
if (sourceLines.length > 0 && message.line <= sourceLines.length) {
|
|
78
|
+
const sourceLine = sourceLines[message.line - 1];
|
|
79
|
+
const lineNumber = message.line.toString().padStart(5, " ");
|
|
80
|
+
output.push(`
|
|
81
|
+
${chalk.dim(lineNumber + " |")} ${sourceLine}`);
|
|
82
|
+
const underlinePrefix = " ".repeat(message.column - 1);
|
|
83
|
+
const underlineLength = message.endColumn ? message.endColumn - message.column : 1;
|
|
84
|
+
const underline = "^".repeat(Math.max(1, underlineLength));
|
|
85
|
+
output.push(`${chalk.dim(" ".repeat(lineNumber.length) + " |")} ${underlinePrefix}${typeColor(underline)}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (totalErrors === 0 && totalWarnings === 0)
|
|
91
|
+
return chalk.bold("\u2705 No ESLint errors or warnings found");
|
|
92
|
+
const errorText = totalErrors > 0 ? chalk.red(`${totalErrors} error(s)`) : "0 errors";
|
|
93
|
+
const warningText = totalWarnings > 0 ? chalk.yellow(`${totalWarnings} warning(s)`) : "0 warnings";
|
|
94
|
+
const summary = [`
|
|
95
|
+
${errorText}, ${warningText} found`];
|
|
96
|
+
return summary.concat(output).join("\n");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get detailed lint information
|
|
100
|
+
* @param filePath - Path to the file to lint
|
|
101
|
+
* @returns Object containing detailed lint information
|
|
102
|
+
*/
|
|
103
|
+
async getDetailedLintInfo(filePath) {
|
|
104
|
+
const { results } = await this.lintFile(filePath);
|
|
105
|
+
const details = results.flatMap(
|
|
106
|
+
(result) => result.messages.map((message) => ({
|
|
107
|
+
line: message.line,
|
|
108
|
+
column: message.column,
|
|
109
|
+
message: message.message,
|
|
110
|
+
ruleId: message.ruleId,
|
|
111
|
+
severity: message.severity === 2 ? "error" : "warning",
|
|
112
|
+
fix: message.fix,
|
|
113
|
+
suggestions: message.suggestions
|
|
114
|
+
}))
|
|
115
|
+
);
|
|
116
|
+
const stats = results.reduce(
|
|
117
|
+
(acc, result) => ({
|
|
118
|
+
errorCount: acc.errorCount + result.errorCount,
|
|
119
|
+
warningCount: acc.warningCount + result.warningCount,
|
|
120
|
+
fixableErrorCount: acc.fixableErrorCount + result.fixableErrorCount,
|
|
121
|
+
fixableWarningCount: acc.fixableWarningCount + result.fixableWarningCount
|
|
122
|
+
}),
|
|
123
|
+
{ errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 }
|
|
124
|
+
);
|
|
125
|
+
return { results, details, stats };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if a file has lint errors
|
|
129
|
+
* @param filePath - Path to the file to check
|
|
130
|
+
* @returns true if there are no errors, false otherwise
|
|
131
|
+
*/
|
|
132
|
+
async hasNoLintErrors(filePath) {
|
|
133
|
+
try {
|
|
134
|
+
const { results } = await this.lintFile(filePath);
|
|
135
|
+
return results.every((result) => result.errorCount === 0);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get only error messages (excluding warnings)
|
|
142
|
+
* @param filePath - Path to the file to lint
|
|
143
|
+
* @returns Array of error messages
|
|
144
|
+
*/
|
|
145
|
+
async getErrors(filePath) {
|
|
146
|
+
const { results } = await this.lintFile(filePath);
|
|
147
|
+
return results.flatMap((result) => result.messages.filter((message) => message.severity === 2));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get only warning messages
|
|
151
|
+
* @param filePath - Path to the file to lint
|
|
152
|
+
* @returns Array of warning messages
|
|
153
|
+
*/
|
|
154
|
+
async getWarnings(filePath) {
|
|
155
|
+
const { results } = await this.lintFile(filePath);
|
|
156
|
+
return results.flatMap((result) => result.messages.filter((message) => message.severity === 1));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fix lint errors automatically
|
|
160
|
+
* @param filePath - Path to the file to fix
|
|
161
|
+
* @param dryRun - If true, returns the fixed content without writing to file
|
|
162
|
+
* @returns Fixed content and remaining issues
|
|
163
|
+
*/
|
|
164
|
+
async fixFile(filePath, dryRun = false) {
|
|
165
|
+
if (!fs.existsSync(filePath))
|
|
166
|
+
throw new Error(`File not found: ${filePath}`);
|
|
167
|
+
const eslint = new ESLint({ cwd: this.lintRoot, fix: true });
|
|
168
|
+
const results = await eslint.lintFiles([filePath]);
|
|
169
|
+
const errors = results.flatMap((result) => result.messages.filter((message) => message.severity === 2));
|
|
170
|
+
const warnings = results.flatMap((result) => result.messages.filter((message) => message.severity === 1));
|
|
171
|
+
if (!dryRun)
|
|
172
|
+
await ESLint.outputFixes(results);
|
|
173
|
+
const fixedResult = results[0];
|
|
174
|
+
return { fixed: fixedResult.output !== void 0, output: fixedResult.output, results, errors, warnings };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get ESLint configuration for a file
|
|
178
|
+
* @param filePath - Path to the file
|
|
179
|
+
* @returns ESLint configuration object
|
|
180
|
+
*/
|
|
181
|
+
async getConfigForFile(filePath) {
|
|
182
|
+
const eslint = new ESLint();
|
|
183
|
+
const config = await eslint.calculateConfigForFile(filePath);
|
|
184
|
+
return config;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get rules that are causing errors in a file
|
|
188
|
+
* @param filePath - Path to the file to check
|
|
189
|
+
* @returns Object mapping rule IDs to their error counts
|
|
190
|
+
*/
|
|
191
|
+
async getProblematicRules(filePath) {
|
|
192
|
+
const { results } = await this.lintFile(filePath);
|
|
193
|
+
const ruleCounts = {};
|
|
194
|
+
results.forEach((result) => {
|
|
195
|
+
result.messages.forEach((message) => {
|
|
196
|
+
if (message.ruleId)
|
|
197
|
+
ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
return ruleCounts;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
Linter
|
|
205
|
+
};
|