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