@constela/start 1.2.0 → 1.2.2

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 ADDED
@@ -0,0 +1,477 @@
1
+ # @constela/start
2
+
3
+ Meta-framework for Constela applications with dev server, build tools, and SSG.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @constela/start
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ This package provides a full-stack framework for building Constela applications:
14
+
15
+ - **Dev Server** - Vite-powered development server with HMR
16
+ - **Build** - Production builds with SSG support
17
+ - **File Router** - File-based routing conventions
18
+ - **Data Loading** - Glob, file, and API data sources
19
+ - **MDX** - Markdown with JSX component support
20
+ - **Layouts** - Nested layout composition
21
+ - **API Routes** - Server-side API endpoints
22
+ - **Middleware** - Request middleware chain
23
+ - **Edge Adapters** - Deploy to Cloudflare, Vercel, Deno
24
+
25
+ ## CLI Usage
26
+
27
+ ```bash
28
+ # Development server
29
+ npx constela-start dev --port 3000
30
+
31
+ # Production build
32
+ npx constela-start build --outDir dist
33
+
34
+ # Start production server
35
+ npx constela-start start --port 3000
36
+ ```
37
+
38
+ ## API Reference
39
+
40
+ ### createDevServer
41
+
42
+ Creates a development server with hot reload.
43
+
44
+ ```typescript
45
+ import { createDevServer } from '@constela/start';
46
+
47
+ const server = await createDevServer({
48
+ port: 3000,
49
+ host: 'localhost',
50
+ routesDir: 'src/routes',
51
+ publicDir: 'public',
52
+ layoutsDir: 'src/layouts',
53
+ css: {
54
+ links: ['/styles/main.css'],
55
+ },
56
+ });
57
+ ```
58
+
59
+ **DevServerOptions:**
60
+ - `port?: number` - Server port (default: 3000)
61
+ - `host?: string` - Server host (default: localhost)
62
+ - `routesDir?: string` - Routes directory (default: src/routes)
63
+ - `publicDir?: string` - Static files directory (default: public)
64
+ - `layoutsDir?: string` - Layouts directory
65
+ - `css?: { links?: string[] }` - CSS configuration
66
+
67
+ ### build
68
+
69
+ Builds the application for production.
70
+
71
+ ```typescript
72
+ import { build } from '@constela/start';
73
+
74
+ const result = await build({
75
+ outDir: 'dist',
76
+ routesDir: 'src/routes',
77
+ publicDir: 'public',
78
+ layoutsDir: 'src/layouts',
79
+ css: { links: ['/styles/main.css'] },
80
+ target: 'node', // or 'edge'
81
+ });
82
+
83
+ console.log('Routes:', result.routes);
84
+ console.log('Files:', result.files);
85
+ ```
86
+
87
+ **BuildOptions:**
88
+ - `outDir?: string` - Output directory (default: dist)
89
+ - `routesDir?: string` - Pages directory
90
+ - `publicDir?: string` - Static files directory
91
+ - `layoutsDir?: string` - Layouts directory
92
+ - `css?: { links?: string[] }` - CSS configuration
93
+ - `target?: 'node' | 'edge'` - Build target
94
+
95
+ ### generateStaticPages
96
+
97
+ Generates static HTML pages for SSG.
98
+
99
+ ```typescript
100
+ import { generateStaticPages } from '@constela/start';
101
+
102
+ const files = await generateStaticPages({
103
+ routesDir: 'src/routes',
104
+ outDir: 'dist',
105
+ layoutsDir: 'src/layouts',
106
+ });
107
+ ```
108
+
109
+ ## File-Based Routing
110
+
111
+ ### scanRoutes
112
+
113
+ Discovers routes from the file system.
114
+
115
+ ```typescript
116
+ import { scanRoutes } from '@constela/start';
117
+
118
+ const routes = await scanRoutes('src/routes');
119
+ // [{ path: '/', file: 'index.json', type: 'page' }, ...]
120
+ ```
121
+
122
+ **Routing Conventions:**
123
+
124
+ | File | Route |
125
+ |------|-------|
126
+ | `index.json` | `/` |
127
+ | `about.json` | `/about` |
128
+ | `users/[id].json` | `/users/:id` |
129
+ | `docs/[...slug].json` | `/docs/*` |
130
+ | `api/users.ts` | `/api/users` (API route) |
131
+ | `_middleware.ts` | Middleware |
132
+
133
+ ### filePathToPattern
134
+
135
+ Converts file paths to route patterns.
136
+
137
+ ```typescript
138
+ import { filePathToPattern } from '@constela/start';
139
+
140
+ filePathToPattern('users/[id].json'); // '/users/:id'
141
+ filePathToPattern('docs/[...slug].json'); // '/docs/*'
142
+ ```
143
+
144
+ ## Data Loading
145
+
146
+ ### DataLoader
147
+
148
+ Loads data from various sources.
149
+
150
+ ```typescript
151
+ import { DataLoader } from '@constela/start';
152
+
153
+ const loader = new DataLoader('src/routes');
154
+
155
+ // Glob pattern
156
+ const posts = await loader.loadGlob('content/blog/*.mdx', 'mdx');
157
+
158
+ // Single file
159
+ const config = await loader.loadFile('data/config.json');
160
+
161
+ // API endpoint
162
+ const users = await loader.loadApi('https://api.example.com/users');
163
+ ```
164
+
165
+ ### Data Source Types
166
+
167
+ **Glob Pattern:**
168
+ ```json
169
+ {
170
+ "data": {
171
+ "posts": {
172
+ "type": "glob",
173
+ "pattern": "content/blog/*.mdx",
174
+ "transform": "mdx"
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ **File:**
181
+ ```json
182
+ {
183
+ "data": {
184
+ "config": {
185
+ "type": "file",
186
+ "path": "data/config.json"
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ **API:**
193
+ ```json
194
+ {
195
+ "data": {
196
+ "users": {
197
+ "type": "api",
198
+ "url": "https://api.example.com/users"
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### Transforms
205
+
206
+ - `mdx` - Markdown with JSX to Constela nodes
207
+ - `yaml` - YAML to JSON
208
+ - `csv` - CSV to array of objects
209
+
210
+ ## MDX Support
211
+
212
+ ### mdxToConstela
213
+
214
+ Transforms MDX content to Constela view nodes.
215
+
216
+ ```typescript
217
+ import { mdxToConstela } from '@constela/start';
218
+
219
+ const result = await mdxToConstela(mdxContent, {
220
+ components: {
221
+ Callout: {
222
+ params: { type: { type: 'string' } },
223
+ view: { kind: 'element', tag: 'div', ... },
224
+ },
225
+ },
226
+ });
227
+ ```
228
+
229
+ **Supported Markdown:**
230
+ - Headings, paragraphs, emphasis, strong
231
+ - Links, images, lists (ordered/unordered)
232
+ - Blockquotes, tables (GFM)
233
+ - Code blocks with language
234
+ - Horizontal rules, line breaks
235
+
236
+ **MDX Extensions:**
237
+ - JSX elements
238
+ - Custom components (PascalCase)
239
+ - Expression interpolation
240
+
241
+ ### Static Paths with MDX
242
+
243
+ ```json
244
+ {
245
+ "data": {
246
+ "docs": {
247
+ "type": "glob",
248
+ "pattern": "content/docs/*.mdx",
249
+ "transform": "mdx"
250
+ }
251
+ },
252
+ "route": {
253
+ "path": "/docs/:slug",
254
+ "getStaticPaths": {
255
+ "source": "docs",
256
+ "params": {
257
+ "slug": { "expr": "get", "base": { "expr": "var", "name": "item" }, "path": "slug" }
258
+ }
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ ## Layout System
265
+
266
+ ### LayoutResolver
267
+
268
+ Manages layout discovery and composition.
269
+
270
+ ```typescript
271
+ import { LayoutResolver } from '@constela/start';
272
+
273
+ const resolver = new LayoutResolver('src/layouts');
274
+ await resolver.scan();
275
+
276
+ if (resolver.has('docs')) {
277
+ const layout = await resolver.load('docs');
278
+ }
279
+ ```
280
+
281
+ ### Layout Definition
282
+
283
+ ```json
284
+ {
285
+ "version": "1.0",
286
+ "type": "layout",
287
+ "view": {
288
+ "kind": "element",
289
+ "tag": "div",
290
+ "children": [
291
+ { "kind": "component", "name": "Header" },
292
+ { "kind": "slot" },
293
+ { "kind": "component", "name": "Footer" }
294
+ ]
295
+ }
296
+ }
297
+ ```
298
+
299
+ **Named Slots:**
300
+ ```json
301
+ { "kind": "slot", "name": "sidebar" }
302
+ ```
303
+
304
+ ### Page with Layout
305
+
306
+ ```json
307
+ {
308
+ "route": {
309
+ "path": "/docs/:slug",
310
+ "layout": "docs",
311
+ "layoutParams": {
312
+ "title": { "expr": "data", "name": "doc", "path": "title" }
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ ## API Routes
319
+
320
+ ### createAPIHandler
321
+
322
+ Creates an API route handler.
323
+
324
+ ```typescript
325
+ import { createAPIHandler } from '@constela/start';
326
+
327
+ const handler = createAPIHandler({
328
+ GET: async (ctx) => {
329
+ return new Response(JSON.stringify({ users: [] }), {
330
+ headers: { 'Content-Type': 'application/json' },
331
+ });
332
+ },
333
+ POST: async (ctx) => {
334
+ const body = await ctx.request.json();
335
+ // ...
336
+ },
337
+ });
338
+ ```
339
+
340
+ **APIContext:**
341
+ ```typescript
342
+ interface APIContext {
343
+ params: Record<string, string>;
344
+ query: URLSearchParams;
345
+ request: Request;
346
+ }
347
+ ```
348
+
349
+ **Supported Methods:** GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
350
+
351
+ ## Middleware
352
+
353
+ ### createMiddlewareChain
354
+
355
+ Creates a middleware chain for request processing.
356
+
357
+ ```typescript
358
+ import { createMiddlewareChain } from '@constela/start';
359
+
360
+ const chain = createMiddlewareChain([
361
+ async (ctx, next) => {
362
+ console.log('Request:', ctx.url);
363
+ const response = await next();
364
+ console.log('Response:', response.status);
365
+ return response;
366
+ },
367
+ async (ctx, next) => {
368
+ ctx.locals.user = await getUser(ctx.request);
369
+ return next();
370
+ },
371
+ ]);
372
+ ```
373
+
374
+ **MiddlewareContext:**
375
+ ```typescript
376
+ interface MiddlewareContext {
377
+ request: Request;
378
+ params: Record<string, string>;
379
+ url: URL;
380
+ locals: Record<string, unknown>;
381
+ }
382
+ ```
383
+
384
+ ## Edge Adapters
385
+
386
+ ### createAdapter
387
+
388
+ Creates an adapter for edge deployment.
389
+
390
+ ```typescript
391
+ import { createAdapter } from '@constela/start';
392
+
393
+ const adapter = createAdapter({
394
+ platform: 'cloudflare', // or 'vercel', 'deno', 'node'
395
+ routes: scannedRoutes,
396
+ fallback: notFoundProgram,
397
+ });
398
+
399
+ // Cloudflare Workers
400
+ export default {
401
+ fetch: adapter.fetch,
402
+ };
403
+ ```
404
+
405
+ **Platforms:**
406
+ - `cloudflare` - Cloudflare Workers
407
+ - `vercel` - Vercel Edge Functions
408
+ - `deno` - Deno Deploy
409
+ - `node` - Node.js
410
+
411
+ ## Page Types
412
+
413
+ ### Static Page
414
+
415
+ ```typescript
416
+ // pages/about.ts
417
+ import type { CompiledProgram } from '@constela/compiler';
418
+
419
+ const page: CompiledProgram = {
420
+ version: '1.0',
421
+ state: {},
422
+ actions: {},
423
+ view: { kind: 'element', tag: 'div', ... },
424
+ };
425
+
426
+ export default page;
427
+ ```
428
+
429
+ ### Dynamic Page
430
+
431
+ ```typescript
432
+ // pages/users/[id].ts
433
+ import type { PageExportFunction, StaticPathsResult } from '@constela/start';
434
+
435
+ export const getStaticPaths = async (): Promise<StaticPathsResult> => ({
436
+ paths: [
437
+ { params: { id: '1' } },
438
+ { params: { id: '2' } },
439
+ ],
440
+ });
441
+
442
+ const page: PageExportFunction = async (params) => {
443
+ const user = await fetchUser(params.id);
444
+ return compileUserPage(user);
445
+ };
446
+
447
+ export default page;
448
+ ```
449
+
450
+ ## Configuration
451
+
452
+ ### ConstelaConfig
453
+
454
+ ```typescript
455
+ interface ConstelaConfig {
456
+ adapter?: 'cloudflare' | 'vercel' | 'deno' | 'node';
457
+ ssg?: {
458
+ routes?: string[];
459
+ };
460
+ }
461
+ ```
462
+
463
+ ## Exports
464
+
465
+ ### Runtime Entry Points
466
+
467
+ ```typescript
468
+ // Client-side hydration
469
+ import '@constela/start/client';
470
+
471
+ // Server-side rendering
472
+ import { render } from '@constela/start/server';
473
+ ```
474
+
475
+ ## License
476
+
477
+ MIT
@@ -1880,7 +1880,7 @@ import { analyzeLayoutPass, transformLayoutPass, composeLayoutWithPage } from "@
1880
1880
  var DEFAULT_PORT = 3e3;
1881
1881
  var DEFAULT_HOST = "localhost";
1882
1882
  var DEFAULT_PUBLIC_DIR = "public";
1883
- var DEFAULT_ROUTES_DIR = "src/pages";
1883
+ var DEFAULT_ROUTES_DIR = "src/routes";
1884
1884
  function matchRoute(url, routes) {
1885
1885
  const normalizedUrl = url === "/" ? "/" : url.replace(/\/$/, "");
1886
1886
  const urlSegments = normalizedUrl.split("/").filter(Boolean);
@@ -2001,7 +2001,7 @@ async function createDevServer(options = {}) {
2001
2001
  return actualPort;
2002
2002
  },
2003
2003
  async listen() {
2004
- return new Promise((resolve4, reject) => {
2004
+ return new Promise((resolve5, reject) => {
2005
2005
  httpServer = createServer(async (req, res) => {
2006
2006
  const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
2007
2007
  const pathname = url.pathname;
@@ -2138,7 +2138,7 @@ h1 { color: #666; }
2138
2138
  if (address) {
2139
2139
  actualPort = address.port;
2140
2140
  }
2141
- resolve4();
2141
+ resolve5();
2142
2142
  });
2143
2143
  });
2144
2144
  },
@@ -2147,9 +2147,9 @@ h1 { color: #666; }
2147
2147
  await viteServer.close();
2148
2148
  viteServer = null;
2149
2149
  }
2150
- return new Promise((resolve4, reject) => {
2150
+ return new Promise((resolve5, reject) => {
2151
2151
  if (!httpServer) {
2152
- resolve4();
2152
+ resolve5();
2153
2153
  return;
2154
2154
  }
2155
2155
  httpServer.closeAllConnections();
@@ -2158,7 +2158,7 @@ h1 { color: #666; }
2158
2158
  reject(err);
2159
2159
  } else {
2160
2160
  httpServer = null;
2161
- resolve4();
2161
+ resolve5();
2162
2162
  }
2163
2163
  });
2164
2164
  });
@@ -2170,7 +2170,7 @@ h1 { color: #666; }
2170
2170
  // src/build/index.ts
2171
2171
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
2172
2172
  import { mkdir as mkdir2, writeFile, cp, readdir } from "fs/promises";
2173
- import { join as join9, dirname as dirname5, relative as relative2, basename as basename4 } from "path";
2173
+ import { join as join9, dirname as dirname5, relative as relative2, basename as basename4, isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
2174
2174
 
2175
2175
  // src/build/bundler.ts
2176
2176
  import * as esbuild from "esbuild";
@@ -2470,8 +2470,11 @@ async function build2(options) {
2470
2470
  };
2471
2471
  }
2472
2472
  const runtimePath = await bundleRuntime({ outDir });
2473
+ const absoluteRoutesDir = isAbsolute2(routesDir) ? routesDir : resolve4(routesDir);
2474
+ const projectRoot = dirname5(dirname5(absoluteRoutesDir));
2473
2475
  for (const route of jsonPages) {
2474
- const relPath = relative2(routesDir, route.file);
2476
+ const relPathFromRoutesDir = relative2(absoluteRoutesDir, route.file);
2477
+ const relPathFromProjectRoot = relative2(projectRoot, route.file);
2475
2478
  const content = readFileSync5(route.file, "utf-8");
2476
2479
  const page = validateJsonPage(content, route.file);
2477
2480
  if (isDynamicRoute(route.pattern)) {
@@ -2485,8 +2488,8 @@ async function build2(options) {
2485
2488
  for (const pathEntry of staticPaths.paths) {
2486
2489
  const params = pathEntry.params;
2487
2490
  const outputPath = paramsToOutputPath(route.pattern, params, outDir);
2488
- const loader = new JsonPageLoader(routesDir);
2489
- let pageInfo = await loader.loadPage(relPath);
2491
+ const loader = new JsonPageLoader(projectRoot);
2492
+ let pageInfo = await loader.loadPage(relPathFromProjectRoot);
2490
2493
  if (layoutsDir) {
2491
2494
  pageInfo = await processLayouts(pageInfo, layoutsDir);
2492
2495
  }
@@ -2503,9 +2506,9 @@ async function build2(options) {
2503
2506
  routes.push(routePath);
2504
2507
  }
2505
2508
  } else {
2506
- const outputPath = getOutputPath(relPath, outDir);
2507
- const loader = new JsonPageLoader(routesDir);
2508
- let pageInfo = await loader.loadPage(relPath);
2509
+ const outputPath = getOutputPath(relPathFromRoutesDir, outDir);
2510
+ const loader = new JsonPageLoader(projectRoot);
2511
+ let pageInfo = await loader.loadPage(relPathFromProjectRoot);
2509
2512
  if (layoutsDir) {
2510
2513
  pageInfo = await processLayouts(pageInfo, layoutsDir);
2511
2514
  }
@@ -2527,6 +2530,47 @@ async function build2(options) {
2527
2530
  };
2528
2531
  }
2529
2532
 
2533
+ // src/config/config-loader.ts
2534
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2535
+ import { join as join10 } from "path";
2536
+ var CONFIG_FILENAME = "constela.config.json";
2537
+ async function loadConfig(projectRoot) {
2538
+ const configPath = join10(projectRoot, CONFIG_FILENAME);
2539
+ if (!existsSync8(configPath)) {
2540
+ return {};
2541
+ }
2542
+ let content;
2543
+ try {
2544
+ content = readFileSync6(configPath, "utf-8");
2545
+ } catch (error) {
2546
+ throw new Error(`Failed to read config file: ${configPath}`);
2547
+ }
2548
+ try {
2549
+ return JSON.parse(content);
2550
+ } catch {
2551
+ throw new Error(`Invalid JSON in config file: ${configPath}`);
2552
+ }
2553
+ }
2554
+ async function resolveConfig(fileConfig, cliOptions) {
2555
+ if (!cliOptions) {
2556
+ return { ...fileConfig };
2557
+ }
2558
+ const result = { ...fileConfig };
2559
+ if (cliOptions.css !== void 0) result.css = cliOptions.css;
2560
+ if (cliOptions.layoutsDir !== void 0) result.layoutsDir = cliOptions.layoutsDir;
2561
+ if (cliOptions.routesDir !== void 0) result.routesDir = cliOptions.routesDir;
2562
+ if (cliOptions.publicDir !== void 0) result.publicDir = cliOptions.publicDir;
2563
+ if (cliOptions.outDir !== void 0) {
2564
+ result.build = { ...result.build, outDir: cliOptions.outDir };
2565
+ }
2566
+ if (cliOptions.port !== void 0 || cliOptions.host !== void 0) {
2567
+ result.dev = { ...result.dev };
2568
+ if (cliOptions.port !== void 0) result.dev.port = cliOptions.port;
2569
+ if (cliOptions.host !== void 0) result.dev.host = cliOptions.host;
2570
+ }
2571
+ return result;
2572
+ }
2573
+
2530
2574
  export {
2531
2575
  filePathToPattern,
2532
2576
  scanRoutes,
@@ -2549,5 +2593,7 @@ export {
2549
2593
  loadLayout,
2550
2594
  LayoutResolver,
2551
2595
  createDevServer,
2552
- build2 as build
2596
+ build2 as build,
2597
+ loadConfig,
2598
+ resolveConfig
2553
2599
  };
package/dist/cli/index.js CHANGED
@@ -1,54 +1,13 @@
1
1
  import {
2
2
  build,
3
- createDevServer
4
- } from "../chunk-6AN7W7KZ.js";
3
+ createDevServer,
4
+ loadConfig,
5
+ resolveConfig
6
+ } from "../chunk-3LUPO6QC.js";
5
7
  import "../chunk-PUTC5BCP.js";
6
8
 
7
9
  // src/cli/index.ts
8
10
  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
52
11
  var devHandler = async (options) => {
53
12
  const port = parseInt(options.port, 10);
54
13
  const host = options.host ?? "localhost";
package/dist/index.d.ts CHANGED
@@ -522,4 +522,35 @@ declare class DataLoader {
522
522
  getCacheSize(): number;
523
523
  }
524
524
 
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 };
525
+ interface ConstelaConfigFile {
526
+ css?: string | string[];
527
+ layoutsDir?: string;
528
+ routesDir?: string;
529
+ publicDir?: string;
530
+ build?: {
531
+ outDir?: string;
532
+ };
533
+ dev?: {
534
+ port?: number;
535
+ host?: string;
536
+ };
537
+ }
538
+ interface CLIOptions {
539
+ css?: string | undefined;
540
+ layoutsDir?: string | undefined;
541
+ routesDir?: string | undefined;
542
+ publicDir?: string | undefined;
543
+ outDir?: string | undefined;
544
+ port?: number | undefined;
545
+ host?: string | undefined;
546
+ }
547
+ /**
548
+ * Load config from constela.config.json in project root
549
+ */
550
+ declare function loadConfig(projectRoot: string): Promise<ConstelaConfigFile>;
551
+ /**
552
+ * Merge file config with CLI options (CLI takes precedence)
553
+ */
554
+ declare function resolveConfig(fileConfig: ConstelaConfigFile, cliOptions?: CLIOptions): Promise<ConstelaConfigFile>;
555
+
556
+ export { type APIContext, type APIModule, type BuildOptions, type CLIOptions, type ComponentDef$1 as ComponentDef, type ConstelaConfig, type ConstelaConfigFile, 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, loadConfig, loadFile, loadGlob, loadLayout, mdxContentToNode, mdxToConstela, resolveConfig, resolveLayout, resolvePageExport, resolveStaticFile, scanLayouts, scanRoutes, transformCsv, transformMdx, transformYaml };
package/dist/index.js CHANGED
@@ -9,11 +9,13 @@ import {
9
9
  isPathSafe,
10
10
  loadApi,
11
11
  loadComponentDefinitions,
12
+ loadConfig,
12
13
  loadFile,
13
14
  loadGlob,
14
15
  loadLayout,
15
16
  mdxContentToNode,
16
17
  mdxToConstela,
18
+ resolveConfig,
17
19
  resolveLayout,
18
20
  resolveStaticFile,
19
21
  scanLayouts,
@@ -21,7 +23,7 @@ import {
21
23
  transformCsv,
22
24
  transformMdx,
23
25
  transformYaml
24
- } from "./chunk-6AN7W7KZ.js";
26
+ } from "./chunk-3LUPO6QC.js";
25
27
  import {
26
28
  generateHydrationScript,
27
29
  renderPage,
@@ -332,11 +334,13 @@ export {
332
334
  isPathSafe,
333
335
  loadApi,
334
336
  loadComponentDefinitions,
337
+ loadConfig,
335
338
  loadFile,
336
339
  loadGlob,
337
340
  loadLayout,
338
341
  mdxContentToNode,
339
342
  mdxToConstela,
343
+ resolveConfig,
340
344
  resolveLayout,
341
345
  resolvePageExport,
342
346
  resolveStaticFile,
@@ -22,11 +22,7 @@ function initClient(options) {
22
22
  getState: (name) => appInstance.getState(name),
23
23
  setState: (name, value) => appInstance.setState(name, value),
24
24
  subscribe: (name, fn) => {
25
- if (typeof appInstance.subscribe === "function") {
26
- return appInstance.subscribe(name, fn);
27
- }
28
- return () => {
29
- };
25
+ return appInstance.subscribe(name, fn);
30
26
  }
31
27
  };
32
28
  try {
@@ -47,10 +43,8 @@ function initClient(options) {
47
43
  if (currentTheme) {
48
44
  updateThemeClass(currentTheme);
49
45
  }
50
- if (appInstance.subscribe) {
51
- const unsubscribeTheme = appInstance.subscribe("theme", updateThemeClass);
52
- cleanupFns.push(unsubscribeTheme);
53
- }
46
+ const unsubscribeTheme = appInstance.subscribe("theme", updateThemeClass);
47
+ cleanupFns.push(unsubscribeTheme);
54
48
  }
55
49
  let destroyed = false;
56
50
  return {
@@ -67,6 +61,9 @@ function initClient(options) {
67
61
  },
68
62
  getState(name) {
69
63
  return appInstance.getState(name);
64
+ },
65
+ subscribe(name, fn) {
66
+ return appInstance.subscribe(name, fn);
70
67
  }
71
68
  };
72
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constela/start",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Meta-framework for Constela applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,10 +41,10 @@
41
41
  "unified": "^11.0.0",
42
42
  "vite": "^6.0.0",
43
43
  "@constela/compiler": "0.7.0",
44
- "@constela/server": "3.0.0",
45
- "@constela/router": "8.0.0",
46
44
  "@constela/core": "0.7.0",
47
- "@constela/runtime": "0.10.0"
45
+ "@constela/router": "8.0.0",
46
+ "@constela/runtime": "0.10.1",
47
+ "@constela/server": "3.0.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/mdast": "^4.0.4",