create-vault-cms 1.0.6 → 1.0.8

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 +47 -91
  2. package/package.json +2 -2
  3. package/src/cli.js +204 -173
package/README.md CHANGED
@@ -1,91 +1,47 @@
1
- # Vault CMS
2
-
3
- Use [Obsidian](https://obsidian.md) as a content management system for your [Astro](https://astro.build) website.
4
-
5
- ![Vault CMS cover with Obsidian and Astro logos at the bottom.](https://github.com/user-attachments/assets/fb5d8368-71dd-4bf8-8851-36ada6d4f530)
6
-
7
- ## Video Guide
8
-
9
- šŸ“ŗ [Video Guide](https://youtu.be/dSm8aLPdVz0)
10
-
11
- ## Features
12
-
13
- - Easy integration into Astro website projects
14
- - **Auto-detection** of your Astro theme and content structure
15
- - Preconfigured plugins, hotkeys and settings optimized for Astro workflows
16
- - CMS-like homepage using Obsidian Bases
17
- - Works with any Astro theme by automatically detecting content types and frontmatter properties
18
- - Optional instant-publish option via the Git plugin
19
-
20
- ![Vault CMS Showcase.](https://github.com/user-attachments/assets/0d1ea89e-9d6b-40b1-944d-cfe6143e222e)
21
-
22
- > [!NOTE]
23
- > To see Vault CMS combined with an Astro site specifically designed with it in mind, check out my theme [Astro Modular](https://github.com/davidvkimball/astro-modular).
24
-
25
- ## Installation Guide
26
-
27
- The fastest way to install Vault CMS into your Astro project is via the CLI:
28
-
29
- ### Standard Installation
30
- Run this in your Astro project root:
31
-
32
- ```bash
33
- # Using pnpm (recommended)
34
- pnpm create vault-cms
35
-
36
- # Using npm
37
- npm create vault-cms
38
-
39
- # Using yarn
40
- yarn create vault-cms
41
- ```
42
-
43
- When prompted for the location, the default is `src/content`.
44
-
45
- ### Using a Preset Template
46
- If you are using a supported theme like **Starlight**, **Slate**, or **Chiri**, you can use a preconfigured preset from the [Presets](https://github.com/davidvkimball/vault-cms-presets) repository:
47
-
48
- ```bash
49
- # Using pnpm
50
- pnpm create vault-cms -- --template starlight
51
-
52
- # Using npm
53
- npm create vault-cms -- --template starlight
54
-
55
- # Using yarn
56
- yarn create vault-cms --template starlight
57
- ```
58
- *(Replace `starlight` with `slate` or `chiri` as needed).*
59
-
60
- ---
61
-
62
- This will automatically:
63
- 1. Copy the necessary `_bases` and `.obsidian` configuration folders.
64
- 2. Setup a `README.md` for your vault.
65
- 3. Update your `.gitignore` with the recommended Obsidian excludes.
66
-
67
- ### Manual Installation
68
-
69
- If you prefer to install manually:
70
- 1. Download the [latest release ZIP](https://github.com/davidvkimball/vault-cms/archive/refs/heads/master.zip).
71
- 2. Copy the `_bases` and `.obsidian` folders into your Astro project (e.g., in `src/content`).
72
- 3. Open Obsidian and select "Open folder as vault", then select the folder containing the `.obsidian` directory.
73
-
74
- ## How Auto-Detection Works
75
-
76
- Vault CMS automatically detects and configures itself based on your Astro project:
77
-
78
- - **Project Detection**: Automatically finds your Astro project by locating `astro.config.mjs`, `astro.config.ts`, or other Astro config files.
79
- - **Content Type Detection**: Scans your content folders (like `posts`, `pages`, `docs`, etc.) and automatically identifies them as content types.
80
- - **Frontmatter Analysis**: Analyzes existing content files to detect frontmatter properties (title, date, description, etc.) and configures the plugin accordingly.
81
-
82
- When you first open the vault, a setup wizard will guide you through the configuration process.
83
-
84
- ### Recommended .gitignore
85
-
86
- If you are not using the CLI, add the following to your Astro project's `.gitignore` file:
87
- ```
88
- # Obsidian
89
- .obsidian/workspace.json
90
- .obsidian/workspace-mobile.json
91
- ```
1
+ # Vault CMS
2
+
3
+ Use [Obsidian](https://obsidian.md) as a content management system for your [Astro](https://astro.build) website.
4
+
5
+ ![Vault CMS cover with Obsidian and Astro logos at the bottom.](https://github.com/user-attachments/assets/fb5d8368-71dd-4bf8-8851-36ada6d4f530)
6
+
7
+ ## Quick Start
8
+
9
+ The fastest way to install Vault CMS into your Astro project is via the CLI:
10
+
11
+ ```bash
12
+ pnpm create vault-cms
13
+ ```
14
+
15
+ *Follow the prompts to install into `src/content` or your desired directory.*
16
+
17
+ ## Documentation
18
+
19
+ For full installation guides, plugin details, and customization options, read the [Vault CMS documentation](https://docs.vaultcms.org).
20
+
21
+ ## Features
22
+
23
+ - Easy integration into Astro website projects
24
+ - **Auto-detection** of your Astro theme and content structure
25
+ - Preconfigured plugins, hotkeys and settings optimized for Astro workflows
26
+ - CMS-like homepage using Obsidian Bases
27
+ - Works with any Astro theme by automatically detecting content types and frontmatter properties
28
+ - Optional instant-publish option via the Git plugin
29
+
30
+ ![Vault CMS Showcase.](https://github.com/user-attachments/assets/0d1ea89e-9d6b-40b1-944d-cfe6143e222e)
31
+
32
+ ## Video Guide
33
+
34
+ šŸ“ŗ [Video Guide](https://youtu.be/dSm8aLPdVz0)
35
+
36
+ > [!NOTE]
37
+ > To see Vault CMS combined with an Astro site specifically designed with it in mind, check out my theme [Astro Modular](https://github.com/davidvkimball/astro-modular).
38
+
39
+ ## Presets
40
+
41
+ If you are using a supported theme like **Starlight**, **Slate**, or **Chiri**, you can use a preconfigured preset:
42
+
43
+ ```bash
44
+ pnpm create vault-cms -- --template starlight
45
+ ```
46
+
47
+ See all available presets at the [Presets Repo](https://github.com/davidvkimball/vault-cms-presets).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-vault-cms",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Installer for Vault CMS",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -32,4 +32,4 @@
32
32
  "fs-extra": "^11.3.3",
33
33
  "inquirer": "^8.2.7"
34
34
  }
35
- }
35
+ }
package/src/cli.js CHANGED
@@ -1,173 +1,204 @@
1
- #!/usr/bin/env node
2
-
3
- const { Command } = require('commander');
4
- const fs = require('fs-extra');
5
- const path = require('path');
6
- const https = require('https');
7
- const AdmZip = require('adm-zip');
8
- const inquirer = require('inquirer');
9
-
10
- const pkg = require('../package.json');
11
-
12
- const program = new Command();
13
-
14
- program
15
- .name('create-vault-cms')
16
- .description('Official installer for Vault CMS')
17
- .version(pkg.version);
18
-
19
- program
20
- .argument('[target]', 'target directory')
21
- .option('-t, --template <name>', 'template to use (from vault-cms-presets)')
22
- .action(async (target, options) => {
23
- try {
24
- console.log('šŸš€ Initializing Vault CMS Installer...');
25
-
26
- // 1. Fetch available templates dynamically
27
- const availableTemplates = await fetchTemplates();
28
-
29
- let template = options.template;
30
- let targetPath = target;
31
-
32
- // Smart detection for misplaced arguments
33
- if (targetPath && availableTemplates.includes(targetPath.toLowerCase()) && !template) {
34
- template = targetPath.toLowerCase();
35
- targetPath = null;
36
- }
37
-
38
- // 2. If no template flag, ask if they want one
39
- if (!template) {
40
- const { useTemplate } = await inquirer.prompt([{
41
- type: 'confirm',
42
- name: 'useTemplate',
43
- message: 'Would you like to use a preset template (e.g. Starlight, Slate)?',
44
- default: false
45
- }]);
46
-
47
- if (useTemplate) {
48
- const { selectedTemplate } = await inquirer.prompt([{
49
- type: 'list',
50
- name: 'selectedTemplate',
51
- message: 'Select a template:',
52
- choices: availableTemplates
53
- }]);
54
- template = selectedTemplate;
55
- }
56
- }
57
-
58
- // 3. Prompt for path if not provided
59
- if (!targetPath) {
60
- const answers = await inquirer.prompt([
61
- {
62
- type: 'input',
63
- name: 'path',
64
- message: 'Where should we install Vault CMS?',
65
- default: 'src/content',
66
- }
67
- ]);
68
- targetPath = answers.path;
69
- }
70
-
71
- const targetDir = path.resolve(targetPath);
72
- const tempZip = path.join(targetDir, 'vault-cms-temp.zip');
73
- const extractDir = path.join(targetDir, '.vault-cms-temp-extract');
74
-
75
- const repoName = template ? 'vault-cms-presets' : 'vault-cms';
76
- const zipUrl = `https://github.com/davidvkimball/${repoName}/archive/refs/heads/master.zip`;
77
-
78
- console.log(`\nšŸš€ Installing Vault CMS${template ? ` (template: ${template})` : ''}...`);
79
-
80
- await fs.ensureDir(targetDir);
81
-
82
- console.log(' šŸ“¦ Downloading archive...');
83
- await downloadFile(zipUrl, tempZip);
84
-
85
- console.log(' šŸ“‚ Extracting files...');
86
- const zip = new AdmZip(tempZip);
87
- zip.extractAllTo(extractDir, true);
88
-
89
- const folders = await fs.readdir(extractDir);
90
- const innerFolder = path.join(extractDir, folders[0]);
91
- const sourcePath = template ? path.join(innerFolder, template) : innerFolder;
92
-
93
- if (!(await fs.pathExists(sourcePath))) {
94
- throw new Error(`Template "${template}" not found in presets repository.`);
95
- }
96
-
97
- const toKeep = ['_bases', '.obsidian', 'README.md'];
98
- for (const item of toKeep) {
99
- const src = path.join(sourcePath, item);
100
- const dest = path.join(targetDir, item);
101
-
102
- if (await fs.pathExists(src)) {
103
- await fs.copy(src, dest, { overwrite: true });
104
- console.log(` āœ“ Added ${item}`);
105
- }
106
- }
107
-
108
- const gitignorePath = path.join(targetDir, '.gitignore');
109
- const ignores = '\n# Vault CMS / Obsidian\n.obsidian/workspace.json\n.obsidian/workspace-mobile.json\n.ref/\n';
110
-
111
- if (await fs.pathExists(gitignorePath)) {
112
- const content = await fs.readFile(gitignorePath, 'utf8');
113
- if (!content.includes('.obsidian/workspace.json')) {
114
- await fs.appendFile(gitignorePath, ignores);
115
- console.log(' āœ“ Updated .gitignore');
116
- }
117
- } else {
118
- await fs.writeFile(gitignorePath, ignores.trim() + '\n');
119
- console.log(' āœ“ Created .gitignore');
120
- }
121
-
122
- await fs.remove(tempZip);
123
- await fs.remove(extractDir);
124
-
125
- console.log('\n✨ Vault CMS is ready!');
126
- process.exit(0);
127
- } catch (err) {
128
- console.error('\nāŒ Installation failed:', err.message);
129
- process.exit(1);
130
- }
131
- });
132
-
133
- function downloadFile(url, dest) {
134
- return new Promise((resolve, reject) => {
135
- https.get(url, { headers: { 'User-Agent': 'vault-cms-installer' } }, (res) => {
136
- if (res.statusCode === 301 || res.statusCode === 302) {
137
- return downloadFile(res.headers.location, dest).then(resolve).catch(reject);
138
- }
139
- if (res.statusCode !== 200) {
140
- return reject(new Error(`Failed to download: ${res.statusCode}`));
141
- }
142
- const file = fs.createWriteStream(dest);
143
- res.pipe(file);
144
- file.on('finish', () => {
145
- file.close();
146
- resolve();
147
- });
148
- }).on('error', reject);
149
- });
150
- }
151
-
152
- function fetchTemplates() {
153
- return new Promise((resolve) => {
154
- const url = 'https://api.github.com/repos/davidvkimball/vault-cms-presets/contents';
155
- https.get(url, { headers: { 'User-Agent': 'vault-cms-installer' } }, (res) => {
156
- let data = '';
157
- res.on('data', (chunk) => data += chunk);
158
- res.on('end', () => {
159
- try {
160
- const contents = JSON.parse(data);
161
- const dirs = contents
162
- .filter(item => item.type === 'dir' && !item.name.startsWith('.'))
163
- .map(item => item.name);
164
- resolve(dirs);
165
- } catch (e) {
166
- resolve(['starlight', 'slate', 'chiri']); // Fallback
167
- }
168
- });
169
- }).on('error', () => resolve(['starlight', 'slate', 'chiri']));
170
- });
171
- }
172
-
173
- program.parse();
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const fs = require('fs-extra');
5
+ const path = require('path');
6
+ const https = require('https');
7
+ const AdmZip = require('adm-zip');
8
+ const inquirer = require('inquirer');
9
+
10
+ const pkg = require('../package.json');
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name('create-vault-cms')
16
+ .description('Official installer for Vault CMS')
17
+ .version(pkg.version);
18
+
19
+ program
20
+ .argument('[target]', 'target directory')
21
+ .option('-t, --template <name>', 'template to use (from vault-cms-presets)')
22
+ .action(async (target, options) => {
23
+ try {
24
+ console.log('šŸš€ Initializing Vault CMS Installer...');
25
+
26
+ const availableTemplates = await fetchTemplates();
27
+
28
+ let template = options.template;
29
+ let targetPath = target;
30
+
31
+ if (targetPath && availableTemplates.includes(targetPath.toLowerCase()) && !template) {
32
+ template = targetPath.toLowerCase();
33
+ targetPath = null;
34
+ }
35
+
36
+ if (!template) {
37
+ const { useTemplate } = await inquirer.prompt([{
38
+ type: 'confirm',
39
+ name: 'useTemplate',
40
+ message: 'Would you like to use a preset template (e.g. Starlight, Slate)?',
41
+ default: false
42
+ }]);
43
+
44
+ if (useTemplate) {
45
+ const { selectedTemplate } = await inquirer.prompt([{
46
+ type: 'list',
47
+ name: 'selectedTemplate',
48
+ message: 'Select a template:',
49
+ choices: availableTemplates
50
+ }]);
51
+ template = selectedTemplate;
52
+ }
53
+ }
54
+
55
+ if (!targetPath) {
56
+ const answers = await inquirer.prompt([
57
+ {
58
+ type: 'input',
59
+ name: 'path',
60
+ message: 'Where should we install Vault CMS?',
61
+ default: 'src/content',
62
+ }
63
+ ]);
64
+ targetPath = answers.path;
65
+ }
66
+
67
+ const targetDir = path.resolve(targetPath);
68
+ const tempZip = path.join(targetDir, 'vault-cms-temp.zip');
69
+ const extractDir = path.join(targetDir, '.vault-cms-temp-extract');
70
+
71
+ const repoName = template ? 'vault-cms-presets' : 'vault-cms';
72
+ const zipUrl = `https://github.com/davidvkimball/${repoName}/archive/refs/heads/master.zip`;
73
+
74
+ console.log(`\nšŸš€ Installing Vault CMS${template ? ` (template: ${template})` : ''}...`);
75
+ console.log(` šŸ“ Target directory: ${targetDir}`);
76
+
77
+ await fs.ensureDir(targetDir);
78
+
79
+ console.log(' šŸ“¦ Downloading archive...');
80
+ await downloadFile(zipUrl, tempZip);
81
+
82
+ console.log(' šŸ“‚ Extracting files...');
83
+ const zip = new AdmZip(tempZip);
84
+ zip.extractAllTo(extractDir, true);
85
+
86
+ const items = await fs.readdir(extractDir);
87
+ const folders = items.filter(item => fs.statSync(path.join(extractDir, item)).isDirectory());
88
+
89
+ if (folders.length === 0) {
90
+ throw new Error('Could not find content in the downloaded archive.');
91
+ }
92
+
93
+ const innerFolder = path.join(extractDir, folders[0]);
94
+ const sourcePath = template ? path.join(innerFolder, template) : innerFolder;
95
+
96
+ if (!(await fs.pathExists(sourcePath))) {
97
+ throw new Error(`Template "${template}" not found in presets repository.`);
98
+ }
99
+
100
+ const toKeep = ['_bases', '.obsidian', 'README.md'];
101
+ for (const item of toKeep) {
102
+ const src = path.join(sourcePath, item);
103
+ const dest = path.join(targetDir, item);
104
+
105
+ if (await fs.pathExists(src)) {
106
+ await fs.copy(src, dest, { overwrite: true });
107
+ console.log(` āœ“ Added ${item}`);
108
+ }
109
+ }
110
+
111
+ // Smart .gitignore logic: Look for project root
112
+ const projectRoot = await findProjectRoot(targetDir);
113
+ const gitignorePath = path.join(projectRoot, '.gitignore');
114
+ const ignores = '\n# Vault CMS / Obsidian\n.obsidian/workspace.json\n.obsidian/workspace-mobile.json\n.ref/\n';
115
+
116
+ const isExternalRoot = projectRoot !== targetDir && !targetDir.startsWith(projectRoot);
117
+
118
+ if (await fs.pathExists(gitignorePath)) {
119
+ const content = await fs.readFile(gitignorePath, 'utf8');
120
+ if (!content.includes('.obsidian/workspace.json')) {
121
+ await fs.appendFile(gitignorePath, ignores);
122
+ console.log(` āœ“ Updated .gitignore at ${path.relative(process.cwd(), gitignorePath)}`);
123
+ }
124
+ } else if (!isExternalRoot) {
125
+ await fs.writeFile(gitignorePath, ignores.trim() + '\n');
126
+ console.log(` āœ“ Created .gitignore at ${path.relative(process.cwd(), gitignorePath)}`);
127
+ } else {
128
+ console.log(` āš ļø Skipped .gitignore (could not find a safe project root)`);
129
+ }
130
+
131
+ await fs.remove(tempZip);
132
+ await fs.remove(extractDir);
133
+
134
+ if (projectRoot === targetDir) {
135
+ console.log('\n āš ļø Note: No Astro project or package.json found in parent directories.');
136
+ console.log(' Installation completed, but you may need to move these files into your content folder manually.');
137
+ }
138
+
139
+ console.log('\n✨ Vault CMS is ready!');
140
+ process.exit(0);
141
+ } catch (err) {
142
+ console.error('\nāŒ Installation failed:', err.message);
143
+ process.exit(1);
144
+ }
145
+ });
146
+
147
+ async function findProjectRoot(startDir) {
148
+ let current = startDir;
149
+ // Look up to 6 levels up for a project root (Astro config, package.json, or .git)
150
+ let depth = 0;
151
+ while (current !== path.parse(current).root && depth < 6) {
152
+ const hasPkg = await fs.pathExists(path.join(current, 'package.json'));
153
+ const hasAstro = await fs.pathExists(path.join(current, 'astro.config.mjs')) || await fs.pathExists(path.join(current, 'astro.config.ts'));
154
+ const hasGit = await fs.pathExists(path.join(current, '.git'));
155
+
156
+ if (hasPkg || hasAstro || hasGit) return current;
157
+
158
+ current = path.dirname(current);
159
+ depth++;
160
+ }
161
+ return startDir; // Fallback to target dir
162
+ }
163
+
164
+ function downloadFile(url, dest) {
165
+ return new Promise((resolve, reject) => {
166
+ https.get(url, { headers: { 'User-Agent': 'vault-cms-installer' } }, (res) => {
167
+ if (res.statusCode === 301 || res.statusCode === 302) {
168
+ return downloadFile(res.headers.location, dest).then(resolve).catch(reject);
169
+ }
170
+ if (res.statusCode !== 200) {
171
+ return reject(new Error(`Failed to download: ${res.statusCode}`));
172
+ }
173
+ const file = fs.createWriteStream(dest);
174
+ res.pipe(file);
175
+ file.on('finish', () => {
176
+ file.close();
177
+ resolve();
178
+ });
179
+ }).on('error', reject);
180
+ });
181
+ }
182
+
183
+ function fetchTemplates() {
184
+ return new Promise((resolve) => {
185
+ const url = 'https://api.github.com/repos/davidvkimball/vault-cms-presets/contents';
186
+ https.get(url, { headers: { 'User-Agent': 'vault-cms-installer' } }, (res) => {
187
+ let data = '';
188
+ res.on('data', (chunk) => data += chunk);
189
+ res.on('end', () => {
190
+ try {
191
+ const contents = JSON.parse(data);
192
+ const dirs = contents
193
+ .filter(item => item.type === 'dir' && !item.name.startsWith('.'))
194
+ .map(item => item.name);
195
+ resolve(dirs);
196
+ } catch (e) {
197
+ resolve(['starlight', 'slate', 'chiri']);
198
+ }
199
+ });
200
+ }).on('error', () => resolve(['starlight', 'slate', 'chiri']));
201
+ });
202
+ }
203
+
204
+ program.parse();