@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.
- package/README.md +36 -0
- package/bin/.fuse_hidden0000000a00000002 +194 -0
- package/bin/.fuse_hidden0000000d00000003 +194 -0
- package/bin/cli.js +194 -0
- package/package.json +38 -0
- package/template/.claude/skills/abstract-data-docs-author/SKILL.md +305 -0
- package/template/.claude/skills/abstract-data-setup/SKILL.md +555 -0
- package/template/.cursor/rules/abstract-data-docs-author.mdc +311 -0
- package/template/.cursor/rules/abstract-data-setup.mdc +561 -0
- package/template/.cursor/rules/welcome.mdc +29 -0
- package/template/.fuse_hidden0000001e00000008 +75 -0
- package/template/.fuse_hidden0000002100000009 +46 -0
- package/template/.fuse_hidden000000210000000a +75 -0
- package/template/.fuse_hidden000000240000000b +46 -0
- package/template/.github/copilot-instructions.md +893 -0
- package/template/.github/workflows/deploy-cloudflare.yml +50 -0
- package/template/.github/workflows/deploy-vercel.yml +47 -0
- package/template/.github/workflows/deploy.yml +55 -0
- package/template/CLAUDE.md +46 -0
- package/template/README.md +75 -0
- package/template/astro.config.mjs +69 -0
- package/template/package.json +25 -0
- package/template/public/favicon.svg +26 -0
- package/template/scripts/build-python-docs.mjs +385 -0
- package/template/scripts/build-ts-docs.mjs +349 -0
- package/template/scripts/python-autodoc.json +10 -0
- package/template/scripts/ts-autodoc.json +10 -0
- package/template/src/assets/README.md +1 -0
- package/template/src/content/docs/index.mdx +81 -0
- package/template/src/content/docs/quickstart.md +162 -0
- package/template/src/content.config.ts +46 -0
- package/template/src/env.d.ts +2 -0
- 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
|
+
}
|