@constela/start 1.3.4 → 1.4.0

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 CHANGED
@@ -227,6 +227,22 @@ title: Getting Started
227
227
  </Callout>
228
228
  ```
229
229
 
230
+ ### Security
231
+
232
+ MDX attribute expressions are validated at compile time. Dangerous patterns like `require()`, `eval()`, or `window` in actual code will throw explicit errors:
233
+
234
+ ```mdx
235
+ <!-- Error: MDX attribute contains disallowed pattern: require -->
236
+ <Button data={require("module")} />
237
+ ```
238
+
239
+ However, these words are allowed inside string literals:
240
+
241
+ ```mdx
242
+ <!-- OK: "require" is inside a string literal -->
243
+ <PropsTable items={[{ description: "operations that require one" }]} />
244
+ ```
245
+
230
246
  ## Configuration
231
247
 
232
248
  Create `constela.config.json`:
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  generateHydrationScript,
3
+ generateMetaTags,
3
4
  renderPage,
4
5
  wrapHtml
5
- } from "./chunk-CYMS3K6V.js";
6
+ } from "./chunk-XAC4ETQU.js";
6
7
 
7
8
  // src/router/file-router.ts
8
9
  import fg from "fast-glob";
@@ -2172,7 +2173,13 @@ async function createDevServer(options = {}) {
2172
2173
  path: pathname
2173
2174
  };
2174
2175
  const hydrationScript = generateHydrationScript(composedProgram, widgets, routeContext);
2176
+ const metaTags = generateMetaTags(composedProgram.route, {
2177
+ params: match.params,
2178
+ query: Object.fromEntries(url.searchParams.entries()),
2179
+ path: pathname
2180
+ });
2175
2181
  const cssHead = css ? (Array.isArray(css) ? css : [css]).map((p) => `<link rel="stylesheet" href="/${p}">`).join("\n") : "";
2182
+ const head = [metaTags, cssHead].filter(Boolean).join("\n");
2176
2183
  const themeState = composedProgram.state?.["theme"];
2177
2184
  const initialTheme = themeState?.initial;
2178
2185
  const importMap = {
@@ -2182,7 +2189,7 @@ async function createDevServer(options = {}) {
2182
2189
  "marked": "/node_modules/marked/lib/marked.esm.js",
2183
2190
  "monaco-editor": "/node_modules/monaco-editor/esm/vs/editor/editor.api.js"
2184
2191
  };
2185
- const html = wrapHtml(content, hydrationScript, cssHead, {
2192
+ const html = wrapHtml(content, hydrationScript, head || void 0, {
2186
2193
  ...initialTheme ? {
2187
2194
  theme: initialTheme,
2188
2195
  defaultTheme: initialTheme,
@@ -136,9 +136,65 @@ ${processedScript}
136
136
  </body>
137
137
  </html>`;
138
138
  }
139
+ function escapeHtmlForMeta(str) {
140
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
141
+ }
142
+ function evaluateMetaExpression(expr, ctx) {
143
+ switch (expr.expr) {
144
+ case "lit":
145
+ return String(expr.value);
146
+ case "route":
147
+ if (expr.source === "param") {
148
+ return ctx.params[expr.name] || "";
149
+ } else if (expr.source === "query") {
150
+ return ctx.query[expr.name] || "";
151
+ } else if (expr.source === "path") {
152
+ return ctx.path;
153
+ }
154
+ return "";
155
+ case "bin":
156
+ if (expr.op === "+") {
157
+ return evaluateMetaExpression(expr.left, ctx) + evaluateMetaExpression(expr.right, ctx);
158
+ }
159
+ return "";
160
+ case "concat":
161
+ return expr.items.map((item) => evaluateMetaExpression(item, ctx)).join("");
162
+ default:
163
+ return "";
164
+ }
165
+ }
166
+ function generateMetaTags(route, ctx) {
167
+ if (!route) {
168
+ return "";
169
+ }
170
+ const tags = [];
171
+ if (route.title) {
172
+ const titleValue = evaluateMetaExpression(route.title, ctx);
173
+ if (titleValue) {
174
+ tags.push(`<title>${escapeHtmlForMeta(titleValue)}</title>`);
175
+ }
176
+ }
177
+ if (route.meta) {
178
+ for (const [key, expr] of Object.entries(route.meta)) {
179
+ const value = evaluateMetaExpression(expr, ctx);
180
+ if (!value) {
181
+ continue;
182
+ }
183
+ const escapedValue = escapeHtmlForMeta(value);
184
+ if (key.startsWith("og:") || key.startsWith("twitter:")) {
185
+ tags.push(`<meta property="${key}" content="${escapedValue}">`);
186
+ } else {
187
+ tags.push(`<meta name="${key}" content="${escapedValue}">`);
188
+ }
189
+ }
190
+ }
191
+ return tags.join("\n");
192
+ }
139
193
 
140
194
  export {
141
195
  renderPage,
142
196
  generateHydrationScript,
143
- wrapHtml
197
+ wrapHtml,
198
+ evaluateMetaExpression,
199
+ generateMetaTags
144
200
  };
package/dist/cli/index.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  hyperlink,
5
5
  loadConfig,
6
6
  resolveConfig
7
- } from "../chunk-QEXDD2F2.js";
8
- import "../chunk-CYMS3K6V.js";
7
+ } from "../chunk-6FV75N45.js";
8
+ import "../chunk-XAC4ETQU.js";
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -24,12 +24,13 @@ import {
24
24
  transformCsv,
25
25
  transformMdx,
26
26
  transformYaml
27
- } from "./chunk-QEXDD2F2.js";
27
+ } from "./chunk-6FV75N45.js";
28
28
  import {
29
29
  generateHydrationScript,
30
+ generateMetaTags,
30
31
  renderPage,
31
32
  wrapHtml
32
- } from "./chunk-CYMS3K6V.js";
33
+ } from "./chunk-XAC4ETQU.js";
33
34
 
34
35
  // src/build/ssg.ts
35
36
  import { mkdir, writeFile } from "fs/promises";
@@ -113,6 +114,11 @@ async function generateSinglePage(pattern, outDir, program, params = {}) {
113
114
  path: pattern
114
115
  };
115
116
  const hydrationScript = generateHydrationScript(program, void 0, routeContext);
117
+ const metaTags = generateMetaTags(program.route, {
118
+ params,
119
+ query: {},
120
+ path: pattern
121
+ });
116
122
  const wrapOptions = {};
117
123
  const themeState = program.state?.["theme"];
118
124
  if (themeState?.initial) {
@@ -122,7 +128,7 @@ async function generateSinglePage(pattern, outDir, program, params = {}) {
122
128
  const html = wrapHtml(
123
129
  content,
124
130
  hydrationScript,
125
- void 0,
131
+ metaTags || void 0,
126
132
  Object.keys(wrapOptions).length > 0 ? wrapOptions : void 0
127
133
  );
128
134
  await writeFile(outputPath, html, "utf-8");
@@ -1,4 +1,4 @@
1
- import { CompiledProgram } from '@constela/compiler';
1
+ import { CompiledProgram, CompiledExpression, CompiledRouteDefinition } from '@constela/compiler';
2
2
 
3
3
  /**
4
4
  * Server-side entry point for Constela applications
@@ -76,5 +76,21 @@ declare function generateHydrationScript(program: CompiledProgram, widgets?: Wid
76
76
  * @returns Complete HTML document string
77
77
  */
78
78
  declare function wrapHtml(content: string, hydrationScript: string, head?: string, options?: WrapHtmlOptions): string;
79
+ /**
80
+ * Context for evaluating meta tag expressions
81
+ */
82
+ interface MetaContext {
83
+ params: Record<string, string>;
84
+ query: Record<string, string>;
85
+ path: string;
86
+ }
87
+ /**
88
+ * Evaluates a compiled expression for meta tag values.
89
+ */
90
+ declare function evaluateMetaExpression(expr: CompiledExpression, ctx: MetaContext): string;
91
+ /**
92
+ * Generates HTML meta tags from route definition.
93
+ */
94
+ declare function generateMetaTags(route: CompiledRouteDefinition | undefined, ctx: MetaContext): string;
79
95
 
80
- export { type HydrationRouteContext, type SSRContext, type WidgetConfig, type WrapHtmlOptions, generateHydrationScript, renderPage, wrapHtml };
96
+ export { type HydrationRouteContext, type MetaContext, type SSRContext, type WidgetConfig, type WrapHtmlOptions, evaluateMetaExpression, generateHydrationScript, generateMetaTags, renderPage, wrapHtml };
@@ -1,10 +1,14 @@
1
1
  import {
2
+ evaluateMetaExpression,
2
3
  generateHydrationScript,
4
+ generateMetaTags,
3
5
  renderPage,
4
6
  wrapHtml
5
- } from "../chunk-CYMS3K6V.js";
7
+ } from "../chunk-XAC4ETQU.js";
6
8
  export {
9
+ evaluateMetaExpression,
7
10
  generateHydrationScript,
11
+ generateMetaTags,
8
12
  renderPage,
9
13
  wrapHtml
10
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/start",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "Meta-framework for Constela applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,11 +43,11 @@
43
43
  "postcss": "^8.5.0",
44
44
  "@tailwindcss/postcss": "^4.0.0",
45
45
  "tailwindcss": "^4.0.0",
46
- "@constela/compiler": "0.9.1",
47
- "@constela/core": "0.9.1",
48
- "@constela/server": "5.0.1",
49
- "@constela/runtime": "0.12.2",
50
- "@constela/router": "10.0.0"
46
+ "@constela/compiler": "0.10.0",
47
+ "@constela/router": "11.0.0",
48
+ "@constela/core": "0.10.0",
49
+ "@constela/runtime": "0.13.0",
50
+ "@constela/server": "6.0.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/mdast": "^4.0.4",