create-nodejs-fn 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -356
- package/dist/index.d.ts +0 -36
- package/dist/index.js +0 -1013
package/dist/index.js
DELETED
|
@@ -1,1013 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
createNodejsFnPlugin: () => createNodejsFnPlugin
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(index_exports);
|
|
36
|
-
var import_node_fs3 = __toESM(require("fs"));
|
|
37
|
-
var import_node_path8 = __toESM(require("path"));
|
|
38
|
-
var import_glob = require("glob");
|
|
39
|
-
|
|
40
|
-
// src/build-container-server.ts
|
|
41
|
-
var import_node_fs2 = __toESM(require("fs"));
|
|
42
|
-
var import_node_path2 = __toESM(require("path"));
|
|
43
|
-
var esbuild = __toESM(require("esbuild"));
|
|
44
|
-
var import_ts_morph2 = require("ts-morph");
|
|
45
|
-
|
|
46
|
-
// src/fs-utils.ts
|
|
47
|
-
var import_node_fs = __toESM(require("fs"));
|
|
48
|
-
var import_node_path = __toESM(require("path"));
|
|
49
|
-
function ensureDir(p) {
|
|
50
|
-
import_node_fs.default.mkdirSync(p, { recursive: true });
|
|
51
|
-
}
|
|
52
|
-
function writeFileIfChanged(filePath, content) {
|
|
53
|
-
ensureDir(import_node_path.default.dirname(filePath));
|
|
54
|
-
const current = import_node_fs.default.existsSync(filePath) ? import_node_fs.default.readFileSync(filePath, "utf8") : null;
|
|
55
|
-
if (current === content) return;
|
|
56
|
-
import_node_fs.default.writeFileSync(filePath, content);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/project-utils.ts
|
|
60
|
-
var import_ts_morph = require("ts-morph");
|
|
61
|
-
function makeProject() {
|
|
62
|
-
return new import_ts_morph.Project({
|
|
63
|
-
useInMemoryFileSystem: false,
|
|
64
|
-
manipulationSettings: { quoteKind: import_ts_morph.QuoteKind.Double, useTrailingCommas: true },
|
|
65
|
-
compilerOptions: {
|
|
66
|
-
target: import_ts_morph.ScriptTarget.ESNext,
|
|
67
|
-
module: import_ts_morph.ModuleKind.ESNext,
|
|
68
|
-
allowJs: true
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
function printSource(sf) {
|
|
73
|
-
const text = sf.getFullText();
|
|
74
|
-
return text.endsWith("\n") ? text : `${text}
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/build-container-server.ts
|
|
79
|
-
function collectExternalDeps(rootPkgPath, needed) {
|
|
80
|
-
let pkgJson = { name: "create-nodejs-fn-container", version: "0.0.0" };
|
|
81
|
-
if (import_node_fs2.default.existsSync(rootPkgPath)) {
|
|
82
|
-
try {
|
|
83
|
-
pkgJson = JSON.parse(import_node_fs2.default.readFileSync(rootPkgPath, "utf8"));
|
|
84
|
-
} catch {
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
const depsSources = [
|
|
88
|
-
pkgJson.dependencies ?? {},
|
|
89
|
-
pkgJson.optionalDependencies ?? {},
|
|
90
|
-
pkgJson.devDependencies ?? {},
|
|
91
|
-
pkgJson.peerDependencies ?? {}
|
|
92
|
-
];
|
|
93
|
-
const out = {};
|
|
94
|
-
for (const name of needed) {
|
|
95
|
-
for (const src of depsSources) {
|
|
96
|
-
if (src[name]) {
|
|
97
|
-
out[name] = src[name];
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
name: pkgJson.name ?? "create-nodejs-fn-container",
|
|
104
|
-
version: pkgJson.version ?? "0.0.0",
|
|
105
|
-
dependencies: out
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
async function buildContainerServer(opts) {
|
|
109
|
-
const { mods, outBaseAbs, dockerOpts, containerPort, external, root } = opts;
|
|
110
|
-
ensureDir(outBaseAbs);
|
|
111
|
-
const entryTs = import_node_path2.default.join(outBaseAbs, "container.entry.ts");
|
|
112
|
-
const outServer = import_node_path2.default.join(outBaseAbs, "server.mjs");
|
|
113
|
-
const dockerfile = import_node_path2.default.join(outBaseAbs, "Dockerfile");
|
|
114
|
-
const genProject = makeProject();
|
|
115
|
-
const sf = genProject.createSourceFile(entryTs, "", { overwrite: true });
|
|
116
|
-
sf.addStatements(["// AUTO-GENERATED. DO NOT EDIT."]);
|
|
117
|
-
sf.addImportDeclaration({ defaultImport: "http", moduleSpecifier: "node:http" });
|
|
118
|
-
sf.addImportDeclaration({
|
|
119
|
-
moduleSpecifier: "capnweb",
|
|
120
|
-
namedImports: ["RpcTarget", "nodeHttpBatchRpcResponse"]
|
|
121
|
-
});
|
|
122
|
-
for (const mod of mods) {
|
|
123
|
-
const relFromEntry = import_node_path2.default.relative(import_node_path2.default.dirname(entryTs), import_node_path2.default.join(root, mod.fileRelFromRoot)).replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
124
|
-
sf.addImportDeclaration({
|
|
125
|
-
namespaceImport: `m_${mod.namespace}`,
|
|
126
|
-
moduleSpecifier: relFromEntry
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
sf.addClass({
|
|
130
|
-
name: "Api",
|
|
131
|
-
extends: "RpcTarget",
|
|
132
|
-
methods: mods.flatMap(
|
|
133
|
-
(mod) => mod.exports.map((ex) => ({
|
|
134
|
-
name: `${mod.namespace}__${ex.name}`,
|
|
135
|
-
parameters: [{ name: "args", isRestParameter: true, type: "any[]" }],
|
|
136
|
-
returnType: "any",
|
|
137
|
-
statements: `return (m_${mod.namespace} as any)[${JSON.stringify(ex.name)}](...args);`
|
|
138
|
-
}))
|
|
139
|
-
)
|
|
140
|
-
});
|
|
141
|
-
sf.addVariableStatement({
|
|
142
|
-
declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
|
|
143
|
-
declarations: [{ name: "api", initializer: "new Api()" }]
|
|
144
|
-
});
|
|
145
|
-
sf.addVariableStatement({
|
|
146
|
-
declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
|
|
147
|
-
declarations: [
|
|
148
|
-
{
|
|
149
|
-
name: "server",
|
|
150
|
-
initializer: `
|
|
151
|
-
http.createServer((req, res) => {
|
|
152
|
-
const u = new URL(req.url ?? "/", "http://localhost");
|
|
153
|
-
|
|
154
|
-
if (u.pathname === "/api") {
|
|
155
|
-
nodeHttpBatchRpcResponse(req, res, api as any);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (u.pathname === "/health") {
|
|
160
|
-
res.statusCode = 200;
|
|
161
|
-
res.end("ok");
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
res.statusCode = 404;
|
|
166
|
-
res.end("Not found");
|
|
167
|
-
})
|
|
168
|
-
`.trim()
|
|
169
|
-
}
|
|
170
|
-
]
|
|
171
|
-
});
|
|
172
|
-
sf.addStatements(
|
|
173
|
-
`server.listen(${containerPort}, "0.0.0.0", () => console.log("create-nodejs-fn container listening on ${containerPort}"));`
|
|
174
|
-
);
|
|
175
|
-
writeFileIfChanged(entryTs, printSource(sf));
|
|
176
|
-
const pkgJson = collectExternalDeps(import_node_path2.default.join(root, "package.json"), external);
|
|
177
|
-
if (pkgJson) {
|
|
178
|
-
writeFileIfChanged(
|
|
179
|
-
import_node_path2.default.join(outBaseAbs, "package.json"),
|
|
180
|
-
`${JSON.stringify(pkgJson, null, 2)}
|
|
181
|
-
`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
const {
|
|
185
|
-
baseImage = "node:20-slim",
|
|
186
|
-
systemPackages = [],
|
|
187
|
-
preInstallCommands = [],
|
|
188
|
-
postInstallCommands = [],
|
|
189
|
-
env: dockerEnv = {}
|
|
190
|
-
} = dockerOpts ?? {};
|
|
191
|
-
const installLines = "RUN corepack enable && pnpm install --prod --no-frozen-lockfile";
|
|
192
|
-
const sysDeps = systemPackages.length > 0 ? [
|
|
193
|
-
"# System packages (from plugin options)",
|
|
194
|
-
`RUN apt-get update && apt-get install -y --no-install-recommends ${systemPackages.join(" ")} \\`,
|
|
195
|
-
" && rm -rf /var/lib/apt/lists/*"
|
|
196
|
-
] : [];
|
|
197
|
-
const preRuns = preInstallCommands.map((cmd) => `RUN ${cmd}`);
|
|
198
|
-
const postRuns = postInstallCommands.map((cmd) => `RUN ${cmd}`);
|
|
199
|
-
const envLines = [
|
|
200
|
-
"ENV NODE_ENV=production",
|
|
201
|
-
...Object.entries(dockerEnv).map(([k, v]) => `ENV ${k}=${JSON.stringify(v ?? "")}`)
|
|
202
|
-
];
|
|
203
|
-
writeFileIfChanged(
|
|
204
|
-
dockerfile,
|
|
205
|
-
[
|
|
206
|
-
"# AUTO-GENERATED. DO NOT EDIT.",
|
|
207
|
-
`FROM ${baseImage}`,
|
|
208
|
-
"WORKDIR /app",
|
|
209
|
-
...sysDeps,
|
|
210
|
-
...preRuns,
|
|
211
|
-
"# Install deps (only externals declared via plugin options)",
|
|
212
|
-
"COPY package.json ./",
|
|
213
|
-
installLines,
|
|
214
|
-
"# Server bundle",
|
|
215
|
-
"COPY ./server.mjs ./server.mjs",
|
|
216
|
-
...envLines,
|
|
217
|
-
...postRuns,
|
|
218
|
-
`EXPOSE ${containerPort}`,
|
|
219
|
-
`CMD ["node", "./server.mjs"]`,
|
|
220
|
-
""
|
|
221
|
-
].filter(Boolean).join("\n")
|
|
222
|
-
);
|
|
223
|
-
const buildOptions = {
|
|
224
|
-
entryPoints: [entryTs],
|
|
225
|
-
outfile: outServer,
|
|
226
|
-
bundle: true,
|
|
227
|
-
platform: "node",
|
|
228
|
-
target: "node20",
|
|
229
|
-
format: "esm",
|
|
230
|
-
sourcemap: true,
|
|
231
|
-
external
|
|
232
|
-
};
|
|
233
|
-
await esbuild.build(buildOptions);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// src/constants.ts
|
|
237
|
-
var GENERATED_FILENAMES = {
|
|
238
|
-
runtime: "create-nodejs-fn.runtime.ts",
|
|
239
|
-
client: "create-nodejs-fn.ts",
|
|
240
|
-
durableObject: "create-nodejs-fn.do.ts",
|
|
241
|
-
context: "create-nodejs-fn.context.ts",
|
|
242
|
-
stubBatch: "create-nodejs-fn-stub-batch.ts"
|
|
243
|
-
};
|
|
244
|
-
var ARTIFACTS_DIR_NAME = ".create-nodejs-fn";
|
|
245
|
-
|
|
246
|
-
// src/extractors.ts
|
|
247
|
-
var import_ts_morph3 = require("ts-morph");
|
|
248
|
-
function methodToFunctionExprText(m) {
|
|
249
|
-
const body = m.getBody();
|
|
250
|
-
if (!body) return void 0;
|
|
251
|
-
const asyncPrefix = m.isAsync() ? "async " : "";
|
|
252
|
-
const params = m.getParameters().map((p) => p.getText()).join(", ");
|
|
253
|
-
return `${asyncPrefix}(${params}) => ${body.getText()}`;
|
|
254
|
-
}
|
|
255
|
-
function keyExprText(expr) {
|
|
256
|
-
if (!expr) return void 0;
|
|
257
|
-
if (import_ts_morph3.Node.isArrowFunction(expr) || import_ts_morph3.Node.isFunctionExpression(expr) || import_ts_morph3.Node.isStringLiteral(expr) || import_ts_morph3.Node.isNoSubstitutionTemplateLiteral(expr)) {
|
|
258
|
-
return expr.getText();
|
|
259
|
-
}
|
|
260
|
-
return void 0;
|
|
261
|
-
}
|
|
262
|
-
function findTopLevelInitializer(sf, name) {
|
|
263
|
-
for (const vs of sf.getVariableStatements()) {
|
|
264
|
-
for (const decl of vs.getDeclarations()) {
|
|
265
|
-
if (decl.getName() !== name) continue;
|
|
266
|
-
return decl.getInitializer();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return void 0;
|
|
270
|
-
}
|
|
271
|
-
function extractContainerKeyFromOptions(sf, exportName, optArg) {
|
|
272
|
-
if (!optArg) return void 0;
|
|
273
|
-
if (import_ts_morph3.Node.isCallExpression(optArg)) {
|
|
274
|
-
const callee = optArg.getExpression();
|
|
275
|
-
const arg0 = optArg.getArguments()[0];
|
|
276
|
-
if (import_ts_morph3.Node.isIdentifier(callee) && callee.getText() === "containerKey") {
|
|
277
|
-
return keyExprText(arg0);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (import_ts_morph3.Node.isIdentifier(optArg)) {
|
|
281
|
-
const init = findTopLevelInitializer(sf, optArg.getText());
|
|
282
|
-
if (init) return extractContainerKeyFromOptions(sf, exportName, init);
|
|
283
|
-
}
|
|
284
|
-
if (!import_ts_morph3.Node.isObjectLiteralExpression(optArg)) return void 0;
|
|
285
|
-
const obj = optArg;
|
|
286
|
-
const direct = obj.getProperty("containerKey");
|
|
287
|
-
if (direct) {
|
|
288
|
-
if (import_ts_morph3.Node.isMethodDeclaration(direct)) return methodToFunctionExprText(direct) ?? void 0;
|
|
289
|
-
if (import_ts_morph3.Node.isPropertyAssignment(direct)) {
|
|
290
|
-
const init = direct.getInitializer();
|
|
291
|
-
if (init && (import_ts_morph3.Node.isArrowFunction(init) || import_ts_morph3.Node.isFunctionExpression(init) || import_ts_morph3.Node.isStringLiteral(init) || import_ts_morph3.Node.isNoSubstitutionTemplateLiteral(init))) {
|
|
292
|
-
return init.getText();
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return void 0;
|
|
297
|
-
}
|
|
298
|
-
function extractExports(sf) {
|
|
299
|
-
const out = [];
|
|
300
|
-
for (const vs of sf.getVariableStatements()) {
|
|
301
|
-
if (!vs.isExported()) continue;
|
|
302
|
-
for (const decl of vs.getDeclarations()) {
|
|
303
|
-
const init = decl.getInitializer();
|
|
304
|
-
const name = decl.getName();
|
|
305
|
-
if (!init || !import_ts_morph3.Node.isCallExpression(init)) continue;
|
|
306
|
-
const callee = init.getExpression();
|
|
307
|
-
if (!import_ts_morph3.Node.isIdentifier(callee) || callee.getText() !== "nodejsFn") continue;
|
|
308
|
-
const args = init.getArguments();
|
|
309
|
-
const optArg = args[1];
|
|
310
|
-
const containerKeyExpr = extractContainerKeyFromOptions(sf, decl.getName(), optArg);
|
|
311
|
-
out.push({ name, containerKeyExpr });
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
for (const fn of sf.getFunctions()) {
|
|
315
|
-
if (!fn.isExported()) continue;
|
|
316
|
-
const hasTag = fn.getJsDocs().some((d) => d.getTags().some((t) => t.getTagName() === "container"));
|
|
317
|
-
if (hasTag) {
|
|
318
|
-
const name = fn.getName();
|
|
319
|
-
if (name) out.push({ name });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return out;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// src/generate-proxy-files.ts
|
|
326
|
-
var import_node_path4 = __toESM(require("path"));
|
|
327
|
-
var import_ts_morph4 = require("ts-morph");
|
|
328
|
-
|
|
329
|
-
// src/path-utils.ts
|
|
330
|
-
var import_node_crypto = __toESM(require("crypto"));
|
|
331
|
-
var import_node_path3 = __toESM(require("path"));
|
|
332
|
-
function hash8(s) {
|
|
333
|
-
return import_node_crypto.default.createHash("sha1").update(s).digest("hex").slice(0, 8);
|
|
334
|
-
}
|
|
335
|
-
function sanitizeNamespace(relNoExt) {
|
|
336
|
-
const noSrc = relNoExt.replace(/^src[/\\]/, "");
|
|
337
|
-
const noSuffix = noSrc.replace(/\.container$/, "");
|
|
338
|
-
return noSuffix.replace(/[/\\]/g, "_").replace(/[^A-Za-z0-9_]/g, "_");
|
|
339
|
-
}
|
|
340
|
-
function proxyFilePath(gdirAbs, containerAbs) {
|
|
341
|
-
const pDir = import_node_path3.default.join(gdirAbs, "__proxies__");
|
|
342
|
-
ensureDir(pDir);
|
|
343
|
-
const h = hash8(containerAbs);
|
|
344
|
-
return import_node_path3.default.join(pDir, `p-${h}.ts`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// src/generate-proxy-files.ts
|
|
348
|
-
function generateProxyFiles(opts) {
|
|
349
|
-
const { gdirAbs, mods, root, generatedClientFileName, generatedContextFileName } = opts;
|
|
350
|
-
const genProject = makeProject();
|
|
351
|
-
for (const mod of mods) {
|
|
352
|
-
const proxyPath = proxyFilePath(gdirAbs, mod.fileAbs);
|
|
353
|
-
const sf = genProject.createSourceFile(proxyPath, "", { overwrite: true });
|
|
354
|
-
sf.addStatements([`// AUTO-GENERATED. DO NOT EDIT.`, `// Proxy for: ${mod.fileRelFromRoot}`]);
|
|
355
|
-
const relToGen = import_node_path4.default.relative(import_node_path4.default.dirname(proxyPath), gdirAbs).replace(/\\/g, "/");
|
|
356
|
-
const genBase = relToGen.startsWith(".") ? relToGen : `./${relToGen}`;
|
|
357
|
-
sf.addImportDeclaration({
|
|
358
|
-
moduleSpecifier: `${genBase}/${generatedClientFileName.replace(/\.ts$/, "")}`,
|
|
359
|
-
namedImports: ["containers"]
|
|
360
|
-
});
|
|
361
|
-
sf.addImportDeclaration({
|
|
362
|
-
moduleSpecifier: `${genBase}/${generatedContextFileName.replace(/\.ts$/, "")}`,
|
|
363
|
-
namedImports: ["__resolveContainerKey"]
|
|
364
|
-
});
|
|
365
|
-
sf.addTypeAlias({
|
|
366
|
-
name: "__Key",
|
|
367
|
-
type: `string | ((ctx: { args: any[] }) => string | Promise<string>)`
|
|
368
|
-
});
|
|
369
|
-
for (const ex of mod.exports) {
|
|
370
|
-
const importRel = import_node_path4.default.relative(import_node_path4.default.dirname(proxyPath), import_node_path4.default.join(root, mod.fileRelFromRoot)).replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
371
|
-
const tName = `__T_${ex.name}`;
|
|
372
|
-
sf.addTypeAlias({
|
|
373
|
-
name: tName,
|
|
374
|
-
type: `typeof import(${JSON.stringify(importRel)}).${ex.name}`
|
|
375
|
-
});
|
|
376
|
-
const keyExpr = ex.containerKeyExpr ?? "undefined";
|
|
377
|
-
sf.addVariableStatement({
|
|
378
|
-
declarationKind: import_ts_morph4.VariableDeclarationKind.Const,
|
|
379
|
-
isExported: true,
|
|
380
|
-
declarations: [
|
|
381
|
-
{
|
|
382
|
-
name: ex.name,
|
|
383
|
-
type: tName,
|
|
384
|
-
initializer: `
|
|
385
|
-
((...args: any[]) => {
|
|
386
|
-
const ctx = { args };
|
|
387
|
-
const localKey = (${keyExpr}) as __Key | undefined;
|
|
388
|
-
const keyP = __resolveContainerKey(${JSON.stringify(mod.namespace)}, ${JSON.stringify(
|
|
389
|
-
ex.name
|
|
390
|
-
)}, ctx, localKey ?? "default");
|
|
391
|
-
|
|
392
|
-
return Promise.resolve(keyP).then((key) =>
|
|
393
|
-
(containers({ containerKey: key }) as any).${mod.namespace}.${ex.name}(...args),
|
|
394
|
-
);
|
|
395
|
-
}) as any
|
|
396
|
-
`.trim()
|
|
397
|
-
}
|
|
398
|
-
]
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
writeFileIfChanged(proxyPath, printSource(sf));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// src/generate-runtime.ts
|
|
406
|
-
var import_node_path5 = __toESM(require("path"));
|
|
407
|
-
function generateRuntime(gdirAbs, runtimeFileName) {
|
|
408
|
-
const genProject = makeProject();
|
|
409
|
-
const filePath = import_node_path5.default.join(gdirAbs, runtimeFileName);
|
|
410
|
-
const sf = genProject.createSourceFile(filePath, "", { overwrite: true });
|
|
411
|
-
sf.addStatements([
|
|
412
|
-
"// AUTO-GENERATED (runtime marker)",
|
|
413
|
-
"// Intentionally tiny. Used only as a marker in *.container.ts"
|
|
414
|
-
]);
|
|
415
|
-
sf.addTypeAlias({
|
|
416
|
-
isExported: true,
|
|
417
|
-
name: "ContainerKey",
|
|
418
|
-
type: `string | ((ctx: { args: any[] }) => string | Promise<string>)`
|
|
419
|
-
});
|
|
420
|
-
sf.addTypeAlias({
|
|
421
|
-
isExported: true,
|
|
422
|
-
name: "ContainerKeyToken",
|
|
423
|
-
type: `{ readonly __containerKeyBrand: true; containerKey: ContainerKey }`
|
|
424
|
-
});
|
|
425
|
-
sf.addFunction({
|
|
426
|
-
isExported: true,
|
|
427
|
-
name: "nodejsFn",
|
|
428
|
-
typeParameters: [{ name: "T", constraint: "(...args: any[]) => any" }],
|
|
429
|
-
parameters: [
|
|
430
|
-
{ name: "fn", type: "T" },
|
|
431
|
-
{ name: "_opts?", type: "ContainerKeyToken" }
|
|
432
|
-
],
|
|
433
|
-
returnType: "T",
|
|
434
|
-
statements: "return fn;"
|
|
435
|
-
});
|
|
436
|
-
sf.addFunction({
|
|
437
|
-
isExported: true,
|
|
438
|
-
name: "containerKey",
|
|
439
|
-
parameters: [{ name: "value", type: "ContainerKey" }],
|
|
440
|
-
returnType: "ContainerKeyToken",
|
|
441
|
-
statements: `return { __containerKeyBrand: true, containerKey: value } as const;`
|
|
442
|
-
});
|
|
443
|
-
writeFileIfChanged(filePath, printSource(sf));
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// src/generate-stub-batch.ts
|
|
447
|
-
var import_node_path6 = __toESM(require("path"));
|
|
448
|
-
function generateStubBatch(gdirAbs, generatedStubBatchFileName) {
|
|
449
|
-
const filePath = import_node_path6.default.join(gdirAbs, generatedStubBatchFileName);
|
|
450
|
-
const content = `// Derived from Cloudflare's create-nodejs-fn helper implementation.
|
|
451
|
-
// Modifications: Use Durable Object stub.fetch() instead of global fetch().
|
|
452
|
-
//
|
|
453
|
-
// Copyright (c) 2025 Cloudflare, Inc.
|
|
454
|
-
// Licensed under the MIT license found in the LICENSE.txt file or at:
|
|
455
|
-
// https://opensource.org/license/mit
|
|
456
|
-
|
|
457
|
-
import { RpcSession, type RpcSessionOptions, type RpcTransport } from "capnweb";
|
|
458
|
-
import type { RpcStub } from "cloudflare:workers";
|
|
459
|
-
|
|
460
|
-
type SendBatchFunc = (batch: string[]) => Promise<string[]>;
|
|
461
|
-
|
|
462
|
-
class BatchClientTransport implements RpcTransport {
|
|
463
|
-
constructor(sendBatch: SendBatchFunc) {
|
|
464
|
-
this.#promise = this.#scheduleBatch(sendBatch);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
#promise: Promise<void>;
|
|
468
|
-
#aborted: any;
|
|
469
|
-
|
|
470
|
-
#batchToSend: string[] | null = [];
|
|
471
|
-
#batchToReceive: string[] | null = null;
|
|
472
|
-
|
|
473
|
-
async send(message: string): Promise<void> {
|
|
474
|
-
if (this.#batchToSend !== null) {
|
|
475
|
-
this.#batchToSend.push(message);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
async receive(): Promise<string> {
|
|
480
|
-
if (!this.#batchToReceive) {
|
|
481
|
-
await this.#promise;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const msg = this.#batchToReceive!.shift();
|
|
485
|
-
if (msg !== undefined) return msg;
|
|
486
|
-
|
|
487
|
-
throw new Error("Batch RPC request ended.");
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
abort?(reason: any): void {
|
|
491
|
-
this.#aborted = reason;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
async #scheduleBatch(sendBatch: SendBatchFunc) {
|
|
495
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
496
|
-
|
|
497
|
-
if (this.#aborted !== undefined) {
|
|
498
|
-
throw this.#aborted;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const batch = this.#batchToSend!;
|
|
502
|
-
this.#batchToSend = null;
|
|
503
|
-
this.#batchToReceive = await sendBatch(batch);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
export function newHttpBatchRpcSession<T extends Rpc.Stubable>(
|
|
508
|
-
stub: { fetch(request: Request): Promise<Response> },
|
|
509
|
-
options?: RpcSessionOptions,
|
|
510
|
-
path: string = "/api",
|
|
511
|
-
): RpcStub<T> {
|
|
512
|
-
const sendBatch: SendBatchFunc = async (batch: string[]) => {
|
|
513
|
-
const req = new Request(\`http://create-nodejs-fn\${path}\`, {
|
|
514
|
-
method: "POST",
|
|
515
|
-
body: batch.join("\\n"),
|
|
516
|
-
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
const response = await stub.fetch(req);
|
|
520
|
-
|
|
521
|
-
if (!response.ok) {
|
|
522
|
-
response.body?.cancel();
|
|
523
|
-
throw new Error(\`RPC request failed: \${response.status} \${response.statusText}\`);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const body = await response.text();
|
|
527
|
-
return body === "" ? [] : body.split("\\n");
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
const transport = new BatchClientTransport(sendBatch);
|
|
531
|
-
const rpc = new RpcSession(transport, undefined, options);
|
|
532
|
-
return rpc.getRemoteMain() as unknown as RpcStub<T>;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/*
|
|
536
|
-
MIT License
|
|
537
|
-
|
|
538
|
-
Copyright (c) 2025 Cloudflare, Inc.
|
|
539
|
-
|
|
540
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
541
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
542
|
-
in the Software without restriction, including without limitation the rights
|
|
543
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
544
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
545
|
-
furnished to do so, subject to the following conditions:
|
|
546
|
-
|
|
547
|
-
The above copyright notice and this permission notice shall be included in all
|
|
548
|
-
copies or substantial portions of the Software.
|
|
549
|
-
|
|
550
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
551
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
552
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
553
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
554
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
555
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
556
|
-
SOFTWARE.
|
|
557
|
-
*/
|
|
558
|
-
`;
|
|
559
|
-
writeFileIfChanged(filePath, content.endsWith("\n") ? content : `${content}
|
|
560
|
-
`);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// src/generate-worker-files.ts
|
|
564
|
-
var import_node_path7 = __toESM(require("path"));
|
|
565
|
-
var import_ts_morph5 = require("ts-morph");
|
|
566
|
-
function generateWorkerFiles(opts) {
|
|
567
|
-
const {
|
|
568
|
-
gdirAbs,
|
|
569
|
-
mods,
|
|
570
|
-
root,
|
|
571
|
-
binding,
|
|
572
|
-
className,
|
|
573
|
-
containerPort,
|
|
574
|
-
workerEnvVars,
|
|
575
|
-
clientFileName,
|
|
576
|
-
doFileName,
|
|
577
|
-
contextFileName,
|
|
578
|
-
stubBatchFileName
|
|
579
|
-
} = opts;
|
|
580
|
-
const genProject = makeProject();
|
|
581
|
-
const clientPath = import_node_path7.default.join(gdirAbs, clientFileName);
|
|
582
|
-
const doPath = import_node_path7.default.join(gdirAbs, doFileName);
|
|
583
|
-
const ctxPath = import_node_path7.default.join(gdirAbs, contextFileName);
|
|
584
|
-
{
|
|
585
|
-
const sf = genProject.createSourceFile(clientPath, "", { overwrite: true });
|
|
586
|
-
sf.addStatements([
|
|
587
|
-
"// AUTO-GENERATED. DO NOT EDIT.",
|
|
588
|
-
"// Generated by vite-plugin-create-nodejs-fn."
|
|
589
|
-
]);
|
|
590
|
-
sf.addImportDeclaration({
|
|
591
|
-
moduleSpecifier: `./${stubBatchFileName.replace(/\.ts$/, "")}`,
|
|
592
|
-
namedImports: ["newHttpBatchRpcSession"]
|
|
593
|
-
});
|
|
594
|
-
sf.addImportDeclaration({
|
|
595
|
-
moduleSpecifier: "cloudflare:workers",
|
|
596
|
-
namedImports: ["env"]
|
|
597
|
-
});
|
|
598
|
-
sf.addVariableStatement({
|
|
599
|
-
declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
|
|
600
|
-
isExported: true,
|
|
601
|
-
declarations: [{ name: "BINDING_NAME", initializer: JSON.stringify(binding) }]
|
|
602
|
-
});
|
|
603
|
-
for (const mod of mods) {
|
|
604
|
-
const importRel = import_node_path7.default.relative(import_node_path7.default.dirname(clientPath), import_node_path7.default.join(root, mod.fileRelFromRoot)).replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
605
|
-
for (const ex of mod.exports) {
|
|
606
|
-
const alias = `__T_${mod.namespace}_${ex.name}`;
|
|
607
|
-
sf.addTypeAlias({
|
|
608
|
-
name: alias,
|
|
609
|
-
type: `typeof import(${JSON.stringify(importRel)}).${ex.name}`
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
sf.addInterface({
|
|
613
|
-
isExported: true,
|
|
614
|
-
name: `__NS_${mod.namespace}`,
|
|
615
|
-
methods: mod.exports.map((ex) => {
|
|
616
|
-
const alias = `__T_${mod.namespace}_${ex.name}`;
|
|
617
|
-
return {
|
|
618
|
-
name: ex.name,
|
|
619
|
-
parameters: [{ name: "args", isRestParameter: true, type: `Parameters<${alias}>` }],
|
|
620
|
-
returnType: `ReturnType<${alias}>`
|
|
621
|
-
};
|
|
622
|
-
})
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
sf.addInterface({
|
|
626
|
-
isExported: true,
|
|
627
|
-
name: "FlatApi",
|
|
628
|
-
extends: ["Rpc.RpcTargetBranded "],
|
|
629
|
-
methods: mods.flatMap(
|
|
630
|
-
(mod) => mod.exports.map((ex) => {
|
|
631
|
-
const alias = `__T_${mod.namespace}_${ex.name}`;
|
|
632
|
-
return {
|
|
633
|
-
name: `${mod.namespace}__${ex.name}`,
|
|
634
|
-
parameters: [{ name: "args", isRestParameter: true, type: `Parameters<${alias}>` }],
|
|
635
|
-
returnType: `ReturnType<${alias}>`
|
|
636
|
-
};
|
|
637
|
-
})
|
|
638
|
-
)
|
|
639
|
-
});
|
|
640
|
-
sf.addInterface({
|
|
641
|
-
isExported: true,
|
|
642
|
-
name: "Containers",
|
|
643
|
-
properties: mods.map((m) => ({ name: m.namespace, type: `__NS_${m.namespace}` }))
|
|
644
|
-
});
|
|
645
|
-
sf.addFunction({
|
|
646
|
-
isExported: true,
|
|
647
|
-
name: "rawClient",
|
|
648
|
-
parameters: [{ name: "ctx", type: `{ containerKey?: string }` }],
|
|
649
|
-
statements: `
|
|
650
|
-
const ns = env?.[BINDING_NAME];
|
|
651
|
-
if (!ns?.getByName) throw new Error(\`Container binding missing: \${BINDING_NAME}\`);
|
|
652
|
-
const stub = ns.getByName(ctx.containerKey ?? "default");
|
|
653
|
-
return newHttpBatchRpcSession<FlatApi>(stub);
|
|
654
|
-
`
|
|
655
|
-
});
|
|
656
|
-
const objLines = [];
|
|
657
|
-
for (const mod of mods) {
|
|
658
|
-
const lines = mod.exports.map(
|
|
659
|
-
(ex) => `${ex.name}: (...args: any[]) => (rpc as any)[${JSON.stringify(
|
|
660
|
-
`${mod.namespace}__${ex.name}`
|
|
661
|
-
)}](...args),`
|
|
662
|
-
);
|
|
663
|
-
objLines.push(`${mod.namespace}: { ${lines.join(" ")} },`);
|
|
664
|
-
}
|
|
665
|
-
sf.addFunction({
|
|
666
|
-
isExported: true,
|
|
667
|
-
name: "containers",
|
|
668
|
-
parameters: [{ name: "ctx", type: `{ containerKey?: string }` }],
|
|
669
|
-
returnType: "Containers",
|
|
670
|
-
statements: `
|
|
671
|
-
const rpc = rawClient(ctx);
|
|
672
|
-
return { ${objLines.join(" ")} } as any;
|
|
673
|
-
`
|
|
674
|
-
});
|
|
675
|
-
writeFileIfChanged(clientPath, printSource(sf));
|
|
676
|
-
}
|
|
677
|
-
{
|
|
678
|
-
const sf = genProject.createSourceFile(doPath, "", { overwrite: true });
|
|
679
|
-
sf.addStatements(["// AUTO-GENERATED. DO NOT EDIT."]);
|
|
680
|
-
sf.addImportDeclaration({
|
|
681
|
-
moduleSpecifier: "@cloudflare/containers",
|
|
682
|
-
namedImports: ["Container"]
|
|
683
|
-
});
|
|
684
|
-
const envVarEntries = Array.isArray(workerEnvVars) ? workerEnvVars.map((n) => [n, n]) : Object.entries(workerEnvVars);
|
|
685
|
-
if (envVarEntries.length > 0) {
|
|
686
|
-
sf.addImportDeclaration({ moduleSpecifier: "cloudflare:workers", namedImports: ["env"] });
|
|
687
|
-
}
|
|
688
|
-
const envVarInitializer = envVarEntries.length === 0 ? void 0 : `{
|
|
689
|
-
${envVarEntries.map(([key, src]) => ` ${JSON.stringify(key)}: env[${JSON.stringify(src)}],`).join("\n")}
|
|
690
|
-
}`;
|
|
691
|
-
sf.addClass({
|
|
692
|
-
isExported: true,
|
|
693
|
-
name: className,
|
|
694
|
-
extends: "Container",
|
|
695
|
-
properties: [
|
|
696
|
-
{ name: "defaultPort", initializer: String(containerPort) },
|
|
697
|
-
{ name: "sleepAfter", initializer: JSON.stringify("10s") },
|
|
698
|
-
...envVarInitializer ? [{ name: "envVars", initializer: envVarInitializer }] : []
|
|
699
|
-
]
|
|
700
|
-
});
|
|
701
|
-
writeFileIfChanged(doPath, printSource(sf));
|
|
702
|
-
}
|
|
703
|
-
{
|
|
704
|
-
const sf = genProject.createSourceFile(ctxPath, "", { overwrite: true });
|
|
705
|
-
sf.addStatements(["// AUTO-GENERATED. DO NOT EDIT."]);
|
|
706
|
-
sf.addTypeAlias({
|
|
707
|
-
name: "ContainerKey",
|
|
708
|
-
type: `string | ((ctx: { args: any[] }) => string | Promise<string>)`
|
|
709
|
-
});
|
|
710
|
-
sf.addVariableStatement({
|
|
711
|
-
declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
|
|
712
|
-
declarations: [{ name: "registry", initializer: "new Map<string, ContainerKey>()" }]
|
|
713
|
-
});
|
|
714
|
-
sf.addFunction({
|
|
715
|
-
isExported: true,
|
|
716
|
-
name: "setContainerKey",
|
|
717
|
-
parameters: [
|
|
718
|
-
{ name: "namespace", type: "string" },
|
|
719
|
-
{ name: "exportName", type: "string" },
|
|
720
|
-
{ name: "key", type: "ContainerKey" }
|
|
721
|
-
],
|
|
722
|
-
statements: `registry.set(\`\${namespace}::\${exportName}\`, key);`
|
|
723
|
-
});
|
|
724
|
-
sf.addFunction({
|
|
725
|
-
isExported: true,
|
|
726
|
-
name: "setNamespaceContainerKey",
|
|
727
|
-
parameters: [
|
|
728
|
-
{ name: "namespace", type: "string" },
|
|
729
|
-
{ name: "key", type: "ContainerKey" }
|
|
730
|
-
],
|
|
731
|
-
statements: `registry.set(\`\${namespace}::*\`, key);`
|
|
732
|
-
});
|
|
733
|
-
sf.addFunction({
|
|
734
|
-
isExported: true,
|
|
735
|
-
name: "__resolveContainerKey",
|
|
736
|
-
parameters: [
|
|
737
|
-
{ name: "namespace", type: "string" },
|
|
738
|
-
{ name: "exportName", type: "string" },
|
|
739
|
-
{ name: "ctx", type: `{ args: any[] }` },
|
|
740
|
-
{ name: "fallback?", type: "ContainerKey" }
|
|
741
|
-
],
|
|
742
|
-
returnType: "string | Promise<string>",
|
|
743
|
-
statements: `
|
|
744
|
-
const direct = registry.get(\`\${namespace}::\${exportName}\`);
|
|
745
|
-
const ns = registry.get(\`\${namespace}::*\`);
|
|
746
|
-
const candidate = direct ?? ns ?? fallback;
|
|
747
|
-
if (typeof candidate === "function") return candidate(ctx);
|
|
748
|
-
if (typeof candidate === "string") return candidate;
|
|
749
|
-
return "default";
|
|
750
|
-
`
|
|
751
|
-
});
|
|
752
|
-
writeFileIfChanged(ctxPath, printSource(sf));
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// src/index.ts
|
|
757
|
-
function createNodejsFnPlugin(opts = {}) {
|
|
758
|
-
const files = opts.files ?? ["src/**/*.container.ts"];
|
|
759
|
-
const generatedDir = opts.generatedDir ?? "src/__generated__";
|
|
760
|
-
const binding = opts.binding ?? "NODEJS_FN";
|
|
761
|
-
const className = opts.className ?? "NodejsFnContainer";
|
|
762
|
-
const containerPort = opts.containerPort ?? 8080;
|
|
763
|
-
const external = opts.external ?? [];
|
|
764
|
-
const docker = opts.docker ?? {};
|
|
765
|
-
const workerEnvVars = opts.workerEnvVars ?? [];
|
|
766
|
-
const autoRebuildContainers = opts.autoRebuildContainers ?? true;
|
|
767
|
-
const rebuildDebounceMs = opts.rebuildDebounceMs ?? 600;
|
|
768
|
-
let root = process.cwd();
|
|
769
|
-
let outDir = "dist";
|
|
770
|
-
let config = null;
|
|
771
|
-
let devServer = null;
|
|
772
|
-
let restartTimer = null;
|
|
773
|
-
let restartingDevServer = false;
|
|
774
|
-
let serveDebounceTimer = null;
|
|
775
|
-
const pendingChanged = /* @__PURE__ */ new Set();
|
|
776
|
-
const pendingRemoved = /* @__PURE__ */ new Set();
|
|
777
|
-
let pendingForce = false;
|
|
778
|
-
let pendingReason = null;
|
|
779
|
-
const project = makeProject();
|
|
780
|
-
const moduleCache = /* @__PURE__ */ new Map();
|
|
781
|
-
const containerFiles = /* @__PURE__ */ new Set();
|
|
782
|
-
let regenQueue = Promise.resolve();
|
|
783
|
-
let generatedOnce = false;
|
|
784
|
-
function discoverFileList() {
|
|
785
|
-
const patterns = files;
|
|
786
|
-
const absFiles = patterns.flatMap((p) => (0, import_glob.globSync)(p, { cwd: root, absolute: true }));
|
|
787
|
-
absFiles.forEach((f) => {
|
|
788
|
-
containerFiles.add(import_node_path8.default.normalize(f));
|
|
789
|
-
});
|
|
790
|
-
return absFiles;
|
|
791
|
-
}
|
|
792
|
-
function loadSourceFile(fileAbs) {
|
|
793
|
-
const existing = project.getSourceFile(fileAbs);
|
|
794
|
-
if (existing) {
|
|
795
|
-
existing.refreshFromFileSystemSync();
|
|
796
|
-
return existing;
|
|
797
|
-
}
|
|
798
|
-
if (import_node_fs3.default.existsSync(fileAbs)) {
|
|
799
|
-
return project.addSourceFileAtPath(fileAbs);
|
|
800
|
-
}
|
|
801
|
-
return void 0;
|
|
802
|
-
}
|
|
803
|
-
function refreshModules(changed, removed) {
|
|
804
|
-
let dirty = false;
|
|
805
|
-
if (removed?.length) {
|
|
806
|
-
for (const abs of removed) {
|
|
807
|
-
moduleCache.delete(import_node_path8.default.normalize(abs));
|
|
808
|
-
const sf = project.getSourceFile(abs);
|
|
809
|
-
if (sf) project.removeSourceFile(sf);
|
|
810
|
-
containerFiles.delete(import_node_path8.default.normalize(abs));
|
|
811
|
-
dirty = true;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
const targets = changed?.length ? changed : Array.from(containerFiles);
|
|
815
|
-
for (const absRaw of targets) {
|
|
816
|
-
const abs = import_node_path8.default.normalize(absRaw);
|
|
817
|
-
if (!import_node_fs3.default.existsSync(abs)) continue;
|
|
818
|
-
const sf = loadSourceFile(abs);
|
|
819
|
-
if (!sf) continue;
|
|
820
|
-
const exportsWithKeys = extractExports(sf);
|
|
821
|
-
const rel = import_node_path8.default.relative(root, abs).replace(/\\/g, "/");
|
|
822
|
-
const relNoExt = rel.replace(/\.[^.]+$/, "");
|
|
823
|
-
if (exportsWithKeys.length === 0) {
|
|
824
|
-
dirty = dirty || moduleCache.delete(abs);
|
|
825
|
-
continue;
|
|
826
|
-
}
|
|
827
|
-
const mod = {
|
|
828
|
-
fileAbs: abs,
|
|
829
|
-
fileRelFromRoot: rel,
|
|
830
|
-
namespace: sanitizeNamespace(relNoExt),
|
|
831
|
-
exports: exportsWithKeys
|
|
832
|
-
};
|
|
833
|
-
const prev = moduleCache.get(abs);
|
|
834
|
-
const changedJson = JSON.stringify(prev) !== JSON.stringify(mod);
|
|
835
|
-
if (changedJson || !prev) {
|
|
836
|
-
moduleCache.set(abs, mod);
|
|
837
|
-
dirty = true;
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
return dirty;
|
|
841
|
-
}
|
|
842
|
-
async function regenerate(kind, delta) {
|
|
843
|
-
if (!generatedOnce) {
|
|
844
|
-
discoverFileList();
|
|
845
|
-
}
|
|
846
|
-
const dirty = refreshModules(delta?.changed, delta?.removed) || delta?.force || !generatedOnce;
|
|
847
|
-
const mods = Array.from(moduleCache.values()).sort(
|
|
848
|
-
(a, b) => a.namespace.localeCompare(b.namespace)
|
|
849
|
-
);
|
|
850
|
-
if (!dirty && generatedOnce) {
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
const gdirAbs = import_node_path8.default.join(root, generatedDir);
|
|
854
|
-
ensureDir(gdirAbs);
|
|
855
|
-
generateRuntime(gdirAbs, GENERATED_FILENAMES.runtime);
|
|
856
|
-
generateStubBatch(gdirAbs, GENERATED_FILENAMES.stubBatch);
|
|
857
|
-
generateWorkerFiles({
|
|
858
|
-
gdirAbs,
|
|
859
|
-
mods,
|
|
860
|
-
root,
|
|
861
|
-
binding,
|
|
862
|
-
className,
|
|
863
|
-
containerPort,
|
|
864
|
-
workerEnvVars,
|
|
865
|
-
clientFileName: GENERATED_FILENAMES.client,
|
|
866
|
-
doFileName: GENERATED_FILENAMES.durableObject,
|
|
867
|
-
contextFileName: GENERATED_FILENAMES.context,
|
|
868
|
-
stubBatchFileName: GENERATED_FILENAMES.stubBatch
|
|
869
|
-
});
|
|
870
|
-
generateProxyFiles({
|
|
871
|
-
gdirAbs,
|
|
872
|
-
mods,
|
|
873
|
-
root,
|
|
874
|
-
generatedContextFileName: GENERATED_FILENAMES.context,
|
|
875
|
-
generatedClientFileName: GENERATED_FILENAMES.client
|
|
876
|
-
});
|
|
877
|
-
await buildContainerServer({
|
|
878
|
-
mods,
|
|
879
|
-
outBaseAbs: import_node_path8.default.join(root, ARTIFACTS_DIR_NAME),
|
|
880
|
-
dockerOpts: docker,
|
|
881
|
-
containerPort,
|
|
882
|
-
external,
|
|
883
|
-
root
|
|
884
|
-
});
|
|
885
|
-
if (kind === "build") {
|
|
886
|
-
const distAbs = import_node_path8.default.join(root, outDir);
|
|
887
|
-
await buildContainerServer({
|
|
888
|
-
mods,
|
|
889
|
-
outBaseAbs: import_node_path8.default.join(distAbs, ARTIFACTS_DIR_NAME),
|
|
890
|
-
dockerOpts: docker,
|
|
891
|
-
containerPort,
|
|
892
|
-
external,
|
|
893
|
-
root
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
generatedOnce = true;
|
|
897
|
-
}
|
|
898
|
-
function enqueueRegeneration(kind, delta) {
|
|
899
|
-
regenQueue = regenQueue.then(() => regenerate(kind, delta)).catch((err) => {
|
|
900
|
-
console.error("[create-nodejs-fn] regeneration failed", err);
|
|
901
|
-
});
|
|
902
|
-
return regenQueue;
|
|
903
|
-
}
|
|
904
|
-
function scheduleContainerRebuild(reason) {
|
|
905
|
-
if (!autoRebuildContainers) return;
|
|
906
|
-
if (config?.command !== "serve") return;
|
|
907
|
-
if (!devServer) return;
|
|
908
|
-
if (restartingDevServer) return;
|
|
909
|
-
const serverForRestart = devServer;
|
|
910
|
-
if (restartTimer) clearTimeout(restartTimer);
|
|
911
|
-
restartTimer = setTimeout(() => {
|
|
912
|
-
restartTimer = null;
|
|
913
|
-
regenQueue.then(async () => {
|
|
914
|
-
if (restartingDevServer) return;
|
|
915
|
-
restartingDevServer = true;
|
|
916
|
-
try {
|
|
917
|
-
serverForRestart?.config.logger?.info?.(
|
|
918
|
-
`[create-nodejs-fn] Restarting Vite dev server to rebuild containers (${reason})`
|
|
919
|
-
);
|
|
920
|
-
await serverForRestart?.restart();
|
|
921
|
-
} catch (err) {
|
|
922
|
-
console.error(
|
|
923
|
-
"[create-nodejs-fn] failed to restart dev server for container rebuild",
|
|
924
|
-
err
|
|
925
|
-
);
|
|
926
|
-
} finally {
|
|
927
|
-
restartingDevServer = false;
|
|
928
|
-
}
|
|
929
|
-
}).catch((err) => {
|
|
930
|
-
console.error("[create-nodejs-fn] regeneration failed before restart", err);
|
|
931
|
-
});
|
|
932
|
-
}, rebuildDebounceMs);
|
|
933
|
-
}
|
|
934
|
-
function scheduleServeRegeneration(reason, delta) {
|
|
935
|
-
delta.changed?.map((p) => pendingChanged.add(import_node_path8.default.normalize(p)));
|
|
936
|
-
delta.removed?.map((p) => pendingRemoved.add(import_node_path8.default.normalize(p)));
|
|
937
|
-
if (delta.force) pendingForce = true;
|
|
938
|
-
pendingReason = reason;
|
|
939
|
-
if (serveDebounceTimer) clearTimeout(serveDebounceTimer);
|
|
940
|
-
serveDebounceTimer = setTimeout(() => {
|
|
941
|
-
serveDebounceTimer = null;
|
|
942
|
-
const changed = pendingChanged.size ? Array.from(pendingChanged) : void 0;
|
|
943
|
-
const removed = pendingRemoved.size ? Array.from(pendingRemoved) : void 0;
|
|
944
|
-
const force = pendingForce;
|
|
945
|
-
pendingChanged.clear();
|
|
946
|
-
pendingRemoved.clear();
|
|
947
|
-
pendingForce = false;
|
|
948
|
-
enqueueRegeneration("serve", { changed, removed, force });
|
|
949
|
-
const reasonText = pendingReason ?? "changes";
|
|
950
|
-
pendingReason = null;
|
|
951
|
-
scheduleContainerRebuild(reasonText);
|
|
952
|
-
}, rebuildDebounceMs);
|
|
953
|
-
}
|
|
954
|
-
return {
|
|
955
|
-
name: "vite-plugin-create-nodejs-fn",
|
|
956
|
-
enforce: "pre",
|
|
957
|
-
config(userConfig) {
|
|
958
|
-
if (!external.length) return;
|
|
959
|
-
const cur = userConfig.optimizeDeps?.exclude ?? [];
|
|
960
|
-
const merged = Array.from(/* @__PURE__ */ new Set([...cur, ...external]));
|
|
961
|
-
return { optimizeDeps: { exclude: merged } };
|
|
962
|
-
},
|
|
963
|
-
configResolved(c) {
|
|
964
|
-
config = c;
|
|
965
|
-
root = c.root;
|
|
966
|
-
outDir = c.build.outDir || "dist";
|
|
967
|
-
},
|
|
968
|
-
async buildStart() {
|
|
969
|
-
await enqueueRegeneration("serve");
|
|
970
|
-
},
|
|
971
|
-
async closeBundle() {
|
|
972
|
-
await enqueueRegeneration("build");
|
|
973
|
-
},
|
|
974
|
-
async resolveId(id, importer, options) {
|
|
975
|
-
const r = await this.resolve(id, importer, {
|
|
976
|
-
...options,
|
|
977
|
-
skipSelf: true
|
|
978
|
-
});
|
|
979
|
-
const resolved = r?.id?.split("?")[0];
|
|
980
|
-
if (!resolved || !resolved.endsWith(".container.ts")) return null;
|
|
981
|
-
const gdirAbs = import_node_path8.default.join(root, generatedDir);
|
|
982
|
-
const proxyPath = proxyFilePath(gdirAbs, resolved);
|
|
983
|
-
if (!import_node_fs3.default.existsSync(proxyPath)) {
|
|
984
|
-
await enqueueRegeneration(config?.command === "build" ? "build" : "serve", {
|
|
985
|
-
changed: [resolved],
|
|
986
|
-
force: true
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
return proxyPath;
|
|
990
|
-
},
|
|
991
|
-
configureServer(server) {
|
|
992
|
-
devServer = server;
|
|
993
|
-
const patterns = files.map((g) => import_node_path8.default.join(root, g));
|
|
994
|
-
server.watcher.add(patterns);
|
|
995
|
-
const onAddOrChange = (p) => {
|
|
996
|
-
if (!p.endsWith(".container.ts")) return;
|
|
997
|
-
containerFiles.add(import_node_path8.default.normalize(p));
|
|
998
|
-
scheduleServeRegeneration(`changed ${import_node_path8.default.relative(root, p)}`, { changed: [p] });
|
|
999
|
-
};
|
|
1000
|
-
const onUnlink = (p) => {
|
|
1001
|
-
if (!p.endsWith(".container.ts")) return;
|
|
1002
|
-
scheduleServeRegeneration(`removed ${import_node_path8.default.relative(root, p)}`, { removed: [p] });
|
|
1003
|
-
};
|
|
1004
|
-
server.watcher.on("add", onAddOrChange);
|
|
1005
|
-
server.watcher.on("change", onAddOrChange);
|
|
1006
|
-
server.watcher.on("unlink", onUnlink);
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1011
|
-
0 && (module.exports = {
|
|
1012
|
-
createNodejsFnPlugin
|
|
1013
|
-
});
|