@karaoke-cms/create 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,12 +27,12 @@
27
27
  "state": {
28
28
  "type": "markdown",
29
29
  "state": {
30
- "file": "karaoke-cms/templates/docs-header.md",
30
+ "file": "karaoke-cms/manual/configuration.md",
31
31
  "mode": "source",
32
32
  "source": false
33
33
  },
34
34
  "icon": "lucide-file",
35
- "title": "docs-header"
35
+ "title": "configuration"
36
36
  }
37
37
  }
38
38
  ],
@@ -183,10 +183,10 @@
183
183
  "state": {
184
184
  "type": "file-properties",
185
185
  "state": {
186
- "file": "karaoke-cms/templates/docs-header.md"
186
+ "file": "karaoke-cms/manual/configuration.md"
187
187
  },
188
188
  "icon": "lucide-info",
189
- "title": "File properties for docs-header"
189
+ "title": "File properties for configuration"
190
190
  }
191
191
  }
192
192
  ],
@@ -210,8 +210,10 @@
210
210
  },
211
211
  "active": "52e565c48d7a7c39",
212
212
  "lastOpenFiles": [
213
- "karaoke-cms/templates/blog-header.md",
213
+ "karaoke-cms/templates/index.md",
214
+ "karaoke-cms/config/index.md",
214
215
  "karaoke-cms/templates/docs-header.md",
216
+ "karaoke-cms/templates/blog-header.md",
215
217
  "karaoke-cms/templates/index-by-foldernote.md",
216
218
  "karaoke-cms/manual/content.md",
217
219
  "karaoke-cms/manual/configuration.md",
@@ -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.
@@ -24,9 +24,34 @@ menus:
24
24
  - text: Tags
25
25
  href: /tags
26
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
27
52
 
28
53
  footer:
29
- orientation: horizontal
54
+ orientation: vertical
30
55
  entries:
31
56
  - text: RSS
32
57
  href: /rss.xml
@@ -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.9.0",
4
+ "version": "0.9.2",
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,7 +108,6 @@ 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
112
  const themeChoice = await askChoice('Theme', ['default', 'minimal', 'blog'], 0);
114
113
  const theme = themeChoice === 'default' || themeChoice === 'minimal'
@@ -127,14 +126,15 @@ async function main() {
127
126
  comments = { repo, repoId, category, categoryId };
128
127
  }
129
128
 
130
- rl.close();
131
-
132
129
  // ── Scaffold ────────────────────────────────────────────────────────────────
133
130
  console.log(`\n Scaffolding ${BOLD}${dir}${R}...\n`);
134
131
 
135
132
  mkdirSync(join(targetDir, 'src'), { recursive: true });
136
133
  mkdirSync(join(targetDir, 'public'), { recursive: true });
137
134
 
135
+ // siteUrl placeholder — replaced with the real *.pages.dev URL after deploy
136
+ const siteUrl = 'https://your-site.pages.dev';
137
+
138
138
  // Config and project files from templates
139
139
  const files = {
140
140
  'package.json': packageJson({ name: dir, astroVersion, theme }),
@@ -175,12 +175,36 @@ async function main() {
175
175
  console.log(` ${GRAY} (git init skipped — install git and run it manually)${R}`);
176
176
  }
177
177
 
178
- // ── 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 ───────────────────────────────────────────────────────────
179
197
  console.log(`\n${GREEN}✓${R} Done! Created ${BOLD}${dir}/${R}\n`);
180
- console.log(` Next steps:\n`);
181
- console.log(` ${CYAN}cd ${dir}${R}`);
182
- console.log(` ${CYAN}npm install${R}`);
183
- 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
+ }
184
208
  console.log(` ${GRAY}Open ${BOLD}${dir}/vault/${R}${GRAY} in Obsidian to write content.${R}\n`);
185
209
  }
186
210
 
@@ -188,3 +212,60 @@ main().catch(err => {
188
212
  console.error(`\n${RED}✗${R} ${err.message}`);
189
213
  process.exit(1);
190
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
@@ -4,10 +4,25 @@
4
4
  * Exported separately so they're independently testable.
5
5
  */
6
6
 
7
+ /**
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
12
+ */
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
+
7
21
  /**
8
22
  * @param {{ name: string, astroVersion: string, theme?: string }} opts
9
23
  */
10
24
  export function packageJson({ name, astroVersion, theme }) {
25
+ const projectSlug = slugify(name);
11
26
  const dependencies = {
12
27
  '@karaoke-cms/astro': `^${astroVersion}`,
13
28
  astro: '^6.0.0',
@@ -24,8 +39,12 @@ export function packageJson({ name, astroVersion, theme }) {
24
39
  dev: 'astro dev',
25
40
  build: 'astro build',
26
41
  preview: 'astro preview',
42
+ deploy: `astro build && npx wrangler pages deploy dist --project-name=${projectSlug}-preview`,
27
43
  },
28
44
  dependencies,
45
+ devDependencies: {
46
+ wrangler: '^3',
47
+ },
29
48
  }, null, 2) + '\n';
30
49
  }
31
50