@constela/start 1.1.0 → 1.2.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.
@@ -0,0 +1,112 @@
1
+ // src/runtime/entry-server.ts
2
+ import { renderToString } from "@constela/server";
3
+ async function renderPage(program, ctx) {
4
+ const options = {
5
+ route: {
6
+ params: ctx.params,
7
+ query: Object.fromEntries(ctx.query.entries()),
8
+ path: ctx.url
9
+ }
10
+ };
11
+ if (program.importData) {
12
+ options.imports = program.importData;
13
+ }
14
+ return await renderToString(program, options);
15
+ }
16
+ function escapeJsString(str) {
17
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
18
+ }
19
+ function escapeJsonForScript(json) {
20
+ return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
21
+ }
22
+ function serializeProgram(program) {
23
+ const serializable = {
24
+ ...program,
25
+ // Convert Map to Object if actions is a Map
26
+ actions: program.actions instanceof Map ? Object.fromEntries(program.actions.entries()) : program.actions
27
+ };
28
+ return JSON.stringify(serializable);
29
+ }
30
+ function toJsIdentifier(id) {
31
+ let result = id.replace(/[^a-zA-Z0-9]/g, "_");
32
+ if (/^[0-9]/.test(result)) {
33
+ result = "_" + result;
34
+ }
35
+ return result;
36
+ }
37
+ function generateHydrationScript(program, widgets, route) {
38
+ const serializedProgram = escapeJsonForScript(serializeProgram(program));
39
+ const hasWidgets = widgets && widgets.length > 0;
40
+ const imports = hasWidgets ? `import { hydrateApp, createApp } from '@constela/runtime';` : `import { hydrateApp } from '@constela/runtime';`;
41
+ const widgetDeclarations = hasWidgets ? widgets.map((widget) => {
42
+ const jsId = toJsIdentifier(widget.id);
43
+ const serializedWidget = escapeJsonForScript(
44
+ serializeProgram(widget.program)
45
+ );
46
+ return `const widgetProgram_${jsId} = ${serializedWidget};`;
47
+ }).join("\n") : "";
48
+ const widgetMounting = hasWidgets ? widgets.map((widget) => {
49
+ const jsId = toJsIdentifier(widget.id);
50
+ const escapedId = escapeJsString(widget.id);
51
+ return `
52
+ const container_${jsId} = document.getElementById('${escapedId}');
53
+ if (container_${jsId}) {
54
+ container_${jsId}.innerHTML = '';
55
+ createApp(widgetProgram_${jsId}, container_${jsId});
56
+ }`;
57
+ }).join("\n") : "";
58
+ const routeDeclaration = route ? `const route = ${escapeJsonForScript(JSON.stringify(route))};` : "";
59
+ const hydrateOptions = route ? `{
60
+ program,
61
+ container: document.getElementById('app'),
62
+ route
63
+ }` : `{
64
+ program,
65
+ container: document.getElementById('app')
66
+ }`;
67
+ return `${imports}
68
+
69
+ const program = ${serializedProgram};
70
+ ${routeDeclaration ? "\n" + routeDeclaration : ""}${widgetDeclarations ? "\n" + widgetDeclarations : ""}
71
+ hydrateApp(${hydrateOptions});${widgetMounting}`;
72
+ }
73
+ function wrapHtml(content, hydrationScript, head, options) {
74
+ const htmlClass = options?.theme === "dark" ? ' class="dark"' : "";
75
+ let processedScript = hydrationScript;
76
+ let importMapScript = "";
77
+ if (options?.runtimePath) {
78
+ if (!/^[a-zA-Z0-9/_.-]+$/.test(options.runtimePath)) {
79
+ throw new Error(`Invalid runtimePath: ${options.runtimePath}. Only alphanumeric characters, slashes, underscores, dots, and hyphens are allowed.`);
80
+ }
81
+ processedScript = hydrationScript.replace(
82
+ /from\s+['"]@constela\/runtime['"]/g,
83
+ `from '${options.runtimePath}'`
84
+ );
85
+ } else if (options?.importMap && Object.keys(options.importMap).length > 0) {
86
+ const importMapJson = JSON.stringify({ imports: options.importMap }, null, 2);
87
+ importMapScript = `<script type="importmap">
88
+ ${importMapJson}
89
+ </script>
90
+ `;
91
+ }
92
+ return `<!DOCTYPE html>
93
+ <html${htmlClass}>
94
+ <head>
95
+ <meta charset="utf-8">
96
+ <meta name="viewport" content="width=device-width, initial-scale=1">
97
+ ${importMapScript}${head ?? ""}
98
+ </head>
99
+ <body>
100
+ <div id="app">${content}</div>
101
+ <script type="module">
102
+ ${processedScript}
103
+ </script>
104
+ </body>
105
+ </html>`;
106
+ }
107
+
108
+ export {
109
+ renderPage,
110
+ generateHydrationScript,
111
+ wrapHtml
112
+ };
@@ -2,15 +2,22 @@ import { Command } from 'commander';
2
2
 
3
3
  type DevHandler = (options: {
4
4
  port: string;
5
- host?: string;
5
+ host?: string | undefined;
6
+ css?: string | undefined;
7
+ layoutsDir?: string | undefined;
6
8
  }) => Promise<{
7
9
  port: number;
8
10
  }>;
9
11
  type BuildHandler = (options: {
10
- outDir?: string;
12
+ outDir?: string | undefined;
13
+ css?: string | undefined;
14
+ layoutsDir?: string | undefined;
11
15
  }) => Promise<void>;
12
16
  type StartHandler = (options: {
13
17
  port: string;
18
+ host?: string | undefined;
19
+ css?: string | undefined;
20
+ layoutsDir?: string | undefined;
14
21
  }) => Promise<{
15
22
  port: number;
16
23
  }>;
package/dist/cli/index.js CHANGED
@@ -1,14 +1,63 @@
1
1
  import {
2
2
  build,
3
3
  createDevServer
4
- } from "../chunk-JXIOHPG5.js";
4
+ } from "../chunk-6AN7W7KZ.js";
5
+ import "../chunk-PUTC5BCP.js";
5
6
 
6
7
  // src/cli/index.ts
7
8
  import { Command } from "commander";
9
+
10
+ // src/config/config-loader.ts
11
+ import { existsSync, readFileSync } from "fs";
12
+ import { join } from "path";
13
+ var CONFIG_FILENAME = "constela.config.json";
14
+ async function loadConfig(projectRoot) {
15
+ const configPath = join(projectRoot, CONFIG_FILENAME);
16
+ if (!existsSync(configPath)) {
17
+ return {};
18
+ }
19
+ let content;
20
+ try {
21
+ content = readFileSync(configPath, "utf-8");
22
+ } catch (error) {
23
+ throw new Error(`Failed to read config file: ${configPath}`);
24
+ }
25
+ try {
26
+ return JSON.parse(content);
27
+ } catch {
28
+ throw new Error(`Invalid JSON in config file: ${configPath}`);
29
+ }
30
+ }
31
+ async function resolveConfig(fileConfig, cliOptions) {
32
+ if (!cliOptions) {
33
+ return { ...fileConfig };
34
+ }
35
+ const result = { ...fileConfig };
36
+ if (cliOptions.css !== void 0) result.css = cliOptions.css;
37
+ if (cliOptions.layoutsDir !== void 0) result.layoutsDir = cliOptions.layoutsDir;
38
+ if (cliOptions.routesDir !== void 0) result.routesDir = cliOptions.routesDir;
39
+ if (cliOptions.publicDir !== void 0) result.publicDir = cliOptions.publicDir;
40
+ if (cliOptions.outDir !== void 0) {
41
+ result.build = { ...result.build, outDir: cliOptions.outDir };
42
+ }
43
+ if (cliOptions.port !== void 0 || cliOptions.host !== void 0) {
44
+ result.dev = { ...result.dev };
45
+ if (cliOptions.port !== void 0) result.dev.port = cliOptions.port;
46
+ if (cliOptions.host !== void 0) result.dev.host = cliOptions.host;
47
+ }
48
+ return result;
49
+ }
50
+
51
+ // src/cli/index.ts
8
52
  var devHandler = async (options) => {
9
53
  const port = parseInt(options.port, 10);
10
54
  const host = options.host ?? "localhost";
11
- const server = await createDevServer({ port, host });
55
+ const server = await createDevServer({
56
+ port,
57
+ host,
58
+ ...options.css ? { css: options.css } : {},
59
+ ...options.layoutsDir ? { layoutsDir: options.layoutsDir } : {}
60
+ });
12
61
  await server.listen();
13
62
  console.log(`Development server running at http://${host}:${server.port}`);
14
63
  process.on("SIGINT", async () => {
@@ -20,14 +69,24 @@ var devHandler = async (options) => {
20
69
  };
21
70
  var buildHandler = async (options) => {
22
71
  console.log("Building for production...");
23
- await build(options.outDir ? { outDir: options.outDir } : {});
72
+ await build({
73
+ outDir: options.outDir,
74
+ layoutsDir: options.layoutsDir,
75
+ css: options.css
76
+ });
24
77
  console.log("Build complete");
25
78
  };
26
79
  var startHandler = async (options) => {
27
80
  const port = parseInt(options.port, 10);
28
- const server = await createDevServer({ port, host: "0.0.0.0" });
81
+ const host = options.host ?? "0.0.0.0";
82
+ const server = await createDevServer({
83
+ port,
84
+ host,
85
+ ...options.css ? { css: options.css } : {},
86
+ ...options.layoutsDir ? { layoutsDir: options.layoutsDir } : {}
87
+ });
29
88
  await server.listen();
30
- console.log(`Production server running at http://0.0.0.0:${server.port}`);
89
+ console.log(`Production server running at http://${host}:${server.port}`);
31
90
  process.on("SIGINT", async () => {
32
91
  console.log("\nShutting down server...");
33
92
  await server.close();
@@ -47,14 +106,43 @@ function setStartHandler(handler) {
47
106
  function createCLI() {
48
107
  const program = new Command();
49
108
  program.name("constela-start").version("0.1.0").description("Meta-framework for Constela applications");
50
- program.command("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("-h, --host <host>", "Host address").action(async (options) => {
109
+ program.command("dev").description("Start development server").option("-p, --port <port>", "Port number", "3000").option("-h, --host <host>", "Host address").option("-c, --css <path>", "CSS entry point for Vite processing").option("-l, --layoutsDir <path>", "Layouts directory for layout composition").action(async (options) => {
51
110
  await devHandler(options);
52
111
  });
53
- program.command("build").description("Build for production").option("-o, --outDir <outDir>", "Output directory").action(async (options) => {
54
- await buildHandler(options);
112
+ program.command("build").description("Build for production").option("-o, --outDir <outDir>", "Output directory").option("-c, --css <path>", "CSS entry point for Vite processing").option("-l, --layoutsDir <path>", "Layouts directory for layout composition").action(async (options) => {
113
+ const fileConfig = await loadConfig(process.cwd());
114
+ const resolved = await resolveConfig(fileConfig, {
115
+ outDir: options.outDir,
116
+ css: options.css,
117
+ layoutsDir: options.layoutsDir
118
+ });
119
+ const mergedOptions = {};
120
+ const outDirValue = options.outDir ?? resolved.build?.outDir;
121
+ if (outDirValue !== void 0) mergedOptions.outDir = outDirValue;
122
+ const cssValue = options.css ?? (typeof resolved.css === "string" ? resolved.css : resolved.css?.[0]);
123
+ if (cssValue !== void 0) mergedOptions.css = cssValue;
124
+ const layoutsDirValue = options.layoutsDir ?? resolved.layoutsDir;
125
+ if (layoutsDirValue !== void 0) mergedOptions.layoutsDir = layoutsDirValue;
126
+ await buildHandler(mergedOptions);
55
127
  });
56
- program.command("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").action(async (options) => {
57
- await startHandler(options);
128
+ program.command("start").description("Start production server").option("-p, --port <port>", "Port number", "3000").option("-h, --host <host>", "Host address").option("-c, --css <path>", "CSS entry point for Vite processing").option("-l, --layoutsDir <path>", "Layouts directory for layout composition").action(async (options) => {
129
+ const fileConfig = await loadConfig(process.cwd());
130
+ const resolved = await resolveConfig(fileConfig, {
131
+ port: options.port ? parseInt(options.port, 10) : void 0,
132
+ host: options.host,
133
+ css: options.css,
134
+ layoutsDir: options.layoutsDir
135
+ });
136
+ const mergedOptions = {
137
+ port: options.port ?? (resolved.dev?.port ? String(resolved.dev.port) : "3000")
138
+ };
139
+ const hostValue = options.host ?? resolved.dev?.host;
140
+ if (hostValue !== void 0) mergedOptions.host = hostValue;
141
+ const cssValue = options.css ?? (typeof resolved.css === "string" ? resolved.css : resolved.css?.[0]);
142
+ if (cssValue !== void 0) mergedOptions.css = cssValue;
143
+ const layoutsDirValue = options.layoutsDir ?? resolved.layoutsDir;
144
+ if (layoutsDirValue !== void 0) mergedOptions.layoutsDir = layoutsDirValue;
145
+ await startHandler(mergedOptions);
58
146
  });
59
147
  return program;
60
148
  }
package/dist/index.d.ts CHANGED
@@ -57,10 +57,17 @@ interface PageModule {
57
57
  default: CompiledProgram | PageExportFunction;
58
58
  getStaticPaths?: () => Promise<StaticPathsResult> | StaticPathsResult;
59
59
  }
60
+ /**
61
+ * Single path entry from getStaticPaths
62
+ */
63
+ interface StaticPathEntry {
64
+ /** Route parameters for this path */
65
+ params: Record<string, string>;
66
+ /** Optional data to inject as __pathData in importData */
67
+ data?: unknown;
68
+ }
60
69
  interface StaticPathsResult {
61
- paths: Array<{
62
- params: Record<string, string>;
63
- }>;
70
+ paths: StaticPathEntry[];
64
71
  }
65
72
  /**
66
73
  * Constela configuration
@@ -81,14 +88,22 @@ interface DevServerOptions {
81
88
  host?: string;
82
89
  routesDir?: string;
83
90
  publicDir?: string;
91
+ /** Layouts directory for layout composition */
92
+ layoutsDir?: string;
93
+ /** CSS entry point(s) for Vite middleware processing */
94
+ css?: string | string[];
84
95
  }
85
96
  /**
86
97
  * Build options
87
98
  */
88
99
  interface BuildOptions {
89
- outDir?: string;
90
- routesDir?: string;
91
- target?: 'node' | 'edge';
100
+ outDir?: string | undefined;
101
+ routesDir?: string | undefined;
102
+ publicDir?: string | undefined;
103
+ layoutsDir?: string | undefined;
104
+ /** CSS entry point(s) for Vite middleware processing */
105
+ css?: string | string[] | undefined;
106
+ target?: 'node' | 'edge' | undefined;
92
107
  }
93
108
 
94
109
  /**
@@ -188,12 +203,13 @@ declare function resolveStaticFile(pathname: string, publicDir: string): StaticF
188
203
  interface BuildResult {
189
204
  outDir: string;
190
205
  routes: string[];
206
+ generatedFiles: string[];
191
207
  }
192
208
  /**
193
209
  * Build application for production
194
210
  *
195
211
  * @param options - Build options
196
- * @returns BuildResult with outDir and discovered routes
212
+ * @returns BuildResult with outDir, discovered routes, and generated files
197
213
  */
198
214
  declare function build(options?: BuildOptions): Promise<BuildResult>;
199
215
 
@@ -506,4 +522,4 @@ declare class DataLoader {
506
522
  getCacheSize(): number;
507
523
  }
508
524
 
509
- export { type APIContext, type APIModule, type BuildOptions, type ComponentDef$1 as ComponentDef, type ConstelaConfig, DataLoader, type DevServerOptions, type GenerateStaticPagesOptions, type GlobResult, type LayoutInfo, LayoutResolver, type MDXToConstelaOptions, type MdxGlobResult, type Middleware, type MiddlewareContext, type MiddlewareNext, type PageExportFunction, type PageModule, type ScannedLayout, type ScannedRoute, type StaticFileResult, type StaticPath, type StaticPathsProvider, type StaticPathsResult, build, createAPIHandler, createAdapter, createDevServer, createMiddlewareChain, filePathToPattern, generateStaticPages, generateStaticPaths, getMimeType, isPageExportFunction, isPathSafe, loadApi, loadComponentDefinitions, loadFile, loadGlob, loadLayout, mdxContentToNode, mdxToConstela, resolveLayout, resolvePageExport, resolveStaticFile, scanLayouts, scanRoutes, transformCsv, transformMdx, transformYaml };
525
+ export { type APIContext, type APIModule, type BuildOptions, type ComponentDef$1 as ComponentDef, type ConstelaConfig, DataLoader, type DevServerOptions, type GenerateStaticPagesOptions, type GlobResult, type LayoutInfo, LayoutResolver, type MDXToConstelaOptions, type MdxGlobResult, type Middleware, type MiddlewareContext, type MiddlewareNext, type PageExportFunction, type PageModule, type ScannedLayout, type ScannedRoute, type StaticFileResult, type StaticPath, type StaticPathEntry, type StaticPathsProvider, type StaticPathsResult, build, createAPIHandler, createAdapter, createDevServer, createMiddlewareChain, filePathToPattern, generateStaticPages, generateStaticPaths, getMimeType, isPageExportFunction, isPathSafe, loadApi, loadComponentDefinitions, loadFile, loadGlob, loadLayout, mdxContentToNode, mdxToConstela, resolveLayout, resolvePageExport, resolveStaticFile, scanLayouts, scanRoutes, transformCsv, transformMdx, transformYaml };