@dusted/anqst 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,13 +7,14 @@ exports.DEFAULT_ANQST_GENERATE_TARGETS = void 0;
7
7
  exports.readProjectPackage = readProjectPackage;
8
8
  exports.resolveAnQstSpecPath = resolveAnQstSpecPath;
9
9
  exports.resolveAnQstGenerateTargets = resolveAnQstGenerateTargets;
10
- exports.installDslShim = installDslShim;
10
+ exports.resolveAnQstWidgetCategory = resolveAnQstWidgetCategory;
11
11
  exports.buildSpecScaffold = buildSpecScaffold;
12
12
  exports.runInstill = runInstill;
13
13
  const node_fs_1 = __importDefault(require("node:fs"));
14
14
  const node_path_1 = __importDefault(require("node:path"));
15
15
  const errors_1 = require("./errors");
16
16
  exports.DEFAULT_ANQST_GENERATE_TARGETS = ["QWidget", "AngularService", "//DOM", "//node_express_ws"];
17
+ const ANQST_DSL_IMPORT_LINE = 'import { AnQst } from "anqst";';
17
18
  function readJsonFile(filePath) {
18
19
  const raw = node_fs_1.default.readFileSync(filePath, "utf8");
19
20
  return JSON.parse(raw);
@@ -55,43 +56,83 @@ function resolveAnQstGenerateTargets(cwd) {
55
56
  }
56
57
  return [...configured];
57
58
  }
58
- function loadDslSource() {
59
- const candidates = [
60
- node_path_1.default.resolve(__dirname, "../../spec/AnQst-Spec-DSL.d.ts"),
61
- node_path_1.default.resolve(__dirname, "../../../spec/AnQst-Spec-DSL.d.ts")
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");
59
+ function resolveAnQstWidgetCategory(cwd) {
60
+ const { packageJson } = readProjectPackage(cwd);
61
+ const configured = packageJson.AnQst?.widgetCategory;
62
+ if (configured === undefined) {
63
+ return undefined;
64
+ }
65
+ if (typeof configured !== "string") {
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;
86
73
  }
87
74
  function buildSpecScaffold(widgetName) {
88
- return `import { AnQst } from "./anqst-dsl/AnQst-Spec-DSL";
89
-
90
- declare namespace ${widgetName} {
91
-
92
- }
75
+ return `${ANQST_DSL_IMPORT_LINE}
76
+
77
+ declare namespace ${widgetName} {
78
+
79
+ }
93
80
  `;
94
81
  }
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
+ }
95
136
  function runInstill(cwd, widgetName) {
96
137
  if (!widgetName || widgetName.trim().length === 0) {
97
138
  throw new errors_1.VerifyError("Usage: anqst instill <WidgetName>");
@@ -101,6 +142,23 @@ function runInstill(cwd, widgetName) {
101
142
  if (packageJson.AnQst) {
102
143
  throw new errors_1.VerifyError("AnQst already instilled, did you mean to run 'npx anqst build'?");
103
144
  }
145
+ let resolvedWidgetName = cleanName;
146
+ const requestedSpecPath = node_path_1.default.join(cwd, `${cleanName}.AnQst.d.ts`);
147
+ const requestedSpecFileName = node_path_1.default.basename(requestedSpecPath);
148
+ if (node_fs_1.default.existsSync(requestedSpecPath)) {
149
+ const existingText = node_fs_1.default.readFileSync(requestedSpecPath, "utf8");
150
+ const normalizedImport = normalizeAnQstImport(existingText);
151
+ if (normalizedImport.changed) {
152
+ node_fs_1.default.writeFileSync(requestedSpecPath, normalizedImport.nextText, "utf8");
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
+ }
160
+ }
161
+ }
104
162
  const next = {
105
163
  ...packageJson,
106
164
  scripts: {
@@ -109,15 +167,27 @@ function runInstill(cwd, widgetName) {
109
167
  test: prependScript(packageJson.scripts?.test, "npx anqst test")
110
168
  },
111
169
  AnQst: {
112
- spec: `${cleanName}.AnQst.d.ts`,
170
+ spec: `${resolvedWidgetName}.AnQst.d.ts`,
113
171
  generate: [...exports.DEFAULT_ANQST_GENERATE_TARGETS]
114
172
  }
115
173
  };
116
174
  node_fs_1.default.writeFileSync(packagePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
117
- const specPath = node_path_1.default.join(cwd, `${cleanName}.AnQst.d.ts`);
118
- if (!node_fs_1.default.existsSync(specPath)) {
119
- node_fs_1.default.writeFileSync(specPath, buildSpecScaffold(cleanName), "utf8");
175
+ const resolvedSpecPath = node_path_1.default.join(cwd, `${resolvedWidgetName}.AnQst.d.ts`);
176
+ let createdScaffold = false;
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
+ }
120
190
  }
121
- installDslShim(cwd);
122
- return `Instill completed: configured package.json and scaffolded ${cleanName}.AnQst.d.ts`;
191
+ const mode = createdScaffold ? "scaffolded" : "using";
192
+ return `Instill completed: configured package.json and ${mode} ${resolvedWidgetName}.AnQst.d.ts`;
123
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dusted/anqst",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Opinionated backend generator for webapps.",
5
5
  "keywords": [
6
6
  "nodejs",
@@ -38,16 +38,19 @@
38
38
  },
39
39
  "scripts": {
40
40
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
41
- "build": "npm run clean && tsc -p tsconfig.build.json",
42
- "build:test": "npm run clean && tsc -p tsconfig.json",
41
+ "chmod:bin": "node -e \"require('node:fs').chmodSync('dist/src/bin/anqst.js', 0o755)\"",
42
+ "build": "node scripts/build-with-stamp.js",
43
+ "build:test": "npm run clean && tsc -p tsconfig.json && npm run chmod:bin",
43
44
  "prepare": "npm run build",
44
45
  "test": "npm run build:test && node --test dist/test/**/*.test.js",
45
46
  "start": "node dist/src/bin/anqst.js"
46
47
  },
47
48
  "dependencies": {
49
+ "pngjs": "^7.0.0",
48
50
  "typescript": "^5.9.2"
49
51
  },
50
52
  "devDependencies": {
51
- "@types/node": "^24.3.0"
53
+ "@types/node": "^24.3.0",
54
+ "@types/pngjs": "^6.0.5"
52
55
  }
53
56
  }