@zenithbuild/core 0.6.6 → 0.6.7

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,15 @@
1
+ export interface ZenithConfig {
2
+ router: boolean;
3
+ embeddedMarkupExpressions: boolean;
4
+ types: boolean;
5
+ typescriptDefault: boolean;
6
+ outDir: string;
7
+ pagesDir: string;
8
+ experimental: Record<string, never>;
9
+ strictDomLints: boolean;
10
+ }
11
+ type ConfigInput = Partial<ZenithConfig> | null | undefined;
12
+ export declare function validateConfig(config: ConfigInput): ZenithConfig;
13
+ export declare function loadConfig(projectRoot: string): Promise<ZenithConfig>;
14
+ export declare function getDefaults(): ZenithConfig;
15
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,78 @@
1
+ import { join } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ const DEFAULTS = {
4
+ router: false,
5
+ embeddedMarkupExpressions: false,
6
+ types: true,
7
+ typescriptDefault: true,
8
+ outDir: 'dist',
9
+ pagesDir: 'pages',
10
+ experimental: {},
11
+ strictDomLints: false
12
+ };
13
+ const SCHEMA = {
14
+ router: 'boolean',
15
+ embeddedMarkupExpressions: 'boolean',
16
+ types: 'boolean',
17
+ typescriptDefault: 'boolean',
18
+ outDir: 'string',
19
+ pagesDir: 'string',
20
+ experimental: 'object',
21
+ strictDomLints: 'boolean'
22
+ };
23
+ export function validateConfig(config) {
24
+ if (config === null || config === undefined) {
25
+ return { ...DEFAULTS };
26
+ }
27
+ if (typeof config !== 'object' || Array.isArray(config)) {
28
+ throw new Error('[Zenith:Config] Config must be a plain object');
29
+ }
30
+ for (const key of Object.keys(config)) {
31
+ if (!(key in SCHEMA)) {
32
+ throw new Error(`[Zenith:Config] Unknown key: "${key}"`);
33
+ }
34
+ }
35
+ const result = { ...DEFAULTS };
36
+ for (const [key, expectedType] of Object.entries(SCHEMA)) {
37
+ if (key in config) {
38
+ const value = config[key];
39
+ if (typeof value !== expectedType) {
40
+ throw new Error(`[Zenith:Config] Key "${key}" must be ${expectedType}, got ${typeof value}`);
41
+ }
42
+ if (expectedType === 'string' && typeof value === 'string' && value.trim() === '') {
43
+ throw new Error(`[Zenith:Config] Key "${key}" must be a non-empty string`);
44
+ }
45
+ if (key === 'experimental' && value) {
46
+ if (typeof value !== 'object' || Array.isArray(value)) {
47
+ throw new Error('[Zenith:Config] Key "experimental" must be a plain object');
48
+ }
49
+ result[key] = { ...DEFAULTS.experimental };
50
+ continue;
51
+ }
52
+ result[key] = value;
53
+ }
54
+ }
55
+ return result;
56
+ }
57
+ export async function loadConfig(projectRoot) {
58
+ const configPath = join(projectRoot, 'zenith.config.js');
59
+ try {
60
+ const url = pathToFileURL(configPath).href;
61
+ const mod = await import(url);
62
+ const raw = mod.default || mod;
63
+ return validateConfig(raw);
64
+ }
65
+ catch (err) {
66
+ const error = err;
67
+ if (error.code === 'ERR_MODULE_NOT_FOUND' ||
68
+ error.code === 'ENOENT' ||
69
+ error.message?.includes('Cannot find module') ||
70
+ error.message?.includes('ENOENT')) {
71
+ return { ...DEFAULTS };
72
+ }
73
+ throw err;
74
+ }
75
+ }
76
+ export function getDefaults() {
77
+ return { ...DEFAULTS };
78
+ }
@@ -0,0 +1 @@
1
+ export declare function coreModuleSource(runtimeImport: string): string;
@@ -1,17 +1,11 @@
1
- // ---------------------------------------------------------------------------
2
- // core-template.js — source template for emitted core asset
3
- // ---------------------------------------------------------------------------
4
-
5
1
  function assertRuntimeImport(runtimeImport) {
6
2
  if (typeof runtimeImport !== 'string' || runtimeImport.trim().length === 0) {
7
3
  throw new Error('[Zenith Core] coreModuleSource(runtimeImport) requires non-empty runtimeImport');
8
4
  }
9
5
  }
10
-
11
6
  export function coreModuleSource(runtimeImport) {
12
7
  assertRuntimeImport(runtimeImport);
13
8
  const runtimeImportLiteral = JSON.stringify(runtimeImport);
14
-
15
9
  return [
16
10
  `import { signal, state, zeneffect, zenEffect as __zenithZenEffect, zenMount as __zenithZenMount } from ${runtimeImportLiteral};`,
17
11
  '',
@@ -0,0 +1,14 @@
1
+ export interface ZenithError extends Error {
2
+ zenithModule: string;
3
+ }
4
+ export declare function createError(module: string, message: string): ZenithError;
5
+ export declare function formatError(module: string, message: string): string;
6
+ export declare function isZenithError(err: Error | unknown): err is ZenithError;
7
+ export declare const ErrorCodes: {
8
+ readonly CONFIG_UNKNOWN_KEY: "CONFIG_UNKNOWN_KEY";
9
+ readonly CONFIG_INVALID_TYPE: "CONFIG_INVALID_TYPE";
10
+ readonly CONFIG_EMPTY_VALUE: "CONFIG_EMPTY_VALUE";
11
+ readonly PATH_REPEATED_PARAM: "PATH_REPEATED_PARAM";
12
+ readonly VERSION_INCOMPATIBLE: "VERSION_INCOMPATIBLE";
13
+ readonly GUARD_VIOLATION: "GUARD_VIOLATION";
14
+ };
package/dist/errors.js ADDED
@@ -0,0 +1,19 @@
1
+ export function createError(module, message) {
2
+ const err = new Error(`[Zenith:${module}] ${message}`);
3
+ err.zenithModule = module;
4
+ return err;
5
+ }
6
+ export function formatError(module, message) {
7
+ return `[Zenith:${module}] ${message}`;
8
+ }
9
+ export function isZenithError(err) {
10
+ return err instanceof Error && typeof err.zenithModule === 'string';
11
+ }
12
+ export const ErrorCodes = {
13
+ CONFIG_UNKNOWN_KEY: 'CONFIG_UNKNOWN_KEY',
14
+ CONFIG_INVALID_TYPE: 'CONFIG_INVALID_TYPE',
15
+ CONFIG_EMPTY_VALUE: 'CONFIG_EMPTY_VALUE',
16
+ PATH_REPEATED_PARAM: 'PATH_REPEATED_PARAM',
17
+ VERSION_INCOMPATIBLE: 'VERSION_INCOMPATIBLE',
18
+ GUARD_VIOLATION: 'GUARD_VIOLATION'
19
+ };
@@ -0,0 +1,5 @@
1
+ export declare function containsForbiddenPattern(source: string, patterns: string[]): string[];
2
+ export { validateRouteParams } from './path.js';
3
+ export { validateConfig as validateConfigSchema } from './config.js';
4
+ export declare const FORBIDDEN_PATTERNS: string[];
5
+ export declare const BROWSER_GLOBALS: string[];
package/dist/guards.js ADDED
@@ -0,0 +1,24 @@
1
+ export function containsForbiddenPattern(source, patterns) {
2
+ const found = [];
3
+ for (const pattern of patterns) {
4
+ if (source.includes(pattern)) {
5
+ found.push(pattern);
6
+ }
7
+ }
8
+ return found;
9
+ }
10
+ export { validateRouteParams } from './path.js';
11
+ export { validateConfig as validateConfigSchema } from './config.js';
12
+ export const FORBIDDEN_PATTERNS = [
13
+ 'eval(',
14
+ 'new Function(',
15
+ 'new Function (',
16
+ 'document.write('
17
+ ];
18
+ export const BROWSER_GLOBALS = [
19
+ 'window',
20
+ 'document',
21
+ 'navigator',
22
+ 'localStorage',
23
+ 'sessionStorage'
24
+ ];
package/dist/hash.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function hash(content: string): string;
2
+ export declare function hashShort(content: string, length?: number): string;
package/dist/hash.js ADDED
@@ -0,0 +1,13 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function hash(content) {
3
+ const normalized = normalizeInput(content);
4
+ return createHash('sha256').update(normalized).digest('hex');
5
+ }
6
+ export function hashShort(content, length = 8) {
7
+ return hash(content).slice(0, length);
8
+ }
9
+ function normalizeInput(content) {
10
+ let result = content.replace(/\\/g, '/');
11
+ result = result.replace(/\n+$/, '');
12
+ return result;
13
+ }
@@ -0,0 +1,9 @@
1
+ export { validateConfig, loadConfig, getDefaults } from './config.js';
2
+ export { normalizeSeparators, fileToRoute, extractParams, isDynamic, validateRouteParams, canonicalize } from './path.js';
3
+ export { sortRoutes, sortAlpha, isCorrectOrder } from './order.js';
4
+ export { hash, hashShort } from './hash.js';
5
+ export { createError, formatError, isZenithError, ErrorCodes } from './errors.js';
6
+ export { parseSemver, formatVersion, validateCompatibility } from './version.js';
7
+ export { IR_VERSION, IR_SCHEMA } from './schema.js';
8
+ export { coreModuleSource } from './core-template.js';
9
+ export { containsForbiddenPattern, FORBIDDEN_PATTERNS, BROWSER_GLOBALS } from './guards.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { validateConfig, loadConfig, getDefaults } from './config.js';
2
+ export { normalizeSeparators, fileToRoute, extractParams, isDynamic, validateRouteParams, canonicalize } from './path.js';
3
+ export { sortRoutes, sortAlpha, isCorrectOrder } from './order.js';
4
+ export { hash, hashShort } from './hash.js';
5
+ export { createError, formatError, isZenithError, ErrorCodes } from './errors.js';
6
+ export { parseSemver, formatVersion, validateCompatibility } from './version.js';
7
+ export { IR_VERSION, IR_SCHEMA } from './schema.js';
8
+ export { coreModuleSource } from './core-template.js';
9
+ export { containsForbiddenPattern, FORBIDDEN_PATTERNS, BROWSER_GLOBALS } from './guards.js';
@@ -0,0 +1 @@
1
+ export { IR_VERSION, IR_SCHEMA } from '../schema.js';
@@ -0,0 +1,6 @@
1
+ export interface RouteEntry {
2
+ path: string;
3
+ }
4
+ export declare function sortRoutes<T extends RouteEntry>(entries: T[]): T[];
5
+ export declare function sortAlpha(items: string[]): string[];
6
+ export declare function isCorrectOrder<T extends RouteEntry>(entries: T[]): boolean;
package/dist/order.js ADDED
@@ -0,0 +1,21 @@
1
+ function isDynamicRoute(routePath) {
2
+ return routePath.includes(':');
3
+ }
4
+ export function sortRoutes(entries) {
5
+ return [...entries].sort((a, b) => {
6
+ const aDynamic = isDynamicRoute(a.path);
7
+ const bDynamic = isDynamicRoute(b.path);
8
+ if (!aDynamic && bDynamic)
9
+ return -1;
10
+ if (aDynamic && !bDynamic)
11
+ return 1;
12
+ return a.path.localeCompare(b.path);
13
+ });
14
+ }
15
+ export function sortAlpha(items) {
16
+ return [...items].sort((a, b) => a.localeCompare(b));
17
+ }
18
+ export function isCorrectOrder(entries) {
19
+ const sorted = sortRoutes(entries);
20
+ return entries.every((entry, i) => entry.path === sorted[i]?.path);
21
+ }
package/dist/path.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export declare function normalizeSeparators(filePath: string): string;
2
+ export declare function fileToRoute(filePath: string, extension?: string): string;
3
+ export declare function extractParams(routePath: string): string[];
4
+ export declare function isDynamic(routePath: string): boolean;
5
+ export declare function validateRouteParams(routePath: string): void;
6
+ export declare function canonicalize(routePath: string): string;
package/dist/path.js ADDED
@@ -0,0 +1,53 @@
1
+ export function normalizeSeparators(filePath) {
2
+ return filePath.replace(/\\/g, '/');
3
+ }
4
+ export function fileToRoute(filePath, extension = '.zen') {
5
+ let route = normalizeSeparators(filePath);
6
+ if (route.endsWith(extension)) {
7
+ route = route.slice(0, -extension.length);
8
+ }
9
+ route = route.replace(/\[([^\]]+)\]/g, ':$1');
10
+ if (route === 'index') {
11
+ return '/';
12
+ }
13
+ if (route.endsWith('/index')) {
14
+ route = route.slice(0, -'/index'.length);
15
+ }
16
+ if (!route.startsWith('/')) {
17
+ route = `/${route}`;
18
+ }
19
+ return route;
20
+ }
21
+ export function extractParams(routePath) {
22
+ const params = [];
23
+ const segments = routePath.split('/');
24
+ for (const segment of segments) {
25
+ if (segment.startsWith(':')) {
26
+ params.push(segment.slice(1));
27
+ }
28
+ }
29
+ return params;
30
+ }
31
+ export function isDynamic(routePath) {
32
+ return routePath.includes(':');
33
+ }
34
+ export function validateRouteParams(routePath) {
35
+ const params = extractParams(routePath);
36
+ const seen = new Set();
37
+ for (const param of params) {
38
+ if (seen.has(param)) {
39
+ throw new Error(`[Zenith:Path] Repeated parameter name ":${param}" in route "${routePath}"`);
40
+ }
41
+ seen.add(param);
42
+ }
43
+ }
44
+ export function canonicalize(routePath) {
45
+ let path = normalizeSeparators(routePath);
46
+ if (path.length > 1 && path.endsWith('/')) {
47
+ path = path.slice(0, -1);
48
+ }
49
+ if (!path.startsWith('/')) {
50
+ path = `/${path}`;
51
+ }
52
+ return path;
53
+ }
@@ -0,0 +1,5 @@
1
+ export declare const IR_VERSION = 1;
2
+ export declare const IR_SCHEMA: Readonly<{
3
+ version: 1;
4
+ requiredTopLevelKeys: readonly string[];
5
+ }>;
@@ -1,9 +1,4 @@
1
- // ---------------------------------------------------------------------------
2
- // schema.js — Zenith Core IR authority
3
- // ---------------------------------------------------------------------------
4
-
5
1
  export const IR_VERSION = 1;
6
-
7
2
  export const IR_SCHEMA = Object.freeze({
8
3
  version: IR_VERSION,
9
4
  requiredTopLevelKeys: Object.freeze([
@@ -0,0 +1,8 @@
1
+ export interface SemverVersion {
2
+ major: number;
3
+ minor: number;
4
+ patch: number;
5
+ }
6
+ export declare function parseSemver(version: string): SemverVersion;
7
+ export declare function formatVersion(ver: SemverVersion): string;
8
+ export declare function validateCompatibility(coreVersion: string, otherVersion: string): string | null;
@@ -0,0 +1,27 @@
1
+ export function parseSemver(version) {
2
+ const cleaned = version.replace(/^v/, '');
3
+ const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
4
+ if (!match) {
5
+ throw new Error(`[Zenith:Version] Invalid semver: "${version}"`);
6
+ }
7
+ return {
8
+ major: Number.parseInt(match[1], 10),
9
+ minor: Number.parseInt(match[2], 10),
10
+ patch: Number.parseInt(match[3], 10)
11
+ };
12
+ }
13
+ export function formatVersion(ver) {
14
+ return `${ver.major}.${ver.minor}.${ver.patch}`;
15
+ }
16
+ export function validateCompatibility(coreVersion, otherVersion) {
17
+ const core = parseSemver(coreVersion);
18
+ const other = parseSemver(otherVersion);
19
+ if (core.major !== other.major) {
20
+ throw new Error(`[Zenith:Version] Incompatible major versions: core=${formatVersion(core)}, other=${formatVersion(other)}`);
21
+ }
22
+ const minorDiff = Math.abs(core.minor - other.minor);
23
+ if (minorDiff > 1) {
24
+ return `[Zenith:Version] Minor version drift: core=${formatVersion(core)}, other=${formatVersion(other)} (diff=${minorDiff})`;
25
+ }
26
+ return null;
27
+ }
package/package.json CHANGED
@@ -1,32 +1,34 @@
1
1
  {
2
2
  "name": "@zenithbuild/core",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
4
4
  "description": "Deterministic utility substrate for the Zenith framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "main": "./src/index.js",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
8
9
  "bin": {
9
10
  "zenith": "bin/zenith.js"
10
11
  },
11
12
  "exports": {
12
- ".": "./src/index.js",
13
- "./config": "./src/config.js",
14
- "./path": "./src/path.js",
15
- "./order": "./src/order.js",
16
- "./hash": "./src/hash.js",
17
- "./errors": "./src/errors.js",
18
- "./version": "./src/version.js",
19
- "./guards": "./src/guards.js",
20
- "./schema": "./src/schema.js",
21
- "./core-template": "./src/core-template.js",
22
- "./ir": "./src/ir/index.js"
13
+ ".": "./dist/index.js",
14
+ "./config": "./dist/config.js",
15
+ "./path": "./dist/path.js",
16
+ "./order": "./dist/order.js",
17
+ "./hash": "./dist/hash.js",
18
+ "./errors": "./dist/errors.js",
19
+ "./version": "./dist/version.js",
20
+ "./guards": "./dist/guards.js",
21
+ "./schema": "./dist/schema.js",
22
+ "./core-template": "./dist/core-template.js",
23
+ "./ir": "./dist/ir/index.js"
23
24
  },
24
25
  "files": [
25
- "src",
26
+ "dist",
26
27
  "bin",
27
28
  "CORE_CONTRACT.md",
28
29
  "README.md",
29
- "contracts"
30
+ "LICENSE",
31
+ "package.json"
30
32
  ],
31
33
  "repository": {
32
34
  "type": "git",
@@ -41,24 +43,29 @@
41
43
  "access": "public"
42
44
  },
43
45
  "dependencies": {
44
- "@zenithbuild/cli": "0.6.6",
45
- "@zenithbuild/compiler": "0.6.6",
46
- "@zenithbuild/runtime": "0.6.6",
47
- "@zenithbuild/router": "0.6.6",
48
- "@zenithbuild/bundler": "0.6.6"
46
+ "@zenithbuild/cli": "0.6.7",
47
+ "@zenithbuild/compiler": "0.6.7",
48
+ "@zenithbuild/runtime": "0.6.7",
49
+ "@zenithbuild/router": "0.6.7",
50
+ "@zenithbuild/bundler": "0.6.7"
49
51
  },
50
52
  "scripts": {
51
- "test": "npm run contract:deps && npm run contract:scan && npm run contract:api && npm run contract:template && npm run contract:golden",
53
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit",
55
+ "test": "npm run build && npm run contract:deps && npm run contract:scan && npm run contract:api && npm run contract:template && npm run contract:golden && npm run test:legacy",
52
56
  "test:legacy": "NODE_OPTIONS=--experimental-vm-modules jest --config jest.config.js",
53
57
  "contract:deps": "node dependency_contract.spec.js",
54
58
  "contract:scan": "node contract-scan.mjs",
55
59
  "contract:api": "node core_api_lock.spec.js",
56
60
  "contract:template": "node template-contract.spec.js",
57
- "contract:golden": "node golden-contract.spec.js"
61
+ "contract:golden": "node golden-contract.spec.js",
62
+ "prepublishOnly": "npm run build"
58
63
  },
59
64
  "devDependencies": {
65
+ "@types/node": "latest",
60
66
  "@jest/globals": "^30.2.0",
61
- "jest": "^30.2.0"
67
+ "jest": "^30.2.0",
68
+ "typescript": "^5.7.3"
62
69
  },
63
70
  "private": false
64
71
  }
package/src/config.js DELETED
@@ -1,136 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // config.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Load and validate zenith.config.js. This is the ONLY module in core
5
- // that touches the filesystem (via dynamic import).
6
- //
7
- // V0 Schema: { router, outDir, pagesDir }
8
- // Unknown keys → throw.
9
- // ---------------------------------------------------------------------------
10
-
11
- import { join } from 'node:path';
12
- import { pathToFileURL } from 'node:url';
13
-
14
- /** V0 config defaults */
15
- const DEFAULTS = {
16
- router: false,
17
- embeddedMarkupExpressions: false,
18
- types: true,
19
- typescriptDefault: true,
20
- outDir: 'dist',
21
- pagesDir: 'pages',
22
- experimental: {},
23
- strictDomLints: false
24
- };
25
-
26
- /** Allowed keys and their expected types */
27
- const SCHEMA = {
28
- router: 'boolean',
29
- embeddedMarkupExpressions: 'boolean',
30
- types: 'boolean',
31
- typescriptDefault: 'boolean',
32
- outDir: 'string',
33
- pagesDir: 'string',
34
- experimental: 'object',
35
- strictDomLints: 'boolean'
36
- };
37
-
38
- /**
39
- * Validate a config object against the V0 schema.
40
- * Throws on unknown keys or wrong types.
41
- *
42
- * @param {object} config
43
- * @returns {object} Normalized config with defaults applied
44
- */
45
- export function validateConfig(config) {
46
- if (config === null || config === undefined) {
47
- return { ...DEFAULTS };
48
- }
49
-
50
- if (typeof config !== 'object' || Array.isArray(config)) {
51
- throw new Error('[Zenith:Config] Config must be a plain object');
52
- }
53
-
54
- // Check for unknown keys
55
- for (const key of Object.keys(config)) {
56
- if (!(key in SCHEMA)) {
57
- throw new Error(`[Zenith:Config] Unknown key: "${key}"`);
58
- }
59
- }
60
-
61
- // Validate types and apply defaults
62
- const result = { ...DEFAULTS };
63
-
64
- for (const [key, expectedType] of Object.entries(SCHEMA)) {
65
- if (key in config) {
66
- const value = config[key];
67
- if (typeof value !== expectedType) {
68
- throw new Error(
69
- `[Zenith:Config] Key "${key}" must be ${expectedType}, got ${typeof value}`
70
- );
71
- }
72
- if (expectedType === 'string' && value.trim() === '') {
73
- throw new Error(
74
- `[Zenith:Config] Key "${key}" must be a non-empty string`
75
- );
76
- }
77
- if (key === 'experimental' && value) {
78
- if (typeof value !== 'object' || Array.isArray(value)) {
79
- throw new Error(`[Zenith:Config] Key "experimental" must be a plain object`);
80
- }
81
- const expDefaults = { ...DEFAULTS.experimental };
82
- for (const expKey of Object.keys(value)) {
83
- if (!(expKey in expDefaults)) {
84
- throw new Error(`[Zenith:Config] Unknown experimental key: "${expKey}"`);
85
- }
86
- if (typeof value[expKey] !== typeof expDefaults[expKey]) {
87
- throw new Error(`[Zenith:Config] Experimental key "${expKey}" must be ${typeof expDefaults[expKey]}`);
88
- }
89
- expDefaults[expKey] = value[expKey];
90
- }
91
- result[key] = expDefaults;
92
- continue;
93
- }
94
- result[key] = value;
95
- }
96
- }
97
-
98
- return result;
99
- }
100
-
101
- /**
102
- * Load zenith.config.js from a project root.
103
- * Returns validated config with defaults applied.
104
- *
105
- * @param {string} projectRoot
106
- * @returns {Promise<object>}
107
- */
108
- export async function loadConfig(projectRoot) {
109
- const configPath = join(projectRoot, 'zenith.config.js');
110
-
111
- try {
112
- const url = pathToFileURL(configPath).href;
113
- const mod = await import(url);
114
- const raw = mod.default || mod;
115
- return validateConfig(raw);
116
- } catch (err) {
117
- // File not found — return defaults
118
- if (
119
- err.code === 'ERR_MODULE_NOT_FOUND' ||
120
- err.code === 'ENOENT' ||
121
- err.message?.includes('Cannot find module') ||
122
- err.message?.includes('ENOENT')
123
- ) {
124
- return { ...DEFAULTS };
125
- }
126
- throw err;
127
- }
128
- }
129
-
130
- /**
131
- * Return the default config.
132
- * @returns {object}
133
- */
134
- export function getDefaults() {
135
- return { ...DEFAULTS };
136
- }
package/src/errors.js DELETED
@@ -1,54 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // errors.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Shared error formatting. All Zenith errors use the format:
5
- // [Zenith:MODULE] message
6
- //
7
- // Pure factory functions. No side effects.
8
- // ---------------------------------------------------------------------------
9
-
10
- /**
11
- * Create a Zenith error with standard formatting.
12
- *
13
- * @param {string} module Module name (e.g. 'Config', 'Path', 'Build')
14
- * @param {string} message Error message
15
- * @returns {Error}
16
- */
17
- export function createError(module, message) {
18
- const err = new Error(`[Zenith:${module}] ${message}`);
19
- err.zenithModule = module;
20
- return err;
21
- }
22
-
23
- /**
24
- * Format an error message with the Zenith prefix.
25
- *
26
- * @param {string} module
27
- * @param {string} message
28
- * @returns {string}
29
- */
30
- export function formatError(module, message) {
31
- return `[Zenith:${module}] ${message}`;
32
- }
33
-
34
- /**
35
- * Check if an error is a Zenith error.
36
- *
37
- * @param {Error} err
38
- * @returns {boolean}
39
- */
40
- export function isZenithError(err) {
41
- return err instanceof Error && typeof err.zenithModule === 'string';
42
- }
43
-
44
- /**
45
- * Predefined error codes for common situations.
46
- */
47
- export const ErrorCodes = {
48
- CONFIG_UNKNOWN_KEY: 'CONFIG_UNKNOWN_KEY',
49
- CONFIG_INVALID_TYPE: 'CONFIG_INVALID_TYPE',
50
- CONFIG_EMPTY_VALUE: 'CONFIG_EMPTY_VALUE',
51
- PATH_REPEATED_PARAM: 'PATH_REPEATED_PARAM',
52
- VERSION_INCOMPATIBLE: 'VERSION_INCOMPATIBLE',
53
- GUARD_VIOLATION: 'GUARD_VIOLATION'
54
- };
package/src/guards.js DELETED
@@ -1,61 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // guards.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Small pure validation helpers. These provide primitives —
5
- // they do NOT scan repos, enforce architecture, or police other layers.
6
- // ---------------------------------------------------------------------------
7
-
8
- /**
9
- * Check if source contains any of the given forbidden patterns.
10
- *
11
- * @param {string} source Source code text
12
- * @param {string[]} patterns Patterns to check for
13
- * @returns {string[]} Array of matched patterns (empty if none)
14
- */
15
- export function containsForbiddenPattern(source, patterns) {
16
- const found = [];
17
- for (const pattern of patterns) {
18
- if (source.includes(pattern)) {
19
- found.push(pattern);
20
- }
21
- }
22
- return found;
23
- }
24
-
25
- /**
26
- * Validate that a route path has no repeated parameter names.
27
- * Re-exported from path.js for convenience.
28
- *
29
- * @param {string} routePath
30
- */
31
- export { validateRouteParams } from './path.js';
32
-
33
- /**
34
- * Validate a config object against the V0 schema.
35
- * Re-exported from config.js for convenience.
36
- *
37
- * @param {object} config
38
- * @returns {object}
39
- */
40
- export { validateConfig as validateConfigSchema } from './config.js';
41
-
42
- /**
43
- * Default forbidden patterns for Zenith source code.
44
- */
45
- export const FORBIDDEN_PATTERNS = [
46
- 'eval(',
47
- 'new Function(',
48
- 'new Function (',
49
- 'document.write('
50
- ];
51
-
52
- /**
53
- * Default browser globals that should not appear in Node-only code.
54
- */
55
- export const BROWSER_GLOBALS = [
56
- 'window',
57
- 'document',
58
- 'navigator',
59
- 'localStorage',
60
- 'sessionStorage'
61
- ];
package/src/hash.js DELETED
@@ -1,52 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // hash.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Deterministic content hashing. SHA-256, hex output.
5
- //
6
- // CRITICAL: This algorithm must match the bundler's hashing exactly.
7
- // If bundler changes hash algorithm, core must change in lockstep.
8
- //
9
- // Input normalization:
10
- // - Path separators → /
11
- // - Trailing newlines stripped
12
- // ---------------------------------------------------------------------------
13
-
14
- import { createHash } from 'node:crypto';
15
-
16
- /**
17
- * Hash a string using SHA-256. Returns hex digest.
18
- *
19
- * @param {string} content
20
- * @returns {string}
21
- */
22
- export function hash(content) {
23
- const normalized = _normalizeInput(content);
24
- return createHash('sha256').update(normalized).digest('hex');
25
- }
26
-
27
- /**
28
- * Hash a string and return a truncated hash (first N characters).
29
- * Useful for content-hashed filenames.
30
- *
31
- * @param {string} content
32
- * @param {number} [length=8]
33
- * @returns {string}
34
- */
35
- export function hashShort(content, length = 8) {
36
- return hash(content).slice(0, length);
37
- }
38
-
39
- /**
40
- * Normalize input for deterministic hashing.
41
- * - Replaces backslashes with forward slashes
42
- * - Strips trailing newlines
43
- *
44
- * @param {string} content
45
- * @returns {string}
46
- */
47
- function _normalizeInput(content) {
48
- let result = content.replace(/\\/g, '/');
49
- // Strip trailing newlines
50
- result = result.replace(/\n+$/, '');
51
- return result;
52
- }
package/src/index.js DELETED
@@ -1,26 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // index.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Public API. Re-exports frozen module namespaces only.
5
- // ---------------------------------------------------------------------------
6
-
7
- export { validateConfig, loadConfig, getDefaults } from './config.js';
8
- export {
9
- normalizeSeparators,
10
- fileToRoute,
11
- extractParams,
12
- isDynamic,
13
- validateRouteParams,
14
- canonicalize
15
- } from './path.js';
16
- export { sortRoutes, sortAlpha, isCorrectOrder } from './order.js';
17
- export { hash, hashShort } from './hash.js';
18
- export { createError, formatError, isZenithError, ErrorCodes } from './errors.js';
19
- export { parseSemver, formatVersion, validateCompatibility } from './version.js';
20
- export { IR_VERSION, IR_SCHEMA } from './schema.js';
21
- export { coreModuleSource } from './core-template.js';
22
- export {
23
- containsForbiddenPattern,
24
- FORBIDDEN_PATTERNS,
25
- BROWSER_GLOBALS
26
- } from './guards.js';
package/src/order.js DELETED
@@ -1,69 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // order.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Deterministic ordering utilities. Pure transforms.
5
- //
6
- // Sorting contract:
7
- // 1. Static routes first, dynamic routes second
8
- // 2. Alphabetical tiebreak within each group
9
- // 3. Stable across runs
10
- // ---------------------------------------------------------------------------
11
-
12
- /**
13
- * Check if a route path contains dynamic segments (starts with :).
14
- *
15
- * @param {string} routePath
16
- * @returns {boolean}
17
- */
18
- function _isDynamic(routePath) {
19
- return routePath.includes(':');
20
- }
21
-
22
- /**
23
- * Sort route entries deterministically.
24
- *
25
- * Rules:
26
- * 1. Static routes before dynamic routes
27
- * 2. Alphabetical (lexicographic) within each group
28
- *
29
- * Entries must have a `path` property.
30
- *
31
- * @param {{ path: string }[]} entries
32
- * @returns {{ path: string }[]}
33
- */
34
- export function sortRoutes(entries) {
35
- return [...entries].sort((a, b) => {
36
- const aDynamic = _isDynamic(a.path);
37
- const bDynamic = _isDynamic(b.path);
38
-
39
- // Static before dynamic
40
- if (!aDynamic && bDynamic) return -1;
41
- if (aDynamic && !bDynamic) return 1;
42
-
43
- // Alphabetical tiebreak
44
- return a.path.localeCompare(b.path);
45
- });
46
- }
47
-
48
- /**
49
- * Sort an array of strings deterministically.
50
- * Simple alphabetical sort with stable ordering.
51
- *
52
- * @param {string[]} items
53
- * @returns {string[]}
54
- */
55
- export function sortAlpha(items) {
56
- return [...items].sort((a, b) => a.localeCompare(b));
57
- }
58
-
59
- /**
60
- * Check if an array of route entries is in correct deterministic order.
61
- * Does not throw — returns boolean.
62
- *
63
- * @param {{ path: string }[]} entries
64
- * @returns {boolean}
65
- */
66
- export function isCorrectOrder(entries) {
67
- const sorted = sortRoutes(entries);
68
- return entries.every((entry, i) => entry.path === sorted[i].path);
69
- }
package/src/path.js DELETED
@@ -1,131 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // path.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // Path normalization utilities. Pure transforms — no filesystem access.
5
- //
6
- // - Normalizes separators (\ → /)
7
- // - Converts [param] → :param
8
- // - Extracts params from route strings
9
- // - Validates no repeated param names
10
- // - Strips file extensions
11
- // ---------------------------------------------------------------------------
12
-
13
- /**
14
- * Normalize path separators to forward slashes.
15
- *
16
- * @param {string} filePath
17
- * @returns {string}
18
- */
19
- export function normalizeSeparators(filePath) {
20
- return filePath.replace(/\\/g, '/');
21
- }
22
-
23
- /**
24
- * Convert a file-system path to a route path.
25
- *
26
- * Rules:
27
- * - Normalize separators
28
- * - Strip the extension
29
- * - Convert [param] → :param
30
- * - index files → parent path
31
- * - Ensure leading /
32
- *
33
- * @param {string} filePath Relative path from pages dir (e.g. "users/[id].zen")
34
- * @param {string} [extension='.zen']
35
- * @returns {string}
36
- */
37
- export function fileToRoute(filePath, extension = '.zen') {
38
- let route = normalizeSeparators(filePath);
39
-
40
- // Strip extension
41
- if (route.endsWith(extension)) {
42
- route = route.slice(0, -extension.length);
43
- }
44
-
45
- // Convert [param] → :param
46
- route = route.replace(/\[([^\]]+)\]/g, ':$1');
47
-
48
- // Handle index → parent
49
- if (route === 'index') {
50
- return '/';
51
- }
52
- if (route.endsWith('/index')) {
53
- route = route.slice(0, -'/index'.length);
54
- }
55
-
56
- // Ensure leading /
57
- if (!route.startsWith('/')) {
58
- route = '/' + route;
59
- }
60
-
61
- return route;
62
- }
63
-
64
- /**
65
- * Extract parameter names from a route path.
66
- *
67
- * @param {string} routePath e.g. "/users/:id/posts/:postId"
68
- * @returns {string[]} e.g. ["id", "postId"]
69
- */
70
- export function extractParams(routePath) {
71
- const params = [];
72
- const segments = routePath.split('/');
73
- for (const segment of segments) {
74
- if (segment.startsWith(':')) {
75
- params.push(segment.slice(1));
76
- }
77
- }
78
- return params;
79
- }
80
-
81
- /**
82
- * Check if a route path contains dynamic segments.
83
- *
84
- * @param {string} routePath
85
- * @returns {boolean}
86
- */
87
- export function isDynamic(routePath) {
88
- return routePath.includes(':');
89
- }
90
-
91
- /**
92
- * Validate that a route path has no repeated parameter names.
93
- * Throws on violation.
94
- *
95
- * @param {string} routePath
96
- */
97
- export function validateRouteParams(routePath) {
98
- const params = extractParams(routePath);
99
- const seen = new Set();
100
- for (const param of params) {
101
- if (seen.has(param)) {
102
- throw new Error(
103
- `[Zenith:Path] Repeated parameter name ":${param}" in route "${routePath}"`
104
- );
105
- }
106
- seen.add(param);
107
- }
108
- }
109
-
110
- /**
111
- * Canonicalize a route path.
112
- * Strips trailing slashes (except root), normalizes separators.
113
- *
114
- * @param {string} routePath
115
- * @returns {string}
116
- */
117
- export function canonicalize(routePath) {
118
- let path = normalizeSeparators(routePath);
119
-
120
- // Remove trailing slash (unless root)
121
- if (path.length > 1 && path.endsWith('/')) {
122
- path = path.slice(0, -1);
123
- }
124
-
125
- // Ensure leading slash
126
- if (!path.startsWith('/')) {
127
- path = '/' + path;
128
- }
129
-
130
- return path;
131
- }
package/src/version.js DELETED
@@ -1,67 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // version.js — Zenith Core V0
3
- // ---------------------------------------------------------------------------
4
- // SemVer parsing and compatibility validation.
5
- //
6
- // Direction of control: Other layers call core's version utility.
7
- // Core never imports other packages to auto-check.
8
- // ---------------------------------------------------------------------------
9
-
10
- /**
11
- * Parse a SemVer string into components.
12
- *
13
- * @param {string} version e.g. "1.2.3"
14
- * @returns {{ major: number, minor: number, patch: number }}
15
- */
16
- export function parseSemver(version) {
17
- const cleaned = version.replace(/^v/, '');
18
- const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
19
- if (!match) {
20
- throw new Error(`[Zenith:Version] Invalid semver: "${version}"`);
21
- }
22
- return {
23
- major: parseInt(match[1], 10),
24
- minor: parseInt(match[2], 10),
25
- patch: parseInt(match[3], 10)
26
- };
27
- }
28
-
29
- /**
30
- * Format a version object back to a string.
31
- *
32
- * @param {{ major: number, minor: number, patch: number }} ver
33
- * @returns {string}
34
- */
35
- export function formatVersion(ver) {
36
- return `${ver.major}.${ver.minor}.${ver.patch}`;
37
- }
38
-
39
- /**
40
- * Validate compatibility between two versions.
41
- *
42
- * Rules:
43
- * - Major versions must match (throws if different)
44
- * - Minor version difference > 1 produces a warning (returns warning string)
45
- * - Otherwise returns null (compatible)
46
- *
47
- * @param {string} coreVersion
48
- * @param {string} otherVersion
49
- * @returns {string|null} Warning message or null if fully compatible
50
- */
51
- export function validateCompatibility(coreVersion, otherVersion) {
52
- const core = parseSemver(coreVersion);
53
- const other = parseSemver(otherVersion);
54
-
55
- if (core.major !== other.major) {
56
- throw new Error(
57
- `[Zenith:Version] Incompatible major versions: core=${formatVersion(core)}, other=${formatVersion(other)}`
58
- );
59
- }
60
-
61
- const minorDiff = Math.abs(core.minor - other.minor);
62
- if (minorDiff > 1) {
63
- return `[Zenith:Version] Minor version drift: core=${formatVersion(core)}, other=${formatVersion(other)} (diff=${minorDiff})`;
64
- }
65
-
66
- return null;
67
- }
File without changes