@karaoke-cms/create 0.6.2 → 0.9.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.
Files changed (30) hide show
  1. package/README.md +76 -0
  2. package/karaoke-create-vault/.obsidian/app.json +14 -0
  3. package/karaoke-create-vault/.obsidian/appearance.json +1 -0
  4. package/karaoke-create-vault/.obsidian/community-plugins.json +4 -0
  5. package/karaoke-create-vault/.obsidian/core-plugins.json +33 -0
  6. package/karaoke-create-vault/.obsidian/plugins/folder-notes/data.json +131 -0
  7. package/karaoke-create-vault/.obsidian/plugins/folder-notes/main.js +9190 -0
  8. package/karaoke-create-vault/.obsidian/plugins/folder-notes/manifest.json +12 -0
  9. package/karaoke-create-vault/.obsidian/plugins/folder-notes/styles.css +355 -0
  10. package/karaoke-create-vault/.obsidian/plugins/templater-obsidian/main.js +45 -0
  11. package/karaoke-create-vault/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
  12. package/karaoke-create-vault/.obsidian/plugins/templater-obsidian/styles.css +226 -0
  13. package/karaoke-create-vault/.obsidian/workspace.json +225 -0
  14. package/karaoke-create-vault/blog/draft-post.md +15 -0
  15. package/karaoke-create-vault/blog/hello-world.md +26 -0
  16. package/karaoke-create-vault/docs/getting-started.md +49 -0
  17. package/karaoke-create-vault/docs/testing.md +0 -0
  18. package/karaoke-create-vault/karaoke-cms/config/collections.yaml +10 -0
  19. package/karaoke-create-vault/karaoke-cms/config/menus.yaml +33 -0
  20. package/karaoke-create-vault/karaoke-cms/manual/configuration.md +77 -0
  21. package/karaoke-create-vault/karaoke-cms/manual/content.md +38 -0
  22. package/karaoke-create-vault/karaoke-cms/manual/deployment.md +46 -0
  23. package/karaoke-create-vault/karaoke-cms/manual/index.md +41 -0
  24. package/karaoke-create-vault/karaoke-cms/manual/privacy.md +37 -0
  25. package/karaoke-create-vault/karaoke-cms/templates/blog-header.md +9 -0
  26. package/karaoke-create-vault/karaoke-cms/templates/docs-header.md +9 -0
  27. package/karaoke-create-vault/karaoke-cms/templates/index-by-foldernote.md +8 -0
  28. package/package.json +3 -2
  29. package/src/index.js +34 -13
  30. package/src/templates.js +31 -78
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: "Writing Content"
3
+ ---
4
+
5
+ # Writing Content
6
+
7
+ > **Handbook — dev only.**
8
+
9
+ ## Frontmatter fields
10
+
11
+ ```yaml
12
+ ---
13
+ title: "My Post" # required — build fails without it
14
+ publish: true # required to appear on site (default: false)
15
+ date: 2026-01-15 # optional — YYYY-MM-DD, used for sorting
16
+ author: "Name" # optional — string or array ["Alice", "Bob"]
17
+ description: "..." # optional — used in OG meta tags and RSS
18
+ tags: [writing, tutorial] # optional — enables tag pages at /tags/[tag]
19
+ ---
20
+ ```
21
+
22
+ ## Privacy model
23
+
24
+ Every file is **private by default**. `publish: false` (or a missing `publish` field) means the file stays in your vault and never enters the build — not in HTML, not in RSS, not in the sitemap, not in the Pagefind search index.
25
+
26
+ Only `publish: true` files appear on your site.
27
+
28
+ ## Wikilinks
29
+
30
+ Use `[[slug]]` syntax to link between notes. karaoke-cms resolves wikilinks automatically:
31
+
32
+ - `[[blog/hello-world]]` → `/blog/hello-world/`
33
+ - `[[docs/getting-started]]` → `/docs/getting-started/`
34
+ - `[[docs/getting-started|Getting Started]]` → link text "Getting Started"
35
+
36
+ ## AI enrichment (optional)
37
+
38
+ Run `npx @karaoke-cms/enrich` to auto-generate `tags`, `description`, and `reading_time` for published notes using OpenAI or Anthropic. Skips already-enriched notes via a local cache.
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: "Deployment"
3
+ ---
4
+
5
+ # Deployment
6
+
7
+ > **Handbook — dev only.**
8
+
9
+ ## Cloudflare Pages (recommended)
10
+
11
+ 1. Push your project to a GitHub repo
12
+ 2. Go to [Cloudflare Pages](https://pages.cloudflare.com) → Create a project → Connect to your repo
13
+ 3. Build settings:
14
+ - **Build command:** `npm run build`
15
+ - **Build output directory:** `dist`
16
+ - **Node.js version:** 22
17
+ 4. Deploy
18
+
19
+ On every push to `main`, Cloudflare Pages rebuilds and redeploys automatically.
20
+
21
+ ## GitHub Actions (privacy gate)
22
+
23
+ The included `deploy.yml` workflow runs `assert-privacy` on every build to verify that no private content leaks into `dist/`. If a private note accidentally gets into the build, the deploy fails before it reaches production.
24
+
25
+ ## Custom domain
26
+
27
+ In Cloudflare Pages → your project → Custom domains → Add a domain. Update `site:` in `astro.config.mjs` to match:
28
+
29
+ ```js
30
+ export default defineConfig({
31
+ site: 'https://your-domain.com',
32
+ // ...
33
+ });
34
+ ```
35
+
36
+ ## Environment variables
37
+
38
+ If you use `@karaoke-cms/enrich` in CI:
39
+
40
+ | Variable | Purpose |
41
+ |----------|---------|
42
+ | `OPENAI_API_KEY` | Required if `ENRICH_PROVIDER=openai` (default) |
43
+ | `ANTHROPIC_API_KEY` | Required if `ENRICH_PROVIDER=anthropic` |
44
+ | `ENRICH_ENABLED` | Set to `true` to enable AI enrichment in CI |
45
+ | `CLOUDFLARE_API_TOKEN` | For Cloudflare Pages deploy via GitHub Actions |
46
+ | `CLOUDFLARE_ACCOUNT_ID` | For Cloudflare Pages deploy via GitHub Actions |
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: "Handbook"
3
+ ---
4
+
5
+ # karaoke-cms Handbook
6
+
7
+ > **Handbook — dev only.** This section is visible at `/karaoke-cms` in dev mode and never ships to production.
8
+
9
+ karaoke-cms is an Astro framework for publishing Obsidian vaults as static sites. Private-by-default: only files with `publish: true` in frontmatter appear on your site.
10
+
11
+ ## How it works
12
+
13
+ ```
14
+ Obsidian vault (this directory)
15
+ ↓ write notes with publish: true
16
+ ↓ push to main
17
+ ↓ GitHub Actions: astro build + assert-privacy
18
+ ↓ Cloudflare Pages
19
+ ↓ live site
20
+ ```
21
+
22
+ Your vault is also your deploy pipeline config, your documentation, and your content collection — all in one place.
23
+
24
+ ## Handbook sections
25
+
26
+ - [[content]] — Writing content and frontmatter
27
+ - [[configuration]] — karaoke.config.ts reference
28
+ - [[deployment]] — Deploying to Cloudflare Pages
29
+ - [[privacy]] — How publish:true works
30
+
31
+ ## Collections
32
+
33
+ This vault has three content collections:
34
+
35
+ | Collection | Mode | Purpose |
36
+ |------------|------|---------|
37
+ | `blog/` | dev + prod | Published blog posts |
38
+ | `docs/` | dev + prod | Published documentation |
39
+ | `karaoke-cms/` | dev only | This handbook — never ships |
40
+
41
+ Edit `content/karaoke-cms/config/collections.yaml` to add your own collections or change their visibility.
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: "Privacy"
3
+ ---
4
+
5
+ # Privacy Model
6
+
7
+ > **Handbook — dev only.**
8
+
9
+ ## The rule
10
+
11
+ **Every file is private by default.** Only files with `publish: true` in frontmatter appear on your site.
12
+
13
+ A file without `publish: true` never enters the build — not in HTML, RSS, sitemap, or the Pagefind search index.
14
+
15
+ ## How it's enforced
16
+
17
+ Two independent layers:
18
+
19
+ 1. **Astro content filter** — `getCollection('blog', ({ data }) => data.publish === true)` filters at the query level. Pages only receive published entries.
20
+
21
+ 2. **assert-privacy** — a post-build check that scans `dist/` for known private note titles and content. If any private content leaks, the build fails before deploy.
22
+
23
+ The collection mode system adds a third layer: `karaoke-cms/` is excluded from `makeCollections()` in production entirely. Even if a handbook page had `publish: true`, it would never enter the prod build graph.
24
+
25
+ ## What "private" means
26
+
27
+ | Field | Builds? | Searchable? | In RSS? | In sitemap? |
28
+ |-------|---------|-------------|---------|-------------|
29
+ | `publish: true` | Yes | Yes | Yes | Yes |
30
+ | `publish: false` | No | No | No | No |
31
+ | missing | No | No | No | No |
32
+
33
+ ## Audit your site
34
+
35
+ After every build, `assert-privacy.js` outputs a summary of what shipped. Check it in your CI logs.
36
+
37
+ For a manual check: `node node_modules/@karaoke-cms/assert-privacy/dist/index.js dist`
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: "" # required — build fails without it
3
+ publish: false # required to appear on site (default: false)
4
+ date: 2026-01-15 # optional — YYYY-MM-DD, used for sorting
5
+ author: "" # optional — string or array ["Alice", "Bob"]
6
+ description: "" # optional — used in OG meta tags and RSS
7
+ tags: [general] # optional — enables tag pages at /tags/[tag]
8
+ notetype: blog
9
+ ---
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: "" # required — build fails without it
3
+ publish: false # required to appear on site (default: false)
4
+ date: 2026-01-15 # optional — YYYY-MM-DD, used for sorting
5
+ author: "" # optional — string or array ["Alice", "Bob"]
6
+ description: "" # optional — used in OG meta tags and RSS
7
+ tags: [general] # optional — enables tag pages at /tags/[tag]
8
+ notetype: blog
9
+ ---
@@ -0,0 +1,8 @@
1
+ ---
2
+ title: "" # required — build fails without it
3
+ publish: false # required to appear on site (default: false)
4
+ date: 2026-01-15 # optional — YYYY-MM-DD, used for sorting
5
+ author: "" # optional — string or array ["Alice", "Bob"]
6
+ description: "..." # optional — used in OG meta tags and RSS
7
+ tags: [general] # optional — enables tag pages at /tags/[tag]
8
+ ---
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@karaoke-cms/create",
3
3
  "type": "module",
4
- "version": "0.6.2",
4
+ "version": "0.9.0",
5
5
  "description": "Scaffold a new karaoke-cms project",
6
6
  "bin": {
7
7
  "create-karaoke-cms": "./src/index.js"
8
8
  },
9
9
  "files": [
10
- "src/"
10
+ "src/",
11
+ "karaoke-create-vault/"
11
12
  ],
12
13
  "engines": {
13
14
  "node": ">=22.12.0"
package/src/index.js CHANGED
@@ -7,12 +7,13 @@
7
7
  */
8
8
 
9
9
  import { createInterface } from 'readline';
10
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
10
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync } from 'fs';
11
11
  import { join, resolve } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+ import { spawnSync } from 'child_process';
12
14
  import {
13
15
  packageJson, astroConfig, karaokeConfig, contentConfig,
14
- envDts, tsConfig, gitignore, cloudflareRedirects,
15
- helloWorld, draftPost, gettingStarted,
16
+ envDts, tsConfig, gitignore, envDefault, cloudflareRedirects,
16
17
  } from './templates.js';
17
18
 
18
19
  // ── ANSI helpers ─────────────────────────────────────────────────────────────
@@ -109,7 +110,10 @@ async function main() {
109
110
  const title = await ask('Site title', defaultTitle);
110
111
  const siteUrl = await ask('Site URL', 'https://my-site.pages.dev');
111
112
  const description = await ask('Description', 'Our team knowledge base.');
112
- const theme = await askChoice('Theme', ['default', 'minimal'], 0);
113
+ const themeChoice = await askChoice('Theme', ['default', 'minimal', 'blog'], 0);
114
+ const theme = themeChoice === 'default' || themeChoice === 'minimal'
115
+ ? themeChoice
116
+ : `@karaoke-cms/theme-${themeChoice}`;
113
117
  const search = await askYesNo('Enable search? (Pagefind)', true);
114
118
  const commentsEnabled = await askYesNo('Enable comments? (requires Giscus setup)', false);
115
119
 
@@ -128,25 +132,20 @@ async function main() {
128
132
  // ── Scaffold ────────────────────────────────────────────────────────────────
129
133
  console.log(`\n Scaffolding ${BOLD}${dir}${R}...\n`);
130
134
 
131
- const date = new Date().toISOString().split('T')[0];
132
-
133
135
  mkdirSync(join(targetDir, 'src'), { recursive: true });
134
- mkdirSync(join(targetDir, 'content/blog'), { recursive: true });
135
- mkdirSync(join(targetDir, 'content/docs'), { recursive: true });
136
136
  mkdirSync(join(targetDir, 'public'), { recursive: true });
137
137
 
138
+ // Config and project files from templates
138
139
  const files = {
139
- 'package.json': packageJson({ name: dir, astroVersion }),
140
+ 'package.json': packageJson({ name: dir, astroVersion, theme }),
140
141
  'astro.config.mjs': astroConfig({ siteUrl }),
141
142
  'karaoke.config.ts': karaokeConfig({ title, description, theme, search, comments }),
142
143
  'src/content.config.ts': contentConfig(),
143
144
  'src/env.d.ts': envDts(),
144
145
  'tsconfig.json': tsConfig(),
145
146
  '.gitignore': gitignore(),
147
+ '.env.default': envDefault(),
146
148
  'public/_redirects': cloudflareRedirects(),
147
- 'content/blog/hello-world.md': helloWorld({ date }),
148
- 'content/blog/draft-post.md': draftPost({ date }),
149
- 'content/docs/getting-started.md': gettingStarted({ date }),
150
149
  };
151
150
 
152
151
  for (const [file, content] of Object.entries(files)) {
@@ -154,13 +153,35 @@ async function main() {
154
153
  console.log(` ${GREEN}+${R} ${file}`);
155
154
  }
156
155
 
156
+ // Vault content: copy the real Obsidian vault into vault/ subdirectory
157
+ const vaultSrc = fileURLToPath(new URL('../karaoke-create-vault', import.meta.url));
158
+ cpSync(vaultSrc, join(targetDir, 'vault'), { recursive: true });
159
+ console.log(` ${GREEN}+${R} vault/ (Obsidian vault)`);
160
+
161
+ // ── Git init ────────────────────────────────────────────────────────────────
162
+ const gitOk = (() => {
163
+ const run = (args) => spawnSync('git', args, { cwd: targetDir, encoding: 'utf8' }).status === 0;
164
+ return (
165
+ run(['init']) &&
166
+ run(['add', '-A']) &&
167
+ run(['-c', 'user.name=karaoke-cms', '-c', 'user.email=setup@karaoke-cms.org',
168
+ 'commit', '-m', 'chore: initial karaoke-cms setup'])
169
+ );
170
+ })();
171
+
172
+ if (gitOk) {
173
+ console.log(` ${GREEN}+${R} git repository initialized`);
174
+ } else {
175
+ console.log(` ${GRAY} (git init skipped — install git and run it manually)${R}`);
176
+ }
177
+
157
178
  // ── Done ────────────────────────────────────────────────────────────────────
158
179
  console.log(`\n${GREEN}✓${R} Done! Created ${BOLD}${dir}/${R}\n`);
159
180
  console.log(` Next steps:\n`);
160
181
  console.log(` ${CYAN}cd ${dir}${R}`);
161
182
  console.log(` ${CYAN}npm install${R}`);
162
183
  console.log(` ${CYAN}npm run dev${R} ${GRAY}→ http://localhost:4321${R}\n`);
163
- console.log(` ${GRAY}Open ${BOLD}${dir}/${R}${GRAY} in Obsidian as a vault to write content.${R}\n`);
184
+ console.log(` ${GRAY}Open ${BOLD}${dir}/vault/${R}${GRAY} in Obsidian to write content.${R}\n`);
164
185
  }
165
186
 
166
187
  main().catch(err => {
package/src/templates.js CHANGED
@@ -5,9 +5,16 @@
5
5
  */
6
6
 
7
7
  /**
8
- * @param {{ name: string, astroVersion: string }} opts
8
+ * @param {{ name: string, astroVersion: string, theme?: string }} opts
9
9
  */
10
- export function packageJson({ name, astroVersion }) {
10
+ export function packageJson({ name, astroVersion, theme }) {
11
+ const dependencies = {
12
+ '@karaoke-cms/astro': `^${astroVersion}`,
13
+ astro: '^6.0.0',
14
+ };
15
+ if (theme?.startsWith('@')) {
16
+ dependencies[theme] = `^${astroVersion}`;
17
+ }
11
18
  return JSON.stringify({
12
19
  name,
13
20
  private: true,
@@ -18,10 +25,7 @@ export function packageJson({ name, astroVersion }) {
18
25
  build: 'astro build',
19
26
  preview: 'astro preview',
20
27
  },
21
- dependencies: {
22
- '@karaoke-cms/astro': `^${astroVersion}`,
23
- astro: '^6.0.0',
24
- },
28
+ dependencies,
25
29
  }, null, 2) + '\n';
26
30
  }
27
31
 
@@ -66,8 +70,12 @@ export function karaokeConfig({ title, description, theme, search, comments }) {
66
70
  : '';
67
71
 
68
72
  return `import type { KaraokeConfig } from '@karaoke-cms/astro';
73
+ import { loadEnv } from '@karaoke-cms/astro/env';
74
+
75
+ const env = loadEnv(new URL('.', import.meta.url));
69
76
 
70
77
  const config: KaraokeConfig = {
78
+ vault: env.KARAOKE_VAULT,
71
79
  title: ${JSON.stringify(title)},
72
80
  description: ${JSON.stringify(description)},
73
81
  theme: ${JSON.stringify(theme)},${modulesStr}
@@ -79,8 +87,11 @@ export default config;
79
87
 
80
88
  export function contentConfig() {
81
89
  return `import { makeCollections } from '@karaoke-cms/astro/collections';
90
+ import { loadEnv } from '@karaoke-cms/astro/env';
91
+
92
+ const env = loadEnv(new URL('..', import.meta.url));
82
93
 
83
- export const collections = makeCollections(new URL('..', import.meta.url));
94
+ export const collections = makeCollections(new URL('..', import.meta.url), env.KARAOKE_VAULT);
84
95
  `;
85
96
  }
86
97
 
@@ -103,82 +114,24 @@ node_modules/
103
114
  dist/
104
115
  .astro/
105
116
  .env
106
- .env.*
117
+ .env.local
118
+ .env.*.local
119
+ # Obsidian workspace state (personal, not shared)
120
+ .obsidian/workspace.json
121
+ .obsidian/workspace-mobile.json
107
122
  `;
108
123
  }
109
124
 
110
- export function cloudflareRedirects() {
111
- return `/* /404.html 404\n`;
112
- }
113
-
114
- /** @param {{ date: string }} opts */
115
- export function helloWorld({ date }) {
116
- return `---
117
- title: "Hello World"
118
- publish: true
119
- date: ${date}
120
- author: "The Team"
121
- description: "Our first published post."
122
- ---
123
-
124
- Welcome! This post has \`publish: true\` so it appears on the site.
125
-
126
- ## How publishing works
127
-
128
- 1. Write Markdown in Obsidian (or any editor)
129
- 2. Add \`publish: true\` to the frontmatter when ready to share
130
- 3. Push to \`main\` — GitHub Actions builds and deploys automatically
131
-
132
- Posts without \`publish: true\` stay private in your vault.
125
+ export function envDefault() {
126
+ return `# Obsidian vault location — open this folder in Obsidian to write content.
127
+ # Override in .env (gitignored) to point to a vault elsewhere on your machine.
128
+ # Absolute path: KARAOKE_VAULT=/Users/you/Obsidian/my-vault
129
+ # Relative path: KARAOKE_VAULT=../my-obsidian-vault
130
+ KARAOKE_VAULT=./vault/
133
131
  `;
134
132
  }
135
133
 
136
- /** @param {{ date: string }} opts */
137
- export function draftPost({ date }) {
138
- return `---
139
- title: "Draft Post"
140
- publish: false
141
- date: ${date}
142
- ---
143
-
144
- This post has \`publish: false\`. It lives in your vault but never appears on the site.
145
-
146
- Change it to \`publish: true\` when you're ready to share it.
147
- `;
134
+ export function cloudflareRedirects() {
135
+ return `/* /404.html 404\n`;
148
136
  }
149
137
 
150
- /** @param {{ date: string }} opts */
151
- export function gettingStarted({ date }) {
152
- return `---
153
- title: "Getting Started"
154
- publish: true
155
- date: ${date}
156
- description: "How to set up and use your karaoke-cms site."
157
- ---
158
-
159
- ## Setup
160
-
161
- 1. Open this folder in Obsidian as a vault
162
- 2. Edit \`karaoke.config.ts\` — set your title, theme, and modules
163
- 3. Set your Cloudflare Pages URL in \`astro.config.mjs\`
164
- 4. Push to \`main\` — your site is live
165
-
166
- ## Writing content
167
-
168
- - Blog posts go in \`content/blog/\`
169
- - Documentation goes in \`content/docs/\`
170
- - Set \`publish: true\` to make a file public
171
-
172
- ## Frontmatter reference
173
-
174
- \`\`\`yaml
175
- ---
176
- title: "My Post" # required
177
- publish: true # required to appear on site
178
- date: 2026-01-15 # optional, YYYY-MM-DD
179
- author: "Name" # optional
180
- description: "..." # optional, used in OG tags and RSS
181
- ---
182
- \`\`\`
183
- `;
184
- }