Skip to content

Commit 7eafede

Browse files
committed
refactor: externalize template files and introduce a fetching and registry system.
1 parent beb5e8b commit 7eafede

61 files changed

Lines changed: 483 additions & 4148 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The open-source Mintlify alternative. Beautiful documentation sites from Markdow
77
**Simple Setup** - Point to your docs folder and go
88
🚀 **Astro-Powered** - Leverages Astro's speed and SEO optimization
99
📝 **Markdown & MDX** - Full support for both formats with frontmatter
10-
🎨 **Responsive Design** - Mobile-friendly, beautiful documentation
10+
🎨 **Customizable Templates** - Use GitHub-hosted or local templates
1111
🔥 **Hot Reload** - Dev server with live file watching
1212
**Fast Builds** - Static site generation for optimal performance
1313
🎯 **SEO Optimized** - Meta tags, semantic HTML, and proper structure
@@ -44,20 +44,10 @@ superdocs build --input ./my-docs --output ./dist
4444

4545
- `-i, --input <path>` (required) - Path to your docs folder
4646
- `-o, --output <path>` - Output directory (default: `./dist`)
47-
- `-t, --theme <name>` - Theme to use (`default`, `dark`)
47+
- `-t, --template <name>` - Template to use (see [Templates](#templates))
4848
- `-b, --base-url <url>` - Base URL for the site (default: `/`)
4949
- `--search` - Enable search functionality
50-
51-
**Example:**
52-
53-
```bash
54-
superdocs build \
55-
--input ./docs \
56-
--output ./public \
57-
--theme dark \
58-
--base-url /docs/ \
59-
--search
60-
```
50+
- `--refresh` - Force re-download template from GitHub
6151

6252
### Dev Command
6353

@@ -70,32 +60,70 @@ superdocs dev --input ./my-docs
7060
**Options:**
7161

7262
- `-i, --input <path>` (required) - Path to your docs folder
73-
- `-t, --theme <name>` - Theme to use
63+
- `-t, --template <name>` - Template to use
7464
- `-b, --base-url <url>` - Base URL for the site
7565
- `-p, --port <number>` - Port for dev server (default: `4321`)
7666
- `--search` - Enable search functionality
67+
- `--refresh` - Force re-download template
7768

78-
**Example:**
69+
### Eject Command
70+
71+
Export the full Astro project source code to customize it further:
7972

8073
```bash
81-
superdocs dev --input ./docs --port 3000
74+
superdocs eject --input ./my-docs --output ./my-project
8275
```
8376

84-
### Eject Command
77+
## Templates
8578

86-
Export the full Astro project source code to customize it further:
79+
SuperDocs supports flexible template sources:
80+
81+
### Default Template
8782

8883
```bash
89-
superdocs eject --input ./my-docs --output ./my-project
84+
superdocs dev -i ./docs
9085
```
9186

92-
**Options:**
87+
### GitHub Templates
9388

94-
- `-i, --input <path>` (required) - Path to your docs folder
95-
- `-o, --output <path>` - Output directory for the project (default: `./astro-docs-project`)
96-
- `-t, --theme <name>` - Theme to use
97-
- `-b, --base-url <url>` - Base URL for the site
98-
- `--search` - Enable search functionality
89+
Use templates hosted on GitHub:
90+
91+
```bash
92+
# From a GitHub repo
93+
superdocs dev -i ./docs --template github:owner/repo
94+
95+
# Specific branch or tag
96+
superdocs dev -i ./docs --template github:owner/repo#v1.0.0
97+
98+
# Template in a subdirectory
99+
superdocs dev -i ./docs --template github:owner/repo/templates/modern
100+
```
101+
102+
### Local Templates
103+
104+
Use a local template folder:
105+
106+
```bash
107+
superdocs dev -i ./docs --template ./my-custom-template
108+
```
109+
110+
### Template Management
111+
112+
```bash
113+
# List available templates
114+
superdocs template list
115+
116+
# Clear template cache
117+
superdocs template cache --clear
118+
```
119+
120+
### Update Templates
121+
122+
Templates are cached for 24 hours. Force update with:
123+
124+
```bash
125+
superdocs dev -i ./docs --refresh
126+
```
99127

100128
## Documentation Structure
101129

@@ -131,11 +159,12 @@ Your content here...
131159

132160
The CLI tool:
133161

134-
1. **Scaffolds** - Creates a temporary Astro project from an internal template
135-
2. **Syncs** - Copies your docs into `src/pages/` for automatic routing
136-
3. **Configures** - Generates dynamic `astro.config.mjs` with your options
137-
4. **Builds/Serves** - Spawns native Astro CLI commands (`astro build` or `astro dev`)
138-
5. **Watches** (dev mode) - Uses `chokidar` to monitor file changes
162+
1. **Resolves Template** - Fetches from GitHub or uses local template
163+
2. **Scaffolds** - Creates a temporary Astro project from the template
164+
3. **Syncs** - Copies your docs into `src/pages/` for automatic routing
165+
4. **Configures** - Generates dynamic `astro.config.mjs` with your options
166+
5. **Builds/Serves** - Spawns native Astro CLI commands
167+
6. **Watches** (dev mode) - Uses `chokidar` to monitor file changes
139168

140169
## Development
141170

@@ -150,18 +179,16 @@ superdocs/
150179
│ ├── commands/
151180
│ │ ├── build.js # Build command
152181
│ │ ├── dev.js # Dev command with watcher
153-
│ │ └── eject.js # Eject command
182+
│ │ ├── eject.js # Eject command
183+
│ │ └── template.js # Template management
154184
│ ├── core/
155185
│ │ ├── scaffold.js # Project scaffolding
156186
│ │ ├── sync.js # File syncing
157187
│ │ ├── config.js # Config generation
158188
│ │ ├── astro.js # Astro CLI spawning
159-
│ │ └── output.js # Output copying
160-
│ └── template/ # Internal Astro template
161-
│ ├── src/
162-
│ │ ├── layouts/
163-
│ │ └── pages/
164-
│ └── package.json
189+
│ │ ├── template-fetcher.js # GitHub template fetching
190+
│ │ └── template-registry.js # Template name registry
191+
│ └── template/ # Bundled fallback template
165192
└── package.json
166193
```
167194

src/cli.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import pc from 'picocolors';
33
import { buildCommand } from './commands/build.js';
44
import { devCommand } from './commands/dev.js';
55
import { ejectCommand } from './commands/eject.js';
6+
import { templateListCommand, templateCacheCommand } from './commands/template.js';
67

78
export async function cli() {
89
const program = new Command();
@@ -17,31 +18,50 @@ export async function cli() {
1718
.description('Build the documentation site')
1819
.requiredOption('-i, --input <path>', 'Path to the docs folder')
1920
.option('-o, --output <path>', 'Output directory for the built site', './dist')
20-
.option('-t, --theme <name>', 'Theme to use', 'default')
21+
.option('-t, --template <name>', 'Template to use (default, github:owner/repo, or local path)', 'default')
2122
.option('-b, --base-url <url>', 'Base URL for the site', '/')
2223
.option('--search', 'Enable search functionality', false)
24+
.option('--refresh', 'Force re-download template (bypass cache)', false)
2325
.action(buildCommand);
2426

2527
program
2628
.command('dev')
2729
.description('Start development server with watch mode')
2830
.requiredOption('-i, --input <path>', 'Path to the docs folder')
29-
.option('-t, --theme <name>', 'Theme to use', 'default')
31+
.option('-t, --template <name>', 'Template to use (default, github:owner/repo, or local path)', 'default')
3032
.option('-b, --base-url <url>', 'Base URL for the site', '/')
3133
.option('--search', 'Enable search functionality', false)
3234
.option('-p, --port <number>', 'Port for dev server', '4321')
35+
.option('--refresh', 'Force re-download template (bypass cache)', false)
3336
.action(devCommand);
3437

3538
program
3639
.command('eject')
3740
.description('Export the full Astro project source code')
3841
.requiredOption('-i, --input <path>', 'Path to the docs folder')
3942
.option('-o, --output <path>', 'Output directory for the project', './astro-docs-project')
40-
.option('-t, --theme <name>', 'Theme to use', 'default')
43+
.option('-t, --template <name>', 'Template to use (default, github:owner/repo, or local path)', 'default')
4144
.option('-b, --base-url <url>', 'Base URL for the site', '/')
4245
.option('--search', 'Enable search functionality', false)
46+
.option('--refresh', 'Force re-download template (bypass cache)', false)
4347
.action(ejectCommand);
4448

49+
// Template management commands
50+
const templateCmd = program
51+
.command('template')
52+
.description('Manage documentation templates');
53+
54+
templateCmd
55+
.command('list')
56+
.description('List available templates')
57+
.action(templateListCommand);
58+
59+
templateCmd
60+
.command('cache')
61+
.description('Manage template cache')
62+
.option('--clear', 'Clear all cached templates')
63+
.action(templateCacheCommand);
64+
4565
try {
4666
await program.parseAsync(process.argv);
4767
} catch (error) {

src/commands/build.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { generateConfig } from '../core/config.js';
88
import { runAstroBuild } from '../core/astro.js';
99
import { syncDocsConfig } from '../core/config-sync.js';
1010
import { copyOutput } from '../core/output.js';
11+
import { getTemplatePath } from '../core/template-fetcher.js';
1112

1213
export async function buildCommand(options) {
1314
try {
@@ -23,9 +24,14 @@ export async function buildCommand(options) {
2324

2425
const s = spinner();
2526

27+
// Step 0: Resolve template
28+
s.start('Resolving template...');
29+
const templatePath = await getTemplatePath(options.template, options.refresh);
30+
s.stop(templatePath ? `Using template: ${pc.cyan(templatePath)}` : 'Using bundled template');
31+
2632
// Step 1: Scaffold temporary Astro project
2733
s.start('Setting up Astro project...');
28-
const projectDir = await scaffoldProject();
34+
const projectDir = await scaffoldProject(templatePath);
2935
s.stop('Astro project scaffolded');
3036

3137
// Step 2: Install dependencies

src/commands/dev.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { syncDocs } from '../core/sync.js';
88
import { generateConfig } from '../core/config.js';
99
import { runAstroDev } from '../core/astro.js';
1010
import { syncDocsConfig } from '../core/config-sync.js';
11+
import { getTemplatePath } from '../core/template-fetcher.js';
1112

1213
export async function devCommand(options) {
1314
try {
@@ -23,9 +24,14 @@ export async function devCommand(options) {
2324

2425
const s = spinner();
2526

27+
// Step 0: Resolve template
28+
s.start('Resolving template...');
29+
const templatePath = await getTemplatePath(options.template, options.refresh);
30+
s.stop(templatePath ? `Using template: ${pc.cyan(templatePath)}` : 'Using bundled template');
31+
2632
// Step 1: Scaffold temporary Astro project
2733
s.start('Setting up Astro project...');
28-
const projectDir = await scaffoldProject();
34+
const projectDir = await scaffoldProject(templatePath);
2935
s.stop('Astro project scaffolded');
3036

3137
// Register cleanup handlers

src/commands/eject.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { scaffoldProject, cleanupProject } from '../core/scaffold.js';
77
import { syncDocs } from '../core/sync.js';
88
import { generateConfig } from '../core/config.js';
99
import { syncDocsConfig } from '../core/config-sync.js';
10+
import { getTemplatePath } from '../core/template-fetcher.js';
1011

1112
export async function ejectCommand(options) {
1213
try {
@@ -29,9 +30,14 @@ export async function ejectCommand(options) {
2930

3031
const s = spinner();
3132

33+
// Step 0: Resolve template
34+
s.start('Resolving template...');
35+
const templatePath = await getTemplatePath(options.template, options.refresh);
36+
s.stop(templatePath ? `Using template: ${pc.cyan(templatePath)}` : 'Using bundled template');
37+
3238
// Step 1: Scaffold temporary Astro project
3339
s.start('Scaffolding Astro project...');
34-
const projectDir = await scaffoldProject();
40+
const projectDir = await scaffoldProject(templatePath);
3541
s.stop('Astro project scaffolded');
3642

3743
// Step 2: Sync docs to Astro

src/commands/template.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { intro, outro, spinner, log, note } from '@clack/prompts';
2+
import pc from 'picocolors';
3+
import { listCachedTemplates, clearTemplateCache } from '../core/template-fetcher.js';
4+
import { getRegistryNames, getRegistryInfo } from '../core/template-registry.js';
5+
6+
/**
7+
* List available templates
8+
*/
9+
export async function templateListCommand() {
10+
intro(pc.inverse(pc.cyan(' SuperDocs - Templates ')));
11+
12+
// Show registry templates
13+
const names = getRegistryNames();
14+
15+
log.info(pc.bold('Available Templates:'));
16+
console.log('');
17+
18+
for (const name of names) {
19+
const info = getRegistryInfo(name);
20+
console.log(` ${pc.blue('●')} ${pc.bold(name)} ${pc.dim(`→ ${info.source}`)}`);
21+
}
22+
23+
console.log('');
24+
log.info(pc.dim('You can also use GitHub URLs directly:'));
25+
console.log(pc.dim(' superdocs dev -i . --template github:owner/repo'));
26+
console.log(pc.dim(' superdocs dev -i . --template github:owner/repo#v1.0.0'));
27+
console.log('');
28+
29+
// Show cached templates
30+
const cached = await listCachedTemplates();
31+
if (cached.length > 0) {
32+
log.info(pc.bold('Cached Templates:'));
33+
console.log('');
34+
for (const t of cached) {
35+
const age = Math.round((Date.now() - t.cachedAt) / (1000 * 60));
36+
console.log(` ${pc.yellow('●')} ${t.owner}/${t.repo}#${t.ref} ${pc.dim(`(cached ${age}m ago)`)}`);
37+
}
38+
console.log('');
39+
}
40+
41+
outro(pc.dim('Use --template to select a template'));
42+
}
43+
44+
/**
45+
* Manage template cache
46+
*/
47+
export async function templateCacheCommand(options) {
48+
intro(pc.inverse(pc.cyan(' SuperDocs - Template Cache ')));
49+
50+
if (options.clear) {
51+
const s = spinner();
52+
s.start('Clearing template cache...');
53+
await clearTemplateCache();
54+
s.stop('Template cache cleared');
55+
outro(pc.green('Done!'));
56+
return;
57+
}
58+
59+
// Show cache info
60+
const cached = await listCachedTemplates();
61+
62+
if (cached.length === 0) {
63+
log.info('No templates cached.');
64+
outro('');
65+
return;
66+
}
67+
68+
log.info(pc.bold(`${cached.length} template(s) cached:`));
69+
console.log('');
70+
71+
for (const t of cached) {
72+
const age = Math.round((Date.now() - t.cachedAt) / (1000 * 60 * 60));
73+
console.log(` ${pc.cyan(t.owner)}/${pc.cyan(t.repo)}#${pc.yellow(t.ref)}`);
74+
console.log(` ${pc.dim(`Cached ${age}h ago`)}`);
75+
}
76+
77+
console.log('');
78+
note('superdocs template cache --clear', 'To clear cache');
79+
outro('');
80+
}

src/core/scaffold.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const __dirname = dirname(__filename);
1111
// Use a consistent directory path instead of random temp directories
1212
const SUPERDOCS_DIR = join(tmpdir(), '.superdocs');
1313

14-
export async function scaffoldProject() {
14+
export async function scaffoldProject(customTemplatePath = null) {
1515
// Ensure the directory exists (creates if it doesn't)
1616
await ensureDir(SUPERDOCS_DIR);
1717

@@ -20,8 +20,8 @@ export async function scaffoldProject() {
2020

2121
const tempDir = SUPERDOCS_DIR;
2222

23-
// Copy template to temp directory, excluding node_modules and lock files
24-
const templatePath = join(__dirname, '../template');
23+
// Use custom template path if provided, otherwise use bundled template
24+
const templatePath = customTemplatePath || join(__dirname, '../template');
2525
await copy(templatePath, tempDir, {
2626
filter: (src) => {
2727
const name = basename(src);

0 commit comments

Comments
 (0)