create-analog 3.0.0-alpha.19 → 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 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 { execSync } from 'node:child_process';
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 || argv.t;
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 = await prompts(
77
- [
78
- {
79
- type: targetDir ? null : 'text',
80
- name: 'projectName',
81
- message: reset('Project name:'),
82
- initial: defaultTargetDir,
83
- onState: (state) => {
84
- targetDir = formatTargetDir(state.value) || defaultTargetDir;
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
- type: () =>
89
- !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
90
- name: 'overwrite',
91
- message: () =>
92
- (targetDir === '.'
93
- ? 'Current directory'
94
- : `Target directory "${targetDir}"`) +
95
- ` is not empty. Remove existing files and continue?`,
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
- name: 'overwriteChecker',
105
- },
106
- {
107
- type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
108
- name: 'packageName',
109
- message: reset('Package name:'),
110
- initial: () => toValidPackageName(getProjectName()),
111
- validate: (dir) =>
112
- isValidPackageName(dir) || 'Invalid package.json name',
113
- },
114
- {
115
- type: template ? null : 'select',
116
- name: 'variant',
117
- message: reset('What would you like to start?:'),
118
- // @ts-ignore
119
- choices: APPS[0].variants.map((variant) => {
120
- const variantColor = variant.color;
121
- return {
122
- title: variantColor(variant.name),
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
- type: (prev) => (prev === 'blog' ? 'select' : null),
129
- name: 'syntaxHighlighter',
130
- message: reset('Choose a syntax highlighter:'),
131
- choices: Object.keys(HIGHLIGHTERS).map((highlighter) => ({
132
- title: highlighter,
133
- value: highlighter,
134
- })),
135
- initial: 1,
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
- type: skipTailwind === undefined ? 'confirm' : null,
139
- name: 'tailwind',
140
- message: 'Would you like to add Tailwind to your project?',
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 (cancelled) {
150
- console.log(cancelled.message);
214
+ } catch (error) {
215
+ console.log(error instanceof Error ? error.message : String(error));
151
216
  return;
152
217
  }
153
218
 
154
- // user choice associated with prompts
155
- const {
156
- framework,
157
- overwrite,
158
- packageName,
159
- variant,
160
- tailwind,
161
- syntaxHighlighter,
162
- } = result;
219
+ const { overwrite, packageName, variant, tailwind, syntaxHighlighter } =
220
+ result;
163
221
 
164
- const root = path.join(cwd, targetDir);
222
+ template = variant ?? template;
223
+ if (!template) {
224
+ throw new Error('A project template must be selected.');
225
+ }
226
+
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,37 +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
- const highlighter =
176
- syntaxHighlighter ?? (template === 'blog' ? 'prism' : null);
177
239
  skipTailwind = skipTailwind ?? !tailwind;
178
240
 
179
241
  console.log(`\nScaffolding project in ${root}...`);
180
242
 
181
- const templateDir = path.resolve(
182
- fileURLToPath(import.meta.url),
183
- '..',
184
- `template-${template}`,
185
- );
186
-
187
- 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');
188
245
 
246
+ /**
247
+ * @param {string} file
248
+ * @param {string | undefined} [content]
249
+ */
189
250
  const write = (file, content) => {
190
251
  const targetPath = renameFiles[file]
191
252
  ? path.join(root, renameFiles[file])
192
253
  : path.join(root, file);
193
254
 
194
- if (content) {
255
+ if (typeof content === 'string') {
195
256
  fs.writeFileSync(targetPath, content);
196
- } else {
197
- copy(path.join(templateDir, file), targetPath);
257
+ return;
198
258
  }
259
+
260
+ copy(path.join(templateDir, file), targetPath);
199
261
  };
200
262
 
201
263
  const files = fs.readdirSync(templateDir);
202
- for (const file of files.filter((f) => f !== 'package.json')) {
264
+ for (const file of files.filter((entry) => entry !== 'package.json')) {
203
265
  write(file);
204
266
  }
205
267
 
@@ -209,18 +271,22 @@ async function init() {
209
271
 
210
272
  replacePlaceholders(root, 'vite.config.ts', {
211
273
  __TAILWIND_IMPORT__: !skipTailwind
212
- ? `import tailwindcss from '@tailwindcss/vite';\n`
274
+ ? "import tailwindcss from '@tailwindcss/vite';\n"
213
275
  : '',
214
276
  __TAILWIND_PLUGIN__: !skipTailwind ? ' tailwindcss(),\n' : '',
215
277
  });
216
278
 
217
- const pkgInfo = pkgFromUserAgent(process.env.npm_config_user_agent);
218
- const pkgManager = pkgInfo ? pkgInfo.name : 'npm';
279
+ /** @type {PackageJson} */
219
280
  const pkg = JSON.parse(
220
- fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'),
281
+ fs.readFileSync(path.join(templateDir, 'package.json'), 'utf-8'),
221
282
  );
283
+ const pkgManager =
284
+ pkgFromUserAgent(process.env.npm_config_user_agent)?.name ?? 'npm';
222
285
 
223
286
  pkg.name = packageName || getProjectName();
287
+ pkg.scripts ??= {};
288
+ pkg.dependencies ??= {};
289
+ pkg.devDependencies ??= {};
224
290
  pkg.scripts.start = getStartCommand(pkgManager);
225
291
 
226
292
  if (template === 'blog' && highlighter) {
@@ -246,18 +312,22 @@ async function init() {
246
312
 
247
313
  setProjectTitle(root, getProjectName());
248
314
 
249
- console.log(`\nInitializing git repository:`);
250
- execSync(`git init ${targetDir} && cd ${targetDir} && git add .`);
251
-
252
- // Fail Silent
253
- // Can fail when user does not have global git credentials
254
- try {
255
- execSync(`cd ${targetDir} && git commit -m "initial commit"`);
256
- } catch {
257
- /* ignore */
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
+ }
258
328
  }
259
329
 
260
- console.log(`\nDone. Now run:\n`);
330
+ console.log('\nDone. Now run:\n');
261
331
  if (root !== cwd) {
262
332
  console.log(` cd ${path.relative(cwd, root)}`);
263
333
  }
@@ -268,22 +338,30 @@ async function init() {
268
338
 
269
339
  /**
270
340
  * @param {string | undefined} targetDir
341
+ * @returns {string | undefined}
271
342
  */
272
343
  function formatTargetDir(targetDir) {
273
344
  return targetDir?.trim().replace(/\/+$/g, '');
274
345
  }
275
346
 
347
+ /**
348
+ * @param {string} src
349
+ * @param {string} dest
350
+ * @returns {void}
351
+ */
276
352
  function copy(src, dest) {
277
353
  const stat = fs.statSync(src);
278
354
  if (stat.isDirectory()) {
279
355
  copyDir(src, dest);
280
- } else {
281
- fs.copyFileSync(src, dest);
356
+ return;
282
357
  }
358
+
359
+ fs.copyFileSync(src, dest);
283
360
  }
284
361
 
285
362
  /**
286
363
  * @param {string} projectName
364
+ * @returns {boolean}
287
365
  */
288
366
  function isValidPackageName(projectName) {
289
367
  return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
@@ -293,6 +371,7 @@ function isValidPackageName(projectName) {
293
371
 
294
372
  /**
295
373
  * @param {string} projectName
374
+ * @returns {string}
296
375
  */
297
376
  function toValidPackageName(projectName) {
298
377
  return projectName
@@ -306,6 +385,7 @@ function toValidPackageName(projectName) {
306
385
  /**
307
386
  * @param {string} srcDir
308
387
  * @param {string} destDir
388
+ * @returns {void}
309
389
  */
310
390
  function copyDir(srcDir, destDir) {
311
391
  fs.mkdirSync(destDir, { recursive: true });
@@ -317,42 +397,49 @@ function copyDir(srcDir, destDir) {
317
397
  }
318
398
 
319
399
  /**
320
- * @param {string} path
400
+ * @param {string} directoryPath
401
+ * @returns {boolean}
321
402
  */
322
- function isEmpty(path) {
323
- const files = fs.readdirSync(path);
403
+ function isEmpty(directoryPath) {
404
+ const files = fs.readdirSync(directoryPath);
324
405
  return files.length === 0 || (files.length === 1 && files[0] === '.git');
325
406
  }
326
407
 
327
408
  /**
328
409
  * @param {string} dir
410
+ * @returns {void}
329
411
  */
330
412
  function emptyDir(dir) {
331
413
  if (!fs.existsSync(dir)) {
332
414
  return;
333
415
  }
416
+
334
417
  for (const file of fs.readdirSync(dir)) {
335
418
  fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
336
419
  }
337
420
  }
338
421
 
339
422
  /**
340
- * @param {string | undefined} userAgent process.env.npm_config_user_agent
341
- * @returns object | undefined
423
+ * @param {string | undefined} userAgent
424
+ * @returns {UserAgentPackage | undefined}
342
425
  */
343
426
  function pkgFromUserAgent(userAgent) {
344
- if (!userAgent) return undefined;
427
+ if (!userAgent) {
428
+ return undefined;
429
+ }
430
+
345
431
  const pkgSpec = userAgent.split(' ')[0];
346
- const pkgSpecArr = pkgSpec.split('/');
347
- return {
348
- name: pkgSpecArr[0],
349
- version: pkgSpecArr[1],
350
- };
432
+ const [name, version] = pkgSpec.split('/');
433
+ if (!name || !version) {
434
+ return undefined;
435
+ }
436
+
437
+ return { name, version };
351
438
  }
352
439
 
353
440
  /**
354
441
  * @param {string} pkgManager
355
- * @returns string
442
+ * @returns {string}
356
443
  */
357
444
  function getInstallCommand(pkgManager) {
358
445
  return pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`;
@@ -360,46 +447,74 @@ function getInstallCommand(pkgManager) {
360
447
 
361
448
  /**
362
449
  * @param {string} pkgManager
363
- * @returns string
450
+ * @returns {string}
364
451
  */
365
452
  function getStartCommand(pkgManager) {
366
453
  return pkgManager === 'yarn' ? 'yarn dev' : `${pkgManager} run dev`;
367
454
  }
368
455
 
456
+ /**
457
+ * @param {(file: string, content?: string) => void} write
458
+ * @param {string} filesDir
459
+ * @returns {void}
460
+ */
369
461
  function addTailwindDirectives(write, filesDir) {
370
462
  write(
371
463
  'src/styles.css',
372
- fs.readFileSync(path.join(filesDir, `styles.css`), 'utf-8'),
464
+ fs.readFileSync(path.join(filesDir, 'styles.css'), 'utf-8'),
373
465
  );
374
466
  }
375
467
 
468
+ /**
469
+ * @param {PackageJson} pkg
470
+ * @returns {void}
471
+ */
376
472
  function addTailwindDependencies(pkg) {
377
- pkg.devDependencies ||= {};
378
- pkg.devDependencies['tailwindcss'] = '^4.2.2';
473
+ pkg.devDependencies ??= {};
474
+ pkg.devDependencies.tailwindcss = '^4.2.2';
379
475
  pkg.devDependencies['@tailwindcss/vite'] = '^4.2.2';
380
476
  }
381
477
 
478
+ /**
479
+ * @param {PackageJson} pkg
480
+ * @param {Template} template
481
+ * @returns {void}
482
+ */
382
483
  function addYarnDevDependencies(pkg, template) {
383
- // v18
384
- if (template === 'latest' || template === 'blog' || template === 'minimal') {
385
- pkg.devDependencies['h3'] = '^1.13.0';
484
+ if (H3_TEMPLATES.includes(template)) {
485
+ pkg.devDependencies ??= {};
486
+ pkg.devDependencies.h3 = '^1.13.0';
386
487
  }
387
488
  }
388
489
 
490
+ /**
491
+ * @param {PackageJson} pkg
492
+ * @param {Template} template
493
+ * @returns {void}
494
+ */
389
495
  function addPnpmDependencies(pkg, template) {
390
- if (template === 'latest' || template === 'blog' || template === 'minimal') {
391
- pkg.dependencies['h3'] = '^1.13.0';
496
+ if (H3_TEMPLATES.includes(template)) {
497
+ pkg.dependencies ??= {};
498
+ pkg.dependencies.h3 = '^1.13.0';
392
499
  }
393
500
  }
394
501
 
502
+ /**
503
+ * @param {string} root
504
+ * @param {PackageJson} pkg
505
+ * @param {HighlighterId} highlighter
506
+ * @returns {void}
507
+ */
395
508
  function ensureSyntaxHighlighter(root, pkg, highlighter) {
509
+ const config = HIGHLIGHTERS[highlighter];
510
+
396
511
  replacePlaceholders(root, 'src/app/app.config.ts', {
397
- __HIGHLIGHTER__: HIGHLIGHTERS[highlighter].highlighter,
398
- __HIGHLIGHTER_ENTRY_POINT__: HIGHLIGHTERS[highlighter].entryPoint,
512
+ __HIGHLIGHTER__: config.highlighter,
513
+ __HIGHLIGHTER_ENTRY_POINT__: config.entryPoint,
399
514
  });
400
515
 
401
- const dependencies = HIGHLIGHTERS[highlighter].dependencies;
402
- for (const [name, version] of Object.entries(dependencies)) {
516
+ pkg.dependencies ??= {};
517
+ for (const [name, version] of Object.entries(config.dependencies)) {
403
518
  pkg.dependencies[name] = version;
404
519
  }
405
520
 
@@ -408,44 +523,94 @@ function ensureSyntaxHighlighter(root, pkg, highlighter) {
408
523
  });
409
524
  }
410
525
 
526
+ /**
527
+ * @param {Record<string, string>} obj
528
+ * @returns {Record<string, string>}
529
+ */
411
530
  function sortObjectKeys(obj) {
412
531
  return Object.keys(obj)
413
532
  .sort()
414
533
  .reduce((result, key) => {
415
534
  result[key] = obj[key];
416
535
  return result;
417
- }, {});
536
+ }, /** @type {Record<string, string>} */ ({}));
418
537
  }
419
538
 
539
+ /**
540
+ * @param {string} root
541
+ * @param {string} title
542
+ * @returns {void}
543
+ */
420
544
  function setProjectTitle(root, title) {
421
545
  replacePlaceholders(root, ['index.html', 'README.md'], {
422
546
  __PROJECT_TITLE__: title,
423
547
  });
424
548
  }
425
549
 
550
+ /**
551
+ * @param {string} root
552
+ * @param {string | readonly string[]} files
553
+ * @param {Record<string, string>} config
554
+ * @returns {void}
555
+ */
426
556
  function replacePlaceholders(root, files, config) {
427
- for (const file of toFlatArray(files)) {
557
+ for (const file of toArray(files)) {
428
558
  const filePath = path.join(root, file);
429
559
  const fileContent = fs.readFileSync(filePath, 'utf-8');
430
560
  const newFileContent = Object.keys(config).reduce(
431
561
  (content, placeholder) =>
432
- content.replace(RegExp(placeholder, 'g'), config[placeholder]),
562
+ content.replaceAll(placeholder, config[placeholder]),
433
563
  fileContent,
434
564
  );
435
565
  fs.writeFileSync(filePath, newFileContent);
436
566
  }
437
567
  }
438
568
 
439
- function toFlatArray(value) {
440
- return (Array.isArray(value) ? value : [value]).filter(Boolean).flat();
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)];
441
579
  }
442
580
 
581
+ /**
582
+ * @param {unknown} arg
583
+ * @returns {boolean | undefined}
584
+ */
443
585
  function fromBoolArg(arg) {
444
- return ['boolean', 'undefined'].includes(typeof arg)
445
- ? arg
446
- : ['', 'true'].includes(arg);
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';
447
611
  }
448
612
 
449
- init().catch((e) => {
450
- console.error(e);
613
+ init().catch((error) => {
614
+ console.error(error);
615
+ process.exitCode = 1;
451
616
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-analog",
3
- "version": "3.0.0-alpha.19",
3
+ "version": "3.0.0-alpha.20",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Brandon Roberts",
@@ -36,8 +36,8 @@
36
36
  },
37
37
  "homepage": "https://github.com/analogjs/analog/tree/main#readme",
38
38
  "dependencies": {
39
- "kolorist": "^1.6.0",
40
- "minimist": "^1.2.7",
39
+ "kolorist": "^1.8.0",
40
+ "minimist": "^1.2.8",
41
41
  "prompts": "^2.4.2"
42
42
  },
43
43
  "devDependencies": {
@@ -15,8 +15,8 @@
15
15
  "test": "ng test"
16
16
  },
17
17
  "dependencies": {
18
- "@analogjs/content": "^3.0.0-alpha.19",
19
- "@analogjs/router": "^3.0.0-alpha.19",
18
+ "@analogjs/content": "^3.0.0-alpha.20",
19
+ "@analogjs/router": "^3.0.0-alpha.20",
20
20
  "@angular/animations": "^17.2.0",
21
21
  "@angular/common": "^17.2.0",
22
22
  "@angular/compiler": "^17.2.0",
@@ -38,9 +38,9 @@
38
38
  "zone.js": "~0.14.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@analogjs/platform": "^3.0.0-alpha.19",
42
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
43
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
41
+ "@analogjs/platform": "^3.0.0-alpha.20",
42
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
43
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
44
44
  "@angular-devkit/build-angular": "^17.2.0",
45
45
  "@angular/cli": "^17.2.0",
46
46
  "@angular/compiler-cli": "^17.2.0",
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "private": true,
17
17
  "dependencies": {
18
- "@analogjs/content": "^3.0.0-alpha.19",
19
- "@analogjs/router": "^3.0.0-alpha.19",
18
+ "@analogjs/content": "^3.0.0-alpha.20",
19
+ "@analogjs/router": "^3.0.0-alpha.20",
20
20
  "@angular/animations": "^18.0.0",
21
21
  "@angular/build": "^18.0.0",
22
22
  "@angular/common": "^18.0.0",
@@ -38,9 +38,9 @@
38
38
  "zone.js": "~0.14.3"
39
39
  },
40
40
  "devDependencies": {
41
- "@analogjs/platform": "^3.0.0-alpha.19",
42
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
43
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
41
+ "@analogjs/platform": "^3.0.0-alpha.20",
42
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
43
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
44
44
  "@angular/cli": "^18.0.0",
45
45
  "@angular/compiler-cli": "^18.0.0",
46
46
  "jsdom": "^22.0.0",
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "private": true,
17
17
  "dependencies": {
18
- "@analogjs/content": "^3.0.0-alpha.19",
19
- "@analogjs/router": "^3.0.0-alpha.19",
18
+ "@analogjs/content": "^3.0.0-alpha.20",
19
+ "@analogjs/router": "^3.0.0-alpha.20",
20
20
  "@angular/animations": "^19.0.0",
21
21
  "@angular/common": "^19.0.0",
22
22
  "@angular/compiler": "^19.0.0",
@@ -37,9 +37,9 @@
37
37
  "zone.js": "~0.15.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@analogjs/platform": "^3.0.0-alpha.19",
41
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
42
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
40
+ "@analogjs/platform": "^3.0.0-alpha.20",
41
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
42
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
43
43
  "@angular-devkit/build-angular": "^19.0.0",
44
44
  "@angular/build": "^19.0.0",
45
45
  "@angular/cli": "^19.0.0",
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "private": true,
18
18
  "dependencies": {
19
- "@analogjs/content": "^3.0.0-alpha.19",
20
- "@analogjs/router": "^3.0.0-alpha.19",
19
+ "@analogjs/content": "^3.0.0-alpha.20",
20
+ "@analogjs/router": "^3.0.0-alpha.20",
21
21
  "@angular/animations": "^20.0.0",
22
22
  "@angular/common": "^20.0.0",
23
23
  "@angular/compiler": "^20.0.0",
@@ -37,9 +37,9 @@
37
37
  "zone.js": "~0.15.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@analogjs/platform": "^3.0.0-alpha.19",
41
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
42
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
40
+ "@analogjs/platform": "^3.0.0-alpha.20",
41
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
42
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
43
43
  "@angular-devkit/build-angular": "^20.0.0",
44
44
  "@angular/build": "^20.0.0",
45
45
  "@angular/cli": "^20.0.0",
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "private": true,
18
18
  "dependencies": {
19
- "@analogjs/content": "^3.0.0-alpha.19",
20
- "@analogjs/router": "^3.0.0-alpha.19",
19
+ "@analogjs/content": "^3.0.0-alpha.20",
20
+ "@analogjs/router": "^3.0.0-alpha.20",
21
21
  "@angular/animations": "^21.0.0",
22
22
  "@angular/common": "^21.0.0",
23
23
  "@angular/compiler": "^21.0.0",
@@ -35,9 +35,9 @@
35
35
  "tslib": "^2.3.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@analogjs/platform": "^3.0.0-alpha.19",
39
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
40
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
38
+ "@analogjs/platform": "^3.0.0-alpha.20",
39
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
40
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
41
41
  "@angular/build": "^21.0.0",
42
42
  "@angular/cli": "^21.0.0",
43
43
  "@angular/compiler-cli": "^21.0.0",
@@ -8,14 +8,18 @@ import {
8
8
  provideBrowserGlobalErrorListeners,
9
9
  } from '@angular/core';
10
10
  import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
11
- import { provideFileRouter, requestContextInterceptor } from '@analogjs/router';
11
+ import {
12
+ provideFileRouter,
13
+ requestContextInterceptor,
14
+ } from '@analogjs/router';
15
+ import { withContentRoutes } from '@analogjs/router/content';
12
16
  import { provideContent, withMarkdownRenderer } from '@analogjs/content';
13
17
  import { __HIGHLIGHTER__ } from '@analogjs/content/__HIGHLIGHTER_ENTRY_POINT__';
14
18
 
15
19
  export const appConfig: ApplicationConfig = {
16
20
  providers: [
17
21
  provideBrowserGlobalErrorListeners(),
18
- provideFileRouter(),
22
+ provideFileRouter(withContentRoutes()),
19
23
  provideHttpClient(
20
24
  withFetch(),
21
25
  withInterceptors([requestContextInterceptor])
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "private": true,
18
18
  "dependencies": {
19
- "@analogjs/content": "^3.0.0-alpha.19",
20
- "@analogjs/router": "^3.0.0-alpha.19",
19
+ "@analogjs/content": "^3.0.0-alpha.20",
20
+ "@analogjs/router": "^3.0.0-alpha.20",
21
21
  "@angular/animations": "^21.0.0",
22
22
  "@angular/common": "^21.0.0",
23
23
  "@angular/compiler": "^21.0.0",
@@ -36,9 +36,9 @@
36
36
  "tslib": "^2.3.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@analogjs/platform": "^3.0.0-alpha.19",
40
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
41
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
39
+ "@analogjs/platform": "^3.0.0-alpha.20",
40
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
41
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
42
42
  "@angular/build": "^21.0.0",
43
43
  "@angular/cli": "^21.0.0",
44
44
  "@angular/compiler-cli": "^21.0.0",
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "private": true,
18
18
  "dependencies": {
19
- "@analogjs/content": "^3.0.0-alpha.19",
20
- "@analogjs/router": "^3.0.0-alpha.19",
19
+ "@analogjs/content": "^3.0.0-alpha.20",
20
+ "@analogjs/router": "^3.0.0-alpha.20",
21
21
  "@angular/animations": "^21.0.0",
22
22
  "@angular/common": "^21.0.0",
23
23
  "@angular/compiler": "^21.0.0",
@@ -36,9 +36,9 @@
36
36
  "tslib": "^2.3.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@analogjs/platform": "^3.0.0-alpha.19",
40
- "@analogjs/vite-plugin-angular": "^3.0.0-alpha.19",
41
- "@analogjs/vitest-angular": "^3.0.0-alpha.19",
39
+ "@analogjs/platform": "^3.0.0-alpha.20",
40
+ "@analogjs/vite-plugin-angular": "^3.0.0-alpha.20",
41
+ "@analogjs/vitest-angular": "^3.0.0-alpha.20",
42
42
  "@angular/build": "^21.0.0",
43
43
  "@angular/cli": "^21.0.0",
44
44
  "@angular/compiler-cli": "^21.0.0",