@abstractdata/create-docs 0.2.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.
Files changed (33) hide show
  1. package/README.md +36 -0
  2. package/bin/.fuse_hidden0000000a00000002 +194 -0
  3. package/bin/.fuse_hidden0000000d00000003 +194 -0
  4. package/bin/cli.js +194 -0
  5. package/package.json +38 -0
  6. package/template/.claude/skills/abstract-data-docs-author/SKILL.md +305 -0
  7. package/template/.claude/skills/abstract-data-setup/SKILL.md +555 -0
  8. package/template/.cursor/rules/abstract-data-docs-author.mdc +311 -0
  9. package/template/.cursor/rules/abstract-data-setup.mdc +561 -0
  10. package/template/.cursor/rules/welcome.mdc +29 -0
  11. package/template/.fuse_hidden0000001e00000008 +75 -0
  12. package/template/.fuse_hidden0000002100000009 +46 -0
  13. package/template/.fuse_hidden000000210000000a +75 -0
  14. package/template/.fuse_hidden000000240000000b +46 -0
  15. package/template/.github/copilot-instructions.md +893 -0
  16. package/template/.github/workflows/deploy-cloudflare.yml +50 -0
  17. package/template/.github/workflows/deploy-vercel.yml +47 -0
  18. package/template/.github/workflows/deploy.yml +55 -0
  19. package/template/CLAUDE.md +46 -0
  20. package/template/README.md +75 -0
  21. package/template/astro.config.mjs +69 -0
  22. package/template/package.json +25 -0
  23. package/template/public/favicon.svg +26 -0
  24. package/template/scripts/build-python-docs.mjs +385 -0
  25. package/template/scripts/build-ts-docs.mjs +349 -0
  26. package/template/scripts/python-autodoc.json +10 -0
  27. package/template/scripts/ts-autodoc.json +10 -0
  28. package/template/src/assets/README.md +1 -0
  29. package/template/src/content/docs/index.mdx +81 -0
  30. package/template/src/content/docs/quickstart.md +162 -0
  31. package/template/src/content.config.ts +46 -0
  32. package/template/src/env.d.ts +2 -0
  33. package/template/tsconfig.json +5 -0
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @abstractdata/create-docs
2
+
3
+ One-command scaffolder for new Abstract Data documentation sites.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ bun create @abstractdata/docs my-docs
9
+ ```
10
+
11
+ (Or `npm create @abstractdata/docs@latest my-docs`, or `pnpm create @abstractdata/docs my-docs`.)
12
+
13
+ That creates a `./my-docs/` folder with a fresh Starlight project pre-wired to `@abstractdata/starlight-theme`. Then:
14
+
15
+ ```bash
16
+ cd my-docs
17
+ bun install
18
+ bun dev
19
+ ```
20
+
21
+ ## What it does
22
+
23
+ 1. Copies the `@abstractdata/docs-template` files into a folder you name.
24
+ 2. Sets the project name in `package.json` and `astro.config.mjs`.
25
+ 3. Replaces the workspace dependency on `@abstractdata/starlight-theme` with a real published version range.
26
+ 4. Runs `git init` and makes an initial commit so you have a clean history from step zero.
27
+
28
+ After that, edit `astro.config.mjs` to replace the placeholder `site`, `social`, `sidebar`, and (optionally) `logo`, then start writing content under `src/content/docs/`.
29
+
30
+ ## Options
31
+
32
+ For now, `bun create @abstractdata/docs <name>` is the only invocation. Configuration (motion mode, credit, version chip) is set inside the generated `astro.config.mjs` after scaffolding — easier to discover than CLI flags.
33
+
34
+ ## Maintenance note
35
+
36
+ The `template/` folder ships a copy of `packages/template/` from the monorepo. The copy is created automatically by the `prepack` script before publish — do not hand-edit it. Edit `packages/template/` and the copy regenerates on the next publish.
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * @abstractdata/create-docs
5
+ *
6
+ * Scaffold a new branded Starlight documentation project from the
7
+ * @abstractdata/docs template. Invoked as:
8
+ *
9
+ * bun create @abstractdata/docs <project-name>
10
+ * npm create @abstractdata/docs@latest <project-name>
11
+ * pnpm create @abstractdata/docs <project-name>
12
+ */
13
+ import { existsSync, mkdirSync, readdirSync, copyFileSync, readFileSync, writeFileSync, statSync } from 'node:fs';
14
+ import { join, resolve, dirname, basename } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { execSync } from 'node:child_process';
17
+ import { stdin, stdout, exit, argv, cwd } from 'node:process';
18
+ import { createInterface } from 'node:readline/promises';
19
+
20
+ // ─── Theme version this CLI scaffolds against ─────────────────────────
21
+ // Auto-updated by `scripts/sync-theme-version.mjs` during the prepack
22
+ // step — DO NOT hand-edit. Reflects the version in
23
+ // `packages/starlight-theme/package.json` at publish time.
24
+ const THEME_VERSION = '^0.4.0';
25
+
26
+ // ─── Paths ────────────────────────────────────────────────────────────
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+ const TEMPLATE_CANDIDATES = [
29
+ resolve(__dirname, '..', 'template'), // bundled (after prepack)
30
+ resolve(__dirname, '..', '..', 'template'), // workspace dev
31
+ ];
32
+ const TEMPLATE_DIR = TEMPLATE_CANDIDATES.find(existsSync);
33
+
34
+ // ─── ANSI colors ──────────────────────────────────────────────────────
35
+ const c = {
36
+ reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
37
+ cyan: '\x1b[36m', gold: '\x1b[33m', red: '\x1b[31m', green: '\x1b[32m',
38
+ };
39
+
40
+ const log = (...args) => console.log(...args);
41
+ const die = (msg) => { console.error(`${c.red}error${c.reset} ${msg}`); exit(1); };
42
+
43
+ // ─── Banner ───────────────────────────────────────────────────────────
44
+ log('');
45
+ log(`${c.cyan}${c.bold}┌─[ ABSTRACT DATA · DOCS ]─────────────────┐${c.reset}`);
46
+ log(`${c.cyan}│${c.reset} ${c.dim}Scaffolding a branded Starlight site${c.reset} ${c.cyan}│${c.reset}`);
47
+ log(`${c.cyan}└──────────────────────────────────────────┘${c.reset}`);
48
+ log('');
49
+
50
+ if (!TEMPLATE_DIR) {
51
+ die('Could not locate template directory. This usually means @abstractdata/create-docs was packaged incorrectly. Reinstall the package or file an issue.');
52
+ }
53
+
54
+ // ─── Resolve project name ─────────────────────────────────────────────
55
+ const args = argv.slice(2);
56
+ let projectName = args.find((a) => !a.startsWith('-'));
57
+
58
+ if (!projectName) {
59
+ const rl = createInterface({ input: stdin, output: stdout });
60
+ projectName = (await rl.question(`${c.cyan}?${c.reset} Project name (and folder): `)).trim();
61
+ rl.close();
62
+ }
63
+
64
+ if (!projectName) die('Project name is required.');
65
+ if (!/^[a-z0-9][a-z0-9-_.]*$/i.test(projectName)) {
66
+ die(`"${projectName}" is not a valid folder/package name. Use letters, numbers, hyphens, underscores, dots — start with a letter or digit.`);
67
+ }
68
+
69
+ const targetDir = resolve(cwd(), projectName);
70
+ if (existsSync(targetDir)) {
71
+ const entries = readdirSync(targetDir);
72
+ if (entries.length > 0) die(`Folder ${c.bold}${projectName}${c.reset} already exists and is not empty.`);
73
+ }
74
+
75
+ // ─── Copy template ────────────────────────────────────────────────────
76
+ log(`${c.dim}→ copying template into${c.reset} ${c.bold}${projectName}${c.reset}/`);
77
+
78
+ function copyRecursive(src, dst) {
79
+ mkdirSync(dst, { recursive: true });
80
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
81
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.astro' || entry.name === 'bun.lock') continue;
82
+ const srcPath = join(src, entry.name);
83
+ const dstPath = join(dst, entry.name);
84
+ if (entry.isDirectory()) copyRecursive(srcPath, dstPath);
85
+ else copyFileSync(srcPath, dstPath);
86
+ }
87
+ }
88
+ copyRecursive(TEMPLATE_DIR, targetDir);
89
+
90
+ // ─── Patch package.json ───────────────────────────────────────────────
91
+ log(`${c.dim}→ wiring up${c.reset} package.json`);
92
+ const pkgPath = join(targetDir, 'package.json');
93
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
94
+
95
+ pkg.name = projectName;
96
+ pkg.version = '0.0.1';
97
+ pkg.private = true;
98
+ delete pkg.description; // user fills theirs
99
+
100
+ // Replace workspace:* with the published theme version range
101
+ if (pkg.dependencies?.['@abstractdata/starlight-theme']?.startsWith('workspace:')) {
102
+ pkg.dependencies['@abstractdata/starlight-theme'] = THEME_VERSION;
103
+ }
104
+
105
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
106
+
107
+ // ─── Write .gitignore ─────────────────────────────────────────────────
108
+ // npm strips `.gitignore` from published tarballs, so we ship the contents
109
+ // inline here. If the user (or template) committed one already after copy,
110
+ // don't overwrite — they may have customized it.
111
+ const gitignorePath = join(targetDir, '.gitignore');
112
+ if (!existsSync(gitignorePath)) {
113
+ log(`${c.dim}→ writing${c.reset} .gitignore`);
114
+ writeFileSync(
115
+ gitignorePath,
116
+ [
117
+ '# build output',
118
+ 'dist/',
119
+ '.astro/',
120
+ '.output/',
121
+ '',
122
+ '# dependencies',
123
+ 'node_modules/',
124
+ '',
125
+ '# bun — bun.lock IS committed (text-based lockfile, needed for reproducible installs)',
126
+ '.bun/',
127
+ '',
128
+ '# logs',
129
+ '*.log',
130
+ 'npm-debug.log*',
131
+ '',
132
+ '# env',
133
+ '.env',
134
+ '.env.local',
135
+ '',
136
+ '# os',
137
+ '.DS_Store',
138
+ 'Thumbs.db',
139
+ '',
140
+ ].join('\n'),
141
+ );
142
+ }
143
+
144
+ // ─── Patch astro.config.mjs ───────────────────────────────────────────
145
+ const astroConfigPath = join(targetDir, 'astro.config.mjs');
146
+ if (existsSync(astroConfigPath)) {
147
+ let cfg = readFileSync(astroConfigPath, 'utf8');
148
+ // Replace the placeholder title
149
+ cfg = cfg.replace(/title:\s*['"]Your Project Docs['"]/, `title: '${projectName}'`);
150
+ writeFileSync(astroConfigPath, cfg);
151
+ }
152
+
153
+ // ─── git init ─────────────────────────────────────────────────────────
154
+ log(`${c.dim}→ initializing git${c.reset}`);
155
+ try {
156
+ execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
157
+ execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
158
+ execSync('git -c user.name=create-docs -c user.email=cli@abstractdata.io commit -q -m "chore: initial scaffold from @abstractdata/create-docs"', {
159
+ cwd: targetDir,
160
+ stdio: 'ignore',
161
+ });
162
+ } catch (err) {
163
+ // Non-fatal — user can `git init` manually.
164
+ const isNotFound =
165
+ err?.code === 'ENOENT' ||
166
+ /not found|no such file|command not found/i.test(err?.message ?? '');
167
+ if (isNotFound) {
168
+ log(`${c.dim} (git not found in PATH — run ${c.reset}${c.bold}git init${c.reset}${c.dim} inside ${c.reset}${c.bold}${projectName}/${c.reset}${c.dim} to start version-tracking your project)${c.reset}`);
169
+ } else {
170
+ log(`${c.dim} (git skipped: ${(err?.message ?? 'unknown error').split('\n')[0]} — run git init manually)${c.reset}`);
171
+ }
172
+ }
173
+
174
+ // ─── Done ─────────────────────────────────────────────────────────────
175
+ log('');
176
+ log(`${c.green}✓${c.reset} ${c.bold}${projectName}${c.reset} scaffolded.`);
177
+ log('');
178
+ log(`${c.gold}Next steps:${c.reset}`);
179
+ log(` ${c.dim}$${c.reset} cd ${projectName}`);
180
+ log(` ${c.dim}$${c.reset} bun install`);
181
+ log(` ${c.dim}$${c.reset} bun dev`);
182
+ log('');
183
+ log(`${c.cyan}Tip:${c.reset} ${c.dim}open your AI coding assistant in ${projectName}/ — it will${c.reset}`);
184
+ log(` ${c.gold}offer to run the abstract-data-setup workflow automatically${c.reset}`);
185
+ log(` ${c.dim}on the first message. No magic phrase needed; just open${c.reset}`);
186
+ log(` ${c.dim}Claude Code, Cursor, or whatever you use, and the AI will${c.reset}`);
187
+ log(` ${c.dim}greet you and ask.${c.reset}`);
188
+ log('');
189
+ log(` ${c.dim}Installed for: Claude Code (CLAUDE.md + .claude/skills/),${c.reset}`);
190
+ log(` ${c.dim}Cursor (.cursor/rules/), GitHub Copilot (.github/${c.reset}`);
191
+ log(` ${c.dim}copilot-instructions.md). Delete what you don't use.${c.reset}`);
192
+ log('');
193
+ log(`${c.dim}Docs · https://github.com/Abstract-Data/abstract-data-doc-theme${c.reset}`);
194
+ log('');
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * @abstract-data/create-docs
5
+ *
6
+ * Scaffold a new branded Starlight documentation project from the
7
+ * @abstractdata/docs template. Invoked as:
8
+ *
9
+ * bun create @abstract-data/docs <project-name>
10
+ * npm create @abstract-data/docs@latest <project-name>
11
+ * pnpm create @abstract-data/docs <project-name>
12
+ */
13
+ import { existsSync, mkdirSync, readdirSync, copyFileSync, readFileSync, writeFileSync, statSync } from 'node:fs';
14
+ import { join, resolve, dirname, basename } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { execSync } from 'node:child_process';
17
+ import { stdin, stdout, exit, argv, cwd } from 'node:process';
18
+ import { createInterface } from 'node:readline/promises';
19
+
20
+ // ─── Theme version this CLI scaffolds against ─────────────────────────
21
+ // Auto-updated by `scripts/sync-theme-version.mjs` during the prepack
22
+ // step — DO NOT hand-edit. Reflects the version in
23
+ // `packages/starlight-theme/package.json` at publish time.
24
+ const THEME_VERSION = '^0.4.0';
25
+
26
+ // ─── Paths ────────────────────────────────────────────────────────────
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+ const TEMPLATE_CANDIDATES = [
29
+ resolve(__dirname, '..', 'template'), // bundled (after prepack)
30
+ resolve(__dirname, '..', '..', 'template'), // workspace dev
31
+ ];
32
+ const TEMPLATE_DIR = TEMPLATE_CANDIDATES.find(existsSync);
33
+
34
+ // ─── ANSI colors ──────────────────────────────────────────────────────
35
+ const c = {
36
+ reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
37
+ cyan: '\x1b[36m', gold: '\x1b[33m', red: '\x1b[31m', green: '\x1b[32m',
38
+ };
39
+
40
+ const log = (...args) => console.log(...args);
41
+ const die = (msg) => { console.error(`${c.red}error${c.reset} ${msg}`); exit(1); };
42
+
43
+ // ─── Banner ───────────────────────────────────────────────────────────
44
+ log('');
45
+ log(`${c.cyan}${c.bold}┌─[ ABSTRACT DATA · DOCS ]─────────────────┐${c.reset}`);
46
+ log(`${c.cyan}│${c.reset} ${c.dim}Scaffolding a branded Starlight site${c.reset} ${c.cyan}│${c.reset}`);
47
+ log(`${c.cyan}└──────────────────────────────────────────┘${c.reset}`);
48
+ log('');
49
+
50
+ if (!TEMPLATE_DIR) {
51
+ die('Could not locate template directory. This usually means @abstract-data/create-docs was packaged incorrectly. Reinstall the package or file an issue.');
52
+ }
53
+
54
+ // ─── Resolve project name ─────────────────────────────────────────────
55
+ const args = argv.slice(2);
56
+ let projectName = args.find((a) => !a.startsWith('-'));
57
+
58
+ if (!projectName) {
59
+ const rl = createInterface({ input: stdin, output: stdout });
60
+ projectName = (await rl.question(`${c.cyan}?${c.reset} Project name (and folder): `)).trim();
61
+ rl.close();
62
+ }
63
+
64
+ if (!projectName) die('Project name is required.');
65
+ if (!/^[a-z0-9][a-z0-9-_.]*$/i.test(projectName)) {
66
+ die(`"${projectName}" is not a valid folder/package name. Use letters, numbers, hyphens, underscores, dots — start with a letter or digit.`);
67
+ }
68
+
69
+ const targetDir = resolve(cwd(), projectName);
70
+ if (existsSync(targetDir)) {
71
+ const entries = readdirSync(targetDir);
72
+ if (entries.length > 0) die(`Folder ${c.bold}${projectName}${c.reset} already exists and is not empty.`);
73
+ }
74
+
75
+ // ─── Copy template ────────────────────────────────────────────────────
76
+ log(`${c.dim}→ copying template into${c.reset} ${c.bold}${projectName}${c.reset}/`);
77
+
78
+ function copyRecursive(src, dst) {
79
+ mkdirSync(dst, { recursive: true });
80
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
81
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.astro' || entry.name === 'bun.lock') continue;
82
+ const srcPath = join(src, entry.name);
83
+ const dstPath = join(dst, entry.name);
84
+ if (entry.isDirectory()) copyRecursive(srcPath, dstPath);
85
+ else copyFileSync(srcPath, dstPath);
86
+ }
87
+ }
88
+ copyRecursive(TEMPLATE_DIR, targetDir);
89
+
90
+ // ─── Patch package.json ───────────────────────────────────────────────
91
+ log(`${c.dim}→ wiring up${c.reset} package.json`);
92
+ const pkgPath = join(targetDir, 'package.json');
93
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
94
+
95
+ pkg.name = projectName;
96
+ pkg.version = '0.0.1';
97
+ pkg.private = true;
98
+ delete pkg.description; // user fills theirs
99
+
100
+ // Replace workspace:* with the published theme version range
101
+ if (pkg.dependencies?.['@abstractdata/starlight-theme']?.startsWith('workspace:')) {
102
+ pkg.dependencies['@abstractdata/starlight-theme'] = THEME_VERSION;
103
+ }
104
+
105
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
106
+
107
+ // ─── Write .gitignore ─────────────────────────────────────────────────
108
+ // npm strips `.gitignore` from published tarballs, so we ship the contents
109
+ // inline here. If the user (or template) committed one already after copy,
110
+ // don't overwrite — they may have customized it.
111
+ const gitignorePath = join(targetDir, '.gitignore');
112
+ if (!existsSync(gitignorePath)) {
113
+ log(`${c.dim}→ writing${c.reset} .gitignore`);
114
+ writeFileSync(
115
+ gitignorePath,
116
+ [
117
+ '# build output',
118
+ 'dist/',
119
+ '.astro/',
120
+ '.output/',
121
+ '',
122
+ '# dependencies',
123
+ 'node_modules/',
124
+ '',
125
+ '# bun — bun.lock IS committed (text-based lockfile, needed for reproducible installs)',
126
+ '.bun/',
127
+ '',
128
+ '# logs',
129
+ '*.log',
130
+ 'npm-debug.log*',
131
+ '',
132
+ '# env',
133
+ '.env',
134
+ '.env.local',
135
+ '',
136
+ '# os',
137
+ '.DS_Store',
138
+ 'Thumbs.db',
139
+ '',
140
+ ].join('\n'),
141
+ );
142
+ }
143
+
144
+ // ─── Patch astro.config.mjs ───────────────────────────────────────────
145
+ const astroConfigPath = join(targetDir, 'astro.config.mjs');
146
+ if (existsSync(astroConfigPath)) {
147
+ let cfg = readFileSync(astroConfigPath, 'utf8');
148
+ // Replace the placeholder title
149
+ cfg = cfg.replace(/title:\s*['"]Your Project Docs['"]/, `title: '${projectName}'`);
150
+ writeFileSync(astroConfigPath, cfg);
151
+ }
152
+
153
+ // ─── git init ─────────────────────────────────────────────────────────
154
+ log(`${c.dim}→ initializing git${c.reset}`);
155
+ try {
156
+ execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
157
+ execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
158
+ execSync('git -c user.name=create-docs -c user.email=cli@abstractdata.io commit -q -m "chore: initial scaffold from @abstract-data/create-docs"', {
159
+ cwd: targetDir,
160
+ stdio: 'ignore',
161
+ });
162
+ } catch (err) {
163
+ // Non-fatal — user can `git init` manually.
164
+ const isNotFound =
165
+ err?.code === 'ENOENT' ||
166
+ /not found|no such file|command not found/i.test(err?.message ?? '');
167
+ if (isNotFound) {
168
+ log(`${c.dim} (git not found in PATH — run ${c.reset}${c.bold}git init${c.reset}${c.dim} inside ${c.reset}${c.bold}${projectName}/${c.reset}${c.dim} to start version-tracking your project)${c.reset}`);
169
+ } else {
170
+ log(`${c.dim} (git skipped: ${(err?.message ?? 'unknown error').split('\n')[0]} — run git init manually)${c.reset}`);
171
+ }
172
+ }
173
+
174
+ // ─── Done ─────────────────────────────────────────────────────────────
175
+ log('');
176
+ log(`${c.green}✓${c.reset} ${c.bold}${projectName}${c.reset} scaffolded.`);
177
+ log('');
178
+ log(`${c.gold}Next steps:${c.reset}`);
179
+ log(` ${c.dim}$${c.reset} cd ${projectName}`);
180
+ log(` ${c.dim}$${c.reset} bun install`);
181
+ log(` ${c.dim}$${c.reset} bun dev`);
182
+ log('');
183
+ log(`${c.cyan}Tip:${c.reset} ${c.dim}open your AI coding assistant in ${projectName}/ — it will${c.reset}`);
184
+ log(` ${c.gold}offer to run the abstract-data-setup workflow automatically${c.reset}`);
185
+ log(` ${c.dim}on the first message. No magic phrase needed; just open${c.reset}`);
186
+ log(` ${c.dim}Claude Code, Cursor, or whatever you use, and the AI will${c.reset}`);
187
+ log(` ${c.dim}greet you and ask.${c.reset}`);
188
+ log('');
189
+ log(` ${c.dim}Installed for: Claude Code (CLAUDE.md + .claude/skills/),${c.reset}`);
190
+ log(` ${c.dim}Cursor (.cursor/rules/), GitHub Copilot (.github/${c.reset}`);
191
+ log(` ${c.dim}copilot-instructions.md). Delete what you don't use.${c.reset}`);
192
+ log('');
193
+ log(`${c.dim}Docs · https://github.com/Abstract-Data/abstract-data-doc-theme${c.reset}`);
194
+ log('');
package/bin/cli.js ADDED
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * @abstractdata/create-docs
5
+ *
6
+ * Scaffold a new branded Starlight documentation project from the
7
+ * @abstractdata/docs template. Invoked as:
8
+ *
9
+ * bun create @abstractdata/docs <project-name>
10
+ * npm create @abstractdata/docs@latest <project-name>
11
+ * pnpm create @abstractdata/docs <project-name>
12
+ */
13
+ import { existsSync, mkdirSync, readdirSync, copyFileSync, readFileSync, writeFileSync, statSync } from 'node:fs';
14
+ import { join, resolve, dirname, basename } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { execSync } from 'node:child_process';
17
+ import { stdin, stdout, exit, argv, cwd } from 'node:process';
18
+ import { createInterface } from 'node:readline/promises';
19
+
20
+ // ─── Theme version this CLI scaffolds against ─────────────────────────
21
+ // Auto-updated by `scripts/sync-theme-version.mjs` during the prepack
22
+ // step — DO NOT hand-edit. Reflects the version in
23
+ // `packages/starlight-theme/package.json` at publish time.
24
+ const THEME_VERSION = '^0.3.4';
25
+
26
+ // ─── Paths ────────────────────────────────────────────────────────────
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+ const TEMPLATE_CANDIDATES = [
29
+ resolve(__dirname, '..', 'template'), // bundled (after prepack)
30
+ resolve(__dirname, '..', '..', 'template'), // workspace dev
31
+ ];
32
+ const TEMPLATE_DIR = TEMPLATE_CANDIDATES.find(existsSync);
33
+
34
+ // ─── ANSI colors ──────────────────────────────────────────────────────
35
+ const c = {
36
+ reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
37
+ cyan: '\x1b[36m', gold: '\x1b[33m', red: '\x1b[31m', green: '\x1b[32m',
38
+ };
39
+
40
+ const log = (...args) => console.log(...args);
41
+ const die = (msg) => { console.error(`${c.red}error${c.reset} ${msg}`); exit(1); };
42
+
43
+ // ─── Banner ───────────────────────────────────────────────────────────
44
+ log('');
45
+ log(`${c.cyan}${c.bold}┌─[ ABSTRACT DATA · DOCS ]─────────────────┐${c.reset}`);
46
+ log(`${c.cyan}│${c.reset} ${c.dim}Scaffolding a branded Starlight site${c.reset} ${c.cyan}│${c.reset}`);
47
+ log(`${c.cyan}└──────────────────────────────────────────┘${c.reset}`);
48
+ log('');
49
+
50
+ if (!TEMPLATE_DIR) {
51
+ die('Could not locate template directory. This usually means @abstractdata/create-docs was packaged incorrectly. Reinstall the package or file an issue.');
52
+ }
53
+
54
+ // ─── Resolve project name ─────────────────────────────────────────────
55
+ const args = argv.slice(2);
56
+ let projectName = args.find((a) => !a.startsWith('-'));
57
+
58
+ if (!projectName) {
59
+ const rl = createInterface({ input: stdin, output: stdout });
60
+ projectName = (await rl.question(`${c.cyan}?${c.reset} Project name (and folder): `)).trim();
61
+ rl.close();
62
+ }
63
+
64
+ if (!projectName) die('Project name is required.');
65
+ if (!/^[a-z0-9][a-z0-9-_.]*$/i.test(projectName)) {
66
+ die(`"${projectName}" is not a valid folder/package name. Use letters, numbers, hyphens, underscores, dots — start with a letter or digit.`);
67
+ }
68
+
69
+ const targetDir = resolve(cwd(), projectName);
70
+ if (existsSync(targetDir)) {
71
+ const entries = readdirSync(targetDir);
72
+ if (entries.length > 0) die(`Folder ${c.bold}${projectName}${c.reset} already exists and is not empty.`);
73
+ }
74
+
75
+ // ─── Copy template ────────────────────────────────────────────────────
76
+ log(`${c.dim}→ copying template into${c.reset} ${c.bold}${projectName}${c.reset}/`);
77
+
78
+ function copyRecursive(src, dst) {
79
+ mkdirSync(dst, { recursive: true });
80
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
81
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.astro' || entry.name === 'bun.lock') continue;
82
+ const srcPath = join(src, entry.name);
83
+ const dstPath = join(dst, entry.name);
84
+ if (entry.isDirectory()) copyRecursive(srcPath, dstPath);
85
+ else copyFileSync(srcPath, dstPath);
86
+ }
87
+ }
88
+ copyRecursive(TEMPLATE_DIR, targetDir);
89
+
90
+ // ─── Patch package.json ───────────────────────────────────────────────
91
+ log(`${c.dim}→ wiring up${c.reset} package.json`);
92
+ const pkgPath = join(targetDir, 'package.json');
93
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
94
+
95
+ pkg.name = projectName;
96
+ pkg.version = '0.0.1';
97
+ pkg.private = true;
98
+ delete pkg.description; // user fills theirs
99
+
100
+ // Replace workspace:* with the published theme version range
101
+ if (pkg.dependencies?.['@abstractdata/starlight-theme']?.startsWith('workspace:')) {
102
+ pkg.dependencies['@abstractdata/starlight-theme'] = THEME_VERSION;
103
+ }
104
+
105
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
106
+
107
+ // ─── Write .gitignore ─────────────────────────────────────────────────
108
+ // npm strips `.gitignore` from published tarballs, so we ship the contents
109
+ // inline here. If the user (or template) committed one already after copy,
110
+ // don't overwrite — they may have customized it.
111
+ const gitignorePath = join(targetDir, '.gitignore');
112
+ if (!existsSync(gitignorePath)) {
113
+ log(`${c.dim}→ writing${c.reset} .gitignore`);
114
+ writeFileSync(
115
+ gitignorePath,
116
+ [
117
+ '# build output',
118
+ 'dist/',
119
+ '.astro/',
120
+ '.output/',
121
+ '',
122
+ '# dependencies',
123
+ 'node_modules/',
124
+ '',
125
+ '# bun — bun.lock IS committed (text-based lockfile, needed for reproducible installs)',
126
+ '.bun/',
127
+ '',
128
+ '# logs',
129
+ '*.log',
130
+ 'npm-debug.log*',
131
+ '',
132
+ '# env',
133
+ '.env',
134
+ '.env.local',
135
+ '',
136
+ '# os',
137
+ '.DS_Store',
138
+ 'Thumbs.db',
139
+ '',
140
+ ].join('\n'),
141
+ );
142
+ }
143
+
144
+ // ─── Patch astro.config.mjs ───────────────────────────────────────────
145
+ const astroConfigPath = join(targetDir, 'astro.config.mjs');
146
+ if (existsSync(astroConfigPath)) {
147
+ let cfg = readFileSync(astroConfigPath, 'utf8');
148
+ // Replace the placeholder title
149
+ cfg = cfg.replace(/title:\s*['"]Your Project Docs['"]/, `title: '${projectName}'`);
150
+ writeFileSync(astroConfigPath, cfg);
151
+ }
152
+
153
+ // ─── git init ─────────────────────────────────────────────────────────
154
+ log(`${c.dim}→ initializing git${c.reset}`);
155
+ try {
156
+ execSync('git init -q', { cwd: targetDir, stdio: 'ignore' });
157
+ execSync('git add -A', { cwd: targetDir, stdio: 'ignore' });
158
+ execSync('git -c user.name=create-docs -c user.email=cli@abstractdata.io commit -q -m "chore: initial scaffold from @abstractdata/create-docs"', {
159
+ cwd: targetDir,
160
+ stdio: 'ignore',
161
+ });
162
+ } catch (err) {
163
+ // Non-fatal — user can `git init` manually.
164
+ const isNotFound =
165
+ err?.code === 'ENOENT' ||
166
+ /not found|no such file|command not found/i.test(err?.message ?? '');
167
+ if (isNotFound) {
168
+ log(`${c.dim} (git not found in PATH — run ${c.reset}${c.bold}git init${c.reset}${c.dim} inside ${c.reset}${c.bold}${projectName}/${c.reset}${c.dim} to start version-tracking your project)${c.reset}`);
169
+ } else {
170
+ log(`${c.dim} (git skipped: ${(err?.message ?? 'unknown error').split('\n')[0]} — run git init manually)${c.reset}`);
171
+ }
172
+ }
173
+
174
+ // ─── Done ─────────────────────────────────────────────────────────────
175
+ log('');
176
+ log(`${c.green}✓${c.reset} ${c.bold}${projectName}${c.reset} scaffolded.`);
177
+ log('');
178
+ log(`${c.gold}Next steps:${c.reset}`);
179
+ log(` ${c.dim}$${c.reset} cd ${projectName}`);
180
+ log(` ${c.dim}$${c.reset} bun install`);
181
+ log(` ${c.dim}$${c.reset} bun dev`);
182
+ log('');
183
+ log(`${c.cyan}Tip:${c.reset} ${c.dim}open your AI coding assistant in ${projectName}/ — it will${c.reset}`);
184
+ log(` ${c.gold}offer to run the abstract-data-setup workflow automatically${c.reset}`);
185
+ log(` ${c.dim}on the first message. No magic phrase needed; just open${c.reset}`);
186
+ log(` ${c.dim}Claude Code, Cursor, or whatever you use, and the AI will${c.reset}`);
187
+ log(` ${c.dim}greet you and ask.${c.reset}`);
188
+ log('');
189
+ log(` ${c.dim}Installed for: Claude Code (CLAUDE.md + .claude/skills/),${c.reset}`);
190
+ log(` ${c.dim}Cursor (.cursor/rules/), GitHub Copilot (.github/${c.reset}`);
191
+ log(` ${c.dim}copilot-instructions.md). Delete what you don't use.${c.reset}`);
192
+ log('');
193
+ log(`${c.dim}Docs · https://github.com/Abstract-Data/abstract-data-doc-theme${c.reset}`);
194
+ log('');
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@abstractdata/create-docs",
3
+ "version": "0.2.3",
4
+ "description": "Scaffold a new branded documentation site using @abstractdata/starlight-theme. Run with `bun create @abstractdata/docs <name>`.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-abstractdata-docs": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "template/"
12
+ ],
13
+ "scripts": {
14
+ "prepack": "node ../../scripts/sync-theme-version.mjs && node ../../scripts/compile-skill.mjs && rm -rf ./template && cp -R ../template ./template && rm -rf ./template/node_modules ./template/dist ./template/.astro ./template/bun.lock",
15
+ "test": "node --test"
16
+ },
17
+ "keywords": [
18
+ "create",
19
+ "scaffold",
20
+ "starlight",
21
+ "abstract-data",
22
+ "documentation"
23
+ ],
24
+ "homepage": "https://github.com/Abstract-Data/abstract-data-doc-theme",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/Abstract-Data/abstract-data-doc-theme.git",
28
+ "directory": "packages/create-docs"
29
+ },
30
+ "license": "MIT",
31
+ "author": "Abstract Data <dev@abstractdata.io>",
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "engines": {
36
+ "node": ">=18"
37
+ }
38
+ }