create-analog 3.0.0-alpha.4 → 3.0.0-alpha.41
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/index.js +328 -153
- package/package.json +7 -4
- package/template-angular-v17/package.json +8 -8
- package/template-angular-v17/tsconfig.json +1 -4
- package/template-angular-v17/tsconfig.spec.json +2 -1
- package/template-angular-v17/vite.config.ts +4 -2
- package/template-angular-v18/package.json +9 -9
- package/template-angular-v18/tsconfig.json +1 -4
- package/template-angular-v18/tsconfig.spec.json +2 -1
- package/template-angular-v18/vite.config.ts +4 -2
- package/template-angular-v19/package.json +9 -9
- package/template-angular-v19/tsconfig.json +0 -6
- package/template-angular-v19/tsconfig.spec.json +2 -1
- package/template-angular-v19/vite.config.ts +2 -2
- package/template-angular-v20/package.json +9 -9
- package/template-angular-v20/tsconfig.json +0 -5
- package/template-angular-v20/tsconfig.spec.json +2 -1
- package/template-angular-v20/vite.config.ts +2 -2
- package/template-blog/package.json +11 -10
- package/template-blog/src/app/app.config.ts +6 -2
- package/template-blog/src/test-setup.ts +1 -0
- package/template-blog/tsconfig.json +3 -8
- package/template-blog/tsconfig.spec.json +2 -1
- package/template-blog/vite.config.ts +3 -3
- package/template-latest/package.json +11 -10
- package/template-latest/src/test-setup.ts +1 -0
- package/template-latest/tsconfig.json +3 -8
- package/template-latest/tsconfig.spec.json +2 -1
- package/template-latest/vite.config.ts +3 -3
- package/template-minimal/package.json +11 -10
- package/template-minimal/tsconfig.json +3 -9
- package/template-minimal/vite.config.ts +3 -3
package/index.js
CHANGED
|
@@ -3,17 +3,73 @@
|
|
|
3
3
|
// @ts-check
|
|
4
4
|
import { blue, green, red, reset, yellow } from 'kolorist';
|
|
5
5
|
import minimist from 'minimist';
|
|
6
|
-
import {
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import prompts from 'prompts';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {'latest' | 'blog' | 'minimal'} Template
|
|
14
|
+
* @typedef {'prismjs' | 'shiki'} HighlighterId
|
|
15
|
+
* @typedef {(value: string) => string} Colorizer
|
|
16
|
+
*
|
|
17
|
+
* @typedef {object} Variant
|
|
18
|
+
* @property {string} name
|
|
19
|
+
* @property {Template} template
|
|
20
|
+
* @property {Colorizer} color
|
|
21
|
+
*
|
|
22
|
+
* @typedef {object} AppDefinition
|
|
23
|
+
* @property {string} name
|
|
24
|
+
* @property {Colorizer} color
|
|
25
|
+
* @property {readonly Variant[]} variants
|
|
26
|
+
*
|
|
27
|
+
* @typedef {object} HighlighterConfig
|
|
28
|
+
* @property {string} highlighter
|
|
29
|
+
* @property {string} entryPoint
|
|
30
|
+
* @property {Record<string, string>} dependencies
|
|
31
|
+
*
|
|
32
|
+
* @typedef {object} PackageJson
|
|
33
|
+
* @property {string} [name]
|
|
34
|
+
* @property {Record<string, string>} [scripts]
|
|
35
|
+
* @property {Record<string, string>} [dependencies]
|
|
36
|
+
* @property {Record<string, string>} [devDependencies]
|
|
37
|
+
*
|
|
38
|
+
* @typedef {object} PromptAnswers
|
|
39
|
+
* @property {string} [projectName]
|
|
40
|
+
* @property {boolean} [overwrite]
|
|
41
|
+
* @property {string} [packageName]
|
|
42
|
+
* @property {Template} [variant]
|
|
43
|
+
* @property {boolean} [tailwind]
|
|
44
|
+
* @property {HighlighterId} [syntaxHighlighter]
|
|
45
|
+
*
|
|
46
|
+
* @typedef {object} UserAgentPackage
|
|
47
|
+
* @property {string} name
|
|
48
|
+
* @property {string} version
|
|
49
|
+
*
|
|
50
|
+
* @typedef {{
|
|
51
|
+
* _: string[];
|
|
52
|
+
* template?: string;
|
|
53
|
+
* t?: string;
|
|
54
|
+
* skipTailwind?: boolean | string;
|
|
55
|
+
* skipGit?: boolean | string;
|
|
56
|
+
* } & Record<string, unknown>} CliArgv
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
60
|
+
const DEFAULT_TARGET_DIR = 'analog-project';
|
|
61
|
+
const DEFAULT_BLOG_HIGHLIGHTER = 'prismjs';
|
|
62
|
+
|
|
63
|
+
/** @type {readonly Template[]} */
|
|
64
|
+
const H3_TEMPLATES = ['latest', 'blog', 'minimal'];
|
|
65
|
+
|
|
12
66
|
// Avoids autoconversion to number of the project name by defining that the args
|
|
13
67
|
// non associated with an option ( _ ) needs to be parsed as a string. See #4606
|
|
68
|
+
/** @type {CliArgv} */
|
|
14
69
|
const argv = minimist(process.argv.slice(2), { string: ['_'] });
|
|
15
70
|
const cwd = process.cwd();
|
|
16
71
|
|
|
72
|
+
/** @type {readonly AppDefinition[]} */
|
|
17
73
|
const APPS = [
|
|
18
74
|
{
|
|
19
75
|
name: 'Analog',
|
|
@@ -37,6 +93,8 @@ const APPS = [
|
|
|
37
93
|
],
|
|
38
94
|
},
|
|
39
95
|
];
|
|
96
|
+
|
|
97
|
+
/** @type {Readonly<Record<HighlighterId, HighlighterConfig>>} */
|
|
40
98
|
const HIGHLIGHTERS = {
|
|
41
99
|
prismjs: {
|
|
42
100
|
highlighter: 'withPrismHighlighter',
|
|
@@ -50,118 +108,134 @@ const HIGHLIGHTERS = {
|
|
|
50
108
|
highlighter: 'withShikiHighlighter',
|
|
51
109
|
entryPoint: 'shiki-highlighter',
|
|
52
110
|
dependencies: {
|
|
53
|
-
marked: '^
|
|
111
|
+
marked: '^18.0.0',
|
|
54
112
|
'marked-shiki': '^1.1.0',
|
|
55
113
|
shiki: '^1.6.1',
|
|
56
114
|
},
|
|
57
115
|
},
|
|
58
116
|
};
|
|
59
117
|
|
|
118
|
+
/** @type {Readonly<Record<string, string>>} */
|
|
60
119
|
const renameFiles = {
|
|
61
120
|
_gitignore: '.gitignore',
|
|
62
121
|
};
|
|
63
122
|
|
|
123
|
+
const TAILWIND_POSTCSS_CONFIG = `export default {
|
|
124
|
+
plugins: {
|
|
125
|
+
'@tailwindcss/postcss': {},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
`;
|
|
129
|
+
|
|
64
130
|
async function init() {
|
|
65
131
|
let targetDir = formatTargetDir(argv._[0]);
|
|
66
|
-
let template = argv.template
|
|
132
|
+
let template = resolveTemplate(argv.template ?? argv.t);
|
|
67
133
|
let skipTailwind = fromBoolArg(argv.skipTailwind);
|
|
134
|
+
const skipGit = fromBoolArg(argv.skipGit ?? argv['skip-git']) ?? false;
|
|
68
135
|
|
|
69
|
-
const defaultTargetDir = 'analog-project';
|
|
70
136
|
const getProjectName = () =>
|
|
71
|
-
targetDir === '.' ? path.basename(path.resolve()) : targetDir;
|
|
137
|
+
targetDir === '.' ? path.basename(path.resolve()) : (targetDir ?? '');
|
|
72
138
|
|
|
139
|
+
/** @type {PromptAnswers} */
|
|
73
140
|
let result = {};
|
|
74
141
|
|
|
75
142
|
try {
|
|
76
|
-
result =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
143
|
+
result = /** @type {PromptAnswers} */ (
|
|
144
|
+
await prompts(
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
type: targetDir ? null : 'text',
|
|
148
|
+
name: 'projectName',
|
|
149
|
+
message: reset('Project name:'),
|
|
150
|
+
initial: DEFAULT_TARGET_DIR,
|
|
151
|
+
onState: (state) => {
|
|
152
|
+
targetDir =
|
|
153
|
+
formatTargetDir(String(state.value ?? '')) ||
|
|
154
|
+
DEFAULT_TARGET_DIR;
|
|
155
|
+
},
|
|
85
156
|
},
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
type: (_, { overwrite } = {}) => {
|
|
99
|
-
if (overwrite === false) {
|
|
100
|
-
throw new Error(red('✖') + ' Operation cancelled');
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
157
|
+
{
|
|
158
|
+
type: () =>
|
|
159
|
+
!targetDir || !fs.existsSync(targetDir) || isEmpty(targetDir)
|
|
160
|
+
? null
|
|
161
|
+
: 'confirm',
|
|
162
|
+
name: 'overwrite',
|
|
163
|
+
message: () =>
|
|
164
|
+
(targetDir === '.'
|
|
165
|
+
? 'Current directory'
|
|
166
|
+
: `Target directory "${targetDir}"`) +
|
|
167
|
+
' is not empty. Remove existing files and continue?',
|
|
103
168
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
169
|
+
{
|
|
170
|
+
type: (_, promptState = {}) => {
|
|
171
|
+
if (promptState.overwrite === false) {
|
|
172
|
+
throw new Error(`${red('✖')} Operation cancelled`);
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
},
|
|
176
|
+
name: 'overwriteChecker',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
|
|
180
|
+
name: 'packageName',
|
|
181
|
+
message: reset('Package name:'),
|
|
182
|
+
initial: () => toValidPackageName(getProjectName()),
|
|
183
|
+
validate: (dir) =>
|
|
184
|
+
isValidPackageName(String(dir)) || 'Invalid package.json name',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: template ? null : 'select',
|
|
188
|
+
name: 'variant',
|
|
189
|
+
message: reset('What would you like to start?:'),
|
|
190
|
+
choices: APPS[0].variants.map((variant) => ({
|
|
191
|
+
title: variant.color(variant.name),
|
|
123
192
|
value: variant.template,
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
193
|
+
})),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: (prev) => (prev === 'blog' ? 'select' : null),
|
|
197
|
+
name: 'syntaxHighlighter',
|
|
198
|
+
message: reset('Choose a syntax highlighter:'),
|
|
199
|
+
choices:
|
|
200
|
+
/** @type {{ title: HighlighterId; value: HighlighterId }[]} */ (
|
|
201
|
+
Object.keys(HIGHLIGHTERS).map((highlighter) => ({
|
|
202
|
+
title: /** @type {HighlighterId} */ (highlighter),
|
|
203
|
+
value: /** @type {HighlighterId} */ (highlighter),
|
|
204
|
+
}))
|
|
205
|
+
),
|
|
206
|
+
initial: 1,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: skipTailwind === undefined ? 'confirm' : null,
|
|
210
|
+
name: 'tailwind',
|
|
211
|
+
message: 'Would you like to add Tailwind to your project?',
|
|
212
|
+
},
|
|
213
|
+
],
|
|
137
214
|
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
{
|
|
144
|
-
onCancel: () => {
|
|
145
|
-
throw new Error(red('✖') + ' Operation cancelled');
|
|
215
|
+
onCancel: () => {
|
|
216
|
+
throw new Error(`${red('✖')} Operation cancelled`);
|
|
217
|
+
},
|
|
146
218
|
},
|
|
147
|
-
|
|
219
|
+
)
|
|
148
220
|
);
|
|
149
|
-
} catch (
|
|
150
|
-
console.log(
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.log(error instanceof Error ? error.message : String(error));
|
|
151
223
|
return;
|
|
152
224
|
}
|
|
153
225
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
framework,
|
|
157
|
-
overwrite,
|
|
158
|
-
packageName,
|
|
159
|
-
variant,
|
|
160
|
-
tailwind,
|
|
161
|
-
syntaxHighlighter,
|
|
162
|
-
} = result;
|
|
226
|
+
const { overwrite, packageName, variant, tailwind, syntaxHighlighter } =
|
|
227
|
+
result;
|
|
163
228
|
|
|
164
|
-
|
|
229
|
+
template = variant ?? template;
|
|
230
|
+
if (!template) {
|
|
231
|
+
throw new Error('A project template must be selected.');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const highlighter =
|
|
235
|
+
syntaxHighlighter ??
|
|
236
|
+
(template === 'blog' ? DEFAULT_BLOG_HIGHLIGHTER : undefined);
|
|
237
|
+
|
|
238
|
+
const root = path.join(cwd, targetDir ?? DEFAULT_TARGET_DIR);
|
|
165
239
|
|
|
166
240
|
if (overwrite) {
|
|
167
241
|
emptyDir(root);
|
|
@@ -169,58 +243,58 @@ async function init() {
|
|
|
169
243
|
fs.mkdirSync(root, { recursive: true });
|
|
170
244
|
}
|
|
171
245
|
|
|
172
|
-
// determine template
|
|
173
|
-
template = variant || framework || template;
|
|
174
|
-
// determine syntax highlighter
|
|
175
|
-
const highlighter =
|
|
176
|
-
syntaxHighlighter ?? (template === 'blog' ? 'prism' : null);
|
|
177
246
|
skipTailwind = skipTailwind ?? !tailwind;
|
|
178
247
|
|
|
179
248
|
console.log(`\nScaffolding project in ${root}...`);
|
|
180
249
|
|
|
181
|
-
const templateDir = path.resolve(
|
|
182
|
-
|
|
183
|
-
'..',
|
|
184
|
-
`template-${template}`,
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
const filesDir = path.resolve(fileURLToPath(import.meta.url), '..', `files`);
|
|
250
|
+
const templateDir = path.resolve(CLI_DIR, `template-${template}`);
|
|
251
|
+
const filesDir = path.resolve(CLI_DIR, 'files');
|
|
188
252
|
|
|
253
|
+
/**
|
|
254
|
+
* @param {string} file
|
|
255
|
+
* @param {string | undefined} [content]
|
|
256
|
+
*/
|
|
189
257
|
const write = (file, content) => {
|
|
190
258
|
const targetPath = renameFiles[file]
|
|
191
259
|
? path.join(root, renameFiles[file])
|
|
192
260
|
: path.join(root, file);
|
|
193
261
|
|
|
194
|
-
if (content) {
|
|
262
|
+
if (typeof content === 'string') {
|
|
195
263
|
fs.writeFileSync(targetPath, content);
|
|
196
|
-
|
|
197
|
-
copy(path.join(templateDir, file), targetPath);
|
|
264
|
+
return;
|
|
198
265
|
}
|
|
266
|
+
|
|
267
|
+
copy(path.join(templateDir, file), targetPath);
|
|
199
268
|
};
|
|
200
269
|
|
|
201
270
|
const files = fs.readdirSync(templateDir);
|
|
202
|
-
for (const file of files.filter((
|
|
271
|
+
for (const file of files.filter((entry) => entry !== 'package.json')) {
|
|
203
272
|
write(file);
|
|
204
273
|
}
|
|
205
274
|
|
|
206
275
|
if (!skipTailwind) {
|
|
207
276
|
addTailwindDirectives(write, filesDir);
|
|
277
|
+
write('postcss.config.mjs', TAILWIND_POSTCSS_CONFIG);
|
|
208
278
|
}
|
|
209
279
|
|
|
210
280
|
replacePlaceholders(root, 'vite.config.ts', {
|
|
211
281
|
__TAILWIND_IMPORT__: !skipTailwind
|
|
212
|
-
?
|
|
282
|
+
? "import tailwindcss from '@tailwindcss/vite';\n"
|
|
213
283
|
: '',
|
|
214
|
-
__TAILWIND_PLUGIN__: !skipTailwind ? '
|
|
284
|
+
__TAILWIND_PLUGIN__: !skipTailwind ? ' tailwindcss(),\n' : '',
|
|
215
285
|
});
|
|
216
286
|
|
|
217
|
-
|
|
218
|
-
const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
|
|
287
|
+
/** @type {PackageJson} */
|
|
219
288
|
const pkg = JSON.parse(
|
|
220
|
-
fs.readFileSync(path.join(templateDir,
|
|
289
|
+
fs.readFileSync(path.join(templateDir, 'package.json'), 'utf-8'),
|
|
221
290
|
);
|
|
291
|
+
const pkgManager =
|
|
292
|
+
pkgFromUserAgent(process.env.npm_config_user_agent)?.name ?? 'npm';
|
|
222
293
|
|
|
223
294
|
pkg.name = packageName || getProjectName();
|
|
295
|
+
pkg.scripts ??= {};
|
|
296
|
+
pkg.dependencies ??= {};
|
|
297
|
+
pkg.devDependencies ??= {};
|
|
224
298
|
pkg.scripts.start = getStartCommand(pkgManager);
|
|
225
299
|
|
|
226
300
|
if (template === 'blog' && highlighter) {
|
|
@@ -246,18 +320,22 @@ async function init() {
|
|
|
246
320
|
|
|
247
321
|
setProjectTitle(root, getProjectName());
|
|
248
322
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
323
|
+
if (!skipGit) {
|
|
324
|
+
console.log('\nInitializing git repository:');
|
|
325
|
+
execFileSync('git', ['init', targetDir], { stdio: 'inherit' });
|
|
326
|
+
execFileSync('git', ['-C', targetDir, 'add', '.'], { stdio: 'inherit' });
|
|
327
|
+
|
|
328
|
+
// Can fail when the user does not have global git credentials.
|
|
329
|
+
try {
|
|
330
|
+
execFileSync('git', ['-C', targetDir, 'commit', '-m', 'initial commit'], {
|
|
331
|
+
stdio: 'inherit',
|
|
332
|
+
});
|
|
333
|
+
} catch {
|
|
334
|
+
/* ignore */
|
|
335
|
+
}
|
|
258
336
|
}
|
|
259
337
|
|
|
260
|
-
console.log(
|
|
338
|
+
console.log('\nDone. Now run:\n');
|
|
261
339
|
if (root !== cwd) {
|
|
262
340
|
console.log(` cd ${path.relative(cwd, root)}`);
|
|
263
341
|
}
|
|
@@ -268,22 +346,30 @@ async function init() {
|
|
|
268
346
|
|
|
269
347
|
/**
|
|
270
348
|
* @param {string | undefined} targetDir
|
|
349
|
+
* @returns {string | undefined}
|
|
271
350
|
*/
|
|
272
351
|
function formatTargetDir(targetDir) {
|
|
273
352
|
return targetDir?.trim().replace(/\/+$/g, '');
|
|
274
353
|
}
|
|
275
354
|
|
|
355
|
+
/**
|
|
356
|
+
* @param {string} src
|
|
357
|
+
* @param {string} dest
|
|
358
|
+
* @returns {void}
|
|
359
|
+
*/
|
|
276
360
|
function copy(src, dest) {
|
|
277
361
|
const stat = fs.statSync(src);
|
|
278
362
|
if (stat.isDirectory()) {
|
|
279
363
|
copyDir(src, dest);
|
|
280
|
-
|
|
281
|
-
fs.copyFileSync(src, dest);
|
|
364
|
+
return;
|
|
282
365
|
}
|
|
366
|
+
|
|
367
|
+
fs.copyFileSync(src, dest);
|
|
283
368
|
}
|
|
284
369
|
|
|
285
370
|
/**
|
|
286
371
|
* @param {string} projectName
|
|
372
|
+
* @returns {boolean}
|
|
287
373
|
*/
|
|
288
374
|
function isValidPackageName(projectName) {
|
|
289
375
|
return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
|
|
@@ -293,6 +379,7 @@ function isValidPackageName(projectName) {
|
|
|
293
379
|
|
|
294
380
|
/**
|
|
295
381
|
* @param {string} projectName
|
|
382
|
+
* @returns {string}
|
|
296
383
|
*/
|
|
297
384
|
function toValidPackageName(projectName) {
|
|
298
385
|
return projectName
|
|
@@ -306,6 +393,7 @@ function toValidPackageName(projectName) {
|
|
|
306
393
|
/**
|
|
307
394
|
* @param {string} srcDir
|
|
308
395
|
* @param {string} destDir
|
|
396
|
+
* @returns {void}
|
|
309
397
|
*/
|
|
310
398
|
function copyDir(srcDir, destDir) {
|
|
311
399
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -317,42 +405,49 @@ function copyDir(srcDir, destDir) {
|
|
|
317
405
|
}
|
|
318
406
|
|
|
319
407
|
/**
|
|
320
|
-
* @param {string}
|
|
408
|
+
* @param {string} directoryPath
|
|
409
|
+
* @returns {boolean}
|
|
321
410
|
*/
|
|
322
|
-
function isEmpty(
|
|
323
|
-
const files = fs.readdirSync(
|
|
411
|
+
function isEmpty(directoryPath) {
|
|
412
|
+
const files = fs.readdirSync(directoryPath);
|
|
324
413
|
return files.length === 0 || (files.length === 1 && files[0] === '.git');
|
|
325
414
|
}
|
|
326
415
|
|
|
327
416
|
/**
|
|
328
417
|
* @param {string} dir
|
|
418
|
+
* @returns {void}
|
|
329
419
|
*/
|
|
330
420
|
function emptyDir(dir) {
|
|
331
421
|
if (!fs.existsSync(dir)) {
|
|
332
422
|
return;
|
|
333
423
|
}
|
|
424
|
+
|
|
334
425
|
for (const file of fs.readdirSync(dir)) {
|
|
335
426
|
fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
|
|
336
427
|
}
|
|
337
428
|
}
|
|
338
429
|
|
|
339
430
|
/**
|
|
340
|
-
* @param {string | undefined} userAgent
|
|
341
|
-
* @returns
|
|
431
|
+
* @param {string | undefined} userAgent
|
|
432
|
+
* @returns {UserAgentPackage | undefined}
|
|
342
433
|
*/
|
|
343
434
|
function pkgFromUserAgent(userAgent) {
|
|
344
|
-
if (!userAgent)
|
|
435
|
+
if (!userAgent) {
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
|
|
345
439
|
const pkgSpec = userAgent.split(' ')[0];
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
440
|
+
const [name, version] = pkgSpec.split('/');
|
|
441
|
+
if (!name || !version) {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return { name, version };
|
|
351
446
|
}
|
|
352
447
|
|
|
353
448
|
/**
|
|
354
449
|
* @param {string} pkgManager
|
|
355
|
-
* @returns string
|
|
450
|
+
* @returns {string}
|
|
356
451
|
*/
|
|
357
452
|
function getInstallCommand(pkgManager) {
|
|
358
453
|
return pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`;
|
|
@@ -360,46 +455,76 @@ function getInstallCommand(pkgManager) {
|
|
|
360
455
|
|
|
361
456
|
/**
|
|
362
457
|
* @param {string} pkgManager
|
|
363
|
-
* @returns string
|
|
458
|
+
* @returns {string}
|
|
364
459
|
*/
|
|
365
460
|
function getStartCommand(pkgManager) {
|
|
366
461
|
return pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`;
|
|
367
462
|
}
|
|
368
463
|
|
|
464
|
+
/**
|
|
465
|
+
* @param {(file: string, content?: string) => void} write
|
|
466
|
+
* @param {string} filesDir
|
|
467
|
+
* @returns {void}
|
|
468
|
+
*/
|
|
369
469
|
function addTailwindDirectives(write, filesDir) {
|
|
370
470
|
write(
|
|
371
471
|
'src/styles.css',
|
|
372
|
-
fs.readFileSync(path.join(filesDir,
|
|
472
|
+
fs.readFileSync(path.join(filesDir, 'styles.css'), 'utf-8'),
|
|
373
473
|
);
|
|
374
474
|
}
|
|
375
475
|
|
|
476
|
+
/**
|
|
477
|
+
* @param {PackageJson} pkg
|
|
478
|
+
* @returns {void}
|
|
479
|
+
*/
|
|
376
480
|
function addTailwindDependencies(pkg) {
|
|
377
|
-
pkg.
|
|
378
|
-
pkg.
|
|
379
|
-
pkg.
|
|
481
|
+
pkg.devDependencies ??= {};
|
|
482
|
+
pkg.devDependencies.postcss = '^8.5.6';
|
|
483
|
+
pkg.devDependencies.tailwindcss = '^4.2.2';
|
|
484
|
+
pkg.devDependencies['@tailwindcss/postcss'] = '^4.2.2';
|
|
485
|
+
pkg.devDependencies['@tailwindcss/vite'] = '^4.2.2';
|
|
380
486
|
}
|
|
381
487
|
|
|
488
|
+
/**
|
|
489
|
+
* @param {PackageJson} pkg
|
|
490
|
+
* @param {Template} template
|
|
491
|
+
* @returns {void}
|
|
492
|
+
*/
|
|
382
493
|
function addYarnDevDependencies(pkg, template) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
pkg.devDependencies
|
|
494
|
+
if (H3_TEMPLATES.includes(template)) {
|
|
495
|
+
pkg.devDependencies ??= {};
|
|
496
|
+
pkg.devDependencies.h3 = '^1.13.0';
|
|
386
497
|
}
|
|
387
498
|
}
|
|
388
499
|
|
|
500
|
+
/**
|
|
501
|
+
* @param {PackageJson} pkg
|
|
502
|
+
* @param {Template} template
|
|
503
|
+
* @returns {void}
|
|
504
|
+
*/
|
|
389
505
|
function addPnpmDependencies(pkg, template) {
|
|
390
|
-
if (template
|
|
391
|
-
pkg.dependencies
|
|
506
|
+
if (H3_TEMPLATES.includes(template)) {
|
|
507
|
+
pkg.dependencies ??= {};
|
|
508
|
+
pkg.dependencies.h3 = '^1.13.0';
|
|
392
509
|
}
|
|
393
510
|
}
|
|
394
511
|
|
|
512
|
+
/**
|
|
513
|
+
* @param {string} root
|
|
514
|
+
* @param {PackageJson} pkg
|
|
515
|
+
* @param {HighlighterId} highlighter
|
|
516
|
+
* @returns {void}
|
|
517
|
+
*/
|
|
395
518
|
function ensureSyntaxHighlighter(root, pkg, highlighter) {
|
|
519
|
+
const config = HIGHLIGHTERS[highlighter];
|
|
520
|
+
|
|
396
521
|
replacePlaceholders(root, 'src/app/app.config.ts', {
|
|
397
|
-
__HIGHLIGHTER__:
|
|
398
|
-
__HIGHLIGHTER_ENTRY_POINT__:
|
|
522
|
+
__HIGHLIGHTER__: config.highlighter,
|
|
523
|
+
__HIGHLIGHTER_ENTRY_POINT__: config.entryPoint,
|
|
399
524
|
});
|
|
400
525
|
|
|
401
|
-
|
|
402
|
-
for (const [name, version] of Object.entries(dependencies)) {
|
|
526
|
+
pkg.dependencies ??= {};
|
|
527
|
+
for (const [name, version] of Object.entries(config.dependencies)) {
|
|
403
528
|
pkg.dependencies[name] = version;
|
|
404
529
|
}
|
|
405
530
|
|
|
@@ -408,44 +533,94 @@ function ensureSyntaxHighlighter(root, pkg, highlighter) {
|
|
|
408
533
|
});
|
|
409
534
|
}
|
|
410
535
|
|
|
536
|
+
/**
|
|
537
|
+
* @param {Record<string, string>} obj
|
|
538
|
+
* @returns {Record<string, string>}
|
|
539
|
+
*/
|
|
411
540
|
function sortObjectKeys(obj) {
|
|
412
541
|
return Object.keys(obj)
|
|
413
542
|
.sort()
|
|
414
543
|
.reduce((result, key) => {
|
|
415
544
|
result[key] = obj[key];
|
|
416
545
|
return result;
|
|
417
|
-
}, {});
|
|
546
|
+
}, /** @type {Record<string, string>} */ ({}));
|
|
418
547
|
}
|
|
419
548
|
|
|
549
|
+
/**
|
|
550
|
+
* @param {string} root
|
|
551
|
+
* @param {string} title
|
|
552
|
+
* @returns {void}
|
|
553
|
+
*/
|
|
420
554
|
function setProjectTitle(root, title) {
|
|
421
555
|
replacePlaceholders(root, ['index.html', 'README.md'], {
|
|
422
556
|
__PROJECT_TITLE__: title,
|
|
423
557
|
});
|
|
424
558
|
}
|
|
425
559
|
|
|
560
|
+
/**
|
|
561
|
+
* @param {string} root
|
|
562
|
+
* @param {string | readonly string[]} files
|
|
563
|
+
* @param {Record<string, string>} config
|
|
564
|
+
* @returns {void}
|
|
565
|
+
*/
|
|
426
566
|
function replacePlaceholders(root, files, config) {
|
|
427
|
-
for (const file of
|
|
567
|
+
for (const file of toArray(files)) {
|
|
428
568
|
const filePath = path.join(root, file);
|
|
429
569
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
430
570
|
const newFileContent = Object.keys(config).reduce(
|
|
431
571
|
(content, placeholder) =>
|
|
432
|
-
content.
|
|
572
|
+
content.replaceAll(placeholder, config[placeholder]),
|
|
433
573
|
fileContent,
|
|
434
574
|
);
|
|
435
575
|
fs.writeFileSync(filePath, newFileContent);
|
|
436
576
|
}
|
|
437
577
|
}
|
|
438
578
|
|
|
439
|
-
|
|
440
|
-
|
|
579
|
+
/**
|
|
580
|
+
* @param {string | readonly string[] | undefined | null} value
|
|
581
|
+
* @returns {string[]}
|
|
582
|
+
*/
|
|
583
|
+
function toArray(value) {
|
|
584
|
+
if (value == null) {
|
|
585
|
+
return [];
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return Array.isArray(value) ? [...value] : [/** @type {string} */ (value)];
|
|
441
589
|
}
|
|
442
590
|
|
|
591
|
+
/**
|
|
592
|
+
* @param {unknown} arg
|
|
593
|
+
* @returns {boolean | undefined}
|
|
594
|
+
*/
|
|
443
595
|
function fromBoolArg(arg) {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
596
|
+
if (typeof arg === 'boolean' || typeof arg === 'undefined') {
|
|
597
|
+
return arg;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (typeof arg !== 'string') {
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return arg === '' || arg === 'true';
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* @param {string | undefined} value
|
|
609
|
+
* @returns {Template | undefined}
|
|
610
|
+
*/
|
|
611
|
+
function resolveTemplate(value) {
|
|
612
|
+
return isTemplate(value) ? value : undefined;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* @param {string | undefined} value
|
|
617
|
+
* @returns {value is Template}
|
|
618
|
+
*/
|
|
619
|
+
function isTemplate(value) {
|
|
620
|
+
return value === 'latest' || value === 'blog' || value === 'minimal';
|
|
447
621
|
}
|
|
448
622
|
|
|
449
|
-
init().catch((
|
|
450
|
-
console.error(
|
|
623
|
+
init().catch((error) => {
|
|
624
|
+
console.error(error);
|
|
625
|
+
process.exitCode = 1;
|
|
451
626
|
});
|