@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/index.ts CHANGED
@@ -6,11 +6,11 @@ export * from "./applicationBuildRunner";
6
6
  export * from "./applicationReleasePackager";
7
7
  export * from "./applicationTestPreload";
8
8
  export * from "./artifact";
9
- export * from "./auth";
10
9
  export * from "./builder";
11
10
  export * from "./capacitorApp";
11
+ export * from "./cloud";
12
+ export * from "./cloud";
12
13
  export * from "./commandDecorators";
13
- export * from "./constants";
14
14
  export * from "./createTunnel";
15
15
  export * from "./dependencyScanner";
16
16
  export * from "./executors";
@@ -35,4 +35,3 @@ export * from "./types";
35
35
  export * from "./ui";
36
36
  export * from "./uploadRelease";
37
37
  export * from "./useStdoutDimensions";
38
- export * from "./cloud";
package/linter.ts CHANGED
@@ -1,60 +1,257 @@
1
+ import { spawn } from "node:child_process";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
- import * as path from "node:path";
3
- import { Logger } from "akanjs/common";
3
+ import path from "node:path";
4
4
  import chalk from "chalk";
5
- // import { ESLint, type Linter as ESLintLinter } from "eslint";
5
+
6
+ type BiomeSeverity = "error" | "warning" | "information" | "hint";
7
+
8
+ interface BiomePosition {
9
+ line: number;
10
+ column: number;
11
+ }
12
+
13
+ interface BiomeDiagnostic {
14
+ severity: BiomeSeverity;
15
+ message: string;
16
+ category?: string;
17
+ location?: {
18
+ path?: string;
19
+ start?: BiomePosition;
20
+ end?: BiomePosition;
21
+ };
22
+ }
23
+
24
+ interface BiomeReport {
25
+ summary?: {
26
+ changed?: number;
27
+ errors?: number;
28
+ warnings?: number;
29
+ infos?: number;
30
+ };
31
+ diagnostics?: BiomeDiagnostic[];
32
+ }
33
+
34
+ interface LintMessage {
35
+ line: number;
36
+ column: number;
37
+ endLine?: number;
38
+ endColumn?: number;
39
+ message: string;
40
+ ruleId: string | null;
41
+ severity: 1 | 2;
42
+ }
43
+
44
+ interface LintResult {
45
+ filePath: string;
46
+ messages: LintMessage[];
47
+ errorCount: number;
48
+ warningCount: number;
49
+ fixableErrorCount: number;
50
+ fixableWarningCount: number;
51
+ }
52
+
53
+ interface LintResponse {
54
+ fixed: boolean;
55
+ output?: string;
56
+ results: LintResult[];
57
+ errors: LintMessage[];
58
+ warnings: LintMessage[];
59
+ }
6
60
 
7
61
  export class Linter {
8
- #logger = new Logger("Linter");
9
- // #eslint: any;
10
62
  lintRoot: string;
63
+ #biomeBin: string;
11
64
 
12
65
  constructor(cwdPath: string) {
13
- this.lintRoot = this.#findEslintRootPath(cwdPath);
14
- // this.#eslint = new ESLint({ cwd: this.lintRoot, errorOnUnmatchedPattern: false });
66
+ this.lintRoot = this.#findBiomeRootPath(cwdPath);
67
+ const localBiomeBin = path.join(this.lintRoot, "node_modules/.bin/biome");
68
+ this.#biomeBin = existsSync(localBiomeBin) ? localBiomeBin : "biome";
15
69
  }
16
- #findEslintRootPath(dir: string): string {
17
- const configPath = path.join(dir, "eslint.config.ts");
70
+
71
+ #findBiomeRootPath(dir: string): string {
72
+ const configPath = path.join(dir, "biome.json");
18
73
  if (existsSync(configPath)) return dir;
19
74
  const parentDir = path.dirname(dir);
20
- return this.#findEslintRootPath(parentDir);
75
+ if (parentDir === dir) throw new Error(`biome.json not found from ${dir}`);
76
+ return this.#findBiomeRootPath(parentDir);
77
+ }
78
+
79
+ #toBiomePath(filePath: string): string {
80
+ const relativePath = path.relative(this.lintRoot, filePath);
81
+ if (!relativePath.startsWith("..") && !path.isAbsolute(relativePath))
82
+ return relativePath;
83
+ return filePath;
84
+ }
85
+
86
+ #resolveFilePath(filePath: string): string {
87
+ return path.isAbsolute(filePath)
88
+ ? filePath
89
+ : path.join(this.lintRoot, filePath);
90
+ }
91
+
92
+ async #runBiome(args: string[], input?: string) {
93
+ return await new Promise<{
94
+ stdout: string;
95
+ stderr: string;
96
+ code: number | null;
97
+ }>((resolve, reject) => {
98
+ const proc = spawn(this.#biomeBin, args, {
99
+ cwd: this.lintRoot,
100
+ stdio: ["pipe", "pipe", "pipe"],
101
+ });
102
+ let stdout = "";
103
+ let stderr = "";
104
+
105
+ proc.stdout.on("data", (data: Buffer) => {
106
+ stdout += data.toString();
107
+ });
108
+ proc.stderr.on("data", (data: Buffer) => {
109
+ stderr += data.toString();
110
+ });
111
+ proc.on("error", reject);
112
+ proc.on("close", (code) => resolve({ stdout, stderr, code }));
113
+ proc.stdin.end(input);
114
+ });
115
+ }
116
+
117
+ #parseBiomeReport(output: string): BiomeReport {
118
+ const jsonStart = output.indexOf("{");
119
+ const jsonEnd = output.lastIndexOf("}");
120
+ if (jsonStart === -1 || jsonEnd === -1 || jsonEnd < jsonStart)
121
+ throw new Error(output.trim() || "No Biome JSON output");
122
+ return JSON.parse(output.slice(jsonStart, jsonEnd + 1)) as BiomeReport;
21
123
  }
22
- async lint(filePath: string, { fix = false, dryRun = false }: { fix?: boolean; dryRun?: boolean } = {}) {
124
+
125
+ #diagnosticFilePath(diagnostic: BiomeDiagnostic, fallbackFilePath: string) {
126
+ const diagnosticPath = diagnostic.location?.path;
127
+ if (!diagnosticPath) return fallbackFilePath;
128
+ return path.isAbsolute(diagnosticPath)
129
+ ? diagnosticPath
130
+ : path.join(this.lintRoot, diagnosticPath);
131
+ }
132
+
133
+ #createLintMessage(diagnostic: BiomeDiagnostic): LintMessage {
134
+ const start = diagnostic.location?.start;
135
+ const end = diagnostic.location?.end;
136
+ return {
137
+ line: Math.max(1, start?.line ?? 1),
138
+ column: Math.max(1, start?.column ?? 1),
139
+ endLine: end?.line,
140
+ endColumn: end?.column,
141
+ message: diagnostic.message,
142
+ ruleId: diagnostic.category ?? null,
143
+ severity: diagnostic.severity === "error" ? 2 : 1,
144
+ };
145
+ }
146
+
147
+ #toLintResults(report: BiomeReport, filePath: string): LintResult[] {
148
+ const resultsByPath = new Map<string, LintResult>();
149
+
150
+ for (const diagnostic of report.diagnostics ?? []) {
151
+ if (diagnostic.severity !== "error" && diagnostic.severity !== "warning")
152
+ continue;
153
+ const diagnosticFilePath = this.#diagnosticFilePath(diagnostic, filePath);
154
+ const result =
155
+ resultsByPath.get(diagnosticFilePath) ??
156
+ ({
157
+ filePath: diagnosticFilePath,
158
+ messages: [],
159
+ errorCount: 0,
160
+ warningCount: 0,
161
+ fixableErrorCount: 0,
162
+ fixableWarningCount: 0,
163
+ } satisfies LintResult);
164
+ const message = this.#createLintMessage(diagnostic);
165
+ result.messages.push(message);
166
+ if (message.severity === 2) result.errorCount += 1;
167
+ else result.warningCount += 1;
168
+ resultsByPath.set(diagnosticFilePath, result);
169
+ }
170
+
171
+ return [
172
+ resultsByPath.get(filePath) ??
173
+ ({
174
+ filePath,
175
+ messages: [],
176
+ errorCount: 0,
177
+ warningCount: 0,
178
+ fixableErrorCount: 0,
179
+ fixableWarningCount: 0,
180
+ } satisfies LintResult),
181
+ ...[...resultsByPath.entries()]
182
+ .filter(([resultPath]) => resultPath !== filePath)
183
+ .map(([, result]) => result),
184
+ ];
185
+ }
186
+
187
+ #splitMessages(results: LintResult[]) {
188
+ const messages = results.flatMap((result) => result.messages);
189
+ return {
190
+ errors: messages.filter((message) => message.severity === 2),
191
+ warnings: messages.filter((message) => message.severity === 1),
192
+ };
193
+ }
194
+
195
+ async #checkFile(
196
+ filePath: string,
197
+ { write = false }: { write?: boolean } = {},
198
+ ): Promise<LintResponse> {
199
+ const originalContent = existsSync(filePath)
200
+ ? readFileSync(filePath, "utf8")
201
+ : "";
202
+ const { stdout, stderr } = await this.#runBiome([
203
+ "check",
204
+ ...(write ? ["--write"] : []),
205
+ "--reporter=json",
206
+ "--max-diagnostics=none",
207
+ "--no-errors-on-unmatched",
208
+ "--config-path",
209
+ path.join(this.lintRoot, "biome.json"),
210
+ this.#toBiomePath(filePath),
211
+ ]);
212
+ const report = this.#parseBiomeReport(stdout || stderr);
213
+ const results = this.#toLintResults(report, filePath);
214
+ const { errors, warnings } = this.#splitMessages(results);
215
+ const output =
216
+ write && existsSync(filePath)
217
+ ? readFileSync(filePath, "utf8")
218
+ : undefined;
219
+
220
+ return {
221
+ fixed: write && output !== originalContent,
222
+ output,
223
+ results,
224
+ errors,
225
+ warnings,
226
+ };
227
+ }
228
+
229
+ async lint(
230
+ filePath: string,
231
+ { fix = false, dryRun = false }: { fix?: boolean; dryRun?: boolean } = {},
232
+ ) {
23
233
  if (fix) return await this.fixFile(filePath, dryRun);
24
234
  return await this.lintFile(filePath);
25
235
  }
26
236
 
27
237
  /**
28
- * Lint a single file using ESLint
238
+ * Lint a single file using Biome.
29
239
  * @param filePath - Path to the file to lint
30
- * @returns Array of ESLint results
240
+ * @returns Array of Biome results in the legacy lint result shape
31
241
  */
32
- async lintFile(filePath: string): Promise<{
33
- fixed: boolean;
34
- output?: string;
35
- results: any[]; // ESLint.LintResult[];
36
- errors: any[]; // ESLintLinter.LintMessage[];
37
- warnings: any[]; // ESLintLinter.LintMessage[];
38
- }> {
39
- if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
40
- // const isIgnored = await this.#eslint.isPathIgnored(filePath);
41
- // if (isIgnored) {
42
- // this.#logger.warn(`File ${filePath} is ignored by ESLint configuration`);
43
- // return { fixed: false, results: [], errors: [], warnings: [] };
44
- // }
45
- // const results = await this.#eslint.lintFiles([filePath]);
46
- // const errors = results.flatMap((result) => result.messages.filter((message) => message.severity === 2));
47
- // const warnings = results.flatMap((result) => result.messages.filter((message) => message.severity === 1));
48
- return { fixed: false, results: [], errors: [], warnings: [] };
242
+ async lintFile(filePath: string): Promise<LintResponse> {
243
+ const resolvedFilePath = this.#resolveFilePath(filePath);
244
+ if (!existsSync(resolvedFilePath))
245
+ throw new Error(`File not found: ${filePath}`);
246
+ return await this.#checkFile(resolvedFilePath);
49
247
  }
50
248
 
51
249
  /**
52
250
  * Format lint results for console output
53
- * @param results - Array of ESLint results
251
+ * @param results - Array of Biome results
54
252
  * @returns Formatted string
55
253
  */
56
- formatLintResults(results: any[]): string {
57
- // ESLint.LintResult[]
254
+ formatLintResults(results: LintResult[]): string {
58
255
  if (results.length === 0) return "No files to lint";
59
256
 
60
257
  const output: string[] = [];
@@ -68,25 +265,30 @@ export class Linter {
68
265
  if (result.messages.length > 0) {
69
266
  output.push(`\n${chalk.cyan(result.filePath)}`);
70
267
 
71
- // Read source file once
72
268
  let sourceLines: string[] = [];
73
269
  if (existsSync(result.filePath)) {
74
270
  try {
75
271
  const sourceContent = readFileSync(result.filePath, "utf8");
76
272
  sourceLines = sourceContent.split("\n");
77
- } catch (error) {
273
+ } catch {
78
274
  // Ignore read errors
79
275
  }
80
276
  }
81
277
 
82
- result.messages.forEach((message: any) => {
278
+ result.messages.forEach((message) => {
83
279
  const type = message.severity === 2 ? "error" : "warning";
84
280
  const typeColor = message.severity === 2 ? chalk.red : chalk.yellow;
85
- const icon = message.severity === 2 ? "" : "⚠️";
86
- const ruleInfo = message.ruleId ? chalk.dim(` (${message.ruleId})`) : "";
281
+ const icon = message.severity === 2 ? "x" : "!";
282
+ const ruleInfo = message.ruleId
283
+ ? chalk.dim(` (${message.ruleId})`)
284
+ : "";
87
285
 
88
- output.push(`\n ${icon} ${typeColor(type)}: ${message.message}${ruleInfo}`);
89
- output.push(` ${chalk.gray("at")} ${result.filePath}:${chalk.bold(`${message.line}:${message.column}`)}`);
286
+ output.push(
287
+ `\n ${icon} ${typeColor(type)}: ${message.message}${ruleInfo}`,
288
+ );
289
+ output.push(
290
+ ` ${chalk.gray("at")} ${result.filePath}:${chalk.bold(`${message.line}:${message.column}`)}`,
291
+ );
90
292
 
91
293
  // Show source line with underline
92
294
  if (sourceLines.length > 0 && message.line <= sourceLines.length) {
@@ -97,19 +299,28 @@ export class Linter {
97
299
 
98
300
  // Create underline
99
301
  const underlinePrefix = " ".repeat(message.column - 1);
100
- const underlineLength = message.endColumn ? message.endColumn - message.column : 1;
302
+ const underlineLength = message.endColumn
303
+ ? message.endColumn - message.column
304
+ : 1;
101
305
  const underline = "^".repeat(Math.max(1, underlineLength));
102
306
 
103
- output.push(`${chalk.dim(`${" ".repeat(lineNumber.length)} |`)} ${underlinePrefix}${typeColor(underline)}`);
307
+ output.push(
308
+ `${chalk.dim(`${" ".repeat(lineNumber.length)} |`)} ${underlinePrefix}${typeColor(underline)}`,
309
+ );
104
310
  }
105
311
  });
106
312
  }
107
313
  });
108
314
 
109
- if (totalErrors === 0 && totalWarnings === 0) return chalk.bold("✅ No ESLint errors or warnings found");
315
+ if (totalErrors === 0 && totalWarnings === 0)
316
+ return chalk.bold("No Biome errors or warnings found");
110
317
 
111
- const errorText = totalErrors > 0 ? chalk.red(`${totalErrors} error(s)`) : "0 errors";
112
- const warningText = totalWarnings > 0 ? chalk.yellow(`${totalWarnings} warning(s)`) : "0 warnings";
318
+ const errorText =
319
+ totalErrors > 0 ? chalk.red(`${totalErrors} error(s)`) : "0 errors";
320
+ const warningText =
321
+ totalWarnings > 0
322
+ ? chalk.yellow(`${totalWarnings} warning(s)`)
323
+ : "0 warnings";
113
324
  const summary = [`\n${errorText}, ${warningText} found`];
114
325
 
115
326
  return summary.concat(output).join("\n");
@@ -121,24 +332,13 @@ export class Linter {
121
332
  * @returns Object containing detailed lint information
122
333
  */
123
334
  async getDetailedLintInfo(filePath: string): Promise<{
124
- results: any[]; // ESLint.LintResult[];
335
+ results: LintResult[];
125
336
  details: {
126
337
  line: number;
127
338
  column: number;
128
339
  message: string;
129
340
  ruleId: string | null;
130
341
  severity: "error" | "warning";
131
- fix?: {
132
- range: [number, number];
133
- text: string;
134
- };
135
- suggestions?: {
136
- desc: string;
137
- fix: {
138
- range: [number, number];
139
- text: string;
140
- };
141
- }[];
142
342
  }[];
143
343
  stats: {
144
344
  errorCount: number;
@@ -150,14 +350,13 @@ export class Linter {
150
350
  const { results } = await this.lintFile(filePath);
151
351
 
152
352
  const details = results.flatMap((result) =>
153
- result.messages.map((message: any) => ({
353
+ result.messages.map((message) => ({
154
354
  line: message.line,
155
355
  column: message.column,
156
356
  message: message.message,
157
357
  ruleId: message.ruleId,
158
- severity: message.severity === 2 ? ("error" as const) : ("warning" as const),
159
- fix: message.fix,
160
- suggestions: message.suggestions,
358
+ severity:
359
+ message.severity === 2 ? ("error" as const) : ("warning" as const),
161
360
  })),
162
361
  );
163
362
 
@@ -166,9 +365,15 @@ export class Linter {
166
365
  errorCount: acc.errorCount + result.errorCount,
167
366
  warningCount: acc.warningCount + result.warningCount,
168
367
  fixableErrorCount: acc.fixableErrorCount + result.fixableErrorCount,
169
- fixableWarningCount: acc.fixableWarningCount + result.fixableWarningCount,
368
+ fixableWarningCount:
369
+ acc.fixableWarningCount + result.fixableWarningCount,
170
370
  }),
171
- { errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 },
371
+ {
372
+ errorCount: 0,
373
+ warningCount: 0,
374
+ fixableErrorCount: 0,
375
+ fixableWarningCount: 0,
376
+ },
172
377
  );
173
378
 
174
379
  return { results, details, stats };
@@ -183,7 +388,7 @@ export class Linter {
183
388
  try {
184
389
  const { results } = await this.lintFile(filePath);
185
390
  return results.every((result) => result.errorCount === 0);
186
- } catch (error) {
391
+ } catch {
187
392
  return false;
188
393
  }
189
394
  }
@@ -193,10 +398,11 @@ export class Linter {
193
398
  * @param filePath - Path to the file to lint
194
399
  * @returns Array of error messages
195
400
  */
196
- async getErrors(filePath: string): Promise<any[]> {
197
- // ESLintLinter.LintMessage[]
401
+ async getErrors(filePath: string): Promise<LintMessage[]> {
198
402
  const { results } = await this.lintFile(filePath);
199
- return results.flatMap((result) => result.messages.filter((message: any) => message.severity === 2));
403
+ return results.flatMap((result) =>
404
+ result.messages.filter((message) => message.severity === 2),
405
+ );
200
406
  }
201
407
 
202
408
  /**
@@ -204,10 +410,11 @@ export class Linter {
204
410
  * @param filePath - Path to the file to lint
205
411
  * @returns Array of warning messages
206
412
  */
207
- async getWarnings(filePath: string): Promise<any[]> {
208
- // ESLintLinter.LintMessage[]
413
+ async getWarnings(filePath: string): Promise<LintMessage[]> {
209
414
  const { results } = await this.lintFile(filePath);
210
- return results.flatMap((result) => result.messages.filter((message: any) => message.severity === 1));
415
+ return results.flatMap((result) =>
416
+ result.messages.filter((message) => message.severity === 1),
417
+ );
211
418
  }
212
419
 
213
420
  /**
@@ -216,39 +423,42 @@ export class Linter {
216
423
  * @param dryRun - If true, returns the fixed content without writing to file
217
424
  * @returns Fixed content and remaining issues
218
425
  */
219
- async fixFile(
220
- filePath: string,
221
- dryRun = false,
222
- ): Promise<{
223
- fixed: boolean;
224
- output?: string;
225
- results: any[]; // ESLint.LintResult[];
226
- errors: any[]; // ESLintLinter.LintMessage[];
227
- warnings: any[]; // ESLintLinter.LintMessage[];
228
- }> {
229
- if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
426
+ async fixFile(filePath: string, dryRun = false): Promise<LintResponse> {
427
+ const resolvedFilePath = this.#resolveFilePath(filePath);
428
+ if (!existsSync(resolvedFilePath))
429
+ throw new Error(`File not found: ${filePath}`);
230
430
 
231
- // const eslint = new ESLint({ cwd: this.lintRoot, fix: true });
232
- // const results = await eslint.lintFiles([filePath]);
233
- // const errors = results.flatMap((result) => result.messages.filter((message) => message.severity === 2));
234
- // const warnings = results.flatMap((result) => result.messages.filter((message) => message.severity === 1));
235
- // if (!dryRun) await ESLint.outputFixes(results);
431
+ if (!dryRun)
432
+ return await this.#checkFile(resolvedFilePath, { write: true });
236
433
 
237
- // const fixedResult = results[0];
238
- // return { fixed: fixedResult.output !== undefined, output: fixedResult.output, results, errors, warnings };
239
- return { fixed: false, output: undefined, results: [], errors: [], warnings: [] };
434
+ const source = readFileSync(resolvedFilePath, "utf8");
435
+ const { stdout } = await this.#runBiome(
436
+ [
437
+ "check",
438
+ "--write",
439
+ "--config-path",
440
+ path.join(this.lintRoot, "biome.json"),
441
+ "--stdin-file-path",
442
+ this.#toBiomePath(resolvedFilePath),
443
+ ],
444
+ source,
445
+ );
446
+ const lintResult = await this.lintFile(resolvedFilePath);
447
+ return { ...lintResult, fixed: stdout !== source, output: stdout };
240
448
  }
241
449
 
242
450
  /**
243
- * Get ESLint configuration for a file
451
+ * Get Biome configuration for a file
244
452
  * @param filePath - Path to the file
245
- * @returns ESLint configuration object
453
+ * @returns Biome configuration object
246
454
  */
247
455
  async getConfigForFile(filePath: string): Promise<unknown> {
248
- // const eslint = new ESLint();
249
- // const config = (await eslint.calculateConfigForFile(filePath)) as unknown;
250
- // return config;
251
- return {};
456
+ const resolvedFilePath = this.#resolveFilePath(filePath);
457
+ if (!existsSync(resolvedFilePath))
458
+ throw new Error(`File not found: ${filePath}`);
459
+ return JSON.parse(
460
+ readFileSync(path.join(this.lintRoot, "biome.json"), "utf8"),
461
+ ) as unknown;
252
462
  }
253
463
 
254
464
  /**
@@ -261,8 +471,9 @@ export class Linter {
261
471
  const ruleCounts: Record<string, number> = {};
262
472
 
263
473
  results.forEach((result) => {
264
- result.messages.forEach((message: any) => {
265
- if (message.ruleId) ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
474
+ result.messages.forEach((message) => {
475
+ if (message.ruleId)
476
+ ruleCounts[message.ruleId] = (ruleCounts[message.ruleId] || 0) + 1;
266
477
  });
267
478
  });
268
479
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "2.1.1-rc.1",
3
+ "version": "2.1.1",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,7 +32,7 @@
32
32
  "@langchain/openai": "^1.4.6",
33
33
  "@tailwindcss/node": "^4.3.0",
34
34
  "@trapezedev/project": "^7.1.4",
35
- "akanjs": "2.1.1-rc.1",
35
+ "akanjs": "2.1.1",
36
36
  "chalk": "^5.6.2",
37
37
  "commander": "^14.0.3",
38
38
  "daisyui": "^5.5.20",
package/prompter.ts CHANGED
@@ -11,7 +11,10 @@ interface FileUpdateRequestProps {
11
11
  export class Prompter {
12
12
  static async #getGuidelineRoot() {
13
13
  const dirname = getDirname(import.meta.url);
14
- const candidates = [`${dirname}/guidelines`, `${dirname}/../cli/guidelines`];
14
+ const candidates = [
15
+ `${dirname}/guidelines`,
16
+ `${dirname}/../cli/guidelines`,
17
+ ];
15
18
  for (const candidate of candidates) {
16
19
  try {
17
20
  await fsPromise.access(candidate);
@@ -25,8 +28,13 @@ export class Prompter {
25
28
 
26
29
  static async selectGuideline() {
27
30
  const guidelineRoot = await Prompter.#getGuidelineRoot();
28
- const guideNames = (await fsPromise.readdir(guidelineRoot)).filter((name) => !name.startsWith("_"));
29
- return await select({ message: "Select a guideline", choices: guideNames.map((name) => ({ name, value: name })) });
31
+ const guideNames = (await fsPromise.readdir(guidelineRoot)).filter(
32
+ (name) => !name.startsWith("_"),
33
+ );
34
+ return await select({
35
+ message: "Select a guideline",
36
+ choices: guideNames.map((name) => ({ name, value: name })),
37
+ });
30
38
  }
31
39
  static async getGuideJson(guideName: string): Promise<GuideGenerateJson> {
32
40
  const guidelineRoot = await Prompter.#getGuidelineRoot();
@@ -41,7 +49,9 @@ export class Prompter {
41
49
  return content;
42
50
  }
43
51
  static async getUpdateRequest(guideName: string) {
44
- return await input({ message: `What do you want to update in ${guideName}?` });
52
+ return await input({
53
+ message: `What do you want to update in ${guideName}?`,
54
+ });
45
55
  }
46
56
 
47
57
  async makeTsFileUpdatePrompt({ context, request }: FileUpdateRequestProps) {
@@ -49,6 +59,9 @@ export class Prompter {
49
59
  ${await this.getDocumentation("framework")}
50
60
  Please understand the following background information, write code that meets the requirements, verify that it satisfies the validation conditions, and return the result.
51
61
 
62
+ # Code Style
63
+ - Use double quotes for all string literals in TypeScript/TSX code. Do not use single quotes.
64
+
52
65
  # Background Information
53
66
  \`\`\`markdown
54
67
  ${context}
@@ -1,6 +1,27 @@
1
+ import { Logger } from "akanjs/common";
1
2
  import { TypeChecker } from "../typeChecker";
2
3
 
3
4
  try {
5
+ const filePath = process.env.AKAN_TYPECHECK_FILE;
6
+ if (filePath) {
7
+ const cwdPath = process.env.AKAN_TYPECHECK_CWD;
8
+ if (!cwdPath) throw new Error("AKAN_TYPECHECK_CWD is required");
9
+
10
+ const typeChecker = new TypeChecker({ cwdPath } as never);
11
+ const { fileDiagnostics, fileErrors, fileWarnings } =
12
+ typeChecker.check(filePath);
13
+ const message = typeChecker.formatDiagnostics(fileDiagnostics);
14
+ Logger.rawLog(
15
+ JSON.stringify({
16
+ fileDiagnosticsCount: fileDiagnostics.length,
17
+ fileErrorsCount: fileErrors.length,
18
+ fileWarningsCount: fileWarnings.length,
19
+ message,
20
+ }),
21
+ );
22
+ process.exit(0);
23
+ }
24
+
4
25
  const configPath = process.env.AKAN_TYPECHECK_TSCONFIG;
5
26
  if (!configPath) throw new Error("AKAN_TYPECHECK_TSCONFIG is required");
6
27
  const result = TypeChecker.checkProject(configPath);