create-hsi-app 0.1.5 → 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 +32 -0
- package/bin/create-hsi-app.mjs +376 -42
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# create-hsi-app
|
|
2
|
+
|
|
3
|
+
Scaffold a new Vite + React + TypeScript app from the frontend template.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm create hsi-app@latest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### yarn
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn create hsi-app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### pnpm
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm create hsi-app@latest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### bun
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bun create hsi-app@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
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,50 +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
|
-
const
|
|
17
|
+
const packageManagers = ['bun', 'npm', 'pnpm', 'yarn'];
|
|
18
|
+
const rawArgs = process.argv.slice(2);
|
|
19
|
+
const selectedPackageManager = resolvePackageManager(rawArgs);
|
|
20
|
+
const shouldInstallDependencies = !rawArgs.includes('--noInstall');
|
|
21
|
+
const shouldSkipRepoSetup = rawArgs.includes('--noRepo');
|
|
22
|
+
const isInteractive = input.isTTY && output.isTTY;
|
|
23
|
+
const targetArg = rawArgs.find((arg) => !arg.startsWith('--')) ?? '.';
|
|
16
24
|
const targetPath = resolve(targetArg);
|
|
17
25
|
const appName = toPackageName(basename(targetPath));
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
fail(
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
63
|
+
|
|
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()}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function run(command, args, options = {}) {
|
|
51
88
|
try {
|
|
52
|
-
execFileSync(command, args, {
|
|
53
|
-
|
|
54
|
-
|
|
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}`);
|
|
55
101
|
}
|
|
56
102
|
}
|
|
57
103
|
|
|
@@ -63,9 +109,12 @@ function updatePackageJson() {
|
|
|
63
109
|
packageJson.version = '0.1.0';
|
|
64
110
|
delete packageJson.repository;
|
|
65
111
|
delete packageJson.publishConfig;
|
|
66
|
-
delete packageJson.
|
|
112
|
+
delete packageJson.packageManager;
|
|
113
|
+
delete packageJson.engines;
|
|
114
|
+
delete packageJson.scripts.release;
|
|
67
115
|
packageJson.scripts.check =
|
|
68
|
-
'
|
|
116
|
+
'tsc -p tsconfig.json --noEmit && eslint . && prettier . --check && vite build';
|
|
117
|
+
packageJson.packageManager = packageManagerDeclaration();
|
|
69
118
|
|
|
70
119
|
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 4)}\n`);
|
|
71
120
|
}
|
|
@@ -77,6 +126,11 @@ function updateBunLock() {
|
|
|
77
126
|
return;
|
|
78
127
|
}
|
|
79
128
|
|
|
129
|
+
if (selectedPackageManager !== 'bun') {
|
|
130
|
+
rmSync(lockPath, { force: true });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
80
134
|
const lock = readFileSync(lockPath, 'utf8').replace(
|
|
81
135
|
'"name": "frontend-template"',
|
|
82
136
|
`"name": "${appName}"`
|
|
@@ -102,7 +156,63 @@ function updateAppText() {
|
|
|
102
156
|
);
|
|
103
157
|
}
|
|
104
158
|
|
|
159
|
+
function updatePackageManagerFiles() {
|
|
160
|
+
rmSync(join(targetPath, 'bunfig.toml'), { force: true });
|
|
161
|
+
rmSync(join(targetPath, '.npmrc'), { force: true });
|
|
162
|
+
rmSync(join(targetPath, 'pnpm-workspace.yaml'), { force: true });
|
|
163
|
+
rmSync(join(targetPath, '.yarnrc.yml'), { force: true });
|
|
164
|
+
|
|
165
|
+
switch (selectedPackageManager) {
|
|
166
|
+
case 'bun':
|
|
167
|
+
writeFileSync(
|
|
168
|
+
join(targetPath, 'bunfig.toml'),
|
|
169
|
+
'[install]\nminimumReleaseAge = 604800\n'
|
|
170
|
+
);
|
|
171
|
+
return;
|
|
172
|
+
case 'npm':
|
|
173
|
+
writeFileSync(join(targetPath, '.npmrc'), 'min-release-age=7\n');
|
|
174
|
+
return;
|
|
175
|
+
case 'pnpm':
|
|
176
|
+
writeFileSync(
|
|
177
|
+
join(targetPath, 'pnpm-workspace.yaml'),
|
|
178
|
+
'minimumReleaseAge: 10080\n'
|
|
179
|
+
);
|
|
180
|
+
return;
|
|
181
|
+
case 'yarn':
|
|
182
|
+
writeFileSync(
|
|
183
|
+
join(targetPath, '.yarnrc.yml'),
|
|
184
|
+
'npmMinimalAgeGate: 7d\n'
|
|
185
|
+
);
|
|
186
|
+
return;
|
|
187
|
+
default:
|
|
188
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
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
|
+
|
|
105
211
|
function writeAppReadme() {
|
|
212
|
+
const installLine = installCommand();
|
|
213
|
+
const devLine = devCommand();
|
|
214
|
+
const checkLine = checkCommand();
|
|
215
|
+
const securityNote = securityNoteForPackageManager();
|
|
106
216
|
const readme = `# ${appName}
|
|
107
217
|
|
|
108
218
|
Created from the frontend template.
|
|
@@ -110,25 +220,154 @@ Created from the frontend template.
|
|
|
110
220
|
## Install
|
|
111
221
|
|
|
112
222
|
\`\`\`bash
|
|
113
|
-
|
|
223
|
+
${installLine}
|
|
114
224
|
\`\`\`
|
|
115
225
|
|
|
116
226
|
## Develop
|
|
117
227
|
|
|
118
228
|
\`\`\`bash
|
|
119
|
-
|
|
229
|
+
${devLine}
|
|
120
230
|
\`\`\`
|
|
121
231
|
|
|
122
232
|
## Check
|
|
123
233
|
|
|
124
234
|
\`\`\`bash
|
|
125
|
-
|
|
235
|
+
${checkLine}
|
|
126
236
|
\`\`\`
|
|
237
|
+
|
|
238
|
+
${securityNote}
|
|
127
239
|
`;
|
|
128
240
|
|
|
129
241
|
writeFileSync(join(targetPath, 'README.md'), readme);
|
|
130
242
|
}
|
|
131
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
|
+
|
|
132
371
|
function replaceInFile(filePath, searchValue, replacement) {
|
|
133
372
|
const source = readFileSync(filePath, 'utf8');
|
|
134
373
|
writeFileSync(filePath, source.replace(searchValue, replacement.with));
|
|
@@ -146,6 +385,101 @@ function toPackageName(value) {
|
|
|
146
385
|
return name || defaultAppName;
|
|
147
386
|
}
|
|
148
387
|
|
|
388
|
+
function resolvePackageManager(args) {
|
|
389
|
+
const selectedFlags = args.filter((arg) =>
|
|
390
|
+
['--bun', '--npm', '--pnpm', '--yarn'].includes(arg)
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (selectedFlags.length > 1) {
|
|
394
|
+
fail('Pass only one of --bun, --npm, --pnpm, or --yarn.');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
switch (selectedFlags[0]) {
|
|
398
|
+
case '--npm':
|
|
399
|
+
return 'npm';
|
|
400
|
+
case '--pnpm':
|
|
401
|
+
return 'pnpm';
|
|
402
|
+
case '--yarn':
|
|
403
|
+
return 'yarn';
|
|
404
|
+
case '--bun':
|
|
405
|
+
case undefined:
|
|
406
|
+
return 'bun';
|
|
407
|
+
default:
|
|
408
|
+
fail(`Unsupported package manager flag: ${selectedFlags[0]}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function packageManagerDeclaration() {
|
|
413
|
+
switch (selectedPackageManager) {
|
|
414
|
+
case 'bun':
|
|
415
|
+
return 'bun@1.3.9';
|
|
416
|
+
case 'npm':
|
|
417
|
+
return 'npm@11';
|
|
418
|
+
case 'pnpm':
|
|
419
|
+
return 'pnpm@10';
|
|
420
|
+
case 'yarn':
|
|
421
|
+
return 'yarn@4';
|
|
422
|
+
default:
|
|
423
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function installCommand() {
|
|
428
|
+
switch (selectedPackageManager) {
|
|
429
|
+
case 'bun':
|
|
430
|
+
return 'bun install';
|
|
431
|
+
case 'npm':
|
|
432
|
+
return 'npm install';
|
|
433
|
+
case 'pnpm':
|
|
434
|
+
return 'pnpm install';
|
|
435
|
+
case 'yarn':
|
|
436
|
+
return 'yarn install';
|
|
437
|
+
default:
|
|
438
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function devCommand() {
|
|
443
|
+
switch (selectedPackageManager) {
|
|
444
|
+
case 'yarn':
|
|
445
|
+
return 'yarn dev';
|
|
446
|
+
case 'bun':
|
|
447
|
+
case 'npm':
|
|
448
|
+
case 'pnpm':
|
|
449
|
+
return `${selectedPackageManager} run dev`;
|
|
450
|
+
default:
|
|
451
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function checkCommand() {
|
|
456
|
+
switch (selectedPackageManager) {
|
|
457
|
+
case 'yarn':
|
|
458
|
+
return 'yarn check';
|
|
459
|
+
case 'bun':
|
|
460
|
+
case 'npm':
|
|
461
|
+
case 'pnpm':
|
|
462
|
+
return `${selectedPackageManager} run check`;
|
|
463
|
+
default:
|
|
464
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function securityNoteForPackageManager() {
|
|
469
|
+
switch (selectedPackageManager) {
|
|
470
|
+
case 'bun':
|
|
471
|
+
return 'This project includes `bunfig.toml` with `minimumReleaseAge = 604800`.';
|
|
472
|
+
case 'npm':
|
|
473
|
+
return 'This project includes `.npmrc` with `min-release-age=7`.';
|
|
474
|
+
case 'pnpm':
|
|
475
|
+
return 'This project includes `pnpm-workspace.yaml` with `minimumReleaseAge: 10080`.';
|
|
476
|
+
case 'yarn':
|
|
477
|
+
return 'This project includes `.yarnrc.yml` with `npmMinimalAgeGate: 7d`.';
|
|
478
|
+
default:
|
|
479
|
+
fail(`Unsupported package manager: ${selectedPackageManager}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
149
483
|
function fail(message) {
|
|
150
484
|
console.error(`create-hsi-app: ${message}`);
|
|
151
485
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-hsi-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Create a new app from the frontend template.",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-hsi-app": "bin/create-hsi-app.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"bin"
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md"
|
|
11
12
|
],
|
|
12
13
|
"engines": {
|
|
13
14
|
"node": ">=18"
|