@dusted/anqst 0.1.0 → 0.1.2
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/README.md +135 -129
- package/dist/src/app.js +287 -113
- package/dist/src/bin/anqst.js +0 -0
- package/dist/src/debug-dump.js +40 -0
- package/dist/src/emit.js +488 -64
- package/dist/src/layout.js +70 -0
- package/dist/src/parser.js +16 -1
- package/dist/src/program.js +120 -0
- package/dist/src/project.js +230 -77
- package/dist/src/typegraph.js +172 -0
- package/dist/src/verify.js +9 -1
- package/index.d.ts +1 -0
- package/package.json +13 -5
- package/spec/AnQst-Spec-DSL.d.ts +2 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ANQST_LAYOUT_VERSION = exports.ANQST_GENERATED_DIRNAME = exports.ANQST_ROOT_DIRNAME = void 0;
|
|
7
|
+
exports.normalizeSlashes = normalizeSlashes;
|
|
8
|
+
exports.anqstRootDir = anqstRootDir;
|
|
9
|
+
exports.anqstGeneratedRootDir = anqstGeneratedRootDir;
|
|
10
|
+
exports.anqstDebugIntermediateRootDir = anqstDebugIntermediateRootDir;
|
|
11
|
+
exports.anqstSpecFileName = anqstSpecFileName;
|
|
12
|
+
exports.anqstSettingsFileName = anqstSettingsFileName;
|
|
13
|
+
exports.anqstSettingsRelativePath = anqstSettingsRelativePath;
|
|
14
|
+
exports.generatedFrontendDirName = generatedFrontendDirName;
|
|
15
|
+
exports.generatedNodeExpressDirName = generatedNodeExpressDirName;
|
|
16
|
+
exports.generatedQtWidgetDirName = generatedQtWidgetDirName;
|
|
17
|
+
exports.resolveGeneratedLayoutPaths = resolveGeneratedLayoutPaths;
|
|
18
|
+
exports.toProjectRelative = toProjectRelative;
|
|
19
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
20
|
+
exports.ANQST_ROOT_DIRNAME = "AnQst";
|
|
21
|
+
exports.ANQST_GENERATED_DIRNAME = "generated";
|
|
22
|
+
exports.ANQST_LAYOUT_VERSION = 2;
|
|
23
|
+
function normalizeSlashes(inputPath) {
|
|
24
|
+
return inputPath.split(node_path_1.default.sep).join("/");
|
|
25
|
+
}
|
|
26
|
+
function anqstRootDir(cwd) {
|
|
27
|
+
return node_path_1.default.join(cwd, exports.ANQST_ROOT_DIRNAME);
|
|
28
|
+
}
|
|
29
|
+
function anqstGeneratedRootDir(cwd) {
|
|
30
|
+
return node_path_1.default.join(anqstRootDir(cwd), exports.ANQST_GENERATED_DIRNAME);
|
|
31
|
+
}
|
|
32
|
+
function anqstDebugIntermediateRootDir(cwd) {
|
|
33
|
+
return node_path_1.default.join(anqstGeneratedRootDir(cwd), "debug", "intermediate");
|
|
34
|
+
}
|
|
35
|
+
function anqstSpecFileName(widgetName) {
|
|
36
|
+
return `${widgetName}.AnQst.d.ts`;
|
|
37
|
+
}
|
|
38
|
+
function anqstSettingsFileName(widgetName) {
|
|
39
|
+
return `${widgetName}.settings.json`;
|
|
40
|
+
}
|
|
41
|
+
function anqstSettingsRelativePath(widgetName) {
|
|
42
|
+
return `./${exports.ANQST_ROOT_DIRNAME}/${anqstSettingsFileName(widgetName)}`;
|
|
43
|
+
}
|
|
44
|
+
function generatedFrontendDirName(widgetName) {
|
|
45
|
+
return `${widgetName}_Angular`;
|
|
46
|
+
}
|
|
47
|
+
function generatedNodeExpressDirName(widgetName) {
|
|
48
|
+
return `${widgetName}_anQst`;
|
|
49
|
+
}
|
|
50
|
+
function generatedQtWidgetDirName(widgetName) {
|
|
51
|
+
return `${widgetName}_widget`;
|
|
52
|
+
}
|
|
53
|
+
function resolveGeneratedLayoutPaths(cwd, widgetName) {
|
|
54
|
+
const generatedRoot = anqstGeneratedRootDir(cwd);
|
|
55
|
+
const cppQtWidgetRoot = node_path_1.default.join(generatedRoot, "backend", "cpp", "qt", generatedQtWidgetDirName(widgetName));
|
|
56
|
+
const designerPluginRoot = node_path_1.default.join(cppQtWidgetRoot, "designerPlugin");
|
|
57
|
+
return {
|
|
58
|
+
generatedRoot,
|
|
59
|
+
frontendRoot: node_path_1.default.join(generatedRoot, "frontend", generatedFrontendDirName(widgetName)),
|
|
60
|
+
nodeExpressRoot: node_path_1.default.join(generatedRoot, "backend", "node", "express", generatedNodeExpressDirName(widgetName)),
|
|
61
|
+
cppCmakeRoot: node_path_1.default.join(generatedRoot, "backend", "cpp", "cmake"),
|
|
62
|
+
cppQtWidgetRoot,
|
|
63
|
+
designerPluginRoot,
|
|
64
|
+
designerPluginBuildRoot: node_path_1.default.join(designerPluginRoot, "build"),
|
|
65
|
+
debugIntermediateRoot: anqstDebugIntermediateRootDir(cwd)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function toProjectRelative(cwd, absolutePath) {
|
|
69
|
+
return normalizeSlashes(node_path_1.default.relative(cwd, absolutePath));
|
|
70
|
+
}
|
package/dist/src/parser.js
CHANGED
|
@@ -8,6 +8,9 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const typescript_1 = __importDefault(require("typescript"));
|
|
10
10
|
const errors_1 = require("./errors");
|
|
11
|
+
const program_1 = require("./program");
|
|
12
|
+
const typegraph_1 = require("./typegraph");
|
|
13
|
+
const debug_dump_1 = require("./debug-dump");
|
|
11
14
|
function locFromNode(source, node) {
|
|
12
15
|
const lc = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
13
16
|
return {
|
|
@@ -207,7 +210,7 @@ function serviceBaseType(iface) {
|
|
|
207
210
|
}
|
|
208
211
|
return null;
|
|
209
212
|
}
|
|
210
|
-
function
|
|
213
|
+
function parseSpecFileAst(specFilePath) {
|
|
211
214
|
if (!node_fs_1.default.existsSync(specFilePath))
|
|
212
215
|
throw new errors_1.VerifyError(`Spec file does not exist: ${specFilePath}`);
|
|
213
216
|
const text = node_fs_1.default.readFileSync(specFilePath, "utf8");
|
|
@@ -257,3 +260,15 @@ function parseSpecFile(specFilePath) {
|
|
|
257
260
|
specImports: importInfo.specImports
|
|
258
261
|
};
|
|
259
262
|
}
|
|
263
|
+
function parseSpecFile(specFilePath) {
|
|
264
|
+
(0, program_1.createTscProgramContext)(specFilePath);
|
|
265
|
+
const parsed = parseSpecFileAst(specFilePath);
|
|
266
|
+
if ((0, debug_dump_1.isDebugEnabled)()) {
|
|
267
|
+
(0, debug_dump_1.writeDebugFile)(process.cwd(), "anqstmodel/parsed-before-typegraph.txt", `${(0, debug_dump_1.inspectText)(parsed)}\n`);
|
|
268
|
+
}
|
|
269
|
+
const normalized = (0, typegraph_1.applyResolvedTypeGraph)(parsed);
|
|
270
|
+
if ((0, debug_dump_1.isDebugEnabled)()) {
|
|
271
|
+
(0, debug_dump_1.writeDebugFile)(process.cwd(), "anqstmodel/parsed-after-typegraph.txt", `${(0, debug_dump_1.inspectText)(normalized)}\n`);
|
|
272
|
+
}
|
|
273
|
+
return normalized;
|
|
274
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTscProgramContext = createTscProgramContext;
|
|
7
|
+
exports.getTscProgramContext = getTscProgramContext;
|
|
8
|
+
exports.getProgramDiagnostics = getProgramDiagnostics;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
|
+
const debug_dump_1 = require("./debug-dump");
|
|
14
|
+
const contextBySpecPath = new Map();
|
|
15
|
+
function diagnosticText(diagnostic) {
|
|
16
|
+
return typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
17
|
+
}
|
|
18
|
+
function formatDiagnostic(diagnostic) {
|
|
19
|
+
if (!diagnostic.file || diagnostic.start === undefined) {
|
|
20
|
+
return diagnosticText(diagnostic);
|
|
21
|
+
}
|
|
22
|
+
const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
|
|
23
|
+
return `${diagnostic.file.fileName}:${pos.line + 1}:${pos.character + 1}: ${diagnosticText(diagnostic)}`;
|
|
24
|
+
}
|
|
25
|
+
function dumpSourceFileAst(sourceFile) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
lines.push(`== SOURCE FILE ==`);
|
|
28
|
+
lines.push(sourceFile.fileName);
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("== FULL TEXT ==");
|
|
31
|
+
lines.push(sourceFile.getFullText());
|
|
32
|
+
lines.push("");
|
|
33
|
+
lines.push("== AST NODES ==");
|
|
34
|
+
const walk = (node, depth) => {
|
|
35
|
+
const indent = " ".repeat(depth);
|
|
36
|
+
const kind = typescript_1.default.SyntaxKind[node.kind];
|
|
37
|
+
const start = node.getStart(sourceFile, false);
|
|
38
|
+
const end = node.getEnd();
|
|
39
|
+
const text = node.getText(sourceFile).replace(/\s+/g, " ").slice(0, 160);
|
|
40
|
+
lines.push(`${indent}${kind} [${start}, ${end}] ${text}`);
|
|
41
|
+
typescript_1.default.forEachChild(node, (child) => walk(child, depth + 1));
|
|
42
|
+
};
|
|
43
|
+
walk(sourceFile, 0);
|
|
44
|
+
return `${lines.join("\n")}\n`;
|
|
45
|
+
}
|
|
46
|
+
function dumpProgramArtifacts(specPath, rootNames, options, program, sourceFile) {
|
|
47
|
+
if (!(0, debug_dump_1.isDebugEnabled)())
|
|
48
|
+
return;
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program, sourceFile).map(formatDiagnostic);
|
|
51
|
+
const sourceFiles = program.getSourceFiles().map((sf) => sf.fileName);
|
|
52
|
+
const contextLines = [
|
|
53
|
+
`specPath: ${specPath}`,
|
|
54
|
+
"",
|
|
55
|
+
"compilerOptions:",
|
|
56
|
+
(0, debug_dump_1.inspectText)(options),
|
|
57
|
+
"",
|
|
58
|
+
"rootNames:",
|
|
59
|
+
rootNames.join("\n"),
|
|
60
|
+
"",
|
|
61
|
+
"diagnostics:",
|
|
62
|
+
diagnostics.length > 0 ? diagnostics.join("\n") : "(none)"
|
|
63
|
+
];
|
|
64
|
+
(0, debug_dump_1.writeDebugFile)(cwd, node_path_1.default.join("tsc", "program-context.txt"), `${contextLines.join("\n")}\n`);
|
|
65
|
+
(0, debug_dump_1.writeDebugFile)(cwd, node_path_1.default.join("tsc", "program-files.txt"), `${sourceFiles.join("\n")}\n`);
|
|
66
|
+
(0, debug_dump_1.writeDebugFile)(cwd, node_path_1.default.join("tsc", "sourcefile-ast.txt"), dumpSourceFileAst(sourceFile));
|
|
67
|
+
}
|
|
68
|
+
function readTsConfigFrom(specPath) {
|
|
69
|
+
const specDir = node_path_1.default.dirname(specPath);
|
|
70
|
+
const configPath = typescript_1.default.findConfigFile(specDir, typescript_1.default.sys.fileExists, "tsconfig.json");
|
|
71
|
+
if (!configPath || !node_fs_1.default.existsSync(configPath)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const config = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
75
|
+
if (config.error) {
|
|
76
|
+
throw new errors_1.VerifyError(`Unable to parse tsconfig.json: ${diagnosticText(config.error)}`);
|
|
77
|
+
}
|
|
78
|
+
const parsed = typescript_1.default.parseJsonConfigFileContent(config.config, typescript_1.default.sys, node_path_1.default.dirname(configPath), {}, configPath);
|
|
79
|
+
return { rootNames: parsed.fileNames, options: parsed.options };
|
|
80
|
+
}
|
|
81
|
+
function createTscProgramContext(specPath) {
|
|
82
|
+
const absoluteSpecPath = node_path_1.default.resolve(specPath);
|
|
83
|
+
const tsConfig = readTsConfigFrom(absoluteSpecPath);
|
|
84
|
+
const rootNames = tsConfig ? [...new Set([...tsConfig.rootNames, absoluteSpecPath])] : [absoluteSpecPath];
|
|
85
|
+
const options = tsConfig?.options ?? {
|
|
86
|
+
target: typescript_1.default.ScriptTarget.ES2022,
|
|
87
|
+
module: typescript_1.default.ModuleKind.CommonJS,
|
|
88
|
+
moduleResolution: typescript_1.default.ModuleResolutionKind.NodeJs,
|
|
89
|
+
skipLibCheck: true,
|
|
90
|
+
esModuleInterop: true,
|
|
91
|
+
strict: false,
|
|
92
|
+
allowJs: false
|
|
93
|
+
};
|
|
94
|
+
const program = typescript_1.default.createProgram({ rootNames, options });
|
|
95
|
+
const sourceFile = program.getSourceFile(absoluteSpecPath);
|
|
96
|
+
if (!sourceFile) {
|
|
97
|
+
throw new errors_1.VerifyError(`Unable to load spec source file into TypeScript program: ${absoluteSpecPath}`);
|
|
98
|
+
}
|
|
99
|
+
const context = {
|
|
100
|
+
specPath: absoluteSpecPath,
|
|
101
|
+
program,
|
|
102
|
+
checker: program.getTypeChecker(),
|
|
103
|
+
sourceFile
|
|
104
|
+
};
|
|
105
|
+
dumpProgramArtifacts(absoluteSpecPath, rootNames, options, program, sourceFile);
|
|
106
|
+
contextBySpecPath.set(absoluteSpecPath, context);
|
|
107
|
+
return context;
|
|
108
|
+
}
|
|
109
|
+
function getTscProgramContext(specPath) {
|
|
110
|
+
const absoluteSpecPath = node_path_1.default.resolve(specPath);
|
|
111
|
+
const existing = contextBySpecPath.get(absoluteSpecPath);
|
|
112
|
+
if (existing)
|
|
113
|
+
return existing;
|
|
114
|
+
return createTscProgramContext(absoluteSpecPath);
|
|
115
|
+
}
|
|
116
|
+
function getProgramDiagnostics(specPath) {
|
|
117
|
+
const context = getTscProgramContext(specPath);
|
|
118
|
+
const diagnostics = typescript_1.default.getPreEmitDiagnostics(context.program, context.sourceFile);
|
|
119
|
+
return diagnostics.map(formatDiagnostic);
|
|
120
|
+
}
|
package/dist/src/project.js
CHANGED
|
@@ -5,119 +5,272 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DEFAULT_ANQST_GENERATE_TARGETS = void 0;
|
|
7
7
|
exports.readProjectPackage = readProjectPackage;
|
|
8
|
+
exports.resolveAnQstSettings = resolveAnQstSettings;
|
|
8
9
|
exports.resolveAnQstSpecPath = resolveAnQstSpecPath;
|
|
9
10
|
exports.resolveAnQstGenerateTargets = resolveAnQstGenerateTargets;
|
|
10
|
-
exports.
|
|
11
|
+
exports.resolveAnQstWidgetCategory = resolveAnQstWidgetCategory;
|
|
12
|
+
exports.resolveAnQstWidgetName = resolveAnQstWidgetName;
|
|
11
13
|
exports.buildSpecScaffold = buildSpecScaffold;
|
|
12
14
|
exports.runInstill = runInstill;
|
|
13
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
17
|
const errors_1 = require("./errors");
|
|
16
|
-
|
|
18
|
+
const layout_1 = require("./layout");
|
|
19
|
+
exports.DEFAULT_ANQST_GENERATE_TARGETS = ["QWidget", "AngularService", "node_express_ws"];
|
|
20
|
+
const ANQST_DSL_IMPORT_LINE = 'import type { AnQst } from "@dusted/anqst";';
|
|
21
|
+
const ANQST_BUILD_HOOK = "npx anqst build";
|
|
17
22
|
function readJsonFile(filePath) {
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
try {
|
|
24
|
+
const raw = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
throw new errors_1.VerifyError(`Unable to parse JSON file '${(0, layout_1.normalizeSlashes)(filePath)}': ${message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function ensureObject(value, errorMessage) {
|
|
33
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
34
|
+
throw new errors_1.VerifyError(errorMessage);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
20
37
|
}
|
|
21
|
-
function
|
|
22
|
-
if (!existing || existing.trim().length === 0)
|
|
23
|
-
return
|
|
38
|
+
function ensureBuildHook(existing) {
|
|
39
|
+
if (!existing || existing.trim().length === 0) {
|
|
40
|
+
return ANQST_BUILD_HOOK;
|
|
41
|
+
}
|
|
24
42
|
const trimmed = existing.trim();
|
|
25
|
-
if (trimmed ===
|
|
43
|
+
if (trimmed === ANQST_BUILD_HOOK || trimmed.startsWith(`${ANQST_BUILD_HOOK} &&`) || trimmed.includes(ANQST_BUILD_HOOK)) {
|
|
26
44
|
return trimmed;
|
|
27
|
-
|
|
45
|
+
}
|
|
46
|
+
return `${ANQST_BUILD_HOOK} && ${trimmed}`;
|
|
47
|
+
}
|
|
48
|
+
function normalizeAnQstImport(sourceText) {
|
|
49
|
+
const importPattern = /^\s*import\s+(?:type\s+)?\{\s*AnQst\s*\}\s+from\s+["'][^"']+["'];\s*$/m;
|
|
50
|
+
if (importPattern.test(sourceText)) {
|
|
51
|
+
const nextText = sourceText.replace(importPattern, ANQST_DSL_IMPORT_LINE);
|
|
52
|
+
return { nextText, changed: nextText !== sourceText };
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
nextText: `${ANQST_DSL_IMPORT_LINE}\n\n${sourceText}`,
|
|
56
|
+
changed: true
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function isSubPath(parentPath, childPath) {
|
|
60
|
+
const relative = node_path_1.default.relative(parentPath, childPath);
|
|
61
|
+
if (relative === "") {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return !relative.startsWith("..") && !node_path_1.default.isAbsolute(relative);
|
|
65
|
+
}
|
|
66
|
+
function readAnQstSettingsFromPath(cwd, settingsPath) {
|
|
67
|
+
if (!node_fs_1.default.existsSync(settingsPath)) {
|
|
68
|
+
throw new errors_1.VerifyError(`Missing AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}'. Run 'anqst instill <WidgetName>' first.`);
|
|
69
|
+
}
|
|
70
|
+
const raw = readJsonFile(settingsPath);
|
|
71
|
+
const settingsObject = ensureObject(raw, `Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': expected JSON object.`);
|
|
72
|
+
const layoutVersion = settingsObject.layoutVersion;
|
|
73
|
+
if (layoutVersion !== layout_1.ANQST_LAYOUT_VERSION) {
|
|
74
|
+
throw new errors_1.VerifyError(`Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': expected layoutVersion ${layout_1.ANQST_LAYOUT_VERSION}.`);
|
|
75
|
+
}
|
|
76
|
+
const widgetName = settingsObject.widgetName;
|
|
77
|
+
if (typeof widgetName !== "string" || widgetName.trim().length === 0) {
|
|
78
|
+
throw new errors_1.VerifyError(`Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': expected non-empty string 'widgetName'.`);
|
|
79
|
+
}
|
|
80
|
+
const spec = settingsObject.spec;
|
|
81
|
+
if (typeof spec !== "string" || spec.trim().length === 0) {
|
|
82
|
+
throw new errors_1.VerifyError(`Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': expected non-empty string 'spec'.`);
|
|
83
|
+
}
|
|
84
|
+
const generate = settingsObject.generate;
|
|
85
|
+
if (generate !== undefined && (!Array.isArray(generate) || generate.some((entry) => typeof entry !== "string"))) {
|
|
86
|
+
throw new errors_1.VerifyError(`Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': expected string array 'generate'.`);
|
|
87
|
+
}
|
|
88
|
+
const widgetCategory = settingsObject.widgetCategory;
|
|
89
|
+
if (widgetCategory !== undefined) {
|
|
90
|
+
if (typeof widgetCategory !== "string" || widgetCategory.trim().length === 0) {
|
|
91
|
+
throw new errors_1.VerifyError(`Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': expected non-empty string 'widgetCategory'.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const resolvedSpecPath = node_path_1.default.resolve(cwd, spec.trim());
|
|
95
|
+
const anqstRoot = (0, layout_1.anqstRootDir)(cwd);
|
|
96
|
+
if (!isSubPath(anqstRoot, resolvedSpecPath)) {
|
|
97
|
+
throw new errors_1.VerifyError(`Invalid AnQst settings file '${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, settingsPath))}': 'spec' must resolve inside './AnQst'.`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
layoutVersion,
|
|
101
|
+
widgetName: widgetName.trim(),
|
|
102
|
+
spec: spec.trim(),
|
|
103
|
+
generate: Array.isArray(generate) ? [...generate] : undefined,
|
|
104
|
+
widgetCategory: typeof widgetCategory === "string" ? widgetCategory.trim() : undefined
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function resolveSettingsPathFromPackage(cwd, packageJson) {
|
|
108
|
+
const settingsRef = packageJson.AnQst;
|
|
109
|
+
if (settingsRef === undefined) {
|
|
110
|
+
throw new errors_1.VerifyError("Missing package.json key 'AnQst'. Run 'anqst instill <WidgetName>' first.");
|
|
111
|
+
}
|
|
112
|
+
if (typeof settingsRef !== "string" || settingsRef.trim().length === 0) {
|
|
113
|
+
throw new errors_1.VerifyError("Invalid package.json key 'AnQst': expected non-empty string path to settings JSON.");
|
|
114
|
+
}
|
|
115
|
+
return node_path_1.default.resolve(cwd, settingsRef.trim());
|
|
116
|
+
}
|
|
117
|
+
function buildAnQstDirectoryReadme(widgetName) {
|
|
118
|
+
return [
|
|
119
|
+
"# AnQst Project Directory",
|
|
120
|
+
"",
|
|
121
|
+
"This directory is owned by the AnQst CLI for this project.",
|
|
122
|
+
"",
|
|
123
|
+
"## Files",
|
|
124
|
+
"",
|
|
125
|
+
`- \`${(0, layout_1.anqstSpecFileName)(widgetName)}\`: AnQst widget spec source.`,
|
|
126
|
+
`- \`${(0, layout_1.anqstSettingsFileName)(widgetName)}\`: project-local AnQst configuration used by \`anqst build\`.`,
|
|
127
|
+
"- `generated/`: deterministic build output roots managed by `anqst build`.",
|
|
128
|
+
"",
|
|
129
|
+
"## Regeneration",
|
|
130
|
+
"",
|
|
131
|
+
"- `npx anqst build` refreshes generated outputs under `generated/`.",
|
|
132
|
+
"- Build hooks in package.json (`postinstall`, `prebuild`, `prestart`) call `npx anqst build`.",
|
|
133
|
+
"",
|
|
134
|
+
"Do not hand-edit generated files under `generated/`; they are overwritten by design.",
|
|
135
|
+
""
|
|
136
|
+
].join("\n");
|
|
137
|
+
}
|
|
138
|
+
function updateTsConfig(cwd, widgetName) {
|
|
139
|
+
const tsConfigPath = node_path_1.default.join(cwd, "tsconfig.json");
|
|
140
|
+
if (!node_fs_1.default.existsSync(tsConfigPath)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const tsConfigRaw = readJsonFile(tsConfigPath);
|
|
144
|
+
const tsConfig = ensureObject(tsConfigRaw, "Invalid tsconfig.json: expected top-level object.");
|
|
145
|
+
const compilerOptions = tsConfig.compilerOptions === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: ensureObject(tsConfig.compilerOptions, "Invalid tsconfig.json: expected object at 'compilerOptions'.");
|
|
148
|
+
tsConfig.compilerOptions = compilerOptions;
|
|
149
|
+
if (compilerOptions.baseUrl === undefined) {
|
|
150
|
+
compilerOptions.baseUrl = ".";
|
|
151
|
+
}
|
|
152
|
+
const pathsObject = compilerOptions.paths === undefined
|
|
153
|
+
? {}
|
|
154
|
+
: ensureObject(compilerOptions.paths, "Invalid tsconfig.json: expected object at 'compilerOptions.paths'.");
|
|
155
|
+
compilerOptions.paths = pathsObject;
|
|
156
|
+
const generatedAliasPath = `AnQst/generated/frontend/${(0, layout_1.generatedFrontendDirName)(widgetName)}/*`;
|
|
157
|
+
const existingAlias = pathsObject["anqst-generated/*"];
|
|
158
|
+
const aliasList = Array.isArray(existingAlias)
|
|
159
|
+
? existingAlias.filter((entry) => typeof entry === "string")
|
|
160
|
+
: [];
|
|
161
|
+
if (!aliasList.includes(generatedAliasPath)) {
|
|
162
|
+
aliasList.unshift(generatedAliasPath);
|
|
163
|
+
}
|
|
164
|
+
pathsObject["anqst-generated/*"] = [...new Set(aliasList)];
|
|
165
|
+
if (Array.isArray(tsConfig.include)) {
|
|
166
|
+
const includeList = tsConfig.include.filter((entry) => typeof entry === "string");
|
|
167
|
+
const generatedTypesPattern = `AnQst/generated/frontend/${(0, layout_1.generatedFrontendDirName)(widgetName)}/**/*.d.ts`;
|
|
168
|
+
if (!includeList.includes(generatedTypesPattern)) {
|
|
169
|
+
includeList.push(generatedTypesPattern);
|
|
170
|
+
}
|
|
171
|
+
tsConfig.include = includeList;
|
|
172
|
+
}
|
|
173
|
+
node_fs_1.default.writeFileSync(tsConfigPath, `${JSON.stringify(tsConfig, null, 2)}\n`, "utf8");
|
|
174
|
+
}
|
|
175
|
+
function validateWidgetName(widgetName) {
|
|
176
|
+
const trimmed = widgetName.trim();
|
|
177
|
+
if (trimmed.length === 0) {
|
|
178
|
+
throw new errors_1.VerifyError("Usage: anqst instill <WidgetName>");
|
|
179
|
+
}
|
|
180
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) {
|
|
181
|
+
throw new errors_1.VerifyError("Invalid widget name: expected a TypeScript identifier for namespace generation.");
|
|
182
|
+
}
|
|
183
|
+
return trimmed;
|
|
28
184
|
}
|
|
29
185
|
function readProjectPackage(cwd) {
|
|
30
186
|
const packagePath = node_path_1.default.join(cwd, "package.json");
|
|
31
187
|
if (!node_fs_1.default.existsSync(packagePath)) {
|
|
32
|
-
throw new errors_1.VerifyError("No package.json:
|
|
188
|
+
throw new errors_1.VerifyError("No package.json: AnQst commands must run inside an npm project.");
|
|
33
189
|
}
|
|
34
190
|
return {
|
|
35
191
|
packagePath,
|
|
36
192
|
packageJson: readJsonFile(packagePath)
|
|
37
193
|
};
|
|
38
194
|
}
|
|
39
|
-
function
|
|
195
|
+
function resolveAnQstSettings(cwd) {
|
|
40
196
|
const { packageJson } = readProjectPackage(cwd);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
197
|
+
const settingsPath = resolveSettingsPathFromPackage(cwd, packageJson);
|
|
198
|
+
return {
|
|
199
|
+
settingsPath,
|
|
200
|
+
settings: readAnQstSettingsFromPath(cwd, settingsPath)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function resolveAnQstSpecPath(cwd) {
|
|
204
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
205
|
+
return node_path_1.default.resolve(cwd, settings.spec);
|
|
46
206
|
}
|
|
47
207
|
function resolveAnQstGenerateTargets(cwd) {
|
|
48
|
-
const {
|
|
49
|
-
|
|
50
|
-
if (configured === undefined) {
|
|
208
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
209
|
+
if (!settings.generate) {
|
|
51
210
|
return [...exports.DEFAULT_ANQST_GENERATE_TARGETS];
|
|
52
211
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
];
|
|
63
|
-
for (const filePath of candidates) {
|
|
64
|
-
if (node_fs_1.default.existsSync(filePath))
|
|
65
|
-
return node_fs_1.default.readFileSync(filePath, "utf8");
|
|
66
|
-
}
|
|
67
|
-
return `export namespace AnQst {
|
|
68
|
-
interface Service {}
|
|
69
|
-
interface Call<T> { dummy: T }
|
|
70
|
-
interface Slot<T> { dummy: T }
|
|
71
|
-
interface Emitter {}
|
|
72
|
-
interface Output<T> {}
|
|
73
|
-
interface Input<T> {}
|
|
74
|
-
enum Type {
|
|
75
|
-
string = "string",
|
|
76
|
-
number = "number",
|
|
77
|
-
qint64 = "qint64",
|
|
78
|
-
qint32 = "qint32"
|
|
79
|
-
}
|
|
80
|
-
}`;
|
|
81
|
-
}
|
|
82
|
-
function installDslShim(cwd) {
|
|
83
|
-
const dslDir = node_path_1.default.join(cwd, "anqst-dsl");
|
|
84
|
-
node_fs_1.default.mkdirSync(dslDir, { recursive: true });
|
|
85
|
-
node_fs_1.default.writeFileSync(node_path_1.default.join(dslDir, "AnQst-Spec-DSL.d.ts"), loadDslSource(), "utf8");
|
|
212
|
+
return [...settings.generate];
|
|
213
|
+
}
|
|
214
|
+
function resolveAnQstWidgetCategory(cwd) {
|
|
215
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
216
|
+
return settings.widgetCategory;
|
|
217
|
+
}
|
|
218
|
+
function resolveAnQstWidgetName(cwd) {
|
|
219
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
220
|
+
return settings.widgetName;
|
|
86
221
|
}
|
|
87
222
|
function buildSpecScaffold(widgetName) {
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
223
|
+
return `${ANQST_DSL_IMPORT_LINE}
|
|
224
|
+
|
|
225
|
+
` +
|
|
226
|
+
`declare namespace ${widgetName} {
|
|
227
|
+
|
|
228
|
+
` +
|
|
229
|
+
`}
|
|
93
230
|
`;
|
|
94
231
|
}
|
|
95
232
|
function runInstill(cwd, widgetName) {
|
|
96
|
-
|
|
97
|
-
throw new errors_1.VerifyError("Usage: anqst instill <WidgetName>");
|
|
98
|
-
}
|
|
99
|
-
const cleanName = widgetName.trim();
|
|
233
|
+
const cleanName = validateWidgetName(widgetName);
|
|
100
234
|
const { packagePath, packageJson } = readProjectPackage(cwd);
|
|
101
|
-
if (packageJson.AnQst) {
|
|
235
|
+
if (packageJson.AnQst !== undefined) {
|
|
102
236
|
throw new errors_1.VerifyError("AnQst already instilled, did you mean to run 'npx anqst build'?");
|
|
103
237
|
}
|
|
104
|
-
const
|
|
238
|
+
const anqstRoot = (0, layout_1.anqstRootDir)(cwd);
|
|
239
|
+
node_fs_1.default.mkdirSync(anqstRoot, { recursive: true });
|
|
240
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(anqstRoot, "generated"), { recursive: true });
|
|
241
|
+
const specPath = node_path_1.default.join(anqstRoot, (0, layout_1.anqstSpecFileName)(cleanName));
|
|
242
|
+
if (node_fs_1.default.existsSync(specPath)) {
|
|
243
|
+
const existingText = node_fs_1.default.readFileSync(specPath, "utf8");
|
|
244
|
+
const normalizedImport = normalizeAnQstImport(existingText);
|
|
245
|
+
if (normalizedImport.changed) {
|
|
246
|
+
node_fs_1.default.writeFileSync(specPath, normalizedImport.nextText, "utf8");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
node_fs_1.default.writeFileSync(specPath, buildSpecScaffold(cleanName), "utf8");
|
|
251
|
+
}
|
|
252
|
+
const settingsPath = node_path_1.default.join(anqstRoot, (0, layout_1.anqstSettingsFileName)(cleanName));
|
|
253
|
+
const settings = {
|
|
254
|
+
layoutVersion: layout_1.ANQST_LAYOUT_VERSION,
|
|
255
|
+
widgetName: cleanName,
|
|
256
|
+
spec: `./AnQst/${(0, layout_1.anqstSpecFileName)(cleanName)}`,
|
|
257
|
+
generate: [...exports.DEFAULT_ANQST_GENERATE_TARGETS],
|
|
258
|
+
widgetCategory: "AnQst Widgets"
|
|
259
|
+
};
|
|
260
|
+
node_fs_1.default.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
261
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(anqstRoot, ".gitignore"), "/generated*\n", "utf8");
|
|
262
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(anqstRoot, "README.md"), buildAnQstDirectoryReadme(cleanName), "utf8");
|
|
263
|
+
const nextPackage = {
|
|
105
264
|
...packageJson,
|
|
106
265
|
scripts: {
|
|
107
|
-
...packageJson.scripts,
|
|
108
|
-
|
|
109
|
-
|
|
266
|
+
...(packageJson.scripts ?? {}),
|
|
267
|
+
postinstall: ensureBuildHook(packageJson.scripts?.postinstall),
|
|
268
|
+
prebuild: ensureBuildHook(packageJson.scripts?.prebuild),
|
|
269
|
+
prestart: ensureBuildHook(packageJson.scripts?.prestart)
|
|
110
270
|
},
|
|
111
|
-
AnQst:
|
|
112
|
-
spec: `${cleanName}.AnQst.d.ts`,
|
|
113
|
-
generate: [...exports.DEFAULT_ANQST_GENERATE_TARGETS]
|
|
114
|
-
}
|
|
271
|
+
AnQst: (0, layout_1.anqstSettingsRelativePath)(cleanName)
|
|
115
272
|
};
|
|
116
|
-
node_fs_1.default.writeFileSync(packagePath, `${JSON.stringify(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
node_fs_1.default.writeFileSync(specPath, buildSpecScaffold(cleanName), "utf8");
|
|
120
|
-
}
|
|
121
|
-
installDslShim(cwd);
|
|
122
|
-
return `Instill completed: configured package.json and scaffolded ${cleanName}.AnQst.d.ts`;
|
|
273
|
+
node_fs_1.default.writeFileSync(packagePath, `${JSON.stringify(nextPackage, null, 2)}\n`, "utf8");
|
|
274
|
+
updateTsConfig(cwd, cleanName);
|
|
275
|
+
return `Instill completed: configured package.json and scaffolded ${(0, layout_1.normalizeSlashes)(node_path_1.default.relative(cwd, specPath))}`;
|
|
123
276
|
}
|