@guchen_0521/create-temp 0.1.6 → 0.1.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/scaffold.js +177 -0
- package/optional-packages.json +30 -0
- package/package.json +2 -1
package/lib/scaffold.js
CHANGED
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
mkdir,
|
|
4
4
|
mkdtemp,
|
|
5
5
|
readdir,
|
|
6
|
+
readFile,
|
|
6
7
|
rm,
|
|
7
8
|
stat,
|
|
9
|
+
writeFile,
|
|
8
10
|
} from "node:fs/promises";
|
|
9
11
|
import { execFile } from "node:child_process";
|
|
10
12
|
import { promisify } from "node:util";
|
|
@@ -29,6 +31,10 @@ const GIT_SSH_COMMAND =
|
|
|
29
31
|
"ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=6";
|
|
30
32
|
const PROMPT_CANCELLED_MESSAGE = "Prompt cancelled.";
|
|
31
33
|
const PROMPT_BACK_VALUE = "__back";
|
|
34
|
+
const OPTIONAL_PACKAGES_PATH = new URL(
|
|
35
|
+
"../optional-packages.json",
|
|
36
|
+
import.meta.url
|
|
37
|
+
);
|
|
32
38
|
|
|
33
39
|
function assertInteractive() {
|
|
34
40
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -38,6 +44,10 @@ function assertInteractive() {
|
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
function isInteractive() {
|
|
48
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
async function cloneTemplateRepo() {
|
|
42
52
|
const tempDir = await mkdtemp(path.join(os.tmpdir(), "template-cli-"));
|
|
43
53
|
const repoDir = path.join(tempDir, `repo-${Date.now().toString(36)}`);
|
|
@@ -111,6 +121,112 @@ function normalizeTemplateKey(templateKey) {
|
|
|
111
121
|
.replace(/^\.\/+/, "");
|
|
112
122
|
}
|
|
113
123
|
|
|
124
|
+
async function loadOptionalPackageRules() {
|
|
125
|
+
try {
|
|
126
|
+
const raw = await readFile(OPTIONAL_PACKAGES_PATH, "utf8");
|
|
127
|
+
return JSON.parse(raw);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error && error.code === "ENOENT") {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function matchOptionalRule(rule, templateKey) {
|
|
137
|
+
if (!rule || !templateKey) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const normalizedTemplate = normalizeTemplateKey(templateKey);
|
|
142
|
+
|
|
143
|
+
if (rule.match) {
|
|
144
|
+
return normalizeTemplateKey(rule.match) === normalizedTemplate;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (rule.matchPrefix) {
|
|
148
|
+
const normalizedPrefix = normalizeTemplateKey(rule.matchPrefix);
|
|
149
|
+
const prefixWithSlash = normalizedPrefix.endsWith("/")
|
|
150
|
+
? normalizedPrefix
|
|
151
|
+
: `${normalizedPrefix}/`;
|
|
152
|
+
return normalizedTemplate.startsWith(prefixWithSlash);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function collectOptionalPackages(rules, templateKey) {
|
|
159
|
+
if (!rules || !Array.isArray(rules.rules)) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const collected = [];
|
|
164
|
+
const seen = new Set();
|
|
165
|
+
|
|
166
|
+
rules.rules.forEach((rule) => {
|
|
167
|
+
if (!matchOptionalRule(rule, templateKey)) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!Array.isArray(rule.packages)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
rule.packages.forEach((pkg) => {
|
|
176
|
+
if (!pkg || !pkg.name || typeof pkg.name !== "string") {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (seen.has(pkg.name)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
seen.add(pkg.name);
|
|
183
|
+
collected.push({
|
|
184
|
+
name: pkg.name,
|
|
185
|
+
dev: Boolean(pkg.dev),
|
|
186
|
+
version: pkg.version,
|
|
187
|
+
description: pkg.description,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return collected;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function promptOptionalPackages(optionalPackages) {
|
|
196
|
+
if (!isInteractive() || optionalPackages.length === 0) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const choices = optionalPackages.map((pkg) => ({
|
|
201
|
+
title: `${pkg.name}${pkg.dev ? " (dev)" : ""}${
|
|
202
|
+
pkg.description ? ` - ${pkg.description}` : ""
|
|
203
|
+
}`,
|
|
204
|
+
value: pkg.name,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
const response = await prompts(
|
|
208
|
+
{
|
|
209
|
+
type: "multiselect",
|
|
210
|
+
name: "selected",
|
|
211
|
+
message: "Select optional packages:",
|
|
212
|
+
choices,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
onCancel: () => {
|
|
216
|
+
throw new Error(PROMPT_CANCELLED_MESSAGE);
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const selected = Array.isArray(response.selected) ? response.selected : [];
|
|
222
|
+
if (selected.length === 0) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const selectedSet = new Set(selected);
|
|
227
|
+
return optionalPackages.filter((pkg) => selectedSet.has(pkg.name));
|
|
228
|
+
}
|
|
229
|
+
|
|
114
230
|
function buildTemplateTree(templates) {
|
|
115
231
|
const root = { name: "", children: new Map(), template: null };
|
|
116
232
|
templates.forEach((template) => {
|
|
@@ -248,6 +364,56 @@ async function copyDir(sourceDir, targetDir) {
|
|
|
248
364
|
}
|
|
249
365
|
}
|
|
250
366
|
|
|
367
|
+
async function applyOptionalPackages(targetPath, packages) {
|
|
368
|
+
if (!packages || packages.length === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const packageJsonPath = path.join(targetPath, "package.json");
|
|
373
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
374
|
+
const pkgJson = JSON.parse(raw);
|
|
375
|
+
|
|
376
|
+
const hadDependencies = Object.prototype.hasOwnProperty.call(
|
|
377
|
+
pkgJson,
|
|
378
|
+
"dependencies"
|
|
379
|
+
);
|
|
380
|
+
const hadDevDependencies = Object.prototype.hasOwnProperty.call(
|
|
381
|
+
pkgJson,
|
|
382
|
+
"devDependencies"
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
const dependencies = hadDependencies ? { ...pkgJson.dependencies } : {};
|
|
386
|
+
const devDependencies = hadDevDependencies ? { ...pkgJson.devDependencies } : {};
|
|
387
|
+
|
|
388
|
+
let changed = false;
|
|
389
|
+
|
|
390
|
+
packages.forEach((pkg) => {
|
|
391
|
+
if (dependencies[pkg.name] || devDependencies[pkg.name]) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const version = pkg.version || "latest";
|
|
395
|
+
if (pkg.dev) {
|
|
396
|
+
devDependencies[pkg.name] = version;
|
|
397
|
+
} else {
|
|
398
|
+
dependencies[pkg.name] = version;
|
|
399
|
+
}
|
|
400
|
+
changed = true;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
if (!changed) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (hadDependencies || Object.keys(dependencies).length > 0) {
|
|
408
|
+
pkgJson.dependencies = dependencies;
|
|
409
|
+
}
|
|
410
|
+
if (hadDevDependencies || Object.keys(devDependencies).length > 0) {
|
|
411
|
+
pkgJson.devDependencies = devDependencies;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
await writeFile(packageJsonPath, `${JSON.stringify(pkgJson, null, 2)}\n`);
|
|
415
|
+
}
|
|
416
|
+
|
|
251
417
|
export async function scaffold({ templateKey, targetDir }) {
|
|
252
418
|
let repoDir = null;
|
|
253
419
|
let tempDir = null;
|
|
@@ -300,11 +466,22 @@ export async function scaffold({ templateKey, targetDir }) {
|
|
|
300
466
|
selectedTemplate = await promptSelectTemplateTree(templates);
|
|
301
467
|
}
|
|
302
468
|
|
|
469
|
+
const optionalRules = await loadOptionalPackageRules();
|
|
470
|
+
const optionalPackages = collectOptionalPackages(
|
|
471
|
+
optionalRules,
|
|
472
|
+
selectedTemplate.key
|
|
473
|
+
);
|
|
474
|
+
const selectedOptionalPackages =
|
|
475
|
+
optionalPackages.length > 0
|
|
476
|
+
? await promptOptionalPackages(optionalPackages)
|
|
477
|
+
: [];
|
|
478
|
+
|
|
303
479
|
const resolvedTargetDir = targetDir || (await promptTargetDir());
|
|
304
480
|
const targetPath = path.resolve(process.cwd(), resolvedTargetDir);
|
|
305
481
|
|
|
306
482
|
await ensureEmptyDir(targetPath);
|
|
307
483
|
await copyDir(selectedTemplate.dir, targetPath);
|
|
484
|
+
await applyOptionalPackages(targetPath, selectedOptionalPackages);
|
|
308
485
|
|
|
309
486
|
if (tempDir) {
|
|
310
487
|
await rm(tempDir, { recursive: true, force: true });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"match": "backend/express/express-no-ts",
|
|
5
|
+
"packages": [
|
|
6
|
+
{
|
|
7
|
+
"name": "sequelize",
|
|
8
|
+
"dev": false
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "mysql2",
|
|
12
|
+
"dev": false
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"matchPrefix": "front/web/vue3/",
|
|
18
|
+
"packages": [
|
|
19
|
+
{
|
|
20
|
+
"name": "axios",
|
|
21
|
+
"dev": false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "eslint",
|
|
25
|
+
"dev": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guchen_0521/create-temp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Project scaffolding CLI (templates pulled from GitHub)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
11
|
"lib",
|
|
12
|
+
"optional-packages.json",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|