@kithinji/pod 1.0.0
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/build.js +22 -0
- package/dist/main.js +4464 -0
- package/dist/main.js.map +7 -0
- package/dist/types/add/component/component.d.ts +5 -0
- package/dist/types/add/component/component.d.ts.map +1 -0
- package/dist/types/add/component/index.d.ts +2 -0
- package/dist/types/add/component/index.d.ts.map +1 -0
- package/dist/types/add/index.d.ts +4 -0
- package/dist/types/add/index.d.ts.map +1 -0
- package/dist/types/add/module/index.d.ts +2 -0
- package/dist/types/add/module/index.d.ts.map +1 -0
- package/dist/types/add/module/module.d.ts +3 -0
- package/dist/types/add/module/module.d.ts.map +1 -0
- package/dist/types/add/new/index.d.ts +2 -0
- package/dist/types/add/new/index.d.ts.map +1 -0
- package/dist/types/config/config.d.ts +18 -0
- package/dist/types/config/config.d.ts.map +1 -0
- package/dist/types/config/index.d.ts +2 -0
- package/dist/types/config/index.d.ts.map +1 -0
- package/dist/types/dev/index.d.ts +2 -0
- package/dist/types/dev/index.d.ts.map +1 -0
- package/dist/types/dev/project.d.ts +9 -0
- package/dist/types/dev/project.d.ts.map +1 -0
- package/dist/types/dev/server.d.ts +2 -0
- package/dist/types/dev/server.d.ts.map +1 -0
- package/dist/types/docker/docker.d.ts +2 -0
- package/dist/types/docker/docker.d.ts.map +1 -0
- package/dist/types/docker/index.d.ts +2 -0
- package/dist/types/docker/index.d.ts.map +1 -0
- package/dist/types/macros/expand_macros.d.ts +48 -0
- package/dist/types/macros/expand_macros.d.ts.map +1 -0
- package/dist/types/macros/index.d.ts +3 -0
- package/dist/types/macros/index.d.ts.map +1 -0
- package/dist/types/macros/macro_executer.d.ts +12 -0
- package/dist/types/macros/macro_executer.d.ts.map +1 -0
- package/dist/types/main.d.ts +13 -0
- package/dist/types/main.d.ts.map +1 -0
- package/dist/types/plugins/analyzers/graph.d.ts +25 -0
- package/dist/types/plugins/analyzers/graph.d.ts.map +1 -0
- package/dist/types/plugins/css/index.d.ts +7 -0
- package/dist/types/plugins/css/index.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_controller.d.ts +2 -0
- package/dist/types/plugins/generators/generate_controller.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_rsc.d.ts +2 -0
- package/dist/types/plugins/generators/generate_rsc.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_server_component.d.ts +2 -0
- package/dist/types/plugins/generators/generate_server_component.d.ts.map +1 -0
- package/dist/types/plugins/generators/tsx_server_stub.d.ts +2 -0
- package/dist/types/plugins/generators/tsx_server_stub.d.ts.map +1 -0
- package/dist/types/plugins/index.d.ts +4 -0
- package/dist/types/plugins/index.d.ts.map +1 -0
- package/dist/types/plugins/my.d.ts +10 -0
- package/dist/types/plugins/my.d.ts.map +1 -0
- package/dist/types/plugins/transformers/j2d.d.ts +11 -0
- package/dist/types/plugins/transformers/j2d.d.ts.map +1 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/index.d.ts.map +1 -0
- package/dist/types/store/store.d.ts +14 -0
- package/dist/types/store/store.d.ts.map +1 -0
- package/dist/types/utils/cases.d.ts +4 -0
- package/dist/types/utils/cases.d.ts.map +1 -0
- package/dist/types/utils/create.d.ts +12 -0
- package/dist/types/utils/create.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/package.json +44 -0
- package/src/add/component/component.ts +496 -0
- package/src/add/component/index.ts +1 -0
- package/src/add/index.ts +3 -0
- package/src/add/module/index.ts +1 -0
- package/src/add/module/module.ts +521 -0
- package/src/add/new/index.ts +135 -0
- package/src/config/config.ts +141 -0
- package/src/config/index.ts +1 -0
- package/src/dev/index.ts +1 -0
- package/src/dev/project.ts +45 -0
- package/src/dev/server.ts +190 -0
- package/src/docker/docker.ts +452 -0
- package/src/docker/index.ts +1 -0
- package/src/macros/expand_macros.ts +791 -0
- package/src/macros/index.ts +2 -0
- package/src/macros/macro_executer.ts +189 -0
- package/src/main.ts +95 -0
- package/src/plugins/analyzers/graph.ts +291 -0
- package/src/plugins/css/index.ts +25 -0
- package/src/plugins/generators/generate_controller.ts +308 -0
- package/src/plugins/generators/generate_rsc.ts +274 -0
- package/src/plugins/generators/generate_server_component.ts +279 -0
- package/src/plugins/generators/tsx_server_stub.ts +295 -0
- package/src/plugins/index.ts +3 -0
- package/src/plugins/my.ts +274 -0
- package/src/plugins/transformers/j2d.ts +1014 -0
- package/src/store/index.ts +1 -0
- package/src/store/store.ts +44 -0
- package/src/utils/cases.ts +15 -0
- package/src/utils/create.ts +26 -0
- package/src/utils/index.ts +2 -0
- package/tsconfig.json +27 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,4464 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/main.ts
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
|
|
11
|
+
// src/dev/server.ts
|
|
12
|
+
import * as esbuild2 from "esbuild";
|
|
13
|
+
import { spawn } from "child_process";
|
|
14
|
+
import * as fs5 from "fs/promises";
|
|
15
|
+
|
|
16
|
+
// src/config/config.ts
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
import { pathToFileURL } from "url";
|
|
19
|
+
import * as fs from "fs/promises";
|
|
20
|
+
var CONFIG_FILES = [
|
|
21
|
+
"pod.config.js",
|
|
22
|
+
"pod.config.mjs",
|
|
23
|
+
"pod.config.ts",
|
|
24
|
+
"pod.config.mts"
|
|
25
|
+
];
|
|
26
|
+
async function loadConfig(root = process.cwd()) {
|
|
27
|
+
for (const configFile of CONFIG_FILES) {
|
|
28
|
+
const configPath = path.resolve(root, configFile);
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(configPath);
|
|
31
|
+
if (configFile.endsWith(".ts") || configFile.endsWith(".mts")) {
|
|
32
|
+
return await loadTsConfig(configPath);
|
|
33
|
+
}
|
|
34
|
+
return await loadJsConfig(configPath);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return getDefaultConfig();
|
|
40
|
+
}
|
|
41
|
+
async function loadJsConfig(configPath) {
|
|
42
|
+
try {
|
|
43
|
+
const fileUrl = pathToFileURL(configPath).href;
|
|
44
|
+
const configModule = await import(`${fileUrl}?t=${Date.now()}`);
|
|
45
|
+
const config = configModule.default || configModule;
|
|
46
|
+
if (typeof config === "function") {
|
|
47
|
+
return await config();
|
|
48
|
+
}
|
|
49
|
+
return config;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(`\u274C Failed to load config from ${configPath}:`, error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function loadTsConfig(configPath) {
|
|
56
|
+
try {
|
|
57
|
+
const esbuild3 = await import("esbuild");
|
|
58
|
+
const result = await esbuild3.build({
|
|
59
|
+
entryPoints: [configPath],
|
|
60
|
+
bundle: true,
|
|
61
|
+
platform: "node",
|
|
62
|
+
format: "esm",
|
|
63
|
+
write: false,
|
|
64
|
+
sourcemap: "inline",
|
|
65
|
+
packages: "external"
|
|
66
|
+
});
|
|
67
|
+
const tempFile = `${configPath}.${Date.now()}.mjs`;
|
|
68
|
+
await fs.writeFile(tempFile, result.outputFiles[0].text);
|
|
69
|
+
try {
|
|
70
|
+
const fileUrl = pathToFileURL(tempFile).href;
|
|
71
|
+
const configModule = await import(fileUrl);
|
|
72
|
+
const config = configModule.default || configModule;
|
|
73
|
+
if (typeof config === "function") {
|
|
74
|
+
return await config();
|
|
75
|
+
}
|
|
76
|
+
return config;
|
|
77
|
+
} finally {
|
|
78
|
+
await fs.unlink(tempFile).catch(() => {
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(
|
|
83
|
+
`\u274C Failed to load TypeScript config from ${configPath}:`,
|
|
84
|
+
error
|
|
85
|
+
);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function getDefaultConfig() {
|
|
90
|
+
return {
|
|
91
|
+
name: "app",
|
|
92
|
+
build: {
|
|
93
|
+
outDir: "dist",
|
|
94
|
+
sourcemap: true,
|
|
95
|
+
minify: false
|
|
96
|
+
},
|
|
97
|
+
plugins: [],
|
|
98
|
+
client_plugins: [],
|
|
99
|
+
server_plugins: []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function mergeConfig(defaults, userConfig) {
|
|
103
|
+
return {
|
|
104
|
+
name: userConfig.name,
|
|
105
|
+
build: { ...defaults.build, ...userConfig.build },
|
|
106
|
+
plugins: [...defaults.plugins || [], ...userConfig.plugins || []],
|
|
107
|
+
client_plugins: [
|
|
108
|
+
...defaults.client_plugins || [],
|
|
109
|
+
...userConfig.client_plugins || []
|
|
110
|
+
],
|
|
111
|
+
server_plugins: [
|
|
112
|
+
...defaults.server_plugins || [],
|
|
113
|
+
...userConfig.server_plugins || []
|
|
114
|
+
]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/plugins/index.ts
|
|
119
|
+
var plugins_exports = {};
|
|
120
|
+
__export(plugins_exports, {
|
|
121
|
+
buildGraph: () => buildGraph,
|
|
122
|
+
stylePlugin: () => stylePlugin,
|
|
123
|
+
useMyPlugin: () => useMyPlugin
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// src/plugins/my.ts
|
|
127
|
+
import * as fs2 from "fs/promises";
|
|
128
|
+
import { transform } from "@swc/core";
|
|
129
|
+
import * as babel from "@babel/core";
|
|
130
|
+
import * as path6 from "path";
|
|
131
|
+
|
|
132
|
+
// src/macros/index.ts
|
|
133
|
+
var macros_exports = {};
|
|
134
|
+
__export(macros_exports, {
|
|
135
|
+
MacroExecutor: () => MacroExecutor,
|
|
136
|
+
expandMacros: () => expandMacros,
|
|
137
|
+
extractValueFromNode: () => extractValueFromNode,
|
|
138
|
+
getGlobalMacroGraph: () => getGlobalMacroGraph,
|
|
139
|
+
macroExecuter: () => macroExecuter,
|
|
140
|
+
resetGlobalMacroGraph: () => resetGlobalMacroGraph
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/macros/expand_macros.ts
|
|
144
|
+
import ts from "typescript";
|
|
145
|
+
import path3 from "path";
|
|
146
|
+
import Module2 from "node:module";
|
|
147
|
+
|
|
148
|
+
// src/macros/macro_executer.ts
|
|
149
|
+
import * as vm from "node:vm";
|
|
150
|
+
import * as esbuild from "esbuild";
|
|
151
|
+
import * as path2 from "node:path";
|
|
152
|
+
import Module from "node:module";
|
|
153
|
+
var MacroExecutor = class {
|
|
154
|
+
constructor() {
|
|
155
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
156
|
+
}
|
|
157
|
+
getMacro(specifier, macroName) {
|
|
158
|
+
if (!this.cache.has(specifier)) {
|
|
159
|
+
this.load(specifier);
|
|
160
|
+
}
|
|
161
|
+
const macros = this.cache.get(specifier);
|
|
162
|
+
if (macroName) {
|
|
163
|
+
const fn = macros[macroName];
|
|
164
|
+
if (!fn) {
|
|
165
|
+
throw new Error(`Macro "${macroName}" not found in ${specifier}`);
|
|
166
|
+
}
|
|
167
|
+
return fn;
|
|
168
|
+
}
|
|
169
|
+
const names = Object.keys(macros);
|
|
170
|
+
if (names.length === 1) {
|
|
171
|
+
return macros[names[0]];
|
|
172
|
+
}
|
|
173
|
+
if (macros.default) {
|
|
174
|
+
return macros.default;
|
|
175
|
+
}
|
|
176
|
+
throw new Error(`Multiple macros in ${specifier}: ${names.join(", ")}`);
|
|
177
|
+
}
|
|
178
|
+
load(specifier) {
|
|
179
|
+
const requireFromHere = Module.createRequire(
|
|
180
|
+
process.cwd() + "/package.json"
|
|
181
|
+
);
|
|
182
|
+
const entry = requireFromHere.resolve(specifier);
|
|
183
|
+
const { outputFiles } = esbuild.buildSync({
|
|
184
|
+
entryPoints: [entry],
|
|
185
|
+
bundle: true,
|
|
186
|
+
write: false,
|
|
187
|
+
platform: "node",
|
|
188
|
+
format: "cjs",
|
|
189
|
+
external: [
|
|
190
|
+
...Module.builtinModules,
|
|
191
|
+
...Module.builtinModules.map((m) => `node:${m}`)
|
|
192
|
+
],
|
|
193
|
+
target: "node18",
|
|
194
|
+
mainFields: ["module", "main"],
|
|
195
|
+
conditions: ["node", "import", "require"],
|
|
196
|
+
packages: "bundle",
|
|
197
|
+
logLevel: "warning"
|
|
198
|
+
});
|
|
199
|
+
const code = outputFiles[0].text;
|
|
200
|
+
const sandboxRequire = Module.createRequire(entry);
|
|
201
|
+
const sandbox = {
|
|
202
|
+
// Module system
|
|
203
|
+
module: { exports: {} },
|
|
204
|
+
exports: {},
|
|
205
|
+
require: sandboxRequire,
|
|
206
|
+
console,
|
|
207
|
+
Buffer,
|
|
208
|
+
URL,
|
|
209
|
+
URLSearchParams,
|
|
210
|
+
TextEncoder,
|
|
211
|
+
TextDecoder,
|
|
212
|
+
atob,
|
|
213
|
+
btoa,
|
|
214
|
+
__filename: entry,
|
|
215
|
+
__dirname: path2.dirname(entry),
|
|
216
|
+
process: {
|
|
217
|
+
env: process.env,
|
|
218
|
+
cwd: process.cwd,
|
|
219
|
+
version: process.version,
|
|
220
|
+
versions: process.versions,
|
|
221
|
+
platform: process.platform,
|
|
222
|
+
arch: process.arch,
|
|
223
|
+
argv: process.argv,
|
|
224
|
+
execPath: process.execPath,
|
|
225
|
+
pid: process.pid
|
|
226
|
+
},
|
|
227
|
+
setTimeout,
|
|
228
|
+
setInterval,
|
|
229
|
+
setImmediate,
|
|
230
|
+
clearTimeout,
|
|
231
|
+
clearInterval,
|
|
232
|
+
clearImmediate,
|
|
233
|
+
Promise,
|
|
234
|
+
Object,
|
|
235
|
+
Array,
|
|
236
|
+
String,
|
|
237
|
+
Number,
|
|
238
|
+
Boolean,
|
|
239
|
+
Date,
|
|
240
|
+
Math,
|
|
241
|
+
JSON,
|
|
242
|
+
RegExp,
|
|
243
|
+
Error,
|
|
244
|
+
TypeError,
|
|
245
|
+
RangeError,
|
|
246
|
+
SyntaxError,
|
|
247
|
+
Map,
|
|
248
|
+
Set,
|
|
249
|
+
WeakMap,
|
|
250
|
+
WeakSet,
|
|
251
|
+
Symbol,
|
|
252
|
+
BigInt,
|
|
253
|
+
Proxy,
|
|
254
|
+
Reflect
|
|
255
|
+
};
|
|
256
|
+
sandbox.global = sandbox;
|
|
257
|
+
sandbox.globalThis = sandbox;
|
|
258
|
+
const context2 = vm.createContext(sandbox, {
|
|
259
|
+
name: `macro-${path2.basename(entry)}`,
|
|
260
|
+
codeGeneration: {
|
|
261
|
+
strings: true,
|
|
262
|
+
wasm: false
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
try {
|
|
266
|
+
const script = new vm.Script(code, {
|
|
267
|
+
filename: entry,
|
|
268
|
+
lineOffset: 0,
|
|
269
|
+
columnOffset: 0
|
|
270
|
+
});
|
|
271
|
+
script.runInContext(context2, {
|
|
272
|
+
breakOnSigint: true
|
|
273
|
+
});
|
|
274
|
+
} catch (error) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
`Failed to execute macro from ${specifier}: ${error instanceof Error ? error.message : String(error)}`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
const macros = {};
|
|
280
|
+
const exports = sandbox.module.exports;
|
|
281
|
+
if (typeof exports === "function") {
|
|
282
|
+
macros.default = exports;
|
|
283
|
+
} else if (typeof exports === "object" && exports !== null) {
|
|
284
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
285
|
+
if (typeof value === "function" && name.endsWith("$")) {
|
|
286
|
+
macros[name] = value;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (typeof exports.default === "function") {
|
|
290
|
+
macros.default = exports.default;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!Object.keys(macros).length) {
|
|
294
|
+
throw new Error(`No macros found in ${specifier}`);
|
|
295
|
+
}
|
|
296
|
+
this.cache.set(specifier, macros);
|
|
297
|
+
}
|
|
298
|
+
clearCache(specifier) {
|
|
299
|
+
if (specifier) {
|
|
300
|
+
this.cache.delete(specifier);
|
|
301
|
+
} else {
|
|
302
|
+
this.cache.clear();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
var macroExecutor = new MacroExecutor();
|
|
307
|
+
function macroExecuter() {
|
|
308
|
+
return macroExecutor;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/store/index.ts
|
|
312
|
+
var store_exports = {};
|
|
313
|
+
__export(store_exports, {
|
|
314
|
+
Store: () => Store
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// src/store/store.ts
|
|
318
|
+
var Store = class _Store {
|
|
319
|
+
constructor() {
|
|
320
|
+
this.containers = /* @__PURE__ */ new Map();
|
|
321
|
+
}
|
|
322
|
+
static getInstance() {
|
|
323
|
+
if (!_Store.instance) {
|
|
324
|
+
_Store.instance = new _Store();
|
|
325
|
+
}
|
|
326
|
+
return _Store.instance;
|
|
327
|
+
}
|
|
328
|
+
set(key, value) {
|
|
329
|
+
const existing = this.containers.get(key) || [];
|
|
330
|
+
existing.push(value);
|
|
331
|
+
this.containers.set(key, existing);
|
|
332
|
+
}
|
|
333
|
+
get(key) {
|
|
334
|
+
const values = this.containers.get(key);
|
|
335
|
+
return values;
|
|
336
|
+
}
|
|
337
|
+
has(key) {
|
|
338
|
+
return this.containers.has(key);
|
|
339
|
+
}
|
|
340
|
+
delete(key) {
|
|
341
|
+
return this.containers.delete(key);
|
|
342
|
+
}
|
|
343
|
+
clear() {
|
|
344
|
+
this.containers.clear();
|
|
345
|
+
}
|
|
346
|
+
keys() {
|
|
347
|
+
return this.containers.keys();
|
|
348
|
+
}
|
|
349
|
+
get size() {
|
|
350
|
+
return this.containers.size;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/macros/expand_macros.ts
|
|
355
|
+
var MacroDependencyGraph = class {
|
|
356
|
+
constructor(projectRoot) {
|
|
357
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
358
|
+
this.projectRoot = projectRoot;
|
|
359
|
+
}
|
|
360
|
+
setTypeChecker(checker) {
|
|
361
|
+
this.typeChecker = checker;
|
|
362
|
+
}
|
|
363
|
+
createKey(sourceFile, variableName) {
|
|
364
|
+
const relativePath = path3.relative(this.projectRoot, sourceFile.fileName);
|
|
365
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
366
|
+
return `${normalized}:${variableName}`;
|
|
367
|
+
}
|
|
368
|
+
addNode(key, variableName, node, sourceFile) {
|
|
369
|
+
if (!this.nodes.has(key)) {
|
|
370
|
+
this.nodes.set(key, {
|
|
371
|
+
key,
|
|
372
|
+
variableName,
|
|
373
|
+
node,
|
|
374
|
+
sourceFile,
|
|
375
|
+
filePath: sourceFile.fileName,
|
|
376
|
+
dependencies: /* @__PURE__ */ new Set(),
|
|
377
|
+
astResult: void 0,
|
|
378
|
+
computed: false
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
getNode(key) {
|
|
383
|
+
return this.nodes.get(key);
|
|
384
|
+
}
|
|
385
|
+
addDependency(fromKey, toKey) {
|
|
386
|
+
const node = this.nodes.get(fromKey);
|
|
387
|
+
if (node) {
|
|
388
|
+
node.dependencies.add(toKey);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
setResult(key, astResult) {
|
|
392
|
+
const node = this.nodes.get(key);
|
|
393
|
+
if (node) {
|
|
394
|
+
node.computed = true;
|
|
395
|
+
node.astResult = astResult;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
getResult(key) {
|
|
399
|
+
return this.nodes.get(key)?.astResult;
|
|
400
|
+
}
|
|
401
|
+
isComputed(key) {
|
|
402
|
+
return this.nodes.get(key)?.computed ?? false;
|
|
403
|
+
}
|
|
404
|
+
topologicalSort() {
|
|
405
|
+
const visited = /* @__PURE__ */ new Set();
|
|
406
|
+
const inProgress = /* @__PURE__ */ new Set();
|
|
407
|
+
const sorted = [];
|
|
408
|
+
const visit = (key, path14 = []) => {
|
|
409
|
+
if (visited.has(key)) return;
|
|
410
|
+
if (inProgress.has(key)) {
|
|
411
|
+
const cycle = [...path14, key].join(" -> ");
|
|
412
|
+
throw new Error(`Circular macro dependency detected: ${cycle}`);
|
|
413
|
+
}
|
|
414
|
+
const node = this.nodes.get(key);
|
|
415
|
+
if (!node) return;
|
|
416
|
+
inProgress.add(key);
|
|
417
|
+
for (const depKey of node.dependencies) {
|
|
418
|
+
visit(depKey, [...path14, key]);
|
|
419
|
+
}
|
|
420
|
+
inProgress.delete(key);
|
|
421
|
+
visited.add(key);
|
|
422
|
+
sorted.push(key);
|
|
423
|
+
};
|
|
424
|
+
for (const key of this.nodes.keys()) {
|
|
425
|
+
visit(key);
|
|
426
|
+
}
|
|
427
|
+
return sorted;
|
|
428
|
+
}
|
|
429
|
+
clear() {
|
|
430
|
+
this.nodes.clear();
|
|
431
|
+
}
|
|
432
|
+
getNodesForFile(filePath) {
|
|
433
|
+
return Array.from(this.nodes.values()).filter(
|
|
434
|
+
(node) => node.filePath === filePath
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
var globalGraph = null;
|
|
439
|
+
function getGlobalMacroGraph(projectRoot) {
|
|
440
|
+
if (!globalGraph) {
|
|
441
|
+
globalGraph = new MacroDependencyGraph(projectRoot);
|
|
442
|
+
}
|
|
443
|
+
return globalGraph;
|
|
444
|
+
}
|
|
445
|
+
function resetGlobalMacroGraph() {
|
|
446
|
+
globalGraph = null;
|
|
447
|
+
}
|
|
448
|
+
function resolveImportSpecifier(importPath, fromFile, compilerOptions) {
|
|
449
|
+
const resolved = ts.resolveModuleName(
|
|
450
|
+
importPath,
|
|
451
|
+
fromFile,
|
|
452
|
+
compilerOptions,
|
|
453
|
+
ts.sys
|
|
454
|
+
);
|
|
455
|
+
if (resolved.resolvedModule?.resolvedFileName) {
|
|
456
|
+
return resolved.resolvedModule.resolvedFileName;
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
const requireFromFile = Module2.createRequire(fromFile);
|
|
460
|
+
return requireFromFile.resolve(importPath);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function resolveImportFullPath(symbolName, sourceFile, compilerOptions) {
|
|
466
|
+
let importPath;
|
|
467
|
+
sourceFile.forEachChild((node) => {
|
|
468
|
+
if (!ts.isImportDeclaration(node) || !node.importClause) return;
|
|
469
|
+
const { namedBindings, name } = node.importClause;
|
|
470
|
+
if (name && name.text === symbolName) {
|
|
471
|
+
importPath = node.moduleSpecifier.text;
|
|
472
|
+
}
|
|
473
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
474
|
+
for (const specifier of namedBindings.elements) {
|
|
475
|
+
const importedName = specifier.name.text;
|
|
476
|
+
if (importedName === symbolName) {
|
|
477
|
+
importPath = node.moduleSpecifier.text;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (namedBindings && ts.isNamespaceImport(namedBindings)) {
|
|
482
|
+
if (symbolName.startsWith(namedBindings.name.text + ".")) {
|
|
483
|
+
importPath = node.moduleSpecifier.text;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
if (!importPath) return void 0;
|
|
488
|
+
const resolvedPath = resolveImportSpecifier(
|
|
489
|
+
importPath,
|
|
490
|
+
sourceFile.fileName,
|
|
491
|
+
compilerOptions
|
|
492
|
+
);
|
|
493
|
+
return {
|
|
494
|
+
importPath: importPath.startsWith(".") ? resolvedPath : importPath,
|
|
495
|
+
resolvedPath
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function isNpmPackage(importPath) {
|
|
499
|
+
return !importPath.startsWith(".") && !importPath.startsWith("/") && !path3.isAbsolute(importPath);
|
|
500
|
+
}
|
|
501
|
+
function findVariableDeclarationInFile(variableName, sourceFile) {
|
|
502
|
+
let found;
|
|
503
|
+
function visit(node) {
|
|
504
|
+
if (found) return;
|
|
505
|
+
if (ts.isVariableDeclaration(node)) {
|
|
506
|
+
if (ts.isIdentifier(node.name) && node.name.text === variableName) {
|
|
507
|
+
found = node;
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
ts.forEachChild(node, visit);
|
|
512
|
+
}
|
|
513
|
+
visit(sourceFile);
|
|
514
|
+
return found;
|
|
515
|
+
}
|
|
516
|
+
function extractValueFromNode(node) {
|
|
517
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
518
|
+
return node.text;
|
|
519
|
+
}
|
|
520
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
521
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
522
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
523
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
524
|
+
if (node.kind === ts.SyntaxKind.UndefinedKeyword) return void 0;
|
|
525
|
+
if (ts.isTemplateExpression(node)) {
|
|
526
|
+
let result = node.head.text;
|
|
527
|
+
for (const span of node.templateSpans) {
|
|
528
|
+
const exprValue = extractValueFromNode(span.expression);
|
|
529
|
+
result += String(exprValue) + span.literal.text;
|
|
530
|
+
}
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
534
|
+
const obj = {};
|
|
535
|
+
for (const prop of node.properties) {
|
|
536
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
537
|
+
const key = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : ts.isNumericLiteral(prop.name) ? prop.name.text : ts.isComputedPropertyName(prop.name) ? extractValueFromNode(prop.name.expression) : void 0;
|
|
538
|
+
if (key !== void 0) {
|
|
539
|
+
obj[key] = extractValueFromNode(prop.initializer);
|
|
540
|
+
}
|
|
541
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
542
|
+
obj[prop.name.text] = prop.name.text;
|
|
543
|
+
} else if (ts.isSpreadAssignment(prop)) {
|
|
544
|
+
const spread = extractValueFromNode(prop.expression);
|
|
545
|
+
if (typeof spread === "object" && spread !== null) {
|
|
546
|
+
Object.assign(obj, spread);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return obj;
|
|
551
|
+
}
|
|
552
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
553
|
+
return node.elements.map((el) => {
|
|
554
|
+
if (ts.isSpreadElement(el)) {
|
|
555
|
+
const spread = extractValueFromNode(el.expression);
|
|
556
|
+
return Array.isArray(spread) ? spread : [spread];
|
|
557
|
+
}
|
|
558
|
+
return extractValueFromNode(el);
|
|
559
|
+
}).flat();
|
|
560
|
+
}
|
|
561
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
562
|
+
const operand = extractValueFromNode(node.operand);
|
|
563
|
+
switch (node.operator) {
|
|
564
|
+
case ts.SyntaxKind.MinusToken:
|
|
565
|
+
return -operand;
|
|
566
|
+
case ts.SyntaxKind.PlusToken:
|
|
567
|
+
return +operand;
|
|
568
|
+
case ts.SyntaxKind.ExclamationToken:
|
|
569
|
+
return !operand;
|
|
570
|
+
case ts.SyntaxKind.TildeToken:
|
|
571
|
+
return ~operand;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (ts.isBinaryExpression(node)) {
|
|
575
|
+
const left = extractValueFromNode(node.left);
|
|
576
|
+
const right = extractValueFromNode(node.right);
|
|
577
|
+
switch (node.operatorToken.kind) {
|
|
578
|
+
case ts.SyntaxKind.PlusToken:
|
|
579
|
+
return left + right;
|
|
580
|
+
case ts.SyntaxKind.MinusToken:
|
|
581
|
+
return left - right;
|
|
582
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
583
|
+
return left * right;
|
|
584
|
+
case ts.SyntaxKind.SlashToken:
|
|
585
|
+
return left / right;
|
|
586
|
+
case ts.SyntaxKind.PercentToken:
|
|
587
|
+
return left % right;
|
|
588
|
+
case ts.SyntaxKind.AsteriskAsteriskToken:
|
|
589
|
+
return left ** right;
|
|
590
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
591
|
+
return left == right;
|
|
592
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
593
|
+
return left === right;
|
|
594
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
595
|
+
return left != right;
|
|
596
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
597
|
+
return left !== right;
|
|
598
|
+
case ts.SyntaxKind.LessThanToken:
|
|
599
|
+
return left < right;
|
|
600
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
601
|
+
return left <= right;
|
|
602
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
603
|
+
return left > right;
|
|
604
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
605
|
+
return left >= right;
|
|
606
|
+
case ts.SyntaxKind.AmpersandAmpersandToken:
|
|
607
|
+
return left && right;
|
|
608
|
+
case ts.SyntaxKind.BarBarToken:
|
|
609
|
+
return left || right;
|
|
610
|
+
case ts.SyntaxKind.QuestionQuestionToken:
|
|
611
|
+
return left ?? right;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
615
|
+
return extractValueFromNode(node.expression);
|
|
616
|
+
}
|
|
617
|
+
if (ts.isConditionalExpression(node)) {
|
|
618
|
+
const condition = extractValueFromNode(node.condition);
|
|
619
|
+
return condition ? extractValueFromNode(node.whenTrue) : extractValueFromNode(node.whenFalse);
|
|
620
|
+
}
|
|
621
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
622
|
+
const obj = extractValueFromNode(node.expression);
|
|
623
|
+
const propName = node.name.text;
|
|
624
|
+
return obj?.[propName];
|
|
625
|
+
}
|
|
626
|
+
if (ts.isElementAccessExpression(node)) {
|
|
627
|
+
const obj = extractValueFromNode(node.expression);
|
|
628
|
+
const index = extractValueFromNode(node.argumentExpression);
|
|
629
|
+
return obj?.[index];
|
|
630
|
+
}
|
|
631
|
+
return void 0;
|
|
632
|
+
}
|
|
633
|
+
function createNodeResolver(graph, currentFileKey, sourceFile, compilerOptions) {
|
|
634
|
+
const trackedDependencies = [];
|
|
635
|
+
function resolveIdentifierToNode(identifier) {
|
|
636
|
+
const name = identifier.text;
|
|
637
|
+
const declaration = findVariableDeclarationInFile(name, sourceFile);
|
|
638
|
+
if (declaration && declaration.initializer) {
|
|
639
|
+
const varStatement = declaration.parent.parent;
|
|
640
|
+
const isConst = varStatement.declarationList.flags & ts.NodeFlags.Const;
|
|
641
|
+
if (!isConst) {
|
|
642
|
+
throw new Error(
|
|
643
|
+
`Macro argument '${name}' must be a const variable. let/var are not allowed.`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
if (ts.isCallExpression(declaration.initializer)) {
|
|
647
|
+
const expr = declaration.initializer.expression;
|
|
648
|
+
if (ts.isIdentifier(expr) && expr.text.endsWith("$")) {
|
|
649
|
+
const depKey = graph.createKey(sourceFile, name);
|
|
650
|
+
trackedDependencies.push(depKey);
|
|
651
|
+
const result = graph.getResult(depKey);
|
|
652
|
+
if (result !== void 0) {
|
|
653
|
+
return result;
|
|
654
|
+
}
|
|
655
|
+
throw new Error(
|
|
656
|
+
`Macro dependency '${name}' has not been computed yet. This should not happen.`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return declaration.initializer;
|
|
661
|
+
}
|
|
662
|
+
const resolved = resolveImportFullPath(name, sourceFile, compilerOptions);
|
|
663
|
+
if (resolved) {
|
|
664
|
+
if (isNpmPackage(resolved.importPath)) {
|
|
665
|
+
throw new Error(
|
|
666
|
+
`Cannot resolve identifier '${name}' from npm package '${resolved.importPath}'. Macro arguments from npm packages must be constants that can be evaluated at compile time.`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
if (!resolved.resolvedPath) {
|
|
670
|
+
throw new Error(
|
|
671
|
+
`Could not resolve import path: ${resolved.importPath}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
const importedSource = ts.sys.readFile(resolved.resolvedPath);
|
|
675
|
+
if (!importedSource) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`Could not read imported file: ${resolved.resolvedPath}`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const importedSourceFile = ts.createSourceFile(
|
|
681
|
+
resolved.resolvedPath,
|
|
682
|
+
importedSource,
|
|
683
|
+
ts.ScriptTarget.Latest,
|
|
684
|
+
true
|
|
685
|
+
);
|
|
686
|
+
const importedDecl = findVariableDeclarationInFile(
|
|
687
|
+
name,
|
|
688
|
+
importedSourceFile
|
|
689
|
+
);
|
|
690
|
+
if (importedDecl && importedDecl.initializer) {
|
|
691
|
+
if (ts.isCallExpression(importedDecl.initializer)) {
|
|
692
|
+
const expr = importedDecl.initializer.expression;
|
|
693
|
+
if (ts.isIdentifier(expr) && expr.text.endsWith("$")) {
|
|
694
|
+
const depKey = graph.createKey(importedSourceFile, name);
|
|
695
|
+
trackedDependencies.push(depKey);
|
|
696
|
+
const result = graph.getResult(depKey);
|
|
697
|
+
if (result !== void 0) {
|
|
698
|
+
return result;
|
|
699
|
+
}
|
|
700
|
+
throw new Error(
|
|
701
|
+
`Cross-file macro dependency '${name}' from '${resolved.resolvedPath}' needs to be computed first.`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return importedDecl.initializer;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
throw new Error(
|
|
709
|
+
`Could not resolve identifier '${name}'. Make sure it's a const variable or imported constant.`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
function resolveNodeValue(node) {
|
|
713
|
+
if (ts.isIdentifier(node)) {
|
|
714
|
+
const resolvedNode = resolveIdentifierToNode(node);
|
|
715
|
+
return extractValueFromNode(resolvedNode);
|
|
716
|
+
}
|
|
717
|
+
return extractValueFromNode(node);
|
|
718
|
+
}
|
|
719
|
+
return {
|
|
720
|
+
resolveIdentifierToNode,
|
|
721
|
+
resolveNodeValue,
|
|
722
|
+
getTrackedDependencies: () => trackedDependencies
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
async function expandMacros(source, filePath, projectRoot = process.cwd()) {
|
|
726
|
+
if (!source.includes("$(") && !source.includes("$`")) {
|
|
727
|
+
return source;
|
|
728
|
+
}
|
|
729
|
+
const compilerOptions = {
|
|
730
|
+
module: ts.ModuleKind.ESNext,
|
|
731
|
+
target: ts.ScriptTarget.Latest,
|
|
732
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext
|
|
733
|
+
};
|
|
734
|
+
const sourceFile = ts.createSourceFile(
|
|
735
|
+
filePath,
|
|
736
|
+
source,
|
|
737
|
+
ts.ScriptTarget.Latest,
|
|
738
|
+
true,
|
|
739
|
+
filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
740
|
+
);
|
|
741
|
+
const graph = getGlobalMacroGraph(projectRoot);
|
|
742
|
+
const getProgram = () => ts.createProgram([filePath], {
|
|
743
|
+
target: ts.ScriptTarget.ESNext,
|
|
744
|
+
module: ts.ModuleKind.ESNext
|
|
745
|
+
});
|
|
746
|
+
const getTypeChecker = () => getProgram()?.getTypeChecker();
|
|
747
|
+
function discoverMacros(node) {
|
|
748
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
749
|
+
if (ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text.endsWith("$")) {
|
|
750
|
+
if (ts.isIdentifier(node.name)) {
|
|
751
|
+
const variableName = node.name.text;
|
|
752
|
+
const key = graph.createKey(sourceFile, variableName);
|
|
753
|
+
graph.addNode(key, variableName, node.initializer, sourceFile);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
ts.forEachChild(node, discoverMacros);
|
|
758
|
+
}
|
|
759
|
+
discoverMacros(sourceFile);
|
|
760
|
+
const fileNodes = graph.getNodesForFile(filePath);
|
|
761
|
+
for (const macroNode of fileNodes) {
|
|
762
|
+
if (graph.isComputed(macroNode.key)) continue;
|
|
763
|
+
const resolver = createNodeResolver(
|
|
764
|
+
graph,
|
|
765
|
+
macroNode.key,
|
|
766
|
+
macroNode.sourceFile,
|
|
767
|
+
compilerOptions
|
|
768
|
+
);
|
|
769
|
+
try {
|
|
770
|
+
for (const arg of macroNode.node.arguments) {
|
|
771
|
+
let visitForDeps2 = function(n) {
|
|
772
|
+
if (ts.isIdentifier(n)) {
|
|
773
|
+
try {
|
|
774
|
+
resolver.resolveIdentifierToNode(n);
|
|
775
|
+
} catch (e) {
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
ts.forEachChild(n, visitForDeps2);
|
|
779
|
+
};
|
|
780
|
+
var visitForDeps = visitForDeps2;
|
|
781
|
+
visitForDeps2(arg);
|
|
782
|
+
}
|
|
783
|
+
const deps = resolver.getTrackedDependencies();
|
|
784
|
+
for (const dep of deps) {
|
|
785
|
+
graph.addDependency(macroNode.key, dep);
|
|
786
|
+
}
|
|
787
|
+
} catch (e) {
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
const sortedKeys = graph.topologicalSort();
|
|
791
|
+
for (const key of sortedKeys) {
|
|
792
|
+
const macroNode = graph.getNode(key);
|
|
793
|
+
if (!macroNode || graph.isComputed(key)) continue;
|
|
794
|
+
const node = macroNode.node;
|
|
795
|
+
const name = node.expression.text;
|
|
796
|
+
const resolved = resolveImportFullPath(
|
|
797
|
+
name,
|
|
798
|
+
macroNode.sourceFile,
|
|
799
|
+
compilerOptions
|
|
800
|
+
);
|
|
801
|
+
if (!resolved) {
|
|
802
|
+
throw new Error(`Could not resolve macro import for '${name}'`);
|
|
803
|
+
}
|
|
804
|
+
const macro = macroExecuter().getMacro(resolved.importPath, name);
|
|
805
|
+
if (!macro) {
|
|
806
|
+
throw new Error(`Could not get macro '${name}' for key '${key}'`);
|
|
807
|
+
}
|
|
808
|
+
const resolver = createNodeResolver(
|
|
809
|
+
graph,
|
|
810
|
+
key,
|
|
811
|
+
macroNode.sourceFile,
|
|
812
|
+
compilerOptions
|
|
813
|
+
);
|
|
814
|
+
const macroContext = {
|
|
815
|
+
node,
|
|
816
|
+
sourceFile: macroNode.sourceFile,
|
|
817
|
+
ts,
|
|
818
|
+
store: Store.getInstance(),
|
|
819
|
+
factory: ts.factory,
|
|
820
|
+
graph,
|
|
821
|
+
get program() {
|
|
822
|
+
return getProgram();
|
|
823
|
+
},
|
|
824
|
+
get checker() {
|
|
825
|
+
return getTypeChecker();
|
|
826
|
+
},
|
|
827
|
+
error: (msg) => {
|
|
828
|
+
throw new Error(msg);
|
|
829
|
+
},
|
|
830
|
+
resolveNodeValue: resolver.resolveNodeValue,
|
|
831
|
+
resolveIdentifier: resolver.resolveIdentifierToNode
|
|
832
|
+
};
|
|
833
|
+
try {
|
|
834
|
+
const result2 = macro(...node.arguments, macroContext);
|
|
835
|
+
if (!result2 || typeof result2 !== "object" || !("kind" in result2)) {
|
|
836
|
+
throw new Error(`Macro '${name}' must return a TypeScript AST node`);
|
|
837
|
+
}
|
|
838
|
+
graph.setResult(key, result2);
|
|
839
|
+
} catch (e) {
|
|
840
|
+
console.error(`Macro '${name}' execution failed: ${e?.message ?? e}`);
|
|
841
|
+
throw e;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const transformer = (context2) => {
|
|
845
|
+
const visit = (node) => {
|
|
846
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
847
|
+
if (ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text.endsWith("$") && ts.isIdentifier(node.name)) {
|
|
848
|
+
const key = graph.createKey(sourceFile, node.name.text);
|
|
849
|
+
const macroNode = graph.getNode(key);
|
|
850
|
+
if (macroNode && graph.isComputed(key)) {
|
|
851
|
+
const result2 = graph.getResult(key);
|
|
852
|
+
return context2.factory.updateVariableDeclaration(
|
|
853
|
+
node,
|
|
854
|
+
node.name,
|
|
855
|
+
node.exclamationToken,
|
|
856
|
+
node.type,
|
|
857
|
+
result2
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
863
|
+
const name = node.expression.text;
|
|
864
|
+
if (name.endsWith("$")) {
|
|
865
|
+
const resolved = resolveImportFullPath(
|
|
866
|
+
name,
|
|
867
|
+
sourceFile,
|
|
868
|
+
compilerOptions
|
|
869
|
+
);
|
|
870
|
+
if (resolved) {
|
|
871
|
+
const macro = macroExecuter().getMacro(resolved.importPath, name);
|
|
872
|
+
if (macro) {
|
|
873
|
+
const tempKey = `${graph.createKey(sourceFile, "__temp__")}:${node.pos}`;
|
|
874
|
+
const resolver = createNodeResolver(
|
|
875
|
+
graph,
|
|
876
|
+
tempKey,
|
|
877
|
+
sourceFile,
|
|
878
|
+
compilerOptions
|
|
879
|
+
);
|
|
880
|
+
const macroContext = {
|
|
881
|
+
node,
|
|
882
|
+
sourceFile,
|
|
883
|
+
ts,
|
|
884
|
+
graph,
|
|
885
|
+
store: Store.getInstance(),
|
|
886
|
+
factory: context2.factory,
|
|
887
|
+
get program() {
|
|
888
|
+
return getProgram();
|
|
889
|
+
},
|
|
890
|
+
get checker() {
|
|
891
|
+
return getTypeChecker();
|
|
892
|
+
},
|
|
893
|
+
error: (msg) => {
|
|
894
|
+
throw new Error(msg);
|
|
895
|
+
},
|
|
896
|
+
resolveNodeValue: resolver.resolveNodeValue,
|
|
897
|
+
resolveIdentifier: resolver.resolveIdentifierToNode
|
|
898
|
+
};
|
|
899
|
+
try {
|
|
900
|
+
const result2 = macro(...node.arguments, macroContext);
|
|
901
|
+
if (!result2 || typeof result2 !== "object" || !("kind" in result2)) {
|
|
902
|
+
throw new Error(
|
|
903
|
+
`Macro '${name}' must return a TypeScript AST node`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
return result2;
|
|
907
|
+
} catch (e) {
|
|
908
|
+
console.log(
|
|
909
|
+
`Macro '${name}' execution failed: ${e?.message ?? e}`
|
|
910
|
+
);
|
|
911
|
+
return node;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return ts.visitEachChild(node, visit, context2);
|
|
918
|
+
};
|
|
919
|
+
return (sf) => ts.visitNode(sf, visit);
|
|
920
|
+
};
|
|
921
|
+
const result = ts.transform(sourceFile, [transformer]);
|
|
922
|
+
const output = ts.createPrinter().printFile(result.transformed[0]);
|
|
923
|
+
result.dispose();
|
|
924
|
+
return output;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/plugins/generators/generate_controller.ts
|
|
928
|
+
import * as path4 from "path";
|
|
929
|
+
import { parseSync } from "@swc/core";
|
|
930
|
+
function generateController(filePath, code) {
|
|
931
|
+
const ast = parseSync(code, {
|
|
932
|
+
syntax: "typescript",
|
|
933
|
+
tsx: filePath.endsWith("x"),
|
|
934
|
+
decorators: true
|
|
935
|
+
});
|
|
936
|
+
const serviceInfo = extractServiceInfo(ast);
|
|
937
|
+
if (!serviceInfo || !serviceInfo.hasInjectable) return null;
|
|
938
|
+
return generateControllerCode(serviceInfo, filePath);
|
|
939
|
+
}
|
|
940
|
+
function extractServiceInfo(ast) {
|
|
941
|
+
let serviceClass = null;
|
|
942
|
+
let hasInjectable = false;
|
|
943
|
+
const importMap = {};
|
|
944
|
+
for (const item of ast.body) {
|
|
945
|
+
if (item.type === "ImportDeclaration") {
|
|
946
|
+
const decl = item;
|
|
947
|
+
const source = decl.source.value;
|
|
948
|
+
decl.specifiers.forEach((spec) => {
|
|
949
|
+
if (spec.type === "ImportSpecifier" || spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") {
|
|
950
|
+
importMap[spec.local.value] = source;
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
|
|
955
|
+
const classDecl = item.declaration;
|
|
956
|
+
if (hasInjectableDecorator(classDecl.decorators)) {
|
|
957
|
+
serviceClass = classDecl;
|
|
958
|
+
hasInjectable = true;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (!serviceClass || !serviceClass.identifier) return null;
|
|
963
|
+
return {
|
|
964
|
+
className: serviceClass.identifier.value,
|
|
965
|
+
methods: extractMethods(serviceClass),
|
|
966
|
+
hasInjectable,
|
|
967
|
+
importMap
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
function hasInjectableDecorator(decorators) {
|
|
971
|
+
return decorators?.some((d) => {
|
|
972
|
+
const expr = d.expression;
|
|
973
|
+
return expr.type === "Identifier" && expr.value === "Injectable" || expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Injectable";
|
|
974
|
+
}) ?? false;
|
|
975
|
+
}
|
|
976
|
+
function extractMethods(classDecl) {
|
|
977
|
+
const methods = [];
|
|
978
|
+
for (const member of classDecl.body) {
|
|
979
|
+
if (member.type === "ClassMethod" && member.accessibility === "public") {
|
|
980
|
+
const method = member;
|
|
981
|
+
const methodName = method.key.type === "Identifier" ? method.key.value : "";
|
|
982
|
+
if (!methodName) continue;
|
|
983
|
+
if (!method.function.async) {
|
|
984
|
+
throw new Error(
|
|
985
|
+
`Server action ${classDecl.identifier.value}.${methodName} must be async.`
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
const { paramSchemas, returnSchema } = extractSignature(
|
|
989
|
+
method.function.decorators,
|
|
990
|
+
method.function.params.length
|
|
991
|
+
);
|
|
992
|
+
methods.push({
|
|
993
|
+
name: methodName,
|
|
994
|
+
params: extractMethodParams(method.function.params),
|
|
995
|
+
returnType: extractReturnType(method.function.returnType),
|
|
996
|
+
isAsync: true,
|
|
997
|
+
paramSchemas,
|
|
998
|
+
returnSchema
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return methods;
|
|
1003
|
+
}
|
|
1004
|
+
function extractSignature(decorators, paramCount) {
|
|
1005
|
+
if (!decorators) return { paramSchemas: [] };
|
|
1006
|
+
for (const decorator of decorators) {
|
|
1007
|
+
const expr = decorator.expression;
|
|
1008
|
+
if (expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Signature") {
|
|
1009
|
+
const args = expr.arguments;
|
|
1010
|
+
if (args.length === 0) return { paramSchemas: [] };
|
|
1011
|
+
const schemaStrings = args.map(
|
|
1012
|
+
(arg) => stringifyExpression(arg.expression)
|
|
1013
|
+
);
|
|
1014
|
+
if (args.length === 1) {
|
|
1015
|
+
return { paramSchemas: [], returnSchema: schemaStrings[0] };
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
paramSchemas: schemaStrings.slice(0, -1),
|
|
1019
|
+
returnSchema: schemaStrings[schemaStrings.length - 1]
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return { paramSchemas: [] };
|
|
1024
|
+
}
|
|
1025
|
+
function stringifyExpression(expr) {
|
|
1026
|
+
if (expr.type === "Identifier") return expr.value;
|
|
1027
|
+
if (expr.type === "MemberExpression") {
|
|
1028
|
+
return `${stringifyExpression(expr.object)}.${expr.property.value || ""}`;
|
|
1029
|
+
}
|
|
1030
|
+
if (expr.type === "CallExpression") {
|
|
1031
|
+
const args = expr.arguments.map((a) => stringifyExpression(a.expression)).join(", ");
|
|
1032
|
+
return `${stringifyExpression(expr.callee)}(${args})`;
|
|
1033
|
+
}
|
|
1034
|
+
return "any";
|
|
1035
|
+
}
|
|
1036
|
+
function extractMethodParams(params) {
|
|
1037
|
+
return params.map((p) => {
|
|
1038
|
+
const pat = p.pat;
|
|
1039
|
+
return {
|
|
1040
|
+
name: pat.value,
|
|
1041
|
+
type: pat.typeAnnotation ? stringifyType(pat.typeAnnotation.typeAnnotation) : "any",
|
|
1042
|
+
decorators: []
|
|
1043
|
+
};
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
function extractReturnType(node) {
|
|
1047
|
+
if (!node?.typeAnnotation) return "any";
|
|
1048
|
+
const type = node.typeAnnotation;
|
|
1049
|
+
if (type.type === "TsTypeReference" && type.typeName.value === "Promise") {
|
|
1050
|
+
return stringifyType(type.typeParams?.params[0]);
|
|
1051
|
+
}
|
|
1052
|
+
return stringifyType(type);
|
|
1053
|
+
}
|
|
1054
|
+
function stringifyType(node) {
|
|
1055
|
+
if (!node) return "any";
|
|
1056
|
+
switch (node.type) {
|
|
1057
|
+
case "TsKeywordType":
|
|
1058
|
+
return node.kind;
|
|
1059
|
+
case "TsTypeReference":
|
|
1060
|
+
const base = node.typeName.value;
|
|
1061
|
+
const args = node.typeParams ? `<${node.typeParams.params.map(stringifyType).join(", ")}>` : "";
|
|
1062
|
+
return base + args;
|
|
1063
|
+
case "TsArrayType":
|
|
1064
|
+
return `${stringifyType(node.elemType)}[]`;
|
|
1065
|
+
default:
|
|
1066
|
+
return "any";
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
function generateControllerCode(serviceInfo, filePath) {
|
|
1070
|
+
const serviceName = serviceInfo.className;
|
|
1071
|
+
const controllerName = serviceName.replace(/Service$/, "AutoController");
|
|
1072
|
+
const serviceImportPath = `./${path4.basename(filePath).replace(/\.ts$/, "")}`;
|
|
1073
|
+
const importGroups = /* @__PURE__ */ new Map();
|
|
1074
|
+
const registerIdentifier = (id) => {
|
|
1075
|
+
const source = serviceInfo.importMap[id] || serviceImportPath;
|
|
1076
|
+
if (!importGroups.has(source)) importGroups.set(source, /* @__PURE__ */ new Set());
|
|
1077
|
+
importGroups.get(source).add(id);
|
|
1078
|
+
};
|
|
1079
|
+
serviceInfo.methods.forEach((m) => {
|
|
1080
|
+
[...m.paramSchemas, m.returnSchema].filter(Boolean).forEach((s) => {
|
|
1081
|
+
const matches = s.match(/[A-Z][a-zA-Z0-9]*/g);
|
|
1082
|
+
matches?.forEach(registerIdentifier);
|
|
1083
|
+
if (s.includes("z.")) registerIdentifier("z");
|
|
1084
|
+
});
|
|
1085
|
+
});
|
|
1086
|
+
let importStrings = `import { Controller, Post, Get, Body } from "@kithinji/orca";
|
|
1087
|
+
`;
|
|
1088
|
+
importGroups.forEach((ids, source) => {
|
|
1089
|
+
const filteredIds = Array.from(ids).filter((id) => id !== serviceName);
|
|
1090
|
+
if (filteredIds.length > 0) {
|
|
1091
|
+
importStrings += `
|
|
1092
|
+
import { ${filteredIds.join(
|
|
1093
|
+
", "
|
|
1094
|
+
)} } from "${source}";`;
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
const methods = serviceInfo.methods.map((m) => {
|
|
1098
|
+
const hasParams = m.params.length > 0;
|
|
1099
|
+
const bodyParam = hasParams ? `@Body() body: any` : "";
|
|
1100
|
+
let body = "";
|
|
1101
|
+
if (hasParams) {
|
|
1102
|
+
if (m.paramSchemas.length > 0) {
|
|
1103
|
+
body += ` const b = typeof body === 'object' && body !== null ? body : {};
|
|
1104
|
+
`;
|
|
1105
|
+
m.params.forEach((p, i) => {
|
|
1106
|
+
body += ` const ${p.name} = ${m.paramSchemas[i]}.parse(b.${p.name});
|
|
1107
|
+
`;
|
|
1108
|
+
});
|
|
1109
|
+
} else {
|
|
1110
|
+
body += ` const { ${m.params.map((p) => p.name).join(", ")} } = body;
|
|
1111
|
+
`;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
const callArgs = m.params.map((p) => p.name).join(", ");
|
|
1115
|
+
const serviceCall = `this.${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}.${m.name}(${callArgs})`;
|
|
1116
|
+
if (m.returnSchema) {
|
|
1117
|
+
body += ` const res = await ${serviceCall};
|
|
1118
|
+
return ${m.returnSchema}.parse(res);`;
|
|
1119
|
+
} else {
|
|
1120
|
+
body += ` return ${serviceCall};`;
|
|
1121
|
+
}
|
|
1122
|
+
return ` @${hasParams ? "Post" : "Get"}("${m.name}")
|
|
1123
|
+
async ${m.name}(${bodyParam}): Promise<${m.returnType}> {
|
|
1124
|
+
${body}
|
|
1125
|
+
}`;
|
|
1126
|
+
}).join("\n\n");
|
|
1127
|
+
return `${importStrings}
|
|
1128
|
+
|
|
1129
|
+
@Controller("/${serviceName}", {
|
|
1130
|
+
providedIn: "root",
|
|
1131
|
+
})
|
|
1132
|
+
export class ${controllerName} {
|
|
1133
|
+
constructor(private readonly ${serviceName.charAt(0).toLowerCase() + serviceName.slice(1)}: ${serviceName}) {}
|
|
1134
|
+
|
|
1135
|
+
${methods}
|
|
1136
|
+
}`;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/plugins/generators/tsx_server_stub.ts
|
|
1140
|
+
import * as path5 from "path";
|
|
1141
|
+
import { createHash } from "crypto";
|
|
1142
|
+
import { parseSync as parseSync2, printSync } from "@swc/core";
|
|
1143
|
+
function generateServerStub(filePath, code) {
|
|
1144
|
+
const hash = createHash("md5").update(filePath).digest("hex").slice(0, 8);
|
|
1145
|
+
const relativeFromSrc = filePath.split("/src/")[1];
|
|
1146
|
+
const parsed = path5.parse(relativeFromSrc);
|
|
1147
|
+
const fileName = path5.join("src", parsed.dir, parsed.name);
|
|
1148
|
+
const ast = parseSync2(code, {
|
|
1149
|
+
syntax: "typescript",
|
|
1150
|
+
tsx: filePath.endsWith("x"),
|
|
1151
|
+
decorators: true
|
|
1152
|
+
});
|
|
1153
|
+
const importMap = {};
|
|
1154
|
+
for (const item of ast.body) {
|
|
1155
|
+
if (item.type === "ImportDeclaration") {
|
|
1156
|
+
const decl = item;
|
|
1157
|
+
for (const specifier of decl.specifiers ?? []) {
|
|
1158
|
+
let localName;
|
|
1159
|
+
if (specifier.type === "ImportSpecifier") {
|
|
1160
|
+
localName = specifier.local.value;
|
|
1161
|
+
} else if (specifier.type === "ImportDefaultSpecifier") {
|
|
1162
|
+
localName = specifier.local.value;
|
|
1163
|
+
} else {
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
importMap[localName] = decl.source.value;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const preservedNodes = [];
|
|
1171
|
+
const stubbedClasses = [];
|
|
1172
|
+
for (const item of ast.body) {
|
|
1173
|
+
let shouldStub = false;
|
|
1174
|
+
if (item.type === "ExportDeclaration" && item.declaration?.type === "ClassDeclaration") {
|
|
1175
|
+
const classDecl = item.declaration;
|
|
1176
|
+
if (hasComponentDecorator(classDecl.decorators)) {
|
|
1177
|
+
shouldStub = true;
|
|
1178
|
+
const stub = extractClassStub(classDecl);
|
|
1179
|
+
if (stub) {
|
|
1180
|
+
stubbedClasses.push(stub);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (!shouldStub) {
|
|
1185
|
+
preservedNodes.push(item);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const preservedCode = preservedNodes.length > 0 ? printSync({
|
|
1189
|
+
type: "Module",
|
|
1190
|
+
span: ast.span,
|
|
1191
|
+
body: preservedNodes,
|
|
1192
|
+
interpreter: ast.interpreter
|
|
1193
|
+
}).code : "";
|
|
1194
|
+
const stubCode = stubbedClasses.map((stub) => generateClassCode(stub, hash, fileName)).join("\n\n");
|
|
1195
|
+
return `
|
|
1196
|
+
${preservedCode}
|
|
1197
|
+
${stubCode}
|
|
1198
|
+
`.trim();
|
|
1199
|
+
}
|
|
1200
|
+
function hasComponentDecorator(decorators) {
|
|
1201
|
+
if (!decorators) return false;
|
|
1202
|
+
return decorators.some((decorator) => {
|
|
1203
|
+
const expr = decorator.expression;
|
|
1204
|
+
if (expr.type === "Identifier" && expr.value === "Component") {
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
if (expr.type === "CallExpression" && expr.callee.type === "Identifier" && expr.callee.value === "Component") {
|
|
1208
|
+
return true;
|
|
1209
|
+
}
|
|
1210
|
+
return false;
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
function extractClassStub(classDecl) {
|
|
1214
|
+
const className = classDecl.identifier?.value;
|
|
1215
|
+
if (!className) return null;
|
|
1216
|
+
let propsType = "{}";
|
|
1217
|
+
const decorators = [];
|
|
1218
|
+
if (classDecl.decorators) {
|
|
1219
|
+
for (const dec of classDecl.decorators) {
|
|
1220
|
+
const str = stringifyDecorator(dec);
|
|
1221
|
+
if (str) decorators.push(str);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
for (const member of classDecl.body) {
|
|
1225
|
+
if (member.type === "ClassProperty") {
|
|
1226
|
+
if (member.key.type === "Identifier" && member.key.value === "props") {
|
|
1227
|
+
propsType = extractPropsType(member);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return {
|
|
1232
|
+
name: className,
|
|
1233
|
+
propsType,
|
|
1234
|
+
decorators
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function stringifyDecorator(decorator) {
|
|
1238
|
+
const expr = decorator.expression;
|
|
1239
|
+
if (expr.type === "CallExpression" && expr.callee.type === "Identifier") {
|
|
1240
|
+
return `@${expr.callee.value}()`;
|
|
1241
|
+
}
|
|
1242
|
+
if (expr.type === "Identifier") {
|
|
1243
|
+
return `@${expr.value}`;
|
|
1244
|
+
}
|
|
1245
|
+
return "";
|
|
1246
|
+
}
|
|
1247
|
+
function extractPropsType(member) {
|
|
1248
|
+
const typeAnn = member.typeAnnotation?.typeAnnotation;
|
|
1249
|
+
if (!typeAnn) return "{}";
|
|
1250
|
+
if (typeAnn.type === "TsTypeLiteral") {
|
|
1251
|
+
const props = [];
|
|
1252
|
+
for (const m of typeAnn.members) {
|
|
1253
|
+
if (m.type === "TsPropertySignature") {
|
|
1254
|
+
const key = m.key.type === "Identifier" ? m.key.value : "?";
|
|
1255
|
+
const t = m.typeAnnotation ? stringifyType2(m.typeAnnotation.typeAnnotation) : "any";
|
|
1256
|
+
props.push(`${key}: ${t}`);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return `{ ${props.join("; ")} }`;
|
|
1260
|
+
}
|
|
1261
|
+
return stringifyType2(typeAnn);
|
|
1262
|
+
}
|
|
1263
|
+
function stringifyType2(typeNode) {
|
|
1264
|
+
if (!typeNode) return "any";
|
|
1265
|
+
switch (typeNode.type) {
|
|
1266
|
+
case "TsKeywordType":
|
|
1267
|
+
return typeNode.kind;
|
|
1268
|
+
case "TsTypeReference":
|
|
1269
|
+
if (typeNode.typeName.type === "Identifier")
|
|
1270
|
+
return typeNode.typeName.value;
|
|
1271
|
+
if (typeNode.typeName.type === "TsQualifiedName") {
|
|
1272
|
+
return `${stringifyQualifiedName(typeNode.typeName.left)}.${typeNode.typeName.right.value}`;
|
|
1273
|
+
}
|
|
1274
|
+
return "any";
|
|
1275
|
+
case "TsArrayType":
|
|
1276
|
+
return `${stringifyType2(typeNode.elemType)}[]`;
|
|
1277
|
+
case "TsUnionType":
|
|
1278
|
+
return typeNode.types.map(stringifyType2).join(" | ");
|
|
1279
|
+
case "TsIntersectionType":
|
|
1280
|
+
return typeNode.types.map(stringifyType2).join(" & ");
|
|
1281
|
+
default:
|
|
1282
|
+
return "any";
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
function stringifyQualifiedName(node) {
|
|
1286
|
+
if (node.type === "Identifier") return node.value;
|
|
1287
|
+
if (node.type === "TsQualifiedName") {
|
|
1288
|
+
return `${stringifyQualifiedName(node.left)}.${node.right.value}`;
|
|
1289
|
+
}
|
|
1290
|
+
return "any";
|
|
1291
|
+
}
|
|
1292
|
+
function generateClassCode(stub, hash, fileName) {
|
|
1293
|
+
const clientId = `${stub.name}_${hash}`;
|
|
1294
|
+
const clientPath = `/${fileName}.js`;
|
|
1295
|
+
const decoratorsStr = stub.decorators.length > 0 ? stub.decorators.join("\n") + "\n" : "";
|
|
1296
|
+
return `
|
|
1297
|
+
${decoratorsStr}export class ${stub.name} {
|
|
1298
|
+
props!: ${stub.propsType};
|
|
1299
|
+
constructor() {}
|
|
1300
|
+
build() {
|
|
1301
|
+
const inputProps = { ...this.props };
|
|
1302
|
+
return {
|
|
1303
|
+
$$typeof: Symbol.for("orca.client.component"),
|
|
1304
|
+
id: "${clientId}_" + Math.random().toString(36).slice(2, 9),
|
|
1305
|
+
type: "${stub.name}",
|
|
1306
|
+
props: {
|
|
1307
|
+
...inputProps,
|
|
1308
|
+
__clientComponent: {
|
|
1309
|
+
id: "${clientId}",
|
|
1310
|
+
path: "${clientPath}",
|
|
1311
|
+
name: "${stub.name}",
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
key: null
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
`.trim();
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/plugins/transformers/j2d.ts
|
|
1322
|
+
var NodeTypeGuards = class {
|
|
1323
|
+
constructor(t) {
|
|
1324
|
+
this.t = t;
|
|
1325
|
+
}
|
|
1326
|
+
isSignalMember(expr) {
|
|
1327
|
+
return this.t.isMemberExpression(expr) && this.t.isIdentifier(expr.property, { name: "value" });
|
|
1328
|
+
}
|
|
1329
|
+
isObservableMember(expr) {
|
|
1330
|
+
return this.t.isMemberExpression(expr) && this.t.isIdentifier(expr.property, { name: "$value" });
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
var ASTUtilities = class {
|
|
1334
|
+
constructor(t, guards) {
|
|
1335
|
+
this.t = t;
|
|
1336
|
+
this.guards = guards;
|
|
1337
|
+
}
|
|
1338
|
+
getObject(expr) {
|
|
1339
|
+
if (this.guards.isSignalMember(expr) || this.guards.isObservableMember(expr)) {
|
|
1340
|
+
return expr.object;
|
|
1341
|
+
}
|
|
1342
|
+
return expr;
|
|
1343
|
+
}
|
|
1344
|
+
replaceThisWithSelf(node) {
|
|
1345
|
+
const cloned = this.t.cloneNode(node, true);
|
|
1346
|
+
this.walkAndTransform(cloned, (n) => {
|
|
1347
|
+
if (this.t.isThisExpression(n)) {
|
|
1348
|
+
Object.assign(n, this.t.identifier("self"));
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
return cloned;
|
|
1352
|
+
}
|
|
1353
|
+
walkAndTransform(node, transform2) {
|
|
1354
|
+
if (!node || typeof node !== "object") return;
|
|
1355
|
+
transform2(node);
|
|
1356
|
+
for (const key in node) {
|
|
1357
|
+
if (this.shouldSkipKey(key)) continue;
|
|
1358
|
+
const value = node[key];
|
|
1359
|
+
if (Array.isArray(value)) {
|
|
1360
|
+
value.forEach((item) => this.walkAndTransform(item, transform2));
|
|
1361
|
+
} else if (value && typeof value === "object") {
|
|
1362
|
+
this.walkAndTransform(value, transform2);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
shouldSkipKey(key) {
|
|
1367
|
+
return ["loc", "start", "end", "extra"].includes(key);
|
|
1368
|
+
}
|
|
1369
|
+
buildMemberExpression(name) {
|
|
1370
|
+
const parts = name.split(".");
|
|
1371
|
+
let expr = this.t.identifier(parts[0]);
|
|
1372
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1373
|
+
expr = this.t.memberExpression(expr, this.t.identifier(parts[i]));
|
|
1374
|
+
}
|
|
1375
|
+
return expr;
|
|
1376
|
+
}
|
|
1377
|
+
insertBeforeReturn(body, statements) {
|
|
1378
|
+
const returnIndex = body.findIndex(
|
|
1379
|
+
(stmt) => this.t.isReturnStatement(stmt)
|
|
1380
|
+
);
|
|
1381
|
+
if (returnIndex !== -1) {
|
|
1382
|
+
body.splice(returnIndex, 0, ...statements);
|
|
1383
|
+
} else {
|
|
1384
|
+
body.push(...statements);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
var JSXUtilities = class {
|
|
1389
|
+
constructor(t) {
|
|
1390
|
+
this.t = t;
|
|
1391
|
+
}
|
|
1392
|
+
getComponentName(nameNode) {
|
|
1393
|
+
if (this.t.isJSXIdentifier(nameNode)) {
|
|
1394
|
+
return nameNode.name;
|
|
1395
|
+
}
|
|
1396
|
+
if (this.t.isJSXMemberExpression(nameNode)) {
|
|
1397
|
+
const parts = [];
|
|
1398
|
+
let current = nameNode;
|
|
1399
|
+
while (this.t.isJSXMemberExpression(current)) {
|
|
1400
|
+
parts.unshift(current.property.name);
|
|
1401
|
+
current = current.object;
|
|
1402
|
+
}
|
|
1403
|
+
if (this.t.isJSXIdentifier(current)) {
|
|
1404
|
+
parts.unshift(current.name);
|
|
1405
|
+
}
|
|
1406
|
+
return parts.join(".");
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
isComponentTag(tag) {
|
|
1411
|
+
return tag ? /^[A-Z]/.test(tag) : false;
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
1414
|
+
var ObservableManager = class {
|
|
1415
|
+
constructor(t, guards) {
|
|
1416
|
+
this.t = t;
|
|
1417
|
+
this.guards = guards;
|
|
1418
|
+
}
|
|
1419
|
+
getObservableKey(expr) {
|
|
1420
|
+
return this.stringifyNode(expr);
|
|
1421
|
+
}
|
|
1422
|
+
stringifyNode(node) {
|
|
1423
|
+
if (!node) return "";
|
|
1424
|
+
if (this.t.isThisExpression(node)) return "this";
|
|
1425
|
+
if (this.t.isIdentifier(node)) return node.name;
|
|
1426
|
+
if (this.t.isMemberExpression(node)) {
|
|
1427
|
+
const obj = this.stringifyNode(node.object);
|
|
1428
|
+
const prop = node.computed ? `[${this.stringifyNode(node.property)}]` : `.${node.property.name}`;
|
|
1429
|
+
return obj + prop;
|
|
1430
|
+
}
|
|
1431
|
+
if (this.t.isCallExpression(node)) {
|
|
1432
|
+
const callee = this.stringifyNode(node.callee);
|
|
1433
|
+
const args = node.arguments.map((arg) => this.stringifyNode(arg)).join(",");
|
|
1434
|
+
return `${callee}(${args})`;
|
|
1435
|
+
}
|
|
1436
|
+
if (this.t.isStringLiteral(node)) return `"${node.value}"`;
|
|
1437
|
+
if (this.t.isNumericLiteral(node)) return String(node.value);
|
|
1438
|
+
return node.type + JSON.stringify(node.name || node.value || "");
|
|
1439
|
+
}
|
|
1440
|
+
collectObservables(node, observables, astUtils) {
|
|
1441
|
+
this.walkNode(node, (n) => {
|
|
1442
|
+
if (this.guards.isObservableMember(n)) {
|
|
1443
|
+
const observable = astUtils.replaceThisWithSelf(
|
|
1444
|
+
n.object
|
|
1445
|
+
);
|
|
1446
|
+
const key = this.getObservableKey(observable);
|
|
1447
|
+
if (!observables.has(key)) {
|
|
1448
|
+
observables.set(key, observable);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
replaceObservablesWithSignals(node, observableSignals, astUtils) {
|
|
1454
|
+
const cloned = this.t.cloneNode(node, true);
|
|
1455
|
+
this.walkNode(cloned, (n) => {
|
|
1456
|
+
if (this.guards.isObservableMember(n)) {
|
|
1457
|
+
const observable = astUtils.replaceThisWithSelf(n.object);
|
|
1458
|
+
const key = this.getObservableKey(observable);
|
|
1459
|
+
const signalId = observableSignals.get(key);
|
|
1460
|
+
if (signalId) {
|
|
1461
|
+
n.object = signalId;
|
|
1462
|
+
n.property = this.t.identifier("value");
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
return cloned;
|
|
1467
|
+
}
|
|
1468
|
+
walkNode(node, callback) {
|
|
1469
|
+
if (!node || typeof node !== "object") return;
|
|
1470
|
+
callback(node);
|
|
1471
|
+
for (const key in node) {
|
|
1472
|
+
if (["loc", "start", "end", "extra"].includes(key)) continue;
|
|
1473
|
+
const value = node[key];
|
|
1474
|
+
if (Array.isArray(value)) {
|
|
1475
|
+
value.forEach((item) => this.walkNode(item, callback));
|
|
1476
|
+
} else if (value && typeof value === "object") {
|
|
1477
|
+
this.walkNode(value, callback);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
var ElementTransformer = class {
|
|
1483
|
+
constructor(t, guards, astUtils, jsxUtils, observableManager) {
|
|
1484
|
+
this.t = t;
|
|
1485
|
+
this.guards = guards;
|
|
1486
|
+
this.astUtils = astUtils;
|
|
1487
|
+
this.jsxUtils = jsxUtils;
|
|
1488
|
+
this.observableManager = observableManager;
|
|
1489
|
+
}
|
|
1490
|
+
transformElement(path14, scope, context2) {
|
|
1491
|
+
if (this.t.isJSXFragment(path14.node)) {
|
|
1492
|
+
return this.transformFragment(
|
|
1493
|
+
path14,
|
|
1494
|
+
scope,
|
|
1495
|
+
context2
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
return this.transformJSXElement(
|
|
1499
|
+
path14,
|
|
1500
|
+
scope,
|
|
1501
|
+
context2
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
transformJSXElement(path14, scope, context2) {
|
|
1505
|
+
const jsxElement = path14.node;
|
|
1506
|
+
const tag = this.jsxUtils.getComponentName(jsxElement.openingElement.name);
|
|
1507
|
+
const isComponent = this.jsxUtils.isComponentTag(tag);
|
|
1508
|
+
if (isComponent && tag) {
|
|
1509
|
+
return this.transformComponentElement(jsxElement, tag, scope, context2);
|
|
1510
|
+
} else if (tag) {
|
|
1511
|
+
return this.transformDOMElement(jsxElement, tag, scope, context2);
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
id: scope.generateUidIdentifier("el"),
|
|
1515
|
+
statements: []
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
transformComponentElement(jsxElement, tag, scope, context2) {
|
|
1519
|
+
const elId = scope.generateUidIdentifier("el");
|
|
1520
|
+
const statements = [];
|
|
1521
|
+
const props = [];
|
|
1522
|
+
const children = [];
|
|
1523
|
+
this.processComponentAttributes(
|
|
1524
|
+
jsxElement.openingElement.attributes,
|
|
1525
|
+
props,
|
|
1526
|
+
context2
|
|
1527
|
+
);
|
|
1528
|
+
this.processChildren(
|
|
1529
|
+
jsxElement.children,
|
|
1530
|
+
children,
|
|
1531
|
+
statements,
|
|
1532
|
+
scope,
|
|
1533
|
+
context2
|
|
1534
|
+
);
|
|
1535
|
+
if (children.length > 0) {
|
|
1536
|
+
props.push(
|
|
1537
|
+
this.t.objectProperty(
|
|
1538
|
+
this.t.identifier("children"),
|
|
1539
|
+
children.length === 1 ? children[0] : this.t.arrayExpression(children)
|
|
1540
|
+
)
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
statements.push(
|
|
1544
|
+
this.t.variableDeclaration("var", [
|
|
1545
|
+
this.t.variableDeclarator(
|
|
1546
|
+
elId,
|
|
1547
|
+
this.t.callExpression(this.t.identifier("$createComponent"), [
|
|
1548
|
+
this.astUtils.buildMemberExpression(tag),
|
|
1549
|
+
this.t.objectExpression(props),
|
|
1550
|
+
this.t.identifier("self")
|
|
1551
|
+
])
|
|
1552
|
+
)
|
|
1553
|
+
])
|
|
1554
|
+
);
|
|
1555
|
+
return { id: elId, statements };
|
|
1556
|
+
}
|
|
1557
|
+
transformDOMElement(jsxElement, tag, scope, context2) {
|
|
1558
|
+
const elId = scope.generateUidIdentifier("el");
|
|
1559
|
+
const statements = [];
|
|
1560
|
+
statements.push(
|
|
1561
|
+
this.t.variableDeclaration("var", [
|
|
1562
|
+
this.t.variableDeclarator(
|
|
1563
|
+
elId,
|
|
1564
|
+
this.t.callExpression(
|
|
1565
|
+
this.t.memberExpression(
|
|
1566
|
+
this.t.identifier("document"),
|
|
1567
|
+
this.t.identifier("createElement")
|
|
1568
|
+
),
|
|
1569
|
+
[this.t.stringLiteral(tag)]
|
|
1570
|
+
)
|
|
1571
|
+
)
|
|
1572
|
+
])
|
|
1573
|
+
);
|
|
1574
|
+
const { hasRef, refValue, hasDangerousHTML, dangerousHTMLValue } = this.processDOMAttributes(
|
|
1575
|
+
jsxElement.openingElement.attributes,
|
|
1576
|
+
elId,
|
|
1577
|
+
statements,
|
|
1578
|
+
context2
|
|
1579
|
+
);
|
|
1580
|
+
if (hasRef && refValue) {
|
|
1581
|
+
statements.push(
|
|
1582
|
+
this.t.expressionStatement(
|
|
1583
|
+
this.t.assignmentExpression("=", refValue, elId)
|
|
1584
|
+
)
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
if (hasDangerousHTML && dangerousHTMLValue) {
|
|
1588
|
+
statements.push(
|
|
1589
|
+
this.t.expressionStatement(
|
|
1590
|
+
this.t.assignmentExpression(
|
|
1591
|
+
"=",
|
|
1592
|
+
this.t.memberExpression(elId, this.t.identifier("innerHTML")),
|
|
1593
|
+
this.t.memberExpression(
|
|
1594
|
+
dangerousHTMLValue,
|
|
1595
|
+
this.t.identifier("__html")
|
|
1596
|
+
)
|
|
1597
|
+
)
|
|
1598
|
+
)
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
if (!hasDangerousHTML) {
|
|
1602
|
+
this.processDOMChildren(
|
|
1603
|
+
jsxElement.children,
|
|
1604
|
+
elId,
|
|
1605
|
+
statements,
|
|
1606
|
+
scope,
|
|
1607
|
+
context2
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
return { id: elId, statements };
|
|
1611
|
+
}
|
|
1612
|
+
transformFragment(path14, scope, context2) {
|
|
1613
|
+
const fragId = scope.generateUidIdentifier("frag");
|
|
1614
|
+
const statements = [];
|
|
1615
|
+
statements.push(
|
|
1616
|
+
this.t.variableDeclaration("var", [
|
|
1617
|
+
this.t.variableDeclarator(
|
|
1618
|
+
fragId,
|
|
1619
|
+
this.t.callExpression(
|
|
1620
|
+
this.t.memberExpression(
|
|
1621
|
+
this.t.identifier("document"),
|
|
1622
|
+
this.t.identifier("createDocumentFragment")
|
|
1623
|
+
),
|
|
1624
|
+
[]
|
|
1625
|
+
)
|
|
1626
|
+
)
|
|
1627
|
+
])
|
|
1628
|
+
);
|
|
1629
|
+
this.processDOMChildren(
|
|
1630
|
+
path14.node.children,
|
|
1631
|
+
fragId,
|
|
1632
|
+
statements,
|
|
1633
|
+
scope,
|
|
1634
|
+
context2
|
|
1635
|
+
);
|
|
1636
|
+
return { id: fragId, statements };
|
|
1637
|
+
}
|
|
1638
|
+
processComponentAttributes(attributes, props, context2) {
|
|
1639
|
+
for (const attr of attributes) {
|
|
1640
|
+
if (this.t.isJSXSpreadAttribute(attr)) {
|
|
1641
|
+
this.observableManager.collectObservables(
|
|
1642
|
+
attr.argument,
|
|
1643
|
+
context2.observables,
|
|
1644
|
+
this.astUtils
|
|
1645
|
+
);
|
|
1646
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1647
|
+
attr.argument,
|
|
1648
|
+
context2.observableSignals,
|
|
1649
|
+
this.astUtils
|
|
1650
|
+
);
|
|
1651
|
+
props.push(
|
|
1652
|
+
this.t.spreadElement(this.astUtils.replaceThisWithSelf(replaced))
|
|
1653
|
+
);
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
const key = attr.name.name;
|
|
1657
|
+
if (this.t.isStringLiteral(attr.value)) {
|
|
1658
|
+
props.push(this.t.objectProperty(this.t.identifier(key), attr.value));
|
|
1659
|
+
} else if (this.t.isJSXExpressionContainer(attr.value)) {
|
|
1660
|
+
const expr = attr.value.expression;
|
|
1661
|
+
this.observableManager.collectObservables(
|
|
1662
|
+
expr,
|
|
1663
|
+
context2.observables,
|
|
1664
|
+
this.astUtils
|
|
1665
|
+
);
|
|
1666
|
+
if (this.guards.isSignalMember(expr) || this.guards.isObservableMember(expr)) {
|
|
1667
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1668
|
+
expr,
|
|
1669
|
+
context2.observableSignals,
|
|
1670
|
+
this.astUtils
|
|
1671
|
+
);
|
|
1672
|
+
props.push(
|
|
1673
|
+
this.t.objectMethod(
|
|
1674
|
+
"get",
|
|
1675
|
+
this.t.identifier(key),
|
|
1676
|
+
[],
|
|
1677
|
+
this.t.blockStatement([
|
|
1678
|
+
this.t.returnStatement(
|
|
1679
|
+
this.astUtils.replaceThisWithSelf(replaced)
|
|
1680
|
+
)
|
|
1681
|
+
])
|
|
1682
|
+
)
|
|
1683
|
+
);
|
|
1684
|
+
} else {
|
|
1685
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1686
|
+
expr,
|
|
1687
|
+
context2.observableSignals,
|
|
1688
|
+
this.astUtils
|
|
1689
|
+
);
|
|
1690
|
+
props.push(
|
|
1691
|
+
this.t.objectProperty(
|
|
1692
|
+
this.t.identifier(key),
|
|
1693
|
+
this.astUtils.replaceThisWithSelf(replaced)
|
|
1694
|
+
)
|
|
1695
|
+
);
|
|
1696
|
+
}
|
|
1697
|
+
} else {
|
|
1698
|
+
props.push(
|
|
1699
|
+
this.t.objectProperty(
|
|
1700
|
+
this.t.identifier(key),
|
|
1701
|
+
this.t.booleanLiteral(true)
|
|
1702
|
+
)
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
processDOMAttributes(attributes, elId, statements, context2) {
|
|
1708
|
+
let hasRef = false;
|
|
1709
|
+
let refValue = null;
|
|
1710
|
+
let hasDangerousHTML = false;
|
|
1711
|
+
let dangerousHTMLValue = null;
|
|
1712
|
+
for (const attr of attributes) {
|
|
1713
|
+
if (this.t.isJSXSpreadAttribute(attr)) {
|
|
1714
|
+
this.observableManager.collectObservables(
|
|
1715
|
+
attr.argument,
|
|
1716
|
+
context2.observables,
|
|
1717
|
+
this.astUtils
|
|
1718
|
+
);
|
|
1719
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1720
|
+
attr.argument,
|
|
1721
|
+
context2.observableSignals,
|
|
1722
|
+
this.astUtils
|
|
1723
|
+
);
|
|
1724
|
+
statements.push(
|
|
1725
|
+
this.t.expressionStatement(
|
|
1726
|
+
this.t.callExpression(this.t.identifier("$spread"), [
|
|
1727
|
+
elId,
|
|
1728
|
+
this.astUtils.replaceThisWithSelf(replaced)
|
|
1729
|
+
])
|
|
1730
|
+
)
|
|
1731
|
+
);
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
const key = attr.name.name;
|
|
1735
|
+
if (key === "ref") {
|
|
1736
|
+
hasRef = true;
|
|
1737
|
+
if (this.t.isJSXExpressionContainer(attr.value)) {
|
|
1738
|
+
this.observableManager.collectObservables(
|
|
1739
|
+
attr.value.expression,
|
|
1740
|
+
context2.observables,
|
|
1741
|
+
this.astUtils
|
|
1742
|
+
);
|
|
1743
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1744
|
+
attr.value.expression,
|
|
1745
|
+
context2.observableSignals,
|
|
1746
|
+
this.astUtils
|
|
1747
|
+
);
|
|
1748
|
+
refValue = this.astUtils.replaceThisWithSelf(replaced);
|
|
1749
|
+
}
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (key === "dangerouslySetInnerHTML") {
|
|
1753
|
+
hasDangerousHTML = true;
|
|
1754
|
+
if (this.t.isJSXExpressionContainer(attr.value)) {
|
|
1755
|
+
this.observableManager.collectObservables(
|
|
1756
|
+
attr.value.expression,
|
|
1757
|
+
context2.observables,
|
|
1758
|
+
this.astUtils
|
|
1759
|
+
);
|
|
1760
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1761
|
+
attr.value.expression,
|
|
1762
|
+
context2.observableSignals,
|
|
1763
|
+
this.astUtils
|
|
1764
|
+
);
|
|
1765
|
+
dangerousHTMLValue = this.astUtils.replaceThisWithSelf(replaced);
|
|
1766
|
+
}
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
if (/^on[A-Z]/.test(key)) {
|
|
1770
|
+
this.processEventListener(key, attr, elId, statements, context2);
|
|
1771
|
+
continue;
|
|
1772
|
+
}
|
|
1773
|
+
if (key === "style" && this.t.isJSXExpressionContainer(attr.value)) {
|
|
1774
|
+
this.processStyleAttribute(attr, elId, statements, context2);
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
this.processRegularAttribute(key, attr, elId, statements, context2);
|
|
1778
|
+
}
|
|
1779
|
+
return { hasRef, refValue, hasDangerousHTML, dangerousHTMLValue };
|
|
1780
|
+
}
|
|
1781
|
+
processEventListener(key, attr, elId, statements, context2) {
|
|
1782
|
+
const eventName = key.slice(2).toLowerCase();
|
|
1783
|
+
let handler = this.t.nullLiteral();
|
|
1784
|
+
if (this.t.isJSXExpressionContainer(attr.value)) {
|
|
1785
|
+
this.observableManager.collectObservables(
|
|
1786
|
+
attr.value.expression,
|
|
1787
|
+
context2.observables,
|
|
1788
|
+
this.astUtils
|
|
1789
|
+
);
|
|
1790
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1791
|
+
attr.value.expression,
|
|
1792
|
+
context2.observableSignals,
|
|
1793
|
+
this.astUtils
|
|
1794
|
+
);
|
|
1795
|
+
handler = this.astUtils.replaceThisWithSelf(replaced);
|
|
1796
|
+
}
|
|
1797
|
+
statements.push(
|
|
1798
|
+
this.t.expressionStatement(
|
|
1799
|
+
this.t.callExpression(
|
|
1800
|
+
this.t.memberExpression(elId, this.t.identifier("addEventListener")),
|
|
1801
|
+
[this.t.stringLiteral(eventName), handler]
|
|
1802
|
+
)
|
|
1803
|
+
)
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
processStyleAttribute(attr, elId, statements, context2) {
|
|
1807
|
+
if (!this.t.isJSXExpressionContainer(attr.value)) return;
|
|
1808
|
+
this.observableManager.collectObservables(
|
|
1809
|
+
attr.value.expression,
|
|
1810
|
+
context2.observables,
|
|
1811
|
+
this.astUtils
|
|
1812
|
+
);
|
|
1813
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1814
|
+
attr.value.expression,
|
|
1815
|
+
context2.observableSignals,
|
|
1816
|
+
this.astUtils
|
|
1817
|
+
);
|
|
1818
|
+
statements.push(
|
|
1819
|
+
this.t.expressionStatement(
|
|
1820
|
+
this.t.callExpression(this.t.identifier("$style"), [
|
|
1821
|
+
elId,
|
|
1822
|
+
this.t.arrowFunctionExpression(
|
|
1823
|
+
[],
|
|
1824
|
+
this.astUtils.replaceThisWithSelf(replaced)
|
|
1825
|
+
)
|
|
1826
|
+
])
|
|
1827
|
+
)
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
processRegularAttribute(key, attr, elId, statements, context2) {
|
|
1831
|
+
const attrName = key === "className" ? "class" : key;
|
|
1832
|
+
let value;
|
|
1833
|
+
if (this.t.isStringLiteral(attr.value)) {
|
|
1834
|
+
value = attr.value;
|
|
1835
|
+
} else if (this.t.isJSXExpressionContainer(attr.value)) {
|
|
1836
|
+
this.observableManager.collectObservables(
|
|
1837
|
+
attr.value.expression,
|
|
1838
|
+
context2.observables,
|
|
1839
|
+
this.astUtils
|
|
1840
|
+
);
|
|
1841
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1842
|
+
attr.value.expression,
|
|
1843
|
+
context2.observableSignals,
|
|
1844
|
+
this.astUtils
|
|
1845
|
+
);
|
|
1846
|
+
value = this.astUtils.replaceThisWithSelf(replaced);
|
|
1847
|
+
} else {
|
|
1848
|
+
value = this.t.booleanLiteral(true);
|
|
1849
|
+
}
|
|
1850
|
+
statements.push(
|
|
1851
|
+
this.t.expressionStatement(
|
|
1852
|
+
this.t.callExpression(
|
|
1853
|
+
this.t.memberExpression(elId, this.t.identifier("setAttribute")),
|
|
1854
|
+
[this.t.stringLiteral(attrName), value]
|
|
1855
|
+
)
|
|
1856
|
+
)
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
processChildren(children, childExpressions, statements, scope, context2) {
|
|
1860
|
+
for (const child of children) {
|
|
1861
|
+
if (this.t.isJSXText(child)) {
|
|
1862
|
+
const text = child.value.trim();
|
|
1863
|
+
if (text) childExpressions.push(this.t.stringLiteral(text));
|
|
1864
|
+
} else if (this.t.isJSXExpressionContainer(child)) {
|
|
1865
|
+
const expr = child.expression;
|
|
1866
|
+
if (!this.t.isJSXEmptyExpression(expr)) {
|
|
1867
|
+
this.observableManager.collectObservables(
|
|
1868
|
+
expr,
|
|
1869
|
+
context2.observables,
|
|
1870
|
+
this.astUtils
|
|
1871
|
+
);
|
|
1872
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1873
|
+
expr,
|
|
1874
|
+
context2.observableSignals,
|
|
1875
|
+
this.astUtils
|
|
1876
|
+
);
|
|
1877
|
+
childExpressions.push(this.astUtils.replaceThisWithSelf(replaced));
|
|
1878
|
+
}
|
|
1879
|
+
} else if (this.t.isJSXElement(child) || this.t.isJSXFragment(child)) {
|
|
1880
|
+
const childEl = this.transformElement({ node: child }, scope, context2);
|
|
1881
|
+
statements.push(...childEl.statements);
|
|
1882
|
+
childExpressions.push(childEl.id);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
processDOMChildren(children, parentId, statements, scope, context2) {
|
|
1887
|
+
for (const child of children) {
|
|
1888
|
+
if (this.t.isJSXText(child)) {
|
|
1889
|
+
const text = child.value.trim();
|
|
1890
|
+
if (!text) continue;
|
|
1891
|
+
statements.push(
|
|
1892
|
+
this.t.expressionStatement(
|
|
1893
|
+
this.t.callExpression(this.t.identifier("$insert"), [
|
|
1894
|
+
parentId,
|
|
1895
|
+
this.t.stringLiteral(text)
|
|
1896
|
+
])
|
|
1897
|
+
)
|
|
1898
|
+
);
|
|
1899
|
+
} else if (this.t.isJSXExpressionContainer(child)) {
|
|
1900
|
+
const expr = child.expression;
|
|
1901
|
+
if (this.t.isJSXEmptyExpression(expr)) continue;
|
|
1902
|
+
this.observableManager.collectObservables(
|
|
1903
|
+
expr,
|
|
1904
|
+
context2.observables,
|
|
1905
|
+
this.astUtils
|
|
1906
|
+
);
|
|
1907
|
+
let insertedValue;
|
|
1908
|
+
if (this.guards.isSignalMember(expr)) {
|
|
1909
|
+
insertedValue = this.astUtils.getObject(
|
|
1910
|
+
expr
|
|
1911
|
+
);
|
|
1912
|
+
} else if (this.guards.isObservableMember(expr)) {
|
|
1913
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1914
|
+
expr,
|
|
1915
|
+
context2.observableSignals,
|
|
1916
|
+
this.astUtils
|
|
1917
|
+
);
|
|
1918
|
+
insertedValue = this.astUtils.getObject(replaced);
|
|
1919
|
+
} else {
|
|
1920
|
+
const replaced = this.observableManager.replaceObservablesWithSignals(
|
|
1921
|
+
expr,
|
|
1922
|
+
context2.observableSignals,
|
|
1923
|
+
this.astUtils
|
|
1924
|
+
);
|
|
1925
|
+
insertedValue = this.t.arrowFunctionExpression(
|
|
1926
|
+
[],
|
|
1927
|
+
this.astUtils.replaceThisWithSelf(replaced)
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
statements.push(
|
|
1931
|
+
this.t.expressionStatement(
|
|
1932
|
+
this.t.callExpression(this.t.identifier("$insert"), [
|
|
1933
|
+
parentId,
|
|
1934
|
+
insertedValue
|
|
1935
|
+
])
|
|
1936
|
+
)
|
|
1937
|
+
);
|
|
1938
|
+
} else if (this.t.isJSXElement(child) || this.t.isJSXFragment(child)) {
|
|
1939
|
+
const childEl = this.transformElement({ node: child }, scope, context2);
|
|
1940
|
+
statements.push(...childEl.statements);
|
|
1941
|
+
statements.push(
|
|
1942
|
+
this.t.expressionStatement(
|
|
1943
|
+
this.t.callExpression(this.t.identifier("$insert"), [
|
|
1944
|
+
parentId,
|
|
1945
|
+
childEl.id
|
|
1946
|
+
])
|
|
1947
|
+
)
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
function j2d({ types: t }) {
|
|
1954
|
+
const guards = new NodeTypeGuards(t);
|
|
1955
|
+
const astUtils = new ASTUtilities(t, guards);
|
|
1956
|
+
const jsxUtils = new JSXUtilities(t);
|
|
1957
|
+
const observableManager = new ObservableManager(t, guards);
|
|
1958
|
+
const elementTransformer = new ElementTransformer(
|
|
1959
|
+
t,
|
|
1960
|
+
guards,
|
|
1961
|
+
astUtils,
|
|
1962
|
+
jsxUtils,
|
|
1963
|
+
observableManager
|
|
1964
|
+
);
|
|
1965
|
+
return {
|
|
1966
|
+
name: "jsx-to-dom",
|
|
1967
|
+
visitor: {
|
|
1968
|
+
Program: {
|
|
1969
|
+
exit(path14, state) {
|
|
1970
|
+
if (state.helpersImported) return;
|
|
1971
|
+
const helpers = [
|
|
1972
|
+
{ local: "$insert", imported: "insert" },
|
|
1973
|
+
{ local: "$createComponent", imported: "createComponent" },
|
|
1974
|
+
{ local: "$style", imported: "style" },
|
|
1975
|
+
{ local: "$spread", imported: "spread" },
|
|
1976
|
+
{ local: "$toSignal", imported: "toSignal" }
|
|
1977
|
+
];
|
|
1978
|
+
for (const helper of helpers) {
|
|
1979
|
+
path14.unshiftContainer(
|
|
1980
|
+
"body",
|
|
1981
|
+
t.importDeclaration(
|
|
1982
|
+
[
|
|
1983
|
+
t.importSpecifier(
|
|
1984
|
+
t.identifier(helper.local),
|
|
1985
|
+
t.identifier(helper.imported)
|
|
1986
|
+
)
|
|
1987
|
+
],
|
|
1988
|
+
t.stringLiteral("@kithinji/orca")
|
|
1989
|
+
)
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
state.helpersImported = true;
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
ClassMethod(path14) {
|
|
1996
|
+
if (path14.getData("processed")) return;
|
|
1997
|
+
let hasJSX = false;
|
|
1998
|
+
path14.traverse({
|
|
1999
|
+
JSXElement() {
|
|
2000
|
+
hasJSX = true;
|
|
2001
|
+
},
|
|
2002
|
+
JSXFragment() {
|
|
2003
|
+
hasJSX = true;
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
if (!hasJSX) return;
|
|
2007
|
+
path14.setData("processed", true);
|
|
2008
|
+
const body = path14.node.body;
|
|
2009
|
+
if (!t.isBlockStatement(body)) return;
|
|
2010
|
+
const observables = /* @__PURE__ */ new Map();
|
|
2011
|
+
path14.traverse({
|
|
2012
|
+
JSXElement(jsxPath) {
|
|
2013
|
+
observableManager.collectObservables(
|
|
2014
|
+
jsxPath.node,
|
|
2015
|
+
observables,
|
|
2016
|
+
astUtils
|
|
2017
|
+
);
|
|
2018
|
+
},
|
|
2019
|
+
JSXFragment(jsxPath) {
|
|
2020
|
+
observableManager.collectObservables(
|
|
2021
|
+
jsxPath.node,
|
|
2022
|
+
observables,
|
|
2023
|
+
astUtils
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
body.body.unshift(
|
|
2028
|
+
t.variableDeclaration("const", [
|
|
2029
|
+
t.variableDeclarator(t.identifier("self"), t.thisExpression())
|
|
2030
|
+
])
|
|
2031
|
+
);
|
|
2032
|
+
const observableSignals = /* @__PURE__ */ new Map();
|
|
2033
|
+
const signalDeclarations = [];
|
|
2034
|
+
for (const [key, observable] of observables) {
|
|
2035
|
+
const signalId = path14.scope.generateUidIdentifier("sig");
|
|
2036
|
+
observableSignals.set(key, signalId);
|
|
2037
|
+
signalDeclarations.push(
|
|
2038
|
+
t.variableDeclaration("const", [
|
|
2039
|
+
t.variableDeclarator(
|
|
2040
|
+
signalId,
|
|
2041
|
+
t.callExpression(t.identifier("$toSignal"), [
|
|
2042
|
+
observable,
|
|
2043
|
+
t.identifier("self")
|
|
2044
|
+
])
|
|
2045
|
+
)
|
|
2046
|
+
])
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
if (signalDeclarations.length > 0) {
|
|
2050
|
+
astUtils.insertBeforeReturn(body.body, signalDeclarations);
|
|
2051
|
+
}
|
|
2052
|
+
const context2 = { observables, observableSignals };
|
|
2053
|
+
path14.traverse({
|
|
2054
|
+
JSXElement(jsxPath) {
|
|
2055
|
+
if (jsxPath.getData("processed")) return;
|
|
2056
|
+
jsxPath.setData("processed", true);
|
|
2057
|
+
const { id, statements } = elementTransformer.transformElement(
|
|
2058
|
+
jsxPath,
|
|
2059
|
+
jsxPath.scope,
|
|
2060
|
+
context2
|
|
2061
|
+
);
|
|
2062
|
+
jsxPath.replaceWith(
|
|
2063
|
+
t.callExpression(
|
|
2064
|
+
t.arrowFunctionExpression(
|
|
2065
|
+
[],
|
|
2066
|
+
t.blockStatement([...statements, t.returnStatement(id)])
|
|
2067
|
+
),
|
|
2068
|
+
[]
|
|
2069
|
+
)
|
|
2070
|
+
);
|
|
2071
|
+
},
|
|
2072
|
+
JSXFragment(jsxPath) {
|
|
2073
|
+
if (jsxPath.getData("processed")) return;
|
|
2074
|
+
jsxPath.setData("processed", true);
|
|
2075
|
+
const { id, statements } = elementTransformer.transformElement(
|
|
2076
|
+
jsxPath,
|
|
2077
|
+
jsxPath.scope,
|
|
2078
|
+
context2
|
|
2079
|
+
);
|
|
2080
|
+
jsxPath.replaceWith(
|
|
2081
|
+
t.callExpression(
|
|
2082
|
+
t.arrowFunctionExpression(
|
|
2083
|
+
[],
|
|
2084
|
+
t.blockStatement([...statements, t.returnStatement(id)])
|
|
2085
|
+
),
|
|
2086
|
+
[]
|
|
2087
|
+
)
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// src/plugins/generators/generate_server_component.ts
|
|
2097
|
+
import { parseSync as parseSync3 } from "@swc/core";
|
|
2098
|
+
function generateServerComponent(filePath, code) {
|
|
2099
|
+
const ast = parseSync3(code, {
|
|
2100
|
+
syntax: "typescript",
|
|
2101
|
+
tsx: filePath.endsWith("x"),
|
|
2102
|
+
decorators: true
|
|
2103
|
+
});
|
|
2104
|
+
const componentInfo = extractComponentInfo(ast);
|
|
2105
|
+
return generateStubCode(componentInfo);
|
|
2106
|
+
}
|
|
2107
|
+
function extractComponentInfo(ast) {
|
|
2108
|
+
let componentClass = null;
|
|
2109
|
+
for (const item of ast.body) {
|
|
2110
|
+
if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
|
|
2111
|
+
const classDecl = item.declaration;
|
|
2112
|
+
if (hasComponentDecorator2(classDecl.decorators)) {
|
|
2113
|
+
componentClass = classDecl;
|
|
2114
|
+
break;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
if (!componentClass || !componentClass.identifier) {
|
|
2119
|
+
throw new Error("Component class is undefined");
|
|
2120
|
+
}
|
|
2121
|
+
const className = componentClass.identifier.value;
|
|
2122
|
+
const methods = extractMethods2(componentClass);
|
|
2123
|
+
return {
|
|
2124
|
+
className,
|
|
2125
|
+
methods
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
function hasComponentDecorator2(decorators) {
|
|
2129
|
+
if (!decorators) return false;
|
|
2130
|
+
return decorators.some((decorator) => {
|
|
2131
|
+
const expr = decorator.expression;
|
|
2132
|
+
if (expr.type === "CallExpression") {
|
|
2133
|
+
if (expr.callee.type === "Identifier" && expr.callee.value === "Component") {
|
|
2134
|
+
return true;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (expr.type === "Identifier" && expr.value === "Component") {
|
|
2138
|
+
return true;
|
|
2139
|
+
}
|
|
2140
|
+
return false;
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
function extractMethods2(classDecl) {
|
|
2144
|
+
const methods = [];
|
|
2145
|
+
for (const member of classDecl.body) {
|
|
2146
|
+
if (member.type === "ClassMethod") {
|
|
2147
|
+
const method = member;
|
|
2148
|
+
const methodName = method.key.type === "Identifier" ? method.key.value : "";
|
|
2149
|
+
if (!methodName) {
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
const params = extractMethodParams2(method.function.params || []);
|
|
2153
|
+
const returnType = extractReturnType2(method.function.returnType);
|
|
2154
|
+
const isAsync = method.function.async || false;
|
|
2155
|
+
methods.push({
|
|
2156
|
+
name: methodName,
|
|
2157
|
+
params,
|
|
2158
|
+
returnType,
|
|
2159
|
+
isAsync
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return methods;
|
|
2164
|
+
}
|
|
2165
|
+
function extractMethodParams2(params) {
|
|
2166
|
+
const result = [];
|
|
2167
|
+
for (const param of params) {
|
|
2168
|
+
if (param.type === "Parameter") {
|
|
2169
|
+
const pat = param.pat;
|
|
2170
|
+
if (pat.type === "Identifier") {
|
|
2171
|
+
const name = pat.value;
|
|
2172
|
+
const type = pat.typeAnnotation?.typeAnnotation ? stringifyType3(pat.typeAnnotation.typeAnnotation) : "any";
|
|
2173
|
+
result.push({
|
|
2174
|
+
name,
|
|
2175
|
+
type
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return result;
|
|
2181
|
+
}
|
|
2182
|
+
function extractReturnType2(returnType) {
|
|
2183
|
+
if (!returnType || !returnType.typeAnnotation) {
|
|
2184
|
+
return "any";
|
|
2185
|
+
}
|
|
2186
|
+
const type = returnType.typeAnnotation;
|
|
2187
|
+
if (type.type === "TsTypeReference") {
|
|
2188
|
+
const typeName = type.typeName;
|
|
2189
|
+
if (typeName.type === "Identifier" && typeName.value === "Promise") {
|
|
2190
|
+
if (type.typeParams && type.typeParams.params.length > 0) {
|
|
2191
|
+
return stringifyType3(type.typeParams.params[0]);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return stringifyType3(type);
|
|
2196
|
+
}
|
|
2197
|
+
function stringifyType3(typeNode) {
|
|
2198
|
+
if (!typeNode) return "any";
|
|
2199
|
+
switch (typeNode.type) {
|
|
2200
|
+
case "TsKeywordType":
|
|
2201
|
+
return typeNode.kind;
|
|
2202
|
+
case "TsTypeReference":
|
|
2203
|
+
if (typeNode.typeName.type === "Identifier") {
|
|
2204
|
+
const baseName = typeNode.typeName.value;
|
|
2205
|
+
if (typeNode.typeParams && typeNode.typeParams.params.length > 0) {
|
|
2206
|
+
const params = typeNode.typeParams.params.map(stringifyType3).join(", ");
|
|
2207
|
+
return `${baseName}<${params}>`;
|
|
2208
|
+
}
|
|
2209
|
+
return baseName;
|
|
2210
|
+
}
|
|
2211
|
+
return "any";
|
|
2212
|
+
case "TsArrayType":
|
|
2213
|
+
return `${stringifyType3(typeNode.elemType)}[]`;
|
|
2214
|
+
case "TsUnionType":
|
|
2215
|
+
return typeNode.types.map(stringifyType3).join(" | ");
|
|
2216
|
+
case "TsIntersectionType":
|
|
2217
|
+
return typeNode.types.map(stringifyType3).join(" & ");
|
|
2218
|
+
case "TsTypeLiteral":
|
|
2219
|
+
const props = typeNode.members.map((member) => {
|
|
2220
|
+
if (member.type === "TsPropertySignature") {
|
|
2221
|
+
const key = member.key.type === "Identifier" ? member.key.value : "";
|
|
2222
|
+
const type = member.typeAnnotation ? stringifyType3(member.typeAnnotation.typeAnnotation) : "any";
|
|
2223
|
+
return `${key}: ${type}`;
|
|
2224
|
+
}
|
|
2225
|
+
return "";
|
|
2226
|
+
}).filter(Boolean);
|
|
2227
|
+
return `{ ${props.join("; ")} }`;
|
|
2228
|
+
default:
|
|
2229
|
+
return "any";
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
function generateStubCode(componentInfo) {
|
|
2233
|
+
const className = componentInfo.className;
|
|
2234
|
+
const build = componentInfo.methods.find((p) => p.name == "build");
|
|
2235
|
+
if (build == void 0) {
|
|
2236
|
+
throw new Error("Component has no build function");
|
|
2237
|
+
}
|
|
2238
|
+
return `import {
|
|
2239
|
+
Component,
|
|
2240
|
+
Inject,
|
|
2241
|
+
getCurrentInjector,
|
|
2242
|
+
OrcaComponent,
|
|
2243
|
+
JSX,
|
|
2244
|
+
OSC,
|
|
2245
|
+
HttpClient,
|
|
2246
|
+
} from "@kithinji/orca";
|
|
2247
|
+
|
|
2248
|
+
@Component()
|
|
2249
|
+
export class ${className} extends OrcaComponent {
|
|
2250
|
+
props!: any;
|
|
2251
|
+
|
|
2252
|
+
constructor(
|
|
2253
|
+
@Inject("OSC_URL", { maybe: true }) private oscUrl?: string,
|
|
2254
|
+
private readonly http: HttpClient,
|
|
2255
|
+
) {
|
|
2256
|
+
super();
|
|
2257
|
+
|
|
2258
|
+
if(this.oscUrl === undefined) {
|
|
2259
|
+
throw new Error("Server component requires osc url be defined");
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
build() {
|
|
2264
|
+
const root = document.createElement("div");
|
|
2265
|
+
root.textContent = "loading...";
|
|
2266
|
+
|
|
2267
|
+
const injector = getCurrentInjector();
|
|
2268
|
+
|
|
2269
|
+
if(injector == null) {
|
|
2270
|
+
throw new Error("Injector is null");
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
const osc = new OSC(root);
|
|
2274
|
+
|
|
2275
|
+
const subscription = this.http.post<JSX.Element>(
|
|
2276
|
+
\`\${this.oscUrl}?c=${className}\`, {
|
|
2277
|
+
body: this.props
|
|
2278
|
+
}
|
|
2279
|
+
).subscribe((jsx: JSX.Element) => {
|
|
2280
|
+
const action = jsx.action || "insert";
|
|
2281
|
+
|
|
2282
|
+
if (action === "insert") {
|
|
2283
|
+
osc.handleInsert(jsx);
|
|
2284
|
+
} else if (action === "update") {
|
|
2285
|
+
osc.handleUpdate(jsx);
|
|
2286
|
+
} else {
|
|
2287
|
+
console.warn(\`Unknown action: \${action}\`);
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
this.pushDrop(() => subscription.unsubscribe());
|
|
2292
|
+
|
|
2293
|
+
return root;
|
|
2294
|
+
}
|
|
2295
|
+
}`;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
// src/plugins/generators/generate_rsc.ts
|
|
2299
|
+
import { parseSync as parseSync4 } from "@swc/core";
|
|
2300
|
+
function generateRscStub(filePath, code) {
|
|
2301
|
+
const ast = parseSync4(code, {
|
|
2302
|
+
syntax: "typescript",
|
|
2303
|
+
tsx: filePath.endsWith("x"),
|
|
2304
|
+
decorators: true
|
|
2305
|
+
});
|
|
2306
|
+
const serviceInfo = extractServiceInfo2(ast);
|
|
2307
|
+
return generateStubCode2(serviceInfo);
|
|
2308
|
+
}
|
|
2309
|
+
function extractServiceInfo2(ast) {
|
|
2310
|
+
let serviceClass = null;
|
|
2311
|
+
for (const item of ast.body) {
|
|
2312
|
+
if (item.type === "ExportDeclaration" && item.declaration.type === "ClassDeclaration") {
|
|
2313
|
+
const classDecl = item.declaration;
|
|
2314
|
+
if (hasInjectableDecorator2(classDecl.decorators)) {
|
|
2315
|
+
serviceClass = classDecl;
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
if (!serviceClass || !serviceClass.identifier) {
|
|
2321
|
+
throw new Error("Service class is undefined");
|
|
2322
|
+
}
|
|
2323
|
+
const className = serviceClass.identifier.value;
|
|
2324
|
+
const methods = extractMethods3(serviceClass);
|
|
2325
|
+
return {
|
|
2326
|
+
className,
|
|
2327
|
+
methods
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
function hasInjectableDecorator2(decorators) {
|
|
2331
|
+
if (!decorators) return false;
|
|
2332
|
+
return decorators.some((decorator) => {
|
|
2333
|
+
const expr = decorator.expression;
|
|
2334
|
+
if (expr.type === "CallExpression") {
|
|
2335
|
+
if (expr.callee.type === "Identifier" && expr.callee.value === "Injectable") {
|
|
2336
|
+
return true;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
if (expr.type === "Identifier" && expr.value === "Injectable") {
|
|
2340
|
+
return true;
|
|
2341
|
+
}
|
|
2342
|
+
return false;
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
function extractMethods3(classDecl) {
|
|
2346
|
+
const methods = [];
|
|
2347
|
+
for (const member of classDecl.body) {
|
|
2348
|
+
if (member.type === "ClassMethod" && member.accessibility === "public") {
|
|
2349
|
+
const method = member;
|
|
2350
|
+
const methodName = method.key.type === "Identifier" ? method.key.value : "";
|
|
2351
|
+
if (!methodName) {
|
|
2352
|
+
continue;
|
|
2353
|
+
}
|
|
2354
|
+
if (!method.function.async) {
|
|
2355
|
+
throw new Error(
|
|
2356
|
+
`Server action ${classDecl.identifier.value}.${methodName} must be async.`
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2359
|
+
const params = extractMethodParams3(method.function.params || []);
|
|
2360
|
+
const returnType = extractReturnType3(method.function.returnType);
|
|
2361
|
+
const isAsync = method.function.async || false;
|
|
2362
|
+
methods.push({
|
|
2363
|
+
name: methodName,
|
|
2364
|
+
params,
|
|
2365
|
+
returnType,
|
|
2366
|
+
isAsync
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return methods;
|
|
2371
|
+
}
|
|
2372
|
+
function extractMethodParams3(params) {
|
|
2373
|
+
const result = [];
|
|
2374
|
+
for (const param of params) {
|
|
2375
|
+
if (param.type === "Parameter") {
|
|
2376
|
+
const pat = param.pat;
|
|
2377
|
+
if (pat.type === "Identifier") {
|
|
2378
|
+
const name = pat.value;
|
|
2379
|
+
const type = pat.typeAnnotation?.typeAnnotation ? stringifyType4(pat.typeAnnotation.typeAnnotation) : "any";
|
|
2380
|
+
result.push({
|
|
2381
|
+
name,
|
|
2382
|
+
type
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
return result;
|
|
2388
|
+
}
|
|
2389
|
+
function extractReturnType3(returnType) {
|
|
2390
|
+
if (!returnType || !returnType.typeAnnotation) {
|
|
2391
|
+
return "any";
|
|
2392
|
+
}
|
|
2393
|
+
const type = returnType.typeAnnotation;
|
|
2394
|
+
if (type.type === "TsTypeReference") {
|
|
2395
|
+
const typeName = type.typeName;
|
|
2396
|
+
if (typeName.type === "Identifier" && typeName.value === "Promise") {
|
|
2397
|
+
if (type.typeParams && type.typeParams.params.length > 0) {
|
|
2398
|
+
return stringifyType4(type.typeParams.params[0]);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
return stringifyType4(type);
|
|
2403
|
+
}
|
|
2404
|
+
function stringifyType4(typeNode) {
|
|
2405
|
+
if (!typeNode) return "any";
|
|
2406
|
+
switch (typeNode.type) {
|
|
2407
|
+
case "TsKeywordType":
|
|
2408
|
+
return typeNode.kind;
|
|
2409
|
+
case "TsTypeReference":
|
|
2410
|
+
if (typeNode.typeName.type === "Identifier") {
|
|
2411
|
+
const baseName = typeNode.typeName.value;
|
|
2412
|
+
if (typeNode.typeParams && typeNode.typeParams.params.length > 0) {
|
|
2413
|
+
const params = typeNode.typeParams.params.map(stringifyType4).join(", ");
|
|
2414
|
+
return `${baseName}<${params}>`;
|
|
2415
|
+
}
|
|
2416
|
+
return baseName;
|
|
2417
|
+
}
|
|
2418
|
+
return "any";
|
|
2419
|
+
case "TsArrayType":
|
|
2420
|
+
return `${stringifyType4(typeNode.elemType)}[]`;
|
|
2421
|
+
case "TsUnionType":
|
|
2422
|
+
return typeNode.types.map(stringifyType4).join(" | ");
|
|
2423
|
+
case "TsIntersectionType":
|
|
2424
|
+
return typeNode.types.map(stringifyType4).join(" & ");
|
|
2425
|
+
case "TsTypeLiteral":
|
|
2426
|
+
const props = typeNode.members.map((member) => {
|
|
2427
|
+
if (member.type === "TsPropertySignature") {
|
|
2428
|
+
const key = member.key.type === "Identifier" ? member.key.value : "";
|
|
2429
|
+
const type = member.typeAnnotation ? stringifyType4(member.typeAnnotation.typeAnnotation) : "any";
|
|
2430
|
+
return `${key}: ${type}`;
|
|
2431
|
+
}
|
|
2432
|
+
return "";
|
|
2433
|
+
}).filter(Boolean);
|
|
2434
|
+
return `{ ${props.join("; ")} }`;
|
|
2435
|
+
default:
|
|
2436
|
+
return "any";
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
function generateStubCode2(serviceInfo) {
|
|
2440
|
+
const className = serviceInfo.className;
|
|
2441
|
+
const methods = serviceInfo.methods.map((method) => {
|
|
2442
|
+
const params = method.params.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
2443
|
+
const paramNames = method.params.map((p) => p.name).join(", ");
|
|
2444
|
+
const asyncKeyword = method.isAsync ? "async " : "";
|
|
2445
|
+
const returnType = method.isAsync ? `Promise<${method.returnType}>` : method.returnType;
|
|
2446
|
+
const hasParams = method.params.length > 0;
|
|
2447
|
+
const bodyParam = hasParams ? `{ ${paramNames} }` : "{}";
|
|
2448
|
+
if (!hasParams) {
|
|
2449
|
+
return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
|
|
2450
|
+
const response = await fetch(\`/${className}/${method.name}\`, {
|
|
2451
|
+
method: 'GET',
|
|
2452
|
+
headers: {
|
|
2453
|
+
'Content-Type': 'application/json',
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
if (!response.ok) {
|
|
2458
|
+
throw new Error(\`HTTP error! status: \${response.status}\`);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
return response.json();
|
|
2462
|
+
}`;
|
|
2463
|
+
}
|
|
2464
|
+
return ` ${asyncKeyword}${method.name}(${params}): ${returnType} {
|
|
2465
|
+
const response = await fetch(\`/${className}/${method.name}\`, {
|
|
2466
|
+
method: 'POST',
|
|
2467
|
+
headers: {
|
|
2468
|
+
'Content-Type': 'application/json',
|
|
2469
|
+
},
|
|
2470
|
+
body: JSON.stringify(${bodyParam}),
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
if (!response.ok) {
|
|
2474
|
+
throw new Error(\`HTTP error! status: \${response.status}\`);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
return response.json();
|
|
2478
|
+
}`;
|
|
2479
|
+
}).join("\n\n");
|
|
2480
|
+
return `import { Injectable } from "@kithinji/orca";
|
|
2481
|
+
|
|
2482
|
+
@Injectable()
|
|
2483
|
+
export class ${className} {
|
|
2484
|
+
${methods}
|
|
2485
|
+
}`;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// src/plugins/my.ts
|
|
2489
|
+
async function swcTransform(source, pathStr, tsx = false, react) {
|
|
2490
|
+
const resolveDir = path6.dirname(pathStr);
|
|
2491
|
+
const swcResult = await transform(source, {
|
|
2492
|
+
filename: pathStr,
|
|
2493
|
+
jsc: {
|
|
2494
|
+
parser: {
|
|
2495
|
+
syntax: "typescript",
|
|
2496
|
+
tsx,
|
|
2497
|
+
decorators: true
|
|
2498
|
+
},
|
|
2499
|
+
transform: {
|
|
2500
|
+
legacyDecorator: true,
|
|
2501
|
+
decoratorMetadata: true,
|
|
2502
|
+
react
|
|
2503
|
+
},
|
|
2504
|
+
target: "esnext"
|
|
2505
|
+
},
|
|
2506
|
+
isModule: true
|
|
2507
|
+
});
|
|
2508
|
+
return {
|
|
2509
|
+
contents: swcResult.code,
|
|
2510
|
+
loader: "js",
|
|
2511
|
+
resolveDir
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function parseFileMetadata(source, path14) {
|
|
2515
|
+
const isTsx = path14.endsWith(".tsx");
|
|
2516
|
+
const isInteractiveFile = source.startsWith('"use interactive"') || source.startsWith("'use interactive'");
|
|
2517
|
+
const isPublicFile = source.startsWith('"use public"') || source.startsWith("'use public'");
|
|
2518
|
+
let directive = null;
|
|
2519
|
+
if (isInteractiveFile) directive = "interactive";
|
|
2520
|
+
else if (isPublicFile) directive = "public";
|
|
2521
|
+
return {
|
|
2522
|
+
source,
|
|
2523
|
+
path: path14,
|
|
2524
|
+
isTsx,
|
|
2525
|
+
directive,
|
|
2526
|
+
isPublicFile,
|
|
2527
|
+
isInteractiveFile
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
var ServerBuildTransformer = class {
|
|
2531
|
+
async transformPublicFile(source, path14) {
|
|
2532
|
+
const controllerCode = generateController(path14, source);
|
|
2533
|
+
if (controllerCode) {
|
|
2534
|
+
source = `${source}
|
|
2535
|
+
|
|
2536
|
+
${controllerCode}
|
|
2537
|
+
`;
|
|
2538
|
+
}
|
|
2539
|
+
return swcTransform(source, path14);
|
|
2540
|
+
}
|
|
2541
|
+
async transformRegularTypeScript(source, path14, isPublic) {
|
|
2542
|
+
if (isPublic) {
|
|
2543
|
+
return this.transformPublicFile(source, path14);
|
|
2544
|
+
}
|
|
2545
|
+
return swcTransform(source, path14);
|
|
2546
|
+
}
|
|
2547
|
+
async transformServerTsx(source, path14) {
|
|
2548
|
+
return swcTransform(source, path14, true, {
|
|
2549
|
+
runtime: "automatic",
|
|
2550
|
+
importSource: "@kithinji/orca"
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
async transformInteractiveTsxStub(source, path14) {
|
|
2554
|
+
const stubSource = generateServerStub(path14, source);
|
|
2555
|
+
return swcTransform(stubSource, path14);
|
|
2556
|
+
}
|
|
2557
|
+
async process(metadata, onClientFound) {
|
|
2558
|
+
const expandedSource = await expandMacros(metadata.source, metadata.path);
|
|
2559
|
+
const expandedMetadata = { ...metadata, source: expandedSource };
|
|
2560
|
+
const { source, path: path14, isTsx, isInteractiveFile, isPublicFile } = expandedMetadata;
|
|
2561
|
+
if (isTsx) {
|
|
2562
|
+
if (isInteractiveFile) {
|
|
2563
|
+
onClientFound(path14);
|
|
2564
|
+
return this.transformInteractiveTsxStub(source, path14);
|
|
2565
|
+
}
|
|
2566
|
+
return this.transformServerTsx(source, path14);
|
|
2567
|
+
}
|
|
2568
|
+
return this.transformRegularTypeScript(source, path14, isPublicFile);
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
var ClientBuildTransformer = class {
|
|
2572
|
+
async transformInteractiveTsx(source, path14) {
|
|
2573
|
+
const swcResult = await swcTransform(source, path14, true, {
|
|
2574
|
+
runtime: "preserve"
|
|
2575
|
+
});
|
|
2576
|
+
const babelResult = await babel.transformAsync(
|
|
2577
|
+
swcResult.contents,
|
|
2578
|
+
{
|
|
2579
|
+
filename: path14,
|
|
2580
|
+
sourceType: "module",
|
|
2581
|
+
plugins: [j2d],
|
|
2582
|
+
parserOpts: {
|
|
2583
|
+
plugins: ["jsx"]
|
|
2584
|
+
},
|
|
2585
|
+
configFile: false,
|
|
2586
|
+
babelrc: false
|
|
2587
|
+
}
|
|
2588
|
+
);
|
|
2589
|
+
return {
|
|
2590
|
+
contents: babelResult?.code || "",
|
|
2591
|
+
loader: "js",
|
|
2592
|
+
resolveDir: swcResult.resolveDir
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
async transformServerComponent(node, source, path14) {
|
|
2596
|
+
const scSource = generateServerComponent(path14, source);
|
|
2597
|
+
return swcTransform(scSource, path14);
|
|
2598
|
+
}
|
|
2599
|
+
async transformPublicFileRsc(node, source, path14) {
|
|
2600
|
+
const stubSource = generateRscStub(path14, source);
|
|
2601
|
+
return swcTransform(stubSource, path14);
|
|
2602
|
+
}
|
|
2603
|
+
async transformSharedCode(source, path14) {
|
|
2604
|
+
return swcTransform(source, path14);
|
|
2605
|
+
}
|
|
2606
|
+
async process(node, metadata) {
|
|
2607
|
+
const expandedSource = await expandMacros(metadata.source, metadata.path);
|
|
2608
|
+
const expandedMetadata = { ...metadata, source: expandedSource };
|
|
2609
|
+
const { source, path: path14, isTsx, directive } = expandedMetadata;
|
|
2610
|
+
if (isTsx) {
|
|
2611
|
+
if (directive === "interactive") {
|
|
2612
|
+
return this.transformInteractiveTsx(source, path14);
|
|
2613
|
+
} else if (directive === null) {
|
|
2614
|
+
return this.transformServerComponent(node, source, path14);
|
|
2615
|
+
} else {
|
|
2616
|
+
throw new Error(
|
|
2617
|
+
`Unexpected directive "${directive}" for TSX file: ${path14}`
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (directive === "public") {
|
|
2622
|
+
return this.transformPublicFileRsc(node, source, path14);
|
|
2623
|
+
}
|
|
2624
|
+
if (directive === null) {
|
|
2625
|
+
return this.transformSharedCode(source, path14);
|
|
2626
|
+
}
|
|
2627
|
+
return {
|
|
2628
|
+
contents: source,
|
|
2629
|
+
loader: isTsx ? "tsx" : "ts"
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
function useMyPlugin(options) {
|
|
2634
|
+
const serverTransformer = new ServerBuildTransformer();
|
|
2635
|
+
const clientTransformer = new ClientBuildTransformer();
|
|
2636
|
+
return {
|
|
2637
|
+
name: "Orca",
|
|
2638
|
+
setup(build) {
|
|
2639
|
+
build.onLoad(
|
|
2640
|
+
{ filter: /\.tsx?$/ },
|
|
2641
|
+
async (args) => {
|
|
2642
|
+
const source = await fs2.readFile(args.path, "utf8");
|
|
2643
|
+
const metadata = parseFileMetadata(source, args.path);
|
|
2644
|
+
if (options.isServerBuild) {
|
|
2645
|
+
return serverTransformer.process(metadata, options.onClientFound);
|
|
2646
|
+
}
|
|
2647
|
+
if (!options.graph) {
|
|
2648
|
+
throw new Error(
|
|
2649
|
+
"Dependency graph is required for client build but was not provided"
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
const node = options.graph[args.path];
|
|
2653
|
+
if (!node) {
|
|
2654
|
+
throw new Error(
|
|
2655
|
+
`File node not found in dependency graph: ${args.path}`
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
return clientTransformer.process(node, metadata);
|
|
2659
|
+
}
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/plugins/analyzers/graph.ts
|
|
2666
|
+
import { parseSync as parseSync5 } from "@swc/core";
|
|
2667
|
+
import * as fs3 from "fs";
|
|
2668
|
+
import * as path7 from "path";
|
|
2669
|
+
function resolveFilePath(fromFile, importPath) {
|
|
2670
|
+
if (!importPath.startsWith(".")) {
|
|
2671
|
+
return null;
|
|
2672
|
+
}
|
|
2673
|
+
const dir = path7.dirname(fromFile);
|
|
2674
|
+
const basePath = path7.resolve(dir, importPath);
|
|
2675
|
+
const extensions = ["", ".ts", ".tsx", ".js", ".jsx"];
|
|
2676
|
+
for (const ext of extensions) {
|
|
2677
|
+
const fullPath = basePath + ext;
|
|
2678
|
+
if (fs3.existsSync(fullPath) && fs3.statSync(fullPath).isFile()) {
|
|
2679
|
+
return fullPath;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
const indexFiles = ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
2683
|
+
for (const indexFile of indexFiles) {
|
|
2684
|
+
const fullPath = basePath + indexFile;
|
|
2685
|
+
if (fs3.existsSync(fullPath)) {
|
|
2686
|
+
return fullPath;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
return null;
|
|
2690
|
+
}
|
|
2691
|
+
function extractDecorators(decorators) {
|
|
2692
|
+
if (!decorators || decorators.length === 0) return [];
|
|
2693
|
+
return decorators.map((decorator) => {
|
|
2694
|
+
if (decorator.expression.type === "CallExpression") {
|
|
2695
|
+
if (decorator.expression.callee.type === "Identifier") {
|
|
2696
|
+
return decorator.expression.callee.value;
|
|
2697
|
+
}
|
|
2698
|
+
} else if (decorator.expression.type === "Identifier") {
|
|
2699
|
+
return decorator.expression.value;
|
|
2700
|
+
}
|
|
2701
|
+
return "unknown";
|
|
2702
|
+
}).filter((name) => name !== "unknown");
|
|
2703
|
+
}
|
|
2704
|
+
function extractDirective(ast) {
|
|
2705
|
+
for (const item of ast.body) {
|
|
2706
|
+
if (item.type === "ExpressionStatement" && item.expression.type === "StringLiteral") {
|
|
2707
|
+
const value = item.expression.value;
|
|
2708
|
+
if (value === "use public") return "public";
|
|
2709
|
+
if (value === "use interactive") return "interactive";
|
|
2710
|
+
}
|
|
2711
|
+
if (item.type !== "ExpressionStatement" || item.expression.type !== "StringLiteral") {
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
return null;
|
|
2716
|
+
}
|
|
2717
|
+
function extractExports(ast) {
|
|
2718
|
+
const exports = [];
|
|
2719
|
+
for (const item of ast.body) {
|
|
2720
|
+
if (item.type === "ExportDeclaration" && item.declaration) {
|
|
2721
|
+
const decl = item.declaration;
|
|
2722
|
+
if (decl.type === "ClassDeclaration" && decl.identifier) {
|
|
2723
|
+
const decorators = extractDecorators(decl.decorators);
|
|
2724
|
+
exports.push({
|
|
2725
|
+
name: decl.identifier.value,
|
|
2726
|
+
kind: "class",
|
|
2727
|
+
decorators: decorators.length > 0 ? decorators : void 0
|
|
2728
|
+
});
|
|
2729
|
+
} else if (decl.type === "FunctionDeclaration" && decl.identifier) {
|
|
2730
|
+
exports.push({ name: decl.identifier.value, kind: "function" });
|
|
2731
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
2732
|
+
for (const declarator of decl.declarations) {
|
|
2733
|
+
if (declarator.id.type === "Identifier") {
|
|
2734
|
+
exports.push({ name: declarator.id.value, kind: "variable" });
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
} else if (decl.type === "TsInterfaceDeclaration") {
|
|
2738
|
+
exports.push({ name: decl.id.value, kind: "interface" });
|
|
2739
|
+
} else if (decl.type === "TsTypeAliasDeclaration") {
|
|
2740
|
+
exports.push({ name: decl.id.value, kind: "type" });
|
|
2741
|
+
} else if (decl.type === "TsEnumDeclaration") {
|
|
2742
|
+
exports.push({ name: decl.id.value, kind: "enum" });
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
if (item.type === "ExportNamedDeclaration") {
|
|
2746
|
+
for (const spec of item.specifiers) {
|
|
2747
|
+
if (spec.type === "ExportSpecifier") {
|
|
2748
|
+
exports.push({ name: spec.orig.value, kind: "unknown" });
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
if (item.type === "ExportDefaultDeclaration") {
|
|
2753
|
+
const decl = item.decl;
|
|
2754
|
+
let kind = "unknown";
|
|
2755
|
+
let decorators;
|
|
2756
|
+
if (decl.type === "ClassExpression") {
|
|
2757
|
+
kind = "class";
|
|
2758
|
+
decorators = extractDecorators(decl.decorators);
|
|
2759
|
+
} else if (decl.type === "FunctionExpression") {
|
|
2760
|
+
kind = "function";
|
|
2761
|
+
} else if (decl.type === "TsInterfaceDeclaration") {
|
|
2762
|
+
kind = "interface";
|
|
2763
|
+
}
|
|
2764
|
+
exports.push({
|
|
2765
|
+
name: "default",
|
|
2766
|
+
kind,
|
|
2767
|
+
isDefault: true,
|
|
2768
|
+
decorators: decorators && decorators.length > 0 ? decorators : void 0
|
|
2769
|
+
});
|
|
2770
|
+
}
|
|
2771
|
+
if (item.type === "ExportDefaultExpression") {
|
|
2772
|
+
exports.push({ name: "default", kind: "unknown", isDefault: true });
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
return exports;
|
|
2776
|
+
}
|
|
2777
|
+
function extractImports(ast) {
|
|
2778
|
+
const imports = [];
|
|
2779
|
+
for (const item of ast.body) {
|
|
2780
|
+
if (item.type === "ImportDeclaration") {
|
|
2781
|
+
imports.push({
|
|
2782
|
+
path: item.source.value,
|
|
2783
|
+
specifiers: item.specifiers
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
return imports;
|
|
2788
|
+
}
|
|
2789
|
+
function buildGraph(entryPoints) {
|
|
2790
|
+
const graph = {};
|
|
2791
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2792
|
+
function processFile(filePath) {
|
|
2793
|
+
const allowed = filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx");
|
|
2794
|
+
if (!allowed) return;
|
|
2795
|
+
if (visited.has(filePath)) return;
|
|
2796
|
+
visited.add(filePath);
|
|
2797
|
+
if (!fs3.existsSync(filePath)) {
|
|
2798
|
+
console.warn(`File not found: ${filePath}`);
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
const isTsx = filePath.endsWith(".tsx") || filePath.endsWith(".jsx");
|
|
2802
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
2803
|
+
const ast = parseSync5(content, {
|
|
2804
|
+
syntax: "typescript",
|
|
2805
|
+
tsx: isTsx,
|
|
2806
|
+
decorators: true
|
|
2807
|
+
});
|
|
2808
|
+
const directive = extractDirective(ast);
|
|
2809
|
+
const exports = extractExports(ast);
|
|
2810
|
+
const rawImports = extractImports(ast);
|
|
2811
|
+
for (const { path: importPath } of rawImports) {
|
|
2812
|
+
const resolved = resolveFilePath(filePath, importPath);
|
|
2813
|
+
if (resolved) {
|
|
2814
|
+
processFile(resolved);
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
const imports = [];
|
|
2818
|
+
for (const { path: importPath, specifiers } of rawImports) {
|
|
2819
|
+
const resolvedPath = resolveFilePath(filePath, importPath);
|
|
2820
|
+
const sourceExports = resolvedPath && graph[resolvedPath] ? graph[resolvedPath].exports : [];
|
|
2821
|
+
const symbols = [];
|
|
2822
|
+
for (const spec of specifiers) {
|
|
2823
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
2824
|
+
const defaultExport = sourceExports.find((e) => e.isDefault);
|
|
2825
|
+
symbols.push({
|
|
2826
|
+
name: spec.local.value,
|
|
2827
|
+
kind: defaultExport?.kind || "unknown",
|
|
2828
|
+
decorators: defaultExport?.decorators,
|
|
2829
|
+
isDefault: true
|
|
2830
|
+
});
|
|
2831
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
2832
|
+
symbols.push({
|
|
2833
|
+
name: spec.local.value,
|
|
2834
|
+
kind: "namespace"
|
|
2835
|
+
});
|
|
2836
|
+
} else if (spec.type === "ImportSpecifier") {
|
|
2837
|
+
const importedName = spec.imported ? spec.imported.value : spec.local.value;
|
|
2838
|
+
const exportedSymbol = sourceExports.find(
|
|
2839
|
+
(e) => e.name === importedName
|
|
2840
|
+
);
|
|
2841
|
+
symbols.push({
|
|
2842
|
+
name: importedName,
|
|
2843
|
+
kind: exportedSymbol?.kind || "unknown",
|
|
2844
|
+
decorators: exportedSymbol?.decorators
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
imports.push({
|
|
2849
|
+
sourcePath: importPath,
|
|
2850
|
+
resolvedPath,
|
|
2851
|
+
symbols
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
graph[filePath] = {
|
|
2855
|
+
filePath,
|
|
2856
|
+
isTsx,
|
|
2857
|
+
directive,
|
|
2858
|
+
imports,
|
|
2859
|
+
exports
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
for (const entry of entryPoints) {
|
|
2863
|
+
const resolved = path7.resolve(entry);
|
|
2864
|
+
processFile(resolved);
|
|
2865
|
+
}
|
|
2866
|
+
return graph;
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
// src/plugins/css/index.ts
|
|
2870
|
+
import * as fs4 from "fs";
|
|
2871
|
+
function stylePlugin(store) {
|
|
2872
|
+
return {
|
|
2873
|
+
name: "style",
|
|
2874
|
+
setup(build) {
|
|
2875
|
+
build.onEnd(() => {
|
|
2876
|
+
const styleRules = store.get("style_rules");
|
|
2877
|
+
if (!styleRules || styleRules.length === 0) {
|
|
2878
|
+
console.log("No style rules generated");
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const allRules = styleRules.flat();
|
|
2882
|
+
const uniqueRules = [...new Set(allRules)];
|
|
2883
|
+
const cssOutput = uniqueRules.join("\n");
|
|
2884
|
+
fs4.writeFileSync("public/index.css", cssOutput);
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/dev/server.ts
|
|
2891
|
+
async function copyFile2() {
|
|
2892
|
+
try {
|
|
2893
|
+
await fs5.mkdir("public", { recursive: true });
|
|
2894
|
+
await fs5.copyFile("./src/client/index.html", "./public/index.html");
|
|
2895
|
+
} catch (error) {
|
|
2896
|
+
console.error("\u274C Failed to copy index.html:", error);
|
|
2897
|
+
throw error;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
async function cleanDirectories() {
|
|
2901
|
+
await Promise.all([
|
|
2902
|
+
fs5.rm("dist", { recursive: true, force: true }),
|
|
2903
|
+
fs5.rm("public", { recursive: true, force: true })
|
|
2904
|
+
]);
|
|
2905
|
+
}
|
|
2906
|
+
function createRestartServerPlugin(serverProcess, onServerBuildComplete) {
|
|
2907
|
+
return {
|
|
2908
|
+
name: "restart-server",
|
|
2909
|
+
setup(build) {
|
|
2910
|
+
build.onEnd((result) => {
|
|
2911
|
+
if (result.errors.length > 0) {
|
|
2912
|
+
console.error(
|
|
2913
|
+
`\u274C Server build failed with ${result.errors.length} error(s)`
|
|
2914
|
+
);
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
if (serverProcess.current) {
|
|
2918
|
+
serverProcess.current.kill("SIGTERM");
|
|
2919
|
+
}
|
|
2920
|
+
serverProcess.current = spawn("node", ["dist/main.js"], {
|
|
2921
|
+
stdio: "inherit"
|
|
2922
|
+
});
|
|
2923
|
+
serverProcess.current.on("error", (err) => {
|
|
2924
|
+
console.error("\u274C Server process error:", err);
|
|
2925
|
+
});
|
|
2926
|
+
onServerBuildComplete();
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
async function startDevServer() {
|
|
2932
|
+
const store = Store.getInstance();
|
|
2933
|
+
const userConfig = await loadConfig();
|
|
2934
|
+
const config = mergeConfig(getDefaultConfig(), userConfig);
|
|
2935
|
+
await cleanDirectories();
|
|
2936
|
+
await copyFile2();
|
|
2937
|
+
const entryPoints = ["src/main.ts"];
|
|
2938
|
+
const clientFiles = /* @__PURE__ */ new Set(["src/client/client.tsx"]);
|
|
2939
|
+
const serverProcessRef = { current: null };
|
|
2940
|
+
let clientCtx = null;
|
|
2941
|
+
let isShuttingDown = false;
|
|
2942
|
+
let pendingClientFiles = /* @__PURE__ */ new Set();
|
|
2943
|
+
let needsClientRebuild = false;
|
|
2944
|
+
async function rebuildClient() {
|
|
2945
|
+
if (isShuttingDown) return;
|
|
2946
|
+
try {
|
|
2947
|
+
if (clientCtx) {
|
|
2948
|
+
await clientCtx.dispose();
|
|
2949
|
+
clientCtx = null;
|
|
2950
|
+
}
|
|
2951
|
+
if (clientFiles.size === 0) return;
|
|
2952
|
+
const entryPoints2 = Array.from(clientFiles);
|
|
2953
|
+
const graph = buildGraph(entryPoints2);
|
|
2954
|
+
clientCtx = await esbuild2.context({
|
|
2955
|
+
entryPoints: entryPoints2,
|
|
2956
|
+
bundle: true,
|
|
2957
|
+
outdir: "public",
|
|
2958
|
+
outbase: ".",
|
|
2959
|
+
platform: "browser",
|
|
2960
|
+
format: "esm",
|
|
2961
|
+
sourcemap: config.build?.sourcemap ?? true,
|
|
2962
|
+
splitting: true,
|
|
2963
|
+
minify: config.build?.minify ?? false,
|
|
2964
|
+
plugins: [
|
|
2965
|
+
...config.plugins?.map((cb) => cb(store)) || [],
|
|
2966
|
+
...config.client_plugins?.map((cb) => cb(store)) || [],
|
|
2967
|
+
useMyPlugin({
|
|
2968
|
+
graph,
|
|
2969
|
+
isServerBuild: false,
|
|
2970
|
+
onClientFound: () => {
|
|
2971
|
+
}
|
|
2972
|
+
}),
|
|
2973
|
+
{
|
|
2974
|
+
name: "client-build-logger",
|
|
2975
|
+
setup(build) {
|
|
2976
|
+
build.onEnd((result) => {
|
|
2977
|
+
if (result.errors.length > 0) {
|
|
2978
|
+
console.error(
|
|
2979
|
+
`\u274C Client build failed with ${result.errors.length} error(s)`
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
],
|
|
2986
|
+
write: true
|
|
2987
|
+
});
|
|
2988
|
+
await clientCtx.watch();
|
|
2989
|
+
pendingClientFiles.clear();
|
|
2990
|
+
needsClientRebuild = false;
|
|
2991
|
+
} catch (error) {
|
|
2992
|
+
console.error("\u274C Failed to rebuild client:", error);
|
|
2993
|
+
throw error;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
async function onServerBuildComplete() {
|
|
2997
|
+
if (needsClientRebuild && pendingClientFiles.size > 0) {
|
|
2998
|
+
await rebuildClient();
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
const serverCtx = await esbuild2.context({
|
|
3002
|
+
entryPoints,
|
|
3003
|
+
bundle: true,
|
|
3004
|
+
outdir: config.build?.outDir || "dist",
|
|
3005
|
+
platform: "node",
|
|
3006
|
+
format: "esm",
|
|
3007
|
+
packages: "external",
|
|
3008
|
+
sourcemap: config.build?.sourcemap ?? true,
|
|
3009
|
+
minify: config.build?.minify ?? false,
|
|
3010
|
+
plugins: [
|
|
3011
|
+
...config.plugins?.map((cb) => cb(store)) || [],
|
|
3012
|
+
useMyPlugin({
|
|
3013
|
+
isServerBuild: true,
|
|
3014
|
+
onClientFound: async (filePath) => {
|
|
3015
|
+
const isNewFile = !clientFiles.has(filePath);
|
|
3016
|
+
if (isNewFile) {
|
|
3017
|
+
clientFiles.add(filePath);
|
|
3018
|
+
pendingClientFiles.add(filePath);
|
|
3019
|
+
needsClientRebuild = true;
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
}),
|
|
3023
|
+
createRestartServerPlugin(serverProcessRef, onServerBuildComplete)
|
|
3024
|
+
],
|
|
3025
|
+
write: true
|
|
3026
|
+
});
|
|
3027
|
+
async function shutdown() {
|
|
3028
|
+
if (isShuttingDown) return;
|
|
3029
|
+
isShuttingDown = true;
|
|
3030
|
+
try {
|
|
3031
|
+
if (serverProcessRef.current) {
|
|
3032
|
+
serverProcessRef.current.kill("SIGTERM");
|
|
3033
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
3034
|
+
}
|
|
3035
|
+
await serverCtx.dispose();
|
|
3036
|
+
if (clientCtx) await clientCtx.dispose();
|
|
3037
|
+
process.exit(0);
|
|
3038
|
+
} catch (error) {
|
|
3039
|
+
console.error("\u274C Error during shutdown:", error);
|
|
3040
|
+
process.exit(1);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
process.on("SIGINT", shutdown);
|
|
3044
|
+
process.on("SIGTERM", shutdown);
|
|
3045
|
+
await serverCtx.watch();
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
// src/config/index.ts
|
|
3049
|
+
var config_exports = {};
|
|
3050
|
+
__export(config_exports, {
|
|
3051
|
+
getDefaultConfig: () => getDefaultConfig,
|
|
3052
|
+
loadConfig: () => loadConfig,
|
|
3053
|
+
mergeConfig: () => mergeConfig
|
|
3054
|
+
});
|
|
3055
|
+
|
|
3056
|
+
// src/add/component/component.ts
|
|
3057
|
+
import * as fs6 from "fs";
|
|
3058
|
+
import * as path8 from "path";
|
|
3059
|
+
import * as ts2 from "typescript";
|
|
3060
|
+
var ComponentDefinition = class {
|
|
3061
|
+
};
|
|
3062
|
+
var ButtonComponent = class extends ComponentDefinition {
|
|
3063
|
+
constructor() {
|
|
3064
|
+
super(...arguments);
|
|
3065
|
+
this.name = "button";
|
|
3066
|
+
this.dependencies = [];
|
|
3067
|
+
}
|
|
3068
|
+
generate() {
|
|
3069
|
+
return `"use interactive";
|
|
3070
|
+
|
|
3071
|
+
import { Component, JSX } from "@kithinji/orca";
|
|
3072
|
+
|
|
3073
|
+
@Component()
|
|
3074
|
+
export class Button {
|
|
3075
|
+
props!: {
|
|
3076
|
+
children: any
|
|
3077
|
+
};
|
|
3078
|
+
|
|
3079
|
+
build() {
|
|
3080
|
+
return (
|
|
3081
|
+
<button>
|
|
3082
|
+
{this.props.children}
|
|
3083
|
+
</button>
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
`;
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
var InputComponent = class extends ComponentDefinition {
|
|
3091
|
+
constructor() {
|
|
3092
|
+
super(...arguments);
|
|
3093
|
+
this.name = "input";
|
|
3094
|
+
this.dependencies = [];
|
|
3095
|
+
}
|
|
3096
|
+
generate() {
|
|
3097
|
+
return `"use interactive";
|
|
3098
|
+
|
|
3099
|
+
import { Component } from "@kithinji/orca";
|
|
3100
|
+
|
|
3101
|
+
@Component()
|
|
3102
|
+
export class Input {
|
|
3103
|
+
build() {
|
|
3104
|
+
return (
|
|
3105
|
+
<input />
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
`;
|
|
3110
|
+
}
|
|
3111
|
+
};
|
|
3112
|
+
var FormComponent = class extends ComponentDefinition {
|
|
3113
|
+
constructor() {
|
|
3114
|
+
super(...arguments);
|
|
3115
|
+
this.name = "form";
|
|
3116
|
+
this.dependencies = ["button", "input"];
|
|
3117
|
+
}
|
|
3118
|
+
generate() {
|
|
3119
|
+
return `"use interactive";
|
|
3120
|
+
|
|
3121
|
+
import { Component } from "@kithinji/orca";
|
|
3122
|
+
import { Button } from "./button.component";
|
|
3123
|
+
import { Input } from "./input.component";
|
|
3124
|
+
|
|
3125
|
+
@Component()
|
|
3126
|
+
export class Form {
|
|
3127
|
+
props!: {
|
|
3128
|
+
onSubmit?: () => void;
|
|
3129
|
+
};
|
|
3130
|
+
|
|
3131
|
+
build() {
|
|
3132
|
+
return (
|
|
3133
|
+
<form onSubmit={this.props.onSubmit}>
|
|
3134
|
+
<Input />
|
|
3135
|
+
<Button>Submit</Button>
|
|
3136
|
+
</form>
|
|
3137
|
+
);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
`;
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
var CardComponent = class extends ComponentDefinition {
|
|
3144
|
+
constructor() {
|
|
3145
|
+
super(...arguments);
|
|
3146
|
+
this.name = "card";
|
|
3147
|
+
this.dependencies = ["button"];
|
|
3148
|
+
}
|
|
3149
|
+
generate() {
|
|
3150
|
+
return `"use interactive";
|
|
3151
|
+
|
|
3152
|
+
import { Component } from "@kithinji/orca";
|
|
3153
|
+
import { Button } from "./button.component";
|
|
3154
|
+
|
|
3155
|
+
@Component()
|
|
3156
|
+
export class Card {
|
|
3157
|
+
props!: {
|
|
3158
|
+
title: string;
|
|
3159
|
+
children: any;
|
|
3160
|
+
onAction?: () => void;
|
|
3161
|
+
};
|
|
3162
|
+
|
|
3163
|
+
build() {
|
|
3164
|
+
return (
|
|
3165
|
+
<div className="card">
|
|
3166
|
+
<h3>{this.props.title}</h3>
|
|
3167
|
+
<div>{this.props.children}</div>
|
|
3168
|
+
{this.props.onAction && (
|
|
3169
|
+
<Button onClick={this.props.onAction}>Action</Button>
|
|
3170
|
+
)}
|
|
3171
|
+
</div>
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
`;
|
|
3176
|
+
}
|
|
3177
|
+
};
|
|
3178
|
+
var ComponentRegistry = class {
|
|
3179
|
+
static {
|
|
3180
|
+
this.components = /* @__PURE__ */ new Map([
|
|
3181
|
+
["button", new ButtonComponent()],
|
|
3182
|
+
["input", new InputComponent()],
|
|
3183
|
+
["form", new FormComponent()],
|
|
3184
|
+
["card", new CardComponent()]
|
|
3185
|
+
]);
|
|
3186
|
+
}
|
|
3187
|
+
static get(name) {
|
|
3188
|
+
return this.components.get(name);
|
|
3189
|
+
}
|
|
3190
|
+
static has(name) {
|
|
3191
|
+
return this.components.has(name);
|
|
3192
|
+
}
|
|
3193
|
+
static getAll() {
|
|
3194
|
+
return Array.from(this.components.keys());
|
|
3195
|
+
}
|
|
3196
|
+
static register(component) {
|
|
3197
|
+
this.components.set(component.name, component);
|
|
3198
|
+
}
|
|
3199
|
+
};
|
|
3200
|
+
function addComponent(name, processedComponents = /* @__PURE__ */ new Set()) {
|
|
3201
|
+
if (processedComponents.has(name)) {
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
const component = ComponentRegistry.get(name);
|
|
3205
|
+
if (!component) {
|
|
3206
|
+
throw new Error(
|
|
3207
|
+
`Component "${name}" not found. Available components: ${ComponentRegistry.getAll().join(
|
|
3208
|
+
", "
|
|
3209
|
+
)}`
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3212
|
+
processedComponents.add(name);
|
|
3213
|
+
if (component.dependencies.length > 0) {
|
|
3214
|
+
console.log(
|
|
3215
|
+
`
|
|
3216
|
+
Processing dependencies for "${name}": [${component.dependencies.join(
|
|
3217
|
+
", "
|
|
3218
|
+
)}]`
|
|
3219
|
+
);
|
|
3220
|
+
for (const dependency of component.dependencies) {
|
|
3221
|
+
addComponent(dependency, processedComponents);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
const componentModulePath = path8.join(
|
|
3225
|
+
process.cwd(),
|
|
3226
|
+
"src/component/component.module.ts"
|
|
3227
|
+
);
|
|
3228
|
+
const componentPath = path8.join(
|
|
3229
|
+
process.cwd(),
|
|
3230
|
+
`src/component/component/${name}.component.tsx`
|
|
3231
|
+
);
|
|
3232
|
+
const componentDir = path8.dirname(componentPath);
|
|
3233
|
+
const appModulePath = path8.join(process.cwd(), "src/app/app.module.ts");
|
|
3234
|
+
if (!fs6.existsSync(componentModulePath)) {
|
|
3235
|
+
const moduleDir = path8.dirname(componentModulePath);
|
|
3236
|
+
if (!fs6.existsSync(moduleDir)) {
|
|
3237
|
+
fs6.mkdirSync(moduleDir, { recursive: true });
|
|
3238
|
+
}
|
|
3239
|
+
fs6.writeFileSync(componentModulePath, createModule(), "utf-8");
|
|
3240
|
+
}
|
|
3241
|
+
if (!fs6.existsSync(componentDir)) {
|
|
3242
|
+
fs6.mkdirSync(componentDir, { recursive: true });
|
|
3243
|
+
}
|
|
3244
|
+
if (!fs6.existsSync(componentPath)) {
|
|
3245
|
+
fs6.writeFileSync(componentPath, component.generate(), "utf-8");
|
|
3246
|
+
console.log(`Created ${name}.component.tsx`);
|
|
3247
|
+
} else {
|
|
3248
|
+
console.log(`${name}.component.tsx already exists, skipping file creation`);
|
|
3249
|
+
}
|
|
3250
|
+
const moduleContent = fs6.readFileSync(componentModulePath, "utf-8");
|
|
3251
|
+
const updatedModule = updateModuleWithComponent(moduleContent, name);
|
|
3252
|
+
fs6.writeFileSync(componentModulePath, updatedModule, "utf-8");
|
|
3253
|
+
if (fs6.existsSync(appModulePath)) {
|
|
3254
|
+
const appModuleContent = fs6.readFileSync(appModulePath, "utf-8");
|
|
3255
|
+
const updatedAppModule = ensureComponentModuleImported(appModuleContent);
|
|
3256
|
+
if (updatedAppModule !== appModuleContent) {
|
|
3257
|
+
fs6.writeFileSync(appModulePath, updatedAppModule, "utf-8");
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
function updateModuleWithComponent(moduleContent, componentName) {
|
|
3262
|
+
const className = capitalize(componentName);
|
|
3263
|
+
const importPath = `./component/${componentName}.component`;
|
|
3264
|
+
const sourceFile = ts2.createSourceFile(
|
|
3265
|
+
"component.module.ts",
|
|
3266
|
+
moduleContent,
|
|
3267
|
+
ts2.ScriptTarget.Latest,
|
|
3268
|
+
true
|
|
3269
|
+
);
|
|
3270
|
+
const hasImport = sourceFile.statements.some((statement) => {
|
|
3271
|
+
if (ts2.isImportDeclaration(statement)) {
|
|
3272
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
3273
|
+
if (ts2.isStringLiteral(moduleSpecifier)) {
|
|
3274
|
+
return moduleSpecifier.text === importPath;
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
return false;
|
|
3278
|
+
});
|
|
3279
|
+
if (hasImport) {
|
|
3280
|
+
return moduleContent;
|
|
3281
|
+
}
|
|
3282
|
+
let lastImportEnd = 0;
|
|
3283
|
+
sourceFile.statements.forEach((statement) => {
|
|
3284
|
+
if (ts2.isImportDeclaration(statement)) {
|
|
3285
|
+
lastImportEnd = statement.end;
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
const importStatement = `import { ${className} } from "${importPath}";
|
|
3289
|
+
`;
|
|
3290
|
+
let updatedContent = moduleContent.slice(0, lastImportEnd) + "\n" + importStatement + moduleContent.slice(lastImportEnd);
|
|
3291
|
+
const newSourceFile = ts2.createSourceFile(
|
|
3292
|
+
"component.module.ts",
|
|
3293
|
+
updatedContent,
|
|
3294
|
+
ts2.ScriptTarget.Latest,
|
|
3295
|
+
true
|
|
3296
|
+
);
|
|
3297
|
+
updatedContent = addToDecoratorArray(
|
|
3298
|
+
updatedContent,
|
|
3299
|
+
newSourceFile,
|
|
3300
|
+
"declarations",
|
|
3301
|
+
className
|
|
3302
|
+
);
|
|
3303
|
+
updatedContent = addToDecoratorArray(
|
|
3304
|
+
updatedContent,
|
|
3305
|
+
ts2.createSourceFile(
|
|
3306
|
+
"component.module.ts",
|
|
3307
|
+
updatedContent,
|
|
3308
|
+
ts2.ScriptTarget.Latest,
|
|
3309
|
+
true
|
|
3310
|
+
),
|
|
3311
|
+
"exports",
|
|
3312
|
+
className
|
|
3313
|
+
);
|
|
3314
|
+
return updatedContent;
|
|
3315
|
+
}
|
|
3316
|
+
function addToDecoratorArray(content, sourceFile, arrayName, className) {
|
|
3317
|
+
let decoratorNode;
|
|
3318
|
+
sourceFile.statements.forEach((statement) => {
|
|
3319
|
+
if (ts2.isClassDeclaration(statement) && statement.modifiers) {
|
|
3320
|
+
statement.modifiers.forEach((modifier) => {
|
|
3321
|
+
if (ts2.isDecorator(modifier)) {
|
|
3322
|
+
const expression = modifier.expression;
|
|
3323
|
+
if (ts2.isCallExpression(expression)) {
|
|
3324
|
+
const expressionText = expression.expression.getText(sourceFile);
|
|
3325
|
+
if (expressionText === "Module") {
|
|
3326
|
+
decoratorNode = modifier;
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
});
|
|
3333
|
+
if (!decoratorNode) {
|
|
3334
|
+
console.warn("Could not find @Module decorator");
|
|
3335
|
+
return content;
|
|
3336
|
+
}
|
|
3337
|
+
const callExpression = decoratorNode.expression;
|
|
3338
|
+
const objectLiteral = callExpression.arguments[0];
|
|
3339
|
+
if (!objectLiteral || !ts2.isObjectLiteralExpression(objectLiteral)) {
|
|
3340
|
+
return content;
|
|
3341
|
+
}
|
|
3342
|
+
let targetProperty;
|
|
3343
|
+
objectLiteral.properties.forEach((prop) => {
|
|
3344
|
+
if (ts2.isPropertyAssignment(prop)) {
|
|
3345
|
+
const propName = prop.name.getText(sourceFile);
|
|
3346
|
+
if (propName === arrayName) {
|
|
3347
|
+
targetProperty = prop;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
if (!targetProperty) {
|
|
3352
|
+
console.warn(`Could not find ${arrayName} property`);
|
|
3353
|
+
return content;
|
|
3354
|
+
}
|
|
3355
|
+
const arrayLiteral = targetProperty.initializer;
|
|
3356
|
+
if (!ts2.isArrayLiteralExpression(arrayLiteral)) {
|
|
3357
|
+
return content;
|
|
3358
|
+
}
|
|
3359
|
+
const hasClassName = arrayLiteral.elements.some((element) => {
|
|
3360
|
+
return element.getText(sourceFile).trim() === className;
|
|
3361
|
+
});
|
|
3362
|
+
if (hasClassName) {
|
|
3363
|
+
return content;
|
|
3364
|
+
}
|
|
3365
|
+
const arrayStart = arrayLiteral.getStart(sourceFile);
|
|
3366
|
+
const arrayEnd = arrayLiteral.getEnd();
|
|
3367
|
+
if (arrayLiteral.elements.length === 0) {
|
|
3368
|
+
const newArray = `[${className}]`;
|
|
3369
|
+
return content.substring(0, arrayStart) + newArray + content.substring(arrayEnd);
|
|
3370
|
+
}
|
|
3371
|
+
const lastElement = arrayLiteral.elements[arrayLiteral.elements.length - 1];
|
|
3372
|
+
const insertPos = lastElement.getEnd();
|
|
3373
|
+
const newElement = `, ${className}`;
|
|
3374
|
+
return content.substring(0, insertPos) + newElement + content.substring(insertPos);
|
|
3375
|
+
}
|
|
3376
|
+
function ensureComponentModuleImported(appModuleContent) {
|
|
3377
|
+
const sourceFile = ts2.createSourceFile(
|
|
3378
|
+
"app.module.ts",
|
|
3379
|
+
appModuleContent,
|
|
3380
|
+
ts2.ScriptTarget.Latest,
|
|
3381
|
+
true
|
|
3382
|
+
);
|
|
3383
|
+
const hasComponentModuleImport = sourceFile.statements.some((statement) => {
|
|
3384
|
+
if (ts2.isImportDeclaration(statement) && statement.importClause) {
|
|
3385
|
+
const namedBindings = statement.importClause.namedBindings;
|
|
3386
|
+
if (namedBindings && ts2.isNamedImports(namedBindings)) {
|
|
3387
|
+
return namedBindings.elements.some(
|
|
3388
|
+
(element) => element.name.text === "ComponentModule"
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
return false;
|
|
3393
|
+
});
|
|
3394
|
+
if (hasComponentModuleImport) {
|
|
3395
|
+
return ensureInImportsArray(appModuleContent, sourceFile);
|
|
3396
|
+
}
|
|
3397
|
+
let lastImportEnd = 0;
|
|
3398
|
+
sourceFile.statements.forEach((statement) => {
|
|
3399
|
+
if (ts2.isImportDeclaration(statement)) {
|
|
3400
|
+
lastImportEnd = statement.end;
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
const importStatement = `import { ComponentModule } from "../component/component.module";
|
|
3404
|
+
`;
|
|
3405
|
+
let updatedContent = appModuleContent.slice(0, lastImportEnd) + "\n" + importStatement + appModuleContent.slice(lastImportEnd);
|
|
3406
|
+
const newSourceFile = ts2.createSourceFile(
|
|
3407
|
+
"app.module.ts",
|
|
3408
|
+
updatedContent,
|
|
3409
|
+
ts2.ScriptTarget.Latest,
|
|
3410
|
+
true
|
|
3411
|
+
);
|
|
3412
|
+
updatedContent = ensureInImportsArray(updatedContent, newSourceFile);
|
|
3413
|
+
return updatedContent;
|
|
3414
|
+
}
|
|
3415
|
+
function ensureInImportsArray(content, sourceFile) {
|
|
3416
|
+
return addToDecoratorArray(content, sourceFile, "imports", "ComponentModule");
|
|
3417
|
+
}
|
|
3418
|
+
function capitalize(str) {
|
|
3419
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
3420
|
+
}
|
|
3421
|
+
function createModule() {
|
|
3422
|
+
return `import { Module } from "@kithinji/orca";
|
|
3423
|
+
|
|
3424
|
+
@Module({
|
|
3425
|
+
imports: [],
|
|
3426
|
+
providers: [],
|
|
3427
|
+
declarations: [],
|
|
3428
|
+
exports: [],
|
|
3429
|
+
})
|
|
3430
|
+
export class ComponentModule {}
|
|
3431
|
+
`;
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
// src/utils/cases.ts
|
|
3435
|
+
function toCamelCase(str) {
|
|
3436
|
+
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^./, (char) => char.toLowerCase());
|
|
3437
|
+
}
|
|
3438
|
+
function toPascalCase(str) {
|
|
3439
|
+
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^./, (char) => char.toUpperCase());
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// src/utils/create.ts
|
|
3443
|
+
import * as fs7 from "fs";
|
|
3444
|
+
import * as path9 from "path";
|
|
3445
|
+
function createStructure(basePath, entry) {
|
|
3446
|
+
fs7.mkdirSync(basePath, { recursive: true });
|
|
3447
|
+
entry.files?.forEach((file) => {
|
|
3448
|
+
fs7.writeFileSync(path9.join(basePath, file.name), file.content);
|
|
3449
|
+
});
|
|
3450
|
+
entry.dirs?.forEach((dir) => {
|
|
3451
|
+
const dirPath = path9.join(basePath, dir.name || "");
|
|
3452
|
+
createStructure(dirPath, dir);
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// src/add/new/index.ts
|
|
3457
|
+
import path11 from "path";
|
|
3458
|
+
|
|
3459
|
+
// src/add/module/module.ts
|
|
3460
|
+
import * as path10 from "path";
|
|
3461
|
+
import * as fs8 from "fs";
|
|
3462
|
+
import * as ts3 from "typescript";
|
|
3463
|
+
function addFeature(name) {
|
|
3464
|
+
const featureDir = path10.join(process.cwd(), "src", "features", name);
|
|
3465
|
+
addModule(name, featureDir);
|
|
3466
|
+
updateFeaturesIndex(name);
|
|
3467
|
+
updateAppModule(name);
|
|
3468
|
+
}
|
|
3469
|
+
function addModule(name, baseDir) {
|
|
3470
|
+
const structure = {
|
|
3471
|
+
files: [
|
|
3472
|
+
{ name: `${name}.module.ts`, content: createModule2(name) },
|
|
3473
|
+
{ name: `${name}.service.ts`, content: createService(name) },
|
|
3474
|
+
{ name: `${name}.page.tsx`, content: createPage(name) }
|
|
3475
|
+
],
|
|
3476
|
+
dirs: [
|
|
3477
|
+
{
|
|
3478
|
+
name: "schemas",
|
|
3479
|
+
files: [
|
|
3480
|
+
{
|
|
3481
|
+
name: "get.ts",
|
|
3482
|
+
content: createGetSchema(name)
|
|
3483
|
+
},
|
|
3484
|
+
{
|
|
3485
|
+
name: "create.ts",
|
|
3486
|
+
content: createCreateSchema(name)
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
name: "update.ts",
|
|
3490
|
+
content: createUpdateSchema(name)
|
|
3491
|
+
},
|
|
3492
|
+
{
|
|
3493
|
+
name: "list.ts",
|
|
3494
|
+
content: createListSchema(name)
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
name: "delete.ts",
|
|
3498
|
+
content: createDeleteSchema(name)
|
|
3499
|
+
}
|
|
3500
|
+
]
|
|
3501
|
+
},
|
|
3502
|
+
{
|
|
3503
|
+
name: "components",
|
|
3504
|
+
files: [
|
|
3505
|
+
{
|
|
3506
|
+
name: `${name}-list.component.tsx`,
|
|
3507
|
+
content: createListComponent(name)
|
|
3508
|
+
}
|
|
3509
|
+
]
|
|
3510
|
+
}
|
|
3511
|
+
]
|
|
3512
|
+
};
|
|
3513
|
+
createStructure(baseDir, structure);
|
|
3514
|
+
}
|
|
3515
|
+
function updateFeaturesIndex(featureName) {
|
|
3516
|
+
const featuresIndexPath = path10.join(
|
|
3517
|
+
process.cwd(),
|
|
3518
|
+
"src",
|
|
3519
|
+
"features",
|
|
3520
|
+
"index.ts"
|
|
3521
|
+
);
|
|
3522
|
+
const moduleName = toPascalCase(featureName + "_Module");
|
|
3523
|
+
const importPath = `./${featureName}/${featureName}.module`;
|
|
3524
|
+
if (fs8.existsSync(featuresIndexPath)) {
|
|
3525
|
+
let content = fs8.readFileSync(featuresIndexPath, "utf-8");
|
|
3526
|
+
const sourceFile = ts3.createSourceFile(
|
|
3527
|
+
"index.ts",
|
|
3528
|
+
content,
|
|
3529
|
+
ts3.ScriptTarget.Latest,
|
|
3530
|
+
true
|
|
3531
|
+
);
|
|
3532
|
+
const hasExport = sourceFile.statements.some((statement) => {
|
|
3533
|
+
if (ts3.isExportDeclaration(statement)) {
|
|
3534
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
3535
|
+
if (moduleSpecifier && ts3.isStringLiteral(moduleSpecifier)) {
|
|
3536
|
+
return moduleSpecifier.text === importPath;
|
|
3537
|
+
}
|
|
3538
|
+
if (statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
|
|
3539
|
+
return statement.exportClause.elements.some(
|
|
3540
|
+
(element) => element.name.text === moduleName
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
return false;
|
|
3545
|
+
});
|
|
3546
|
+
if (hasExport) {
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
const exportStatement = `export { ${moduleName} } from "${importPath}";
|
|
3550
|
+
`;
|
|
3551
|
+
fs8.appendFileSync(featuresIndexPath, exportStatement);
|
|
3552
|
+
} else {
|
|
3553
|
+
const featuresDir = path10.dirname(featuresIndexPath);
|
|
3554
|
+
if (!fs8.existsSync(featuresDir)) {
|
|
3555
|
+
fs8.mkdirSync(featuresDir, { recursive: true });
|
|
3556
|
+
}
|
|
3557
|
+
const exportStatement = `export { ${moduleName} } from "${importPath}";
|
|
3558
|
+
`;
|
|
3559
|
+
fs8.writeFileSync(featuresIndexPath, exportStatement, "utf-8");
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
function updateAppModule(featureName) {
|
|
3563
|
+
const appModulePath = path10.join(process.cwd(), "src", "app", "app.module.ts");
|
|
3564
|
+
if (!fs8.existsSync(appModulePath)) {
|
|
3565
|
+
return;
|
|
3566
|
+
}
|
|
3567
|
+
const moduleName = toPascalCase(featureName + "_Module");
|
|
3568
|
+
let content = fs8.readFileSync(appModulePath, "utf-8");
|
|
3569
|
+
const sourceFile = ts3.createSourceFile(
|
|
3570
|
+
"app.module.ts",
|
|
3571
|
+
content,
|
|
3572
|
+
ts3.ScriptTarget.Latest,
|
|
3573
|
+
true
|
|
3574
|
+
);
|
|
3575
|
+
const hasImport = sourceFile.statements.some((statement) => {
|
|
3576
|
+
if (ts3.isImportDeclaration(statement)) {
|
|
3577
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
3578
|
+
if (ts3.isStringLiteral(moduleSpecifier)) {
|
|
3579
|
+
const importPath = moduleSpecifier.text;
|
|
3580
|
+
return importPath.includes(`/${featureName}/${featureName}.module`);
|
|
3581
|
+
}
|
|
3582
|
+
if (statement.importClause?.namedBindings && ts3.isNamedImports(statement.importClause.namedBindings)) {
|
|
3583
|
+
return statement.importClause.namedBindings.elements.some(
|
|
3584
|
+
(element) => element.name.text === moduleName
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
return false;
|
|
3589
|
+
});
|
|
3590
|
+
if (hasImport) {
|
|
3591
|
+
content = addToModuleImportsArray(content, sourceFile, moduleName);
|
|
3592
|
+
fs8.writeFileSync(appModulePath, content, "utf-8");
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
let lastImportEnd = 0;
|
|
3596
|
+
sourceFile.statements.forEach((statement) => {
|
|
3597
|
+
if (ts3.isImportDeclaration(statement)) {
|
|
3598
|
+
lastImportEnd = statement.end;
|
|
3599
|
+
}
|
|
3600
|
+
});
|
|
3601
|
+
const importStatement = `import { ${moduleName} } from "../features/${featureName}/${featureName}.module";
|
|
3602
|
+
`;
|
|
3603
|
+
content = content.slice(0, lastImportEnd) + "\n" + importStatement + content.slice(lastImportEnd);
|
|
3604
|
+
const newSourceFile = ts3.createSourceFile(
|
|
3605
|
+
"app.module.ts",
|
|
3606
|
+
content,
|
|
3607
|
+
ts3.ScriptTarget.Latest,
|
|
3608
|
+
true
|
|
3609
|
+
);
|
|
3610
|
+
content = addToModuleImportsArray(content, newSourceFile, moduleName);
|
|
3611
|
+
fs8.writeFileSync(appModulePath, content, "utf-8");
|
|
3612
|
+
}
|
|
3613
|
+
function addToModuleImportsArray(content, sourceFile, moduleName) {
|
|
3614
|
+
let decoratorNode;
|
|
3615
|
+
sourceFile.statements.forEach((statement) => {
|
|
3616
|
+
if (ts3.isClassDeclaration(statement) && statement.modifiers) {
|
|
3617
|
+
statement.modifiers.forEach((modifier) => {
|
|
3618
|
+
if (ts3.isDecorator(modifier)) {
|
|
3619
|
+
const expression = modifier.expression;
|
|
3620
|
+
if (ts3.isCallExpression(expression)) {
|
|
3621
|
+
const expressionText = expression.expression.getText(sourceFile);
|
|
3622
|
+
if (expressionText === "Module") {
|
|
3623
|
+
decoratorNode = modifier;
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
if (!decoratorNode) {
|
|
3631
|
+
return content;
|
|
3632
|
+
}
|
|
3633
|
+
const callExpression = decoratorNode.expression;
|
|
3634
|
+
const objectLiteral = callExpression.arguments[0];
|
|
3635
|
+
if (!objectLiteral || !ts3.isObjectLiteralExpression(objectLiteral)) {
|
|
3636
|
+
return content;
|
|
3637
|
+
}
|
|
3638
|
+
let importsProperty;
|
|
3639
|
+
objectLiteral.properties.forEach((prop) => {
|
|
3640
|
+
if (ts3.isPropertyAssignment(prop)) {
|
|
3641
|
+
const propName = prop.name.getText(sourceFile);
|
|
3642
|
+
if (propName === "imports") {
|
|
3643
|
+
importsProperty = prop;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
});
|
|
3647
|
+
if (!importsProperty) {
|
|
3648
|
+
return content;
|
|
3649
|
+
}
|
|
3650
|
+
const arrayLiteral = importsProperty.initializer;
|
|
3651
|
+
if (!ts3.isArrayLiteralExpression(arrayLiteral)) {
|
|
3652
|
+
return content;
|
|
3653
|
+
}
|
|
3654
|
+
const hasModule = arrayLiteral.elements.some((element) => {
|
|
3655
|
+
return element.getText(sourceFile).trim() === moduleName;
|
|
3656
|
+
});
|
|
3657
|
+
if (hasModule) {
|
|
3658
|
+
return content;
|
|
3659
|
+
}
|
|
3660
|
+
const arrayStart = arrayLiteral.getStart(sourceFile);
|
|
3661
|
+
const arrayEnd = arrayLiteral.getEnd();
|
|
3662
|
+
if (arrayLiteral.elements.length === 0) {
|
|
3663
|
+
const newArray = `[${moduleName}]`;
|
|
3664
|
+
return content.substring(0, arrayStart) + newArray + content.substring(arrayEnd);
|
|
3665
|
+
}
|
|
3666
|
+
const lastElement = arrayLiteral.elements[arrayLiteral.elements.length - 1];
|
|
3667
|
+
const insertPos = lastElement.getEnd();
|
|
3668
|
+
const newElement = `, ${moduleName}`;
|
|
3669
|
+
return content.substring(0, insertPos) + newElement + content.substring(insertPos);
|
|
3670
|
+
}
|
|
3671
|
+
function createModule2(name) {
|
|
3672
|
+
const serviceName = toPascalCase(name + "_Service");
|
|
3673
|
+
const pageName = toPascalCase(name + "_Page");
|
|
3674
|
+
const moduleName = toPascalCase(name + "_Module");
|
|
3675
|
+
const componentName = toPascalCase(name + "_List");
|
|
3676
|
+
return `import { Module } from "@kithinji/orca";
|
|
3677
|
+
import { ComponentModule } from "@/component/component.module";
|
|
3678
|
+
import { ${serviceName} } from "./${name}.service";
|
|
3679
|
+
import { ${pageName} } from "./${name}.page";
|
|
3680
|
+
import { ${componentName} } from "./components/${name}-list.component";
|
|
3681
|
+
|
|
3682
|
+
@Module({
|
|
3683
|
+
imports: [ComponentModule],
|
|
3684
|
+
providers: [${serviceName}],
|
|
3685
|
+
declarations: [${pageName}, ${componentName}],
|
|
3686
|
+
exports: [${serviceName}, ${pageName}]
|
|
3687
|
+
})
|
|
3688
|
+
export class ${moduleName} {}
|
|
3689
|
+
`;
|
|
3690
|
+
}
|
|
3691
|
+
function createGetSchema(name) {
|
|
3692
|
+
return `import { z } from "zod";
|
|
3693
|
+
|
|
3694
|
+
export const ${toCamelCase(name + "_GetInput")} = z.object({
|
|
3695
|
+
id: z.string().uuid(),
|
|
3696
|
+
});
|
|
3697
|
+
|
|
3698
|
+
export const ${toCamelCase(name + "_GetOutput")} = z.object({
|
|
3699
|
+
id: z.string().uuid(),
|
|
3700
|
+
name: z.string(),
|
|
3701
|
+
description: z.string().optional(),
|
|
3702
|
+
createdAt: z.date(),
|
|
3703
|
+
updatedAt: z.date().optional(),
|
|
3704
|
+
});
|
|
3705
|
+
`;
|
|
3706
|
+
}
|
|
3707
|
+
function createCreateSchema(name) {
|
|
3708
|
+
return `import { z } from "zod";
|
|
3709
|
+
|
|
3710
|
+
export const ${toCamelCase(name + "_CreateInput")} = z.object({
|
|
3711
|
+
name: z.string().min(1),
|
|
3712
|
+
description: z.string().optional(),
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3715
|
+
export const ${toCamelCase(name + "_CreateOutput")} = z.object({
|
|
3716
|
+
id: z.string().uuid(),
|
|
3717
|
+
name: z.string(),
|
|
3718
|
+
description: z.string().optional(),
|
|
3719
|
+
createdAt: z.date(),
|
|
3720
|
+
});
|
|
3721
|
+
`;
|
|
3722
|
+
}
|
|
3723
|
+
function createUpdateSchema(name) {
|
|
3724
|
+
return `import { z } from "zod";
|
|
3725
|
+
|
|
3726
|
+
export const ${toCamelCase(name + "_UpdateInput")} = z.object({
|
|
3727
|
+
id: z.string().uuid(),
|
|
3728
|
+
name: z.string().min(1).optional(),
|
|
3729
|
+
description: z.string().optional(),
|
|
3730
|
+
});
|
|
3731
|
+
|
|
3732
|
+
export const ${toCamelCase(name + "_UpdateOutput")} = z.object({
|
|
3733
|
+
id: z.string().uuid(),
|
|
3734
|
+
name: z.string(),
|
|
3735
|
+
description: z.string().optional(),
|
|
3736
|
+
createdAt: z.date(),
|
|
3737
|
+
updatedAt: z.date(),
|
|
3738
|
+
});
|
|
3739
|
+
`;
|
|
3740
|
+
}
|
|
3741
|
+
function createListSchema(name) {
|
|
3742
|
+
return `import { z } from "zod";
|
|
3743
|
+
|
|
3744
|
+
export const ${toCamelCase(name + "_ListOutput")} = z.array(
|
|
3745
|
+
z.object({
|
|
3746
|
+
id: z.string().uuid(),
|
|
3747
|
+
name: z.string(),
|
|
3748
|
+
description: z.string().optional(),
|
|
3749
|
+
createdAt: z.date(),
|
|
3750
|
+
updatedAt: z.date().optional(),
|
|
3751
|
+
})
|
|
3752
|
+
);
|
|
3753
|
+
`;
|
|
3754
|
+
}
|
|
3755
|
+
function createDeleteSchema(name) {
|
|
3756
|
+
return `import { z } from "zod";
|
|
3757
|
+
|
|
3758
|
+
export const ${toCamelCase(name + "_DeleteInput")} = z.object({
|
|
3759
|
+
id: z.string().uuid(),
|
|
3760
|
+
});
|
|
3761
|
+
|
|
3762
|
+
export const ${toCamelCase(name + "_DeleteOutput")} = z.object({
|
|
3763
|
+
id: z.string().uuid(),
|
|
3764
|
+
name: z.string(),
|
|
3765
|
+
description: z.string().optional(),
|
|
3766
|
+
createdAt: z.date(),
|
|
3767
|
+
updatedAt: z.date().optional(),
|
|
3768
|
+
});
|
|
3769
|
+
`;
|
|
3770
|
+
}
|
|
3771
|
+
function createService(name) {
|
|
3772
|
+
const serviceName = toPascalCase(name + "_Service");
|
|
3773
|
+
return `"use public";
|
|
3774
|
+
|
|
3775
|
+
import { Injectable, Signature } from "@kithinji/orca";
|
|
3776
|
+
import {
|
|
3777
|
+
${toCamelCase(name + "_CreateInput")},
|
|
3778
|
+
${toCamelCase(name + "_CreateOutput")}
|
|
3779
|
+
} from "./schemas/create";
|
|
3780
|
+
import {
|
|
3781
|
+
${toCamelCase(name + "_GetInput")},
|
|
3782
|
+
${toCamelCase(name + "_GetOutput")}
|
|
3783
|
+
} from "./schemas/get";
|
|
3784
|
+
import {
|
|
3785
|
+
${toCamelCase(name + "_UpdateInput")},
|
|
3786
|
+
${toCamelCase(name + "_UpdateOutput")}
|
|
3787
|
+
} from "./schemas/update";
|
|
3788
|
+
import { ${toCamelCase(name + "_ListOutput")} } from "./schemas/list";
|
|
3789
|
+
import {
|
|
3790
|
+
${toCamelCase(name + "_DeleteInput")},
|
|
3791
|
+
${toCamelCase(name + "_DeleteOutput")}
|
|
3792
|
+
} from "./schemas/delete";
|
|
3793
|
+
|
|
3794
|
+
@Injectable()
|
|
3795
|
+
export class ${serviceName} {
|
|
3796
|
+
private items: any[] = [];
|
|
3797
|
+
|
|
3798
|
+
@Signature(${toCamelCase(name + "_CreateInput")}, ${toCamelCase(
|
|
3799
|
+
name + "_CreateOutput"
|
|
3800
|
+
)})
|
|
3801
|
+
public async create(input: any) {
|
|
3802
|
+
const item = {
|
|
3803
|
+
id: crypto.randomUUID(),
|
|
3804
|
+
...input,
|
|
3805
|
+
createdAt: new Date(),
|
|
3806
|
+
};
|
|
3807
|
+
this.items.push(item);
|
|
3808
|
+
return item;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
@Signature(${toCamelCase(name + "_GetInput")}, ${toCamelCase(
|
|
3812
|
+
name + "_GetOutput"
|
|
3813
|
+
)})
|
|
3814
|
+
public async get(input: any) {
|
|
3815
|
+
const item = this.items.find((i) => i.id === input.id);
|
|
3816
|
+
if (!item) {
|
|
3817
|
+
throw new Error("Item not found");
|
|
3818
|
+
}
|
|
3819
|
+
return item;
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
@Signature(${toCamelCase(name + "_ListOutput")})
|
|
3823
|
+
public async list() {
|
|
3824
|
+
return this.items;
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
@Signature(${toCamelCase(name + "_UpdateInput")}, ${toCamelCase(
|
|
3828
|
+
name + "_UpdateOutput"
|
|
3829
|
+
)})
|
|
3830
|
+
public async update(input: any) {
|
|
3831
|
+
const index = this.items.findIndex((i) => i.id === input.id);
|
|
3832
|
+
if (index === -1) {
|
|
3833
|
+
throw new Error("Item not found");
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
this.items[index] = {
|
|
3837
|
+
...this.items[index],
|
|
3838
|
+
...input,
|
|
3839
|
+
updatedAt: new Date(),
|
|
3840
|
+
};
|
|
3841
|
+
|
|
3842
|
+
return this.items[index];
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
@Signature(${toCamelCase(name + "_DeleteInput")}, ${toCamelCase(
|
|
3846
|
+
name + "_DeleteOutput"
|
|
3847
|
+
)})
|
|
3848
|
+
public async delete(input: any) {
|
|
3849
|
+
const index = this.items.findIndex((i) => i.id === input.id);
|
|
3850
|
+
if (index === -1) {
|
|
3851
|
+
throw new Error("Item not found");
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
const deleted = this.items.splice(index, 1)[0];
|
|
3855
|
+
return deleted;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
`;
|
|
3859
|
+
}
|
|
3860
|
+
function createPage(name) {
|
|
3861
|
+
const pageName = toPascalCase(name + "_Page");
|
|
3862
|
+
const serviceName = toPascalCase(name + "_Service");
|
|
3863
|
+
const serviceVar = toCamelCase(name + "_Service");
|
|
3864
|
+
const listComponent = toPascalCase(name + "_List");
|
|
3865
|
+
return `import { Component } from "@kithinji/orca";
|
|
3866
|
+
import { ${serviceName} } from "./${name}.service";
|
|
3867
|
+
import { ${listComponent} } from "./components/${name}-list.component";
|
|
3868
|
+
|
|
3869
|
+
@Component()
|
|
3870
|
+
export class ${pageName} {
|
|
3871
|
+
constructor(
|
|
3872
|
+
public ${serviceVar}: ${serviceName}
|
|
3873
|
+
) {}
|
|
3874
|
+
|
|
3875
|
+
build() {
|
|
3876
|
+
return (
|
|
3877
|
+
<div>
|
|
3878
|
+
<h1>${toPascalCase(name)} Management</h1>
|
|
3879
|
+
<${listComponent} service={this.${serviceVar}} />
|
|
3880
|
+
</div>
|
|
3881
|
+
);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
`;
|
|
3885
|
+
}
|
|
3886
|
+
function createListComponent(name) {
|
|
3887
|
+
const componentName = toPascalCase(name + "_List");
|
|
3888
|
+
const serviceName = toPascalCase(name + "_Service");
|
|
3889
|
+
return `"use interactive";
|
|
3890
|
+
|
|
3891
|
+
import { Component } from "@kithinji/orca";
|
|
3892
|
+
import { ${serviceName} } from "../${name}.service";
|
|
3893
|
+
|
|
3894
|
+
@Component()
|
|
3895
|
+
export class ${componentName} {
|
|
3896
|
+
props!: {
|
|
3897
|
+
service: ${serviceName};
|
|
3898
|
+
};
|
|
3899
|
+
|
|
3900
|
+
build() {
|
|
3901
|
+
return (
|
|
3902
|
+
<div>
|
|
3903
|
+
<h2>${toPascalCase(name)} List</h2>
|
|
3904
|
+
<p>List component for ${name}</p>
|
|
3905
|
+
{/* Add your list implementation here */}
|
|
3906
|
+
</div>
|
|
3907
|
+
);
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
`;
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
// src/add/new/index.ts
|
|
3914
|
+
function addNew(name) {
|
|
3915
|
+
const baseDir = path11.join(process.cwd(), name);
|
|
3916
|
+
const structure = {
|
|
3917
|
+
files: [
|
|
3918
|
+
{ name: "package.json", content: genPackageJson(name) },
|
|
3919
|
+
{ name: "tsconfig.json", content: gentsconfig() },
|
|
3920
|
+
{ name: "pod.config.ts", content: genPodConfig(name) },
|
|
3921
|
+
{ name: "README.md", content: genReadMe() },
|
|
3922
|
+
{ name: ".gitignore", content: genGitIgnore() },
|
|
3923
|
+
{ name: ".env", content: genEnv() }
|
|
3924
|
+
],
|
|
3925
|
+
dirs: [
|
|
3926
|
+
{
|
|
3927
|
+
name: "src",
|
|
3928
|
+
files: [{ name: "main.ts", content: genMainTs() }]
|
|
3929
|
+
}
|
|
3930
|
+
]
|
|
3931
|
+
};
|
|
3932
|
+
createStructure(baseDir, structure);
|
|
3933
|
+
const appDir = path11.join(process.cwd(), name, "src", "app");
|
|
3934
|
+
addModule("app", appDir);
|
|
3935
|
+
process.chdir(baseDir);
|
|
3936
|
+
addComponent("button");
|
|
3937
|
+
console.log(`App ${name} created successfully`);
|
|
3938
|
+
}
|
|
3939
|
+
function genPackageJson(name) {
|
|
3940
|
+
const pj = {
|
|
3941
|
+
name,
|
|
3942
|
+
private: true,
|
|
3943
|
+
version: "0.0.0",
|
|
3944
|
+
type: "module",
|
|
3945
|
+
scripts: {
|
|
3946
|
+
dev: "pod dev",
|
|
3947
|
+
build: "pod build",
|
|
3948
|
+
start: "pod start"
|
|
3949
|
+
},
|
|
3950
|
+
dependencies: {
|
|
3951
|
+
"reflect-metadata": "latest",
|
|
3952
|
+
zod: "^4.2.1",
|
|
3953
|
+
"@kithinji/orca": "latest"
|
|
3954
|
+
},
|
|
3955
|
+
devDependencies: {
|
|
3956
|
+
"@types/node": "^20.19.27",
|
|
3957
|
+
typescript: "~5.9.3",
|
|
3958
|
+
"@kithinji/pod": "latest"
|
|
3959
|
+
}
|
|
3960
|
+
};
|
|
3961
|
+
return JSON.stringify(pj, null, 2);
|
|
3962
|
+
}
|
|
3963
|
+
function gentsconfig() {
|
|
3964
|
+
const tsconfig = {
|
|
3965
|
+
compilerOptions: {
|
|
3966
|
+
target: "ES2020",
|
|
3967
|
+
module: "ESNext",
|
|
3968
|
+
moduleResolution: "bundler",
|
|
3969
|
+
strict: true,
|
|
3970
|
+
esModuleInterop: true,
|
|
3971
|
+
skipLibCheck: true,
|
|
3972
|
+
jsx: "react-jsx",
|
|
3973
|
+
jsxImportSource: "@kithinji/orca",
|
|
3974
|
+
experimentalDecorators: true,
|
|
3975
|
+
emitDecoratorMetadata: true,
|
|
3976
|
+
baseUrl: ".",
|
|
3977
|
+
paths: {
|
|
3978
|
+
"@/*": ["src/*"]
|
|
3979
|
+
}
|
|
3980
|
+
},
|
|
3981
|
+
include: ["src"]
|
|
3982
|
+
};
|
|
3983
|
+
return JSON.stringify(tsconfig, null, 2);
|
|
3984
|
+
}
|
|
3985
|
+
function genPodConfig(name) {
|
|
3986
|
+
return `import { PodConfig, stylePlugin } from "@kithinji/pod";
|
|
3987
|
+
|
|
3988
|
+
export default function defaultConfig(): PodConfig {
|
|
3989
|
+
return {
|
|
3990
|
+
name: "${name}",
|
|
3991
|
+
client_plugins: [stylePlugin],
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
`;
|
|
3995
|
+
}
|
|
3996
|
+
function genReadMe() {
|
|
3997
|
+
return `# Pod Project
|
|
3998
|
+
`;
|
|
3999
|
+
}
|
|
4000
|
+
function genGitIgnore() {
|
|
4001
|
+
return `node_modules
|
|
4002
|
+
dist
|
|
4003
|
+
build
|
|
4004
|
+
.orca
|
|
4005
|
+
*.log
|
|
4006
|
+
.env
|
|
4007
|
+
.DS_Store
|
|
4008
|
+
`;
|
|
4009
|
+
}
|
|
4010
|
+
function genEnv() {
|
|
4011
|
+
return `NODE_ENV=development
|
|
4012
|
+
`;
|
|
4013
|
+
}
|
|
4014
|
+
function genMainTs() {
|
|
4015
|
+
return `import { NodeFactory } from "@kithinji/orca";
|
|
4016
|
+
import { AppModule } from "./app/app.module";
|
|
4017
|
+
|
|
4018
|
+
async function bootstrap() {
|
|
4019
|
+
const app = await NodeFactory.create(AppModule);
|
|
4020
|
+
app.listen(8080, () => {
|
|
4021
|
+
console.log("Server started");
|
|
4022
|
+
});
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
bootstrap();
|
|
4026
|
+
`;
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
// src/main.ts
|
|
4030
|
+
import path13 from "path";
|
|
4031
|
+
import { execSync } from "child_process";
|
|
4032
|
+
|
|
4033
|
+
// src/docker/docker.ts
|
|
4034
|
+
import fs9 from "fs-extra";
|
|
4035
|
+
import path12 from "path";
|
|
4036
|
+
import prompts from "prompts";
|
|
4037
|
+
import yaml from "js-yaml";
|
|
4038
|
+
async function dockerize(env = "prod") {
|
|
4039
|
+
const cwd = process.cwd();
|
|
4040
|
+
const packageJsonPath = path12.join(cwd, "package.json");
|
|
4041
|
+
if (!fs9.existsSync(packageJsonPath)) {
|
|
4042
|
+
throw new Error("package.json not found. Are you in a Pod project?");
|
|
4043
|
+
}
|
|
4044
|
+
const packageJson = await fs9.readJSON(packageJsonPath);
|
|
4045
|
+
const projectName = packageJson.name;
|
|
4046
|
+
const detectedServices = detectServices(packageJson);
|
|
4047
|
+
const selectedServices = await selectServices(detectedServices);
|
|
4048
|
+
await restructureProject(cwd, projectName);
|
|
4049
|
+
await createDockerfile(cwd, projectName);
|
|
4050
|
+
if (env === "prod") {
|
|
4051
|
+
await setupProduction(cwd, projectName, selectedServices);
|
|
4052
|
+
} else {
|
|
4053
|
+
await setupDevelopment(cwd, projectName, selectedServices);
|
|
4054
|
+
}
|
|
4055
|
+
printNextSteps(projectName, env, selectedServices);
|
|
4056
|
+
}
|
|
4057
|
+
function detectServices(packageJson) {
|
|
4058
|
+
const deps = packageJson.dependencies || {};
|
|
4059
|
+
const services = [];
|
|
4060
|
+
if (deps.pg || deps.postgres) services.push({ name: "postgres" });
|
|
4061
|
+
if (deps.mysql || deps.mysql2) services.push({ name: "mysql" });
|
|
4062
|
+
if (deps.redis || deps.ioredis) services.push({ name: "redis" });
|
|
4063
|
+
if (deps.mongodb || deps.mongoose) services.push({ name: "mongodb" });
|
|
4064
|
+
return services;
|
|
4065
|
+
}
|
|
4066
|
+
async function selectServices(detected) {
|
|
4067
|
+
if (detected.length === 0) return [];
|
|
4068
|
+
const response = await prompts({
|
|
4069
|
+
type: "multiselect",
|
|
4070
|
+
name: "services",
|
|
4071
|
+
message: "Select services to include:",
|
|
4072
|
+
choices: detected.map((s) => ({
|
|
4073
|
+
title: s.name,
|
|
4074
|
+
value: s.name,
|
|
4075
|
+
selected: true
|
|
4076
|
+
}))
|
|
4077
|
+
});
|
|
4078
|
+
if (!response.services) return [];
|
|
4079
|
+
return detected.filter((s) => response.services.includes(s.name));
|
|
4080
|
+
}
|
|
4081
|
+
async function restructureProject(cwd, projectName) {
|
|
4082
|
+
const nestedDir = path12.join(cwd, projectName);
|
|
4083
|
+
if (fs9.existsSync(nestedDir)) {
|
|
4084
|
+
console.log("\u26A0\uFE0F Project already restructured, skipping...");
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
await fs9.ensureDir(nestedDir);
|
|
4088
|
+
const items = await fs9.readdir(cwd);
|
|
4089
|
+
const toMove = items.filter((item) => item !== projectName);
|
|
4090
|
+
for (const item of toMove) {
|
|
4091
|
+
const src = path12.join(cwd, item);
|
|
4092
|
+
const dest = path12.join(nestedDir, item);
|
|
4093
|
+
await fs9.move(src, dest, { overwrite: true });
|
|
4094
|
+
}
|
|
4095
|
+
const envSrc = path12.join(nestedDir, ".env");
|
|
4096
|
+
const envDest = path12.join(cwd, ".env");
|
|
4097
|
+
if (fs9.existsSync(envSrc)) {
|
|
4098
|
+
await fs9.move(envSrc, envDest, { overwrite: true });
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
async function createDockerfile(cwd, projectName) {
|
|
4102
|
+
const dockerfilePath = path12.join(cwd, projectName, "Dockerfile");
|
|
4103
|
+
const dockerfile = `FROM node:18-alpine
|
|
4104
|
+
|
|
4105
|
+
WORKDIR /app
|
|
4106
|
+
|
|
4107
|
+
COPY package*.json ./
|
|
4108
|
+
RUN npm ci --only=production
|
|
4109
|
+
|
|
4110
|
+
COPY . .
|
|
4111
|
+
RUN if [ -f "tsconfig.json" ]; then npm run build || true; fi
|
|
4112
|
+
|
|
4113
|
+
EXPOSE 3000
|
|
4114
|
+
CMD ["npm", "start"]
|
|
4115
|
+
`;
|
|
4116
|
+
await fs9.writeFile(dockerfilePath, dockerfile);
|
|
4117
|
+
}
|
|
4118
|
+
async function setupProduction(cwd, projectName, services) {
|
|
4119
|
+
const compose = {
|
|
4120
|
+
services: {
|
|
4121
|
+
traefik: {
|
|
4122
|
+
image: "traefik:v2.10",
|
|
4123
|
+
command: [
|
|
4124
|
+
"--api.insecure=true",
|
|
4125
|
+
"--providers.docker=true",
|
|
4126
|
+
"--providers.docker.exposedbydefault=false",
|
|
4127
|
+
"--entrypoints.web.address=:80",
|
|
4128
|
+
"--entrypoints.websecure.address=:443",
|
|
4129
|
+
"--certificatesresolvers.myresolver.acme.tlschallenge=true",
|
|
4130
|
+
"--certificatesresolvers.myresolver.acme.email=admin@example.com",
|
|
4131
|
+
"--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
|
|
4132
|
+
],
|
|
4133
|
+
ports: ["80:80", "443:443", "8080:8080"],
|
|
4134
|
+
volumes: [
|
|
4135
|
+
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
|
4136
|
+
"./letsencrypt:/letsencrypt"
|
|
4137
|
+
],
|
|
4138
|
+
networks: ["web"]
|
|
4139
|
+
},
|
|
4140
|
+
[projectName]: {
|
|
4141
|
+
build: ".",
|
|
4142
|
+
labels: [
|
|
4143
|
+
"traefik.enable=true",
|
|
4144
|
+
"traefik.http.routers.app.rule=Host(`localhost`)",
|
|
4145
|
+
"traefik.http.routers.app.entrypoints=websecure",
|
|
4146
|
+
"traefik.http.routers.app.tls.certresolver=myresolver",
|
|
4147
|
+
"traefik.http.services.app.loadbalancer.server.port=3000"
|
|
4148
|
+
],
|
|
4149
|
+
env_file: [".env"],
|
|
4150
|
+
depends_on: [],
|
|
4151
|
+
networks: ["web"]
|
|
4152
|
+
}
|
|
4153
|
+
},
|
|
4154
|
+
networks: {
|
|
4155
|
+
web: {
|
|
4156
|
+
driver: "bridge"
|
|
4157
|
+
}
|
|
4158
|
+
},
|
|
4159
|
+
volumes: {}
|
|
4160
|
+
};
|
|
4161
|
+
for (const service of services) {
|
|
4162
|
+
const config = getServiceConfig(service.name);
|
|
4163
|
+
compose.services[service.name] = config.service;
|
|
4164
|
+
if (config.volume) {
|
|
4165
|
+
compose.volumes[config.volume.name] = {};
|
|
4166
|
+
}
|
|
4167
|
+
compose.services[projectName].depends_on.push(service.name);
|
|
4168
|
+
}
|
|
4169
|
+
const composePath = path12.join(cwd, "docker-compose.yml");
|
|
4170
|
+
await fs9.writeFile(
|
|
4171
|
+
composePath,
|
|
4172
|
+
yaml.dump(compose, { indent: 2, lineWidth: -1 })
|
|
4173
|
+
);
|
|
4174
|
+
await createEnvTemplate(cwd, services, "prod");
|
|
4175
|
+
}
|
|
4176
|
+
async function setupDevelopment(cwd, projectName, services) {
|
|
4177
|
+
const existingCompose = path12.join(cwd, "docker-compose.yml");
|
|
4178
|
+
let existingServices = [];
|
|
4179
|
+
if (fs9.existsSync(existingCompose)) {
|
|
4180
|
+
const content = await fs9.readFile(existingCompose, "utf8");
|
|
4181
|
+
const existing = yaml.load(content);
|
|
4182
|
+
if (existing.services) {
|
|
4183
|
+
existingServices = Object.keys(existing.services).filter((s) => ["postgres", "mysql", "redis", "mongodb"].includes(s)).map((name) => ({ name }));
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
const servicesToTunnel = [];
|
|
4187
|
+
if (existingServices.length > 0) {
|
|
4188
|
+
const { tunnel } = await prompts({
|
|
4189
|
+
type: "confirm",
|
|
4190
|
+
name: "tunnel",
|
|
4191
|
+
message: "Tunnel to remote database services?",
|
|
4192
|
+
initial: false
|
|
4193
|
+
});
|
|
4194
|
+
if (tunnel) {
|
|
4195
|
+
const { selected } = await prompts({
|
|
4196
|
+
type: "multiselect",
|
|
4197
|
+
name: "selected",
|
|
4198
|
+
message: "Select services to tunnel:",
|
|
4199
|
+
choices: existingServices.map((s) => ({
|
|
4200
|
+
title: s.name,
|
|
4201
|
+
value: s.name
|
|
4202
|
+
}))
|
|
4203
|
+
});
|
|
4204
|
+
if (selected) {
|
|
4205
|
+
servicesToTunnel.push(
|
|
4206
|
+
...existingServices.filter((s) => selected.includes(s.name)).map((s) => ({ ...s, needsTunnel: true }))
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
for (const service of servicesToTunnel) {
|
|
4212
|
+
await createTunnelService(cwd, service.name);
|
|
4213
|
+
}
|
|
4214
|
+
const compose = {
|
|
4215
|
+
services: {
|
|
4216
|
+
[projectName]: {
|
|
4217
|
+
build: ".",
|
|
4218
|
+
ports: ["3000:3000"],
|
|
4219
|
+
env_file: [".env"],
|
|
4220
|
+
volumes: [".:/app", "/app/node_modules"],
|
|
4221
|
+
command: "npm run dev",
|
|
4222
|
+
depends_on: []
|
|
4223
|
+
}
|
|
4224
|
+
},
|
|
4225
|
+
networks: {
|
|
4226
|
+
default: {
|
|
4227
|
+
driver: "bridge"
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
};
|
|
4231
|
+
for (const service of servicesToTunnel) {
|
|
4232
|
+
const tunnelName = `${service.name}-tunnel`;
|
|
4233
|
+
compose.services[tunnelName] = {
|
|
4234
|
+
build: `./${tunnelName}`,
|
|
4235
|
+
environment: [
|
|
4236
|
+
`REMOTE_HOST=\${${service.name.toUpperCase()}_REMOTE_HOST}`,
|
|
4237
|
+
`REMOTE_PORT=\${${service.name.toUpperCase()}_REMOTE_PORT:-${getDefaultPort(
|
|
4238
|
+
service.name
|
|
4239
|
+
)}}`,
|
|
4240
|
+
`LOCAL_PORT=${getDefaultPort(service.name)}`
|
|
4241
|
+
],
|
|
4242
|
+
volumes: [`./${service.name}.pem:/ssh/${service.name}.pem:ro`]
|
|
4243
|
+
};
|
|
4244
|
+
compose.services[projectName].depends_on.push(tunnelName);
|
|
4245
|
+
}
|
|
4246
|
+
const devComposePath = path12.join(cwd, "docker-compose.dev.yml");
|
|
4247
|
+
await fs9.writeFile(
|
|
4248
|
+
devComposePath,
|
|
4249
|
+
yaml.dump(compose, { indent: 2, lineWidth: -1 })
|
|
4250
|
+
);
|
|
4251
|
+
await createEnvTemplate(cwd, services, "dev");
|
|
4252
|
+
}
|
|
4253
|
+
async function createTunnelService(projectDir, serviceName) {
|
|
4254
|
+
const tunnelDir = path12.join(projectDir, `${serviceName}-tunnel`);
|
|
4255
|
+
await fs9.ensureDir(tunnelDir);
|
|
4256
|
+
const dockerfile = `FROM alpine:latest
|
|
4257
|
+
|
|
4258
|
+
RUN apk add --no-cache openssh-client
|
|
4259
|
+
|
|
4260
|
+
COPY tunnel.sh /tunnel.sh
|
|
4261
|
+
RUN chmod +x /tunnel.sh
|
|
4262
|
+
|
|
4263
|
+
CMD ["/tunnel.sh"]
|
|
4264
|
+
`;
|
|
4265
|
+
const tunnelScript = `#!/bin/sh
|
|
4266
|
+
|
|
4267
|
+
SSH_KEY="/ssh/${serviceName}.pem"
|
|
4268
|
+
REMOTE_HOST=\${REMOTE_HOST}
|
|
4269
|
+
REMOTE_PORT=\${REMOTE_PORT:-${getDefaultPort(serviceName)}}
|
|
4270
|
+
LOCAL_PORT=\${LOCAL_PORT:-${getDefaultPort(serviceName)}}
|
|
4271
|
+
|
|
4272
|
+
chmod 600 $SSH_KEY
|
|
4273
|
+
|
|
4274
|
+
echo "Starting SSH tunnel for ${serviceName}..."
|
|
4275
|
+
echo "Remote: $REMOTE_HOST:$REMOTE_PORT -> Local: $LOCAL_PORT"
|
|
4276
|
+
|
|
4277
|
+
ssh -i $SSH_KEY \\
|
|
4278
|
+
-N -L 0.0.0.0:$LOCAL_PORT:localhost:$REMOTE_PORT \\
|
|
4279
|
+
-o StrictHostKeyChecking=no \\
|
|
4280
|
+
-o ServerAliveInterval=60 \\
|
|
4281
|
+
$REMOTE_HOST
|
|
4282
|
+
`;
|
|
4283
|
+
await fs9.writeFile(path12.join(tunnelDir, "Dockerfile"), dockerfile);
|
|
4284
|
+
await fs9.writeFile(path12.join(tunnelDir, "tunnel.sh"), tunnelScript);
|
|
4285
|
+
}
|
|
4286
|
+
async function createEnvTemplate(projectDir, services, env) {
|
|
4287
|
+
const envPath = path12.join(projectDir, ".env.example");
|
|
4288
|
+
let content = `NODE_ENV=${env === "prod" ? "production" : "development"}
|
|
4289
|
+
PORT=3000
|
|
4290
|
+
`;
|
|
4291
|
+
if (services.length > 0) {
|
|
4292
|
+
content += `
|
|
4293
|
+
`;
|
|
4294
|
+
for (const service of services) {
|
|
4295
|
+
const vars = getEnvVars(service.name);
|
|
4296
|
+
content += vars.join("\n") + "\n\n";
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
await fs9.writeFile(envPath, content);
|
|
4300
|
+
}
|
|
4301
|
+
function getServiceConfig(serviceName) {
|
|
4302
|
+
const configs = {
|
|
4303
|
+
postgres: {
|
|
4304
|
+
service: {
|
|
4305
|
+
image: "postgres:15-alpine",
|
|
4306
|
+
environment: [
|
|
4307
|
+
"POSTGRES_USER=${DB_USER}",
|
|
4308
|
+
"POSTGRES_PASSWORD=${DB_PASSWORD}",
|
|
4309
|
+
"POSTGRES_DB=${DB_NAME}"
|
|
4310
|
+
],
|
|
4311
|
+
volumes: ["postgres_data:/var/lib/postgresql/data"],
|
|
4312
|
+
networks: ["web"]
|
|
4313
|
+
},
|
|
4314
|
+
volume: { name: "postgres_data" }
|
|
4315
|
+
},
|
|
4316
|
+
mysql: {
|
|
4317
|
+
service: {
|
|
4318
|
+
image: "mysql:8",
|
|
4319
|
+
environment: [
|
|
4320
|
+
"MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}",
|
|
4321
|
+
"MYSQL_DATABASE=${DB_NAME}",
|
|
4322
|
+
"MYSQL_USER=${DB_USER}",
|
|
4323
|
+
"MYSQL_PASSWORD=${DB_PASSWORD}"
|
|
4324
|
+
],
|
|
4325
|
+
volumes: ["mysql_data:/var/lib/mysql"],
|
|
4326
|
+
networks: ["web"]
|
|
4327
|
+
},
|
|
4328
|
+
volume: { name: "mysql_data" }
|
|
4329
|
+
},
|
|
4330
|
+
redis: {
|
|
4331
|
+
service: {
|
|
4332
|
+
image: "redis:7-alpine",
|
|
4333
|
+
volumes: ["redis_data:/data"],
|
|
4334
|
+
networks: ["web"]
|
|
4335
|
+
},
|
|
4336
|
+
volume: { name: "redis_data" }
|
|
4337
|
+
},
|
|
4338
|
+
mongodb: {
|
|
4339
|
+
service: {
|
|
4340
|
+
image: "mongo:6",
|
|
4341
|
+
environment: [
|
|
4342
|
+
"MONGO_INITDB_ROOT_USERNAME=${DB_USER}",
|
|
4343
|
+
"MONGO_INITDB_ROOT_PASSWORD=${DB_PASSWORD}"
|
|
4344
|
+
],
|
|
4345
|
+
volumes: ["mongo_data:/data/db"],
|
|
4346
|
+
networks: ["web"]
|
|
4347
|
+
},
|
|
4348
|
+
volume: { name: "mongo_data" }
|
|
4349
|
+
}
|
|
4350
|
+
};
|
|
4351
|
+
return configs[serviceName];
|
|
4352
|
+
}
|
|
4353
|
+
function getEnvVars(serviceName) {
|
|
4354
|
+
const vars = {
|
|
4355
|
+
postgres: [
|
|
4356
|
+
"DB_HOST=postgres",
|
|
4357
|
+
"DB_PORT=5432",
|
|
4358
|
+
"DB_USER=myuser",
|
|
4359
|
+
"DB_PASSWORD=mypassword",
|
|
4360
|
+
"DB_NAME=mydb"
|
|
4361
|
+
],
|
|
4362
|
+
mysql: [
|
|
4363
|
+
"DB_HOST=mysql",
|
|
4364
|
+
"DB_PORT=3306",
|
|
4365
|
+
"DB_USER=myuser",
|
|
4366
|
+
"DB_PASSWORD=mypassword",
|
|
4367
|
+
"DB_NAME=mydb",
|
|
4368
|
+
"DB_ROOT_PASSWORD=rootpassword"
|
|
4369
|
+
],
|
|
4370
|
+
redis: ["REDIS_HOST=redis", "REDIS_PORT=6379"],
|
|
4371
|
+
mongodb: [
|
|
4372
|
+
"MONGO_HOST=mongodb",
|
|
4373
|
+
"MONGO_PORT=27017",
|
|
4374
|
+
"MONGO_USER=myuser",
|
|
4375
|
+
"MONGO_PASSWORD=mypassword"
|
|
4376
|
+
]
|
|
4377
|
+
};
|
|
4378
|
+
return vars[serviceName] || [];
|
|
4379
|
+
}
|
|
4380
|
+
function getDefaultPort(service) {
|
|
4381
|
+
const ports = {
|
|
4382
|
+
postgres: 5432,
|
|
4383
|
+
mysql: 3306,
|
|
4384
|
+
redis: 6379,
|
|
4385
|
+
mongodb: 27017
|
|
4386
|
+
};
|
|
4387
|
+
return ports[service] || 3e3;
|
|
4388
|
+
}
|
|
4389
|
+
function printNextSteps(projectName, env, services) {
|
|
4390
|
+
console.log(`
|
|
4391
|
+
\u2705 Done! Next steps:
|
|
4392
|
+
`);
|
|
4393
|
+
if (env === "prod") {
|
|
4394
|
+
console.log(` # Edit .env with your settings`);
|
|
4395
|
+
console.log(` docker-compose up -d`);
|
|
4396
|
+
console.log(` # Access at https://localhost
|
|
4397
|
+
`);
|
|
4398
|
+
} else {
|
|
4399
|
+
console.log(` # Edit .env with your settings`);
|
|
4400
|
+
if (services.some((s) => s.needsTunnel)) {
|
|
4401
|
+
console.log(` # Add SSH keys: {service}.pem`);
|
|
4402
|
+
}
|
|
4403
|
+
console.log(` docker-compose -f docker-compose.dev.yml up -d
|
|
4404
|
+
`);
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
// src/main.ts
|
|
4409
|
+
var program = new Command();
|
|
4410
|
+
program.name("pod").description("Pod cli tool").version("0.0.0");
|
|
4411
|
+
program.command("new <name>").description("Start a new Pod Project").action(async (name) => {
|
|
4412
|
+
await addNew(name);
|
|
4413
|
+
const appDir = path13.resolve(process.cwd(), name);
|
|
4414
|
+
console.log("Installing dependencies...");
|
|
4415
|
+
execSync("npm install", { stdio: "inherit", cwd: appDir });
|
|
4416
|
+
console.log("Starting development server...");
|
|
4417
|
+
execSync("npm run dev", { stdio: "inherit", cwd: appDir });
|
|
4418
|
+
console.log(`All done! Your app "${name}" is running in development mode.`);
|
|
4419
|
+
});
|
|
4420
|
+
program.command("dev").description("Start Pod development server").action(async (opts) => {
|
|
4421
|
+
await startDevServer();
|
|
4422
|
+
});
|
|
4423
|
+
program.command("add <type> <name>").description("Add a component (c) or a feature (f)").action(async (type, name) => {
|
|
4424
|
+
try {
|
|
4425
|
+
if (type === "c") {
|
|
4426
|
+
await addComponent(name);
|
|
4427
|
+
} else if (type === "f") {
|
|
4428
|
+
await addFeature(name);
|
|
4429
|
+
} else {
|
|
4430
|
+
console.error("\u274C Unknown type. Use 'c' or 'f'.");
|
|
4431
|
+
}
|
|
4432
|
+
} catch (err) {
|
|
4433
|
+
console.error("\u274C Error:", err.message);
|
|
4434
|
+
}
|
|
4435
|
+
});
|
|
4436
|
+
program.command("dockerize <env>").description("Dockerize a pod project.").action(async (env) => {
|
|
4437
|
+
try {
|
|
4438
|
+
await dockerize(env);
|
|
4439
|
+
} catch (err) {
|
|
4440
|
+
console.error("\u274C Error:", err.message);
|
|
4441
|
+
}
|
|
4442
|
+
});
|
|
4443
|
+
program.command("deploy <type> <options>").description("Deploy a Pod Project").action(async (type, name) => {
|
|
4444
|
+
try {
|
|
4445
|
+
} catch (err) {
|
|
4446
|
+
console.error("\u274C Error:", err.message);
|
|
4447
|
+
}
|
|
4448
|
+
});
|
|
4449
|
+
program.parse(process.argv);
|
|
4450
|
+
export {
|
|
4451
|
+
config_exports as config,
|
|
4452
|
+
expandMacros,
|
|
4453
|
+
extractValueFromNode,
|
|
4454
|
+
getDefaultConfig,
|
|
4455
|
+
getGlobalMacroGraph,
|
|
4456
|
+
loadConfig,
|
|
4457
|
+
macros_exports as macros,
|
|
4458
|
+
mergeConfig,
|
|
4459
|
+
plugins_exports as plugins,
|
|
4460
|
+
resetGlobalMacroGraph,
|
|
4461
|
+
store_exports as store,
|
|
4462
|
+
stylePlugin
|
|
4463
|
+
};
|
|
4464
|
+
//# sourceMappingURL=main.js.map
|