create-velocms-theme 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VeloCMS
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.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # create-velocms-theme
2
+
3
+ Scaffold a new VeloCMS theme project ready for npm publish.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-velocms-theme my-theme
9
+ ```
10
+
11
+ Or install globally:
12
+
13
+ ```bash
14
+ npm install -g create-velocms-theme
15
+ create-velocms-theme my-theme
16
+ ```
17
+
18
+ ## Prompts
19
+
20
+ The CLI will ask you:
21
+
22
+ 1. **Theme name** — human-readable display name (e.g. "Minimal Portfolio")
23
+ 2. **Theme slug** — kebab-case package suffix (e.g. "minimal-portfolio")
24
+ 3. **Theme type** — `layout` | `niche` | `pack`
25
+ 4. **Niche category** — (only if type = niche) photographer | podcaster | restaurant | ...
26
+ 5. **Author name**
27
+ 6. **Author email**
28
+ 7. **License** — default: MIT
29
+ 8. **Short description**
30
+
31
+ ## Non-interactive mode
32
+
33
+ ```bash
34
+ npx create-velocms-theme my-theme --non-interactive
35
+ ```
36
+
37
+ Uses sensible defaults for CI environments.
38
+
39
+ ## Generated scaffold
40
+
41
+ ```
42
+ my-theme/
43
+ ├── package.json # velocms-theme-my-theme, peerDeps: react@19, next@16
44
+ ├── theme.manifest.json # ThemeManifest (spec §4)
45
+ ├── tsconfig.json
46
+ ├── tsconfig.build.json
47
+ ├── README.md
48
+ ├── .gitignore
49
+ ├── preview/ # Place thumbnail.png + screenshots here
50
+ └── src/
51
+ ├── index.ts # Re-exports BlogIndex, PostLayout
52
+ ├── index.test.ts # Smoke test (vitest) — extend as your theme grows
53
+ ├── BlogIndex.tsx # Blog listing layout (TODO markers)
54
+ ├── PostLayout.tsx # Single post layout (TODO markers)
55
+ ├── theme.config.ts # Tailwind preset overrides
56
+ └── styles.css # Global CSS tokens
57
+ ```
58
+
59
+ ## Building
60
+
61
+ ```bash
62
+ npm run build # compiles src/ → dist/
63
+ ```
64
+
65
+ ## Submitting to marketplace
66
+
67
+ 1. `npm run build`
68
+ 2. Verify `theme.manifest.json` is complete
69
+ 3. `npx velocms-cli theme submit` (coming Q3 2026)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { join } from "node:path";
3
+ import { collectPrompts } from "./prompts.js";
4
+ import { scaffoldTheme } from "./scaffold.js";
5
+ async function main() {
6
+ const args = process.argv.slice(2);
7
+ const nonInteractive = args.includes("--non-interactive");
8
+ const nameArg = args.find((a) => !a.startsWith("-"));
9
+ console.log("\nVeloCMS Theme Scaffolder\n");
10
+ let cfg;
11
+ try {
12
+ cfg = await collectPrompts(nameArg, nonInteractive);
13
+ }
14
+ catch (err) {
15
+ console.error("Failed to collect prompts:", err);
16
+ process.exit(1);
17
+ }
18
+ const outputDir = join(process.cwd(), cfg.slug);
19
+ console.log(`\nScaffolding theme "${cfg.name}" (${cfg.type}) into ./${cfg.slug}/`);
20
+ try {
21
+ scaffoldTheme(outputDir, cfg);
22
+ }
23
+ catch (err) {
24
+ if (err instanceof Error) {
25
+ console.error(`Error: ${err.message}`);
26
+ }
27
+ else {
28
+ console.error("Unknown error:", err);
29
+ }
30
+ process.exit(1);
31
+ }
32
+ console.log(`\nDone! Theme scaffold created at ./${cfg.slug}/`);
33
+ console.log(` cd ${cfg.slug}`);
34
+ console.log(" npm install");
35
+ console.log(" npm run dev");
36
+ console.log("\nWhen ready to submit:");
37
+ console.log(" npm run build");
38
+ console.log(" npx velocms-cli theme submit (coming soon)\n");
39
+ }
40
+ main().catch((err) => {
41
+ console.error(err);
42
+ process.exit(1);
43
+ });
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ export interface ThemeConfig {
3
+ name: string;
4
+ slug: string;
5
+ type: "layout" | "niche" | "pack";
6
+ authorName: string;
7
+ authorEmail: string;
8
+ license: string;
9
+ description: string;
10
+ niche?: string;
11
+ }
12
+ declare const VALID_TYPES: readonly ["layout", "niche", "pack"];
13
+ declare const NICHE_CATEGORIES: readonly ["photographer", "podcaster", "restaurant", "creator", "writer", "agency", "developer", "indie-maker"];
14
+ export type ThemeType = (typeof VALID_TYPES)[number];
15
+ export type NicheCategory = (typeof NICHE_CATEGORIES)[number];
16
+ export declare function collectPrompts(nameArg?: string, nonInteractive?: boolean): Promise<ThemeConfig>;
17
+ export {};
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from "node:readline";
3
+ const VALID_TYPES = ["layout", "niche", "pack"];
4
+ const NICHE_CATEGORIES = [
5
+ "photographer",
6
+ "podcaster",
7
+ "restaurant",
8
+ "creator",
9
+ "writer",
10
+ "agency",
11
+ "developer",
12
+ "indie-maker",
13
+ ];
14
+ function ask(question, defaultValue) {
15
+ return new Promise((resolve) => {
16
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
17
+ const query = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
18
+ rl.question(query, (answer) => {
19
+ rl.close();
20
+ resolve(answer.trim() || defaultValue || "");
21
+ });
22
+ });
23
+ }
24
+ function toSlug(str) {
25
+ return str
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9-]+/g, "-")
28
+ .replace(/^-+|-+$/g, "");
29
+ }
30
+ export async function collectPrompts(nameArg, nonInteractive = false) {
31
+ let name = nameArg ?? "";
32
+ if (!name && !nonInteractive) {
33
+ name = await ask("Theme name", "my-theme");
34
+ }
35
+ if (!name) {
36
+ name = "my-theme";
37
+ }
38
+ const defaultSlug = toSlug(name);
39
+ const slug = nonInteractive
40
+ ? defaultSlug
41
+ : await ask("Theme slug (kebab-case)", defaultSlug);
42
+ let type = "layout";
43
+ if (!nonInteractive) {
44
+ const typeAnswer = await ask("Theme type — layout | niche | pack", "layout");
45
+ if (VALID_TYPES.includes(typeAnswer)) {
46
+ type = typeAnswer;
47
+ }
48
+ else {
49
+ console.log(`Unknown type "${typeAnswer}", defaulting to "layout"`);
50
+ }
51
+ }
52
+ let niche;
53
+ if (type === "niche") {
54
+ if (!nonInteractive) {
55
+ const nicheAnswer = await ask(`Niche category — ${NICHE_CATEGORIES.join(" | ")}`, "photographer");
56
+ niche = nicheAnswer || "photographer";
57
+ }
58
+ else {
59
+ niche = "photographer";
60
+ }
61
+ }
62
+ const authorName = nonInteractive
63
+ ? "Theme Author"
64
+ : await ask("Author name", "Theme Author");
65
+ const authorEmail = nonInteractive
66
+ ? "author@example.com"
67
+ : await ask("Author email", "author@example.com");
68
+ const license = nonInteractive ? "MIT" : await ask("License", "MIT");
69
+ const description = nonInteractive
70
+ ? `A VeloCMS ${type} theme`
71
+ : await ask("Short description", `A VeloCMS ${type} theme`);
72
+ return { name, slug, type, authorName, authorEmail, license, description, niche };
73
+ }
@@ -0,0 +1,2 @@
1
+ import type { ThemeConfig } from "./prompts.js";
2
+ export declare function scaffoldTheme(outputDir: string, cfg: ThemeConfig): void;
@@ -0,0 +1,370 @@
1
+ import { mkdirSync, writeFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ function packageJsonContent(cfg) {
4
+ return JSON.stringify({
5
+ name: `velocms-theme-${cfg.slug}`,
6
+ version: "1.0.0",
7
+ description: cfg.description,
8
+ type: "module",
9
+ main: "./dist/index.js",
10
+ types: "./dist/index.d.ts",
11
+ scripts: {
12
+ build: "tsc -p tsconfig.build.json",
13
+ typecheck: "tsc --noEmit",
14
+ test: "vitest run",
15
+ dev: "tsc --watch",
16
+ },
17
+ keywords: ["velocms", "theme", cfg.type, ...(cfg.niche ? [cfg.niche] : [])],
18
+ author: {
19
+ name: cfg.authorName,
20
+ email: cfg.authorEmail,
21
+ },
22
+ license: cfg.license,
23
+ peerDependencies: {
24
+ react: "^19.0.0",
25
+ next: "^16.0.0",
26
+ },
27
+ devDependencies: {
28
+ typescript: "^5.0.0",
29
+ "@types/react": "^19.0.0",
30
+ vitest: "^4.0.0",
31
+ },
32
+ publishConfig: {
33
+ access: "public",
34
+ },
35
+ }, null, 2);
36
+ }
37
+ function manifestJsonContent(cfg) {
38
+ return JSON.stringify({
39
+ $schema: "https://velocms.org/schemas/theme-v1.json",
40
+ name: `velocms-theme-${cfg.slug}`,
41
+ displayName: cfg.name,
42
+ version: "1.0.0",
43
+ description: cfg.description,
44
+ author: {
45
+ name: cfg.authorName,
46
+ email: cfg.authorEmail,
47
+ },
48
+ type: cfg.type,
49
+ category: cfg.niche ?? "general",
50
+ tags: [cfg.type, ...(cfg.niche ? [cfg.niche] : [])],
51
+ engines: {
52
+ velocms: ">=1.0.0",
53
+ },
54
+ preview: {
55
+ thumbnail: "./preview/thumbnail.png",
56
+ screenshots: ["./preview/screenshot-1.png"],
57
+ },
58
+ exports: {
59
+ components: {
60
+ BlogLayout: "./dist/BlogIndex.js",
61
+ PostLayout: "./dist/PostLayout.js",
62
+ PageLayout: "./dist/PostLayout.js",
63
+ },
64
+ },
65
+ pricing: {
66
+ model: "free",
67
+ },
68
+ }, null, 2);
69
+ }
70
+ function tsconfigContent() {
71
+ return JSON.stringify({
72
+ compilerOptions: {
73
+ target: "ES2020",
74
+ module: "ESNext",
75
+ moduleResolution: "bundler",
76
+ jsx: "react-jsx",
77
+ strict: true,
78
+ esModuleInterop: true,
79
+ skipLibCheck: true,
80
+ outDir: "./dist",
81
+ rootDir: "./src",
82
+ declaration: true,
83
+ declarationDir: "./dist",
84
+ },
85
+ include: ["src"],
86
+ exclude: ["node_modules", "dist"],
87
+ }, null, 2);
88
+ }
89
+ function tsconfigBuildContent() {
90
+ return JSON.stringify({
91
+ extends: "./tsconfig.json",
92
+ compilerOptions: {
93
+ declaration: true,
94
+ sourceMap: false,
95
+ removeComments: true,
96
+ },
97
+ exclude: ["**/*.test.ts", "**/*.test.tsx"],
98
+ }, null, 2);
99
+ }
100
+ function indexTestTsContent(cfg) {
101
+ return `/**
102
+ * Smoke test for ${cfg.name} — verifies the theme entry point loads and
103
+ * exports the constants VeloCMS reads at install/registration time.
104
+ *
105
+ * Replace/extend with real component tests as your theme grows.
106
+ */
107
+
108
+ import { describe, it, expect } from "vitest";
109
+ import { THEME_SLUG, THEME_VERSION, BlogIndex, PostLayout } from "./index";
110
+
111
+ describe("${cfg.name} theme entry point", () => {
112
+ it("exports the expected theme metadata", () => {
113
+ expect(THEME_SLUG).toBe("${cfg.slug}");
114
+ expect(THEME_VERSION).toBe("1.0.0");
115
+ });
116
+
117
+ it("exports BlogIndex and PostLayout components", () => {
118
+ expect(typeof BlogIndex).toBe("function");
119
+ expect(typeof PostLayout).toBe("function");
120
+ });
121
+ });
122
+ `;
123
+ }
124
+ function indexTsContent(cfg) {
125
+ const name = cfg.name;
126
+ const slug = cfg.slug;
127
+ return `/**
128
+ * ${name} — VeloCMS Theme
129
+ *
130
+ * Entry point. Re-exports all theme components.
131
+ * VeloCMS loads these via the manifest "exports.components" paths.
132
+ */
133
+
134
+ export { BlogIndex } from "./BlogIndex";
135
+ export { PostLayout } from "./PostLayout";
136
+
137
+ // Theme metadata (convenience re-export for integrations)
138
+ export const THEME_SLUG = "${slug}";
139
+ export const THEME_VERSION = "1.0.0";
140
+ `;
141
+ }
142
+ function blogIndexContent(cfg) {
143
+ return `/**
144
+ * BlogIndex — ${cfg.name}
145
+ *
146
+ * TODO: Replace with your actual blog listing layout.
147
+ * This minimal scaffold renders a list of post titles.
148
+ *
149
+ * Props injected by VeloCMS at render time:
150
+ * - posts: array of post objects
151
+ * - settings: tenant site settings
152
+ * - tenant: tenant context (id, slug, blogName, etc.)
153
+ */
154
+
155
+ import React from "react";
156
+
157
+ interface Post {
158
+ id: string;
159
+ title: string;
160
+ slug: string;
161
+ excerpt?: string;
162
+ published_at?: string;
163
+ }
164
+
165
+ interface BlogIndexProps {
166
+ posts: Post[];
167
+ settings?: { blog_name?: string; description?: string };
168
+ tenant?: { blogName?: string };
169
+ }
170
+
171
+ export function BlogIndex({ posts, settings, tenant }: BlogIndexProps) {
172
+ const blogName = tenant?.blogName ?? settings?.blog_name ?? "Blog";
173
+
174
+ return (
175
+ <main>
176
+ <header>
177
+ <h1>{blogName}</h1>
178
+ {settings?.description && <p>{settings.description}</p>}
179
+ </header>
180
+
181
+ {/* TODO: Add your custom layout, hero section, filter pills, etc. */}
182
+ <section>
183
+ {posts.map((post) => (
184
+ <article key={post.id}>
185
+ <h2>
186
+ <a href={\`/blog/\${post.slug}\`}>{post.title}</a>
187
+ </h2>
188
+ {post.excerpt && <p>{post.excerpt}</p>}
189
+ </article>
190
+ ))}
191
+ </section>
192
+ </main>
193
+ );
194
+ }
195
+ `;
196
+ }
197
+ function postLayoutContent(cfg) {
198
+ return `/**
199
+ * PostLayout — ${cfg.name}
200
+ *
201
+ * TODO: Replace with your actual single-post layout.
202
+ *
203
+ * Props injected by VeloCMS at render time:
204
+ * - post: the current post object
205
+ * - settings: tenant site settings
206
+ * - content: pre-rendered HTML string of the post body
207
+ */
208
+
209
+ import React from "react";
210
+
211
+ interface Post {
212
+ id: string;
213
+ title: string;
214
+ slug: string;
215
+ author_name?: string;
216
+ published_at?: string;
217
+ featured_image?: string;
218
+ }
219
+
220
+ interface PostLayoutProps {
221
+ post: Post;
222
+ content: string;
223
+ settings?: { blog_name?: string };
224
+ }
225
+
226
+ export function PostLayout({ post, content, settings }: PostLayoutProps) {
227
+ const blogName = settings?.blog_name ?? "Blog";
228
+
229
+ return (
230
+ <main>
231
+ <nav>
232
+ <a href="/blog">{blogName}</a>
233
+ </nav>
234
+
235
+ <article>
236
+ <header>
237
+ <h1>{post.title}</h1>
238
+ {post.author_name && (
239
+ <p>
240
+ By {post.author_name}
241
+ {post.published_at && (
242
+ <>
243
+ {" · "}
244
+ <time dateTime={post.published_at}>
245
+ {new Date(post.published_at).toLocaleDateString()}
246
+ </time>
247
+ </>
248
+ )}
249
+ </p>
250
+ )}
251
+ </header>
252
+
253
+ {/* TODO: Style the post body to match your theme */}
254
+ {/* eslint-disable-next-line react/no-danger */}
255
+ <div dangerouslySetInnerHTML={{ __html: content }} />
256
+ </article>
257
+ </main>
258
+ );
259
+ }
260
+ `;
261
+ }
262
+ function themeConfigContent(cfg) {
263
+ return `/**
264
+ * theme.config.ts — ${cfg.name}
265
+ *
266
+ * Tailwind CSS preset overrides applied when this theme is active.
267
+ * Extend the defaults object with your color palette, fonts, etc.
268
+ *
269
+ * See: https://tailwindcss.com/docs/configuration#presets
270
+ */
271
+
272
+ const themeConfig = {
273
+ theme: {
274
+ extend: {
275
+ colors: {
276
+ // TODO: Add your palette
277
+ // primary: "oklch(60% 0.20 240)",
278
+ },
279
+ fontFamily: {
280
+ // TODO: Add your font stack
281
+ // sans: ["Inter", "ui-sans-serif", "system-ui"],
282
+ },
283
+ },
284
+ },
285
+ };
286
+
287
+ export default themeConfig;
288
+ `;
289
+ }
290
+ function stylesCssContent(cfg) {
291
+ return `/**
292
+ * styles.css — ${cfg.name}
293
+ *
294
+ * Global CSS for this theme. Loaded alongside Tailwind utilities.
295
+ * Keep it minimal — prefer Tailwind classes in components.
296
+ *
297
+ * CSS custom properties prefixed with --theme- to avoid collisions.
298
+ */
299
+
300
+ :root {
301
+ /* TODO: Replace with your theme tokens */
302
+ --theme-font-heading: system-ui, sans-serif;
303
+ --theme-font-body: system-ui, sans-serif;
304
+ --theme-color-bg: #ffffff;
305
+ --theme-color-text: #1a1a1a;
306
+ --theme-color-accent: oklch(60% 0.20 240);
307
+ }
308
+
309
+ /* TODO: Add your global base styles */
310
+ `;
311
+ }
312
+ function readmeContent(cfg) {
313
+ return `# ${cfg.name}
314
+
315
+ A VeloCMS ${cfg.type} theme${cfg.niche ? ` for ${cfg.niche}s` : ""}.
316
+
317
+ ## Getting started
318
+
319
+ \`\`\`bash
320
+ npm install
321
+ npm run dev
322
+ \`\`\`
323
+
324
+ ## Build
325
+
326
+ \`\`\`bash
327
+ npm run build
328
+ \`\`\`
329
+
330
+ Produces \`dist/\` ready for publishing to the VeloCMS marketplace.
331
+
332
+ ## Publish to marketplace
333
+
334
+ 1. Build: \`npm run build\`
335
+ 2. Validate manifest: ensure \`theme.manifest.json\` is complete
336
+ 3. Submit: \`npx velocms-cli theme submit\` (coming soon)
337
+
338
+ ## License
339
+
340
+ ${cfg.license}
341
+ `;
342
+ }
343
+ function gitignoreContent() {
344
+ return `node_modules/
345
+ dist/
346
+ *.tsbuildinfo
347
+ .env
348
+ .env.local
349
+ `;
350
+ }
351
+ export function scaffoldTheme(outputDir, cfg) {
352
+ if (existsSync(outputDir)) {
353
+ throw new Error(`Directory already exists: ${outputDir}`);
354
+ }
355
+ mkdirSync(outputDir, { recursive: true });
356
+ mkdirSync(join(outputDir, "src"), { recursive: true });
357
+ mkdirSync(join(outputDir, "preview"), { recursive: true });
358
+ writeFileSync(join(outputDir, "package.json"), packageJsonContent(cfg));
359
+ writeFileSync(join(outputDir, "theme.manifest.json"), manifestJsonContent(cfg));
360
+ writeFileSync(join(outputDir, "tsconfig.json"), tsconfigContent());
361
+ writeFileSync(join(outputDir, "tsconfig.build.json"), tsconfigBuildContent());
362
+ writeFileSync(join(outputDir, "README.md"), readmeContent(cfg));
363
+ writeFileSync(join(outputDir, ".gitignore"), gitignoreContent());
364
+ writeFileSync(join(outputDir, "src", "index.ts"), indexTsContent(cfg));
365
+ writeFileSync(join(outputDir, "src", "index.test.ts"), indexTestTsContent(cfg));
366
+ writeFileSync(join(outputDir, "src", "BlogIndex.tsx"), blogIndexContent(cfg));
367
+ writeFileSync(join(outputDir, "src", "PostLayout.tsx"), postLayoutContent(cfg));
368
+ writeFileSync(join(outputDir, "src", "theme.config.ts"), themeConfigContent(cfg));
369
+ writeFileSync(join(outputDir, "src", "styles.css"), stylesCssContent(cfg));
370
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "create-velocms-theme",
3
+ "version": "1.0.0",
4
+ "description": "Scaffolding tool for VeloCMS theme development",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-velocms-theme": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "typecheck": "tsc --noEmit",
18
+ "dev": "node dist/index.js",
19
+ "prepublishOnly": "npm run build && npm run typecheck"
20
+ },
21
+ "keywords": [
22
+ "velocms",
23
+ "cms",
24
+ "theme",
25
+ "cli",
26
+ "scaffold",
27
+ "scaffolding",
28
+ "generator",
29
+ "create-velocms-theme"
30
+ ],
31
+ "license": "MIT",
32
+ "author": {
33
+ "name": "VeloCMS",
34
+ "url": "https://velocms.org"
35
+ },
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/VeloCMS/create-velocms-theme.git"
39
+ },
40
+ "homepage": "https://velocms.org/developers",
41
+ "bugs": {
42
+ "url": "https://github.com/VeloCMS/create-velocms-theme/issues"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.0.0"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+ }