@akanjs/devkit 0.0.142 → 0.0.144

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.
@@ -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
+ };
@@ -0,0 +1,45 @@
1
+ import { select } from "@inquirer/prompts";
2
+ import fsPromise from "fs/promises";
3
+ class Prompter {
4
+ static async selectGuideline() {
5
+ const guideNames = (await fsPromise.readdir(`${__dirname}/src/guidelines`)).filter((name) => !name.startsWith("_"));
6
+ return await select({ message: "Select a guideline", choices: guideNames.map((name) => ({ name, value: name })) });
7
+ }
8
+ static async getGuideJson(guideName) {
9
+ const filePath = `${__dirname}/src/guidelines/${guideName}/${guideName}.generate.json`;
10
+ const guideJson = await fsPromise.readFile(filePath, "utf-8");
11
+ return JSON.parse(guideJson);
12
+ }
13
+ static async getInstruction(guideName) {
14
+ const filePath = `${__dirname}/src/guidelines/${guideName}/${guideName}.instruction.md`;
15
+ const content = await fsPromise.readFile(filePath, "utf-8");
16
+ return content;
17
+ }
18
+ async makeTsFileUpdatePrompt({ context, request }) {
19
+ return `You are a senior developer writing TypeScript-based programs using Akan.js, an in-house framework. Here's an overview of the Akan.js framework:
20
+ ${await this.getDocumentation("framework")}
21
+ Please understand the following background information, write code that meets the requirements, verify that it satisfies the validation conditions, and return the result.
22
+
23
+ # Background Information
24
+ \`\`\`markdown
25
+ ${context}
26
+ \`\`\`
27
+
28
+ # Requirements
29
+ \`\`\`markdown
30
+ ${request}
31
+ \`\`\`
32
+ `;
33
+ }
34
+ async getDocumentation(guideName) {
35
+ const filePath = `${__dirname}/src/guidelines/${guideName}/${guideName}.instruction.md`;
36
+ const document = await fsPromise.readFile(filePath, "utf-8");
37
+ return `\`\`\`markdown
38
+ ${document}
39
+ \`\`\`
40
+ `;
41
+ }
42
+ }
43
+ export {
44
+ Prompter
45
+ };
@@ -0,0 +1,170 @@
1
+ import chalk from "chalk";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as ts from "typescript";
5
+ class TypeChecker {
6
+ configPath;
7
+ configFile;
8
+ config;
9
+ constructor(executor) {
10
+ const configPath = this.#findConfigFile(executor.cwdPath);
11
+ if (!configPath)
12
+ throw new Error("No tsconfig.json found in the project");
13
+ this.configPath = configPath;
14
+ this.configFile = ts.readConfigFile(this.configPath, (fileName) => ts.sys.readFile(fileName));
15
+ const parsedConfig = ts.parseJsonConfigFileContent(
16
+ this.configFile.config,
17
+ ts.sys,
18
+ path.dirname(this.configPath),
19
+ void 0,
20
+ this.configPath
21
+ );
22
+ if (parsedConfig.errors.length > 0) {
23
+ const errorMessages = parsedConfig.errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, "\n")).join("\n");
24
+ throw new Error(`Error parsing tsconfig.json:
25
+ ${errorMessages}`);
26
+ }
27
+ this.config = parsedConfig;
28
+ }
29
+ /**
30
+ * Find tsconfig.json by walking up the directory tree
31
+ */
32
+ #findConfigFile(searchPath) {
33
+ return ts.findConfigFile(searchPath, (fileName) => ts.sys.fileExists(fileName), "tsconfig.json");
34
+ }
35
+ /**
36
+ * Type-check a single TypeScript file
37
+ * @param filePath - Path to the TypeScript file to check
38
+ * @returns Array of diagnostic messages
39
+ */
40
+ check(filePath) {
41
+ const program = ts.createProgram([filePath], this.config.options);
42
+ const diagnostics = [
43
+ ...program.getSemanticDiagnostics(),
44
+ ...program.getSyntacticDiagnostics(),
45
+ ...program.getDeclarationDiagnostics()
46
+ ];
47
+ const errors = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error);
48
+ const warnings = diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Warning);
49
+ return { diagnostics, errors, warnings };
50
+ }
51
+ /**
52
+ * Format diagnostics for console output
53
+ * @param diagnostics - Array of TypeScript diagnostics
54
+ * @returns Formatted string
55
+ */
56
+ formatDiagnostics(diagnostics) {
57
+ if (diagnostics.length === 0)
58
+ return chalk.bold("\u2705 No type errors found");
59
+ const output = [];
60
+ let errorCount = 0;
61
+ let warningCount = 0;
62
+ let suggestionCount = 0;
63
+ const diagnosticsByFile = /* @__PURE__ */ new Map();
64
+ diagnostics.forEach((diagnostic) => {
65
+ if (diagnostic.category === ts.DiagnosticCategory.Error)
66
+ errorCount++;
67
+ else if (diagnostic.category === ts.DiagnosticCategory.Warning)
68
+ warningCount++;
69
+ else if (diagnostic.category === ts.DiagnosticCategory.Suggestion)
70
+ suggestionCount++;
71
+ if (diagnostic.file) {
72
+ const fileName = diagnostic.file.fileName;
73
+ if (!diagnosticsByFile.has(fileName))
74
+ diagnosticsByFile.set(fileName, []);
75
+ const fileDiagnostics = diagnosticsByFile.get(fileName);
76
+ if (fileDiagnostics)
77
+ fileDiagnostics.push(diagnostic);
78
+ } else {
79
+ if (!diagnosticsByFile.has(""))
80
+ diagnosticsByFile.set("", []);
81
+ const fileDiagnostics = diagnosticsByFile.get("");
82
+ if (fileDiagnostics)
83
+ fileDiagnostics.push(diagnostic);
84
+ }
85
+ });
86
+ diagnosticsByFile.forEach((fileDiagnostics, fileName) => {
87
+ if (fileName)
88
+ output.push(`
89
+ ${chalk.cyan(fileName)}`);
90
+ fileDiagnostics.forEach((diagnostic) => {
91
+ const categoryText = diagnostic.category === ts.DiagnosticCategory.Error ? "error" : diagnostic.category === ts.DiagnosticCategory.Warning ? "warning" : "suggestion";
92
+ const categoryColor = diagnostic.category === ts.DiagnosticCategory.Error ? chalk.red : diagnostic.category === ts.DiagnosticCategory.Warning ? chalk.yellow : chalk.blue;
93
+ const icon = diagnostic.category === ts.DiagnosticCategory.Error ? "\u274C" : diagnostic.category === ts.DiagnosticCategory.Warning ? "\u26A0\uFE0F" : "\u{1F4A1}";
94
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
95
+ const tsCode = chalk.dim(`(TS${diagnostic.code})`);
96
+ if (diagnostic.file && diagnostic.start !== void 0) {
97
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
98
+ output.push(`
99
+ ${icon} ${categoryColor(categoryText)}: ${message} ${tsCode}`);
100
+ output.push(` ${chalk.gray("at")} ${fileName}:${chalk.bold(`${line + 1}:${character + 1}`)}`);
101
+ const sourceLines = diagnostic.file.text.split("\n");
102
+ if (line < sourceLines.length) {
103
+ const sourceLine = sourceLines[line];
104
+ const lineNumber = (line + 1).toString().padStart(5, " ");
105
+ output.push(`
106
+ ${chalk.dim(lineNumber + " |")} ${sourceLine}`);
107
+ const underlinePrefix = " ".repeat(character);
108
+ const length = diagnostic.length ?? 1;
109
+ const underline = "~".repeat(Math.max(1, length));
110
+ output.push(
111
+ `${chalk.dim(" ".repeat(lineNumber.length) + " |")} ${underlinePrefix}${categoryColor(underline)}`
112
+ );
113
+ }
114
+ } else
115
+ output.push(`
116
+ ${icon} ${categoryColor(categoryText)}: ${message} ${tsCode}`);
117
+ });
118
+ });
119
+ const summary = [];
120
+ if (errorCount > 0)
121
+ summary.push(chalk.red(`${errorCount} error(s)`));
122
+ if (warningCount > 0)
123
+ summary.push(chalk.yellow(`${warningCount} warning(s)`));
124
+ if (suggestionCount > 0)
125
+ summary.push(chalk.blue(`${suggestionCount} suggestion(s)`));
126
+ return `
127
+ ${summary.join(", ")} found` + output.join("\n");
128
+ }
129
+ /**
130
+ * Get detailed diagnostic information with code snippet
131
+ * @param filePath - Path to the TypeScript file to check
132
+ * @returns Object containing diagnostics and detailed information
133
+ */
134
+ getDetailedDiagnostics(filePath) {
135
+ const { diagnostics } = this.check(filePath);
136
+ const sourceFile = ts.createSourceFile(filePath, fs.readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest, true);
137
+ const details = diagnostics.map((diagnostic) => {
138
+ if (diagnostic.file && diagnostic.start !== void 0) {
139
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
140
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
141
+ const lines = sourceFile.text.split("\n");
142
+ const codeSnippet = line < lines.length ? lines[line] : void 0;
143
+ return { line: line + 1, column: character + 1, message, code: diagnostic.code, codeSnippet };
144
+ }
145
+ return {
146
+ line: 0,
147
+ column: 0,
148
+ message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
149
+ code: diagnostic.code
150
+ };
151
+ });
152
+ return { diagnostics, details };
153
+ }
154
+ /**
155
+ * Check if a file has type errors
156
+ * @param filePath - Path to the TypeScript file to check
157
+ * @returns true if there are no type errors, false otherwise
158
+ */
159
+ hasNoTypeErrors(filePath) {
160
+ try {
161
+ const { diagnostics } = this.check(filePath);
162
+ return diagnostics.length === 0;
163
+ } catch (error) {
164
+ return false;
165
+ }
166
+ }
167
+ }
168
+ export {
169
+ TypeChecker
170
+ };
@@ -1,49 +1,75 @@
1
+ import { Logger } from "@akanjs/common";
1
2
  import axios from "axios";
2
3
  import FormData from "form-data";
3
4
  import fs from "fs";
4
- const uploadRelease = async (projectName, {
5
+ import { Spinner } from "./spinner";
6
+ const spinning = (message) => {
7
+ const spinner = new Spinner(message, { prefix: message, enableSpin: true }).start();
8
+ return spinner;
9
+ };
10
+ const uploadRelease = async (appName, {
5
11
  workspaceRoot,
6
12
  environment,
7
13
  buildNum,
8
14
  platformVersion,
9
15
  local
10
16
  }) => {
17
+ const logger = new Logger("uploadRelease");
11
18
  const basePath = local ? "http://localhost:8080/backend" : "https://akasys.akamir.com/backend";
12
- const buildPath = `${workspaceRoot}/releases/builds/${projectName}-release.tar.gz`;
13
- const appBuildPath = `${workspaceRoot}/releases/builds/${projectName}-appBuild.zip`;
14
- const sourcePath = `${workspaceRoot}/releases/sources/${projectName}-source.tar.gz`;
15
- const formData = new FormData();
16
- const build = fs.readFileSync(buildPath);
17
- const source = fs.readFileSync(sourcePath);
18
- const appBuild = fs.readFileSync(appBuildPath);
19
- const buildStat = fs.statSync(buildPath);
20
- const sourceStat = fs.statSync(sourcePath);
21
- const appBuildStat = fs.statSync(appBuildPath);
22
- formData.append("files", build, `${projectName}-release.tar.gz`);
23
- formData.append("files", source, `${projectName}-source.tar.gz`);
24
- formData.append("files", appBuild, `${projectName}-appBuild.zip`);
25
- formData.append(
26
- "metas",
27
- JSON.stringify([
28
- { lastModifiedAt: buildStat.mtime, size: buildStat.size },
29
- { lastModifiedAt: sourceStat.mtime, size: sourceStat.size },
30
- { lastModifiedAt: appBuildStat.mtime, size: appBuildStat.size }
31
- ])
32
- );
33
- formData.append("type", "release");
19
+ const buildPath = `${workspaceRoot}/releases/builds/${appName}-release.tar.gz`;
20
+ const appBuildPath = `${workspaceRoot}/releases/builds/${appName}-appBuild.zip`;
21
+ const sourcePath = `${workspaceRoot}/releases/sources/${appName}-source.tar.gz`;
22
+ const readingFilesSpinner = spinning("Reading files...");
34
23
  try {
35
- const [buildFile, sourceFile, appBuildFile] = (await axios.post(`${basePath}/file/addFilesRestApi`, formData)).data;
36
- const major = platformVersion ? parseInt(platformVersion.split(".")[0]) : 1;
37
- const minor = platformVersion ? parseInt(platformVersion.split(".")[1]) : 0;
38
- const patch = platformVersion ? parseInt(platformVersion.split(".")[2]) : 0;
39
- const latestRelease = await axios.get(
40
- `${basePath}/release/findVersionRelease?appName=${projectName}&branch=${environment}&major=${major}&minor=${minor}`
24
+ const build = fs.readFileSync(buildPath);
25
+ const source = fs.readFileSync(sourcePath);
26
+ const appBuild = fs.readFileSync(appBuildPath);
27
+ const buildStat = fs.statSync(buildPath);
28
+ const sourceStat = fs.statSync(sourcePath);
29
+ const appBuildStat = fs.statSync(appBuildPath);
30
+ readingFilesSpinner.succeed("Reading files... done");
31
+ const preparingFormSpinner = spinning("Preparing form data...");
32
+ const formData = new FormData();
33
+ formData.append("files", build, `${appName}-release.tar.gz`);
34
+ formData.append("files", source, `${appName}-source.tar.gz`);
35
+ formData.append("files", appBuild, `${appName}-appBuild.zip`);
36
+ formData.append(
37
+ "metas",
38
+ JSON.stringify([
39
+ { lastModifiedAt: buildStat.mtime, size: buildStat.size },
40
+ { lastModifiedAt: sourceStat.mtime, size: sourceStat.size },
41
+ { lastModifiedAt: appBuildStat.mtime, size: appBuildStat.size }
42
+ ])
41
43
  );
42
- const release = (await axios.post(
43
- `${basePath}/release/pushRelease/${projectName}/${environment}/${major}/${minor}/${sourceFile.id}/${buildFile.id}/${appBuildFile.id}`
44
- )).data;
45
- return release;
44
+ formData.append("type", "release");
45
+ preparingFormSpinner.succeed("Preparing form data... done");
46
+ try {
47
+ const uploadingFilesSpinner = spinning("Uploading files to server...");
48
+ const [buildFile, sourceFile, appBuildFile] = (await axios.post(`${basePath}/file/addFilesRestApi`, formData)).data;
49
+ uploadingFilesSpinner.succeed("Uploading files to server... done");
50
+ const fetchingAppSpinner = spinning(`Fetching dev app information for ${appName}...`);
51
+ const major = platformVersion ? parseInt(platformVersion.split(".")[0]) : 1;
52
+ const minor = platformVersion ? parseInt(platformVersion.split(".")[1]) : 0;
53
+ const patch = platformVersion ? parseInt(platformVersion.split(".")[2]) : 0;
54
+ const devApp = (await axios.get(`${basePath}/devApp/devAppInName/${appName}`)).data;
55
+ fetchingAppSpinner.succeed(`Fetching dev app information for ${appName}... done`);
56
+ const pushingReleaseSpinner = spinning(`Pushing release to ${environment} environment...`);
57
+ const release = (await axios.post(
58
+ `${basePath}/release/pushRelease/${devApp.id}/${environment}/${major}/${minor}/${sourceFile.id}/${buildFile.id}/${appBuildFile.id}`
59
+ )).data;
60
+ pushingReleaseSpinner.succeed(`Pushing release to ${environment} environment... done`);
61
+ new Spinner(`Successfully pushed release to ${appName}-${environment} server. `, {
62
+ prefix: `Successfully pushed release to ${appName}-${environment} server. `,
63
+ enableSpin: false
64
+ }).succeed(`Successfully pushed release to ${appName}-${environment} server. `);
65
+ return release;
66
+ } catch (e) {
67
+ const errorMessage = e instanceof Error ? e.message : "Unknown error";
68
+ return null;
69
+ }
46
70
  } catch (e) {
71
+ const errorMessage = e instanceof Error ? e.message : "Unknown error";
72
+ readingFilesSpinner.fail(`Reading files failed: ${errorMessage}`);
47
73
  return null;
48
74
  }
49
75
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "0.0.142",
3
+ "version": "0.0.144",
4
4
  "sourceType": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,6 +17,7 @@
17
17
  "dependencies": {
18
18
  "@inquirer/prompts": "^7.2.1",
19
19
  "@langchain/core": "^0.3.56",
20
+ "@langchain/deepseek": "^0.0.1",
20
21
  "@langchain/openai": "^0.5.10",
21
22
  "@trapezedev/project": "^7.1.3",
22
23
  "axios": "^1.7.9",
@@ -25,6 +26,7 @@
25
26
  "dotenv": "^16.4.7",
26
27
  "esbuild": "^0.19.2",
27
28
  "esbuild-plugin-d.ts": "^1.3.1",
29
+ "eslint": "^9.19.0",
28
30
  "form-data": "^4.0.1",
29
31
  "js-yaml": "^4.1.0",
30
32
  "ora": "^3.4.0",
package/src/aiEditor.d.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  import { BaseMessage } from "@langchain/core/messages";
2
+ import type { Executor, WorkspaceExecutor } from "./executors";
3
+ import type { FileContent } from "./types";
2
4
  export declare const supportedLlmModels: readonly ["deepseek-chat", "deepseek-reasoner"];
3
5
  export type SupportedLlmModel = (typeof supportedLlmModels)[number];
4
6
  interface EditOptions {
7
+ onReasoning?: (reasoning: string) => void;
5
8
  onChunk?: (chunk: string) => void;
6
9
  maxTry?: number;
10
+ validate?: string[];
11
+ approve?: boolean;
7
12
  }
8
13
  export declare class AiSession {
9
14
  #private;
@@ -19,13 +24,27 @@ export declare class AiSession {
19
24
  model: SupportedLlmModel;
20
25
  apiKey: string;
21
26
  } | null): typeof AiSession;
22
- readonly messageHistory: BaseMessage[];
23
- constructor(messageHistory?: BaseMessage[]);
24
- ask(question: string, { onChunk, }?: EditOptions): Promise<{
27
+ static clearCache(workspaceRoot: string): void;
28
+ messageHistory: BaseMessage[];
29
+ readonly sessionKey: string;
30
+ isCacheLoaded: boolean;
31
+ workspace: WorkspaceExecutor;
32
+ constructor(type: string, { workspace, cacheKey, isContinued }: {
33
+ workspace: WorkspaceExecutor;
34
+ cacheKey?: string;
35
+ isContinued?: boolean;
36
+ });
37
+ ask(question: string, { onReasoning, onChunk, }?: EditOptions): Promise<{
25
38
  content: string;
26
39
  messageHistory: BaseMessage[];
27
40
  }>;
28
- edit(question: string, { onChunk, maxTry }?: EditOptions): Promise<string>;
41
+ edit(question: string, { onChunk, onReasoning, maxTry, validate, approve }?: EditOptions): Promise<string>;
29
42
  editTypescript(question: string, options?: EditOptions): Promise<string>;
43
+ addToolMessgaes(messages: {
44
+ type: string;
45
+ content: string;
46
+ }[]): this;
47
+ writeTypescripts(question: string, executor: Executor, options?: EditOptions): Promise<FileContent[]>;
48
+ editMarkdown(request: string, options?: EditOptions): Promise<string>;
30
49
  }
31
50
  export {};
@@ -1,9 +1,9 @@
1
1
  import "reflect-metadata";
2
- import type { AppExecutor, Executor, LibExecutor, PkgExecutor, SysExecutor, WorkspaceExecutor } from "../executors";
2
+ import type { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, SysExecutor, WorkspaceExecutor } from "../executors";
3
3
  import type { Type } from "./types";
4
4
  export declare const argTypes: readonly ["Argument", "Option"];
5
5
  export type ArgType = (typeof argTypes)[number];
6
- export declare const internalArgTypes: readonly ["Workspace", "App", "Lib", "Sys", "Pkg", "Exec"];
6
+ export declare const internalArgTypes: readonly ["Workspace", "App", "Lib", "Sys", "Pkg", "Module", "Exec"];
7
7
  export type InternalArgType = (typeof internalArgTypes)[number];
8
8
  interface ArgsOption {
9
9
  type?: "string" | "number" | "boolean";
@@ -53,6 +53,10 @@ export declare const Pkg: (option?: {
53
53
  nullable?: boolean;
54
54
  }) => (prototype: object, key: string, idx: number) => void;
55
55
  export type Pkg = PkgExecutor;
56
+ export declare const Module: (option?: {
57
+ nullable?: boolean;
58
+ }) => (prototype: object, key: string, idx: number) => void;
59
+ export type Module = ModuleExecutor;
56
60
  export declare const Workspace: (option?: {
57
61
  nullable?: boolean;
58
62
  }) => (prototype: object, key: string, idx: number) => void;