@dusted/anqst 0.1.1 → 0.1.3
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 +99 -117
- package/dist/src/app.js +180 -272
- package/dist/src/build-stamp.js +5 -0
- package/dist/src/{backend/tsc/debug-dump.js → debug-dump.js} +2 -1
- package/dist/src/emit.js +368 -170
- package/dist/src/layout.js +70 -0
- package/dist/src/parser.js +124 -6
- package/dist/src/{backend/tsc/program.js → program.js} +1 -1
- package/dist/src/project.js +220 -137
- package/dist/src/verify.js +15 -2
- package/index.d.ts +1 -0
- package/package.json +7 -2
- package/spec/AnQst-Spec-DSL.d.ts +49 -17
- package/dist/src/backend/ast/emit.js +0 -5
- package/dist/src/backend/ast/index.js +0 -13
- package/dist/src/backend/ast/parser.js +0 -5
- package/dist/src/backend/ast/verify.js +0 -5
- package/dist/src/backend/index.js +0 -16
- package/dist/src/backend/tsc/emit-cpp.js +0 -13
- package/dist/src/backend/tsc/emit-node.js +0 -13
- package/dist/src/backend/tsc/index.js +0 -41
- package/dist/src/backend/tsc/parser.js +0 -19
- package/dist/src/backend/tsc/verify.js +0 -13
- package/dist/src/backend/types.js +0 -2
- /package/dist/src/{backend/tsc/typegraph.js → typegraph.js} +0 -0
package/dist/src/project.js
CHANGED
|
@@ -5,189 +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
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
|
-
|
|
17
|
-
|
|
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";
|
|
18
22
|
function readJsonFile(filePath) {
|
|
19
|
-
|
|
20
|
-
|
|
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;
|
|
21
37
|
}
|
|
22
|
-
function
|
|
23
|
-
if (!existing || existing.trim().length === 0)
|
|
24
|
-
return
|
|
38
|
+
function ensureBuildHook(existing) {
|
|
39
|
+
if (!existing || existing.trim().length === 0) {
|
|
40
|
+
return ANQST_BUILD_HOOK;
|
|
41
|
+
}
|
|
25
42
|
const trimmed = existing.trim();
|
|
26
|
-
if (trimmed ===
|
|
43
|
+
if (trimmed === ANQST_BUILD_HOOK || trimmed.startsWith(`${ANQST_BUILD_HOOK} &&`) || trimmed.includes(ANQST_BUILD_HOOK)) {
|
|
27
44
|
return trimmed;
|
|
28
|
-
|
|
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;
|
|
29
184
|
}
|
|
30
185
|
function readProjectPackage(cwd) {
|
|
31
186
|
const packagePath = node_path_1.default.join(cwd, "package.json");
|
|
32
187
|
if (!node_fs_1.default.existsSync(packagePath)) {
|
|
33
|
-
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.");
|
|
34
189
|
}
|
|
35
190
|
return {
|
|
36
191
|
packagePath,
|
|
37
192
|
packageJson: readJsonFile(packagePath)
|
|
38
193
|
};
|
|
39
194
|
}
|
|
40
|
-
function
|
|
195
|
+
function resolveAnQstSettings(cwd) {
|
|
41
196
|
const { packageJson } = readProjectPackage(cwd);
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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);
|
|
47
206
|
}
|
|
48
207
|
function resolveAnQstGenerateTargets(cwd) {
|
|
49
|
-
const {
|
|
50
|
-
|
|
51
|
-
if (configured === undefined) {
|
|
208
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
209
|
+
if (!settings.generate) {
|
|
52
210
|
return [...exports.DEFAULT_ANQST_GENERATE_TARGETS];
|
|
53
211
|
}
|
|
54
|
-
|
|
55
|
-
throw new errors_1.VerifyError("Invalid package.json key 'AnQst.generate': expected string array.");
|
|
56
|
-
}
|
|
57
|
-
return [...configured];
|
|
212
|
+
return [...settings.generate];
|
|
58
213
|
}
|
|
59
214
|
function resolveAnQstWidgetCategory(cwd) {
|
|
60
|
-
const {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
throw new errors_1.VerifyError("Invalid package.json key 'AnQst.widgetCategory': expected string.");
|
|
67
|
-
}
|
|
68
|
-
const trimmed = configured.trim();
|
|
69
|
-
if (trimmed.length === 0) {
|
|
70
|
-
throw new errors_1.VerifyError("Invalid package.json key 'AnQst.widgetCategory': expected non-empty string.");
|
|
71
|
-
}
|
|
72
|
-
return trimmed;
|
|
215
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
216
|
+
return settings.widgetCategory;
|
|
217
|
+
}
|
|
218
|
+
function resolveAnQstWidgetName(cwd) {
|
|
219
|
+
const { settings } = resolveAnQstSettings(cwd);
|
|
220
|
+
return settings.widgetName;
|
|
73
221
|
}
|
|
74
222
|
function buildSpecScaffold(widgetName) {
|
|
75
223
|
return `${ANQST_DSL_IMPORT_LINE}
|
|
76
224
|
|
|
77
|
-
|
|
225
|
+
` +
|
|
226
|
+
`declare namespace ${widgetName} {
|
|
78
227
|
|
|
79
|
-
|
|
228
|
+
` +
|
|
229
|
+
`}
|
|
80
230
|
`;
|
|
81
231
|
}
|
|
82
|
-
function normalizeAnQstImport(sourceText) {
|
|
83
|
-
const importPattern = /^\s*import\s+\{\s*AnQst\s*\}\s+from\s+["'][^"']+["'];\s*$/m;
|
|
84
|
-
if (importPattern.test(sourceText)) {
|
|
85
|
-
const nextText = sourceText.replace(importPattern, ANQST_DSL_IMPORT_LINE);
|
|
86
|
-
return { nextText, changed: nextText !== sourceText };
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
nextText: `${ANQST_DSL_IMPORT_LINE}\n\n${sourceText}`,
|
|
90
|
-
changed: true
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function extractDeclaredNamespace(specText) {
|
|
94
|
-
const match = specText.match(/^\s*declare\s+namespace\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\{/m);
|
|
95
|
-
return match ? match[1] : null;
|
|
96
|
-
}
|
|
97
|
-
function readLineSync() {
|
|
98
|
-
const out = [];
|
|
99
|
-
const buf = Buffer.alloc(1);
|
|
100
|
-
while (true) {
|
|
101
|
-
const bytesRead = node_fs_1.default.readSync(0, buf, 0, 1, null);
|
|
102
|
-
if (bytesRead <= 0)
|
|
103
|
-
break;
|
|
104
|
-
const code = buf[0];
|
|
105
|
-
if (code === 10)
|
|
106
|
-
break; // \n
|
|
107
|
-
if (code === 13)
|
|
108
|
-
continue; // \r
|
|
109
|
-
out.push(code);
|
|
110
|
-
}
|
|
111
|
-
return Buffer.from(out).toString("utf8").trim();
|
|
112
|
-
}
|
|
113
|
-
function chooseWidgetNamePreference(argumentName, namespaceName, specFileName) {
|
|
114
|
-
const envChoice = process.env.ANQST_INSTILL_WIDGET_NAME_CHOICE;
|
|
115
|
-
if (envChoice === "argument" || envChoice === "namespace") {
|
|
116
|
-
return envChoice;
|
|
117
|
-
}
|
|
118
|
-
if (!process.stdin.isTTY) {
|
|
119
|
-
console.warn(`[AnQst] Existing template ${specFileName} declares namespace '${namespaceName}', but command used '${argumentName}'. Non-interactive session; defaulting to '${argumentName}'.`);
|
|
120
|
-
return "argument";
|
|
121
|
-
}
|
|
122
|
-
console.log(`[AnQst] Existing template ${specFileName} declares namespace '${namespaceName}'.`);
|
|
123
|
-
console.log(`Choose widget name: [1] ${argumentName} (command argument), [2] ${namespaceName} (template namespace)`);
|
|
124
|
-
while (true) {
|
|
125
|
-
process.stdout.write("Selection [1/2]: ");
|
|
126
|
-
const answer = readLineSync().toLowerCase();
|
|
127
|
-
if (answer === "1" || answer === argumentName.toLowerCase() || answer === "argument") {
|
|
128
|
-
return "argument";
|
|
129
|
-
}
|
|
130
|
-
if (answer === "2" || answer === namespaceName.toLowerCase() || answer === "namespace") {
|
|
131
|
-
return "namespace";
|
|
132
|
-
}
|
|
133
|
-
console.log("Please type 1 or 2.");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
232
|
function runInstill(cwd, widgetName) {
|
|
137
|
-
|
|
138
|
-
throw new errors_1.VerifyError("Usage: anqst instill <WidgetName>");
|
|
139
|
-
}
|
|
140
|
-
const cleanName = widgetName.trim();
|
|
233
|
+
const cleanName = validateWidgetName(widgetName);
|
|
141
234
|
const { packagePath, packageJson } = readProjectPackage(cwd);
|
|
142
|
-
if (packageJson.AnQst) {
|
|
235
|
+
if (packageJson.AnQst !== undefined) {
|
|
143
236
|
throw new errors_1.VerifyError("AnQst already instilled, did you mean to run 'npx anqst build'?");
|
|
144
237
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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");
|
|
150
244
|
const normalizedImport = normalizeAnQstImport(existingText);
|
|
151
245
|
if (normalizedImport.changed) {
|
|
152
|
-
node_fs_1.default.writeFileSync(
|
|
153
|
-
}
|
|
154
|
-
const declaredNamespace = extractDeclaredNamespace(normalizedImport.nextText);
|
|
155
|
-
if (declaredNamespace && declaredNamespace !== cleanName) {
|
|
156
|
-
const choice = chooseWidgetNamePreference(cleanName, declaredNamespace, requestedSpecFileName);
|
|
157
|
-
if (choice === "namespace") {
|
|
158
|
-
resolvedWidgetName = declaredNamespace;
|
|
159
|
-
}
|
|
246
|
+
node_fs_1.default.writeFileSync(specPath, normalizedImport.nextText, "utf8");
|
|
160
247
|
}
|
|
161
248
|
}
|
|
162
|
-
|
|
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 = {
|
|
163
264
|
...packageJson,
|
|
164
265
|
scripts: {
|
|
165
|
-
...packageJson.scripts,
|
|
166
|
-
|
|
167
|
-
|
|
266
|
+
...(packageJson.scripts ?? {}),
|
|
267
|
+
postinstall: ensureBuildHook(packageJson.scripts?.postinstall),
|
|
268
|
+
prebuild: ensureBuildHook(packageJson.scripts?.prebuild),
|
|
269
|
+
prestart: ensureBuildHook(packageJson.scripts?.prestart)
|
|
168
270
|
},
|
|
169
|
-
AnQst:
|
|
170
|
-
spec: `${resolvedWidgetName}.AnQst.d.ts`,
|
|
171
|
-
generate: [...exports.DEFAULT_ANQST_GENERATE_TARGETS]
|
|
172
|
-
}
|
|
271
|
+
AnQst: (0, layout_1.anqstSettingsRelativePath)(cleanName)
|
|
173
272
|
};
|
|
174
|
-
node_fs_1.default.writeFileSync(packagePath, `${JSON.stringify(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (!node_fs_1.default.existsSync(resolvedSpecPath)) {
|
|
178
|
-
if (resolvedWidgetName !== cleanName && node_fs_1.default.existsSync(requestedSpecPath)) {
|
|
179
|
-
node_fs_1.default.renameSync(requestedSpecPath, resolvedSpecPath);
|
|
180
|
-
const movedText = node_fs_1.default.readFileSync(resolvedSpecPath, "utf8");
|
|
181
|
-
const normalizedMovedImport = normalizeAnQstImport(movedText);
|
|
182
|
-
if (normalizedMovedImport.changed) {
|
|
183
|
-
node_fs_1.default.writeFileSync(resolvedSpecPath, normalizedMovedImport.nextText, "utf8");
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
node_fs_1.default.writeFileSync(resolvedSpecPath, buildSpecScaffold(resolvedWidgetName), "utf8");
|
|
188
|
-
createdScaffold = true;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
const mode = createdScaffold ? "scaffolded" : "using";
|
|
192
|
-
return `Instill completed: configured package.json and ${mode} ${resolvedWidgetName}.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))}`;
|
|
193
276
|
}
|
package/dist/src/verify.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.verifySpec = verifySpec;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
8
|
const errors_1 = require("./errors");
|
|
9
|
+
const program_1 = require("./program");
|
|
9
10
|
function parseTypeNodeFromText(typeText) {
|
|
10
11
|
const source = typescript_1.default.createSourceFile("__inline__.ts", `type __X = ${typeText};`, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
11
12
|
const stmt = source.statements.find(typescript_1.default.isTypeAliasDeclaration);
|
|
@@ -170,7 +171,7 @@ function collectReachableTypeNames(spec) {
|
|
|
170
171
|
}
|
|
171
172
|
return seen;
|
|
172
173
|
}
|
|
173
|
-
function
|
|
174
|
+
function verifySpecSemantics(spec) {
|
|
174
175
|
checkServiceDuplicates(spec);
|
|
175
176
|
for (const service of spec.services) {
|
|
176
177
|
for (const member of service.members) {
|
|
@@ -193,8 +194,20 @@ function verifySpec(spec) {
|
|
|
193
194
|
reachableGeneratedTypes: reachable.size,
|
|
194
195
|
serviceCount: spec.services.length
|
|
195
196
|
};
|
|
197
|
+
const warnings = [...spec.warnings];
|
|
198
|
+
const warningSummary = warnings.length === 0
|
|
199
|
+
? ""
|
|
200
|
+
: `\nWarnings:\n${warnings.map((w) => ` [warn] ${w.loc.file}:${w.loc.line}:${w.loc.column} ${w.memberPath} - ${w.message}`).join("\n")}`;
|
|
196
201
|
return {
|
|
197
202
|
stats,
|
|
198
|
-
message: `AnQst spec valid:\n ${stats.namespaceDeclaredTypes} types.\n ${stats.serviceCount} services
|
|
203
|
+
message: `AnQst spec valid:\n ${stats.namespaceDeclaredTypes} types.\n ${stats.serviceCount} services.${warningSummary}`,
|
|
204
|
+
warnings
|
|
199
205
|
};
|
|
200
206
|
}
|
|
207
|
+
function verifySpec(spec) {
|
|
208
|
+
const diagnostics = (0, program_1.getProgramDiagnostics)(spec.filePath);
|
|
209
|
+
if (diagnostics.length > 0) {
|
|
210
|
+
throw new errors_1.VerifyError(`TypeScript diagnostics in spec:\n ${diagnostics.join("\n ")}`);
|
|
211
|
+
}
|
|
212
|
+
return verifySpecSemantics(spec);
|
|
213
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AnQst } from "./spec/AnQst-Spec-DSL";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dusted/anqst",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Opinionated backend generator for webapps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nodejs",
|
|
@@ -21,14 +21,19 @@
|
|
|
21
21
|
"author": "DusteD",
|
|
22
22
|
"type": "commonjs",
|
|
23
23
|
"main": "dist/src/app.js",
|
|
24
|
+
"types": "index.d.ts",
|
|
24
25
|
"exports": {
|
|
25
|
-
".":
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./index.d.ts",
|
|
28
|
+
"default": "./dist/src/app.js"
|
|
29
|
+
}
|
|
26
30
|
},
|
|
27
31
|
"bin": {
|
|
28
32
|
"anqst": "dist/src/bin/anqst.js"
|
|
29
33
|
},
|
|
30
34
|
"files": [
|
|
31
35
|
"dist/src",
|
|
36
|
+
"index.d.ts",
|
|
32
37
|
"spec/AnQst-Spec-DSL.d.ts",
|
|
33
38
|
"README.md",
|
|
34
39
|
"LICENSE"
|
package/spec/AnQst-Spec-DSL.d.ts
CHANGED
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
* Not for use by TypeScript application implementation.
|
|
12
12
|
* @example
|
|
13
13
|
* package.json:
|
|
14
|
-
* - "AnQst":
|
|
14
|
+
* - "AnQst": "./AnQst/MyUserMgmtWidget.settings.json"
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
export namespace AnQst {
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Declare service `InterfaceName`
|
|
21
|
-
*
|
|
21
|
+
*
|
|
22
22
|
* @remarks
|
|
23
23
|
* Multiple allowed.
|
|
24
24
|
* Affords developers of advanced widgets the ability to create domain-informed categories.
|
|
25
25
|
* - Duplicate method declarations with identical parameter lists are invalid in normative AnQst-Spec input.
|
|
26
|
-
*
|
|
26
|
+
*
|
|
27
27
|
* @example
|
|
28
28
|
* export interface UserService extends Widget.Service { }
|
|
29
29
|
* // Generates UserService.
|
|
@@ -44,6 +44,7 @@ export namespace AnQst {
|
|
|
44
44
|
*/
|
|
45
45
|
interface AngularHTTPBaseServerClass extends Service { }
|
|
46
46
|
|
|
47
|
+
type CallConfig = { timeoutSeconds: number } | { timeoutMilliseconds: number } | {};
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* Declare non-blocking service method `MethodName`(`MethodArguments`): Promise<`MethodReturnType`>.
|
|
@@ -52,27 +53,38 @@ export namespace AnQst {
|
|
|
52
53
|
* - Flow:
|
|
53
54
|
* - Widget: Call to service method `MethodName`(`MethodArguments`).
|
|
54
55
|
* - Widget: Returns Promise<`MethodReturnType`>.
|
|
55
|
-
* - Parent: Registered
|
|
56
|
-
* -
|
|
57
|
-
*
|
|
56
|
+
* - Parent: Registered callback receives args and returns `T` synchronously.
|
|
57
|
+
* - Widget: Promise resolves with payload `T` or rejects with plain error object.
|
|
58
|
+
* - Optional config supports timeout tuning:
|
|
59
|
+
* - `AnQst.Call<T, { timeoutSeconds: N }>`
|
|
60
|
+
* - `AnQst.Call<T, { timeoutMilliseconds: N }>`
|
|
61
|
+
* - Exactly one timeout key is allowed. Value must be integer >= 0.
|
|
62
|
+
* - Default timeout is 120s. `0` means wait forever.
|
|
58
63
|
* @example
|
|
59
64
|
* // AnQst spec:
|
|
60
65
|
* getUserById(userId: string): AnQst.Call<User>
|
|
61
66
|
* //Angular app:
|
|
62
67
|
* const user: User = await this.userService.getUserById("abc");
|
|
63
68
|
*/
|
|
64
|
-
interface Call<T> { dummy: T }
|
|
69
|
+
interface Call<T, Config extends CallConfig = {}> { dummy: T; config?: Config }
|
|
65
70
|
|
|
66
|
-
/**
|
|
67
|
-
* Declare blocking service method onSlot.`MethodName`( handler(`MethodArguments`):`MethodReturnType` ): void
|
|
71
|
+
/**
|
|
72
|
+
* Declare blocking service method onSlot.`MethodName`( handler(`MethodArguments`):`MethodReturnType` ): void
|
|
68
73
|
* @remarks
|
|
69
74
|
* - **Parent** -> Widget
|
|
70
75
|
* - Impl note: Autogenerated stub handler queues until handler is set (set method calls spools queue through handler)
|
|
71
76
|
* - Flow:
|
|
72
77
|
* - Parent: Call to generated widget method `MethodName`(`MethodArguments`).
|
|
73
|
-
* - Widget: Registered handler(`MethodArguments`)
|
|
74
|
-
* - Widget: Handler
|
|
78
|
+
* - Widget: Registered handler(`MethodArguments`) is called.
|
|
79
|
+
* - Widget: Handler return forms:
|
|
80
|
+
* - `T` -> success payload
|
|
81
|
+
* - `Promise<T>` -> awaited, success payload on resolve
|
|
82
|
+
* - `Error` -> failure
|
|
83
|
+
* - throw -> failure
|
|
84
|
+
* - rejected promise -> failure
|
|
75
85
|
* - Parent: `MethodName` call returns with result.
|
|
86
|
+
* - Generated C++ Slot methods do not expose `ok/error` out parameters.
|
|
87
|
+
* - Default Slot timeout is 1000ms.
|
|
76
88
|
* Note: One active handler, calling will replace existing and is valid and allowed.
|
|
77
89
|
* @example
|
|
78
90
|
* // AnQst spec:
|
|
@@ -95,6 +107,7 @@ export namespace AnQst {
|
|
|
95
107
|
* - Widget: Call to service method `MethodName`(`MethodArguments`).
|
|
96
108
|
* - Widget: Returns void.
|
|
97
109
|
* - Parent: Might have something connected to the signal, might not.
|
|
110
|
+
* - If no listener is connected, event is dropped.
|
|
98
111
|
* @example
|
|
99
112
|
* // AnQst spec:
|
|
100
113
|
* complain(whine: string): AnQst.Emitter;
|
|
@@ -144,20 +157,20 @@ export namespace AnQst {
|
|
|
144
157
|
|
|
145
158
|
/**
|
|
146
159
|
* AnQst-Spec type mapping overview and control.
|
|
147
|
-
*
|
|
160
|
+
*
|
|
148
161
|
* @remarks
|
|
149
162
|
* Any Type that be mapped between TypeScript and Qt, C++ standard types or
|
|
150
163
|
* Plain Old Data (POD) types (in that order of preference) will be mapped by default.
|
|
151
|
-
*
|
|
164
|
+
*
|
|
152
165
|
* Canonical mapping directive namespace is AnQst.Type.<type>.
|
|
153
166
|
* To express advisory mapping preference, use AnQst.Type.<type>.
|
|
154
167
|
* Advisory means generator SHOULD honor it, but MAY fall back to inferred/default mapping and emit diagnostic.
|
|
155
168
|
* Array forms are equivalent: T[] == Array<T>, and AnQst.Type.X[] == Array<AnQst.Type.X>.
|
|
156
|
-
*
|
|
169
|
+
*
|
|
157
170
|
* TypeScript definitions+classes and C++ structs are generated for each
|
|
158
171
|
* structured TypeScript type ( type = {...} or interface { ... } )
|
|
159
172
|
* referenced in an AnQst spec.
|
|
160
|
-
*
|
|
173
|
+
*
|
|
161
174
|
*/
|
|
162
175
|
enum Type {
|
|
163
176
|
object = "JavaScript Object <-> QVariantMap (JSON.stringify/parse semantics)",
|
|
@@ -195,13 +208,13 @@ export namespace AnQst {
|
|
|
195
208
|
|
|
196
209
|
/**
|
|
197
210
|
* These are explicitly forbidden argument/return types.
|
|
198
|
-
*
|
|
211
|
+
*
|
|
199
212
|
* @remarks
|
|
200
213
|
* - They may not be referenced in AnQst specs or imported types.
|
|
201
214
|
* - Service methods cannot accept them as arguments
|
|
202
215
|
* - Service methods cannot return them.
|
|
203
216
|
* - Service properties of their type cannot be declared.
|
|
204
|
-
*
|
|
217
|
+
*
|
|
205
218
|
* - For Objects/Maps/Sets use AnQst.Type.<Type> instead.
|
|
206
219
|
*/
|
|
207
220
|
export enum ForbiddenType {
|
|
@@ -213,5 +226,24 @@ export namespace AnQst {
|
|
|
213
226
|
any = "Passing 'any' type across the boundary is not allowed",
|
|
214
227
|
}
|
|
215
228
|
|
|
229
|
+
/**
|
|
230
|
+
* JS/TS Error instances can be returned, but they have special meaning.
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* AnQst is opinionated, Errors are not for control flow. They are for signalling unrecoverable and unhandled circumstance.
|
|
234
|
+
* Use: To indicate unrecoverable program error/wrong use.
|
|
235
|
+
* Don't use: To communicate expected and/or ignorable/handable situations, define a domain-specific transport type instad.
|
|
236
|
+
* When else encountered: Unhandled Errors.
|
|
237
|
+
* Effect: The receiving end throws an exception with message `<service>.<member> emitted error: <WidgetStackTrace>`
|
|
238
|
+
* AnQst internal behavior:
|
|
239
|
+
* When AnQst type translation/mapping encounters a true Error object instance, the Error object is not transported, instead
|
|
240
|
+
* the sending AnQst code signals to the receiving AnQst code a message, and the receiving AnQst code throws a runtime exception on
|
|
241
|
+
* the call or callback site in the Parent.
|
|
242
|
+
|
|
243
|
+
*/
|
|
244
|
+
export enum ExceptionalType {
|
|
245
|
+
Error = "AnQst will not transport an Error object, but will cause an exception to be thrown on reception site."
|
|
246
|
+
}
|
|
247
|
+
|
|
216
248
|
}
|
|
217
249
|
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateOutputs = void 0;
|
|
4
|
-
var emit_1 = require("../../emit");
|
|
5
|
-
Object.defineProperty(exports, "generateOutputs", { enumerable: true, get: function () { return emit_1.generateOutputs; } });
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.astBackend = void 0;
|
|
4
|
-
const emit_1 = require("./emit");
|
|
5
|
-
const parser_1 = require("./parser");
|
|
6
|
-
const verify_1 = require("./verify");
|
|
7
|
-
exports.astBackend = {
|
|
8
|
-
id: "ast",
|
|
9
|
-
parseSpecFile: parser_1.parseSpecFile,
|
|
10
|
-
verifySpec: verify_1.verifySpec,
|
|
11
|
-
generateOutputs: emit_1.generateOutputs,
|
|
12
|
-
emitsArtifacts: true
|
|
13
|
-
};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseSpecFile = void 0;
|
|
4
|
-
var parser_1 = require("../../parser");
|
|
5
|
-
Object.defineProperty(exports, "parseSpecFile", { enumerable: true, get: function () { return parser_1.parseSpecFile; } });
|