create-dox 0.1.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 (3) hide show
  1. package/README.md +79 -0
  2. package/dist/index.js +350 -0
  3. package/package.json +30 -0
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # create-dox
2
+
3
+ Scaffold a new [Dox](https://github.com/kenny-io/Dox) documentation project in seconds.
4
+
5
+ ## Usage
6
+
7
+ ### With npx (recommended)
8
+
9
+ ```bash
10
+ npx create-dox my-docs
11
+ ```
12
+
13
+ ### Or install globally
14
+
15
+ ```bash
16
+ npm install -g create-dox
17
+ create-dox my-docs
18
+ ```
19
+
20
+ ### Interactive prompts
21
+
22
+ If you run without arguments, the CLI will ask you:
23
+
24
+ ```
25
+ $ npx create-dox
26
+
27
+ ╔══════════════════════════════════════╗
28
+ ║ ██████╗ ██████╗ ██╗ ██╗ ║
29
+ ║ ██╔══██╗██╔═══██╗╚██╗██╔╝ ║
30
+ ║ ██║ ██║██║ ██║ ╚███╔╝ ║
31
+ ║ ██║ ██║██║ ██║ ██╔██╗ ║
32
+ ║ ██████╔╝╚██████╔╝██╔╝ ██╗ ║
33
+ ║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ║
34
+ ║ ║
35
+ ║ Beautiful docs, zero lock-in. ║
36
+ ╚══════════════════════════════════════╝
37
+
38
+ Project directory (my-docs): acme-docs
39
+ Project name (Acme Docs):
40
+ Description (Documentation for Acme Docs.):
41
+ Brand preset:
42
+ 1) primary
43
+ 2) secondary
44
+ > Choose [1]: 1
45
+ GitHub repo URL (optional): https://github.com/acme/docs
46
+ Install dependencies? (Y/n): Y
47
+ ```
48
+
49
+ ## What it does
50
+
51
+ 1. Clones the Dox template from GitHub
52
+ 2. Replaces example content with starter pages customized to your project name
53
+ 3. Updates `src/data/site.ts` with your name, description, branding, and repo URL
54
+ 4. Writes a minimal `docs.json` navigation config
55
+ 5. Installs dependencies
56
+ 6. Initializes a fresh git repo
57
+
58
+ ## After scaffolding
59
+
60
+ ```bash
61
+ cd my-docs
62
+ npm run dev
63
+ ```
64
+
65
+ Open [http://localhost:3040](http://localhost:3040) to see your docs.
66
+
67
+ ### Key files
68
+
69
+ | File | Purpose |
70
+ | --- | --- |
71
+ | `src/data/site.ts` | Site name, links, brand colors |
72
+ | `docs.json` | Navigation tabs, groups, pages |
73
+ | `src/content/*.mdx` | Your documentation pages |
74
+ | `openapi.yaml` | API spec (optional) |
75
+
76
+ ## Requirements
77
+
78
+ - Node.js >= 18
79
+ - Git
package/dist/index.js ADDED
@@ -0,0 +1,350 @@
1
+ #!/usr/bin/env node
2
+
3
+ // create-dox — Scaffold a new Dox documentation project.
4
+ // Zero dependencies. Requires Node >= 18.
5
+
6
+ import { createInterface } from 'node:readline'
7
+ import { execSync } from 'node:child_process'
8
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, cpSync } from 'node:fs'
9
+ import { resolve, join, basename } from 'node:path'
10
+
11
+ // ── Constants ────────────────────────────────────────────────────────────────
12
+
13
+ const REPO_URL = 'https://github.com/kenny-io/Dox.git'
14
+ const BRAND_PRESETS = ['primary', 'secondary']
15
+ const args = process.argv.slice(2)
16
+ const flags = args.filter((a) => a.startsWith('-'))
17
+ const positional = args.filter((a) => !a.startsWith('-'))
18
+ const useDefaults = flags.includes('--yes') || flags.includes('-y')
19
+
20
+ const STARTER_PAGES = {
21
+ 'introduction.mdx': `---
22
+ title: Introduction
23
+ description: Welcome to {NAME} documentation.
24
+ ---
25
+
26
+ ## Welcome
27
+
28
+ This is the home page of your **{NAME}** documentation site, powered by [Dox](https://github.com/kenny-io/Dox).
29
+
30
+ Get started by editing this file at \`src/content/introduction.mdx\`.
31
+ `,
32
+ 'quickstart.mdx': `---
33
+ title: Quickstart
34
+ description: Get up and running with {NAME} in under 5 minutes.
35
+ ---
36
+
37
+ ## Installation
38
+
39
+ \`\`\`bash
40
+ npm install {SLUG}
41
+ \`\`\`
42
+
43
+ ## Basic usage
44
+
45
+ \`\`\`ts
46
+ import { create } from '{SLUG}'
47
+
48
+ const client = create({ apiKey: 'your-api-key' })
49
+ \`\`\`
50
+
51
+ That's it — you're ready to go!
52
+ `,
53
+ }
54
+
55
+ const STARTER_DOCS_JSON = `{
56
+ "tabs": [
57
+ {
58
+ "tab": "Overview",
59
+ "groups": [
60
+ {
61
+ "group": "Getting Started",
62
+ "pages": ["introduction", "quickstart"]
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "tab": "Changelog",
68
+ "href": "/changelog"
69
+ }
70
+ ]
71
+ }
72
+ `
73
+
74
+ // ── Helpers ──────────────────────────────────────────────────────────────────
75
+
76
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
77
+
78
+ function ask(question, fallback) {
79
+ return new Promise((resolve) => {
80
+ const suffix = fallback ? ` (${fallback})` : ''
81
+ rl.question(`${question}${suffix}: `, (answer) => {
82
+ resolve(answer.trim() || fallback || '')
83
+ })
84
+ })
85
+ }
86
+
87
+ function choose(question, options, fallback) {
88
+ return new Promise((resolve) => {
89
+ const optionList = options.map((o, i) => ` ${i + 1}) ${o}`).join('\n')
90
+ const defaultIndex = options.indexOf(fallback) + 1
91
+ const suffix = defaultIndex ? ` [${defaultIndex}]` : ''
92
+ rl.question(`${question}\n${optionList}\n> Choose${suffix}: `, (answer) => {
93
+ const num = parseInt(answer.trim(), 10)
94
+ if (num >= 1 && num <= options.length) {
95
+ resolve(options[num - 1])
96
+ } else {
97
+ resolve(fallback || options[0])
98
+ }
99
+ })
100
+ })
101
+ }
102
+
103
+ function slugify(name) {
104
+ return name
105
+ .toLowerCase()
106
+ .replace(/[^a-z0-9]+/g, '-')
107
+ .replace(/(^-|-$)/g, '')
108
+ }
109
+
110
+ function run(cmd, cwd) {
111
+ execSync(cmd, { cwd, stdio: 'inherit' })
112
+ }
113
+
114
+ function runSilent(cmd, cwd) {
115
+ return execSync(cmd, { cwd, encoding: 'utf8' }).trim()
116
+ }
117
+
118
+ function logo() {
119
+ console.log('')
120
+ console.log(' ╔══════════════════════════════════════╗')
121
+ console.log(' ║ ║')
122
+ console.log(' ║ ██████╗ ██████╗ ██╗ ██╗ ║')
123
+ console.log(' ║ ██╔══██╗██╔═══██╗╚██╗██╔╝ ║')
124
+ console.log(' ║ ██║ ██║██║ ██║ ╚███╔╝ ║')
125
+ console.log(' ║ ██║ ██║██║ ██║ ██╔██╗ ║')
126
+ console.log(' ║ ██████╔╝╚██████╔╝██╔╝ ██╗ ║')
127
+ console.log(' ║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ║')
128
+ console.log(' ║ ║')
129
+ console.log(' ║ Beautiful docs, zero lock-in. ║')
130
+ console.log(' ║ ║')
131
+ console.log(' ╚══════════════════════════════════════╝')
132
+ console.log('')
133
+ }
134
+
135
+ function success(projectDir, projectName) {
136
+ console.log('')
137
+ console.log(' ✅ Your Dox project is ready!')
138
+ console.log('')
139
+ console.log(` 📂 ${projectDir}`)
140
+ console.log('')
141
+ console.log(' Next steps:')
142
+ console.log('')
143
+ console.log(` cd ${basename(projectDir)}`)
144
+ console.log(' npm run dev')
145
+ console.log('')
146
+ console.log(` Then open http://localhost:3040 to see your ${projectName} docs.`)
147
+ console.log('')
148
+ console.log(' 📝 Key files to edit:')
149
+ console.log(' • src/data/site.ts — name, links, branding')
150
+ console.log(' • docs.json — navigation structure')
151
+ console.log(' • src/content/*.mdx — your documentation')
152
+ console.log(' • openapi.yaml — API spec (optional)')
153
+ console.log('')
154
+ console.log(' Happy documenting! 🚀')
155
+ console.log('')
156
+ }
157
+
158
+ // ── Scaffold logic ───────────────────────────────────────────────────────────
159
+
160
+ function cloneTemplate(targetDir) {
161
+ console.log('')
162
+ console.log(' ⏳ Cloning Dox template...')
163
+ run(`git clone --depth 1 ${REPO_URL} "${targetDir}"`)
164
+
165
+ // Remove the template's .git so the user starts fresh
166
+ const gitDir = join(targetDir, '.git')
167
+ if (existsSync(gitDir)) {
168
+ execSync(`rm -rf "${gitDir}"`)
169
+ }
170
+
171
+ // Remove the CLI folder from the cloned project (they don't need it)
172
+ const cliDir = join(targetDir, 'cli')
173
+ if (existsSync(cliDir)) {
174
+ execSync(`rm -rf "${cliDir}"`)
175
+ }
176
+ }
177
+
178
+ function writeStarterContent(targetDir, projectName, slug) {
179
+ const contentDir = join(targetDir, 'src', 'content')
180
+
181
+ // Clear existing example content
182
+ if (existsSync(contentDir)) {
183
+ const entries = readdirSync(contentDir)
184
+ for (const entry of entries) {
185
+ const fullPath = join(contentDir, entry)
186
+ execSync(`rm -rf "${fullPath}"`)
187
+ }
188
+ } else {
189
+ mkdirSync(contentDir, { recursive: true })
190
+ }
191
+
192
+ // Write starter pages
193
+ for (const [filename, template] of Object.entries(STARTER_PAGES)) {
194
+ const content = template
195
+ .replace(/\{NAME\}/g, projectName)
196
+ .replace(/\{SLUG\}/g, slug)
197
+ writeFileSync(join(contentDir, filename), content, 'utf8')
198
+ }
199
+
200
+ // Write docs.json
201
+ writeFileSync(join(targetDir, 'docs.json'), STARTER_DOCS_JSON, 'utf8')
202
+ }
203
+
204
+ function updateSiteConfig(targetDir, projectName, description, brandPreset, repoUrl) {
205
+ const siteFile = join(targetDir, 'src', 'data', 'site.ts')
206
+ if (!existsSync(siteFile)) {
207
+ console.log(' ⚠️ Could not find src/data/site.ts — skipping config update.')
208
+ return
209
+ }
210
+
211
+ let source = readFileSync(siteFile, 'utf8')
212
+
213
+ // Replace name
214
+ source = source.replace(
215
+ /name:\s*'[^']*'/,
216
+ `name: '${projectName.replace(/'/g, "\\'")}'`,
217
+ )
218
+
219
+ // Replace description (handles multiline template string)
220
+ source = source.replace(
221
+ /description:[\s\S]*?'([^']*)'/,
222
+ `description:\n '${description.replace(/'/g, "\\'")}'`,
223
+ )
224
+
225
+ // Replace brand preset
226
+ source = source.replace(
227
+ /const brandPreset:\s*BrandPresetKey\s*=\s*'[^']*'/,
228
+ `const brandPreset: BrandPresetKey = '${brandPreset}'`,
229
+ )
230
+
231
+ // Replace repo URL
232
+ if (repoUrl) {
233
+ source = source.replace(
234
+ /repoUrl:\s*'[^']*'/,
235
+ `repoUrl: '${repoUrl}'`,
236
+ )
237
+ // Also update GitHub link
238
+ source = source.replace(
239
+ /\{\s*label:\s*'GitHub',\s*href:\s*'[^']*'\s*\}/,
240
+ `{ label: 'GitHub', href: '${repoUrl}' }`,
241
+ )
242
+ // Update support link
243
+ source = source.replace(
244
+ /\{\s*label:\s*'Support',\s*href:\s*'[^']*'\s*\}/,
245
+ `{ label: 'Support', href: '${repoUrl}/issues/new' }`,
246
+ )
247
+ }
248
+
249
+ writeFileSync(siteFile, source, 'utf8')
250
+ }
251
+
252
+ function updateEnvExample(targetDir) {
253
+ const envFile = join(targetDir, '.env.example')
254
+ if (existsSync(envFile)) {
255
+ // Copy .env.example to .env.local for immediate use
256
+ const envLocal = join(targetDir, '.env.local')
257
+ if (!existsSync(envLocal)) {
258
+ cpSync(envFile, envLocal)
259
+ }
260
+ }
261
+ }
262
+
263
+ function initGit(targetDir) {
264
+ try {
265
+ run('git init', targetDir)
266
+ run('git add -A', targetDir)
267
+ run('git commit -m "Initial commit from create-dox"', targetDir)
268
+ } catch {
269
+ // Git might not be configured — that's fine
270
+ console.log(' ⚠️ Could not initialize git (you can do this manually).')
271
+ }
272
+ }
273
+
274
+ function installDeps(targetDir) {
275
+ console.log('')
276
+ console.log(' 📦 Installing dependencies...')
277
+ console.log('')
278
+ run('npm install', targetDir)
279
+ }
280
+
281
+ // ── Main ─────────────────────────────────────────────────────────────────────
282
+
283
+ async function main() {
284
+ logo()
285
+
286
+ // 1. Project directory
287
+ const dirArg = positional[0]
288
+ let projectDir
289
+ if (dirArg) {
290
+ projectDir = resolve(dirArg)
291
+ } else if (useDefaults) {
292
+ projectDir = resolve('my-docs')
293
+ } else {
294
+ const dirName = await ask(' Project directory', 'my-docs')
295
+ projectDir = resolve(dirName)
296
+ }
297
+
298
+ if (existsSync(projectDir) && readdirSync(projectDir).length > 0) {
299
+ console.log(`\n ❌ Directory "${projectDir}" already exists and is not empty.`)
300
+ rl.close()
301
+ process.exit(1)
302
+ }
303
+
304
+ // 2. Project name
305
+ const defaultName = basename(projectDir)
306
+ .replace(/[-_]/g, ' ')
307
+ .replace(/\b\w/g, (c) => c.toUpperCase())
308
+ const projectName = useDefaults ? defaultName : await ask(' Project name', defaultName)
309
+
310
+ // 3. Description
311
+ const defaultDesc = `Documentation for ${projectName}.`
312
+ const description = useDefaults ? defaultDesc : await ask(' Description', defaultDesc)
313
+
314
+ // 4. Brand preset
315
+ const brandPreset = useDefaults ? 'primary' : await choose('\n Brand preset:', BRAND_PRESETS, 'primary')
316
+
317
+ // 5. GitHub repo (optional)
318
+ const repoUrl = useDefaults ? '' : await ask(' GitHub repo URL (optional)', '')
319
+
320
+ // 6. Install deps?
321
+ let doInstall = true
322
+ if (!useDefaults) {
323
+ const shouldInstall = await ask(' Install dependencies? (Y/n)', 'Y')
324
+ doInstall = shouldInstall.toLowerCase() !== 'n'
325
+ }
326
+
327
+ const slug = slugify(projectName)
328
+
329
+ // ── Execute ──────────────────────────────────────────────────────────────
330
+
331
+ cloneTemplate(projectDir)
332
+ writeStarterContent(projectDir, projectName, slug)
333
+ updateSiteConfig(projectDir, projectName, description, brandPreset, repoUrl)
334
+ updateEnvExample(projectDir)
335
+
336
+ if (doInstall) {
337
+ installDeps(projectDir)
338
+ }
339
+
340
+ initGit(projectDir)
341
+ success(projectDir, projectName)
342
+
343
+ rl.close()
344
+ }
345
+
346
+ main().catch((err) => {
347
+ console.error('\n ❌ Error:', err.message)
348
+ rl.close()
349
+ process.exit(1)
350
+ })
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "create-dox",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a new Dox documentation project.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-dox": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "node build.mjs",
15
+ "dev": "node src/index.js"
16
+ },
17
+ "dependencies": {},
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "keywords": [
22
+ "dox",
23
+ "docs",
24
+ "documentation",
25
+ "cli",
26
+ "scaffold",
27
+ "template",
28
+ "mintlify"
29
+ ]
30
+ }