@mastrojs/create-mastro 0.0.8 → 0.1.0

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 +1 -1
  2. package/index.js +196 -34
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@ Create a new [Mastro](https://mastrojs.github.io/) project. Usage:
2
2
 
3
3
  Deno:
4
4
 
5
- deno run -A npm:@mastrojs/create-mastro@latest
5
+ deno run --reload -A npm:@mastrojs/create-mastro
6
6
 
7
7
  Node.js
8
8
 
package/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  //@ts-check
4
3
 
5
4
  /**
@@ -15,15 +14,87 @@ import { createWriteStream } from "node:fs";
15
14
  import fs from "node:fs/promises";
16
15
  import { join } from "node:path";
17
16
  import { stdin, stdout } from "node:process";
17
+ import readline from "node:readline";
18
18
  import { createInterface } from "node:readline/promises";
19
19
  import { Readable } from "node:stream";
20
20
 
21
+ /**
22
+ * Constants
23
+ */
24
+
21
25
  const userAgent = process.env.npm_config_user_agent;
26
+ const runtime = (() => {
27
+ if (typeof Deno === "object") {
28
+ return "deno"
29
+ } else if (userAgent?.startsWith("bun/")) {
30
+ // the usual ways to detect Bun don't appear to work in `bun create`
31
+ return "bun";
32
+ } else if (process.argv[2] === "--cloudflare") {
33
+ return "cloudflare";
34
+ } else {
35
+ return "node";
36
+ }
37
+ })();
38
+ const packageManager = (() => {
39
+ if (runtime === "deno") return "deno";
40
+ switch (userAgent?.split("/")[0]) {
41
+ case "pnpm": return "pnpm";
42
+ case "yarn": return "yarn";
43
+ case "bun": return "bun";
44
+ default: return "npm";
45
+ }
46
+ })();
47
+
48
+
49
+ /**
50
+ * Helper Functions
51
+ */
52
+
53
+ /**
54
+ * @template {string} T
55
+ * @param {string} question
56
+ * @param {T[]} options
57
+ * @returns {Promise<T>}
58
+ */
59
+ const select = async (question, options) =>
60
+ new Promise(resolve => {
61
+ let index = 0;
62
+
63
+ const render = () => {
64
+ console.clear();
65
+ console.log(question);
66
+ options.forEach((opt, i) => {
67
+ console.log(i === index ? `● ${opt}` : `\x1b[2m○ ${opt}\x1b[0m`);
68
+ });
69
+ }
70
+ render();
71
+
72
+ process.stdin.on("keypress", (_, key) => {
73
+ switch (key.name) {
74
+ case "c": {
75
+ if (key.ctrl) {
76
+ console.clear();
77
+ process.exit();
78
+ }
79
+ return;
80
+ }
81
+ case "up": {
82
+ index = (index - 1 + options.length) % options.length;
83
+ return render();
84
+ }
85
+ case "down": {
86
+ index = (index + 1) % options.length;
87
+ return render();
88
+ }
89
+ case "return": {
90
+ console.clear();
91
+ process.stdin.removeAllListeners("keypress");
92
+ return resolve(options[index]);
93
+ }
94
+ }
95
+ });
96
+ });
22
97
 
23
- const runtime = typeof Deno === "object"
24
- ? "deno"
25
- // the usual ways to detect Bun don't appear to work in `bun create`
26
- : (userAgent?.startsWith("bun/") ? "bun" : "node");
27
98
 
28
99
  /**
29
100
  * @param {string} path
@@ -58,19 +129,12 @@ const execCmd = (cmd) =>
58
129
  }))
59
130
  );
60
131
 
61
- const repoName = `template-basic-${runtime}`;
62
- const repoUrl = `https://github.com/mastrojs/${repoName}/archive/refs/heads/main.zip`;
63
- const zipFilePromise = fetch(repoUrl);
64
-
65
- const rl = createInterface({ input: stdin, output: stdout, crlfDelay: Infinity });
66
- const dir = await rl.question("What folder should we create for your new project?\n");
67
- rl.close();
68
- stdin.destroy();
69
-
70
- if (dir) {
71
- const outDir = repoName + "-main"; // this cannot be changed and is determined by the zip file
72
- const zipFileName = outDir + ".zip";
73
- const res = await zipFilePromise;
132
+ /**
133
+ * @param { {fetchZipPromise: Promise<Response>; zipFileName: string } } opts
134
+ */
135
+ const unzip = async (opts) => {
136
+ const { fetchZipPromise, zipFileName } = opts;
137
+ const res = await fetchZipPromise;
74
138
  if (res.ok && res.body) {
75
139
  await writeFile(zipFileName, res.body);
76
140
  }
@@ -84,30 +148,125 @@ if (dir) {
84
148
  }
85
149
  await fs.rm(zipFileName, { force: true, recursive: true });
86
150
 
87
- if (unzipSuccess) {
88
- await fs.rename(outDir, dir);
151
+ if (!unzipSuccess) {
152
+ process.exit(-1);
153
+ }
154
+ }
155
+
156
+ /**
157
+ * @param { string } dir
158
+ * @param { (dependencies: Record<string, string>) => void } cb
159
+ */
160
+ const updateDeps = async (dir, cb) => {
161
+ const path = join(dir, runtime === "deno" ? "deno.json" : "package.json");
162
+ const json = JSON.parse(await fs.readFile(path, { encoding: "utf8" }));
163
+ cb(json[runtime === "deno" ? "imports" : "dependencies"]);
164
+ await fs.writeFile(path, JSON.stringify(json, null, 2));
165
+ }
89
166
 
90
- const packageManager = (() => {
91
- switch (userAgent?.split("/")[0]) {
92
- case "pnpm": return "pnpm";
93
- case "yarn": return "yarn";
94
- case "bun": return "bun";
95
- default: return "npm";
96
- }
97
- })();
167
+ /**
168
+ * @param { string } dir
169
+ */
170
+ const addSveltiaFiles = async (dir) => {
171
+ const sveltiaConfig = `# yaml-language-server: $schema=https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json
172
+
173
+ backend:
174
+ name: github
175
+ repo: user/repo
176
+
177
+ media_folder: /routes/media
178
+ public_folder: /media
179
+
180
+ collections:
181
+ - name: posts
182
+ label: Posts
183
+ folder: /data/posts
184
+ fields:
185
+ - { label: Title, name: title, widget: string }
186
+ - { label: Body, name: body, widget: markdown }
187
+ `;
188
+
189
+ const sveltiaIndexHtml = `<!DOCTYPE html>
190
+ <html>
191
+ <head>
192
+ <meta charset="utf-8" />
193
+ <meta name="robots" content="noindex" />
194
+ <title>Sveltia CMS</title>
195
+ </head>
196
+ <body>
197
+ <!--
198
+ You can also pin a version like https://unpkg.com/@sveltia/cms@0.129.2/dist/sveltia-cms.js
199
+ or innstead of loading from unpkg.com, download the file into your routes folder.
200
+ -->
201
+ <script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
202
+ </body>
203
+ </html>
204
+ `;
205
+ await fs.mkdir(join(dir, "routes", "admin"));
206
+ await fs.writeFile(join(dir, "routes", "admin", "config.yml"), sveltiaConfig);
207
+ await fs.writeFile(join(dir, "routes", "admin", "index.html"), sveltiaIndexHtml);
208
+ }
209
+
210
+ /**
211
+ * Main function
212
+ */
213
+ const main = async () => {
214
+ const repoName = `template-basic-${runtime}`;
215
+ const fetchZipPromise = fetch(`https://github.com/mastrojs/${repoName}/archive/refs/heads/main.zip`);
216
+
217
+ const rl = createInterface({ input: stdin, output: stdout, crlfDelay: Infinity });
218
+ const dir = await rl.question("What name should we use for your new project folder?\n");
219
+ if (dir) {
220
+ readline.emitKeypressEvents(process.stdin);
221
+ process.stdin.setRawMode(true);
222
+
223
+ const template = await select("Which template do you want to start with?", ["basic", "blog"]);
224
+ const templateFetchZipPromise = template === "basic"
225
+ ? undefined
226
+ : fetch(`https://github.com/mastrojs/mastro/archive/refs/heads/main.zip`);
227
+
228
+ const addSveltia = await select(
229
+ "Do you want to add a git-based CMS? This adds a routes/admin/ folder.",
230
+ ["No", "Add Sveltia CMS"],
231
+ ) === "Add Sveltia CMS";
232
+
233
+ const zipOutDir = repoName + "-main"; // cannot be changed and is determined by the zip file
234
+ await unzip({ fetchZipPromise, zipFileName: zipOutDir + ".zip" });
235
+ await fs.rename(zipOutDir, dir);
98
236
 
99
237
  if (packageManager === "npm") {
100
238
  try {
101
- const path = join(dir, "package.json");
102
- const packageJson = JSON.parse(await fs.readFile(path, { encoding: "utf8" }));
103
- packageJson.dependencies["@mastrojs/mastro"] = "npm:@jsr/mastrojs__mastro@^0";
104
- await fs.writeFile(path, JSON.stringify(packageJson, null, 2));
239
+ await updateDeps(dir, dependencies => {
240
+ dependencies["@mastrojs/mastro"] = "npm:@jsr/mastrojs__mastro@^0";
241
+ });
105
242
  await fs.writeFile(join(dir, ".npmrc"), "@jsr:registry=https://npm.jsr.io");
106
243
  } catch (e) {
107
244
  console.error(`Created folder ${dir} but failed to patch it for npm. Please use pnpm instead.`);
108
245
  }
109
246
  }
110
247
 
248
+ if (templateFetchZipPromise) {
249
+ // Update dir with things from @mastrojs/mastro's `examples/blog/` folder.
250
+ const templateOutDir = "mastro-main"; // determined by zip file
251
+ await unzip({ fetchZipPromise: templateFetchZipPromise, zipFileName: templateOutDir + ".zip" });
252
+
253
+ await Promise.all(["components", "data", "routes"].map(async folder => {
254
+ await fs.rm(join(dir, folder), { recursive: true, force: true });
255
+ return fs.rename(join(templateOutDir, "examples", "blog", folder), join(dir, folder));
256
+ }));
257
+ await updateDeps(dir, deps => {
258
+ deps["@mastrojs/markdown"] = packageManager === "npm"
259
+ ? "npm:@jsr/mastrojs__markdown@^0"
260
+ : "jsr:@mastrojs/markdown@^0";
261
+ });
262
+
263
+ await fs.rm(templateOutDir, { recursive: true });
264
+
265
+ if (addSveltia) {
266
+ await addSveltiaFiles(dir);
267
+ }
268
+ }
269
+
111
270
  const installInstr = runtime === "deno"
112
271
  ? ""
113
272
  : `\n\nThen install dependencies with: ${packageManager} install\n`;
@@ -116,8 +275,7 @@ if (dir) {
116
275
  : `${packageManager} run start`;
117
276
 
118
277
  const codeStyle = "color: blue";
119
- console.log(
120
- `
278
+ console.log(`
121
279
  Success!
122
280
 
123
281
  Enter the newly created folder with: %ccd ${dir}${installInstr}
@@ -126,5 +284,9 @@ Enter the newly created folder with: %ccd ${dir}${installInstr}
126
284
  "",
127
285
  codeStyle,
128
286
  );
287
+
288
+ rl.close();
289
+ stdin.destroy();
129
290
  }
130
291
  }
292
+ await main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastrojs/create-mastro",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "npm-publish": "deno check && npm publish --access public"