@karaoke-cms/create 0.6.1 → 0.6.3
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/karaoke-create-vault/.obsidian/app.json +14 -0
- package/karaoke-create-vault/.obsidian/appearance.json +1 -0
- package/karaoke-create-vault/.obsidian/community-plugins.json +4 -0
- package/karaoke-create-vault/.obsidian/core-plugins.json +33 -0
- package/karaoke-create-vault/.obsidian/plugins/folder-notes/data.json +131 -0
- package/karaoke-create-vault/.obsidian/plugins/folder-notes/main.js +9190 -0
- package/karaoke-create-vault/.obsidian/plugins/folder-notes/manifest.json +12 -0
- package/karaoke-create-vault/.obsidian/plugins/folder-notes/styles.css +355 -0
- package/karaoke-create-vault/.obsidian/plugins/templater-obsidian/main.js +45 -0
- package/karaoke-create-vault/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
- package/karaoke-create-vault/.obsidian/plugins/templater-obsidian/styles.css +226 -0
- package/karaoke-create-vault/blog/draft-post.md +15 -0
- package/karaoke-create-vault/blog/hello-world.md +26 -0
- package/karaoke-create-vault/docs/getting-started.md +49 -0
- package/karaoke-create-vault/docs/testing.md +0 -0
- package/karaoke-create-vault/karaoke-cms/config/collections.yaml +10 -0
- package/karaoke-create-vault/karaoke-cms/manual/configuration.md +77 -0
- package/karaoke-create-vault/karaoke-cms/manual/content.md +38 -0
- package/karaoke-create-vault/karaoke-cms/manual/deployment.md +46 -0
- package/karaoke-create-vault/karaoke-cms/manual/index.md +41 -0
- package/karaoke-create-vault/karaoke-cms/manual/privacy.md +37 -0
- package/karaoke-create-vault/karaoke-cms/templates/blog-header.md +9 -0
- package/karaoke-create-vault/karaoke-cms/templates/docs-header.md +9 -0
- package/karaoke-create-vault/karaoke-cms/templates/index-by-foldernote.md +8 -0
- package/package.json +14 -3
- package/src/index.js +187 -0
- package/src/templates.js +133 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @karaoke-cms/create — scaffold a new karaoke-cms project.
|
|
4
|
+
* Usage: npm create @karaoke-cms@latest [project-dir]
|
|
5
|
+
*
|
|
6
|
+
* Zero runtime dependencies — uses Node.js built-ins only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createInterface } from 'readline';
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync } from 'fs';
|
|
11
|
+
import { join, resolve } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { spawnSync } from 'child_process';
|
|
14
|
+
import {
|
|
15
|
+
packageJson, astroConfig, karaokeConfig, contentConfig,
|
|
16
|
+
envDts, tsConfig, gitignore, envDefault, cloudflareRedirects,
|
|
17
|
+
} from './templates.js';
|
|
18
|
+
|
|
19
|
+
// ── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
20
|
+
const R = '\x1b[0m';
|
|
21
|
+
const BOLD = '\x1b[1m';
|
|
22
|
+
const GREEN = '\x1b[32m';
|
|
23
|
+
const CYAN = '\x1b[36m';
|
|
24
|
+
const GRAY = '\x1b[90m';
|
|
25
|
+
const RED = '\x1b[31m';
|
|
26
|
+
|
|
27
|
+
// Use create package's own version as the @karaoke-cms/astro dep range
|
|
28
|
+
// (packages ship in lockstep)
|
|
29
|
+
const { version: astroVersion } = JSON.parse(
|
|
30
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8')
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Separate input (readline) from output (process.stdout) so piped stdin works
|
|
34
|
+
// correctly. Buffer all 'line' events into a queue so that lines emitted before
|
|
35
|
+
// the next nextLine() call are never dropped (this happens with piped input
|
|
36
|
+
// because readline reads the entire pipe before the first await completes).
|
|
37
|
+
const rl = createInterface({ input: process.stdin });
|
|
38
|
+
const lineQueue = [];
|
|
39
|
+
let lineWaiter = null;
|
|
40
|
+
let inputClosed = false;
|
|
41
|
+
|
|
42
|
+
rl.on('line', line => {
|
|
43
|
+
if (lineWaiter) {
|
|
44
|
+
const resolve = lineWaiter;
|
|
45
|
+
lineWaiter = null;
|
|
46
|
+
resolve(line);
|
|
47
|
+
} else {
|
|
48
|
+
lineQueue.push(line);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
rl.on('close', () => {
|
|
53
|
+
inputClosed = true;
|
|
54
|
+
if (lineWaiter) { lineWaiter(''); lineWaiter = null; }
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function nextLine() {
|
|
58
|
+
if (lineQueue.length > 0) return Promise.resolve(lineQueue.shift());
|
|
59
|
+
if (inputClosed) return Promise.resolve('');
|
|
60
|
+
return new Promise(resolve => { lineWaiter = resolve; });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function ask(question, defaultValue = '') {
|
|
64
|
+
const hint = defaultValue ? ` ${GRAY}(${defaultValue})${R}` : '';
|
|
65
|
+
process.stdout.write(`${CYAN}?${R} ${question}${hint} › `);
|
|
66
|
+
const ans = await nextLine();
|
|
67
|
+
return ans.trim() || defaultValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function askYesNo(question, defaultYes = true) {
|
|
71
|
+
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
72
|
+
process.stdout.write(`${CYAN}?${R} ${question} ${GRAY}(${hint})${R} › `);
|
|
73
|
+
const ans = await nextLine();
|
|
74
|
+
return ans.trim() ? ans.trim().toLowerCase().startsWith('y') : defaultYes;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function askChoice(question, choices, defaultIndex = 0) {
|
|
78
|
+
console.log(`${CYAN}?${R} ${question}`);
|
|
79
|
+
choices.forEach((c, i) => {
|
|
80
|
+
const marker = i === defaultIndex ? `${CYAN}›${R}` : ' ';
|
|
81
|
+
console.log(` ${marker} ${i + 1}) ${c}`);
|
|
82
|
+
});
|
|
83
|
+
process.stdout.write(` ${GRAY}Enter number (default ${defaultIndex + 1})${R} › `);
|
|
84
|
+
const ans = await nextLine();
|
|
85
|
+
const n = parseInt(ans.trim()) - 1;
|
|
86
|
+
return choices[n >= 0 && n < choices.length ? n : defaultIndex];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
90
|
+
async function main() {
|
|
91
|
+
console.log(`\n${BOLD}Create a new karaoke-cms project${R}\n`);
|
|
92
|
+
|
|
93
|
+
// Project directory (can be passed as first CLI arg)
|
|
94
|
+
let dir = process.argv[2]?.replace(/^-+/, '') || '';
|
|
95
|
+
if (!dir) dir = await ask('Project directory', 'my-cms');
|
|
96
|
+
|
|
97
|
+
const targetDir = resolve(process.cwd(), dir);
|
|
98
|
+
|
|
99
|
+
if (existsSync(targetDir)) {
|
|
100
|
+
console.error(`\n${RED}✗${R} Directory already exists: ${BOLD}${targetDir}${R}`);
|
|
101
|
+
rl.close();
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Derive a readable default title from the directory name
|
|
106
|
+
const defaultTitle = dir
|
|
107
|
+
.replace(/[-_]/g, ' ')
|
|
108
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
109
|
+
|
|
110
|
+
const title = await ask('Site title', defaultTitle);
|
|
111
|
+
const siteUrl = await ask('Site URL', 'https://my-site.pages.dev');
|
|
112
|
+
const description = await ask('Description', 'Our team knowledge base.');
|
|
113
|
+
const theme = await askChoice('Theme', ['default', 'minimal'], 0);
|
|
114
|
+
const search = await askYesNo('Enable search? (Pagefind)', true);
|
|
115
|
+
const commentsEnabled = await askYesNo('Enable comments? (requires Giscus setup)', false);
|
|
116
|
+
|
|
117
|
+
let comments = null;
|
|
118
|
+
if (commentsEnabled) {
|
|
119
|
+
console.log(`\n ${GRAY}Get these values from giscus.app after enabling Discussions on your repo.${R}\n`);
|
|
120
|
+
const repo = await ask('GitHub repo (owner/repo)');
|
|
121
|
+
const repoId = await ask('Repo ID');
|
|
122
|
+
const category = await ask('Discussion category', 'General');
|
|
123
|
+
const categoryId = await ask('Category ID');
|
|
124
|
+
comments = { repo, repoId, category, categoryId };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
rl.close();
|
|
128
|
+
|
|
129
|
+
// ── Scaffold ────────────────────────────────────────────────────────────────
|
|
130
|
+
console.log(`\n Scaffolding ${BOLD}${dir}${R}...\n`);
|
|
131
|
+
|
|
132
|
+
mkdirSync(join(targetDir, 'src'), { recursive: true });
|
|
133
|
+
mkdirSync(join(targetDir, 'public'), { recursive: true });
|
|
134
|
+
|
|
135
|
+
// Config and project files from templates
|
|
136
|
+
const files = {
|
|
137
|
+
'package.json': packageJson({ name: dir, astroVersion }),
|
|
138
|
+
'astro.config.mjs': astroConfig({ siteUrl }),
|
|
139
|
+
'karaoke.config.ts': karaokeConfig({ title, description, theme, search, comments }),
|
|
140
|
+
'src/content.config.ts': contentConfig(),
|
|
141
|
+
'src/env.d.ts': envDts(),
|
|
142
|
+
'tsconfig.json': tsConfig(),
|
|
143
|
+
'.gitignore': gitignore(),
|
|
144
|
+
'.env.default': envDefault(),
|
|
145
|
+
'public/_redirects': cloudflareRedirects(),
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
for (const [file, content] of Object.entries(files)) {
|
|
149
|
+
writeFileSync(join(targetDir, file), content);
|
|
150
|
+
console.log(` ${GREEN}+${R} ${file}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Vault content: copy the real Obsidian vault into vault/ subdirectory
|
|
154
|
+
const vaultSrc = fileURLToPath(new URL('../karaoke-create-vault', import.meta.url));
|
|
155
|
+
cpSync(vaultSrc, join(targetDir, 'vault'), { recursive: true });
|
|
156
|
+
console.log(` ${GREEN}+${R} vault/ (Obsidian vault)`);
|
|
157
|
+
|
|
158
|
+
// ── Git init ────────────────────────────────────────────────────────────────
|
|
159
|
+
const gitOk = (() => {
|
|
160
|
+
const run = (args) => spawnSync('git', args, { cwd: targetDir, encoding: 'utf8' }).status === 0;
|
|
161
|
+
return (
|
|
162
|
+
run(['init']) &&
|
|
163
|
+
run(['add', '-A']) &&
|
|
164
|
+
run(['-c', 'user.name=karaoke-cms', '-c', 'user.email=setup@karaoke-cms.org',
|
|
165
|
+
'commit', '-m', 'chore: initial karaoke-cms setup'])
|
|
166
|
+
);
|
|
167
|
+
})();
|
|
168
|
+
|
|
169
|
+
if (gitOk) {
|
|
170
|
+
console.log(` ${GREEN}+${R} git repository initialized`);
|
|
171
|
+
} else {
|
|
172
|
+
console.log(` ${GRAY} (git init skipped — install git and run it manually)${R}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Done ────────────────────────────────────────────────────────────────────
|
|
176
|
+
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`);
|
|
181
|
+
console.log(` ${GRAY}Open ${BOLD}${dir}/vault/${R}${GRAY} in Obsidian to write content.${R}\n`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main().catch(err => {
|
|
185
|
+
console.error(`\n${RED}✗${R} ${err.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|
package/src/templates.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure template functions for karaoke-cms project scaffolding.
|
|
3
|
+
* Each returns a file's content as a string.
|
|
4
|
+
* Exported separately so they're independently testable.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {{ name: string, astroVersion: string }} opts
|
|
9
|
+
*/
|
|
10
|
+
export function packageJson({ name, astroVersion }) {
|
|
11
|
+
return JSON.stringify({
|
|
12
|
+
name,
|
|
13
|
+
private: true,
|
|
14
|
+
type: 'module',
|
|
15
|
+
engines: { node: '>=22.12.0' },
|
|
16
|
+
scripts: {
|
|
17
|
+
dev: 'astro dev',
|
|
18
|
+
build: 'astro build',
|
|
19
|
+
preview: 'astro preview',
|
|
20
|
+
},
|
|
21
|
+
dependencies: {
|
|
22
|
+
'@karaoke-cms/astro': `^${astroVersion}`,
|
|
23
|
+
astro: '^6.0.0',
|
|
24
|
+
},
|
|
25
|
+
}, null, 2) + '\n';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {{ siteUrl: string }} opts
|
|
30
|
+
*/
|
|
31
|
+
export function astroConfig({ siteUrl }) {
|
|
32
|
+
return `// @ts-check
|
|
33
|
+
import { defineConfig } from 'astro/config';
|
|
34
|
+
import karaoke from '@karaoke-cms/astro';
|
|
35
|
+
|
|
36
|
+
let karaokeConfig = {};
|
|
37
|
+
try {
|
|
38
|
+
karaokeConfig = (await import('./karaoke.config.ts')).default;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err.code !== 'ERR_MODULE_NOT_FOUND') throw err;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
site: ${JSON.stringify(siteUrl)},
|
|
45
|
+
integrations: [karaoke(karaokeConfig)],
|
|
46
|
+
});
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {{
|
|
52
|
+
* title: string,
|
|
53
|
+
* description: string,
|
|
54
|
+
* theme: string,
|
|
55
|
+
* search: boolean,
|
|
56
|
+
* comments: { repo: string, repoId: string, category: string, categoryId: string } | null,
|
|
57
|
+
* }} opts
|
|
58
|
+
*/
|
|
59
|
+
export function karaokeConfig({ title, description, theme, search, comments }) {
|
|
60
|
+
const modules = {};
|
|
61
|
+
if (search) modules.search = { enabled: true };
|
|
62
|
+
if (comments) modules.comments = { enabled: true, ...comments };
|
|
63
|
+
|
|
64
|
+
const modulesStr = Object.keys(modules).length > 0
|
|
65
|
+
? `\n modules: ${JSON.stringify(modules, null, 2).replace(/\n/g, '\n ')},`
|
|
66
|
+
: '';
|
|
67
|
+
|
|
68
|
+
return `import type { KaraokeConfig } from '@karaoke-cms/astro';
|
|
69
|
+
import { loadEnv } from '@karaoke-cms/astro/env';
|
|
70
|
+
|
|
71
|
+
const env = loadEnv(new URL('.', import.meta.url));
|
|
72
|
+
|
|
73
|
+
const config: KaraokeConfig = {
|
|
74
|
+
vault: env.KARAOKE_VAULT,
|
|
75
|
+
title: ${JSON.stringify(title)},
|
|
76
|
+
description: ${JSON.stringify(description)},
|
|
77
|
+
theme: ${JSON.stringify(theme)},${modulesStr}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default config;
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function contentConfig() {
|
|
85
|
+
return `import { makeCollections } from '@karaoke-cms/astro/collections';
|
|
86
|
+
import { loadEnv } from '@karaoke-cms/astro/env';
|
|
87
|
+
|
|
88
|
+
const env = loadEnv(new URL('..', import.meta.url));
|
|
89
|
+
|
|
90
|
+
export const collections = makeCollections(new URL('..', import.meta.url), env.KARAOKE_VAULT);
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function envDts() {
|
|
95
|
+
return `/// <reference types="astro/client" />
|
|
96
|
+
/// <reference types="@karaoke-cms/astro/client" />
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function tsConfig() {
|
|
101
|
+
return JSON.stringify(
|
|
102
|
+
{ extends: 'astro/tsconfigs/strict', include: ['.astro/types.d.ts', '**/*'], exclude: ['dist'] },
|
|
103
|
+
null, 2
|
|
104
|
+
) + '\n';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function gitignore() {
|
|
108
|
+
return `.DS_Store
|
|
109
|
+
node_modules/
|
|
110
|
+
dist/
|
|
111
|
+
.astro/
|
|
112
|
+
.env
|
|
113
|
+
.env.local
|
|
114
|
+
.env.*.local
|
|
115
|
+
# Obsidian workspace state (personal, not shared)
|
|
116
|
+
.obsidian/workspace.json
|
|
117
|
+
.obsidian/workspace-mobile.json
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function envDefault() {
|
|
122
|
+
return `# Obsidian vault location — open this folder in Obsidian to write content.
|
|
123
|
+
# Override in .env (gitignored) to point to a vault elsewhere on your machine.
|
|
124
|
+
# Absolute path: KARAOKE_VAULT=/Users/you/Obsidian/my-vault
|
|
125
|
+
# Relative path: KARAOKE_VAULT=../my-obsidian-vault
|
|
126
|
+
KARAOKE_VAULT=./vault/
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function cloudflareRedirects() {
|
|
131
|
+
return `/* /404.html 404\n`;
|
|
132
|
+
}
|
|
133
|
+
|