create-hsi-app 0.2.0 → 0.5.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 +2 -5
- package/bin/create-hsi-app.mjs +224 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,8 +28,5 @@ pnpm create hsi-app@latest
|
|
|
28
28
|
bun create hsi-app@latest
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm create hsi-app@latest -- --pnpm
|
|
35
|
-
```
|
|
31
|
+
Full CLI usage, flags, and repo/install behavior:
|
|
32
|
+
[docs/create-hsi-app.md](https://github.com/Hsiii/frontend-template/blob/main/docs/create-hsi-app.md)
|
package/bin/create-hsi-app.mjs
CHANGED
|
@@ -8,58 +8,96 @@ import {
|
|
|
8
8
|
writeFileSync,
|
|
9
9
|
} from 'node:fs';
|
|
10
10
|
import { basename, join, resolve } from 'node:path';
|
|
11
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
12
|
+
import readline from 'node:readline/promises';
|
|
11
13
|
|
|
12
14
|
const templateRepo = 'https://github.com/Hsiii/frontend-template.git';
|
|
13
|
-
const templateTag = 'v0.
|
|
15
|
+
const templateTag = 'v0.5.0';
|
|
14
16
|
const defaultAppName = 'my-app';
|
|
15
17
|
const packageManagers = ['bun', 'npm', 'pnpm', 'yarn'];
|
|
16
18
|
const rawArgs = process.argv.slice(2);
|
|
17
|
-
const selectedPackageManager =
|
|
19
|
+
const selectedPackageManager = resolvePackageManager(rawArgs);
|
|
20
|
+
const shouldInstallDependencies = !rawArgs.includes('--noInstall');
|
|
21
|
+
const shouldSkipRepoSetup = rawArgs.includes('--noRepo');
|
|
22
|
+
const isInteractive = input.isTTY && output.isTTY;
|
|
18
23
|
const targetArg = rawArgs.find((arg) => !arg.startsWith('--')) ?? '.';
|
|
19
24
|
const targetPath = resolve(targetArg);
|
|
20
25
|
const appName = toPackageName(basename(targetPath));
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
fail(
|
|
24
|
-
}
|
|
27
|
+
main().catch((error) => {
|
|
28
|
+
fail(error.message);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
if (existsSync(targetPath) && readdirSync(targetPath).length > 0) {
|
|
33
|
+
fail(`Target directory is not empty: ${targetPath}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
run('git', [
|
|
37
|
+
'-c',
|
|
38
|
+
'advice.detachedHead=false',
|
|
39
|
+
'clone',
|
|
40
|
+
'--branch',
|
|
41
|
+
templateTag,
|
|
42
|
+
'--depth',
|
|
43
|
+
'1',
|
|
44
|
+
templateRepo,
|
|
45
|
+
targetPath,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
rmSync(join(targetPath, '.git'), { force: true, recursive: true });
|
|
49
|
+
rmSync(join(targetPath, '.github'), { force: true, recursive: true });
|
|
50
|
+
rmSync(join(targetPath, 'docs'), { force: true, recursive: true });
|
|
51
|
+
rmSync(join(targetPath, 'packages'), { force: true, recursive: true });
|
|
52
|
+
rmSync(join(targetPath, 'scripts'), { force: true, recursive: true });
|
|
53
|
+
|
|
54
|
+
updatePackageJson();
|
|
55
|
+
updateBunLock();
|
|
56
|
+
updateAppText();
|
|
57
|
+
updatePackageManagerFiles();
|
|
58
|
+
writeAppReadme();
|
|
59
|
+
|
|
60
|
+
if (shouldInstallDependencies) {
|
|
61
|
+
installDependencies();
|
|
62
|
+
}
|
|
25
63
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
updatePackageManagerFiles();
|
|
48
|
-
writeAppReadme();
|
|
49
|
-
|
|
50
|
-
console.log(`\nCreated ${appName} in ${targetPath}\n`);
|
|
51
|
-
console.log('Next steps:');
|
|
52
|
-
if (targetArg !== '.') {
|
|
53
|
-
console.log(` cd ${targetArg}`);
|
|
64
|
+
const repoSetup = await maybeSetupRepo();
|
|
65
|
+
|
|
66
|
+
console.log(`\nCreated ${appName} in ${targetPath}\n`);
|
|
67
|
+
if (repoSetup === 'github') {
|
|
68
|
+
console.log(
|
|
69
|
+
'Created a local git repository and configured GitHub origin.'
|
|
70
|
+
);
|
|
71
|
+
} else if (repoSetup === 'local') {
|
|
72
|
+
console.log('Initialized a local git repository.');
|
|
73
|
+
}
|
|
74
|
+
if (shouldInstallDependencies) {
|
|
75
|
+
console.log(`Installed dependencies with ${selectedPackageManager}.`);
|
|
76
|
+
}
|
|
77
|
+
console.log('\nNext steps:');
|
|
78
|
+
if (targetArg !== '.') {
|
|
79
|
+
console.log(` cd ${targetArg}`);
|
|
80
|
+
}
|
|
81
|
+
if (!shouldInstallDependencies) {
|
|
82
|
+
console.log(` ${installCommand()}`);
|
|
83
|
+
}
|
|
84
|
+
console.log(` ${devCommand()}`);
|
|
54
85
|
}
|
|
55
|
-
console.log(` ${installCommand()}`);
|
|
56
|
-
console.log(` ${devCommand()}`);
|
|
57
86
|
|
|
58
|
-
function run(command, args) {
|
|
87
|
+
function run(command, args, options = {}) {
|
|
59
88
|
try {
|
|
60
|
-
execFileSync(command, args, {
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
return execFileSync(command, args, {
|
|
90
|
+
cwd: options.cwd,
|
|
91
|
+
encoding: options.capture ? 'utf8' : undefined,
|
|
92
|
+
stdio: options.capture ? 'pipe' : 'inherit',
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (options.allowFailure) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const details = error.stderr?.toString().trim() || error.message;
|
|
100
|
+
fail(`Failed to run: ${command} ${args.join(' ')}\n${details}`);
|
|
63
101
|
}
|
|
64
102
|
}
|
|
65
103
|
|
|
@@ -151,6 +189,25 @@ function updatePackageManagerFiles() {
|
|
|
151
189
|
}
|
|
152
190
|
}
|
|
153
191
|
|
|
192
|
+
function installDependencies() {
|
|
193
|
+
switch (selectedPackageManager) {
|
|
194
|
+
case 'bun':
|
|
195
|
+
run('bun', ['install'], { cwd: targetPath });
|
|
196
|
+
return;
|
|
197
|
+
case 'npm':
|
|
198
|
+
run('npm', ['install'], { cwd: targetPath });
|
|
199
|
+
return;
|
|
200
|
+
case 'pnpm':
|
|
201
|
+
run('pnpm', ['install'], { cwd: targetPath });
|
|
202
|
+
return;
|
|
203
|
+
case 'yarn':
|
|
204
|
+
run('yarn', ['install'], { cwd: targetPath });
|
|
205
|
+
return;
|
|
206
|
+
default:
|
|
207
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
154
211
|
function writeAppReadme() {
|
|
155
212
|
const installLine = installCommand();
|
|
156
213
|
const devLine = devCommand();
|
|
@@ -184,6 +241,133 @@ ${securityNote}
|
|
|
184
241
|
writeFileSync(join(targetPath, 'README.md'), readme);
|
|
185
242
|
}
|
|
186
243
|
|
|
244
|
+
async function maybeSetupRepo() {
|
|
245
|
+
if (shouldSkipRepoSetup || !isInteractive) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const rl = readline.createInterface({ input, output });
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const shouldCreateRepo = await promptYesNo(
|
|
253
|
+
rl,
|
|
254
|
+
'Create a git repository?',
|
|
255
|
+
true
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
if (!shouldCreateRepo) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
initLocalRepo();
|
|
263
|
+
|
|
264
|
+
if (!canUseGitHubCli()) {
|
|
265
|
+
return 'local';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const defaultRepoName = basename(targetPath);
|
|
269
|
+
const repoName = await promptWithDefault(
|
|
270
|
+
rl,
|
|
271
|
+
'Repository name',
|
|
272
|
+
defaultRepoName
|
|
273
|
+
);
|
|
274
|
+
const visibility = await promptChoice(rl, 'Visibility', [
|
|
275
|
+
{ label: 'private', value: 'private', default: true },
|
|
276
|
+
{ label: 'public', value: 'public' },
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
run(
|
|
280
|
+
'gh',
|
|
281
|
+
[
|
|
282
|
+
'repo',
|
|
283
|
+
'create',
|
|
284
|
+
repoName,
|
|
285
|
+
`--${visibility}`,
|
|
286
|
+
'--source=.',
|
|
287
|
+
'--remote=origin',
|
|
288
|
+
],
|
|
289
|
+
{ cwd: targetPath }
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
return 'github';
|
|
293
|
+
} finally {
|
|
294
|
+
rl.close();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function initLocalRepo() {
|
|
299
|
+
run('git', ['init', '-b', 'main'], { cwd: targetPath });
|
|
300
|
+
run('git', ['config', 'core.hooksPath', '.githooks'], { cwd: targetPath });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function canUseGitHubCli() {
|
|
304
|
+
return Boolean(
|
|
305
|
+
run('gh', ['auth', 'status'], {
|
|
306
|
+
cwd: targetPath,
|
|
307
|
+
capture: true,
|
|
308
|
+
allowFailure: true,
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function promptYesNo(rl, label, defaultValue) {
|
|
314
|
+
const hint = defaultValue ? 'Y/n' : 'y/N';
|
|
315
|
+
|
|
316
|
+
while (true) {
|
|
317
|
+
const answer = (await rl.question(`${label} [${hint}] `))
|
|
318
|
+
.trim()
|
|
319
|
+
.toLowerCase();
|
|
320
|
+
|
|
321
|
+
if (!answer) {
|
|
322
|
+
return defaultValue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (['y', 'yes'].includes(answer)) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (['n', 'no'].includes(answer)) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function promptWithDefault(rl, label, defaultValue) {
|
|
336
|
+
const answer = (await rl.question(`${label} (${defaultValue}): `)).trim();
|
|
337
|
+
|
|
338
|
+
return answer || defaultValue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function promptChoice(rl, label, choices) {
|
|
342
|
+
const renderedChoices = choices
|
|
343
|
+
.map((choice) =>
|
|
344
|
+
choice.default ? `${choice.label.toUpperCase()}` : choice.label
|
|
345
|
+
)
|
|
346
|
+
.join('/');
|
|
347
|
+
|
|
348
|
+
while (true) {
|
|
349
|
+
const answer = (await rl.question(`${label} (${renderedChoices}): `))
|
|
350
|
+
.trim()
|
|
351
|
+
.toLowerCase();
|
|
352
|
+
|
|
353
|
+
if (!answer) {
|
|
354
|
+
const defaultChoice = choices.find((choice) => choice.default);
|
|
355
|
+
|
|
356
|
+
if (defaultChoice) {
|
|
357
|
+
return defaultChoice.value;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const matchingChoice = choices.find(
|
|
362
|
+
(choice) => choice.label === answer || choice.value === answer
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
if (matchingChoice) {
|
|
366
|
+
return matchingChoice.value;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
187
371
|
function replaceInFile(filePath, searchValue, replacement) {
|
|
188
372
|
const source = readFileSync(filePath, 'utf8');
|
|
189
373
|
writeFileSync(filePath, source.replace(searchValue, replacement.with));
|
|
@@ -201,7 +385,7 @@ function toPackageName(value) {
|
|
|
201
385
|
return name || defaultAppName;
|
|
202
386
|
}
|
|
203
387
|
|
|
204
|
-
function
|
|
388
|
+
function resolvePackageManager(args) {
|
|
205
389
|
const selectedFlags = args.filter((arg) =>
|
|
206
390
|
['--bun', '--npm', '--pnpm', '--yarn'].includes(arg)
|
|
207
391
|
);
|