@stacksjs/buddy 0.70.225 → 0.70.226
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/dist/commands/serve.d.ts +6 -45
- package/dist/commands/serve.js +8 -240
- package/dist/production-server.d.ts +21 -0
- package/dist/production-server.js +242 -0
- package/dist/serve-entry.js +1 -1
- package/package.json +1 -1
package/dist/commands/serve.d.ts
CHANGED
|
@@ -1,49 +1,10 @@
|
|
|
1
|
+
import { startProductionServer } from '../production-server';
|
|
1
2
|
import type { CLI } from '@stacksjs/types';
|
|
2
|
-
/**
|
|
3
|
-
* Resolve the project's includes directory.
|
|
4
|
-
*
|
|
5
|
-
* A configured `config/stx.ts#partialsDir` wins outright. The convention list
|
|
6
|
-
* below is only a fallback for apps that never set one, and it cannot stand in
|
|
7
|
-
* for the config: the candidates are probed for existence in order, so an app
|
|
8
|
-
* that keeps its includes in `resources/components` but also has an unrelated
|
|
9
|
-
* `resources/partials` directory silently resolved to the wrong one and every
|
|
10
|
-
* `@include` failed with ENOENT at runtime.
|
|
11
|
-
*
|
|
12
|
-
* The configured value is relative to the stx root (`resources`), matching how
|
|
13
|
-
* stx itself reads it, but an app-root-relative path is accepted too so either
|
|
14
|
-
* spelling resolves.
|
|
15
|
-
*/
|
|
16
|
-
export declare function resolveUserPartialsPath(cwd?: unknown, configuredDir?: string): string | undefined;
|
|
17
|
-
/**
|
|
18
|
-
* Read `partialsDir` off the app's stx config. Failure is non-fatal: without
|
|
19
|
-
* it {@link resolveUserPartialsPath} falls back to the conventions.
|
|
20
|
-
*/
|
|
21
|
-
export declare function loadStxPartialsDir(cwd?: unknown): Promise<string | undefined>;
|
|
22
|
-
export declare function startProductionServer(options?: { port?: string | number, verbose?: boolean }): Promise<void>;
|
|
23
3
|
export declare function serve(buddy: CLI): void;
|
|
24
|
-
/**
|
|
25
|
-
* `buddy serve:api` — boot the production API server (bun-router routes).
|
|
26
|
-
*
|
|
27
|
-
* The twin of `buddy serve`: where that serves the STX frontend, this runs the
|
|
28
|
-
* loopback API the frontend proxies `/api` + non-GET requests to. The entry
|
|
29
|
-
* (`@stacksjs/actions/serve/api`) is resolved through the module graph, so it
|
|
30
|
-
* works whether the framework is vendored at `storage/framework/core` OR only
|
|
31
|
-
* installed under `node_modules/@stacksjs/actions`. This keeps deployments from
|
|
32
|
-
* having to hardcode a `storage/framework/core/...` path in their `start`
|
|
33
|
-
* command — `./buddy serve:api` resolves the framework wherever it lives.
|
|
34
|
-
*/
|
|
35
4
|
export declare function serveApi(buddy: CLI): void;
|
|
36
|
-
/**
|
|
37
|
-
* `buddy preview` — serve a finished static build locally.
|
|
38
|
-
*
|
|
39
|
-
* The twin of `buddy serve`, for the other kind of output: where that renders
|
|
40
|
-
* stx pages and proxies an API, this hands over pre-built files exactly as a
|
|
41
|
-
* static host would. It exists so checking a build before shipping it does not
|
|
42
|
-
* require a `preview.ts` in every project's root, hand-rolled per app and
|
|
43
|
-
* quietly drifting from how the host actually behaves.
|
|
44
|
-
*
|
|
45
|
-
* Extensionless paths resolve to `.html`, matching the "pretty URLs" behaviour
|
|
46
|
-
* of Netlify, Vercel and Cloudflare Pages, so a link that works here works
|
|
47
|
-
* there.
|
|
48
|
-
*/
|
|
49
5
|
export declare function preview(buddy: CLI): void;
|
|
6
|
+
export {
|
|
7
|
+
loadStxPartialsDir,
|
|
8
|
+
resolveUserPartialsPath,
|
|
9
|
+
startProductionServer,
|
|
10
|
+
} from '../production-server';
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,168 +1,14 @@
|
|
|
1
|
-
import { ExitCode } from "@stacksjs/types";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { join } from "node:path";
|
|
5
3
|
import process from "node:process";
|
|
6
4
|
import { log } from "@stacksjs/logging";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
url() {
|
|
15
|
-
const snapshot = globalThis.__stxServeContext;
|
|
16
|
-
return snapshot?.url || snapshot?.search || globalThis.__stxServeSearch || "";
|
|
17
|
-
},
|
|
18
|
-
search() {
|
|
19
|
-
return globalThis.__stxServeContext?.search ?? globalThis.__stxServeSearch ?? "";
|
|
20
|
-
},
|
|
21
|
-
locale() {
|
|
22
|
-
return globalThis.__stxServeContext?.locale ?? "en";
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
function parseCookies(req) {
|
|
26
|
-
const out = {}, header = req.headers.get("cookie") || "";
|
|
27
|
-
if (!header)
|
|
28
|
-
return out;
|
|
29
|
-
for (const part of header.split(";")) {
|
|
30
|
-
const trimmed = part.trim(), eq = trimmed.indexOf("=");
|
|
31
|
-
if (eq === -1)
|
|
32
|
-
continue;
|
|
33
|
-
const k = trimmed.slice(0, eq).trim(), v = trimmed.slice(eq + 1).trim();
|
|
34
|
-
if (!k)
|
|
35
|
-
continue;
|
|
36
|
-
try {
|
|
37
|
-
out[k] = decodeURIComponent(v);
|
|
38
|
-
} catch {
|
|
39
|
-
out[k] = v;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return out;
|
|
43
|
-
}
|
|
44
|
-
export function resolveUserPartialsPath(cwd = process.cwd(), configuredDir) {
|
|
45
|
-
if (configuredDir) {
|
|
46
|
-
const configured = [
|
|
47
|
-
join(cwd, "resources", configuredDir),
|
|
48
|
-
join(cwd, configuredDir)
|
|
49
|
-
].find((candidate) => existsSync(candidate));
|
|
50
|
-
if (configured)
|
|
51
|
-
return configured;
|
|
52
|
-
}
|
|
53
|
-
const existing = [
|
|
54
|
-
"resources/partials",
|
|
55
|
-
"resources/views/partials",
|
|
56
|
-
"partials",
|
|
57
|
-
"resources/components"
|
|
58
|
-
].filter((candidate) => existsSync(join(cwd, candidate)));
|
|
59
|
-
if (existing.length === 0)
|
|
60
|
-
return;
|
|
61
|
-
const populated = existing.find((candidate) => containsTemplates(join(cwd, candidate)));
|
|
62
|
-
return join(cwd, populated ?? existing[0]);
|
|
63
|
-
}
|
|
64
|
-
function containsTemplates(dir) {
|
|
65
|
-
try {
|
|
66
|
-
return [...new Bun.Glob("**/*.stx").scanSync({ cwd: dir, onlyFiles: !0 })].length > 0;
|
|
67
|
-
} catch {
|
|
68
|
-
return !1;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
export async function loadStxPartialsDir(cwd = process.cwd()) {
|
|
72
|
-
const configPath = join(cwd, "config/stx.ts");
|
|
73
|
-
if (!existsSync(configPath))
|
|
74
|
-
return;
|
|
75
|
-
try {
|
|
76
|
-
const dir = (await import(configPath)).default?.partialsDir;
|
|
77
|
-
return typeof dir === "string" && dir.length > 0 ? dir : void 0;
|
|
78
|
-
} catch {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function resolveCsrfMiddlewarePath() {
|
|
83
|
-
const rel = "app/Middleware/Csrf.ts", vendored = join(process.cwd(), "storage/framework/defaults", rel);
|
|
84
|
-
if (existsSync(vendored))
|
|
85
|
-
return vendored;
|
|
86
|
-
try {
|
|
87
|
-
const pkgJson = Bun.resolveSync("@stacksjs/defaults/package.json", process.cwd());
|
|
88
|
-
return `${pkgJson.slice(0, pkgJson.lastIndexOf("/"))}/${rel}`;
|
|
89
|
-
} catch {
|
|
90
|
-
return vendored;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
export async function startProductionServer(options) {
|
|
94
|
-
if (options?.port)
|
|
95
|
-
process.env.PORT = String(options.port);
|
|
96
|
-
process.env.APP_ENV = process.env.APP_ENV || "production";
|
|
97
|
-
const port = Number(process.env.PORT) || 3000, { config, overridesReady } = await import("@stacksjs/config");
|
|
98
|
-
await overridesReady;
|
|
99
|
-
const { injectGlobalAutoImports } = await import("@stacksjs/server");
|
|
100
|
-
await injectGlobalAutoImports();
|
|
101
|
-
let stxServe;
|
|
102
|
-
const serveCandidates = [
|
|
103
|
-
join(homedir(), "Code/Tools/stx/packages/bun-plugin/dist/serve.js"),
|
|
104
|
-
join(process.cwd(), "pantry/bun-plugin-stx/dist/serve.js")
|
|
105
|
-
];
|
|
106
|
-
for (const entry of serveCandidates)
|
|
107
|
-
try {
|
|
108
|
-
if (existsSync(entry)) {
|
|
109
|
-
({ serve: stxServe } = await import(entry));
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
} catch {}
|
|
113
|
-
if (!stxServe)
|
|
114
|
-
({ serve: stxServe } = await import("bun-plugin-stx/serve"));
|
|
115
|
-
const stxModule = await resolveVendoredStxModule(), { site: siteConfig, i18n: i18nConfig } = await loadStxSiteConfig(), userViewsPath = "resources/views", defaultsResources = resolveDefaultsResources(), defaultViewsPath = join(defaultsResources, "views"), userLayoutsPath = existsSync("resources/views/layouts") ? "resources/views/layouts" : "resources/layouts", userPartialsPath = resolveUserPartialsPath(process.cwd(), await loadStxPartialsDir()), apiBase = process.env.API_URL || `http://127.0.0.1:${Number(process.env.PORT_API) || config.ports?.api || 3008}`;
|
|
116
|
-
log.info(`Starting production server on port ${port}...`);
|
|
117
|
-
await stxServe({
|
|
118
|
-
patterns: [userViewsPath, defaultViewsPath],
|
|
119
|
-
port,
|
|
120
|
-
autoIncrementPort: !1,
|
|
121
|
-
reusePort: ["production", "staging", "development"].includes((process.env.APP_ENV || "").toLowerCase()),
|
|
122
|
-
componentsDir: join(defaultsResources, "components"),
|
|
123
|
-
layoutsDir: userLayoutsPath,
|
|
124
|
-
...userPartialsPath && { partialsDir: userPartialsPath },
|
|
125
|
-
fallbackLayoutsDir: join(defaultsResources, "layouts"),
|
|
126
|
-
fallbackPartialsDir: defaultViewsPath,
|
|
127
|
-
quiet: options?.verbose !== !0,
|
|
128
|
-
...stxModule && { stxModule },
|
|
129
|
-
...i18nConfig && { i18n: i18nConfig },
|
|
130
|
-
...siteConfig?.url && { site: siteConfig },
|
|
131
|
-
onRequest: async (req) => {
|
|
132
|
-
const { maintenanceGate, isApiBoundRequest, proxyToBackend } = await import("@stacksjs/server"), gated = await maintenanceGate(req);
|
|
133
|
-
if (gated)
|
|
134
|
-
return gated;
|
|
135
|
-
const url = new URL(req.url);
|
|
136
|
-
if (isApiBoundRequest(req, url.pathname))
|
|
137
|
-
try {
|
|
138
|
-
return await proxyToBackend(req, apiBase);
|
|
139
|
-
} catch (error) {
|
|
140
|
-
log.error(`API proxy to ${apiBase} failed: ${error.message}`);
|
|
141
|
-
return new Response("Bad Gateway", { status: 502 });
|
|
142
|
-
}
|
|
143
|
-
if (existsSync(join(process.cwd(), "resources/views/blog.stx"))) {
|
|
144
|
-
const { renderBlogFeed } = await import("@stacksjs/actions/blog"), feed = await renderBlogFeed(req);
|
|
145
|
-
if (feed)
|
|
146
|
-
return feed;
|
|
147
|
-
}
|
|
148
|
-
globalThis.__stxServeSearch = url.search;
|
|
149
|
-
globalThis.__stxServeCookies = parseCookies(req);
|
|
150
|
-
return;
|
|
151
|
-
},
|
|
152
|
-
onResponse: async (req, response) => {
|
|
153
|
-
const method = req.method.toUpperCase();
|
|
154
|
-
if (method !== "GET" && method !== "HEAD")
|
|
155
|
-
return;
|
|
156
|
-
try {
|
|
157
|
-
const { seedCsrfCookieIfMissing } = await import(resolveCsrfMiddlewarePath());
|
|
158
|
-
return seedCsrfCookieIfMissing(req, response);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
log.debug(`CSRF cookie seeding skipped: ${error.message}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
log.success(`Production server listening on http://0.0.0.0:${port}`);
|
|
165
|
-
}
|
|
5
|
+
import { ExitCode } from "@stacksjs/types";
|
|
6
|
+
import { startProductionServer } from "../production-server";
|
|
7
|
+
export {
|
|
8
|
+
loadStxPartialsDir,
|
|
9
|
+
resolveUserPartialsPath,
|
|
10
|
+
startProductionServer
|
|
11
|
+
} from "../production-server";
|
|
166
12
|
export function serve(buddy) {
|
|
167
13
|
buddy.command("serve", "Start the production HTTP server (STX views + /api proxy + coming-soon/maintenance gate)").option("-p, --port <port>", "Port to listen on (defaults to PORT env or 3000)").option("--verbose", "Enable verbose output", { default: !1 }).action(startProductionServer);
|
|
168
14
|
}
|
|
@@ -180,84 +26,6 @@ function resolveApiEntry() {
|
|
|
180
26
|
return vendored;
|
|
181
27
|
return "@stacksjs/actions/serve/api";
|
|
182
28
|
}
|
|
183
|
-
function resolveDefaultsResources() {
|
|
184
|
-
const vendored = "storage/framework/defaults/resources";
|
|
185
|
-
if (existsSync(vendored))
|
|
186
|
-
return vendored;
|
|
187
|
-
try {
|
|
188
|
-
const pkgJson = Bun.resolveSync("@stacksjs/defaults/package.json", process.cwd());
|
|
189
|
-
return join(dirname(pkgJson), "resources");
|
|
190
|
-
} catch {
|
|
191
|
-
return vendored;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async function resolveVendoredStxModule() {
|
|
195
|
-
const candidates = [
|
|
196
|
-
join(homedir(), "Code/Tools/stx/packages/stx/dist/index.js"),
|
|
197
|
-
join(process.cwd(), "pantry/@stacksjs/stx/dist/index.js")
|
|
198
|
-
];
|
|
199
|
-
for (const entry of candidates)
|
|
200
|
-
try {
|
|
201
|
-
if (existsSync(entry))
|
|
202
|
-
return await import(entry);
|
|
203
|
-
} catch {}
|
|
204
|
-
try {
|
|
205
|
-
return await import("@stacksjs/stx");
|
|
206
|
-
} catch {}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
function fallbackI18nFromSite(site) {
|
|
210
|
-
const locales = site.i18n.locales, defaultLocale = site.i18n.defaultLocale ?? locales[0];
|
|
211
|
-
return {
|
|
212
|
-
locales,
|
|
213
|
-
defaultLocale,
|
|
214
|
-
labels: site.i18n.labels ?? Object.fromEntries(locales.map((c) => [c, c.toUpperCase()])),
|
|
215
|
-
translations: {},
|
|
216
|
-
pickerSelector: site.i18n.pickerSelector ?? "#lang-picker"
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
async function resolveSiteI18n(site) {
|
|
220
|
-
const resolverPaths = [
|
|
221
|
-
join(homedir(), "Code/Tools/stx/packages/stx/src/site-builder/i18n.ts"),
|
|
222
|
-
join(homedir(), "Code/Tools/stx/packages/stx/dist/index.js"),
|
|
223
|
-
join(process.cwd(), "pantry/@stacksjs/stx/dist/index.js")
|
|
224
|
-
];
|
|
225
|
-
for (const resolverPath of resolverPaths)
|
|
226
|
-
try {
|
|
227
|
-
if (!existsSync(resolverPath))
|
|
228
|
-
continue;
|
|
229
|
-
const resolved = await import(resolverPath);
|
|
230
|
-
if (typeof resolved.resolveI18n !== "function")
|
|
231
|
-
continue;
|
|
232
|
-
const i18n = resolved.resolveI18n(site, process.cwd());
|
|
233
|
-
if (i18n)
|
|
234
|
-
return i18n;
|
|
235
|
-
} catch {}
|
|
236
|
-
try {
|
|
237
|
-
const resolved = await import("@stacksjs/stx");
|
|
238
|
-
if (typeof resolved.resolveI18n === "function") {
|
|
239
|
-
const i18n = resolved.resolveI18n(site, process.cwd());
|
|
240
|
-
if (i18n)
|
|
241
|
-
return i18n;
|
|
242
|
-
}
|
|
243
|
-
} catch {}
|
|
244
|
-
return fallbackI18nFromSite(site);
|
|
245
|
-
}
|
|
246
|
-
async function loadStxSiteConfig() {
|
|
247
|
-
const sitePath = join(process.cwd(), "site.config.ts");
|
|
248
|
-
if (!existsSync(sitePath))
|
|
249
|
-
return {};
|
|
250
|
-
try {
|
|
251
|
-
const site = (await import(sitePath)).default;
|
|
252
|
-
if (!site)
|
|
253
|
-
return {};
|
|
254
|
-
if (!site.i18n)
|
|
255
|
-
return { site };
|
|
256
|
-
const i18n = await resolveSiteI18n(site);
|
|
257
|
-
return { site, i18n };
|
|
258
|
-
} catch {}
|
|
259
|
-
return {};
|
|
260
|
-
}
|
|
261
29
|
export function preview(buddy) {
|
|
262
30
|
buddy.command("preview [dir]", "Serve a static build locally, the way a static host would").option("-p, --port <port>", "Port to listen on", { default: "3001" }).example("buddy preview").example("buddy preview dist --port 4000").action(async (dir, options) => {
|
|
263
31
|
const root = dir || "dist", port = Number(options?.port) || 3001;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the project's includes directory.
|
|
3
|
+
*
|
|
4
|
+
* A configured `config/stx.ts#partialsDir` wins outright. The convention list
|
|
5
|
+
* below is only a fallback for apps that never set one, and it cannot stand in
|
|
6
|
+
* for the config: the candidates are probed for existence in order, so an app
|
|
7
|
+
* that keeps its includes in `resources/components` but also has an unrelated
|
|
8
|
+
* `resources/partials` directory silently resolved to the wrong one and every
|
|
9
|
+
* `@include` failed with ENOENT at runtime.
|
|
10
|
+
*
|
|
11
|
+
* The configured value is relative to the stx root (`resources`), matching how
|
|
12
|
+
* stx itself reads it, but an app-root-relative path is accepted too so either
|
|
13
|
+
* spelling resolves.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveUserPartialsPath(cwd?: unknown, configuredDir?: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Read `partialsDir` off the app's stx config. Failure is non-fatal: without
|
|
18
|
+
* it {@link resolveUserPartialsPath} falls back to the conventions.
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadStxPartialsDir(cwd?: unknown): Promise<string | undefined>;
|
|
21
|
+
export declare function startProductionServer(options?: { port?: string | number, verbose?: boolean }): Promise<void>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { log } from "@stacksjs/logging";
|
|
6
|
+
globalThis.requestContext = {
|
|
7
|
+
cookie(name) {
|
|
8
|
+
const snapshot = globalThis.__stxServeContext;
|
|
9
|
+
if (snapshot?.cookies && name in snapshot.cookies)
|
|
10
|
+
return snapshot.cookies[name] ?? null;
|
|
11
|
+
return globalThis.__stxServeCookies?.[name] ?? null;
|
|
12
|
+
},
|
|
13
|
+
url() {
|
|
14
|
+
const snapshot = globalThis.__stxServeContext;
|
|
15
|
+
return snapshot?.url || snapshot?.search || globalThis.__stxServeSearch || "";
|
|
16
|
+
},
|
|
17
|
+
search() {
|
|
18
|
+
return globalThis.__stxServeContext?.search ?? globalThis.__stxServeSearch ?? "";
|
|
19
|
+
},
|
|
20
|
+
locale() {
|
|
21
|
+
return globalThis.__stxServeContext?.locale ?? "en";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function parseCookies(req) {
|
|
25
|
+
const out = {}, header = req.headers.get("cookie") || "";
|
|
26
|
+
if (!header)
|
|
27
|
+
return out;
|
|
28
|
+
for (const part of header.split(";")) {
|
|
29
|
+
const trimmed = part.trim(), eq = trimmed.indexOf("=");
|
|
30
|
+
if (eq === -1)
|
|
31
|
+
continue;
|
|
32
|
+
const k = trimmed.slice(0, eq).trim(), v = trimmed.slice(eq + 1).trim();
|
|
33
|
+
if (!k)
|
|
34
|
+
continue;
|
|
35
|
+
try {
|
|
36
|
+
out[k] = decodeURIComponent(v);
|
|
37
|
+
} catch {
|
|
38
|
+
out[k] = v;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
export function resolveUserPartialsPath(cwd = process.cwd(), configuredDir) {
|
|
44
|
+
if (configuredDir) {
|
|
45
|
+
const configured = [
|
|
46
|
+
join(cwd, "resources", configuredDir),
|
|
47
|
+
join(cwd, configuredDir)
|
|
48
|
+
].find((candidate) => existsSync(candidate));
|
|
49
|
+
if (configured)
|
|
50
|
+
return configured;
|
|
51
|
+
}
|
|
52
|
+
const existing = [
|
|
53
|
+
"resources/partials",
|
|
54
|
+
"resources/views/partials",
|
|
55
|
+
"partials",
|
|
56
|
+
"resources/components"
|
|
57
|
+
].filter((candidate) => existsSync(join(cwd, candidate)));
|
|
58
|
+
if (existing.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
const populated = existing.find((candidate) => containsTemplates(join(cwd, candidate)));
|
|
61
|
+
return join(cwd, populated ?? existing[0]);
|
|
62
|
+
}
|
|
63
|
+
function containsTemplates(dir) {
|
|
64
|
+
try {
|
|
65
|
+
return [...new Bun.Glob("**/*.stx").scanSync({ cwd: dir, onlyFiles: !0 })].length > 0;
|
|
66
|
+
} catch {
|
|
67
|
+
return !1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function loadStxPartialsDir(cwd = process.cwd()) {
|
|
71
|
+
const configPath = join(cwd, "config/stx.ts");
|
|
72
|
+
if (!existsSync(configPath))
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
const dir = (await import(configPath)).default?.partialsDir;
|
|
76
|
+
return typeof dir === "string" && dir.length > 0 ? dir : void 0;
|
|
77
|
+
} catch {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function resolveCsrfMiddlewarePath() {
|
|
82
|
+
const rel = "app/Middleware/Csrf.ts", vendored = join(process.cwd(), "storage/framework/defaults", rel);
|
|
83
|
+
if (existsSync(vendored))
|
|
84
|
+
return vendored;
|
|
85
|
+
try {
|
|
86
|
+
const pkgJson = Bun.resolveSync("@stacksjs/defaults/package.json", process.cwd());
|
|
87
|
+
return `${pkgJson.slice(0, pkgJson.lastIndexOf("/"))}/${rel}`;
|
|
88
|
+
} catch {
|
|
89
|
+
return vendored;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function startProductionServer(options) {
|
|
93
|
+
if (options?.port)
|
|
94
|
+
process.env.PORT = String(options.port);
|
|
95
|
+
process.env.APP_ENV = process.env.APP_ENV || "production";
|
|
96
|
+
const port = Number(process.env.PORT) || 3000, { config, overridesReady } = await import("@stacksjs/config");
|
|
97
|
+
await overridesReady;
|
|
98
|
+
const { injectGlobalAutoImports } = await import("@stacksjs/server");
|
|
99
|
+
await injectGlobalAutoImports();
|
|
100
|
+
let stxServe;
|
|
101
|
+
const serveCandidates = [
|
|
102
|
+
join(homedir(), "Code/Tools/stx/packages/bun-plugin/dist/serve.js"),
|
|
103
|
+
join(process.cwd(), "pantry/bun-plugin-stx/dist/serve.js")
|
|
104
|
+
];
|
|
105
|
+
for (const entry of serveCandidates)
|
|
106
|
+
try {
|
|
107
|
+
if (existsSync(entry)) {
|
|
108
|
+
({ serve: stxServe } = await import(entry));
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
if (!stxServe)
|
|
113
|
+
({ serve: stxServe } = await import("bun-plugin-stx/serve"));
|
|
114
|
+
const stxModule = await resolveVendoredStxModule(), { site: siteConfig, i18n: i18nConfig } = await loadStxSiteConfig(), userViewsPath = "resources/views", defaultsResources = resolveDefaultsResources(), defaultViewsPath = join(defaultsResources, "views"), userLayoutsPath = existsSync("resources/views/layouts") ? "resources/views/layouts" : "resources/layouts", userPartialsPath = resolveUserPartialsPath(process.cwd(), await loadStxPartialsDir()), apiBase = process.env.API_URL || `http://127.0.0.1:${Number(process.env.PORT_API) || config.ports?.api || 3008}`;
|
|
115
|
+
log.info(`Starting production server on port ${port}...`);
|
|
116
|
+
await stxServe({
|
|
117
|
+
patterns: [userViewsPath, defaultViewsPath],
|
|
118
|
+
port,
|
|
119
|
+
autoIncrementPort: !1,
|
|
120
|
+
reusePort: ["production", "staging", "development"].includes((process.env.APP_ENV || "").toLowerCase()),
|
|
121
|
+
componentsDir: join(defaultsResources, "components"),
|
|
122
|
+
layoutsDir: userLayoutsPath,
|
|
123
|
+
...userPartialsPath && { partialsDir: userPartialsPath },
|
|
124
|
+
fallbackLayoutsDir: join(defaultsResources, "layouts"),
|
|
125
|
+
fallbackPartialsDir: defaultViewsPath,
|
|
126
|
+
quiet: options?.verbose !== !0,
|
|
127
|
+
...stxModule && { stxModule },
|
|
128
|
+
...i18nConfig && { i18n: i18nConfig },
|
|
129
|
+
...siteConfig?.url && { site: siteConfig },
|
|
130
|
+
onRequest: async (req) => {
|
|
131
|
+
const { maintenanceGate, isApiBoundRequest, proxyToBackend } = await import("@stacksjs/server"), gated = await maintenanceGate(req);
|
|
132
|
+
if (gated)
|
|
133
|
+
return gated;
|
|
134
|
+
const url = new URL(req.url);
|
|
135
|
+
if (isApiBoundRequest(req, url.pathname))
|
|
136
|
+
try {
|
|
137
|
+
return await proxyToBackend(req, apiBase);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
log.error(`API proxy to ${apiBase} failed: ${error.message}`);
|
|
140
|
+
return new Response("Bad Gateway", { status: 502 });
|
|
141
|
+
}
|
|
142
|
+
if (existsSync(join(process.cwd(), "resources/views/blog.stx"))) {
|
|
143
|
+
const { renderBlogFeed } = await import("@stacksjs/actions/blog"), feed = await renderBlogFeed(req);
|
|
144
|
+
if (feed)
|
|
145
|
+
return feed;
|
|
146
|
+
}
|
|
147
|
+
globalThis.__stxServeSearch = url.search;
|
|
148
|
+
globalThis.__stxServeCookies = parseCookies(req);
|
|
149
|
+
return;
|
|
150
|
+
},
|
|
151
|
+
onResponse: async (req, response) => {
|
|
152
|
+
const method = req.method.toUpperCase();
|
|
153
|
+
if (method !== "GET" && method !== "HEAD")
|
|
154
|
+
return;
|
|
155
|
+
try {
|
|
156
|
+
const { seedCsrfCookieIfMissing } = await import(resolveCsrfMiddlewarePath());
|
|
157
|
+
return seedCsrfCookieIfMissing(req, response);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
log.debug(`CSRF cookie seeding skipped: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
log.success(`Production server listening on http://0.0.0.0:${port}`);
|
|
164
|
+
}
|
|
165
|
+
function resolveDefaultsResources() {
|
|
166
|
+
const vendored = "storage/framework/defaults/resources";
|
|
167
|
+
if (existsSync(vendored))
|
|
168
|
+
return vendored;
|
|
169
|
+
try {
|
|
170
|
+
const pkgJson = Bun.resolveSync("@stacksjs/defaults/package.json", process.cwd());
|
|
171
|
+
return join(dirname(pkgJson), "resources");
|
|
172
|
+
} catch {
|
|
173
|
+
return vendored;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function resolveVendoredStxModule() {
|
|
177
|
+
const candidates = [
|
|
178
|
+
join(homedir(), "Code/Tools/stx/packages/stx/dist/index.js"),
|
|
179
|
+
join(process.cwd(), "pantry/@stacksjs/stx/dist/index.js")
|
|
180
|
+
];
|
|
181
|
+
for (const entry of candidates)
|
|
182
|
+
try {
|
|
183
|
+
if (existsSync(entry))
|
|
184
|
+
return await import(entry);
|
|
185
|
+
} catch {}
|
|
186
|
+
try {
|
|
187
|
+
return await import("@stacksjs/stx");
|
|
188
|
+
} catch {}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
function fallbackI18nFromSite(site) {
|
|
192
|
+
const locales = site.i18n.locales, defaultLocale = site.i18n.defaultLocale ?? locales[0];
|
|
193
|
+
return {
|
|
194
|
+
locales,
|
|
195
|
+
defaultLocale,
|
|
196
|
+
labels: site.i18n.labels ?? Object.fromEntries(locales.map((c) => [c, c.toUpperCase()])),
|
|
197
|
+
translations: {},
|
|
198
|
+
pickerSelector: site.i18n.pickerSelector ?? "#lang-picker"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function resolveSiteI18n(site) {
|
|
202
|
+
const resolverPaths = [
|
|
203
|
+
join(homedir(), "Code/Tools/stx/packages/stx/src/site-builder/i18n.ts"),
|
|
204
|
+
join(homedir(), "Code/Tools/stx/packages/stx/dist/index.js"),
|
|
205
|
+
join(process.cwd(), "pantry/@stacksjs/stx/dist/index.js")
|
|
206
|
+
];
|
|
207
|
+
for (const resolverPath of resolverPaths)
|
|
208
|
+
try {
|
|
209
|
+
if (!existsSync(resolverPath))
|
|
210
|
+
continue;
|
|
211
|
+
const resolved = await import(resolverPath);
|
|
212
|
+
if (typeof resolved.resolveI18n !== "function")
|
|
213
|
+
continue;
|
|
214
|
+
const i18n = resolved.resolveI18n(site, process.cwd());
|
|
215
|
+
if (i18n)
|
|
216
|
+
return i18n;
|
|
217
|
+
} catch {}
|
|
218
|
+
try {
|
|
219
|
+
const resolved = await import("@stacksjs/stx");
|
|
220
|
+
if (typeof resolved.resolveI18n === "function") {
|
|
221
|
+
const i18n = resolved.resolveI18n(site, process.cwd());
|
|
222
|
+
if (i18n)
|
|
223
|
+
return i18n;
|
|
224
|
+
}
|
|
225
|
+
} catch {}
|
|
226
|
+
return fallbackI18nFromSite(site);
|
|
227
|
+
}
|
|
228
|
+
async function loadStxSiteConfig() {
|
|
229
|
+
const sitePath = join(process.cwd(), "site.config.ts");
|
|
230
|
+
if (!existsSync(sitePath))
|
|
231
|
+
return {};
|
|
232
|
+
try {
|
|
233
|
+
const site = (await import(sitePath)).default;
|
|
234
|
+
if (!site)
|
|
235
|
+
return {};
|
|
236
|
+
if (!site.i18n)
|
|
237
|
+
return { site };
|
|
238
|
+
const i18n = await resolveSiteI18n(site);
|
|
239
|
+
return { site, i18n };
|
|
240
|
+
} catch {}
|
|
241
|
+
return {};
|
|
242
|
+
}
|
package/dist/serve-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import { startProductionServer } from "./
|
|
2
|
+
import { startProductionServer } from "./production-server";
|
|
3
3
|
process.env.APP_ENV ||= "production";
|
|
4
4
|
process.env.NODE_ENV ||= "production";
|
|
5
5
|
await startProductionServer();
|