appstage 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/dist/bin/build.js +532 -0
  4. package/dist/bin/startDev.js +545 -0
  5. package/dist/bin/startProd.js +545 -0
  6. package/dist/index.cjs +671 -0
  7. package/dist/index.d.ts +1162 -0
  8. package/dist/index.mjs +622 -0
  9. package/index.ts +32 -0
  10. package/package.json +39 -0
  11. package/src/controllers/dir.ts +119 -0
  12. package/src/controllers/unhandledError.ts +15 -0
  13. package/src/controllers/unhandledRoute.ts +14 -0
  14. package/src/lib/lang/getEffectiveLocale.ts +52 -0
  15. package/src/lib/lang/getLocales.ts +10 -0
  16. package/src/lib/lang/toLanguage.ts +3 -0
  17. package/src/lib/logger/LogOptions.ts +8 -0
  18. package/src/lib/logger/ansiEscapeCodes.ts +6 -0
  19. package/src/lib/logger/levelColors.ts +4 -0
  20. package/src/lib/logger/log.ts +82 -0
  21. package/src/middleware/init.ts +22 -0
  22. package/src/middleware/lang.ts +83 -0
  23. package/src/middleware/requestEvents.ts +29 -0
  24. package/src/scripts/bin/build.ts +5 -0
  25. package/src/scripts/bin/startDev.ts +5 -0
  26. package/src/scripts/bin/startProd.ts +5 -0
  27. package/src/scripts/build.ts +45 -0
  28. package/src/scripts/cli.ts +46 -0
  29. package/src/scripts/const/commonBuildOptions.ts +13 -0
  30. package/src/scripts/const/entryExtensions.ts +1 -0
  31. package/src/scripts/start.ts +18 -0
  32. package/src/scripts/types/BuildParams.ts +9 -0
  33. package/src/scripts/utils/buildClient.ts +41 -0
  34. package/src/scripts/utils/buildServer.ts +35 -0
  35. package/src/scripts/utils/buildServerCSS.ts +38 -0
  36. package/src/scripts/utils/createPostbuildPlugins.ts +66 -0
  37. package/src/scripts/utils/getEntries.ts +22 -0
  38. package/src/scripts/utils/getEntryPoints.ts +25 -0
  39. package/src/scripts/utils/getFirstAvailable.ts +22 -0
  40. package/src/scripts/utils/populateEntries.ts +28 -0
  41. package/src/scripts/utils/toImportPath.ts +12 -0
  42. package/src/types/Controller.ts +4 -0
  43. package/src/types/ErrorController.ts +3 -0
  44. package/src/types/LogEventPayload.ts +12 -0
  45. package/src/types/LogLevel.ts +1 -0
  46. package/src/types/Middleware.ts +7 -0
  47. package/src/types/MiddlewareSet.ts +3 -0
  48. package/src/types/RenderStatus.ts +9 -0
  49. package/src/types/ReqCtx.ts +11 -0
  50. package/src/types/TransformContent.ts +11 -0
  51. package/src/types/express.d.ts +15 -0
  52. package/src/types/global.d.ts +17 -0
  53. package/src/utils/createApp.ts +44 -0
  54. package/src/utils/cspNonce.ts +6 -0
  55. package/src/utils/emitLog.ts +18 -0
  56. package/src/utils/getEntries.ts +22 -0
  57. package/src/utils/getStatusMessage.ts +5 -0
  58. package/src/utils/injectNonce.ts +7 -0
  59. package/src/utils/renderStatus.ts +20 -0
  60. package/src/utils/resolveFilePath.ts +78 -0
  61. package/src/utils/serializeState.ts +3 -0
  62. package/src/utils/servePipeableStream.ts +32 -0
  63. package/tsconfig.json +18 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,671 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __copyProps = (to, from, except, desc) => {
8
+ if (from && typeof from === "object" || typeof from === "function") {
9
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) {
12
+ __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ }
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
22
+ value: mod,
23
+ enumerable: true
24
+ }) : target, mod));
25
+
26
+ let node_fs_promises = require("node:fs/promises");
27
+ let node_path = require("node:path");
28
+ let dateshape = require("dateshape");
29
+ let node_crypto = require("node:crypto");
30
+ let node_http = require("node:http");
31
+ let node_child_process = require("node:child_process");
32
+ let esbuild = require("esbuild");
33
+ esbuild = __toESM(esbuild);
34
+ let node_events = require("node:events");
35
+ node_events = __toESM(node_events);
36
+ let express = require("express");
37
+ express = __toESM(express);
38
+
39
+ function emitLog(app, message, payload) {
40
+ let normalizedPayload = {
41
+ timestamp: Date.now(),
42
+ ...payload,
43
+ ...typeof message === "string" || message instanceof Error ? { message } : message
44
+ };
45
+ return app.events?.emit("log", normalizedPayload);
46
+ }
47
+
48
+ function toLanguage(locale) {
49
+ return locale.split(/[-_]/)[0];
50
+ }
51
+
52
+ async function resolveFilePath({ name, dir = ".", lang, supportedLocales = [], ext, index }) {
53
+ let cwd = process.cwd();
54
+ let localeSet = new Set(supportedLocales);
55
+ let langSet = new Set(supportedLocales.map(toLanguage));
56
+ let availableNames = [name, ...[...localeSet, ...langSet].map((item) => `${name}.${item}`)];
57
+ let preferredLangNames;
58
+ if (lang && (!supportedLocales.length || localeSet.has(lang) || langSet.has(lang))) preferredLangNames = [`${name}.${lang}`, `${name}.${toLanguage(lang)}`];
59
+ let names = new Set(preferredLangNames ? [...preferredLangNames, ...availableNames] : availableNames);
60
+ let exts = Array.isArray(ext) ? ext : [ext];
61
+ for (let item of names) for (let itemExt of exts) {
62
+ let path = (0, node_path.join)(cwd, dir, `${item}${itemExt ? `.${itemExt}` : ""}`);
63
+ try {
64
+ await (0, node_fs_promises.access)(path);
65
+ return path;
66
+ } catch {}
67
+ }
68
+ if (index) for (let item of names) for (let itemExt of exts) {
69
+ let path = (0, node_path.join)(cwd, dir, item, `index${itemExt ? `.${itemExt}` : ""}`);
70
+ try {
71
+ await (0, node_fs_promises.access)(path);
72
+ return path;
73
+ } catch {}
74
+ }
75
+ }
76
+
77
+ const defaultExt = ["html", "htm"];
78
+ const defaultName = (req) => req.path.split("/").at(-1);
79
+ /**
80
+ * Serves files from the specified directory path in a locale-aware
81
+ * fashion after applying optional transforms.
82
+ *
83
+ * A file ending with `.<lang>.<ext>` is picked first if the `<lang>`
84
+ * part matches `req.ctx.lang`. If the `supportedLocales` array is
85
+ * provided, the `*.<lang>.<ext>` file is picked only if the given
86
+ * array contains `req.ctx.lang`. Otherwise, a file without the locale
87
+ * in its path (`*.<ext>`) is picked.
88
+ */
89
+ const dir = ({ path, name = defaultName, ext = defaultExt, transform, supportedLocales, index = true }) => {
90
+ if (typeof path !== "string") throw new Error(`'path' is not a string`);
91
+ let transformSet = (Array.isArray(transform) ? transform : [transform]).filter((item) => typeof item === "function");
92
+ return async (req, res) => {
93
+ let fileName = typeof name === "function" ? name(req, res) : name;
94
+ emitLog(req.app, `Name: ${JSON.stringify(fileName)}`, {
95
+ req,
96
+ res
97
+ });
98
+ if (fileName === void 0) {
99
+ res.status(404).send(await req.app.renderStatus?.(req, res));
100
+ return;
101
+ }
102
+ let filePath = await resolveFilePath({
103
+ name: fileName,
104
+ dir: path,
105
+ ext,
106
+ supportedLocales,
107
+ lang: req.ctx?.lang,
108
+ index
109
+ });
110
+ emitLog(req.app, `Path: ${JSON.stringify(filePath)}`, {
111
+ req,
112
+ res
113
+ });
114
+ if (!filePath) {
115
+ res.status(404).send(await req.app.renderStatus?.(req, res));
116
+ return;
117
+ }
118
+ let content = (await (0, node_fs_promises.readFile)(filePath)).toString();
119
+ for (let transformItem of transformSet) content = await transformItem(req, res, {
120
+ content,
121
+ path: filePath,
122
+ name: (0, node_path.basename)(filePath, (0, node_path.extname)(filePath))
123
+ });
124
+ res.send(content);
125
+ };
126
+ };
127
+
128
+ const unhandledError = () => async (err, req, res) => {
129
+ emitLog(req.app, "Unhandled error", {
130
+ level: "error",
131
+ data: err,
132
+ req,
133
+ res
134
+ });
135
+ res.status(500).send(await req.app.renderStatus?.(req, res, "unhandled_error"));
136
+ };
137
+
138
+ const unhandledRoute = () => async (req, res) => {
139
+ emitLog(req.app, "Unhandled route", {
140
+ level: "debug",
141
+ req,
142
+ res
143
+ });
144
+ res.status(404).send(await req.app.renderStatus?.(req, res, "unhandled_route"));
145
+ };
146
+
147
+ function getEffectiveLocale(preferredLocales, supportedLocales) {
148
+ if (!supportedLocales || supportedLocales.length === 0) return void 0;
149
+ if (!preferredLocales || preferredLocales.length === 0) return supportedLocales[0];
150
+ let exactMatch = {};
151
+ for (let i = 0; i < preferredLocales.length && !exactMatch.locale; i++) {
152
+ let k = supportedLocales.indexOf(preferredLocales[i]);
153
+ if (k !== -1) {
154
+ exactMatch.index = i;
155
+ exactMatch.locale = supportedLocales[k];
156
+ }
157
+ }
158
+ let languageMatch = {};
159
+ let supportedLanguages = supportedLocales.map(toLanguage);
160
+ let preferredLanguages = preferredLocales.map(toLanguage);
161
+ for (let i = 0; i < preferredLanguages.length && !languageMatch.locale; i++) {
162
+ let k = supportedLanguages.indexOf(preferredLanguages[i]);
163
+ if (k !== -1) {
164
+ languageMatch.index = i;
165
+ languageMatch.locale = supportedLocales[k];
166
+ }
167
+ }
168
+ if (exactMatch.locale && (!languageMatch.locale || exactMatch.index < languageMatch.index)) return exactMatch.locale;
169
+ return languageMatch.locale ?? supportedLocales[0];
170
+ }
171
+
172
+ /**
173
+ * Parses a language range string (typically a value of the 'Accept-Language'
174
+ * HTTP request header) and returns a corresponding array of locales
175
+ * @example 'fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5'
176
+ */
177
+ function getLocales(languageRange) {
178
+ return (languageRange ?? "").split(/[,;]\s*/).filter((s) => !s.startsWith("q=") && s !== "*");
179
+ }
180
+
181
+ const ansiEscapeCodes = {
182
+ reset: "\x1B[0m",
183
+ dim: "\x1B[2m",
184
+ red: "\x1B[31m",
185
+ yellow: "\x1B[33m"
186
+ };
187
+
188
+ const levelColors = {
189
+ error: "red",
190
+ warn: "yellow"
191
+ };
192
+
193
+ function isEmpty(x) {
194
+ if (x === null || x === void 0 || x === "") return true;
195
+ if (Array.isArray(x) && x.length === 0) return true;
196
+ if (typeof x === "object" && Object.keys(x).length === 0) return true;
197
+ return false;
198
+ }
199
+ function log(message = "", { timestamp, level, data, req } = {}) {
200
+ let currentTime = timestamp ?? Date.now();
201
+ let error = null;
202
+ if (message instanceof Error) {
203
+ error = message;
204
+ message = error.message;
205
+ data = {
206
+ error,
207
+ data
208
+ };
209
+ if (!level) level = "error";
210
+ }
211
+ if (data instanceof Error) {
212
+ error = data;
213
+ if (data.message) message = `${message ? `${message} - ` : ""}${data.message}`;
214
+ if (!level) level = "error";
215
+ }
216
+ if (!level) level = "info";
217
+ let levelCode = ansiEscapeCodes[levelColors[level]] ?? "";
218
+ let requestTarget = req ? `${req.method} ${req.originalUrl}` : "";
219
+ let { startTime, id: sessionId } = req?.ctx ?? {};
220
+ console[level]();
221
+ console[level](levelCode + ansiEscapeCodes.dim + (0, dateshape.formatDate)(currentTime, "{isoDate} {isoTimeMs} {tz}") + (sessionId ? ` <${sessionId}>` : "") + (startTime === void 0 ? "" : ` +${(0, dateshape.formatDuration)(currentTime - startTime)}`) + ansiEscapeCodes.reset);
222
+ console[level](levelCode + requestTarget + (requestTarget && message && !message.startsWith("\n") ? " - " : "") + message + ansiEscapeCodes.reset);
223
+ if (!isEmpty(data)) console[level](levelCode + ansiEscapeCodes.dim + (typeof data === "string" ? data : JSON.stringify(data, null, 2)) + ansiEscapeCodes.reset);
224
+ if (error?.stack) console[level](levelCode + ansiEscapeCodes.dim + error.stack + ansiEscapeCodes.reset);
225
+ }
226
+
227
+ /**
228
+ * Initializes the request context on `req.ctx`.
229
+ */
230
+ const init = () => (req, res, next) => {
231
+ req.ctx = {
232
+ ...req.ctx,
233
+ id: (0, node_crypto.randomBytes)(16).toString("hex"),
234
+ nonce: (0, node_crypto.randomBytes)(8).toString("hex"),
235
+ startTime: Date.now()
236
+ };
237
+ emitLog(req.app, "Inited", {
238
+ req,
239
+ res
240
+ });
241
+ next();
242
+ };
243
+
244
+ const defaultLangCookieOptions = { maxAge: 90 * 864e5 };
245
+ const lang = ({ supportedLocales = [], shouldSetCookie = true, shouldRedirect = true, langCookieOptions = defaultLangCookieOptions } = {}) => {
246
+ let langSet = new Set(supportedLocales.map(toLanguage));
247
+ let localeSet = new Set(supportedLocales);
248
+ return (req, res, next) => {
249
+ let langParam = req.query.lang;
250
+ let lang = (Array.isArray(langParam) ? langParam[0] : langParam) ?? "";
251
+ if (localeSet.has(lang) || langSet.has(lang)) {
252
+ if (shouldSetCookie) {
253
+ emitLog(req.app, `Set lang cookie: ${JSON.stringify(lang)}`, {
254
+ req,
255
+ res
256
+ });
257
+ res.cookie("lang", lang, langCookieOptions);
258
+ }
259
+ if (shouldRedirect) {
260
+ let { originalUrl } = req;
261
+ let nextUrl = originalUrl.replace(/[?&]lang=[^&]+/g, "");
262
+ if (nextUrl !== originalUrl) {
263
+ emitLog(req.app, "Strip lang param and redirect", {
264
+ req,
265
+ res
266
+ });
267
+ res.redirect(nextUrl);
268
+ return;
269
+ }
270
+ }
271
+ }
272
+ let langCookie = shouldSetCookie ? req.cookies.lang : void 0;
273
+ let userAgentLocales = getLocales(req.get("accept-language"));
274
+ let effectiveLang = getEffectiveLocale(langCookie ? [langCookie, ...userAgentLocales] : userAgentLocales, supportedLocales);
275
+ if (req.ctx && effectiveLang) req.ctx.lang = effectiveLang;
276
+ emitLog(req.app, `Detected lang: ${JSON.stringify(effectiveLang)}`, {
277
+ req,
278
+ res,
279
+ data: {
280
+ userAgentLocales,
281
+ langCookie,
282
+ lang: effectiveLang
283
+ }
284
+ });
285
+ next();
286
+ };
287
+ };
288
+
289
+ function getStatusMessage(prefix, statusCode) {
290
+ return `${prefix} - [${statusCode}] ${node_http.STATUS_CODES[statusCode]}`;
291
+ }
292
+
293
+ /**
294
+ * Adds event handlers, like logging, to essential request phases.
295
+ */
296
+ const requestEvents = () => (req, res, next) => {
297
+ let finished = false;
298
+ res.on("finish", () => {
299
+ finished = true;
300
+ emitLog(req.app, getStatusMessage("Finished", res.statusCode), {
301
+ req,
302
+ res
303
+ });
304
+ });
305
+ res.on("close", () => {
306
+ if (!finished) emitLog(req.app, getStatusMessage("Closed", res.statusCode), {
307
+ req,
308
+ res
309
+ });
310
+ });
311
+ next();
312
+ };
313
+
314
+ const commonBuildOptions = {
315
+ format: "cjs",
316
+ jsx: "automatic",
317
+ jsxDev: process.env.NODE_ENV === "development",
318
+ loader: {
319
+ ".png": "dataurl",
320
+ ".svg": "dataurl",
321
+ ".html": "text",
322
+ ".txt": "text"
323
+ }
324
+ };
325
+
326
+ async function getEntries() {
327
+ let cwd = process.cwd();
328
+ try {
329
+ let list = await (0, node_fs_promises.readdir)((0, node_path.join)(cwd, "src/entries"));
330
+ return (await Promise.all(list.map(async (name) => {
331
+ return (await (0, node_fs_promises.lstat)((0, node_path.join)(cwd, "src/entries", name))).isDirectory() ? name : void 0;
332
+ }))).filter((dir) => dir !== void 0);
333
+ } catch {
334
+ return [];
335
+ }
336
+ }
337
+
338
+ const entryExtensions = [
339
+ "js",
340
+ "jsx",
341
+ "ts",
342
+ "tsx"
343
+ ];
344
+
345
+ async function getFirstAvailable(dirPath, path) {
346
+ let paths = Array.isArray(path) ? path : [path];
347
+ for (let filePath of paths) for (let ext of entryExtensions) {
348
+ let path = (0, node_path.join)(process.cwd(), dirPath, `${filePath}.${ext}`);
349
+ try {
350
+ await (0, node_fs_promises.access)(path);
351
+ return path;
352
+ } catch {}
353
+ }
354
+ }
355
+
356
+ async function getEntryPoints(path) {
357
+ let entries = await getEntries();
358
+ return (await Promise.all(entries.map(async (name) => {
359
+ let resolvedPath = await getFirstAvailable(`src/entries/${name}`, path);
360
+ return resolvedPath === void 0 ? void 0 : {
361
+ name,
362
+ path: resolvedPath
363
+ };
364
+ }))).filter((item) => item !== void 0);
365
+ }
366
+
367
+ /**
368
+ * Builds the client-side code from the 'src/entries/<entry_name>/ui'
369
+ * directories. The directories should preferrably be called 'ui' rather
370
+ * than client since their contents can also be used with the server-side
371
+ * rendering.
372
+ */
373
+ async function buildClient({ publicAssetsDir, watch, watchClient }, plugins) {
374
+ let clientEntries = await getEntryPoints(["ui/index"]);
375
+ let buildOptions = {
376
+ ...commonBuildOptions,
377
+ entryPoints: clientEntries.map(({ path }) => path),
378
+ bundle: true,
379
+ splitting: true,
380
+ format: "esm",
381
+ outdir: `${publicAssetsDir}/-`,
382
+ outbase: "src/entries",
383
+ minify: process.env.NODE_ENV !== "development",
384
+ plugins
385
+ };
386
+ if (watch || watchClient) {
387
+ let ctx = await esbuild.default.context(buildOptions);
388
+ await ctx.watch();
389
+ return async () => {
390
+ await ctx.dispose();
391
+ };
392
+ }
393
+ await esbuild.default.build(buildOptions);
394
+ }
395
+
396
+ function toImportPath(relativePath, referencePath = ".") {
397
+ let cwd = process.cwd();
398
+ let importPath = node_path.posix.join(...(0, node_path.relative)((0, node_path.join)(cwd, referencePath), relativePath).split(node_path.sep));
399
+ if (importPath && !/^\.+\//.test(importPath)) importPath = `./${importPath}`;
400
+ return importPath;
401
+ }
402
+
403
+ async function populateEntries() {
404
+ let serverEntries = await getEntryPoints(["server", "server/index"]);
405
+ let content = "";
406
+ if (serverEntries.length === 0) content = "export const entries = [];";
407
+ else {
408
+ content = "export const entries = (\n await Promise.all([";
409
+ for (let i = 0; i < serverEntries.length; i++) content += `\n // ${serverEntries[i].name}
410
+ import("${toImportPath(serverEntries[i].path, "src/server")}"),`;
411
+ content += "\n ])\n).map(({ server }) => server);";
412
+ }
413
+ await (0, node_fs_promises.writeFile)("src/server/entries.ts", `// Populated automatically during the build phase by picking
414
+ // all server exports from 'src/entries/<entry_name>/server(/index)?.(js|ts)'
415
+ ${content}
416
+ `);
417
+ }
418
+
419
+ async function buildServer({ targetDir, watch, watchServer }, plugins) {
420
+ await populateEntries();
421
+ let buildOptions = {
422
+ ...commonBuildOptions,
423
+ entryPoints: ["src/server/index.ts"],
424
+ bundle: true,
425
+ splitting: true,
426
+ outdir: `${targetDir}/server`,
427
+ platform: "node",
428
+ format: "esm",
429
+ packages: "external",
430
+ plugins
431
+ };
432
+ if (watch || watchServer) {
433
+ let ctx = await esbuild.default.context(buildOptions);
434
+ await ctx.watch();
435
+ return async () => {
436
+ await ctx.dispose();
437
+ };
438
+ }
439
+ await esbuild.default.build(buildOptions);
440
+ }
441
+
442
+ async function buildServerCSS({ targetDir, watch, watchServer }, plugins) {
443
+ let serverEntries = await getEntryPoints(["server", "server/index"]);
444
+ let buildOptions = {
445
+ ...commonBuildOptions,
446
+ entryPoints: serverEntries.map(({ name, path }) => ({
447
+ in: path,
448
+ out: name
449
+ })),
450
+ bundle: true,
451
+ splitting: false,
452
+ outdir: `${targetDir}/server-css`,
453
+ platform: "node",
454
+ format: "esm",
455
+ packages: "external",
456
+ plugins
457
+ };
458
+ if (watch || watchServer) {
459
+ let ctx = await esbuild.default.context(buildOptions);
460
+ await ctx.watch();
461
+ return async () => {
462
+ await ctx.dispose();
463
+ };
464
+ }
465
+ await esbuild.default.build(buildOptions);
466
+ }
467
+
468
+ function createPostbuildPlugins({ targetDir, publicAssetsDir }, onServerRebuild) {
469
+ return {
470
+ serverPlugins: [{
471
+ name: "skip-css",
472
+ setup(build) {
473
+ /** @see https://github.com/evanw/esbuild/issues/599#issuecomment-745118158 */
474
+ build.onLoad({ filter: /\.css$/ }, () => ({ contents: "" }));
475
+ }
476
+ }, {
477
+ name: "postbuild-server",
478
+ setup(build) {
479
+ build.onEnd(() => {
480
+ onServerRebuild();
481
+ });
482
+ }
483
+ }],
484
+ serverCSSPlugins: [{
485
+ name: "postbuild-server-css",
486
+ setup(build) {
487
+ build.onEnd(async () => {
488
+ let dir = `${targetDir}/server-css`;
489
+ try {
490
+ let files = (await (0, node_fs_promises.readdir)(dir)).filter((name) => name.endsWith(".css"));
491
+ if (files.length === 0) return;
492
+ await (0, node_fs_promises.mkdir)(`${publicAssetsDir}/-`, { recursive: true });
493
+ await Promise.all(files.map(async (name) => {
494
+ let dir = `${publicAssetsDir}/-/${name.slice(0, -4)}`;
495
+ await (0, node_fs_promises.mkdir)(dir, { recursive: true });
496
+ await (0, node_fs_promises.rename)(`${targetDir}/server-css/${name}`, `${dir}/index.css`);
497
+ }));
498
+ await (0, node_fs_promises.rm)(dir, {
499
+ recursive: true,
500
+ force: true
501
+ });
502
+ } catch {}
503
+ });
504
+ }
505
+ }]
506
+ };
507
+ }
508
+
509
+ async function build(params) {
510
+ let startTime = Date.now();
511
+ let log = params.silent ? () => {} : console.log;
512
+ log("Build started");
513
+ let serverProcess = null;
514
+ let inited = false;
515
+ function handleServerRebuild() {
516
+ if (serverProcess) {
517
+ serverProcess.kill();
518
+ serverProcess = null;
519
+ }
520
+ if (!inited) {
521
+ log(`Build completed +${(0, dateshape.formatDuration)(Date.now() - startTime)}`);
522
+ inited = true;
523
+ }
524
+ if (params.start) serverProcess = (0, node_child_process.spawn)("node", [`${params.targetDir}/server/index.js`], { stdio: "inherit" });
525
+ }
526
+ let { serverPlugins, serverCSSPlugins } = createPostbuildPlugins(params, handleServerRebuild);
527
+ await Promise.all([
528
+ buildServer(params, serverPlugins),
529
+ buildServerCSS(params, serverCSSPlugins),
530
+ buildClient(params)
531
+ ]);
532
+ }
533
+
534
+ const defaultTargetDir = "dist";
535
+ async function clean({ targetDir, publicAssetsDir }) {
536
+ let dirs = [
537
+ `${targetDir}/server`,
538
+ `${targetDir}/server-css`,
539
+ `${publicAssetsDir}/-`
540
+ ];
541
+ return Promise.all(dirs.map((dir) => (0, node_fs_promises.rm)(dir, {
542
+ recursive: true,
543
+ force: true
544
+ })));
545
+ }
546
+ async function cli(args = []) {
547
+ let publicAssetsDir = args[0];
548
+ let targetDir = args[1];
549
+ if (!publicAssetsDir || publicAssetsDir.startsWith("--")) throw new Error("Public assets directory is undefined");
550
+ if (!targetDir || targetDir.startsWith("--")) targetDir = defaultTargetDir;
551
+ let params = {
552
+ targetDir,
553
+ publicAssetsDir,
554
+ silent: args.includes("--silent"),
555
+ watch: args.includes("--watch"),
556
+ watchServer: args.includes("--watch-server"),
557
+ watchClient: args.includes("--watch-client"),
558
+ start: args.includes("--start")
559
+ };
560
+ if (args.includes("--clean-only")) {
561
+ await clean(params);
562
+ return;
563
+ }
564
+ if (args.includes("--clean")) await clean(params);
565
+ await build(params);
566
+ }
567
+
568
+ async function start(nodeEnv = "development", host) {
569
+ if (nodeEnv) process.env.NODE_ENV = nodeEnv;
570
+ if (host) {
571
+ let [hostname, port] = host.split(":");
572
+ if (hostname) process.env.APP_HOST = hostname;
573
+ if (port) process.env.APP_PORT = port;
574
+ }
575
+ await cli(nodeEnv === "development" ? [
576
+ "src/public",
577
+ "--clean",
578
+ "--start",
579
+ "--watch"
580
+ ] : [
581
+ "src/public",
582
+ "--clean",
583
+ "--start",
584
+ "--silent"
585
+ ]);
586
+ }
587
+
588
+ const renderStatus = async (req, res) => {
589
+ let { id, nonce } = req.ctx;
590
+ let statusText = `${res.statusCode} ${node_http.STATUS_CODES[res.statusCode]}`;
591
+ let date = `${(/* @__PURE__ */ new Date()).toISOString().replace(/T/, " ").replace(/Z$/, "")} UTC`;
592
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width"/><title>${statusText}</title><style${nonce ? ` nonce="${nonce}"` : ""}>body{text-align:center;}</style></head><body><h1>${statusText}</h1><hr/><p>` + (id ? `<code>ID: ${id}</code><br/>` : "") + `<code>${date}</code></p></body></html>`;
593
+ };
594
+
595
+ function createApp(callback) {
596
+ let app = (0, express.default)();
597
+ if (!app.events) app.events = new node_events.default();
598
+ let host = process.env.APP_HOST || "localhost";
599
+ let port = Number(process.env.APP_PORT) || 80;
600
+ let listen = () => {
601
+ app.listen(port, host, () => {
602
+ emitLog(app, `Server running at ${`http://${host}:${port}/`} (${`NODE_ENV=${process.env.NODE_ENV}`})`);
603
+ });
604
+ };
605
+ if (process.env.NODE_ENV === "development") app.events?.on("log", ({ message, ...payload }) => {
606
+ log(message, payload);
607
+ });
608
+ if (!app.renderStatus) app.renderStatus = renderStatus;
609
+ app.disable("x-powered-by");
610
+ app.use(init());
611
+ app.use(requestEvents());
612
+ let callbackResult = typeof callback === "function" ? callback() : null;
613
+ if (callbackResult instanceof Promise) callbackResult.then(listen);
614
+ else listen();
615
+ return app;
616
+ }
617
+
618
+ const cspNonce = (req) => {
619
+ return `'nonce-${req.ctx?.nonce}'`;
620
+ };
621
+
622
+ const injectNonce = (req, _res, { content }) => {
623
+ let { nonce } = req.ctx;
624
+ return nonce ? content.replace(/\{\{nonce\}\}/g, nonce) : content;
625
+ };
626
+
627
+ function serializeState(state) {
628
+ return JSON.stringify(state).replace(/</g, "\\x3c");
629
+ }
630
+
631
+ function servePipeableStream(req, res) {
632
+ return async ({ pipe }, error) => {
633
+ let statusCode = error ? 500 : 200;
634
+ emitLog(req.app, getStatusMessage("Stream", statusCode), {
635
+ level: error ? "error" : void 0,
636
+ req,
637
+ res,
638
+ data: error
639
+ });
640
+ res.status(statusCode);
641
+ if (statusCode >= 400) {
642
+ res.send(await req.app.renderStatus?.(req, res));
643
+ return;
644
+ }
645
+ res.set("Content-Type", "text/html");
646
+ pipe(res);
647
+ };
648
+ }
649
+
650
+ exports.build = build;
651
+ exports.cli = cli;
652
+ exports.createApp = createApp;
653
+ exports.cspNonce = cspNonce;
654
+ exports.dir = dir;
655
+ exports.emitLog = emitLog;
656
+ exports.getEffectiveLocale = getEffectiveLocale;
657
+ exports.getLocales = getLocales;
658
+ exports.getStatusMessage = getStatusMessage;
659
+ exports.init = init;
660
+ exports.injectNonce = injectNonce;
661
+ exports.lang = lang;
662
+ exports.log = log;
663
+ exports.renderStatus = renderStatus;
664
+ exports.requestEvents = requestEvents;
665
+ exports.resolveFilePath = resolveFilePath;
666
+ exports.serializeState = serializeState;
667
+ exports.servePipeableStream = servePipeableStream;
668
+ exports.start = start;
669
+ exports.toLanguage = toLanguage;
670
+ exports.unhandledError = unhandledError;
671
+ exports.unhandledRoute = unhandledRoute;