bun-plugin-stx 0.2.10 → 0.2.12
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/README.md +13 -11
- package/dist/{chunk-a3qganyh.js → chunk-5npmwyts.js} +17 -18
- package/dist/chunk-762q71t6.js +1296 -0
- package/dist/index.js +1 -1
- package/dist/preload.js +1 -2
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +400 -151
- package/dist/watch.d.ts +2 -1
- package/package.json +7 -7
- package/dist/chunk-fj6fej0j.js +0 -12771
package/dist/serve.js
CHANGED
|
@@ -5064,9 +5064,39 @@ var defaultStxConfig = {
|
|
|
5064
5064
|
partialsDir: "partials",
|
|
5065
5065
|
componentsDir: "components",
|
|
5066
5066
|
layoutsDir: "layouts",
|
|
5067
|
+
publicDir: "public",
|
|
5067
5068
|
ssr: true,
|
|
5068
5069
|
defaultTitle: "stx App"
|
|
5069
5070
|
};
|
|
5071
|
+
var staticContentTypes = {
|
|
5072
|
+
css: "text/css",
|
|
5073
|
+
js: "application/javascript",
|
|
5074
|
+
mjs: "application/javascript",
|
|
5075
|
+
json: "application/json",
|
|
5076
|
+
html: "text/html; charset=utf-8",
|
|
5077
|
+
txt: "text/plain",
|
|
5078
|
+
xml: "application/xml",
|
|
5079
|
+
pdf: "application/pdf",
|
|
5080
|
+
png: "image/png",
|
|
5081
|
+
jpg: "image/jpeg",
|
|
5082
|
+
jpeg: "image/jpeg",
|
|
5083
|
+
gif: "image/gif",
|
|
5084
|
+
webp: "image/webp",
|
|
5085
|
+
avif: "image/avif",
|
|
5086
|
+
svg: "image/svg+xml",
|
|
5087
|
+
ico: "image/x-icon",
|
|
5088
|
+
bmp: "image/bmp",
|
|
5089
|
+
mp4: "video/mp4",
|
|
5090
|
+
webm: "video/webm",
|
|
5091
|
+
ogg: "audio/ogg",
|
|
5092
|
+
mp3: "audio/mpeg",
|
|
5093
|
+
wav: "audio/wav",
|
|
5094
|
+
woff: "font/woff",
|
|
5095
|
+
woff2: "font/woff2",
|
|
5096
|
+
ttf: "font/ttf",
|
|
5097
|
+
otf: "font/otf",
|
|
5098
|
+
eot: "application/vnd.ms-fontobject"
|
|
5099
|
+
};
|
|
5070
5100
|
async function serve(options) {
|
|
5071
5101
|
const stxConfig = await loadConfig5({
|
|
5072
5102
|
name: "stx",
|
|
@@ -5076,6 +5106,7 @@ async function serve(options) {
|
|
|
5076
5106
|
const componentsDir = options.componentsDir ?? stxConfig.componentsDir ?? defaultStxConfig.componentsDir;
|
|
5077
5107
|
const layoutsDir = options.layoutsDir ?? stxConfig.layoutsDir ?? defaultStxConfig.layoutsDir;
|
|
5078
5108
|
const partialsDir = options.partialsDir ?? stxConfig.partialsDir ?? defaultStxConfig.partialsDir;
|
|
5109
|
+
const publicDir = options.publicDir ?? stxConfig.publicDir ?? "public";
|
|
5079
5110
|
const { patterns, port = 3456 } = options;
|
|
5080
5111
|
if (patterns.length === 0) {
|
|
5081
5112
|
console.error("Usage: serve <files...> [--port 3000]");
|
|
@@ -5103,10 +5134,17 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5103
5134
|
const fs = await import("fs/promises");
|
|
5104
5135
|
const stat3 = await fs.stat(pattern).catch(() => null);
|
|
5105
5136
|
if (stat3?.isDirectory()) {
|
|
5137
|
+
const excludeDirs = ["layouts", "components", "partials"];
|
|
5138
|
+
if (layoutsDir)
|
|
5139
|
+
excludeDirs.push(layoutsDir.replace(/^.*\//, ""));
|
|
5140
|
+
if (componentsDir)
|
|
5141
|
+
excludeDirs.push(componentsDir.replace(/^.*\//, ""));
|
|
5142
|
+
if (partialsDir)
|
|
5143
|
+
excludeDirs.push(partialsDir.replace(/^.*\//, ""));
|
|
5106
5144
|
for (const ext of [".stx", ".md", ".html"]) {
|
|
5107
5145
|
const glob = new Glob(`**/*${ext}`);
|
|
5108
5146
|
const discovered = await Array.fromAsync(glob.scan({ cwd: pattern, followSymlinks: true }));
|
|
5109
|
-
files.push(...discovered.map((f) => `${pattern}/${f}`.replace(/\/+/g, "/")));
|
|
5147
|
+
files.push(...discovered.filter((f) => !excludeDirs.some((dir) => f.startsWith(`${dir}/`))).map((f) => `${pattern}/${f}`.replace(/\/+/g, "/")));
|
|
5110
5148
|
}
|
|
5111
5149
|
} else if (pattern.includes("*")) {
|
|
5112
5150
|
const glob = new Glob(pattern);
|
|
@@ -5121,6 +5159,12 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5121
5159
|
}
|
|
5122
5160
|
}
|
|
5123
5161
|
sourceFiles = files;
|
|
5162
|
+
try {
|
|
5163
|
+
const { Router } = await import("./chunk-762q71t6.js");
|
|
5164
|
+
const pagesDir = patterns[0]?.replace(/\/$/, "") || "pages";
|
|
5165
|
+
const router = new Router(process13.cwd(), { pagesDir });
|
|
5166
|
+
console.log(`[stx] Generated ${router.routes.length} routes \u2192 .stx/routes.ts`);
|
|
5167
|
+
} catch (e) {}
|
|
5124
5168
|
return files;
|
|
5125
5169
|
}
|
|
5126
5170
|
async function ensureAssets() {
|
|
@@ -5145,7 +5189,7 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5145
5189
|
return crosswindModule;
|
|
5146
5190
|
crosswindLoadAttempted = true;
|
|
5147
5191
|
try {
|
|
5148
|
-
const mod = await import("
|
|
5192
|
+
const mod = await import("@cwcss/crosswind");
|
|
5149
5193
|
crosswindModule = { CSSGenerator: mod.CSSGenerator, config: mod.config };
|
|
5150
5194
|
return crosswindModule;
|
|
5151
5195
|
} catch {
|
|
@@ -5175,13 +5219,89 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5175
5219
|
}
|
|
5176
5220
|
match = classRegex.exec(htmlContent);
|
|
5177
5221
|
}
|
|
5222
|
+
const dynRegex = /(?:x-class|:class)\s*=\s*"([^"]+)"/gi;
|
|
5223
|
+
let dynMatch = dynRegex.exec(htmlContent);
|
|
5224
|
+
while (dynMatch !== null) {
|
|
5225
|
+
const strLiterals = dynMatch[1].match(/'([^']+)'/g);
|
|
5226
|
+
if (strLiterals) {
|
|
5227
|
+
for (const lit of strLiterals) {
|
|
5228
|
+
const unquoted = lit.slice(1, -1);
|
|
5229
|
+
for (const cls of unquoted.split(/\s+/)) {
|
|
5230
|
+
if (cls.trim())
|
|
5231
|
+
classes.add(cls.trim());
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
dynMatch = dynRegex.exec(htmlContent);
|
|
5236
|
+
}
|
|
5178
5237
|
if (classes.size === 0)
|
|
5179
5238
|
return "";
|
|
5180
|
-
|
|
5239
|
+
let userConfig = {};
|
|
5240
|
+
try {
|
|
5241
|
+
const nodePath = await import("path");
|
|
5242
|
+
const configPath = nodePath.join(process13.cwd(), "crosswind.config.ts");
|
|
5243
|
+
console.log("[crosswind] loading config from:", configPath);
|
|
5244
|
+
userConfig = (await import(configPath)).default || {};
|
|
5245
|
+
console.log("[crosswind] config loaded, keys:", Object.keys(userConfig));
|
|
5246
|
+
} catch (e) {
|
|
5247
|
+
console.warn("[crosswind] config load error:", e?.message || e);
|
|
5248
|
+
}
|
|
5249
|
+
if (userConfig.safelist) {
|
|
5250
|
+
for (const cls of userConfig.safelist)
|
|
5251
|
+
classes.add(cls);
|
|
5252
|
+
console.log("[crosswind] safelist:", userConfig.safelist);
|
|
5253
|
+
}
|
|
5254
|
+
if (userConfig.shortcuts) {
|
|
5255
|
+
console.log("[crosswind] shortcuts:", Object.keys(userConfig.shortcuts));
|
|
5256
|
+
}
|
|
5257
|
+
const generatorConfig = { ...cw.config, preflight: true, minify: false };
|
|
5258
|
+
if (userConfig.shortcuts) {
|
|
5259
|
+
generatorConfig.shortcuts = { ...generatorConfig.shortcuts || {}, ...userConfig.shortcuts };
|
|
5260
|
+
}
|
|
5261
|
+
if (userConfig.theme) {
|
|
5262
|
+
generatorConfig.theme = { ...generatorConfig.theme || {}, ...userConfig.theme };
|
|
5263
|
+
}
|
|
5264
|
+
const generator = new cw.CSSGenerator(generatorConfig);
|
|
5181
5265
|
for (const className of classes) {
|
|
5182
5266
|
generator.generate(className);
|
|
5183
5267
|
}
|
|
5184
|
-
|
|
5268
|
+
let shortcutCSS = "";
|
|
5269
|
+
const shortcuts = generatorConfig.shortcuts || {};
|
|
5270
|
+
for (const [name, classStr] of Object.entries(shortcuts)) {
|
|
5271
|
+
if (!classes.has(name))
|
|
5272
|
+
continue;
|
|
5273
|
+
const parts = classStr.split(/\s+/).filter(Boolean);
|
|
5274
|
+
for (const p of parts)
|
|
5275
|
+
generator.generate(p);
|
|
5276
|
+
}
|
|
5277
|
+
const baseCss = generator.toCSS(true, false);
|
|
5278
|
+
for (const [name, classStr] of Object.entries(shortcuts)) {
|
|
5279
|
+
if (!classes.has(name))
|
|
5280
|
+
continue;
|
|
5281
|
+
const parts = classStr.split(/\s+/).filter(Boolean);
|
|
5282
|
+
const decls = [];
|
|
5283
|
+
const darkDecls = [];
|
|
5284
|
+
for (const cls of parts) {
|
|
5285
|
+
const isDark = cls.startsWith("dark:");
|
|
5286
|
+
const actualCls = isDark ? cls.slice(5) : cls;
|
|
5287
|
+
const escaped = actualCls.replace(/[/:.[\]%()]/g, (c) => `\\${c}`);
|
|
5288
|
+
const re = new RegExp(`\\.${escaped}\\s*\\{([^}]+)\\}`);
|
|
5289
|
+
const m = baseCss.match(re);
|
|
5290
|
+
if (m) {
|
|
5291
|
+
if (isDark)
|
|
5292
|
+
darkDecls.push(m[1].trim());
|
|
5293
|
+
else
|
|
5294
|
+
decls.push(m[1].trim());
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
if (decls.length)
|
|
5298
|
+
shortcutCSS += `.${name} { ${decls.join(" ")} }
|
|
5299
|
+
`;
|
|
5300
|
+
if (darkDecls.length)
|
|
5301
|
+
shortcutCSS += `@media (prefers-color-scheme: dark) { .dark .${name} { ${darkDecls.join(" ")} } }
|
|
5302
|
+
`;
|
|
5303
|
+
}
|
|
5304
|
+
return baseCss + shortcutCSS;
|
|
5185
5305
|
} catch (error) {
|
|
5186
5306
|
console.warn("Failed to generate Crosswind CSS:", error);
|
|
5187
5307
|
return "";
|
|
@@ -5190,35 +5310,33 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5190
5310
|
async function processTemplate(filePath) {
|
|
5191
5311
|
const path = await import("path");
|
|
5192
5312
|
const content = await Bun.file(filePath).text();
|
|
5193
|
-
const
|
|
5194
|
-
const clientScripts = [];
|
|
5313
|
+
const serverScriptRegex = /<script\b([^>]*)\bserver\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
5195
5314
|
const serverScripts = [];
|
|
5196
5315
|
let scriptMatch;
|
|
5197
|
-
while ((scriptMatch =
|
|
5198
|
-
|
|
5199
|
-
const scriptBody = scriptMatch[2];
|
|
5200
|
-
const fullScript = scriptMatch[0];
|
|
5201
|
-
const isClientScript = attrs.includes("client") || attrs.includes('type="module"') || attrs.includes("src=");
|
|
5202
|
-
if (isClientScript) {
|
|
5203
|
-
clientScripts.push(fullScript);
|
|
5204
|
-
} else {
|
|
5205
|
-
serverScripts.push(scriptBody);
|
|
5206
|
-
}
|
|
5316
|
+
while ((scriptMatch = serverScriptRegex.exec(content)) !== null) {
|
|
5317
|
+
serverScripts.push(scriptMatch[2]);
|
|
5207
5318
|
}
|
|
5208
|
-
let templateContent = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
5319
|
+
let templateContent = content.replace(/<script\b[^>]*\bserver\b[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
5209
5320
|
const context = {
|
|
5210
5321
|
__filename: filePath,
|
|
5211
5322
|
__dirname: path.dirname(filePath)
|
|
5212
5323
|
};
|
|
5213
|
-
const { processDirectives, extractVariables, defaultConfig: defaultConfig3, generateSpaShell, injectRouterScript } = await import("@stacksjs/stx");
|
|
5324
|
+
const { processDirectives, extractVariables, defaultConfig: defaultConfig3, generateSpaShell, injectRouterScript, resetHead } = await import("@stacksjs/stx");
|
|
5325
|
+
resetHead();
|
|
5214
5326
|
for (const scriptBody of serverScripts) {
|
|
5215
5327
|
await extractVariables(scriptBody, context, filePath);
|
|
5216
5328
|
}
|
|
5329
|
+
context.__stx_head_preset = true;
|
|
5217
5330
|
const config3 = {
|
|
5218
5331
|
...defaultConfig3,
|
|
5219
5332
|
...componentsDir && { componentsDir },
|
|
5220
5333
|
...layoutsDir && { layoutsDir },
|
|
5221
|
-
...partialsDir && { partialsDir }
|
|
5334
|
+
...partialsDir && { partialsDir },
|
|
5335
|
+
autoShell: true,
|
|
5336
|
+
ssr: stxConfig.ssr ?? defaultStxConfig.ssr ?? true,
|
|
5337
|
+
app: stxConfig.app || {},
|
|
5338
|
+
..."strict" in stxConfig && { strict: stxConfig.strict },
|
|
5339
|
+
..."router" in stxConfig && { router: stxConfig.router }
|
|
5222
5340
|
};
|
|
5223
5341
|
if (config3.ssr === false || stxConfig.ssr === false) {
|
|
5224
5342
|
return generateSpaShell({
|
|
@@ -5230,11 +5348,12 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5230
5348
|
let output = templateContent;
|
|
5231
5349
|
const dependencies = new Set;
|
|
5232
5350
|
output = await processDirectives(output, context, filePath, config3, dependencies);
|
|
5233
|
-
output = injectRouterScript(output);
|
|
5234
|
-
const
|
|
5351
|
+
output = await injectRouterScript(output);
|
|
5352
|
+
const directiveTemplateRe = /@for|:for|@if|:if|x-for|x-if/;
|
|
5353
|
+
const hasDirectiveTemplates = /<template\s[^>]*(?:@for|:for|@if|:if|x-for|x-if)/.test(output);
|
|
5235
5354
|
if (hasDirectiveTemplates) {
|
|
5236
5355
|
output = output.replace(/<template(?:\s[^>]*)?>|<\/template>/gi, (match) => {
|
|
5237
|
-
if (
|
|
5356
|
+
if (directiveTemplateRe.test(match))
|
|
5238
5357
|
return match;
|
|
5239
5358
|
if (match === "</template>")
|
|
5240
5359
|
return match;
|
|
@@ -5243,25 +5362,9 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5243
5362
|
} else {
|
|
5244
5363
|
output = output.replace(/<template[^>]*>/gi, "").replace(/<\/template>/gi, "");
|
|
5245
5364
|
}
|
|
5246
|
-
const crosswindCSS = await generateCrosswindCSS(output);
|
|
5247
|
-
if (crosswindCSS) {
|
|
5248
|
-
const styleTag = `<style>/* crosswind css */
|
|
5249
|
-
${crosswindCSS}</style>`;
|
|
5250
|
-
if (output.includes("</head>")) {
|
|
5251
|
-
output = output.replace("</head>", `${styleTag}
|
|
5252
|
-
</head>`);
|
|
5253
|
-
} else if (output.includes("<body")) {
|
|
5254
|
-
output = output.replace(/<body/i, `${styleTag}
|
|
5255
|
-
<body`);
|
|
5256
|
-
} else {
|
|
5257
|
-
output = styleTag + output;
|
|
5258
|
-
}
|
|
5259
|
-
}
|
|
5260
5365
|
return output;
|
|
5261
5366
|
}
|
|
5262
5367
|
async function getRoute(requestPath) {
|
|
5263
|
-
if (routes.has(requestPath))
|
|
5264
|
-
return routes.get(requestPath);
|
|
5265
5368
|
const files = await discoverFiles();
|
|
5266
5369
|
const nodePath = await import("path");
|
|
5267
5370
|
let normalizedPath = requestPath.startsWith("/") ? requestPath.slice(1) : requestPath;
|
|
@@ -5290,7 +5393,6 @@ ${crosswindCSS}</style>`;
|
|
|
5290
5393
|
const normalizedFilePath = filePath.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
5291
5394
|
if (normalizedFilePath === possible || normalizedFilePath.endsWith(`/${possible}`)) {
|
|
5292
5395
|
const output = await processTemplate(filePath);
|
|
5293
|
-
routes.set(requestPath, output);
|
|
5294
5396
|
return output;
|
|
5295
5397
|
}
|
|
5296
5398
|
}
|
|
@@ -5359,22 +5461,13 @@ ${crosswindCSS}</style>`;
|
|
|
5359
5461
|
async function processTemplateDynamic(filePath, paramNames, paramValues, routePath) {
|
|
5360
5462
|
const path = await import("path");
|
|
5361
5463
|
const content = await Bun.file(filePath).text();
|
|
5362
|
-
const
|
|
5363
|
-
const dynClientScripts = [];
|
|
5464
|
+
const serverScriptRegex = /<script\b([^>]*)\bserver\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
5364
5465
|
const dynServerScripts = [];
|
|
5365
5466
|
let dynScriptMatch;
|
|
5366
|
-
while ((dynScriptMatch =
|
|
5367
|
-
|
|
5368
|
-
const scriptBody = dynScriptMatch[2];
|
|
5369
|
-
const fullScript = dynScriptMatch[0];
|
|
5370
|
-
const isClientScript = attrs.includes("client") || attrs.includes('type="module"') || attrs.includes("src=");
|
|
5371
|
-
if (isClientScript) {
|
|
5372
|
-
dynClientScripts.push(fullScript);
|
|
5373
|
-
} else {
|
|
5374
|
-
dynServerScripts.push(scriptBody);
|
|
5375
|
-
}
|
|
5467
|
+
while ((dynScriptMatch = serverScriptRegex.exec(content)) !== null) {
|
|
5468
|
+
dynServerScripts.push(dynScriptMatch[2]);
|
|
5376
5469
|
}
|
|
5377
|
-
const templateContent = content.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
5470
|
+
const templateContent = content.replace(/<script\b[^>]*\bserver\b[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
5378
5471
|
const context = {
|
|
5379
5472
|
__filename: filePath,
|
|
5380
5473
|
__dirname: path.dirname(filePath),
|
|
@@ -5383,39 +5476,53 @@ ${crosswindCSS}</style>`;
|
|
|
5383
5476
|
for (let i = 0;i < paramNames.length; i++) {
|
|
5384
5477
|
context[paramNames[i]] = paramValues[i];
|
|
5385
5478
|
}
|
|
5386
|
-
const { processDirectives, extractVariables, defaultConfig: defaultConfig3, injectRouterScript: injectRouter } = await import("@stacksjs/stx");
|
|
5479
|
+
const { processDirectives, extractVariables, defaultConfig: defaultConfig3, injectRouterScript: injectRouter, resetHead: resetHeadDyn } = await import("@stacksjs/stx");
|
|
5480
|
+
resetHeadDyn();
|
|
5387
5481
|
for (const scriptBody of dynServerScripts) {
|
|
5388
5482
|
await extractVariables(scriptBody, context, filePath);
|
|
5389
5483
|
}
|
|
5484
|
+
context.__stx_head_preset = true;
|
|
5390
5485
|
const config3 = {
|
|
5391
5486
|
...defaultConfig3,
|
|
5392
5487
|
...componentsDir && { componentsDir },
|
|
5393
5488
|
...layoutsDir && { layoutsDir },
|
|
5394
|
-
...partialsDir && { partialsDir }
|
|
5489
|
+
...partialsDir && { partialsDir },
|
|
5490
|
+
autoShell: true,
|
|
5491
|
+
ssr: stxConfig.ssr ?? defaultStxConfig.ssr ?? true,
|
|
5492
|
+
app: stxConfig.app || {},
|
|
5493
|
+
..."strict" in stxConfig && { strict: stxConfig.strict },
|
|
5494
|
+
..."router" in stxConfig && { router: stxConfig.router }
|
|
5395
5495
|
};
|
|
5396
5496
|
let output = templateContent;
|
|
5397
5497
|
const dependencies = new Set;
|
|
5398
5498
|
output = await processDirectives(output, context, filePath, config3, dependencies);
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5499
|
+
if (paramNames.length > 0) {
|
|
5500
|
+
const paramsObj = {};
|
|
5501
|
+
for (let i = 0;i < paramNames.length; i++) {
|
|
5502
|
+
paramsObj[paramNames[i]] = paramValues[i];
|
|
5503
|
+
}
|
|
5504
|
+
const paramsScript = `<script>if(window.stx)window.stx._rp=${JSON.stringify(paramsObj)};else window.__stx_rp=${JSON.stringify(paramsObj)};</script>`;
|
|
5405
5505
|
if (output.includes("</head>")) {
|
|
5406
|
-
output = output.replace("</head>", `${
|
|
5506
|
+
output = output.replace("</head>", `${paramsScript}
|
|
5407
5507
|
</head>`);
|
|
5408
|
-
} else if (output.includes("<body")) {
|
|
5409
|
-
output = output.replace(/<body/i, `${styleTag}
|
|
5410
|
-
<body`);
|
|
5411
5508
|
} else {
|
|
5412
|
-
output =
|
|
5509
|
+
output = `${paramsScript}
|
|
5510
|
+
${output}`;
|
|
5413
5511
|
}
|
|
5414
5512
|
}
|
|
5513
|
+
output = await injectRouter(output);
|
|
5514
|
+
const directiveTemplateRe2 = /@for|:for|@if|:if|x-for|x-if/;
|
|
5515
|
+
output = output.replace(/<template(?:\s[^>]*)?>|<\/template>/gi, (match) => {
|
|
5516
|
+
if (directiveTemplateRe2.test(match))
|
|
5517
|
+
return match;
|
|
5518
|
+
if (match === "</template>")
|
|
5519
|
+
return match;
|
|
5520
|
+
return "";
|
|
5521
|
+
});
|
|
5415
5522
|
return output;
|
|
5416
5523
|
}
|
|
5417
|
-
|
|
5418
|
-
|
|
5524
|
+
const startTime = performance.now();
|
|
5525
|
+
await discoverFiles();
|
|
5419
5526
|
const corsHeaders = {
|
|
5420
5527
|
"Access-Control-Allow-Origin": "*",
|
|
5421
5528
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
@@ -5440,6 +5547,31 @@ ${crosswindCSS}</style>`;
|
|
|
5440
5547
|
return routeHandler(req);
|
|
5441
5548
|
}
|
|
5442
5549
|
}
|
|
5550
|
+
if (path.startsWith("/_stx/component/")) {
|
|
5551
|
+
const componentName = decodeURIComponent(path.slice("/_stx/component/".length));
|
|
5552
|
+
if (componentName) {
|
|
5553
|
+
try {
|
|
5554
|
+
const { processDirectives, defaultConfig: defaultConfig3 } = await import("@stacksjs/stx");
|
|
5555
|
+
const componentTemplate = `<${componentName} />`;
|
|
5556
|
+
const componentOpts = {
|
|
5557
|
+
...defaultConfig3,
|
|
5558
|
+
...componentsDir && { componentsDir },
|
|
5559
|
+
...layoutsDir && { layoutsDir },
|
|
5560
|
+
...partialsDir && { partialsDir },
|
|
5561
|
+
autoShell: false
|
|
5562
|
+
};
|
|
5563
|
+
const html = await processDirectives(componentTemplate, {}, `${componentsDir}/${componentName}.stx`, componentOpts, new Set);
|
|
5564
|
+
return new Response(html, {
|
|
5565
|
+
headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
|
|
5566
|
+
});
|
|
5567
|
+
} catch (e) {
|
|
5568
|
+
return new Response(`<div class="stx-async-error">${e.message}</div>`, {
|
|
5569
|
+
status: 500,
|
|
5570
|
+
headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5443
5575
|
if (path.startsWith("/api/data/") && req.method === "POST") {
|
|
5444
5576
|
try {
|
|
5445
5577
|
const tableName = path.replace("/api/data/", "").split("/")[0];
|
|
@@ -5449,62 +5581,70 @@ ${crosswindCSS}</style>`;
|
|
|
5449
5581
|
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5450
5582
|
});
|
|
5451
5583
|
}
|
|
5584
|
+
if (!/^[a-zA-Z_]\w*$/.test(tableName)) {
|
|
5585
|
+
return new Response(JSON.stringify({ error: "Invalid table name" }), {
|
|
5586
|
+
status: 400,
|
|
5587
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5588
|
+
});
|
|
5589
|
+
}
|
|
5452
5590
|
const body = await req.json();
|
|
5453
5591
|
const nodePath = await import("path");
|
|
5454
5592
|
const { Database } = await import("bun:sqlite");
|
|
5455
5593
|
const dbPath = nodePath.resolve(process13.cwd(), "database/stacks.sqlite");
|
|
5456
5594
|
const db = new Database(dbPath);
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5595
|
+
try {
|
|
5596
|
+
const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
5597
|
+
const validColumns = tableInfo.map((c) => c.name).filter((n) => n !== "id" && n !== "created_at" && n !== "updated_at");
|
|
5598
|
+
const columns = [];
|
|
5599
|
+
const placeholders = [];
|
|
5600
|
+
const values = [];
|
|
5601
|
+
for (const col of validColumns) {
|
|
5602
|
+
if (body[col] !== undefined && body[col] !== "") {
|
|
5603
|
+
columns.push(col);
|
|
5604
|
+
placeholders.push("?");
|
|
5605
|
+
values.push(body[col]);
|
|
5606
|
+
}
|
|
5607
|
+
}
|
|
5608
|
+
const now = new Date().toISOString();
|
|
5609
|
+
if (tableInfo.some((c) => c.name === "created_at")) {
|
|
5610
|
+
columns.push("created_at");
|
|
5465
5611
|
placeholders.push("?");
|
|
5466
|
-
values.push(
|
|
5612
|
+
values.push(now);
|
|
5467
5613
|
}
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
return new Response(JSON.stringify({
|
|
5483
|
-
|
|
5614
|
+
if (tableInfo.some((c) => c.name === "updated_at")) {
|
|
5615
|
+
columns.push("updated_at");
|
|
5616
|
+
placeholders.push("?");
|
|
5617
|
+
values.push(now);
|
|
5618
|
+
}
|
|
5619
|
+
if (columns.length === 0) {
|
|
5620
|
+
return new Response(JSON.stringify({ error: "No valid fields provided" }), {
|
|
5621
|
+
status: 400,
|
|
5622
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
const query = `INSERT INTO "${tableName}" (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
|
|
5626
|
+
const stmt = db.prepare(query);
|
|
5627
|
+
const result = stmt.run(...values);
|
|
5628
|
+
return new Response(JSON.stringify({
|
|
5629
|
+
success: true,
|
|
5630
|
+
id: result.lastInsertRowid,
|
|
5631
|
+
message: "Record created successfully"
|
|
5632
|
+
}), {
|
|
5633
|
+
status: 201,
|
|
5484
5634
|
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5485
5635
|
});
|
|
5636
|
+
} catch (error) {
|
|
5637
|
+
console.error("API Error:", error);
|
|
5638
|
+
return new Response(JSON.stringify({
|
|
5639
|
+
error: error.message || "Failed to create record"
|
|
5640
|
+
}), {
|
|
5641
|
+
status: 500,
|
|
5642
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5643
|
+
});
|
|
5644
|
+
} finally {
|
|
5645
|
+
db.close();
|
|
5486
5646
|
}
|
|
5487
|
-
|
|
5488
|
-
const stmt = db.prepare(query);
|
|
5489
|
-
const result = stmt.run(...values);
|
|
5490
|
-
db.close();
|
|
5491
|
-
return new Response(JSON.stringify({
|
|
5492
|
-
success: true,
|
|
5493
|
-
id: result.lastInsertRowid,
|
|
5494
|
-
message: "Record created successfully"
|
|
5495
|
-
}), {
|
|
5496
|
-
status: 201,
|
|
5497
|
-
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5498
|
-
});
|
|
5499
|
-
} catch (error) {
|
|
5500
|
-
console.error("API Error:", error);
|
|
5501
|
-
return new Response(JSON.stringify({
|
|
5502
|
-
error: error.message || "Failed to create record"
|
|
5503
|
-
}), {
|
|
5504
|
-
status: 500,
|
|
5505
|
-
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
5506
|
-
});
|
|
5507
|
-
}
|
|
5647
|
+
} catch {}
|
|
5508
5648
|
}
|
|
5509
5649
|
if (path === "/index")
|
|
5510
5650
|
path = "/";
|
|
@@ -5525,9 +5665,92 @@ ${crosswindCSS}</style>`;
|
|
|
5525
5665
|
}
|
|
5526
5666
|
const content = await getRoute(path);
|
|
5527
5667
|
if (content) {
|
|
5528
|
-
|
|
5668
|
+
const isSpaNav = req.headers.get("X-STX-Router") === "true";
|
|
5669
|
+
if (isSpaNav) {
|
|
5670
|
+
const layoutMatch = content.match(/<!-- stx-layout: ([^ ]+) -->/);
|
|
5671
|
+
const pageLayout = layoutMatch ? layoutMatch[1] : "default";
|
|
5672
|
+
let fragment = content;
|
|
5673
|
+
const headStyles = [];
|
|
5674
|
+
const headMatch = content.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i);
|
|
5675
|
+
if (headMatch) {
|
|
5676
|
+
const headContent = headMatch[1];
|
|
5677
|
+
let styleMatch;
|
|
5678
|
+
const styleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
|
|
5679
|
+
while ((styleMatch = styleRe.exec(headContent)) !== null) {
|
|
5680
|
+
if (styleMatch[0].includes("data-crosswind")) {
|
|
5681
|
+
const cssContent = styleMatch[0].replace(/<style[^>]*>/, "").replace(/<\/style>/, "");
|
|
5682
|
+
const hiddenRule = cssContent.indexOf("[hidden]");
|
|
5683
|
+
if (hiddenRule !== -1) {
|
|
5684
|
+
const afterPreflight = cssContent.indexOf("}", hiddenRule) + 1;
|
|
5685
|
+
const utilities = cssContent.slice(afterPreflight).trim();
|
|
5686
|
+
if (utilities) {
|
|
5687
|
+
headStyles.push(`<style data-crosswind="fragment">${utilities}</style>`);
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
continue;
|
|
5691
|
+
}
|
|
5692
|
+
headStyles.push(styleMatch[0]);
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
const mainOpenMatch = fragment.match(/<main\b[^>]*>/i);
|
|
5696
|
+
const mainCloseIdx = fragment.lastIndexOf("</main>");
|
|
5697
|
+
if (mainOpenMatch && mainCloseIdx !== -1) {
|
|
5698
|
+
const bodyMatch = content.match(/<body\b[^>]*>/i);
|
|
5699
|
+
if (bodyMatch) {
|
|
5700
|
+
const bodyStart = bodyMatch.index + bodyMatch[0].length;
|
|
5701
|
+
const mainIdx = mainOpenMatch.index;
|
|
5702
|
+
const beforeMain = content.slice(bodyStart, mainIdx);
|
|
5703
|
+
let bodyStyleMatch;
|
|
5704
|
+
const bodyStyleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
|
|
5705
|
+
while ((bodyStyleMatch = bodyStyleRe.exec(beforeMain)) !== null) {
|
|
5706
|
+
headStyles.push(bodyStyleMatch[0]);
|
|
5707
|
+
}
|
|
5708
|
+
}
|
|
5709
|
+
const mainStart = mainOpenMatch.index + mainOpenMatch[0].length;
|
|
5710
|
+
fragment = fragment.slice(mainStart, mainCloseIdx).trim();
|
|
5711
|
+
}
|
|
5712
|
+
const pageSetupScripts = [];
|
|
5713
|
+
const allScriptRe = /<script\b[^>]*data-stx-scoped[^>]*>[\s\S]*?<\/script>/gi;
|
|
5714
|
+
let setupMatch;
|
|
5715
|
+
while ((setupMatch = allScriptRe.exec(content)) !== null) {
|
|
5716
|
+
const scriptContent = setupMatch[0];
|
|
5717
|
+
if (scriptContent.includes("__stx_early_mounts"))
|
|
5718
|
+
continue;
|
|
5719
|
+
if (scriptContent.includes("data-stx-reactive") && scriptContent.includes("window.__stx_reactive"))
|
|
5720
|
+
continue;
|
|
5721
|
+
pageSetupScripts.push(scriptContent);
|
|
5722
|
+
}
|
|
5723
|
+
const bridgeInitRe = /<script\b[^>]*data-stx-reactive[^>]*>(?![\s\S]*window\.__stx_reactive)[\s\S]*?<\/script>/gi;
|
|
5724
|
+
while ((setupMatch = bridgeInitRe.exec(content)) !== null) {
|
|
5725
|
+
pageSetupScripts.push(setupMatch[0]);
|
|
5726
|
+
}
|
|
5727
|
+
fragment = fragment.replace(/<script data-stx-scoped>\s*;?\(function\(\)\s*\{[\s\S]*?<\/script>/g, "");
|
|
5728
|
+
const clearStale = "<script data-stx-page>if(window.stx)window.stx._latestSetup=null;</script>";
|
|
5729
|
+
fragment = `${headStyles.join(`
|
|
5730
|
+
`)}
|
|
5731
|
+
${fragment}
|
|
5732
|
+
${clearStale}
|
|
5733
|
+
${pageSetupScripts.join(`
|
|
5734
|
+
`)}`;
|
|
5735
|
+
return new Response(fragment, {
|
|
5736
|
+
headers: {
|
|
5737
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
5738
|
+
"X-STX-Fragment": "true",
|
|
5739
|
+
"X-STX-Layout": pageLayout,
|
|
5740
|
+
"Cache-Control": "no-cache",
|
|
5741
|
+
...corsHeaders
|
|
5742
|
+
}
|
|
5743
|
+
});
|
|
5744
|
+
}
|
|
5745
|
+
let cleaned = content;
|
|
5746
|
+
let runtimeCount = 0;
|
|
5747
|
+
cleaned = content.replace(/<script data-stx-scoped>\(function\(\)\{'use strict';var cloakStyle[\s\S]*?<\/script>/g, (match) => {
|
|
5748
|
+
runtimeCount++;
|
|
5749
|
+
return runtimeCount === 1 ? match : "";
|
|
5750
|
+
});
|
|
5751
|
+
return new Response(cleaned, {
|
|
5529
5752
|
headers: {
|
|
5530
|
-
"Content-Type": "text/html",
|
|
5753
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
5531
5754
|
"Cache-Control": "no-cache",
|
|
5532
5755
|
...corsHeaders
|
|
5533
5756
|
}
|
|
@@ -5537,7 +5760,7 @@ ${crosswindCSS}</style>`;
|
|
|
5537
5760
|
if (contentWithExt) {
|
|
5538
5761
|
return new Response(contentWithExt, {
|
|
5539
5762
|
headers: {
|
|
5540
|
-
"Content-Type": "text/html",
|
|
5763
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
5541
5764
|
"Cache-Control": "no-cache",
|
|
5542
5765
|
...corsHeaders
|
|
5543
5766
|
}
|
|
@@ -5584,24 +5807,9 @@ ${crosswindCSS}</style>`;
|
|
|
5584
5807
|
}
|
|
5585
5808
|
});
|
|
5586
5809
|
}
|
|
5587
|
-
const contentTypes = {
|
|
5588
|
-
css: "text/css",
|
|
5589
|
-
js: "application/javascript",
|
|
5590
|
-
json: "application/json",
|
|
5591
|
-
png: "image/png",
|
|
5592
|
-
jpg: "image/jpeg",
|
|
5593
|
-
jpeg: "image/jpeg",
|
|
5594
|
-
gif: "image/gif",
|
|
5595
|
-
svg: "image/svg+xml",
|
|
5596
|
-
ico: "image/x-icon",
|
|
5597
|
-
woff: "font/woff",
|
|
5598
|
-
woff2: "font/woff2",
|
|
5599
|
-
ttf: "font/ttf",
|
|
5600
|
-
eot: "application/vnd.ms-fontobject"
|
|
5601
|
-
};
|
|
5602
5810
|
return new Response(file, {
|
|
5603
5811
|
headers: {
|
|
5604
|
-
"Content-Type":
|
|
5812
|
+
"Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
|
|
5605
5813
|
"Cache-Control": "public, max-age=31536000"
|
|
5606
5814
|
}
|
|
5607
5815
|
});
|
|
@@ -5611,27 +5819,38 @@ ${crosswindCSS}</style>`;
|
|
|
5611
5819
|
}
|
|
5612
5820
|
}
|
|
5613
5821
|
}
|
|
5614
|
-
if (
|
|
5615
|
-
const
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5822
|
+
if ((req.method === "GET" || req.method === "HEAD") && path !== "/") {
|
|
5823
|
+
const nodePathMod = await import("path");
|
|
5824
|
+
const publicRoot = nodePathMod.resolve(process13.cwd(), publicDir);
|
|
5825
|
+
let safePathname;
|
|
5826
|
+
try {
|
|
5827
|
+
safePathname = decodeURIComponent(path);
|
|
5828
|
+
} catch {
|
|
5829
|
+
safePathname = path;
|
|
5830
|
+
}
|
|
5831
|
+
if (!safePathname.includes("\x00")) {
|
|
5832
|
+
const resolvedPath = nodePathMod.resolve(publicRoot, `.${safePathname}`);
|
|
5833
|
+
const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePathMod.sep}`);
|
|
5834
|
+
if (isInsidePublicRoot) {
|
|
5835
|
+
try {
|
|
5836
|
+
const file = Bun.file(resolvedPath);
|
|
5837
|
+
if (await file.exists()) {
|
|
5838
|
+
const stat3 = await file.stat().catch(() => null);
|
|
5839
|
+
if (stat3 && !stat3.isDirectory()) {
|
|
5840
|
+
const ext = resolvedPath.split(".").pop()?.toLowerCase();
|
|
5841
|
+
return new Response(file, {
|
|
5842
|
+
headers: {
|
|
5843
|
+
"Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
|
|
5844
|
+
"Cache-Control": "public, max-age=3600"
|
|
5845
|
+
}
|
|
5846
|
+
});
|
|
5628
5847
|
}
|
|
5629
|
-
}
|
|
5630
|
-
}
|
|
5631
|
-
} catch {
|
|
5632
|
-
continue;
|
|
5848
|
+
}
|
|
5849
|
+
} catch {}
|
|
5633
5850
|
}
|
|
5634
5851
|
}
|
|
5852
|
+
}
|
|
5853
|
+
if (path === "/favicon.ico") {
|
|
5635
5854
|
return new Response(null, { status: 204 });
|
|
5636
5855
|
}
|
|
5637
5856
|
const files = await discoverFiles();
|
|
@@ -5682,10 +5901,40 @@ ${crosswindCSS}</style>`;
|
|
|
5682
5901
|
</html>
|
|
5683
5902
|
`, {
|
|
5684
5903
|
status: 404,
|
|
5685
|
-
headers: { "Content-Type": "text/html" }
|
|
5904
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
5686
5905
|
});
|
|
5687
5906
|
}
|
|
5688
5907
|
});
|
|
5908
|
+
if (!options.quiet) {
|
|
5909
|
+
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
5910
|
+
const routeCount = sourceFiles?.length || 0;
|
|
5911
|
+
const patternsStr = patterns.join(", ");
|
|
5912
|
+
console.log();
|
|
5913
|
+
console.log(` \x1B[36m\x1B[1mstx\x1B[0m`);
|
|
5914
|
+
console.log();
|
|
5915
|
+
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mLocal\x1B[0m: \x1B[36mhttp://localhost:${port}/\x1B[0m`);
|
|
5916
|
+
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mRoutes\x1B[0m: \x1B[2m${routeCount} files from ${patternsStr}\x1B[0m`);
|
|
5917
|
+
console.log();
|
|
5918
|
+
console.log(` \x1B[2mready in ${elapsed}ms\x1B[0m`);
|
|
5919
|
+
console.log();
|
|
5920
|
+
console.log(` \x1B[2mPress\x1B[0m o + Enter \x1B[2mto open in browser\x1B[0m`);
|
|
5921
|
+
console.log(` \x1B[2mPress\x1B[0m q + Enter \x1B[2mto quit\x1B[0m`);
|
|
5922
|
+
console.log();
|
|
5923
|
+
if (process13.stdin.isTTY) {
|
|
5924
|
+
process13.stdin.setRawMode(false);
|
|
5925
|
+
const rl = await import("readline");
|
|
5926
|
+
const reader = rl.createInterface({ input: process13.stdin });
|
|
5927
|
+
reader.on("line", (line) => {
|
|
5928
|
+
const cmd = line.trim().toLowerCase();
|
|
5929
|
+
if (cmd === "o") {
|
|
5930
|
+
const openCmd = process13.platform === "darwin" ? "open" : process13.platform === "win32" ? "start" : "xdg-open";
|
|
5931
|
+
Bun.spawn([openCmd, `http://localhost:${port}/`]);
|
|
5932
|
+
} else if (cmd === "q") {
|
|
5933
|
+
process13.exit(0);
|
|
5934
|
+
}
|
|
5935
|
+
});
|
|
5936
|
+
}
|
|
5937
|
+
}
|
|
5689
5938
|
await Bun.sleep(Number.POSITIVE_INFINITY);
|
|
5690
5939
|
}
|
|
5691
5940
|
async function main() {
|