fossbook 0.0.5 → 0.0.7

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/lib/init.js CHANGED
@@ -1,265 +1,313 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const { execSync } = require("child_process");
4
-
5
- const DEPLOY_WORKFLOW = `name: Deploy to GitHub Pages
6
-
7
- on:
8
- push:
9
- branches: [main]
10
-
11
- permissions:
12
- contents: read
13
- pages: write
14
- id-token: write
15
-
16
- concurrency:
17
- group: "pages"
18
- cancel-in-progress: false
19
-
20
- jobs:
21
- build-and-deploy:
22
- runs-on: ubuntu-latest
23
- environment:
24
- name: github-pages
25
- url: \${{ steps.deployment.outputs.page_url }}
26
- steps:
27
- - uses: actions/checkout@v4
28
-
29
- - name: Setup Node.js
30
- uses: actions/setup-node@v4
31
- with:
32
- node-version: '20'
33
- cache: 'npm'
34
-
35
- - name: Install dependencies
36
- run: npm ci
37
-
38
- - name: Build site
39
- run: npx fossbook build
40
-
41
- - name: Setup Pages
42
- uses: actions/configure-pages@v4
43
-
44
- - name: Upload artifact
45
- uses: actions/upload-pages-artifact@v3
46
- with:
47
- path: './public'
48
-
49
- - name: Deploy to GitHub Pages
50
- id: deployment
51
- uses: actions/deploy-pages@v4
52
- `;
53
-
54
- function initProject(options = {}) {
55
- let cwd = process.cwd();
56
-
57
- // If a project name is given, create the directory and work inside it
58
- if (options.name) {
59
- const projectDir = path.join(cwd, options.name);
60
- if (fs.existsSync(projectDir)) {
61
- console.error(`Error: Directory "${options.name}" already exists.`);
62
- process.exit(1);
63
- }
64
- fs.mkdirSync(projectDir, { recursive: true });
65
- cwd = projectDir;
66
- console.log(`Creating new site in ${cwd}\n`);
67
- }
68
-
69
- // Create directories
70
- const dirs = [
71
- "content/posts",
72
- "static/images",
73
- ];
74
-
75
- dirs.forEach((dir) => {
76
- const dirPath = path.join(cwd, dir);
77
- if (!fs.existsSync(dirPath)) {
78
- fs.mkdirSync(dirPath, { recursive: true });
79
- console.log(`Created: ${dir}/`);
80
- }
81
- });
82
-
83
- // Create .github/workflows/deploy.yml
84
- const workflowDir = path.join(cwd, ".github", "workflows");
85
- const workflowPath = path.join(workflowDir, "deploy.yml");
86
- if (!fs.existsSync(workflowPath)) {
87
- fs.mkdirSync(workflowDir, { recursive: true });
88
- fs.writeFileSync(workflowPath, DEPLOY_WORKFLOW);
89
- console.log("Created: .github/workflows/deploy.yml");
90
- }
91
-
92
- // Create .gitignore
93
- const gitignorePath = path.join(cwd, ".gitignore");
94
- if (!fs.existsSync(gitignorePath)) {
95
- fs.writeFileSync(gitignorePath, "node_modules/\npublic/\n");
96
- console.log("Created: .gitignore");
97
- }
98
-
99
- // Create fossbook.config.js
100
- const configPath = path.join(cwd, "fossbook.config.js");
101
- if (!fs.existsSync(configPath)) {
102
- const configContent = `module.exports = {
103
- blogName: "My Blog",
104
- authorName: "",
105
- authorDescription: "",
106
- authorWebsite: "",
107
- blogDescription: "A blog powered by fossbook",
108
- blogsite: "http://localhost:3000",
109
-
110
- // Optional
111
- githubCNAME: "",
112
- googleAnalyticsID: "",
113
- authorTwitter: "",
114
- siteTwitter: "",
115
- githubRepository: "",
116
- image: "",
117
- theme: "archie",
118
-
119
- // Comment system (optional)
120
- // comments: { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" },
121
-
122
- // Deployment
123
- // deploy: { branch: "main", remote: "origin" },
124
-
125
- // Directory overrides (defaults shown)
126
- content: "./content",
127
- postsDir: "./content/posts",
128
- outputDir: "./public",
129
- staticDir: "./static",
130
- themesDir: "./themes",
131
- };
132
- `;
133
- fs.writeFileSync(configPath, configContent);
134
- console.log("Created: fossbook.config.js");
135
- }
136
-
137
- // Create content/about.md
138
- const aboutPath = path.join(cwd, "content", "about.md");
139
- if (!fs.existsSync(aboutPath)) {
140
- const aboutContent = `---
141
- title: About
142
- ---
143
-
144
- Welcome to my blog!
145
- `;
146
- fs.writeFileSync(aboutPath, aboutContent);
147
- console.log("Created: content/about.md");
148
- }
149
-
150
- // Create package.json if it doesn't exist
151
- const pkgPath = path.join(cwd, "package.json");
152
- const createdPkg = !fs.existsSync(pkgPath);
153
- if (createdPkg) {
154
- const pkg = {
155
- name: path.basename(cwd),
156
- version: "1.0.0",
157
- private: true,
158
- scripts: {
159
- build: "fossbook build",
160
- start: "fossbook serve",
161
- deploy: "fossbook deploy",
162
- },
163
- dependencies: {
164
- fossbook: "latest",
165
- },
166
- };
167
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
168
- console.log("Created: package.json");
169
- }
170
-
171
- // Generate package-lock.json (required by CI/CD npm ci + cache)
172
- const lockPath = path.join(cwd, "package-lock.json");
173
- if (!fs.existsSync(lockPath)) {
174
- console.log("Running npm install to generate package-lock.json...");
175
- try {
176
- execSync("npm install", { cwd, stdio: "inherit" });
177
- console.log("Created: package-lock.json");
178
- } catch {
179
- console.warn("Warning: npm install failed. You may need to run it manually.");
180
- }
181
- }
182
-
183
- // Handle --github flag: create repo and push
184
- if (options.github) {
185
- initGitHub(cwd);
186
- }
187
-
188
- console.log("\nSite initialized! Next steps:");
189
- if (!options.github) {
190
- console.log(' 1. Edit fossbook.config.js with your site settings');
191
- console.log(' 2. Run: fossbook new "My First Post"');
192
- console.log(" 3. Edit your post in content/posts/");
193
- console.log(" 4. Run: fossbook build");
194
- console.log(" 5. Run: fossbook serve");
195
- } else {
196
- console.log(' 1. Edit fossbook.config.js with your site settings');
197
- console.log(' 2. Run: fossbook new "My First Post"');
198
- console.log(" 3. Edit your post in content/posts/");
199
- console.log(" 4. Run: fossbook deploy");
200
- }
201
- }
202
-
203
- function initGitHub(cwd) {
204
- // Check if gh CLI is available
205
- try {
206
- execSync("gh --version", { stdio: "ignore" });
207
- } catch {
208
- console.error(
209
- "Error: GitHub CLI (gh) is not installed.\n" +
210
- "Install it: https://cli.github.com/\n" +
211
- " Linux: sudo apt install gh\n" +
212
- " macOS: brew install gh\n" +
213
- " Windows: winget install GitHub.cli"
214
- );
215
- process.exit(1);
216
- }
217
-
218
- // Check if gh is authenticated
219
- try {
220
- execSync("gh auth status", { stdio: "ignore" });
221
- } catch {
222
- console.error("Error: GitHub CLI is not authenticated. Run: gh auth login");
223
- process.exit(1);
224
- }
225
-
226
- // Initialize git repo if needed
227
- if (!fs.existsSync(path.join(cwd, ".git"))) {
228
- console.log("Initializing git repository...");
229
- execSync("git init", { cwd, stdio: "inherit" });
230
- execSync("git branch -M main", { cwd, stdio: "inherit" });
231
- }
232
-
233
- // Create GitHub repository
234
- const repoName = path.basename(cwd);
235
- console.log(`Creating GitHub repository: ${repoName}...`);
236
- try {
237
- execSync(`gh repo create ${repoName} --public --source=. --remote=origin`, {
238
- cwd,
239
- stdio: "inherit",
240
- });
241
- } catch {
242
- // Repo may already exist or remote may already be set
243
- console.warn("Note: GitHub repo creation skipped (may already exist).");
244
- }
245
-
246
- // Initial commit and push
247
- console.log("Committing and pushing to GitHub...");
248
- execSync("git add -A", { cwd, stdio: "inherit" });
249
- try {
250
- execSync('git commit -m "Initial fossbook site"', { cwd, stdio: "inherit" });
251
- } catch {
252
- // Nothing to commit
253
- }
254
- try {
255
- execSync("git push -u origin main", { cwd, stdio: "inherit" });
256
- } catch (e) {
257
- console.warn("Warning: Could not push to origin. You may need to push manually.");
258
- }
259
-
260
- console.log("\nGitHub repository created and pushed!");
261
- console.log("Enable GitHub Pages in your repo settings:");
262
- console.log(" Settings Pages → Source → GitHub Actions");
263
- }
264
-
265
- module.exports = { initProject };
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+ const readline = require("readline");
5
+
6
+ const DEPLOY_WORKFLOW = `name: Deploy to GitHub Pages
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+
12
+ permissions:
13
+ contents: read
14
+ pages: write
15
+ id-token: write
16
+
17
+ concurrency:
18
+ group: "pages"
19
+ cancel-in-progress: false
20
+
21
+ jobs:
22
+ build-and-deploy:
23
+ runs-on: ubuntu-latest
24
+ environment:
25
+ name: github-pages
26
+ url: \${{ steps.deployment.outputs.page_url }}
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - name: Setup Node.js
31
+ uses: actions/setup-node@v4
32
+ with:
33
+ node-version: '20'
34
+ cache: 'npm'
35
+
36
+ - name: Install dependencies
37
+ run: npm ci
38
+
39
+ - name: Build site
40
+ run: npx fossbook build
41
+
42
+ - name: Setup Pages
43
+ uses: actions/configure-pages@v4
44
+ with:
45
+ enablement: true
46
+
47
+ - name: Upload artifact
48
+ uses: actions/upload-pages-artifact@v3
49
+ with:
50
+ path: './public'
51
+
52
+ - name: Deploy to GitHub Pages
53
+ id: deployment
54
+ uses: actions/deploy-pages@v4
55
+ `;
56
+
57
+ function initProject(options = {}) {
58
+ let cwd = process.cwd();
59
+
60
+ // If a project name is given, create the directory and work inside it
61
+ if (options.name) {
62
+ const projectDir = path.join(cwd, options.name);
63
+ if (fs.existsSync(projectDir)) {
64
+ console.error(`Error: Directory "${options.name}" already exists.`);
65
+ process.exit(1);
66
+ }
67
+ fs.mkdirSync(projectDir, { recursive: true });
68
+ cwd = projectDir;
69
+ console.log(`Creating new site in ${cwd}\n`);
70
+ }
71
+
72
+ // Create directories
73
+ const dirs = [
74
+ "content/posts",
75
+ "static/images",
76
+ ];
77
+
78
+ dirs.forEach((dir) => {
79
+ const dirPath = path.join(cwd, dir);
80
+ if (!fs.existsSync(dirPath)) {
81
+ fs.mkdirSync(dirPath, { recursive: true });
82
+ console.log(`Created: ${dir}/`);
83
+ }
84
+ });
85
+
86
+ // Create .github/workflows/deploy.yml
87
+ const workflowDir = path.join(cwd, ".github", "workflows");
88
+ const workflowPath = path.join(workflowDir, "deploy.yml");
89
+ if (!fs.existsSync(workflowPath)) {
90
+ fs.mkdirSync(workflowDir, { recursive: true });
91
+ fs.writeFileSync(workflowPath, DEPLOY_WORKFLOW);
92
+ console.log("Created: .github/workflows/deploy.yml");
93
+ }
94
+
95
+ // Create .gitignore
96
+ const gitignorePath = path.join(cwd, ".gitignore");
97
+ if (!fs.existsSync(gitignorePath)) {
98
+ fs.writeFileSync(gitignorePath, "node_modules/\npublic/\n");
99
+ console.log("Created: .gitignore");
100
+ }
101
+
102
+ // Create fossbook.config.js
103
+ const configPath = path.join(cwd, "fossbook.config.js");
104
+ if (!fs.existsSync(configPath)) {
105
+ const configContent = `module.exports = {
106
+ blogName: "My Blog",
107
+ authorName: "",
108
+ authorDescription: "",
109
+ authorWebsite: "",
110
+ blogDescription: "A blog powered by fossbook",
111
+ blogsite: "http://localhost:3000",
112
+
113
+ // Optional
114
+ githubCNAME: "",
115
+ googleAnalyticsID: "",
116
+ authorTwitter: "",
117
+ siteTwitter: "",
118
+ githubRepository: "",
119
+ image: "",
120
+ theme: "archie",
121
+
122
+ // Comment system (optional)
123
+ // comments: { provider: "utterances", repo: "user/repo", issueTerm: "pathname", theme: "github-light" },
124
+
125
+ // Deployment
126
+ // deploy: { branch: "main", remote: "origin" },
127
+
128
+ // Directory overrides (defaults shown)
129
+ content: "./content",
130
+ postsDir: "./content/posts",
131
+ outputDir: "./public",
132
+ staticDir: "./static",
133
+ themesDir: "./themes",
134
+ };
135
+ `;
136
+ fs.writeFileSync(configPath, configContent);
137
+ console.log("Created: fossbook.config.js");
138
+ }
139
+
140
+ // Create content/about.md
141
+ const aboutPath = path.join(cwd, "content", "about.md");
142
+ if (!fs.existsSync(aboutPath)) {
143
+ const aboutContent = `---
144
+ title: About
145
+ ---
146
+
147
+ Welcome to my blog!
148
+ `;
149
+ fs.writeFileSync(aboutPath, aboutContent);
150
+ console.log("Created: content/about.md");
151
+ }
152
+
153
+ // Create package.json if it doesn't exist
154
+ const pkgPath = path.join(cwd, "package.json");
155
+ const createdPkg = !fs.existsSync(pkgPath);
156
+ if (createdPkg) {
157
+ const pkg = {
158
+ name: path.basename(cwd),
159
+ version: "1.0.0",
160
+ private: true,
161
+ scripts: {
162
+ build: "fossbook build",
163
+ start: "fossbook serve",
164
+ deploy: "fossbook deploy",
165
+ },
166
+ dependencies: {
167
+ fossbook: "latest",
168
+ },
169
+ };
170
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
171
+ console.log("Created: package.json");
172
+ }
173
+
174
+ // Generate package-lock.json (required by CI/CD npm ci + cache)
175
+ const lockPath = path.join(cwd, "package-lock.json");
176
+ if (!fs.existsSync(lockPath)) {
177
+ console.log("Running npm install to generate package-lock.json...");
178
+ try {
179
+ execSync("npm install", { cwd, stdio: "inherit" });
180
+ console.log("Created: package-lock.json");
181
+ } catch {
182
+ console.warn("Warning: npm install failed. You may need to run it manually.");
183
+ }
184
+ }
185
+
186
+ // Handle --github flag: create repo and push
187
+ if (options.github) {
188
+ initGitHub(cwd);
189
+ }
190
+
191
+ console.log("\nSite initialized! Next steps:");
192
+ if (!options.github) {
193
+ console.log(' 1. Edit fossbook.config.js with your site settings');
194
+ console.log(' 2. Run: fossbook new "My First Post"');
195
+ console.log(" 3. Edit your post in content/posts/");
196
+ console.log(" 4. Run: fossbook build");
197
+ console.log(" 5. Run: fossbook serve");
198
+ } else {
199
+ console.log(' 1. Edit fossbook.config.js with your site settings');
200
+ console.log(' 2. Run: fossbook new "My First Post"');
201
+ console.log(" 3. Edit your post in content/posts/");
202
+ console.log(" 4. Run: fossbook deploy");
203
+ }
204
+ }
205
+
206
+ function initGitHub(cwd) {
207
+ // Check if gh CLI is available
208
+ try {
209
+ execSync("gh --version", { stdio: "ignore" });
210
+ } catch {
211
+ console.error(
212
+ "Error: GitHub CLI (gh) is not installed.\n" +
213
+ "Install it: https://cli.github.com/\n" +
214
+ " Linux: sudo apt install gh\n" +
215
+ " macOS: brew install gh\n" +
216
+ " Windows: winget install GitHub.cli"
217
+ );
218
+ process.exit(1);
219
+ }
220
+
221
+ // Check if gh is authenticated
222
+ try {
223
+ execSync("gh auth status", { stdio: "ignore" });
224
+ } catch {
225
+ console.error("Error: GitHub CLI is not authenticated. Run: gh auth login");
226
+ process.exit(1);
227
+ }
228
+
229
+ // Initialize git repo if needed
230
+ if (!fs.existsSync(path.join(cwd, ".git"))) {
231
+ console.log("Initializing git repository...");
232
+ execSync("git init", { cwd, stdio: "inherit" });
233
+ execSync("git branch -M main", { cwd, stdio: "inherit" });
234
+ }
235
+
236
+ // Create GitHub repository
237
+ const repoName = path.basename(cwd);
238
+ console.log(`Creating GitHub repository: ${repoName}...`);
239
+ try {
240
+ execSync(`gh repo create ${repoName} --public --source=. --remote=origin`, {
241
+ cwd,
242
+ stdio: "inherit",
243
+ });
244
+ } catch {
245
+ // Repo may already exist — ask user what to do
246
+ console.warn(`\nA GitHub repository named "${repoName}" already exists on your account.`);
247
+ const answer = promptSync("Push to the existing repo? (y/n): ");
248
+ if (answer.toLowerCase() !== "y") {
249
+ console.log("Cancelled. Your local site files are still available.");
250
+ return;
251
+ }
252
+ // Ensure the remote is set
253
+ try {
254
+ // Check if origin remote already exists
255
+ execSync("git remote get-url origin", { cwd, stdio: "ignore" });
256
+ } catch {
257
+ // Add origin remote using the authenticated user's repo URL
258
+ try {
259
+ const ghUser = execSync("gh api user --jq .login", {
260
+ cwd,
261
+ encoding: "utf-8",
262
+ stdio: ["pipe", "pipe", "ignore"],
263
+ }).trim();
264
+ const remoteUrl = `git@github.com:${ghUser}/${repoName}.git`;
265
+ execSync(`git remote add origin ${remoteUrl}`, { cwd, stdio: "inherit" });
266
+ console.log(`Added remote: ${remoteUrl}`);
267
+ } catch {
268
+ console.warn("Warning: Could not add git remote. Add it manually with:");
269
+ console.warn(` git remote add origin git@github.com:<user>/${repoName}.git`);
270
+ }
271
+ }
272
+ }
273
+
274
+ // Initial commit and push
275
+ console.log("Committing and pushing to GitHub...");
276
+ execSync("git add -A", { cwd, stdio: "inherit" });
277
+ try {
278
+ execSync('git commit -m "Initial fossbook site"', { cwd, stdio: "inherit" });
279
+ } catch {
280
+ // Nothing to commit
281
+ }
282
+ try {
283
+ execSync("git push -u origin main", { cwd, stdio: "inherit" });
284
+ } catch (e) {
285
+ console.warn("Warning: Could not push to origin. You may need to push manually.");
286
+ }
287
+
288
+ console.log("\nGitHub repository created and pushed!");
289
+ console.log("Enable GitHub Pages in your repo settings:");
290
+ console.log(" Settings → Pages → Source → GitHub Actions");
291
+ }
292
+
293
+ /**
294
+ * Synchronous prompt that reads a single line from stdin.
295
+ */
296
+ function promptSync(question) {
297
+ const buf = Buffer.alloc(256);
298
+ process.stdout.write(question);
299
+ const fd = fs.openSync("/dev/tty", "r");
300
+ let str = "";
301
+ // Read one byte at a time until newline
302
+ while (true) {
303
+ const bytesRead = fs.readSync(fd, buf, 0, 1);
304
+ if (bytesRead === 0) break;
305
+ const char = buf.toString("utf-8", 0, 1);
306
+ if (char === "\n") break;
307
+ str += char;
308
+ }
309
+ fs.closeSync(fd);
310
+ return str.trim();
311
+ }
312
+
313
+ module.exports = { initProject };