@karaoke-cms/create 0.6.3 → 0.9.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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @karaoke-cms/create
2
+
3
+ Scaffolding tool for karaoke-cms. Creates a complete new project from scratch with a single command.
4
+
5
+ ## Where it belongs
6
+
7
+ `packages/create/` in the monorepo. Published to npm as `@karaoke-cms/create` and invoked via npm's `create` shorthand:
8
+
9
+ ```bash
10
+ npm create @karaoke-cms@latest
11
+ # or
12
+ npm create @karaoke-cms@latest my-site
13
+ ```
14
+
15
+ This resolves to running `packages/create/src/index.js` via the `create-karaoke-cms` bin entry.
16
+
17
+ ## What it does
18
+
19
+ Runs an interactive CLI that prompts for project configuration, then generates a complete project directory:
20
+
21
+ **Prompts:**
22
+ 1. Project directory name
23
+ 2. Site title
24
+ 3. Site URL (for sitemap and OG tags)
25
+ 4. Description
26
+ 5. Theme (`default`, `minimal`, or `blog`)
27
+ 6. Enable Pagefind search? (y/n)
28
+ 7. Enable Giscus comments? (y/n) — if yes, prompts for repo, repoId, category, categoryId
29
+
30
+ **Generated files:**
31
+ ```
32
+ my-site/
33
+ package.json # deps pinned to same version as create package
34
+ astro.config.mjs # site URL, karaoke() integration
35
+ karaoke.config.ts # title, description, theme, modules, vault path
36
+ src/
37
+ content.config.ts # makeCollections() wired to vault/
38
+ env.d.ts # /// <reference types="@karaoke-cms/astro/client" />
39
+ tsconfig.json
40
+ .gitignore
41
+ .env.default # KARAOKE_VAULT=./vault/
42
+ public/
43
+ _redirects # Cloudflare Pages 404 config
44
+ vault/ # Obsidian vault with sample blog post and doc
45
+ blog/
46
+ docs/
47
+ karaoke-cms/
48
+ config/
49
+ collections.yaml
50
+ menus.yaml
51
+ ```
52
+
53
+ After scaffolding, `git init` + initial commit runs automatically if `git` is available.
54
+
55
+ ## How to use
56
+
57
+ ```bash
58
+ npm create @karaoke-cms@latest
59
+
60
+ cd my-site
61
+ npm install
62
+ npm run dev # → http://localhost:4321
63
+ ```
64
+
65
+ Open `my-site/vault/` in Obsidian to write content. Files with `publish: true` in frontmatter appear on the live site.
66
+
67
+ Zero runtime dependencies — the scaffolder uses Node.js built-ins only.
68
+
69
+ ## How it changes the behavior of the system
70
+
71
+ The create package is not imported by running sites — it only runs once at project setup time. Its output determines:
72
+
73
+ - Which version of `@karaoke-cms/astro` and the theme packages are installed (pinned to the same version as the create package, since packages ship in lockstep)
74
+ - The initial `karaoke.config.ts` shape: which theme, which modules, vault location
75
+ - The initial vault structure: two sample posts so the site is not empty on first `dev`
76
+ - Whether search and comments infrastructure is included in `karaoke.config.ts`
@@ -0,0 +1,227 @@
1
+ {
2
+ "main": {
3
+ "id": "bf48daa9b3525d37",
4
+ "type": "split",
5
+ "children": [
6
+ {
7
+ "id": "4d1b0940e2f6130e",
8
+ "type": "tabs",
9
+ "children": [
10
+ {
11
+ "id": "d663da17c7b12a45",
12
+ "type": "leaf",
13
+ "state": {
14
+ "type": "markdown",
15
+ "state": {
16
+ "file": "docs/getting-started.md",
17
+ "mode": "source",
18
+ "source": false
19
+ },
20
+ "icon": "lucide-file",
21
+ "title": "getting-started"
22
+ }
23
+ },
24
+ {
25
+ "id": "52e565c48d7a7c39",
26
+ "type": "leaf",
27
+ "state": {
28
+ "type": "markdown",
29
+ "state": {
30
+ "file": "karaoke-cms/manual/configuration.md",
31
+ "mode": "source",
32
+ "source": false
33
+ },
34
+ "icon": "lucide-file",
35
+ "title": "configuration"
36
+ }
37
+ }
38
+ ],
39
+ "currentTab": 1
40
+ }
41
+ ],
42
+ "direction": "vertical"
43
+ },
44
+ "left": {
45
+ "id": "f848878ad9626a8e",
46
+ "type": "split",
47
+ "children": [
48
+ {
49
+ "id": "ea3061d9ba4255be",
50
+ "type": "tabs",
51
+ "children": [
52
+ {
53
+ "id": "94f03deca1f3d6ef",
54
+ "type": "leaf",
55
+ "state": {
56
+ "type": "file-explorer",
57
+ "state": {
58
+ "sortOrder": "alphabetical",
59
+ "autoReveal": false
60
+ },
61
+ "icon": "lucide-folder-closed",
62
+ "title": "Files"
63
+ }
64
+ },
65
+ {
66
+ "id": "b92a29ffd5bd2fcd",
67
+ "type": "leaf",
68
+ "state": {
69
+ "type": "search",
70
+ "state": {
71
+ "query": "",
72
+ "matchingCase": false,
73
+ "explainSearch": false,
74
+ "collapseAll": false,
75
+ "extraContext": false,
76
+ "sortOrder": "alphabetical"
77
+ },
78
+ "icon": "lucide-search",
79
+ "title": "Search"
80
+ }
81
+ },
82
+ {
83
+ "id": "2fa9963b38cc204e",
84
+ "type": "leaf",
85
+ "state": {
86
+ "type": "bookmarks",
87
+ "state": {},
88
+ "icon": "lucide-bookmark",
89
+ "title": "Bookmarks"
90
+ }
91
+ }
92
+ ]
93
+ }
94
+ ],
95
+ "direction": "horizontal",
96
+ "width": 300
97
+ },
98
+ "right": {
99
+ "id": "068d5dd11487213f",
100
+ "type": "split",
101
+ "children": [
102
+ {
103
+ "id": "b36a87ef12a5b05e",
104
+ "type": "tabs",
105
+ "children": [
106
+ {
107
+ "id": "5040cbb52c2ed323",
108
+ "type": "leaf",
109
+ "state": {
110
+ "type": "backlink",
111
+ "state": {
112
+ "collapseAll": false,
113
+ "extraContext": false,
114
+ "sortOrder": "alphabetical",
115
+ "showSearch": false,
116
+ "searchQuery": "",
117
+ "backlinkCollapsed": false,
118
+ "unlinkedCollapsed": true
119
+ },
120
+ "icon": "links-coming-in",
121
+ "title": "Backlinks"
122
+ }
123
+ },
124
+ {
125
+ "id": "262dc809ddada450",
126
+ "type": "leaf",
127
+ "state": {
128
+ "type": "outgoing-link",
129
+ "state": {
130
+ "linksCollapsed": false,
131
+ "unlinkedCollapsed": true
132
+ },
133
+ "icon": "links-going-out",
134
+ "title": "Outgoing links"
135
+ }
136
+ },
137
+ {
138
+ "id": "27e78afa43f86c41",
139
+ "type": "leaf",
140
+ "state": {
141
+ "type": "tag",
142
+ "state": {
143
+ "sortOrder": "frequency",
144
+ "useHierarchy": true,
145
+ "showSearch": false,
146
+ "searchQuery": ""
147
+ },
148
+ "icon": "lucide-tags",
149
+ "title": "Tags"
150
+ }
151
+ },
152
+ {
153
+ "id": "b25127a84b56f576",
154
+ "type": "leaf",
155
+ "state": {
156
+ "type": "all-properties",
157
+ "state": {
158
+ "sortOrder": "frequency",
159
+ "showSearch": false,
160
+ "searchQuery": ""
161
+ },
162
+ "icon": "lucide-archive",
163
+ "title": "All properties"
164
+ }
165
+ },
166
+ {
167
+ "id": "9144cec5db6c0b0e",
168
+ "type": "leaf",
169
+ "state": {
170
+ "type": "outline",
171
+ "state": {
172
+ "followCursor": false,
173
+ "showSearch": false,
174
+ "searchQuery": ""
175
+ },
176
+ "icon": "lucide-list",
177
+ "title": "Outline"
178
+ }
179
+ },
180
+ {
181
+ "id": "d5b8fda7e074d1fc",
182
+ "type": "leaf",
183
+ "state": {
184
+ "type": "file-properties",
185
+ "state": {
186
+ "file": "karaoke-cms/manual/configuration.md"
187
+ },
188
+ "icon": "lucide-info",
189
+ "title": "File properties for configuration"
190
+ }
191
+ }
192
+ ],
193
+ "currentTab": 5
194
+ }
195
+ ],
196
+ "direction": "horizontal",
197
+ "width": 300
198
+ },
199
+ "left-ribbon": {
200
+ "hiddenItems": {
201
+ "switcher:Open quick switcher": false,
202
+ "graph:Open graph view": false,
203
+ "canvas:Create new canvas": false,
204
+ "daily-notes:Open today's daily note": false,
205
+ "templates:Insert template": false,
206
+ "command-palette:Open command palette": false,
207
+ "bases:Create new base": false,
208
+ "templater-obsidian:Templater": false
209
+ }
210
+ },
211
+ "active": "52e565c48d7a7c39",
212
+ "lastOpenFiles": [
213
+ "karaoke-cms/templates/index.md",
214
+ "karaoke-cms/config/index.md",
215
+ "karaoke-cms/templates/docs-header.md",
216
+ "karaoke-cms/templates/blog-header.md",
217
+ "karaoke-cms/templates/index-by-foldernote.md",
218
+ "karaoke-cms/manual/content.md",
219
+ "karaoke-cms/manual/configuration.md",
220
+ "blog/hello-world.md",
221
+ "karaoke-cms/templates",
222
+ "karaoke-cms/index.md",
223
+ "blog/draft-post.md",
224
+ "docs/testing.md",
225
+ "docs/getting-started.md"
226
+ ]
227
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: "Config"
3
+ ---
4
+
5
+ # Config
6
+
7
+ Configuration files for your karaoke-cms vault.
8
+
9
+ | File | Purpose |
10
+ |------|---------|
11
+ | `collections.yaml` | Which collections are visible in dev vs production |
12
+ | `menus.yaml` | Site navigation: header menu, footer columns, and their entries |
13
+
14
+ Edit these files to change what appears on your site and in the navigation. Changes take effect on the next dev server restart or build.
@@ -0,0 +1,58 @@
1
+ # menus.yaml — define named menus for your site.
2
+ #
3
+ # Each menu has an orientation (horizontal or vertical) and a list of entries.
4
+ # Entries are sorted by weight (ascending). Submenus are defined by nesting entries.
5
+ #
6
+ # when: collection:name — hide entry when the collection is disabled or has no
7
+ # published posts. Omit `when` to always show.
8
+ #
9
+ # If this file is absent, karaoke-cms generates a default main menu (Blog/Docs/Tags)
10
+ # and a default footer menu (RSS) from your active collections.
11
+
12
+ menus:
13
+ main:
14
+ orientation: horizontal
15
+ entries:
16
+ - text: Blog
17
+ href: /blog
18
+ weight: 10
19
+ when: collection:blog
20
+ - text: Docs
21
+ href: /docs
22
+ weight: 20
23
+ when: collection:docs
24
+ - text: Tags
25
+ href: /tags
26
+ weight: 30
27
+ - text: karaoke-cms
28
+ href: /karaoke-cms
29
+ weight: 40
30
+ when: collection:karaoke-cms
31
+
32
+ footer-2:
33
+ orientation: vertical
34
+ entries:
35
+ - text: karaoke-cms
36
+ href: /karaoke-cms/manual/index
37
+ weight: 10
38
+ when: collection:karaoke-cms
39
+ entries:
40
+ - text: Manual
41
+ href: /karaoke-cms/manual/index
42
+ weight: 10
43
+ when: collection:karaoke-cms
44
+ - text: Config
45
+ href: /karaoke-cms/config/index
46
+ weight: 20
47
+ when: collection:karaoke-cms
48
+ - text: Templates
49
+ href: /karaoke-cms/templates/index
50
+ weight: 30
51
+ when: collection:karaoke-cms
52
+
53
+ footer:
54
+ orientation: vertical
55
+ entries:
56
+ - text: RSS
57
+ href: /rss.xml
58
+ weight: 10
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: "Templates"
3
+ ---
4
+
5
+ # Templates
6
+
7
+ Obsidian templates for common note types. Use with the [Templater](https://github.com/SilentVoid13/Templater) plugin.
8
+
9
+ | Template | Use for |
10
+ |----------|---------|
11
+ | `blog-header.md` | New blog posts — sets `title`, `date`, `publish: false` |
12
+ | `docs-header.md` | New documentation pages |
13
+ | `index-by-foldernote.md` | Folder index notes (requires the folder-notes Obsidian plugin) |
14
+
15
+ In Templater settings, set the template folder to `karaoke-cms/templates`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@karaoke-cms/create",
3
3
  "type": "module",
4
- "version": "0.6.3",
4
+ "version": "0.9.1",
5
5
  "description": "Scaffold a new karaoke-cms project",
6
6
  "bin": {
7
7
  "create-karaoke-cms": "./src/index.js"
@@ -24,6 +24,6 @@
24
24
  "vitest": "^4.1.1"
25
25
  },
26
26
  "scripts": {
27
- "test": "vitest run test/templates.test.js"
27
+ "test": "vitest run test/templates.test.js test/index.test.js"
28
28
  }
29
29
  }
package/src/index.js CHANGED
@@ -13,7 +13,7 @@ import { fileURLToPath } from 'url';
13
13
  import { spawnSync } from 'child_process';
14
14
  import {
15
15
  packageJson, astroConfig, karaokeConfig, contentConfig,
16
- envDts, tsConfig, gitignore, envDefault, cloudflareRedirects,
16
+ envDts, tsConfig, gitignore, envDefault, cloudflareRedirects, slugify,
17
17
  } from './templates.js';
18
18
 
19
19
  // ── ANSI helpers ─────────────────────────────────────────────────────────────
@@ -108,9 +108,11 @@ async function main() {
108
108
  .replace(/\b\w/g, c => c.toUpperCase());
109
109
 
110
110
  const title = await ask('Site title', defaultTitle);
111
- const siteUrl = await ask('Site URL', 'https://my-site.pages.dev');
112
111
  const description = await ask('Description', 'Our team knowledge base.');
113
- const theme = await askChoice('Theme', ['default', 'minimal'], 0);
112
+ const themeChoice = await askChoice('Theme', ['default', 'minimal', 'blog'], 0);
113
+ const theme = themeChoice === 'default' || themeChoice === 'minimal'
114
+ ? themeChoice
115
+ : `@karaoke-cms/theme-${themeChoice}`;
114
116
  const search = await askYesNo('Enable search? (Pagefind)', true);
115
117
  const commentsEnabled = await askYesNo('Enable comments? (requires Giscus setup)', false);
116
118
 
@@ -124,17 +126,18 @@ async function main() {
124
126
  comments = { repo, repoId, category, categoryId };
125
127
  }
126
128
 
127
- rl.close();
128
-
129
129
  // ── Scaffold ────────────────────────────────────────────────────────────────
130
130
  console.log(`\n Scaffolding ${BOLD}${dir}${R}...\n`);
131
131
 
132
132
  mkdirSync(join(targetDir, 'src'), { recursive: true });
133
133
  mkdirSync(join(targetDir, 'public'), { recursive: true });
134
134
 
135
+ // siteUrl placeholder — replaced with the real *.pages.dev URL after deploy
136
+ const siteUrl = 'https://your-site.pages.dev';
137
+
135
138
  // Config and project files from templates
136
139
  const files = {
137
- 'package.json': packageJson({ name: dir, astroVersion }),
140
+ 'package.json': packageJson({ name: dir, astroVersion, theme }),
138
141
  'astro.config.mjs': astroConfig({ siteUrl }),
139
142
  'karaoke.config.ts': karaokeConfig({ title, description, theme, search, comments }),
140
143
  'src/content.config.ts': contentConfig(),
@@ -172,12 +175,36 @@ async function main() {
172
175
  console.log(` ${GRAY} (git init skipped — install git and run it manually)${R}`);
173
176
  }
174
177
 
175
- // ── Done ────────────────────────────────────────────────────────────────────
178
+ // ── npm install ──────────────────────────────────────────────────────────────
179
+ console.log(`\n Installing dependencies...\n`);
180
+ const installResult = spawnSync('npm', ['install'], { cwd: targetDir, stdio: 'inherit' });
181
+ if (installResult.status !== 0) {
182
+ console.log(` ${GRAY}(install failed — run npm install manually)${R}`);
183
+ }
184
+
185
+ // ── Deploy prompt ────────────────────────────────────────────────────────────
186
+ const deployNow = await askYesNo('Publish a live preview to Cloudflare Pages now?', false);
187
+
188
+ rl.close();
189
+
190
+ let liveUrl = null;
191
+ if (deployNow) {
192
+ const projectName = `${slugify(dir)}-preview`;
193
+ liveUrl = await deployToCloudflare(targetDir, projectName);
194
+ }
195
+
196
+ // ── Success screen ───────────────────────────────────────────────────────────
176
197
  console.log(`\n${GREEN}✓${R} Done! Created ${BOLD}${dir}/${R}\n`);
177
- console.log(` Next steps:\n`);
178
- console.log(` ${CYAN}cd ${dir}${R}`);
179
- console.log(` ${CYAN}npm install${R}`);
180
- console.log(` ${CYAN}npm run dev${R} ${GRAY} http://localhost:4321${R}\n`);
198
+ if (liveUrl) {
199
+ console.log(` ${BOLD}🎤 Live:${R} ${liveUrl}`);
200
+ console.log(` ${BOLD}💻 Dev:${R} ${CYAN}cd ${dir} && npm run dev${R}`);
201
+ console.log(` ${BOLD}🔄 Redeploy:${R} ${CYAN}npm run deploy${R}\n`);
202
+ } else {
203
+ console.log(` Next steps:\n`);
204
+ console.log(` ${CYAN}cd ${dir}${R}`);
205
+ console.log(` ${CYAN}npm run dev${R} ${GRAY}→ http://localhost:4321${R}`);
206
+ console.log(` ${CYAN}npm run deploy${R} ${GRAY}→ publish to Cloudflare Pages${R}\n`);
207
+ }
181
208
  console.log(` ${GRAY}Open ${BOLD}${dir}/vault/${R}${GRAY} in Obsidian to write content.${R}\n`);
182
209
  }
183
210
 
@@ -185,3 +212,60 @@ main().catch(err => {
185
212
  console.error(`\n${RED}✗${R} ${err.message}`);
186
213
  process.exit(1);
187
214
  });
215
+
216
+ // ── Cloudflare Pages deploy ───────────────────────────────────────────────────
217
+ export async function deployToCloudflare(targetDir, projectName) {
218
+ console.log(`\n ${BOLD}Deploying to Cloudflare Pages...${R}\n`);
219
+
220
+ // 1. Check if logged in
221
+ const whoami = spawnSync('npx', ['wrangler', 'whoami', '--json'],
222
+ { cwd: targetDir, encoding: 'utf8' });
223
+
224
+ if (whoami.status !== 0 || whoami.stdout.includes('You are not authenticated')) {
225
+ console.log(` ${CYAN}Opening Cloudflare login...${R}`);
226
+ const login = spawnSync('npx', ['wrangler', 'login'],
227
+ { cwd: targetDir, stdio: 'inherit' });
228
+ if (login.status !== 0) {
229
+ console.log(` ${GRAY}Login cancelled — run 'npm run deploy' when ready.${R}`);
230
+ return null;
231
+ }
232
+ }
233
+
234
+ // 2. Build
235
+ console.log(` ${CYAN}Building...${R}`);
236
+ const build = spawnSync('npm', ['run', 'build'],
237
+ { cwd: targetDir, stdio: 'inherit' });
238
+ if (build.status !== 0) {
239
+ console.log(` ${RED}Build failed — run 'npm run deploy' when ready.${R}`);
240
+ return null;
241
+ }
242
+
243
+ // 3. Create Pages project — best-effort, ignore failure (project may already exist on re-run)
244
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
245
+ const createArgs = ['wrangler', 'pages', 'project', 'create', projectName,
246
+ '--production-branch', 'main'];
247
+ if (accountId) createArgs.push('--account-id', accountId);
248
+ spawnSync('npx', createArgs, { cwd: targetDir, stdio: 'pipe' });
249
+
250
+ // 4. Deploy — stdio: inherit so wrangler output + interactive prompts (e.g. account picker) are visible
251
+ console.log(` ${CYAN}Uploading...${R}`);
252
+ const deployArgs = ['wrangler', 'pages', 'deploy', 'dist',
253
+ `--project-name=${projectName}`];
254
+ if (accountId) deployArgs.push('--account-id', accountId);
255
+ const deploy = spawnSync('npx', deployArgs, { cwd: targetDir, stdio: 'inherit' });
256
+
257
+ if (deploy.status !== 0) {
258
+ console.log(` ${RED}Deploy failed — run 'npm run deploy' when ready.${R}`);
259
+ return null;
260
+ }
261
+
262
+ // 5. URL is deterministic — no need to parse wrangler stdout
263
+ const liveUrl = `https://${projectName}.pages.dev`;
264
+
265
+ // 6. Write real URL back to astro.config.mjs (replaces placeholder set during scaffold)
266
+ const configPath = join(targetDir, 'astro.config.mjs');
267
+ const configSrc = readFileSync(configPath, 'utf8');
268
+ writeFileSync(configPath, configSrc.replace('https://your-site.pages.dev', liveUrl));
269
+
270
+ return liveUrl;
271
+ }
package/src/templates.js CHANGED
@@ -5,9 +5,31 @@
5
5
  */
6
6
 
7
7
  /**
8
- * @param {{ name: string, astroVersion: string }} opts
8
+ * Compute a Cloudflare-safe project slug from a directory name.
9
+ * Rules: lowercase, spaces/underscores → hyphens, non-alphanumeric stripped,
10
+ * truncated to 24 chars (leaves room for the -preview suffix within CF's 28-char limit).
11
+ * @param {string} dir
9
12
  */
10
- export function packageJson({ name, astroVersion }) {
13
+ export function slugify(dir) {
14
+ return dir
15
+ .toLowerCase()
16
+ .replace(/[\s_]+/g, '-')
17
+ .replace(/[^a-z0-9-]/g, '')
18
+ .slice(0, 24);
19
+ }
20
+
21
+ /**
22
+ * @param {{ name: string, astroVersion: string, theme?: string }} opts
23
+ */
24
+ export function packageJson({ name, astroVersion, theme }) {
25
+ const projectSlug = slugify(name);
26
+ const dependencies = {
27
+ '@karaoke-cms/astro': `^${astroVersion}`,
28
+ astro: '^6.0.0',
29
+ };
30
+ if (theme?.startsWith('@')) {
31
+ dependencies[theme] = `^${astroVersion}`;
32
+ }
11
33
  return JSON.stringify({
12
34
  name,
13
35
  private: true,
@@ -17,10 +39,11 @@ export function packageJson({ name, astroVersion }) {
17
39
  dev: 'astro dev',
18
40
  build: 'astro build',
19
41
  preview: 'astro preview',
42
+ deploy: `astro build && npx wrangler pages deploy dist --project-name=${projectSlug}-preview`,
20
43
  },
21
- dependencies: {
22
- '@karaoke-cms/astro': `^${astroVersion}`,
23
- astro: '^6.0.0',
44
+ dependencies,
45
+ devDependencies: {
46
+ wrangler: '^3',
24
47
  },
25
48
  }, null, 2) + '\n';
26
49
  }