@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 +76 -0
- package/karaoke-create-vault/.obsidian/workspace.json +227 -0
- package/karaoke-create-vault/karaoke-cms/config/index.md +14 -0
- package/karaoke-create-vault/karaoke-cms/config/menus.yaml +58 -0
- package/karaoke-create-vault/karaoke-cms/templates/index.md +15 -0
- package/package.json +2 -2
- package/src/index.js +95 -11
- package/src/templates.js +28 -5
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.
|
|
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
|
|
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
|
-
// ──
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
44
|
+
dependencies,
|
|
45
|
+
devDependencies: {
|
|
46
|
+
wrangler: '^3',
|
|
24
47
|
},
|
|
25
48
|
}, null, 2) + '\n';
|
|
26
49
|
}
|