@constela/start 1.7.0 → 1.8.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.
|
@@ -90,8 +90,8 @@ hydrateApp(${hydrateOptions});${widgetMounting}`;
|
|
|
90
90
|
function wrapHtml(content, hydrationScript, head, options) {
|
|
91
91
|
let langAttr = "";
|
|
92
92
|
if (options?.lang) {
|
|
93
|
-
if (!/^[a-zA-Z]{2,3}
|
|
94
|
-
throw new Error(`Invalid lang: ${options.lang}. Expected BCP 47 language tag (e.g., 'en', 'ja', 'en-US', 'zh-Hans-CN').`);
|
|
93
|
+
if (!/^([a-zA-Z]{2,3}|i)(-[a-zA-Z0-9]{1,8})*$/.test(options.lang)) {
|
|
94
|
+
throw new Error(`Invalid lang: ${options.lang}. Expected BCP 47 language tag (e.g., 'en', 'ja', 'en-US', 'zh-Hans-CN', 'zh-cmn-Hans', 'de-DE-u-co-phonebk').`);
|
|
95
95
|
}
|
|
96
96
|
langAttr = ` lang="${options.lang}"`;
|
|
97
97
|
}
|
|
@@ -217,6 +217,18 @@ function evaluateJsonLdExpression(expr, ctx) {
|
|
|
217
217
|
return "";
|
|
218
218
|
case "concat":
|
|
219
219
|
return expr.items.map((item) => String(evaluateJsonLdExpression(item, ctx))).join("");
|
|
220
|
+
case "object": {
|
|
221
|
+
const obj = {};
|
|
222
|
+
if (expr.type) {
|
|
223
|
+
obj["@type"] = expr.type;
|
|
224
|
+
}
|
|
225
|
+
for (const [key, propExpr] of Object.entries(expr.properties)) {
|
|
226
|
+
obj[key] = evaluateJsonLdExpression(propExpr, ctx);
|
|
227
|
+
}
|
|
228
|
+
return obj;
|
|
229
|
+
}
|
|
230
|
+
case "array":
|
|
231
|
+
return expr.items.map((item) => evaluateJsonLdExpression(item, ctx));
|
|
220
232
|
default:
|
|
221
233
|
return "";
|
|
222
234
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
generateMetaTags,
|
|
4
4
|
renderPage,
|
|
5
5
|
wrapHtml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3GFRJEKD.js";
|
|
7
7
|
|
|
8
8
|
// src/router/file-router.ts
|
|
9
9
|
import fg from "fast-glob";
|
|
@@ -2104,9 +2104,19 @@ async function convertToCompiledProgram(pageInfo) {
|
|
|
2104
2104
|
if (page.route.meta) {
|
|
2105
2105
|
program.route.meta = {};
|
|
2106
2106
|
for (const [key, value] of Object.entries(page.route.meta)) {
|
|
2107
|
-
|
|
2107
|
+
const isExpression = value && typeof value === "object" && "expr" in value;
|
|
2108
|
+
program.route.meta[key] = isExpression ? value : { expr: "lit", value };
|
|
2108
2109
|
}
|
|
2109
2110
|
}
|
|
2111
|
+
if (page.route.title) {
|
|
2112
|
+
program.route.title = page.route.title;
|
|
2113
|
+
}
|
|
2114
|
+
if (page.route.canonical) {
|
|
2115
|
+
program.route.canonical = page.route.canonical;
|
|
2116
|
+
}
|
|
2117
|
+
if (page.route.jsonLd) {
|
|
2118
|
+
program.route.jsonLd = page.route.jsonLd;
|
|
2119
|
+
}
|
|
2110
2120
|
}
|
|
2111
2121
|
const hasImports = Object.keys(resolvedImports).length > 0;
|
|
2112
2122
|
const hasData = Object.keys(loadedData).length > 0;
|
|
@@ -2652,7 +2662,8 @@ async function createDevServer(options = {}) {
|
|
|
2652
2662
|
defaultTheme: initialTheme,
|
|
2653
2663
|
themeStorageKey: "theme"
|
|
2654
2664
|
} : {},
|
|
2655
|
-
importMap
|
|
2665
|
+
importMap,
|
|
2666
|
+
...options.seo?.lang ? { lang: options.seo.lang } : {}
|
|
2656
2667
|
});
|
|
2657
2668
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
2658
2669
|
res.end(html);
|
|
@@ -2793,25 +2804,69 @@ h1 { color: #666; }
|
|
|
2793
2804
|
return devServer;
|
|
2794
2805
|
}
|
|
2795
2806
|
|
|
2807
|
+
// src/config/config-loader.ts
|
|
2808
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
2809
|
+
import { join as join9 } from "path";
|
|
2810
|
+
var CONFIG_FILENAME = "constela.config.json";
|
|
2811
|
+
async function loadConfig(projectRoot) {
|
|
2812
|
+
const configPath = join9(projectRoot, CONFIG_FILENAME);
|
|
2813
|
+
if (!existsSync7(configPath)) {
|
|
2814
|
+
return {};
|
|
2815
|
+
}
|
|
2816
|
+
let content;
|
|
2817
|
+
try {
|
|
2818
|
+
content = readFileSync5(configPath, "utf-8");
|
|
2819
|
+
} catch (error) {
|
|
2820
|
+
throw new Error(`Failed to read config file: ${configPath}`);
|
|
2821
|
+
}
|
|
2822
|
+
try {
|
|
2823
|
+
return JSON.parse(content);
|
|
2824
|
+
} catch {
|
|
2825
|
+
throw new Error(`Invalid JSON in config file: ${configPath}`);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
async function resolveConfig(fileConfig, cliOptions) {
|
|
2829
|
+
if (!cliOptions) {
|
|
2830
|
+
return { ...fileConfig };
|
|
2831
|
+
}
|
|
2832
|
+
const result = { ...fileConfig };
|
|
2833
|
+
if (cliOptions.css !== void 0) result.css = cliOptions.css;
|
|
2834
|
+
if (cliOptions.cssContent !== void 0) {
|
|
2835
|
+
result.cssContent = cliOptions.cssContent.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2836
|
+
}
|
|
2837
|
+
if (cliOptions.layoutsDir !== void 0) result.layoutsDir = cliOptions.layoutsDir;
|
|
2838
|
+
if (cliOptions.routesDir !== void 0) result.routesDir = cliOptions.routesDir;
|
|
2839
|
+
if (cliOptions.publicDir !== void 0) result.publicDir = cliOptions.publicDir;
|
|
2840
|
+
if (cliOptions.outDir !== void 0) {
|
|
2841
|
+
result.build = { ...result.build, outDir: cliOptions.outDir };
|
|
2842
|
+
}
|
|
2843
|
+
if (cliOptions.port !== void 0 || cliOptions.host !== void 0) {
|
|
2844
|
+
result.dev = { ...result.dev };
|
|
2845
|
+
if (cliOptions.port !== void 0) result.dev.port = cliOptions.port;
|
|
2846
|
+
if (cliOptions.host !== void 0) result.dev.host = cliOptions.host;
|
|
2847
|
+
}
|
|
2848
|
+
return result;
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2796
2851
|
// src/build/index.ts
|
|
2797
|
-
import { existsSync as
|
|
2852
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
2798
2853
|
import { mkdir as mkdir2, writeFile, cp, readdir } from "fs/promises";
|
|
2799
|
-
import { join as
|
|
2854
|
+
import { join as join11, dirname as dirname5, relative as relative5, basename as basename4, isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
|
|
2800
2855
|
import { isCookieInitialExpr as isCookieInitialExpr3 } from "@constela/core";
|
|
2801
2856
|
|
|
2802
2857
|
// src/build/bundler.ts
|
|
2803
2858
|
import * as esbuild from "esbuild";
|
|
2804
|
-
import { existsSync as
|
|
2859
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2805
2860
|
import { mkdir, readFile } from "fs/promises";
|
|
2806
2861
|
import { createRequire } from "module";
|
|
2807
|
-
import { join as
|
|
2862
|
+
import { join as join10, dirname as dirname4, isAbsolute as isAbsolute2, relative as relative4 } from "path";
|
|
2808
2863
|
import { fileURLToPath } from "url";
|
|
2809
2864
|
var __dirname = dirname4(fileURLToPath(import.meta.url));
|
|
2810
2865
|
async function bundleRuntime(options) {
|
|
2811
2866
|
const entryContent = `
|
|
2812
2867
|
export { hydrateApp, createApp } from '@constela/runtime';
|
|
2813
2868
|
`;
|
|
2814
|
-
const outFile =
|
|
2869
|
+
const outFile = join10(options.outDir, "_constela", "runtime.js");
|
|
2815
2870
|
await mkdir(dirname4(outFile), { recursive: true });
|
|
2816
2871
|
try {
|
|
2817
2872
|
await esbuild.build({
|
|
@@ -2836,12 +2891,12 @@ async function bundleRuntime(options) {
|
|
|
2836
2891
|
}
|
|
2837
2892
|
async function bundleCSS(options) {
|
|
2838
2893
|
const cssFiles = Array.isArray(options.css) ? options.css : [options.css];
|
|
2839
|
-
const outFile =
|
|
2894
|
+
const outFile = join10(options.outDir, "_constela", "styles.css");
|
|
2840
2895
|
const shouldMinify = options.minify ?? true;
|
|
2841
2896
|
await mkdir(dirname4(outFile), { recursive: true });
|
|
2842
|
-
const resolvedCssFiles = cssFiles.map((f) => isAbsolute2(f) ? f :
|
|
2897
|
+
const resolvedCssFiles = cssFiles.map((f) => isAbsolute2(f) ? f : join10(process.cwd(), f));
|
|
2843
2898
|
for (const fullPath of resolvedCssFiles) {
|
|
2844
|
-
if (!
|
|
2899
|
+
if (!existsSync8(fullPath)) {
|
|
2845
2900
|
throw new Error(`CSS file not found: ${fullPath}`);
|
|
2846
2901
|
}
|
|
2847
2902
|
}
|
|
@@ -2868,7 +2923,7 @@ async function bundleCSS(options) {
|
|
|
2868
2923
|
if (options.content.length > 0) {
|
|
2869
2924
|
const fg4 = (await import("fast-glob")).default;
|
|
2870
2925
|
const resolvedContentPaths = options.content.map(
|
|
2871
|
-
(p) => isAbsolute2(p) ? p :
|
|
2926
|
+
(p) => isAbsolute2(p) ? p : join10(process.cwd(), p)
|
|
2872
2927
|
);
|
|
2873
2928
|
const matchedFiles = await fg4(resolvedContentPaths, { onlyFiles: true });
|
|
2874
2929
|
if (matchedFiles.length === 0) {
|
|
@@ -2879,7 +2934,7 @@ async function bundleCSS(options) {
|
|
|
2879
2934
|
}
|
|
2880
2935
|
const sourceDir = dirname4(firstCssFile);
|
|
2881
2936
|
const sourceDirectives = options.content.map((contentPath) => {
|
|
2882
|
-
const absolutePath = isAbsolute2(contentPath) ? contentPath :
|
|
2937
|
+
const absolutePath = isAbsolute2(contentPath) ? contentPath : join10(process.cwd(), contentPath);
|
|
2883
2938
|
const srcPath = absolutePath.includes("*") ? dirname4(absolutePath.split("*")[0] ?? absolutePath) : absolutePath;
|
|
2884
2939
|
const relativePath = relative4(sourceDir, srcPath);
|
|
2885
2940
|
return `@source "${relativePath}";`;
|
|
@@ -2961,9 +3016,9 @@ function isDynamicRoute(pattern) {
|
|
|
2961
3016
|
function getOutputPath(filePath, outDir) {
|
|
2962
3017
|
const withoutExt = filePath.replace(/\.(json|ts|tsx|js|jsx)$/, "");
|
|
2963
3018
|
if (withoutExt === "index" || withoutExt.endsWith("/index")) {
|
|
2964
|
-
return
|
|
3019
|
+
return join11(outDir, withoutExt + ".html");
|
|
2965
3020
|
}
|
|
2966
|
-
return
|
|
3021
|
+
return join11(outDir, withoutExt, "index.html");
|
|
2967
3022
|
}
|
|
2968
3023
|
function paramsToOutputPath(basePattern, params, outDir) {
|
|
2969
3024
|
let path = basePattern;
|
|
@@ -2973,19 +3028,19 @@ function paramsToOutputPath(basePattern, params, outDir) {
|
|
|
2973
3028
|
}
|
|
2974
3029
|
const relativePath = path.startsWith("/") ? path.slice(1) : path;
|
|
2975
3030
|
if (relativePath === "") {
|
|
2976
|
-
return
|
|
3031
|
+
return join11(outDir, "index.html");
|
|
2977
3032
|
}
|
|
2978
|
-
return
|
|
3033
|
+
return join11(outDir, relativePath, "index.html");
|
|
2979
3034
|
}
|
|
2980
3035
|
async function loadGetStaticPaths(pageFile) {
|
|
2981
3036
|
const dir = dirname5(pageFile);
|
|
2982
3037
|
const baseName = basename4(pageFile, ".json");
|
|
2983
|
-
const pathsFile =
|
|
2984
|
-
if (!
|
|
3038
|
+
const pathsFile = join11(dir, `${baseName}.paths.ts`);
|
|
3039
|
+
if (!existsSync9(pathsFile)) {
|
|
2985
3040
|
return null;
|
|
2986
3041
|
}
|
|
2987
3042
|
try {
|
|
2988
|
-
const content =
|
|
3043
|
+
const content = readFileSync6(pathsFile, "utf-8");
|
|
2989
3044
|
const pathsMatch = content.match(/paths:\s*\[([\s\S]*?)\]/);
|
|
2990
3045
|
if (!pathsMatch) {
|
|
2991
3046
|
throw new Error(`Invalid getStaticPaths format in ${pathsFile}`);
|
|
@@ -3021,12 +3076,12 @@ async function loadGetStaticPaths(pageFile) {
|
|
|
3021
3076
|
}
|
|
3022
3077
|
}
|
|
3023
3078
|
async function loadLayout2(layoutName, layoutsDir) {
|
|
3024
|
-
const layoutPath =
|
|
3025
|
-
if (!
|
|
3079
|
+
const layoutPath = join11(layoutsDir, `${layoutName}.json`);
|
|
3080
|
+
if (!existsSync9(layoutPath)) {
|
|
3026
3081
|
throw new Error(`Layout "${layoutName}" not found at ${layoutPath}`);
|
|
3027
3082
|
}
|
|
3028
3083
|
try {
|
|
3029
|
-
const content =
|
|
3084
|
+
const content = readFileSync6(layoutPath, "utf-8");
|
|
3030
3085
|
const parsed = JSON.parse(content);
|
|
3031
3086
|
if (parsed.imports && Object.keys(parsed.imports).length > 0) {
|
|
3032
3087
|
const layoutDir = dirname5(layoutPath);
|
|
@@ -3361,13 +3416,13 @@ async function processLayouts(pageInfo, layoutsDir, routeParams = {}) {
|
|
|
3361
3416
|
};
|
|
3362
3417
|
return updatedPageInfo;
|
|
3363
3418
|
}
|
|
3364
|
-
async function renderPageToHtml(program, params, runtimePath, cssPath, externalImports, widgets) {
|
|
3419
|
+
async function renderPageToHtml(program, params, routePath, runtimePath, cssPath, externalImports, widgets, lang) {
|
|
3365
3420
|
const normalizedProgram = {
|
|
3366
3421
|
...program,
|
|
3367
3422
|
view: normalizeViewNode(structuredClone(program.view))
|
|
3368
3423
|
};
|
|
3369
3424
|
const ctx = {
|
|
3370
|
-
url:
|
|
3425
|
+
url: routePath,
|
|
3371
3426
|
params,
|
|
3372
3427
|
query: new URLSearchParams()
|
|
3373
3428
|
};
|
|
@@ -3375,12 +3430,12 @@ async function renderPageToHtml(program, params, runtimePath, cssPath, externalI
|
|
|
3375
3430
|
const routeContext = {
|
|
3376
3431
|
params,
|
|
3377
3432
|
query: {},
|
|
3378
|
-
path:
|
|
3433
|
+
path: routePath
|
|
3379
3434
|
};
|
|
3380
3435
|
const metaTags = generateMetaTags(normalizedProgram.route, {
|
|
3381
3436
|
params,
|
|
3382
3437
|
query: {},
|
|
3383
|
-
path:
|
|
3438
|
+
path: routePath
|
|
3384
3439
|
});
|
|
3385
3440
|
const cssLinkTag = cssPath ? `<link rel="stylesheet" href="${cssPath}">` : void 0;
|
|
3386
3441
|
const head = [metaTags, cssLinkTag].filter(Boolean).join("\n") || void 0;
|
|
@@ -3393,6 +3448,9 @@ async function renderPageToHtml(program, params, runtimePath, cssPath, externalI
|
|
|
3393
3448
|
if (externalImports && Object.keys(externalImports).length > 0) {
|
|
3394
3449
|
wrapOptions.importMap = externalImports;
|
|
3395
3450
|
}
|
|
3451
|
+
if (lang) {
|
|
3452
|
+
wrapOptions.lang = lang;
|
|
3453
|
+
}
|
|
3396
3454
|
const themeState = program.state?.["theme"];
|
|
3397
3455
|
if (themeState) {
|
|
3398
3456
|
let defaultTheme;
|
|
@@ -3414,7 +3472,7 @@ async function renderPageToHtml(program, params, runtimePath, cssPath, externalI
|
|
|
3414
3472
|
);
|
|
3415
3473
|
}
|
|
3416
3474
|
async function copyPublicDir(publicDir, outDir, generatedFiles) {
|
|
3417
|
-
if (!
|
|
3475
|
+
if (!existsSync9(publicDir)) {
|
|
3418
3476
|
return;
|
|
3419
3477
|
}
|
|
3420
3478
|
await copyDirRecursive(publicDir, outDir, generatedFiles);
|
|
@@ -3422,8 +3480,8 @@ async function copyPublicDir(publicDir, outDir, generatedFiles) {
|
|
|
3422
3480
|
async function copyDirRecursive(srcDir, destDir, skipFiles) {
|
|
3423
3481
|
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
3424
3482
|
for (const entry of entries) {
|
|
3425
|
-
const srcPath =
|
|
3426
|
-
const destPath =
|
|
3483
|
+
const srcPath = join11(srcDir, entry.name);
|
|
3484
|
+
const destPath = join11(destDir, entry.name);
|
|
3427
3485
|
if (entry.isDirectory()) {
|
|
3428
3486
|
await mkdir2(destPath, { recursive: true });
|
|
3429
3487
|
await copyDirRecursive(srcPath, destPath, skipFiles);
|
|
@@ -3492,10 +3550,12 @@ async function build2(options) {
|
|
|
3492
3550
|
}
|
|
3493
3551
|
const absoluteRoutesDir = isAbsolute3(routesDir) ? routesDir : resolve4(routesDir);
|
|
3494
3552
|
const projectRoot = dirname5(dirname5(absoluteRoutesDir));
|
|
3553
|
+
const config = await loadConfig(projectRoot);
|
|
3554
|
+
const seoLang = options?.seo?.lang ?? config.seo?.lang;
|
|
3495
3555
|
for (const route of jsonPages) {
|
|
3496
3556
|
const relPathFromRoutesDir = relative5(absoluteRoutesDir, route.file);
|
|
3497
3557
|
const relPathFromProjectRoot = relative5(projectRoot, route.file);
|
|
3498
|
-
const content =
|
|
3558
|
+
const content = readFileSync6(route.file, "utf-8");
|
|
3499
3559
|
const page = validateJsonPage(content, route.file);
|
|
3500
3560
|
if (isDynamicRoute(route.pattern)) {
|
|
3501
3561
|
const loader = new JsonPageLoader(projectRoot, { routesDir: absoluteRoutesDir });
|
|
@@ -3519,6 +3579,11 @@ async function build2(options) {
|
|
|
3519
3579
|
for (const pathEntry of staticPathsResult) {
|
|
3520
3580
|
const params = pathEntry.params;
|
|
3521
3581
|
const outputPath = paramsToOutputPath(route.pattern, params, outDir);
|
|
3582
|
+
let routePath = route.pattern;
|
|
3583
|
+
for (const [key, value] of Object.entries(params)) {
|
|
3584
|
+
routePath = routePath.replace(`:${key}`, value);
|
|
3585
|
+
routePath = routePath.replace("*", value);
|
|
3586
|
+
}
|
|
3522
3587
|
let boundPageInfo = pageInfo;
|
|
3523
3588
|
if (pathEntry.data && pageInfo.page.getStaticPaths?.source) {
|
|
3524
3589
|
const source = pageInfo.page.getStaticPaths.source;
|
|
@@ -3538,37 +3603,32 @@ async function build2(options) {
|
|
|
3538
3603
|
processedPageInfo = await processLayouts(boundPageInfo, layoutsDir, params);
|
|
3539
3604
|
}
|
|
3540
3605
|
const program = await convertToCompiledProgram(processedPageInfo);
|
|
3541
|
-
const html = await renderPageToHtml(program, params, runtimePath, cssPath, processedPageInfo.page.externalImports, processedPageInfo.widgets);
|
|
3606
|
+
const html = await renderPageToHtml(program, params, routePath, runtimePath, cssPath, processedPageInfo.page.externalImports, processedPageInfo.widgets, seoLang);
|
|
3542
3607
|
await mkdir2(dirname5(outputPath), { recursive: true });
|
|
3543
3608
|
await writeFile(outputPath, html, "utf-8");
|
|
3544
3609
|
generatedFiles.push(outputPath);
|
|
3545
3610
|
const slugValue = params["slug"];
|
|
3546
3611
|
if (slugValue && (slugValue === "index" || slugValue.endsWith("/index"))) {
|
|
3547
|
-
const parentOutputPath =
|
|
3612
|
+
const parentOutputPath = join11(dirname5(dirname5(outputPath)), "index.html");
|
|
3548
3613
|
if (!generatedFiles.includes(parentOutputPath)) {
|
|
3549
3614
|
await mkdir2(dirname5(parentOutputPath), { recursive: true });
|
|
3550
3615
|
await writeFile(parentOutputPath, html, "utf-8");
|
|
3551
3616
|
generatedFiles.push(parentOutputPath);
|
|
3552
3617
|
}
|
|
3553
3618
|
}
|
|
3554
|
-
let routePath = route.pattern;
|
|
3555
|
-
for (const [key, value] of Object.entries(params)) {
|
|
3556
|
-
routePath = routePath.replace(`:${key}`, value);
|
|
3557
|
-
routePath = routePath.replace("*", value);
|
|
3558
|
-
}
|
|
3559
3619
|
routes.push(routePath);
|
|
3560
3620
|
}
|
|
3561
3621
|
} else {
|
|
3562
3622
|
const loader = new JsonPageLoader(projectRoot, { routesDir: absoluteRoutesDir });
|
|
3563
3623
|
let pageInfo = await loader.loadPage(relPathFromProjectRoot);
|
|
3564
3624
|
let outputPath;
|
|
3625
|
+
const routePath = pageInfo.page.route?.path ?? route.pattern;
|
|
3565
3626
|
if (pageInfo.page.route?.path) {
|
|
3566
|
-
const routePath = pageInfo.page.route.path;
|
|
3567
3627
|
const relativePath = routePath.startsWith("/") ? routePath.slice(1) : routePath;
|
|
3568
3628
|
if (relativePath === "" || relativePath === "/") {
|
|
3569
|
-
outputPath =
|
|
3629
|
+
outputPath = join11(outDir, "index.html");
|
|
3570
3630
|
} else {
|
|
3571
|
-
outputPath =
|
|
3631
|
+
outputPath = join11(outDir, relativePath, "index.html");
|
|
3572
3632
|
}
|
|
3573
3633
|
} else {
|
|
3574
3634
|
outputPath = getOutputPath(relPathFromRoutesDir, outDir);
|
|
@@ -3577,7 +3637,7 @@ async function build2(options) {
|
|
|
3577
3637
|
pageInfo = await processLayouts(pageInfo, layoutsDir, {});
|
|
3578
3638
|
}
|
|
3579
3639
|
const program = await convertToCompiledProgram(pageInfo);
|
|
3580
|
-
const html = await renderPageToHtml(program, {}, runtimePath, cssPath, pageInfo.page.externalImports, pageInfo.widgets);
|
|
3640
|
+
const html = await renderPageToHtml(program, {}, routePath, runtimePath, cssPath, pageInfo.page.externalImports, pageInfo.widgets, seoLang);
|
|
3581
3641
|
await mkdir2(dirname5(outputPath), { recursive: true });
|
|
3582
3642
|
await writeFile(outputPath, html, "utf-8");
|
|
3583
3643
|
generatedFiles.push(outputPath);
|
|
@@ -3594,50 +3654,6 @@ async function build2(options) {
|
|
|
3594
3654
|
};
|
|
3595
3655
|
}
|
|
3596
3656
|
|
|
3597
|
-
// src/config/config-loader.ts
|
|
3598
|
-
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3599
|
-
import { join as join11 } from "path";
|
|
3600
|
-
var CONFIG_FILENAME = "constela.config.json";
|
|
3601
|
-
async function loadConfig(projectRoot) {
|
|
3602
|
-
const configPath = join11(projectRoot, CONFIG_FILENAME);
|
|
3603
|
-
if (!existsSync9(configPath)) {
|
|
3604
|
-
return {};
|
|
3605
|
-
}
|
|
3606
|
-
let content;
|
|
3607
|
-
try {
|
|
3608
|
-
content = readFileSync6(configPath, "utf-8");
|
|
3609
|
-
} catch (error) {
|
|
3610
|
-
throw new Error(`Failed to read config file: ${configPath}`);
|
|
3611
|
-
}
|
|
3612
|
-
try {
|
|
3613
|
-
return JSON.parse(content);
|
|
3614
|
-
} catch {
|
|
3615
|
-
throw new Error(`Invalid JSON in config file: ${configPath}`);
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
async function resolveConfig(fileConfig, cliOptions) {
|
|
3619
|
-
if (!cliOptions) {
|
|
3620
|
-
return { ...fileConfig };
|
|
3621
|
-
}
|
|
3622
|
-
const result = { ...fileConfig };
|
|
3623
|
-
if (cliOptions.css !== void 0) result.css = cliOptions.css;
|
|
3624
|
-
if (cliOptions.cssContent !== void 0) {
|
|
3625
|
-
result.cssContent = cliOptions.cssContent.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3626
|
-
}
|
|
3627
|
-
if (cliOptions.layoutsDir !== void 0) result.layoutsDir = cliOptions.layoutsDir;
|
|
3628
|
-
if (cliOptions.routesDir !== void 0) result.routesDir = cliOptions.routesDir;
|
|
3629
|
-
if (cliOptions.publicDir !== void 0) result.publicDir = cliOptions.publicDir;
|
|
3630
|
-
if (cliOptions.outDir !== void 0) {
|
|
3631
|
-
result.build = { ...result.build, outDir: cliOptions.outDir };
|
|
3632
|
-
}
|
|
3633
|
-
if (cliOptions.port !== void 0 || cliOptions.host !== void 0) {
|
|
3634
|
-
result.dev = { ...result.dev };
|
|
3635
|
-
if (cliOptions.port !== void 0) result.dev.port = cliOptions.port;
|
|
3636
|
-
if (cliOptions.host !== void 0) result.dev.host = cliOptions.host;
|
|
3637
|
-
}
|
|
3638
|
-
return result;
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3641
3657
|
// src/utils/terminal.ts
|
|
3642
3658
|
function hyperlink(url, text) {
|
|
3643
3659
|
const displayText = text ?? url;
|
|
@@ -3673,8 +3689,8 @@ export {
|
|
|
3673
3689
|
loadLayout,
|
|
3674
3690
|
LayoutResolver,
|
|
3675
3691
|
createDevServer,
|
|
3676
|
-
build2 as build,
|
|
3677
3692
|
loadConfig,
|
|
3678
3693
|
resolveConfig,
|
|
3694
|
+
build2 as build,
|
|
3679
3695
|
hyperlink
|
|
3680
3696
|
};
|
package/dist/cli/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -93,6 +93,10 @@ interface DevServerOptions {
|
|
|
93
93
|
layoutsDir?: string;
|
|
94
94
|
/** CSS entry point(s) for Vite middleware processing */
|
|
95
95
|
css?: string | string[];
|
|
96
|
+
/** SEO configuration */
|
|
97
|
+
seo?: {
|
|
98
|
+
lang?: string;
|
|
99
|
+
};
|
|
96
100
|
}
|
|
97
101
|
/**
|
|
98
102
|
* Build options
|
|
@@ -107,6 +111,10 @@ interface BuildOptions {
|
|
|
107
111
|
/** Content paths for Tailwind CSS class scanning (enables PostCSS processing) */
|
|
108
112
|
cssContent?: string[] | undefined;
|
|
109
113
|
target?: 'node' | 'edge' | undefined;
|
|
114
|
+
/** SEO configuration */
|
|
115
|
+
seo?: {
|
|
116
|
+
lang?: string;
|
|
117
|
+
};
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
/**
|
|
@@ -232,6 +240,10 @@ interface GenerateStaticPagesOptions {
|
|
|
232
240
|
* Primarily used for testing purposes.
|
|
233
241
|
*/
|
|
234
242
|
staticPathsProvider?: StaticPathsProvider;
|
|
243
|
+
/**
|
|
244
|
+
* Optional language code for HTML lang attribute.
|
|
245
|
+
*/
|
|
246
|
+
lang?: string;
|
|
235
247
|
}
|
|
236
248
|
/**
|
|
237
249
|
* Generate static pages for SSG routes
|
package/dist/index.js
CHANGED
|
@@ -28,14 +28,14 @@ import {
|
|
|
28
28
|
transformCsv,
|
|
29
29
|
transformMdx,
|
|
30
30
|
transformYaml
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-FQH4I77H.js";
|
|
32
32
|
import {
|
|
33
33
|
evaluateMetaExpression,
|
|
34
34
|
generateHydrationScript,
|
|
35
35
|
generateMetaTags,
|
|
36
36
|
renderPage,
|
|
37
37
|
wrapHtml
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-3GFRJEKD.js";
|
|
39
39
|
|
|
40
40
|
// src/build/ssg.ts
|
|
41
41
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -84,7 +84,7 @@ async function getStaticPathsForRoute(route, module, staticPathsProvider) {
|
|
|
84
84
|
}
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
87
|
-
async function generateSinglePage(pattern, outDir, program, params = {}) {
|
|
87
|
+
async function generateSinglePage(pattern, outDir, program, params = {}, lang) {
|
|
88
88
|
const outputPath = getOutputPath(pattern, outDir);
|
|
89
89
|
const outputDir = dirname(outputPath);
|
|
90
90
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -119,6 +119,9 @@ async function generateSinglePage(pattern, outDir, program, params = {}) {
|
|
|
119
119
|
wrapOptions.themeStorageKey = "theme";
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
if (lang) {
|
|
123
|
+
wrapOptions.lang = lang;
|
|
124
|
+
}
|
|
122
125
|
const html = wrapHtml(
|
|
123
126
|
content,
|
|
124
127
|
hydrationScript,
|
|
@@ -129,7 +132,7 @@ async function generateSinglePage(pattern, outDir, program, params = {}) {
|
|
|
129
132
|
return outputPath;
|
|
130
133
|
}
|
|
131
134
|
async function generateStaticPages(routes, outDir, options = {}) {
|
|
132
|
-
const { staticPathsProvider } = options;
|
|
135
|
+
const { staticPathsProvider, lang } = options;
|
|
133
136
|
const generatedPaths = [];
|
|
134
137
|
const pageRoutes = routes.filter((r) => r.type === "page");
|
|
135
138
|
for (const route of pageRoutes) {
|
|
@@ -154,13 +157,14 @@ async function generateStaticPages(routes, outDir, options = {}) {
|
|
|
154
157
|
resolvedPattern,
|
|
155
158
|
outDir,
|
|
156
159
|
program,
|
|
157
|
-
pathData.params
|
|
160
|
+
pathData.params,
|
|
161
|
+
lang
|
|
158
162
|
);
|
|
159
163
|
generatedPaths.push(filePath);
|
|
160
164
|
}
|
|
161
165
|
} else {
|
|
162
166
|
const program = await resolvePageExport(pageExport, {});
|
|
163
|
-
const filePath = await generateSinglePage(route.pattern, outDir, program);
|
|
167
|
+
const filePath = await generateSinglePage(route.pattern, outDir, program, {}, lang);
|
|
164
168
|
generatedPaths.push(filePath);
|
|
165
169
|
}
|
|
166
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constela/start",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "Meta-framework for Constela applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"ws": "^8.18.0",
|
|
47
47
|
"@constela/compiler": "0.12.0",
|
|
48
48
|
"@constela/core": "0.13.0",
|
|
49
|
+
"@constela/server": "9.0.0",
|
|
49
50
|
"@constela/router": "15.0.0",
|
|
50
|
-
"@constela/runtime": "0.16.5"
|
|
51
|
-
"@constela/server": "9.0.0"
|
|
51
|
+
"@constela/runtime": "0.16.5"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/ws": "^8.5.0",
|