create-stardrive 1.0.2 → 1.2.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.
- package/README.md +4 -4
- package/bin/index.js +398 -250
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -9,25 +9,25 @@ It helps you getting Stardrive installed and also auto-adjusts some details for
|
|
|
9
9
|
Simply run:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm create stardrive
|
|
12
|
+
npm create stardrive@latest
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
*or*
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
pnpm create stardrive
|
|
18
|
+
pnpm create stardrive@latest
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
*or*
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
yarn create stardrive
|
|
24
|
+
yarn create stardrive@latest
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
*or*
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
|
-
bun create stardrive
|
|
30
|
+
bun create stardrive@latest
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
<br />
|
package/bin/index.js
CHANGED
|
@@ -1,148 +1,347 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import path4 from "node:path";
|
|
5
|
+
|
|
6
|
+
// src/args.ts
|
|
7
|
+
function parseArgs(argv) {
|
|
8
|
+
const args = argv.slice(2);
|
|
9
|
+
const skipInstall2 = args.includes("--no-install");
|
|
10
|
+
const parseFlag = (name) => {
|
|
11
|
+
const eqIndex = args.findIndex((a) => a.startsWith(`${name}=`));
|
|
12
|
+
if (eqIndex !== -1) {
|
|
13
|
+
return args[eqIndex].slice(name.length + 1);
|
|
14
|
+
}
|
|
15
|
+
const i = args.indexOf(name);
|
|
16
|
+
const next = args[i + 1];
|
|
17
|
+
if (i !== -1 && next && !next.startsWith("-")) {
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
return void 0;
|
|
21
|
+
};
|
|
22
|
+
const requestedVersion2 = parseFlag("--version") ?? parseFlag("-v");
|
|
23
|
+
const positional2 = args.filter((a, i) => {
|
|
24
|
+
if (a.startsWith("-")) return false;
|
|
25
|
+
const prev = args[i - 1];
|
|
26
|
+
if (prev === "--version" || prev === "-v") return false;
|
|
27
|
+
return true;
|
|
28
|
+
});
|
|
29
|
+
return { positional: positional2, requestedVersion: requestedVersion2, skipInstall: skipInstall2 };
|
|
30
|
+
}
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
// src/logger.ts
|
|
33
|
+
import { stdout as output } from "node:process";
|
|
34
|
+
var supportsColor = output.isTTY && process.env["TERM"] !== "dumb" && !process.env["NO_COLOR"];
|
|
35
|
+
var paint = (code, text) => supportsColor ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
36
|
+
var c = {
|
|
25
37
|
dim: (t) => paint("2", t),
|
|
26
38
|
bold: (t) => paint("1", t),
|
|
27
39
|
cyan: (t) => paint("36", t),
|
|
28
40
|
magenta: (t) => paint("35", t),
|
|
29
41
|
green: (t) => paint("32", t),
|
|
30
42
|
yellow: (t) => paint("33", t),
|
|
31
|
-
red: (t) => paint("31", t)
|
|
43
|
+
red: (t) => paint("31", t)
|
|
32
44
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
${c.magenta("★")} ${c.bold(c.cyan("Stardrive Launchpad"))} ${c.magenta("★")}
|
|
45
|
+
var banner = `
|
|
46
|
+
${c.magenta("\u2605")} ${c.bold(c.cyan("Stardrive Launchpad"))} ${c.magenta("\u2605")}
|
|
36
47
|
${c.dim("Fueling your next Astro project...")}
|
|
37
48
|
`;
|
|
49
|
+
var stepNum = 0;
|
|
50
|
+
var step = (msg) => {
|
|
51
|
+
console.log(`
|
|
52
|
+
${c.cyan(`[${++stepNum}]`)} ${c.bold(msg)}`);
|
|
53
|
+
};
|
|
54
|
+
var info = (msg) => {
|
|
55
|
+
console.log(` ${c.dim(msg)}`);
|
|
56
|
+
};
|
|
57
|
+
var ok = (msg) => {
|
|
58
|
+
console.log(` ${c.green("\u2713")} ${msg}`);
|
|
59
|
+
};
|
|
60
|
+
var fail = (msg) => {
|
|
61
|
+
console.error(`
|
|
62
|
+
${c.red("\u2717")} ${msg}
|
|
63
|
+
`);
|
|
64
|
+
};
|
|
38
65
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
console.log(`\n${c.cyan(`[${++stepNum}]`)} ${c.bold(msg)}`);
|
|
42
|
-
const info = (msg) => console.log(` ${c.dim(msg)}`);
|
|
43
|
-
const ok = (msg) => console.log(` ${c.green("✓")} ${msg}`);
|
|
44
|
-
const fail = (msg) => console.error(`\n${c.red("✗")} ${msg}\n`);
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
// Argument parsing
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
function parseFlag(name) {
|
|
51
|
-
const eqIndex = args.findIndex((a) => a.startsWith(`${name}=`));
|
|
52
|
-
|
|
53
|
-
if (eqIndex !== -1) {
|
|
54
|
-
return args[eqIndex].slice(name.length + 1);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const i = args.indexOf(name);
|
|
58
|
-
|
|
59
|
-
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
60
|
-
return args[i + 1];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const requestedVersion = parseFlag("--version") || parseFlag("-v");
|
|
67
|
-
|
|
68
|
-
const positional = args.filter((a, i) => {
|
|
69
|
-
if (a.startsWith("-")) return false;
|
|
70
|
-
const prev = args[i - 1];
|
|
71
|
-
if (prev === "--version" || prev === "-v") return false;
|
|
72
|
-
return true;
|
|
73
|
-
});
|
|
74
|
-
|
|
66
|
+
// src/package-manager.ts
|
|
67
|
+
import { spawnSync } from "node:child_process";
|
|
75
68
|
function detectPackageManager() {
|
|
76
|
-
const ua = process.env
|
|
77
|
-
|
|
69
|
+
const ua = process.env["npm_config_user_agent"] ?? "";
|
|
78
70
|
if (ua.startsWith("pnpm")) return "pnpm";
|
|
79
71
|
if (ua.startsWith("yarn")) return "yarn";
|
|
80
72
|
if (ua.startsWith("bun")) return "bun";
|
|
81
|
-
|
|
82
73
|
return "npm";
|
|
83
74
|
}
|
|
84
|
-
|
|
85
75
|
function commandExists(cmd) {
|
|
86
76
|
return spawnSync(cmd, ["--version"], {
|
|
87
|
-
stdio: "ignore"
|
|
77
|
+
stdio: "ignore"
|
|
88
78
|
}).status === 0;
|
|
89
79
|
}
|
|
80
|
+
var devCommands = {
|
|
81
|
+
npm: "npm run dev",
|
|
82
|
+
pnpm: "pnpm dev",
|
|
83
|
+
yarn: "yarn dev",
|
|
84
|
+
bun: "bun run dev"
|
|
85
|
+
};
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
// src/features.ts
|
|
88
|
+
import fs from "node:fs";
|
|
89
|
+
import path from "node:path";
|
|
90
|
+
import readline from "node:readline/promises";
|
|
91
|
+
import { stdin as input, stdout as output2 } from "node:process";
|
|
92
|
+
function removePaths(targetDir2, entries) {
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
fs.rmSync(path.join(targetDir2, entry), {
|
|
95
|
+
recursive: true,
|
|
96
|
+
force: true
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function editFile(targetDir2, relativePath, transform) {
|
|
101
|
+
const filePath = path.join(targetDir2, relativePath);
|
|
102
|
+
if (!fs.existsSync(filePath)) return;
|
|
103
|
+
const before = fs.readFileSync(filePath, "utf8");
|
|
104
|
+
const after = transform(before);
|
|
105
|
+
if (after !== before) fs.writeFileSync(filePath, after);
|
|
106
|
+
}
|
|
107
|
+
function removeLines(source, pattern) {
|
|
108
|
+
return source.split("\n").filter((line) => !pattern.test(line)).join("\n");
|
|
109
|
+
}
|
|
110
|
+
function removeBraceBlock(source, startPattern) {
|
|
111
|
+
const match = startPattern.exec(source);
|
|
112
|
+
if (!match) return source;
|
|
113
|
+
const start = match.index;
|
|
114
|
+
let i = source.indexOf("{", start);
|
|
115
|
+
if (i === -1) return source;
|
|
116
|
+
let depth = 0;
|
|
117
|
+
let end = -1;
|
|
118
|
+
for (; i < source.length; i++) {
|
|
119
|
+
const ch = source[i];
|
|
120
|
+
if (ch === "{") depth++;
|
|
121
|
+
else if (ch === "}") {
|
|
122
|
+
depth--;
|
|
123
|
+
if (depth === 0) {
|
|
124
|
+
end = i;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (end === -1) return source;
|
|
130
|
+
let after = end + 1;
|
|
131
|
+
while (after < source.length && /[);,]/.test(source[after])) after++;
|
|
132
|
+
if (source[after] === "\n") after++;
|
|
133
|
+
let head = start;
|
|
134
|
+
while (head > 0 && /[ \t]/.test(source[head - 1])) head--;
|
|
135
|
+
return source.slice(0, head) + source.slice(after);
|
|
136
|
+
}
|
|
137
|
+
function removeCollectionFromExport(source, name) {
|
|
138
|
+
return source.replace(
|
|
139
|
+
/(export const collections\s*=\s*\{)([^}]*)(\};?)/,
|
|
140
|
+
(_full, open, body, close) => {
|
|
141
|
+
const items = body.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0 && entry !== name);
|
|
142
|
+
return `${open} ${items.join(", ")} ${close}`;
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
function removeBlog(targetDir2) {
|
|
147
|
+
removePaths(targetDir2, [
|
|
148
|
+
"src/utils/blog.ts",
|
|
149
|
+
"src/utils/reading-time.ts",
|
|
150
|
+
"src/styles/blog.css",
|
|
151
|
+
"src/pages/rss.xml.js",
|
|
152
|
+
"src/pages/[lang]/rss.xml.js",
|
|
153
|
+
"src/pages/blog",
|
|
154
|
+
"src/pages/[lang]/blog",
|
|
155
|
+
"src/layouts/article.astro",
|
|
156
|
+
"src/images/content/articles-fallback.jpg",
|
|
157
|
+
"src/images/content/articles",
|
|
158
|
+
"src/content/articles",
|
|
159
|
+
"src/components/structured/article.astro",
|
|
160
|
+
"src/components/blog",
|
|
161
|
+
"scripts/processSocialImages.js",
|
|
162
|
+
"public/data/articles"
|
|
163
|
+
]);
|
|
164
|
+
editFile(
|
|
165
|
+
targetDir2,
|
|
166
|
+
"scripts/postbuild.js",
|
|
167
|
+
(content) => removeLines(content, /await import\(['"]\.\/processSocialImages\.js['"]\);/)
|
|
168
|
+
);
|
|
169
|
+
editFile(targetDir2, "src/content.config.ts", (content) => {
|
|
170
|
+
const withoutDecl = removeBraceBlock(content, /const articles = defineCollection\(/);
|
|
171
|
+
return removeCollectionFromExport(withoutDecl, "articles");
|
|
172
|
+
});
|
|
173
|
+
editFile(targetDir2, "theme.config.ts", (content) => {
|
|
174
|
+
const withoutSection = removeBraceBlock(content, /^[ \t]*articles:\s*\{/m);
|
|
175
|
+
const withoutComment = removeLines(withoutSection, /^\s*\/\/ content\/article settings\s*$/);
|
|
176
|
+
return removeLines(withoutComment, /^\s*addArticles:/);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function removeFaq(targetDir2) {
|
|
180
|
+
removePaths(targetDir2, [
|
|
181
|
+
"src/content/faq-answers",
|
|
182
|
+
"src/pages/faq.astro",
|
|
183
|
+
"src/pages/[lang]/faq.astro"
|
|
184
|
+
]);
|
|
185
|
+
editFile(targetDir2, "src/content.config.ts", (content) => {
|
|
186
|
+
const withoutDecl = removeBraceBlock(content, /const faq_answers = defineCollection\(/);
|
|
187
|
+
return removeCollectionFromExport(withoutDecl, "faq_answers");
|
|
188
|
+
});
|
|
189
|
+
editFile(
|
|
190
|
+
targetDir2,
|
|
191
|
+
"theme.config.ts",
|
|
192
|
+
(content) => removeLines(content, /^\s*addFAQ:/)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
function removeIntegration(targetDir2) {
|
|
196
|
+
removePaths(targetDir2, [
|
|
197
|
+
"src/images/content/integration",
|
|
198
|
+
"src/content/integration-options",
|
|
199
|
+
"src/pages/integration",
|
|
200
|
+
"src/pages/[lang]/integration",
|
|
201
|
+
"src/components/integration-list.astro"
|
|
202
|
+
]);
|
|
203
|
+
editFile(targetDir2, "src/content.config.ts", (content) => {
|
|
204
|
+
const withoutDecl = removeBraceBlock(
|
|
205
|
+
content,
|
|
206
|
+
/const integration_options = defineCollection\(/
|
|
207
|
+
);
|
|
208
|
+
return removeCollectionFromExport(withoutDecl, "integration_options");
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function cleanupNav(targetDir2, removed) {
|
|
212
|
+
const routes = [
|
|
213
|
+
removed.blog ? "blog" : null,
|
|
214
|
+
removed.faq ? "faq" : null,
|
|
215
|
+
removed.integration ? "integration" : null
|
|
216
|
+
].filter((route) => route !== null);
|
|
217
|
+
if (routes.length === 0) return;
|
|
218
|
+
const pattern = new RegExp(
|
|
219
|
+
`getLocaleUrl\\(['"](?:${routes.join("|")})['"]`
|
|
220
|
+
);
|
|
221
|
+
editFile(
|
|
222
|
+
targetDir2,
|
|
223
|
+
"src/components/layout/nav/footer-nav.astro",
|
|
224
|
+
(content) => removeLines(content, pattern)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
function removeCloudflare(targetDir2) {
|
|
228
|
+
removePaths(targetDir2, [
|
|
229
|
+
"scripts/purgeCloudflareCache.js",
|
|
230
|
+
"worker-configuration.d.ts",
|
|
231
|
+
"wrangler.jsonc",
|
|
232
|
+
"public/_headers"
|
|
233
|
+
]);
|
|
234
|
+
editFile(targetDir2, "astro.config.ts", (content) => {
|
|
235
|
+
const withoutImport = removeLines(
|
|
236
|
+
content,
|
|
237
|
+
/^import cloudflare from ['"]@astrojs\/cloudflare['"];/
|
|
238
|
+
);
|
|
239
|
+
return removeLines(withoutImport, /^\s*adapter: cloudflare\(/);
|
|
240
|
+
});
|
|
241
|
+
const packageJsonPath = path.join(targetDir2, "package.json");
|
|
242
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
243
|
+
const pkg = JSON.parse(
|
|
244
|
+
fs.readFileSync(packageJsonPath, "utf8")
|
|
245
|
+
);
|
|
246
|
+
if (pkg.scripts) delete pkg.scripts["purge:cloudflare"];
|
|
247
|
+
if (pkg.dependencies) {
|
|
248
|
+
delete pkg.dependencies["@astrojs/cloudflare"];
|
|
249
|
+
delete pkg.dependencies["wrangler"];
|
|
250
|
+
}
|
|
251
|
+
if (pkg.devDependencies) {
|
|
252
|
+
delete pkg.devDependencies["@astrojs/cloudflare"];
|
|
253
|
+
delete pkg.devDependencies["wrangler"];
|
|
254
|
+
}
|
|
255
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function confirm(rl, question, defaultYes = true) {
|
|
259
|
+
const hint = defaultYes ? "(Y/n)" : "(y/N)";
|
|
260
|
+
const answer = (await rl.question(`${c.cyan("?")} ${c.bold(question)} ${c.dim(`${hint} `)}`)).trim().toLowerCase();
|
|
261
|
+
if (!answer) return defaultYes;
|
|
262
|
+
return answer === "y" || answer === "yes";
|
|
263
|
+
}
|
|
264
|
+
async function configureFeatures(targetDir2) {
|
|
265
|
+
step("Configuring optional features");
|
|
266
|
+
if (!input.isTTY) {
|
|
267
|
+
info("Non-interactive shell detected; keeping all features.");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const rl = readline.createInterface({ input, output: output2 });
|
|
271
|
+
try {
|
|
272
|
+
const keepBlog = await confirm(rl, "Keep the blog feature?");
|
|
273
|
+
if (!keepBlog) {
|
|
274
|
+
removeBlog(targetDir2);
|
|
275
|
+
ok("Removed the blog feature");
|
|
276
|
+
}
|
|
277
|
+
const keepFaq = await confirm(rl, "Keep the FAQ feature?");
|
|
278
|
+
if (!keepFaq) {
|
|
279
|
+
removeFaq(targetDir2);
|
|
280
|
+
ok("Removed the FAQ feature");
|
|
281
|
+
}
|
|
282
|
+
const keepIntegration = await confirm(rl, "Keep the integration catalog?");
|
|
283
|
+
if (!keepIntegration) {
|
|
284
|
+
removeIntegration(targetDir2);
|
|
285
|
+
ok("Removed the integration catalog");
|
|
286
|
+
}
|
|
287
|
+
cleanupNav(targetDir2, {
|
|
288
|
+
blog: !keepBlog,
|
|
289
|
+
faq: !keepFaq,
|
|
290
|
+
integration: !keepIntegration
|
|
291
|
+
});
|
|
292
|
+
const useCloudflare = await confirm(
|
|
293
|
+
rl,
|
|
294
|
+
"Will you host on Cloudflare Workers?"
|
|
295
|
+
);
|
|
296
|
+
if (!useCloudflare) {
|
|
297
|
+
removeCloudflare(targetDir2);
|
|
298
|
+
ok("Removed Cloudflare-specific setup");
|
|
299
|
+
}
|
|
300
|
+
} finally {
|
|
301
|
+
rl.close();
|
|
302
|
+
}
|
|
96
303
|
}
|
|
97
304
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
305
|
+
// src/project-name.ts
|
|
306
|
+
import fs2 from "node:fs";
|
|
307
|
+
import path2 from "node:path";
|
|
308
|
+
import readline2 from "node:readline/promises";
|
|
309
|
+
import { stdin as input2, stdout as output3 } from "node:process";
|
|
102
310
|
function isValidProjectName(name) {
|
|
103
311
|
return /^[a-z0-9._-]+$/i.test(name) && !/^[._]/.test(name);
|
|
104
312
|
}
|
|
105
|
-
|
|
106
313
|
async function askProjectName(initial) {
|
|
107
314
|
if (initial && isValidProjectName(initial)) {
|
|
108
|
-
const
|
|
109
|
-
if (!
|
|
315
|
+
const targetDir2 = path2.resolve(initial);
|
|
316
|
+
if (!fs2.existsSync(targetDir2)) return initial;
|
|
110
317
|
fail(`Directory "${initial}" already exists.`);
|
|
111
318
|
}
|
|
112
|
-
|
|
113
|
-
if (!input.isTTY) {
|
|
319
|
+
if (!input2.isTTY) {
|
|
114
320
|
fail(
|
|
115
|
-
`Project name "${initial
|
|
321
|
+
`Project name "${initial ?? ""}" is missing or invalid and stdin is not interactive.`
|
|
116
322
|
);
|
|
117
323
|
process.exit(1);
|
|
118
324
|
}
|
|
119
|
-
|
|
120
|
-
const rl = readline.createInterface({ input, output });
|
|
121
|
-
|
|
325
|
+
const rl = readline2.createInterface({ input: input2, output: output3 });
|
|
122
326
|
try {
|
|
123
327
|
while (true) {
|
|
124
|
-
const answer = (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)
|
|
128
|
-
).trim();
|
|
129
|
-
|
|
328
|
+
const answer = (await rl.question(
|
|
329
|
+
`${c.cyan("?")} ${c.bold("Project name:")} ${c.dim("(my-stardrive) ")}`
|
|
330
|
+
)).trim();
|
|
130
331
|
const name = answer || "my-stardrive";
|
|
131
|
-
|
|
132
332
|
if (!isValidProjectName(name)) {
|
|
133
333
|
console.log(
|
|
134
334
|
` ${c.yellow("!")} Use letters, digits, dots, dashes or underscores.`
|
|
135
335
|
);
|
|
136
336
|
continue;
|
|
137
337
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
338
|
+
const targetDir2 = path2.resolve(name);
|
|
339
|
+
if (fs2.existsSync(targetDir2)) {
|
|
340
|
+
console.log(
|
|
341
|
+
` ${c.yellow("!")} "${name}" already exists. Try another.`
|
|
342
|
+
);
|
|
143
343
|
continue;
|
|
144
344
|
}
|
|
145
|
-
|
|
146
345
|
return name;
|
|
147
346
|
}
|
|
148
347
|
} finally {
|
|
@@ -150,196 +349,145 @@ async function askProjectName(initial) {
|
|
|
150
349
|
}
|
|
151
350
|
}
|
|
152
351
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
// Resolve which tag to clone
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
const repo = "https://github.com/Peltmonger/stardrive.git";
|
|
163
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "create-app-"));
|
|
164
|
-
|
|
165
|
-
function normalizeTag(tag) {
|
|
166
|
-
return tag.startsWith("v") ? tag : `v${tag}`;
|
|
352
|
+
// src/release.ts
|
|
353
|
+
import { execSync } from "node:child_process";
|
|
354
|
+
var STARDRIVE_REPO = "https://github.com/Peltmonger/stardrive.git";
|
|
355
|
+
function normalizeTag(tag2) {
|
|
356
|
+
return tag2.startsWith("v") ? tag2 : `v${tag2}`;
|
|
167
357
|
}
|
|
168
|
-
|
|
169
358
|
function compareSemver(a, b) {
|
|
170
359
|
const pa = a.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
171
360
|
const pb = b.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
172
|
-
|
|
173
361
|
for (let i = 0; i < 3; i++) {
|
|
174
|
-
const diff = (pa[i]
|
|
362
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
175
363
|
if (diff !== 0) return diff;
|
|
176
364
|
}
|
|
177
|
-
|
|
178
365
|
return 0;
|
|
179
366
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
encoding: "utf8",
|
|
367
|
+
function resolveTag(requestedVersion2) {
|
|
368
|
+
const output4 = execSync(`git ls-remote --tags --refs ${STARDRIVE_REPO}`, {
|
|
369
|
+
encoding: "utf8"
|
|
184
370
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
.map((line) => line.split("refs/tags/")[1])
|
|
189
|
-
.filter(Boolean);
|
|
190
|
-
|
|
191
|
-
if (requestedVersion) {
|
|
192
|
-
const wanted = normalizeTag(requestedVersion);
|
|
193
|
-
|
|
371
|
+
const tags = output4.split("\n").map((line) => line.split("refs/tags/")[1]).filter((t) => Boolean(t));
|
|
372
|
+
if (requestedVersion2) {
|
|
373
|
+
const wanted = normalizeTag(requestedVersion2);
|
|
194
374
|
if (!tags.includes(wanted)) {
|
|
195
|
-
fail(`Version "${
|
|
375
|
+
fail(`Version "${requestedVersion2}" not found in ${STARDRIVE_REPO}`);
|
|
196
376
|
process.exit(1);
|
|
197
377
|
}
|
|
198
|
-
|
|
199
378
|
return wanted;
|
|
200
379
|
}
|
|
201
|
-
|
|
202
380
|
const stable = tags.filter((t) => /^v?\d+\.\d+\.\d+$/.test(t));
|
|
203
|
-
|
|
204
381
|
if (stable.length === 0) {
|
|
205
|
-
fail(`No tagged releases found in ${
|
|
382
|
+
fail(`No tagged releases found in ${STARDRIVE_REPO}`);
|
|
206
383
|
process.exit(1);
|
|
207
384
|
}
|
|
208
|
-
|
|
209
385
|
return stable.sort(compareSemver).pop();
|
|
210
386
|
}
|
|
211
387
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
step(`Beaming ${c.bold(tag)} down from orbit`);
|
|
221
|
-
info(repo);
|
|
222
|
-
|
|
223
|
-
execSync(
|
|
224
|
-
`git -c advice.detachedHead=false clone --depth=1 --branch ${tag} --quiet ${repo} "${tempDir}"`,
|
|
225
|
-
{
|
|
226
|
-
stdio: ["ignore", "ignore", "inherit"],
|
|
227
|
-
}
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
fs.rmSync(path.join(tempDir, ".git"), {
|
|
231
|
-
recursive: true,
|
|
232
|
-
force: true,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
fs.cpSync(tempDir, targetDir, {
|
|
236
|
-
recursive: true,
|
|
237
|
-
});
|
|
238
|
-
ok(`Landed in ${c.bold(projectName)}/`);
|
|
239
|
-
|
|
240
|
-
//
|
|
241
|
-
// Trim files not needed in the generated project
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
step("Trimming unused boosters");
|
|
245
|
-
|
|
246
|
-
[
|
|
247
|
-
"scripts/syncVersion.js",
|
|
248
|
-
"SECURITY.md",
|
|
249
|
-
".github",
|
|
250
|
-
].forEach((entry) => {
|
|
251
|
-
fs.rmSync(path.join(targetDir, entry), {
|
|
252
|
-
recursive: true,
|
|
253
|
-
force: true,
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
// Remove all lockfiles (should not be there, but just to be safe)
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
[
|
|
388
|
+
// src/scaffold.ts
|
|
389
|
+
import fs3 from "node:fs";
|
|
390
|
+
import os from "node:os";
|
|
391
|
+
import path3 from "node:path";
|
|
392
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
393
|
+
var TRIM_ENTRIES = ["scripts/syncVersion.js", ".ai/TRIMMING_GUIDE.md", "SECURITY.md", ".github"];
|
|
394
|
+
var STALE_LOCKFILES = [
|
|
262
395
|
"package-lock.json",
|
|
263
396
|
"pnpm-lock.yaml",
|
|
264
397
|
"yarn.lock",
|
|
265
|
-
"bun.lockb"
|
|
266
|
-
]
|
|
267
|
-
|
|
268
|
-
|
|
398
|
+
"bun.lockb"
|
|
399
|
+
];
|
|
400
|
+
function cloneRepo(tag2, targetDir2, projectName2) {
|
|
401
|
+
const tempDir = fs3.mkdtempSync(path3.join(os.tmpdir(), "create-app-"));
|
|
402
|
+
step(`Beaming ${c.bold(tag2)} down from orbit`);
|
|
403
|
+
info(STARDRIVE_REPO);
|
|
404
|
+
execSync2(
|
|
405
|
+
`git -c advice.detachedHead=false clone --depth=1 --branch ${tag2} --quiet ${STARDRIVE_REPO} "${tempDir}"`,
|
|
406
|
+
{
|
|
407
|
+
stdio: ["ignore", "ignore", "inherit"]
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
fs3.rmSync(path3.join(tempDir, ".git"), {
|
|
411
|
+
recursive: true,
|
|
412
|
+
force: true
|
|
269
413
|
});
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// Update package.json
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
step("Calibrating package.json");
|
|
278
|
-
|
|
279
|
-
const packageJsonPath = path.join(targetDir, "package.json");
|
|
280
|
-
|
|
281
|
-
const pkg = JSON.parse(
|
|
282
|
-
fs.readFileSync(packageJsonPath, "utf8")
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
pkg.name = projectName;
|
|
286
|
-
|
|
287
|
-
if (pkg.scripts) {
|
|
288
|
-
delete pkg.scripts["sync-version"];
|
|
289
|
-
delete pkg.scripts.prebuild;
|
|
414
|
+
fs3.cpSync(tempDir, targetDir2, {
|
|
415
|
+
recursive: true
|
|
416
|
+
});
|
|
417
|
+
ok(`Landed in ${c.bold(projectName2)}/`);
|
|
290
418
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
.
|
|
295
|
-
|
|
419
|
+
function trimProject(targetDir2) {
|
|
420
|
+
step("Trimming unused boosters");
|
|
421
|
+
for (const entry of TRIM_ENTRIES) {
|
|
422
|
+
fs3.rmSync(path3.join(targetDir2, entry), {
|
|
423
|
+
recursive: true,
|
|
424
|
+
force: true
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
for (const file of STALE_LOCKFILES) {
|
|
428
|
+
fs3.rmSync(path3.join(targetDir2, file), {
|
|
429
|
+
force: true
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
ok("Stripped extras and stale lockfiles");
|
|
296
433
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
.
|
|
434
|
+
function calibratePackageJson(targetDir2, projectName2, pm2) {
|
|
435
|
+
step("Calibrating package.json");
|
|
436
|
+
const packageJsonPath = path3.join(targetDir2, "package.json");
|
|
437
|
+
const pkg = JSON.parse(
|
|
438
|
+
fs3.readFileSync(packageJsonPath, "utf8")
|
|
439
|
+
);
|
|
440
|
+
pkg.name = projectName2;
|
|
441
|
+
if (pkg.scripts) {
|
|
442
|
+
delete pkg.scripts["sync-version"];
|
|
443
|
+
delete pkg.scripts["prebuild"];
|
|
444
|
+
}
|
|
445
|
+
if (pm2 === "pnpm") {
|
|
446
|
+
pkg.packageManager = `pnpm@${execSync2("pnpm --version").toString().trim()}`;
|
|
447
|
+
}
|
|
448
|
+
if (pm2 === "bun") {
|
|
449
|
+
pkg.packageManager = `bun@${execSync2("bun --version").toString().trim()}`;
|
|
450
|
+
}
|
|
451
|
+
fs3.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
|
|
452
|
+
ok(`Named your ship ${c.bold(projectName2)}`);
|
|
302
453
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (!skipInstall) {
|
|
315
|
-
step(`Loading dependencies with ${c.bold(pm)}`);
|
|
316
|
-
|
|
317
|
-
execSync(`${pm} install`, {
|
|
318
|
-
cwd: targetDir,
|
|
319
|
-
stdio: "inherit",
|
|
454
|
+
function installDependencies(targetDir2, projectName2, pm2, skipInstall2) {
|
|
455
|
+
if (skipInstall2) {
|
|
456
|
+
step("Skipping dependency install");
|
|
457
|
+
info(`Run "${pm2} install" inside ${projectName2}/ when you're ready.`);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
step(`Loading dependencies with ${c.bold(pm2)}`);
|
|
461
|
+
execSync2(`${pm2} install`, {
|
|
462
|
+
cwd: targetDir2,
|
|
463
|
+
stdio: "inherit"
|
|
320
464
|
});
|
|
321
465
|
ok("Engines online");
|
|
322
|
-
} else {
|
|
323
|
-
step("Skipping dependency install");
|
|
324
|
-
info(`Run "${pm} install" inside ${projectName}/ when you're ready.`);
|
|
325
466
|
}
|
|
326
467
|
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
468
|
+
// src/index.ts
|
|
469
|
+
var { positional, requestedVersion, skipInstall } = parseArgs(process.argv);
|
|
470
|
+
var pm = detectPackageManager();
|
|
471
|
+
if (!commandExists(pm)) {
|
|
472
|
+
fail(`${pm} is not installed`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
console.log(banner);
|
|
476
|
+
var projectName = await askProjectName(positional[0]);
|
|
477
|
+
var targetDir = path4.resolve(projectName);
|
|
478
|
+
step("Locating the latest Stardrive release");
|
|
479
|
+
var tag = resolveTag(requestedVersion);
|
|
480
|
+
ok(`Selected ${c.bold(tag)}`);
|
|
481
|
+
cloneRepo(tag, targetDir, projectName);
|
|
482
|
+
trimProject(targetDir);
|
|
483
|
+
calibratePackageJson(targetDir, projectName, pm);
|
|
484
|
+
await configureFeatures(targetDir);
|
|
485
|
+
installDependencies(targetDir, projectName, pm, skipInstall);
|
|
338
486
|
console.log(`
|
|
339
487
|
${c.green("All systems go.")} ${c.dim("Pre-flight checklist complete.")}
|
|
340
488
|
|
|
341
489
|
${c.dim("$")} ${c.cyan(`cd ${projectName}`)}
|
|
342
490
|
${c.dim("$")} ${c.cyan(devCommands[pm])}
|
|
343
491
|
|
|
344
|
-
${c.bold("Happy launching!")} ${c.magenta("
|
|
492
|
+
${c.bold("Happy launching!")} ${c.magenta("\u{1F680}")}
|
|
345
493
|
`);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-stardrive",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "This is the launchpad application to create the Astro Stardrive boilerplate.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-stardrive": "bin/index.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"
|
|
9
|
+
"bin"
|
|
10
10
|
],
|
|
11
11
|
"keywords": [
|
|
12
12
|
"astro",
|
|
@@ -44,6 +44,15 @@
|
|
|
44
44
|
"funding": "https://github.com/peltmonger/stardrive?sponsor=1",
|
|
45
45
|
"type": "module",
|
|
46
46
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "node build.mjs",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^26.0.0",
|
|
55
|
+
"esbuild": "^0.28.1",
|
|
56
|
+
"typescript": "^6.0.3"
|
|
48
57
|
}
|
|
49
58
|
}
|