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