create-sovecom-theme 1.0.0-rc.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.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export interface CliIo {
3
+ log: (message: string) => void;
4
+ err: (message: string) => void;
5
+ /** Base directory for a relative/omitted `--dir`. Defaults to `process.cwd()`. */
6
+ cwd?: string;
7
+ }
8
+ /**
9
+ * Parse argv (after `node script.js`) and scaffold. Returns a process exit code: 0 on success,
10
+ * 1 on a usage/validation/IO error. Never calls process.exit itself.
11
+ */
12
+ export declare function runCli(argv: readonly string[], io: CliIo): number;
13
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAoBA,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,MAAM,CAsDjE"}
package/dist/cli.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * the non-interactive CLI entrypoint.
4
+ *
5
+ * create-sovecom-theme <theme-name> [--dir <path>]
6
+ *
7
+ * Built on Node built-ins ONLY: `node:util` parseArgs for flags. No commander/yargs/prompts/
8
+ * inquirer; no interactive prompts. `runCli` is pure (returns an exit code, takes injectable I/O)
9
+ * so it is unit-testable; the bottom-of-file bootstrap wires real argv + process.exit.
10
+ *
11
+ * The bin has ZERO `@sovecom/theme-sdk` runtime import — the only SDK rule it needs (the theme-name
12
+ * slug) is carried locally in `theme-name.ts` and drift-guarded by a conformance test. This is the
13
+ * Decision-052 lesson applied from the start: the SDK is source-first, so the emitted bin must not
14
+ * depend on it at runtime.
15
+ */
16
+ import { parseArgs } from 'node:util';
17
+ import { scaffoldTheme, InvalidThemeNameError } from './scaffold.js';
18
+ const USAGE = 'usage: create-sovecom-theme <theme-name> [--dir <path>]';
19
+ /**
20
+ * Parse argv (after `node script.js`) and scaffold. Returns a process exit code: 0 on success,
21
+ * 1 on a usage/validation/IO error. Never calls process.exit itself.
22
+ */
23
+ export function runCli(argv, io) {
24
+ const cwd = io.cwd ?? process.cwd();
25
+ let positionals;
26
+ let values;
27
+ try {
28
+ const parsed = parseArgs({
29
+ args: argv,
30
+ allowPositionals: true,
31
+ options: {
32
+ dir: { type: 'string' },
33
+ help: { type: 'boolean', short: 'h' },
34
+ },
35
+ });
36
+ positionals = parsed.positionals;
37
+ values = parsed.values;
38
+ }
39
+ catch (error) {
40
+ io.err(`${error.message}\n${USAGE}`);
41
+ return 1;
42
+ }
43
+ if (values.help) {
44
+ io.log(USAGE);
45
+ return 0;
46
+ }
47
+ if (positionals.length === 0) {
48
+ io.err(`error: missing required <theme-name>\n${USAGE}`);
49
+ return 1;
50
+ }
51
+ if (positionals.length > 1) {
52
+ io.err(`error: unexpected extra arguments: ${positionals.slice(1).join(' ')}\n${USAGE}`);
53
+ return 1;
54
+ }
55
+ const themeName = positionals[0];
56
+ const targetDir = values.dir ?? cwd;
57
+ try {
58
+ const outDir = scaffoldTheme({ themeName, targetDir });
59
+ io.log(`Created SovEcom theme starter at ${outDir}`);
60
+ io.log('Next steps:');
61
+ io.log(` cd ${themeName}`);
62
+ io.log(' pnpm install');
63
+ io.log(' pnpm typecheck');
64
+ return 0;
65
+ }
66
+ catch (error) {
67
+ if (error instanceof InvalidThemeNameError) {
68
+ io.err(`error: ${error.message}`);
69
+ return 1;
70
+ }
71
+ io.err(`error: ${error.message}`);
72
+ return 1;
73
+ }
74
+ }
75
+ /* istanbul ignore next — the real-process bootstrap is exercised by the end-to-end CLI run. */
76
+ if (import.meta.url === `file://${process.argv[1]}`) {
77
+ const code = runCli(process.argv.slice(2), {
78
+ // eslint-disable-next-line no-console
79
+ log: (m) => console.log(m),
80
+ // eslint-disable-next-line no-console
81
+ err: (m) => console.error(m),
82
+ });
83
+ process.exit(code);
84
+ }
85
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public surface of the `create-sovecom-theme` scaffolder for programmatic consumers.
3
+ * The CLI bin lives in `cli.ts`.
4
+ */
5
+ export { scaffoldTheme, assertValidThemeName, InvalidThemeNameError } from './scaffold.js';
6
+ export type { ScaffoldOptions } from './scaffold.js';
7
+ export { runCli } from './cli.js';
8
+ export type { CliIo } from './cli.js';
9
+ export { THEME_NAME_RE } from './theme-name.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Public surface of the `create-sovecom-theme` scaffolder for programmatic consumers.
3
+ * The CLI bin lives in `cli.ts`.
4
+ */
5
+ export { scaffoldTheme, assertValidThemeName, InvalidThemeNameError } from './scaffold.js';
6
+ export { runCli } from './cli.js';
7
+ export { THEME_NAME_RE } from './theme-name.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3F,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** Thrown when the requested theme name is not a valid lowercase slug. */
2
+ export declare class InvalidThemeNameError extends Error {
3
+ constructor(name: string);
4
+ }
5
+ export interface ScaffoldOptions {
6
+ /** The theme slug — also the package name and manifest `name`. */
7
+ readonly themeName: string;
8
+ /** Directory the new `<themeName>/` folder is created INSIDE. */
9
+ readonly targetDir: string;
10
+ }
11
+ /** Validate the theme name with the slug regex (a drift-guarded local copy of the SDK's rule). */
12
+ export declare function assertValidThemeName(name: string): void;
13
+ /**
14
+ * Scaffold a theme starter into `<targetDir>/<themeName>/`. Returns the absolute path of the
15
+ * created theme directory. Throws {@link InvalidThemeNameError} on a bad name and a plain
16
+ * `Error` if the destination already exists and is non-empty (never clobber an author's work).
17
+ */
18
+ export declare function scaffoldTheme(opts: ScaffoldOptions): string;
19
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AA0BA,0EAA0E;AAC1E,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,IAAI,EAAE,MAAM;CAOzB;AAED,MAAM,WAAW,eAAe;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAIvD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAW3D"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * the theme-starter scaffolder.
3
+ *
4
+ * Pure file emission: validate the requested theme name against the theme-name slug rule
5
+ * (`THEME_NAME_RE`), then copy the in-package `templates/` tree into the target, substituting
6
+ * the theme name. No interactive prompts, no network, no code execution. ZERO runtime
7
+ * dependencies — only `./theme-name.js` (a drift-guarded local copy of the SDK's rule, so the
8
+ * built bin runs under plain `node` without loading the SDK's source-first entry; see
9
+ * `theme-name.ts`) and Node built-ins.
10
+ *
11
+ * Emits a minimal MIT skeleton without React/Next/tailwind/app files. The full Next.js theme
12
+ * starter is planned for a future release when the render runtime is ready.
13
+ */
14
+ import { readdirSync, readFileSync, mkdirSync, writeFileSync, existsSync, statSync } from 'node:fs';
15
+ import { join, resolve, dirname } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { THEME_NAME_RE } from './theme-name.js';
18
+ /** Placeholder token substituted with the theme slug throughout the template tree. */
19
+ const NAME_PLACEHOLDER = '__THEME_NAME__';
20
+ /** Templates are stored with a `.tmpl` suffix so they are not compiled/linted as package source. */
21
+ const TEMPLATE_SUFFIX = '.tmpl';
22
+ const templatesDir = resolve(fileURLToPath(new URL('.', import.meta.url)), '..', 'templates');
23
+ /** Thrown when the requested theme name is not a valid lowercase slug. */
24
+ export class InvalidThemeNameError extends Error {
25
+ constructor(name) {
26
+ super(`invalid theme name "${name}": must be a lowercase slug matching ${THEME_NAME_RE.source} ` +
27
+ `(e.g. "aurora", "minimal-shop")`);
28
+ this.name = 'InvalidThemeNameError';
29
+ }
30
+ }
31
+ /** Validate the theme name with the slug regex (a drift-guarded local copy of the SDK's rule). */
32
+ export function assertValidThemeName(name) {
33
+ if (typeof name !== 'string' || !THEME_NAME_RE.test(name)) {
34
+ throw new InvalidThemeNameError(String(name));
35
+ }
36
+ }
37
+ /**
38
+ * Scaffold a theme starter into `<targetDir>/<themeName>/`. Returns the absolute path of the
39
+ * created theme directory. Throws {@link InvalidThemeNameError} on a bad name and a plain
40
+ * `Error` if the destination already exists and is non-empty (never clobber an author's work).
41
+ */
42
+ export function scaffoldTheme(opts) {
43
+ assertValidThemeName(opts.themeName);
44
+ const outDir = resolve(opts.targetDir, opts.themeName);
45
+ if (existsSync(outDir) && statSync(outDir).isDirectory() && readdirSync(outDir).length > 0) {
46
+ throw new Error(`destination "${outDir}" already exists and is not empty; aborting`);
47
+ }
48
+ mkdirSync(outDir, { recursive: true });
49
+ copyTemplateTree(templatesDir, outDir, opts.themeName);
50
+ return outDir;
51
+ }
52
+ /** Recursively copy `from` → `to`, dropping the `.tmpl` suffix and substituting the theme name. */
53
+ function copyTemplateTree(from, to, themeName) {
54
+ for (const entry of readdirSync(from, { withFileTypes: true })) {
55
+ const src = join(from, entry.name);
56
+ if (entry.isDirectory()) {
57
+ const destSub = join(to, entry.name);
58
+ mkdirSync(destSub, { recursive: true });
59
+ copyTemplateTree(src, destSub, themeName);
60
+ continue;
61
+ }
62
+ if (!entry.isFile())
63
+ continue;
64
+ const destName = entry.name.endsWith(TEMPLATE_SUFFIX)
65
+ ? entry.name.slice(0, -TEMPLATE_SUFFIX.length)
66
+ : entry.name;
67
+ const dest = join(to, destName);
68
+ const content = readFileSync(src, 'utf8').split(NAME_PLACEHOLDER).join(themeName);
69
+ mkdirSync(dirname(dest), { recursive: true });
70
+ writeFileSync(dest, content, 'utf8');
71
+ }
72
+ }
73
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The theme-name slug rule, declared locally.
3
+ *
4
+ * The single source of truth for this rule is `THEME_NAME_RE` in `@sovecom/theme-sdk`. The
5
+ * compiled `dist/cli.js` binary must run under plain `node`, but the SDK ships source-first
6
+ * so a runtime import from the binary cannot load it without a bundler. This drift-guarded local
7
+ * copy is kept honest by a conformance test (`test/theme-name.conformance.test.ts`) that asserts
8
+ * this regex's `.source` and `.flags` equal the SDK's.
9
+ *
10
+ * MUST stay byte-for-byte equal to `@sovecom/theme-sdk`'s `THEME_NAME_RE`.
11
+ */
12
+ export declare const THEME_NAME_RE: RegExp;
13
+ //# sourceMappingURL=theme-name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-name.d.ts","sourceRoot":"","sources":["../src/theme-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QAAsB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The theme-name slug rule, declared locally.
3
+ *
4
+ * The single source of truth for this rule is `THEME_NAME_RE` in `@sovecom/theme-sdk`. The
5
+ * compiled `dist/cli.js` binary must run under plain `node`, but the SDK ships source-first
6
+ * so a runtime import from the binary cannot load it without a bundler. This drift-guarded local
7
+ * copy is kept honest by a conformance test (`test/theme-name.conformance.test.ts`) that asserts
8
+ * this regex's `.source` and `.flags` equal the SDK's.
9
+ *
10
+ * MUST stay byte-for-byte equal to `@sovecom/theme-sdk`'s `THEME_NAME_RE`.
11
+ */
12
+ export const THEME_NAME_RE = /^[a-z][a-z0-9-]*$/;
13
+ //# sourceMappingURL=theme-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-name.js","sourceRoot":"","sources":["../src/theme-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "create-sovecom-theme",
3
+ "version": "1.0.0-rc.1",
4
+ "license": "MIT",
5
+ "description": "Non-interactive scaffolder for a SovEcom theme starter.",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "create-sovecom-theme": "./dist/cli.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "templates"
15
+ ],
16
+ "devDependencies": {
17
+ "@types/node": "^24.0.0",
18
+ "typescript": "^5.4.0",
19
+ "vitest": "^4.0.0",
20
+ "@sovecom/theme-sdk": "1.0.0-rc.1"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/asifwanders/SovEcom.git",
28
+ "directory": "packages/create-sovecom-theme"
29
+ },
30
+ "homepage": "https://github.com/asifwanders/SovEcom",
31
+ "bugs": {
32
+ "url": "https://github.com/asifwanders/SovEcom/issues"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "lint": "eslint src --ext .ts",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "clean": "rm -rf dist"
41
+ }
42
+ }
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ dist/
3
+ *.log
4
+ .DS_Store
5
+ .env
6
+ .env.*
7
+ !.env.example
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SovEcom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,50 @@
1
+ # __THEME_NAME__
2
+
3
+ A [SovEcom](https://github.com/) theme, scaffolded with `create-sovecom-theme`.
4
+
5
+ ## What's here
6
+
7
+ ```
8
+ __THEME_NAME__/
9
+ ├── sovecom.theme.json # the manifest: name, version, compatibleCore, slots, settingsSchema
10
+ ├── settings.schema.json # JSON-schema describing your tunable settings (path is opaque to core)
11
+ ├── package.json # depends on @sovecom/theme-sdk (dev); typechecks the authoring sources
12
+ ├── tsconfig.json # typecheck-only (a theme is a declarative asset — no build output)
13
+ ├── src/
14
+ │ ├── theme.ts # the typed authoring view — `defineTheme({ ... })`
15
+ │ ├── slots.ts # the declarative slot slugs — `defineThemeSlots([...])`
16
+ │ └── settings.ts # typed default settings — `defineThemeSettings({ ... })`
17
+ ├── README.md
18
+ ├── .gitignore
19
+ └── LICENSE # MIT
20
+ ```
21
+
22
+ ## The theme contract
23
+
24
+ A theme is a **declarative ASSET**, not code. There is **no `activate`, no worker, no runtime
25
+ entrypoint, no capabilities, no namespaced tables** — the core ingests your `sovecom.theme.json` and
26
+ validates it; it never executes theme code. The entire store-facing
27
+ runtime contract a theme touches is two already-shipped read-only endpoints:
28
+
29
+ | Endpoint | Shape | Purpose |
30
+ | ----------------------- | ---------------------------------------------- | ------------------------------- |
31
+ | `GET /store/v1/theme` | `{ name, version, settings }` | The active theme the storefront reads to render. |
32
+ | `GET /store/v1/slots` | `Record<slot, { module, component }>` | The resolved slot map (conflicts omitted). |
33
+
34
+ `slots` are **declarative manifest metadata** — there is no runtime `registerSlot()`. The slot
35
+ registry is derived from enabled **modules'** manifests.
36
+
37
+ ## Author
38
+
39
+ ```bash
40
+ pnpm install
41
+ pnpm typecheck # tsc --noEmit over src/ — validates your defineTheme/defineThemeSlots config
42
+ ```
43
+
44
+ `src/theme.ts` is the typed authoring view of `sovecom.theme.json`; keep the two in sync. The
45
+ manifest (`sovecom.theme.json`) is the artifact the core ingests.
46
+
47
+ ## License
48
+
49
+ MIT. See `LICENSE`. Themes are MIT-licensed — distinct from the AGPL core — so they can be authored
50
+ and distributed commercially.
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "__THEME_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "A SovEcom theme.",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "scripts": {
9
+ "typecheck": "tsc -p tsconfig.json --noEmit"
10
+ },
11
+ "devDependencies": {
12
+ "@sovecom/theme-sdk": "^0.0.1",
13
+ "@types/node": "^24.0.0",
14
+ "typescript": "^5.4.0"
15
+ }
16
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "__THEME_NAME__ settings",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "properties": {
7
+ "primaryColor": {
8
+ "type": "string",
9
+ "description": "Primary brand colour, as a CSS hex string.",
10
+ "default": "#111111"
11
+ },
12
+ "showBreadcrumbs": {
13
+ "type": "boolean",
14
+ "description": "Whether product pages render breadcrumbs.",
15
+ "default": true
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "__THEME_NAME__",
3
+ "displayName": "__THEME_NAME__",
4
+ "version": "0.1.0",
5
+ "compatibleCore": "^1.0.0",
6
+ "slots": ["product-page"],
7
+ "settingsSchema": "settings.schema.json"
8
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * __THEME_NAME__ — the typed default settings this theme exposes.
3
+ *
4
+ * `settingsSchema` in the manifest points at a JSON-schema file describing the tunable settings;
5
+ * that path stays OPAQUE — the core never reads or executes it at validation time.
6
+ * `defineThemeSettings` is purely COMPILE-TIME ergonomics: it types-and-returns your defaults so
7
+ * you get autocomplete and a single inferred shape to reuse. NO runtime behaviour, NO file read.
8
+ */
9
+ import { defineThemeSettings } from '@sovecom/theme-sdk';
10
+
11
+ export const settings = defineThemeSettings({
12
+ primaryColor: '#111111',
13
+ showBreadcrumbs: true,
14
+ });
15
+
16
+ export type ThemeSettingsShape = typeof settings;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * __THEME_NAME__ — the declarative slot slugs this theme exposes/renders.
3
+ *
4
+ * Slots are DECLARATIVE manifest metadata; the slot registry is DERIVED at runtime from enabled
5
+ * MODULES' manifests. `defineThemeSlots` is NOT an RPC and there is NO runtime `registerSlot()` —
6
+ * it just validates the slug shape (`^[a-z][a-z0-9-]*$`, no duplicates) and
7
+ * returns a frozen array dropped verbatim into the manifest's `slots`.
8
+ */
9
+ import { defineThemeSlots } from '@sovecom/theme-sdk';
10
+
11
+ export const slots = defineThemeSlots(['product-page']);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * __THEME_NAME__ — a SovEcom theme.
3
+ *
4
+ * A theme is a declarative ASSET: NO worker, NO runtime entrypoint, NO runtime lifecycle hook, NO
5
+ * capabilities, NO namespaced tables. `defineTheme(config)` validates your config
6
+ * against the SAME manifest schema the core runs at install time and returns the typed, validated
7
+ * `ThemeManifest` — a build-/author-time helper, never a runtime entry. A misconfigured theme
8
+ * fails fast here with a clear message, not as an opaque install rejection.
9
+ *
10
+ * The authored object below mirrors `sovecom.theme.json` (which is the artifact the core ingests).
11
+ * Keep them in sync — this file is the typed, validated authoring view of that manifest.
12
+ */
13
+ import { defineTheme } from '@sovecom/theme-sdk';
14
+ import { slots } from './slots.js';
15
+
16
+ export const theme = defineTheme({
17
+ name: '__THEME_NAME__',
18
+ displayName: '__THEME_NAME__',
19
+ version: '0.1.0',
20
+ compatibleCore: '^1.0.0',
21
+ slots,
22
+ settingsSchema: 'settings.schema.json',
23
+ });
24
+
25
+ export default theme;
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "types": ["node"],
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "noUncheckedIndexedAccess": true,
15
+ "noEmit": true
16
+ },
17
+ "include": ["src"],
18
+ "exclude": ["node_modules"]
19
+ }