create-dev-to 1.3.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +210 -71
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,77 +5,97 @@ import { spawn, execSync } from "node:child_process";
5
5
  import process from "node:process";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import readline from "node:readline";
8
+ import os from "node:os";
8
9
  import * as clack from "@clack/prompts";
9
10
  import { red, cyan, yellow, green, dim } from "kolorist";
10
11
  import { InstallLogger } from "./installLogger.js";
11
12
  import { displayInstallSummary } from "./visualComponents.js";
12
13
  const PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
13
14
  const __BUILD_INFO__ = {
14
- commit: "36c1f26",
15
+ commit: "395653e",
15
16
  branch: "main",
16
- buildTime: "2026-01-10 15:26",
17
- version: "1.3.3"
17
+ buildTime: "2026-01-11 02:43",
18
+ version: "1.5.0"
18
19
  };
19
20
  const FRAMEWORKS = [
20
21
  {
21
22
  name: "react",
22
23
  display: "React",
23
24
  color: cyan,
24
- supported: true
25
+ variants: [
26
+ {
27
+ name: "react-ts",
28
+ display: "TypeScript",
29
+ color: cyan
30
+ },
31
+ {
32
+ name: "react-compiler-ts",
33
+ display: "TypeScript + React Compiler",
34
+ color: cyan
35
+ },
36
+ {
37
+ name: "react-swc-ts",
38
+ display: "TypeScript + SWC",
39
+ color: cyan
40
+ },
41
+ {
42
+ name: "react",
43
+ display: "JavaScript",
44
+ color: yellow
45
+ },
46
+ {
47
+ name: "react-compiler",
48
+ display: "JavaScript + React Compiler",
49
+ color: yellow
50
+ },
51
+ {
52
+ name: "react-swc",
53
+ display: "JavaScript + SWC",
54
+ color: yellow
55
+ }
56
+ ]
25
57
  },
26
58
  {
27
59
  name: "vue",
28
60
  display: "Vue",
29
61
  color: green,
30
- supported: false
62
+ variants: []
31
63
  },
32
64
  {
33
65
  name: "svelte",
34
66
  display: "Svelte",
35
67
  color: red,
36
- supported: false
68
+ variants: []
37
69
  },
38
70
  {
39
71
  name: "solid",
40
72
  display: "Solid",
41
73
  color: cyan,
42
- supported: false
74
+ variants: []
43
75
  },
44
76
  {
45
77
  name: "preact",
46
78
  display: "Preact",
47
79
  color: cyan,
48
- supported: false
80
+ variants: []
49
81
  },
50
82
  {
51
83
  name: "lit",
52
84
  display: "Lit",
53
85
  color: yellow,
54
- supported: false
86
+ variants: []
55
87
  },
56
88
  {
57
89
  name: "qwik",
58
90
  display: "Qwik",
59
91
  color: cyan,
60
- supported: false
92
+ variants: []
61
93
  },
62
94
  {
63
95
  name: "vanilla",
64
96
  display: "Vanilla",
65
97
  color: yellow,
66
- supported: false
67
- }
68
- ];
69
- const REACT_TEMPLATES = [
70
- {
71
- name: "react-ts",
72
- display: "TypeScript",
73
- color: cyan
74
- },
75
- {
76
- name: "react",
77
- display: "JavaScript",
78
- color: yellow
98
+ variants: []
79
99
  }
80
100
  ];
81
101
  const TEMPLATE_SOURCES = [
@@ -235,6 +255,19 @@ function findViteConfigFile(projectDir) {
235
255
  return null;
236
256
  }
237
257
  async function cloneViteTemplate(template, targetDir, packageManager, spinner) {
258
+ const cacheMetadata = getTemplateCacheMetadata(template);
259
+ if (cacheMetadata) {
260
+ const shortHash = cacheMetadata.commitHash.slice(0, 8);
261
+ spinner.message(`Checking cache ${dim(`(${shortHash})`)}`);
262
+ const cachedRestored = await restoreFromCache(template, targetDir);
263
+ if (cachedRestored) {
264
+ spinner.stop(`Template restored from cache ${dim(`(${shortHash})`)}`);
265
+ return { fromCache: true, commitHash: cacheMetadata.commitHash };
266
+ }
267
+ spinner.message(`Downloading template ${dim("(cache outdated)")}`);
268
+ } else {
269
+ spinner.message("Downloading template");
270
+ }
238
271
  const errors = [];
239
272
  for (let i = 0; i < TEMPLATE_SOURCES.length; i++) {
240
273
  const source = TEMPLATE_SOURCES[i];
@@ -243,6 +276,12 @@ async function cloneViteTemplate(template, targetDir, packageManager, spinner) {
243
276
  if (i > 0) {
244
277
  spinner.message(`Trying ${source.name}...`);
245
278
  }
279
+ spinner.message(`Checking template version from ${source.name}...`);
280
+ const commitHash = getTemplateCommitHash(source, template);
281
+ if (commitHash) {
282
+ const shortHash = commitHash.slice(0, 8);
283
+ spinner.message(`Latest template version: ${shortHash}`);
284
+ }
246
285
  const { command, args } = source.getCloneCommand(template, targetDir, packageManager);
247
286
  if (source.isGitBased) {
248
287
  const tempCloneDir = path.join(process.cwd(), `.tmp-clone-${randomUUID()}`);
@@ -274,7 +313,13 @@ async function cloneViteTemplate(template, targetDir, packageManager, spinner) {
274
313
  } else {
275
314
  await run(command, args, process.cwd());
276
315
  }
277
- return;
316
+ if (commitHash) {
317
+ const shortHash = commitHash.slice(0, 8);
318
+ spinner.message(`Caching template ${dim(`(${shortHash})`)} for future use...`);
319
+ await saveToCache(template, targetDir, commitHash);
320
+ spinner.message(`Template cached ${dim(`(${shortHash})`)}`);
321
+ }
322
+ return { fromCache: false, commitHash };
278
323
  } catch (error) {
279
324
  if (tempTargetDir && fs.existsSync(tempTargetDir)) {
280
325
  fs.rmSync(tempTargetDir, { recursive: true, force: true });
@@ -298,6 +343,7 @@ Please check your network connection or try again later.`
298
343
  }
299
344
  }
300
345
  }
346
+ throw new Error("Unexpected end of cloneViteTemplate function");
301
347
  }
302
348
  function getDegitCommandForPM(pm) {
303
349
  switch (pm) {
@@ -323,6 +369,95 @@ function getDegitCommandForPM(pm) {
323
369
  };
324
370
  }
325
371
  }
372
+ function getTemplateCacheDir() {
373
+ const cacheDir = path.join(process.env.HOME || process.env.USERPROFILE || os.homedir(), ".create-dev-to-cache");
374
+ return cacheDir;
375
+ }
376
+ function getTemplateCommitHash(source, template) {
377
+ try {
378
+ if (source.name === "GitHub") {
379
+ const templatePath = `packages/create-vite/template-${template}`;
380
+ const hash = execSync(
381
+ `git ls-remote https://github.com/vitejs/vite.git HEAD | cut -f1`,
382
+ { stdio: "pipe" }
383
+ ).toString().trim();
384
+ try {
385
+ const apiUrl = `https://api.github.com/repos/vitejs/vite/commits?path=${templatePath}&per_page=1`;
386
+ const apiHash = execSync(
387
+ `curl -s "${apiUrl}" | grep -m 1 '"sha"' | cut -d'"' -f4`,
388
+ { stdio: "pipe" }
389
+ ).toString().trim();
390
+ return apiHash || hash;
391
+ } catch {
392
+ return hash;
393
+ }
394
+ } else if (source.name === "Gitee Mirror (\u56FD\u5185\u955C\u50CF)") {
395
+ const hash = execSync("git ls-remote https://gitee.com/mirrors/ViteJS.git HEAD", { stdio: "pipe" }).toString().split(" ")[0].trim();
396
+ return hash;
397
+ } else {
398
+ return null;
399
+ }
400
+ } catch {
401
+ return null;
402
+ }
403
+ }
404
+ function getTemplateCachePath(template, commitHash) {
405
+ const cacheDir = getTemplateCacheDir();
406
+ const templateCacheFile = `${template}-${commitHash.slice(0, 8)}.zip`;
407
+ return path.join(cacheDir, templateCacheFile);
408
+ }
409
+ function getTemplateCacheMetadata(template) {
410
+ const cacheDir = getTemplateCacheDir();
411
+ const metadataFile = path.join(cacheDir, `${template}.json`);
412
+ try {
413
+ if (fs.existsSync(metadataFile)) {
414
+ const metadata = JSON.parse(fs.readFileSync(metadataFile, "utf-8"));
415
+ return metadata;
416
+ }
417
+ } catch {
418
+ }
419
+ return null;
420
+ }
421
+ function saveTemplateCacheMetadata(template, commitHash) {
422
+ const cacheDir = getTemplateCacheDir();
423
+ if (!fs.existsSync(cacheDir)) {
424
+ fs.mkdirSync(cacheDir, { recursive: true });
425
+ }
426
+ const metadataFile = path.join(cacheDir, `${template}.json`);
427
+ fs.writeFileSync(metadataFile, JSON.stringify({ commitHash }, null, 2));
428
+ }
429
+ async function restoreFromCache(template, targetDir) {
430
+ try {
431
+ const metadata = getTemplateCacheMetadata(template);
432
+ if (!metadata) return false;
433
+ const cachePath = getTemplateCachePath(template, metadata.commitHash);
434
+ const cacheSourceDir = path.join(path.dirname(cachePath), `${template}-${metadata.commitHash.slice(0, 8)}`);
435
+ if (fs.existsSync(cacheSourceDir)) {
436
+ if (fs.existsSync(targetDir)) {
437
+ fs.rmSync(targetDir, { recursive: true, force: true });
438
+ }
439
+ copyDir(cacheSourceDir, targetDir);
440
+ return true;
441
+ }
442
+ } catch {
443
+ }
444
+ return false;
445
+ }
446
+ async function saveToCache(template, sourceDir, commitHash) {
447
+ try {
448
+ const cacheDir = getTemplateCacheDir();
449
+ if (!fs.existsSync(cacheDir)) {
450
+ fs.mkdirSync(cacheDir, { recursive: true });
451
+ }
452
+ const cachePath = getTemplateCachePath(template, commitHash);
453
+ const cacheSourceDir = path.join(path.dirname(cachePath), `${template}-${commitHash.slice(0, 8)}`);
454
+ if (!fs.existsSync(cacheSourceDir)) {
455
+ copyDir(sourceDir, cacheSourceDir);
456
+ }
457
+ saveTemplateCacheMetadata(template, commitHash);
458
+ } catch {
459
+ }
460
+ }
326
461
  function injectPluginIntoViteConfig(content, pluginPackage, pluginName) {
327
462
  const hasImport = new RegExp(`['"]${pluginPackage.replace(/\//g, "\\/")}['"]`).test(content);
328
463
  const hasCall = content.includes(`${pluginName}(`);
@@ -399,8 +534,16 @@ ${newPlugin}
399
534
  }
400
535
  return out;
401
536
  }
402
- function updatePluginComponentName(content, pluginName, componentName) {
537
+ function updatePluginComponentName(content, pluginName, componentName, projectName) {
538
+ const defaultComponentName = projectName.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
403
539
  const pluginCall = `${pluginName}()`;
540
+ if (componentName === defaultComponentName) {
541
+ const newPluginCall2 = `${pluginName}('${componentName}')`;
542
+ if (content.includes(pluginCall)) {
543
+ return content.replace(pluginCall, newPluginCall2);
544
+ }
545
+ return content;
546
+ }
404
547
  const newPluginCall = `${pluginName}({
405
548
  ${componentName}: 'src/${componentName}/index.tsx',
406
549
  })`;
@@ -635,8 +778,8 @@ async function init() {
635
778
  });
636
779
  },
637
780
  componentName: ({ results }) => {
638
- const projectName = results.projectName || "dev-to-app";
639
- const defaultComponentName = projectName.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
781
+ const projectName2 = results.projectName || "dev-to-app";
782
+ const defaultComponentName = projectName2.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
640
783
  return clack.text({
641
784
  message: `First component name ${dim("\n\u2502 Leave blank to default to project name.You can modify it in vite config later.\n")}`,
642
785
  placeholder: defaultComponentName,
@@ -651,7 +794,7 @@ async function init() {
651
794
  }
652
795
  }
653
796
  );
654
- const { shouldOverwrite, componentName } = project;
797
+ const { shouldOverwrite, componentName, projectName } = project;
655
798
  if (shouldOverwrite) {
656
799
  emptyDir(path.join(cwd, targetDir));
657
800
  }
@@ -669,24 +812,23 @@ async function init() {
669
812
  }
670
813
  packageManager = pmChoice;
671
814
  }
672
- const framework = await clack.select({
815
+ const selectedFramework = await clack.select({
673
816
  message: "Select a framework:",
674
817
  options: FRAMEWORKS.map((fw) => ({
675
- value: fw.name,
676
- label: fw.supported ? fw.color(fw.display) : `${fw.color(fw.display)} ${dim("(Coming soon)")}`,
677
- hint: fw.supported ? void 0 : "Not yet supported"
818
+ value: fw,
819
+ label: fw.variants.length > 0 ? fw.color(fw.display) : `${fw.color(fw.display)} ${dim("(Coming soon)")}`,
820
+ hint: fw.variants.length > 0 ? void 0 : "Not yet supported"
678
821
  })),
679
- initialValue: "react"
822
+ initialValue: FRAMEWORKS[0]
680
823
  });
681
- if (clack.isCancel(framework)) {
824
+ if (clack.isCancel(selectedFramework)) {
682
825
  clack.cancel("Operation cancelled.");
683
826
  process.exit(0);
684
827
  }
685
- const selectedFramework = FRAMEWORKS.find((fw) => fw.name === framework);
686
- if (!selectedFramework?.supported) {
687
- clack.outro(yellow(`\u26A0\uFE0F ${selectedFramework?.display} support is coming soon!`));
828
+ if (selectedFramework.variants.length === 0) {
829
+ clack.outro(yellow(`\u26A0\uFE0F ${selectedFramework.display} support is coming soon!`));
688
830
  clack.note(
689
- `We're working hard to add support for ${selectedFramework?.display}.
831
+ `We're working hard to add support for ${selectedFramework.display}.
690
832
 
691
833
  For now, please use React or stay tuned for updates!`,
692
834
  "Roadmap"
@@ -695,54 +837,40 @@ For now, please use React or stay tuned for updates!`,
695
837
  }
696
838
  const variant = await clack.select({
697
839
  message: "Select a variant:",
698
- options: REACT_TEMPLATES.map((template2) => ({
699
- value: template2.name,
700
- label: template2.color(template2.display)
840
+ options: selectedFramework.variants.map((v) => ({
841
+ value: v.name,
842
+ label: v.color(v.display)
701
843
  }))
702
844
  });
703
845
  if (clack.isCancel(variant)) {
704
846
  clack.cancel("Operation cancelled.");
705
847
  process.exit(0);
706
848
  }
707
- const template = variant;
708
- const shouldUseSWC = await clack.confirm({
709
- message: "Use SWC for faster transpilation? (Optional)",
710
- initialValue: false
711
- });
712
- if (clack.isCancel(shouldUseSWC)) {
713
- clack.cancel("Operation cancelled.");
714
- process.exit(0);
715
- }
716
- const shouldUseReactCompiler = await clack.confirm({
717
- message: "Use React Compiler? (Experimental)",
718
- initialValue: false
719
- });
720
- if (clack.isCancel(shouldUseReactCompiler)) {
721
- clack.cancel("Operation cancelled.");
722
- process.exit(0);
723
- }
724
- const shouldUseRolldown = await clack.confirm({
725
- message: "Use Rolldown for bundling? (Experimental)",
726
- initialValue: false
727
- });
728
- if (clack.isCancel(shouldUseRolldown)) {
729
- clack.cancel("Operation cancelled.");
730
- process.exit(0);
731
- }
849
+ let template = variant;
732
850
  const root = path.join(cwd, targetDir);
733
851
  const spinner = clack.spinner();
734
852
  spinner.start("Scaffolding project");
735
- await cloneViteTemplate(template, root, packageManager, spinner);
853
+ let isReactSwc = false;
854
+ if (template.includes("-swc")) {
855
+ isReactSwc = true;
856
+ template = template.replace("-swc", "");
857
+ }
858
+ let isReactCompiler = false;
859
+ if (template.includes("-compiler")) {
860
+ isReactCompiler = true;
861
+ template = template.replace("-compiler", "");
862
+ }
863
+ const cloneResult = await cloneViteTemplate(template, root, packageManager, spinner);
736
864
  const pkgPath = path.join(root, "package.json");
737
865
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
738
866
  pkg.name = toValidPackageName(getProjectName());
739
867
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
740
868
  const isTs = template.includes("ts");
741
- if (shouldUseSWC) {
869
+ if (isReactSwc) {
742
870
  spinner.message("Setting up SWC...");
743
871
  setupReactSWC(root, isTs);
744
872
  }
745
- if (shouldUseReactCompiler) {
873
+ if (isReactCompiler) {
746
874
  spinner.message("Setting up React Compiler...");
747
875
  setupReactCompiler(root, isTs);
748
876
  }
@@ -754,17 +882,28 @@ For now, please use React or stay tuned for updates!`,
754
882
  if (viteConfigPath) {
755
883
  let patched = fs.readFileSync(viteConfigPath, "utf-8");
756
884
  patched = injectPluginIntoViteConfig(patched, pluginPackage, pluginName);
757
- patched = updatePluginComponentName(patched, pluginName, componentName);
885
+ patched = updatePluginComponentName(patched, pluginName, componentName, projectName);
758
886
  fs.writeFileSync(viteConfigPath, patched);
759
887
  }
760
888
  spinner.message(`Creating component ${componentName}...`);
761
889
  createComponentFile(root, componentName, isTs);
762
- spinner.stop("Project created");
890
+ let completionMessage = "Project created";
891
+ if (cloneResult.commitHash) {
892
+ const shortHash = cloneResult.commitHash.slice(0, 8);
893
+ if (cloneResult.fromCache) {
894
+ completionMessage += ` ${dim(`with cached template (${shortHash})`)}`;
895
+ } else {
896
+ completionMessage += ` ${dim(`(${shortHash})`)}`;
897
+ }
898
+ }
899
+ spinner.stop(completionMessage);
763
900
  const shouldInstall = await clack.confirm({
764
901
  message: "Install dependencies and start dev server?",
765
902
  initialValue: true
766
903
  });
767
904
  if (clack.isCancel(shouldInstall)) {
905
+ clack.cancel("Operation cancelled.");
906
+ process.exit(0);
768
907
  }
769
908
  if (shouldInstall) {
770
909
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-dev-to",
3
- "version": "1.3.3",
3
+ "version": "1.5.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {