create-bestax 4.2.12 → 4.2.14

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 (57) hide show
  1. package/README.md +22 -9
  2. package/dist/cli.d.ts +13 -3
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +20 -4
  5. package/dist/constants.d.ts +7 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +18 -3
  8. package/dist/file-system.d.ts +7 -0
  9. package/dist/file-system.d.ts.map +1 -1
  10. package/dist/file-system.js +15 -0
  11. package/dist/index.js +2 -2
  12. package/dist/project-creator.d.ts +21 -1
  13. package/dist/project-creator.d.ts.map +1 -1
  14. package/dist/project-creator.js +84 -51
  15. package/dist/prompts.d.ts +7 -2
  16. package/dist/prompts.d.ts.map +1 -1
  17. package/dist/prompts.js +8 -3
  18. package/dist/validators.d.ts +7 -0
  19. package/dist/validators.d.ts.map +1 -1
  20. package/dist/validators.js +13 -1
  21. package/package.json +6 -3
  22. package/templates/skills/bestax-custom-component/SKILL.md +21 -4
  23. package/templates/skills/bestax-custom-component/examples/stat-card.tsx +3 -4
  24. package/templates/skills/bestax-custom-component/references/api.md +103 -11
  25. package/templates/skills/bestax-custom-component/references/component-catalog.md +12 -5
  26. package/templates/skills/bestax-custom-component/references/library-contributor.md +23 -0
  27. package/templates/skills/bestax-form/SKILL.md +28 -17
  28. package/templates/skills/bestax-form/references/api.md +83 -23
  29. package/templates/skills/bestax-form/references/patterns.md +19 -0
  30. package/templates/skills/bestax-icons/SKILL.md +8 -6
  31. package/templates/skills/bestax-icons/examples/icon-usage.tsx +3 -3
  32. package/templates/skills/bestax-icons/references/icon-libraries.md +3 -3
  33. package/templates/skills/bestax-layout-scaffold/SKILL.md +17 -4
  34. package/templates/skills/bestax-layout-scaffold/examples/app-shell.tsx +1 -1
  35. package/templates/skills/bestax-layout-scaffold/examples/card-grid.tsx +0 -1
  36. package/templates/skills/bestax-layout-scaffold/examples/centered.tsx +0 -1
  37. package/templates/skills/bestax-layout-scaffold/examples/content-page.tsx +0 -1
  38. package/templates/skills/bestax-layout-scaffold/examples/landing.tsx +1 -1
  39. package/templates/skills/bestax-layout-scaffold/references/layout-components.md +22 -15
  40. package/templates/skills/bestax-migrate/SKILL.md +12 -7
  41. package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +9 -9
  42. package/templates/skills/bestax-migrate/references/bloomer/unmappables.md +19 -2
  43. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +286 -41
  44. package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +125 -48
  45. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +221 -39
  46. package/templates/skills/bestax-migrate/references/rbx/component-map.md +15 -15
  47. package/templates/skills/bestax-migrate/references/rbx/prop-map.md +17 -19
  48. package/templates/skills/bestax-migrate/references/rbx/unmappables.md +17 -0
  49. package/templates/skills/bestax-migrate/references/react-bulma-components/component-map.md +34 -34
  50. package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +34 -7
  51. package/templates/skills/bestax-optimize/SKILL.md +13 -8
  52. package/templates/skills/bestax-optimize/references/modular-build.md +29 -6
  53. package/templates/skills/bestax-theming/SKILL.md +4 -3
  54. package/templates/skills/bestax-theming/examples/dark-mode.tsx +5 -4
  55. package/templates/skills/bestax-theming/examples/theme-config.tsx +2 -2
  56. package/templates/skills/bestax-theming/references/css-variables.md +51 -8
  57. package/templates/skills/bestax-theming/references/themeable-components.md +12 -10
package/README.md CHANGED
@@ -29,9 +29,6 @@ npm create bestax@latest my-app
29
29
 
30
30
  # Using npx
31
31
  npx create-bestax@latest my-app
32
-
33
- # Alternative naming
34
- npm create bestax-bulma@latest my-app
35
32
  ```
36
33
 
37
34
  ### Interactive Mode
@@ -44,9 +41,10 @@ npm create bestax@latest
44
41
 
45
42
  You'll be asked to:
46
43
 
47
- 1. Enter a project name
44
+ 1. Enter a project name (and, if that directory already holds files, confirm removing them;
45
+ nothing is removed until you have answered the last question)
48
46
  2. Select a framework (Vite or Vite + TypeScript)
49
- 3. Choose a Bulma CSS flavor (Complete or Minimal)
47
+ 3. Choose a Bulma CSS flavor (the `--bulma` options below)
50
48
  4. Select an icon library (Font Awesome, Material Icons, etc.)
51
49
  5. Choose whether to install the bestax AI skills into `.claude/skills/`
52
50
 
@@ -55,12 +53,17 @@ Every prompt has a flag equivalent, so the whole flow is scriptable and CI-frien
55
53
  ### Command Line Options
56
54
 
57
55
  ```bash
58
- npm create bestax@latest [project-directory] [options]
56
+ npm create bestax@latest [project-directory] -- [options]
59
57
  ```
60
58
 
59
+ Put the options after `--`. npm needs it to pass them on at all, and so does bun, whose
60
+ `bun create` rejects short flags such as `-t` and `-y` itself. pnpm and Yarn 1 accept the options
61
+ with or without it.
62
+
61
63
  **Arguments:**
62
64
 
63
- - `[project-directory]` - Optional project directory name
65
+ - `[project-directory]` - Optional project directory name. It also becomes the `package.json`
66
+ name, lower-cased the way `npm init` does and without a leading `_`, since npm rejects both.
64
67
 
65
68
  **Options:**
66
69
 
@@ -91,14 +94,24 @@ npm create bestax@latest [project-directory] [options]
91
94
  - Icon library: `none`
92
95
  - AI skills: installed (pass `--no-skills` to opt out)
93
96
 
97
+ `-y` never deletes files. If the target directory is not empty, the run stops, lists what is
98
+ there, and changes nothing; add `--overwrite` to empty it first. Earlier versions emptied the
99
+ directory under `-y` alone, so a script that relied on that now needs `--overwrite`.
100
+
101
+ - `--overwrite` - Remove existing files if the target directory is not empty, without asking.
102
+ Every other flag is checked first, so a typo stops the run before anything is deleted.
103
+
94
104
  **Example:**
95
105
 
96
106
  ```bash
97
107
  # Create a TypeScript project with Font Awesome icons
98
- npm create bestax@latest my-app -t vite-ts -i fontawesome -b complete
108
+ npm create bestax@latest my-app -- -t vite-ts -i fontawesome -b complete
99
109
 
100
110
  # Use all defaults (skip prompts)
101
- npm create bestax@latest my-app -y
111
+ npm create bestax@latest my-app -- -y
112
+
113
+ # The same with bun
114
+ bun create bestax@latest my-app -- -y
102
115
  ```
103
116
 
104
117
  ## Templates
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { ProjectCreator, type CLIOptions } from './project-creator.js';
3
+ import { ProjectCreator, type CLIOptions, type ExistingDirectoryPlan } from './project-creator.js';
4
4
  export { validateProjectName } from './validators.js';
5
5
  export { TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS } from './constants.js';
6
6
  export type { ProjectConfig, CLIOptions } from './project-creator.js';
@@ -10,11 +10,21 @@ export { displayHeader, displaySuccess } from './display.js';
10
10
  export { promptProjectName, promptOverwriteDirectory, promptTemplate as selectTemplate, promptIconLibrary as selectIconLibrary, promptBulmaFlavor as selectBulmaFlavor, } from './prompts.js';
11
11
  export { checkDirectoryExists, isDirectoryEmpty, copyDirectory, updatePackageJson, } from './file-system.js';
12
12
  export declare function getProjectName(projectDir?: string): Promise<string | null>;
13
- export declare function checkExistingDirectory(targetPath: string, targetDir: string): Promise<boolean>;
13
+ export declare function checkExistingDirectory(targetPath: string, targetDir: string): Promise<ExistingDirectoryPlan>;
14
14
  export declare function getTemplatePath(template: string): string;
15
15
  export declare function copyTemplate(templatePath: string, targetPath: string): Promise<void>;
16
16
  export declare function createProject(projectDir?: string, options?: CLIOptions): Promise<void>;
17
17
  export declare function createCLI(): Command;
18
18
  export declare function isMainModule(importMetaUrl: string, argv1: string): boolean;
19
- export declare function runCLI(): Promise<void>;
19
+ /**
20
+ * pnpm, bun and bunx hand a `--` to the scaffolder as it was typed, where npm
21
+ * and Yarn 1 strip it, so `pnpm create bestax my-app -- -t vite-ts` arrives as
22
+ * `my-app -- -t vite-ts`. Commander reads `--` as the end of the options, and
23
+ * every flag after it became an extra argument: "too many arguments" (#950).
24
+ * Bun users need that `--`, because `bun create` rejects short flags such as
25
+ * `-t` and `-y` itself. So the first `--` after the script path is dropped
26
+ * before parsing, and a second keeps its usual meaning.
27
+ */
28
+ export declare function dropForwardedSeparator(argv: readonly string[]): string[];
29
+ export declare function runCLI(argv?: readonly string[]): Promise<void>;
20
30
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,IAAI,cAAc,EAChC,iBAAiB,IAAI,iBAAiB,EACtC,iBAAiB,IAAI,iBAAiB,GACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAGf;AAcD,wBAAgB,SAAS,IAAI,OAAO,CA+BnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5C"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EACL,cAAc,EACd,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,IAAI,cAAc,EAChC,iBAAiB,IAAI,iBAAiB,EACtC,iBAAiB,IAAI,iBAAiB,GACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,CAAC,CAGhC;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAGf;AAcD,wBAAgB,SAAS,IAAI,OAAO,CAsCnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAKxE;AAED,wBAAsB,MAAM,CAC1B,IAAI,GAAE,SAAS,MAAM,EAAiB,GACrC,OAAO,CAAC,IAAI,CAAC,CAGf"}
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { Command } from 'commander';
3
3
  import { readFileSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
- import { ProjectCreator } from './project-creator.js';
6
+ import { ProjectCreator, } from './project-creator.js';
7
7
  import { copyDirectory } from './file-system.js';
8
8
  // Re-export for backward compatibility with tests
9
9
  export { validateProjectName } from './validators.js';
@@ -61,7 +61,8 @@ export function createCLI() {
61
61
  .option('--no-skills', 'do not install the bestax AI skills')
62
62
  .option('--telemetry', 'enable anonymous usage telemetry (https://bestax.io/docs/guides/telemetry)')
63
63
  .option('--no-telemetry', 'disable anonymous usage telemetry')
64
- .option('-y, --yes', 'skip prompts and use defaults or provided options')
64
+ .option('-y, --yes', 'skip prompts and use defaults or provided options (never removes files: see --overwrite)')
65
+ .option('--overwrite', 'remove existing files if the target directory is not empty')
65
66
  .action(async (projectDir, options) => {
66
67
  await projectCreator.create(projectDir, options);
67
68
  });
@@ -70,9 +71,24 @@ export function createCLI() {
70
71
  export function isMainModule(importMetaUrl, argv1) {
71
72
  return importMetaUrl === `file://${argv1}`;
72
73
  }
73
- export async function runCLI() {
74
+ /**
75
+ * pnpm, bun and bunx hand a `--` to the scaffolder as it was typed, where npm
76
+ * and Yarn 1 strip it, so `pnpm create bestax my-app -- -t vite-ts` arrives as
77
+ * `my-app -- -t vite-ts`. Commander reads `--` as the end of the options, and
78
+ * every flag after it became an extra argument: "too many arguments" (#950).
79
+ * Bun users need that `--`, because `bun create` rejects short flags such as
80
+ * `-t` and `-y` itself. So the first `--` after the script path is dropped
81
+ * before parsing, and a second keeps its usual meaning.
82
+ */
83
+ export function dropForwardedSeparator(argv) {
84
+ const separator = argv.indexOf('--', 2);
85
+ return separator === -1
86
+ ? [...argv]
87
+ : [...argv.slice(0, separator), ...argv.slice(separator + 1)];
88
+ }
89
+ export async function runCLI(argv = process.argv) {
74
90
  const program = createCLI();
75
- await program.parseAsync();
91
+ await program.parseAsync(dropForwardedSeparator(argv));
76
92
  }
77
93
  // Only run if this is the main module
78
94
  /* istanbul ignore next */
@@ -8,14 +8,19 @@ export declare const TEMPLATES: Template[];
8
8
  export declare const DEFAULT_PROJECT_NAME = "my-bestax-app";
9
9
  export declare const MAX_PROJECT_NAME_LENGTH = 214;
10
10
  export declare const PROJECT_NAME_REGEX: RegExp;
11
+ export declare const MAX_LISTED_ENTRIES = 5;
11
12
  export declare const MESSAGES: {
12
13
  readonly PROJECT_NAME_REQUIRED: "Project name is required";
13
14
  readonly PROJECT_NAME_TOO_LONG: "Project name too long";
14
15
  readonly PROJECT_NAME_INVALID_CHARS: "Project name can only contain letters, numbers, dots, dashes and underscores";
15
16
  readonly PROJECT_NAME_DOT: "Project name cannot start with a dot (names like \".\" or \"..\" would scaffold outside a new directory) — pass a directory name";
17
+ readonly PROJECT_NAME_NO_PACKAGE_NAME: "Project name must contain a letter, number or dash (npm package names cannot start with \"_\" or \".\")";
18
+ readonly PACKAGE_NAME_NORMALIZED: (name: string) => string;
16
19
  readonly OPERATION_CANCELLED: "✖ Operation cancelled";
17
20
  readonly NO_TTY: string;
18
- readonly DIRECTORY_NOT_EMPTY: (dir: string) => string;
21
+ readonly DIRECTORY_NOT_EMPTY: (dir: string, entries: string[]) => string;
22
+ readonly NOT_A_DIRECTORY: (dir: string) => string;
23
+ readonly DIRECTORY_NOT_EMPTY_REFUSED: (dir: string, entries: string[]) => string;
19
24
  readonly EMPTYING_DIRECTORY: (dir: string) => string;
20
25
  readonly CREATING_PROJECT: (path: string) => string;
21
26
  readonly TEMPLATE_NOT_FOUND: (template: string) => string;
@@ -23,6 +28,7 @@ export declare const MESSAGES: {
23
28
  readonly NEXT_STEPS: "Next steps:";
24
29
  readonly HAPPY_CODING: "Happy coding! 🎉";
25
30
  readonly SKILLS_ADDED: "✔ Installed bestax AI skills into .claude/skills/ (+ CLAUDE.md, .claude/launch.json)";
31
+ readonly ICON_CSS_NOT_ADDED: (file: string, importStatement: string) => string;
26
32
  readonly TELEMETRY_NOTICE: string;
27
33
  readonly TELEMETRY_ACK_ON: "Thanks! Opt out anytime with --no-telemetry.";
28
34
  readonly TELEMETRY_ACK_OFF: "No problem — we won't ask again.";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;;;;;CAiB7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAQV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAoIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAGtD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAOpC,eAAO,MAAM,QAAQ;;;;;;6CASa,MAAM;;;wCAQX,MAAM,WAAW,MAAM,EAAE;oCAE7B,MAAM;gDAIM,MAAM,WAAW,MAAM,EAAE;uCAIlC,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;wCAMV,MAAM,mBAAmB,MAAM;;;;;CAclD,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAQV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAmIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
package/dist/constants.js CHANGED
@@ -6,17 +6,31 @@ export const TEMPLATES = [
6
6
  export const DEFAULT_PROJECT_NAME = 'my-bestax-app';
7
7
  export const MAX_PROJECT_NAME_LENGTH = 214;
8
8
  export const PROJECT_NAME_REGEX = /^[a-zA-Z0-9-._]+$/;
9
+ // How many of a non-empty directory's entries a message names before it
10
+ // switches to "and N more".
11
+ export const MAX_LISTED_ENTRIES = 5;
12
+ const listEntries = (entries) => entries.length > MAX_LISTED_ENTRIES
13
+ ? `${entries.slice(0, MAX_LISTED_ENTRIES).join(', ')} and ${entries.length - MAX_LISTED_ENTRIES} more`
14
+ : entries.join(', ');
9
15
  export const MESSAGES = {
10
16
  PROJECT_NAME_REQUIRED: 'Project name is required',
11
17
  PROJECT_NAME_TOO_LONG: 'Project name too long',
12
18
  PROJECT_NAME_INVALID_CHARS: 'Project name can only contain letters, numbers, dots, dashes and underscores',
13
19
  PROJECT_NAME_DOT: 'Project name cannot start with a dot (names like "." or ".." would scaffold outside a new directory) — pass a directory name',
20
+ PROJECT_NAME_NO_PACKAGE_NAME: 'Project name must contain a letter, number or dash (npm package names cannot start with "_" or ".")',
21
+ PACKAGE_NAME_NORMALIZED: (name) => ` package.json name: ${name} (npm package names are lower-case and cannot start with "_")`,
14
22
  OPERATION_CANCELLED: '✖ Operation cancelled',
15
23
  NO_TTY: 'No interactive terminal detected — cannot prompt for input.\n' +
16
24
  'Re-run non-interactively with a project name and flags, e.g.:\n' +
17
25
  ' npm create bestax@latest my-app -- -t vite-ts -b complete -i none -y\n' +
18
26
  'Run with --help to see all options.',
19
- DIRECTORY_NOT_EMPTY: (dir) => `Directory ${chalk.yellow(dir)} is not empty. Remove existing files and continue?`,
27
+ DIRECTORY_NOT_EMPTY: (dir, entries) => `Directory ${chalk.yellow(dir)} is not empty (${listEntries(entries)}). Remove existing files and continue?`,
28
+ NOT_A_DIRECTORY: (dir) => `${dir} already exists and is not a directory, so nothing was written. Choose another project name.`,
29
+ // Shown instead of the question above when it cannot be asked: under -y, or
30
+ // without a terminal. -y never answers it (#945), so this must not suggest -y.
31
+ DIRECTORY_NOT_EMPTY_REFUSED: (dir, entries) => `Directory ${dir} is not empty (${listEntries(entries)}), so nothing was written.\n` +
32
+ ' Re-run with --overwrite to delete its contents first, or choose a new or empty directory.\n' +
33
+ ' -y alone never deletes files.',
20
34
  EMPTYING_DIRECTORY: (dir) => `\n Emptying ${dir}...`,
21
35
  CREATING_PROJECT: (path) => `✔ Creating project in ${chalk.bold(path)}`,
22
36
  TEMPLATE_NOT_FOUND: (template) => `Template not found at ${template}`,
@@ -24,6 +38,8 @@ export const MESSAGES = {
24
38
  NEXT_STEPS: 'Next steps:',
25
39
  HAPPY_CODING: 'Happy coding! 🎉',
26
40
  SKILLS_ADDED: '✔ Installed bestax AI skills into .claude/skills/ (+ CLAUDE.md, .claude/launch.json)',
41
+ ICON_CSS_NOT_ADDED: (file, importStatement) => ` Warning: ${file} has no bestax stylesheet import to follow, so ${importStatement} ` +
42
+ 'was not added. Add it by hand or the icons will not render.',
27
43
  TELEMETRY_NOTICE: 'Help improve bestax — share anonymous usage stats?\n' +
28
44
  'Sends only the options you chose (template, Bulma flavor, icon library,\n' +
29
45
  'skills, package manager) plus CLI version, Node major, and OS name.\n' +
@@ -118,14 +134,13 @@ cases. Before writing \`style\`, translate each declaration with this table:
118
134
  | \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
119
135
  | \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
120
136
  | \`display: 'none'\` | \`visibility="hidden"\`, or responsive \`display*\` props (\`displayMobile\`, \`displayTablet\`, …) |
137
+ | \`gap: '1rem'\` (flex or grid) | \`gap="2"\`, or one axis: \`columnGap\`, \`rowGap\`; gap scale: \`1\`=0.5rem, \`2\`=1rem, \`4\`=2rem, half steps (\`"1.5"\`) |
121
138
 
122
139
  - Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
123
140
  on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
124
141
  ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
125
142
  \`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
126
143
  the semantic \`color\` prop, not \`bgColor\`.
127
- - Flex layouts have no \`gap\` helper — space children with margins (\`Grid\` and \`Columns\`
128
- take a \`gap\` prop, so prefer that there).
129
144
  - No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
130
145
  \`src/App.css\` and pass it via \`className\` — still never inline \`style\`.
131
146
  - Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
@@ -1,5 +1,12 @@
1
1
  export declare function checkDirectoryExists(targetPath: string): Promise<boolean>;
2
2
  export declare function isDirectoryEmpty(targetPath: string): Promise<boolean>;
3
+ /**
4
+ * Whether something other than a directory, such as a file, already sits at
5
+ * targetPath. listDirectoryEntries cannot read one, so callers check first.
6
+ */
7
+ export declare function existsAsNonDirectory(targetPath: string): Promise<boolean>;
8
+ /** A directory's top-level entries, sorted; none when it does not exist. */
9
+ export declare function listDirectoryEntries(targetPath: string): Promise<string[]>;
3
10
  export declare function emptyDirectory(targetPath: string): Promise<void>;
4
11
  export declare function ensureDirectory(targetPath: string): Promise<void>;
5
12
  export declare function copyDirectory(source: string, destination: string, renames?: Record<string, string>): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"file-system.d.ts","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAIA,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM3E;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAE5E;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,MAAM,SAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAQf"}
1
+ {"version":3,"file":"file-system.d.ts","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAIA,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM3E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,4EAA4E;AAC5E,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAKnB;AAED,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAE5E;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,MAAM,SAAI,GACT,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -11,6 +11,21 @@ export async function isDirectoryEmpty(targetPath) {
11
11
  const files = fs.readdirSync(targetPath);
12
12
  return files.length === 0;
13
13
  }
14
+ /**
15
+ * Whether something other than a directory, such as a file, already sits at
16
+ * targetPath. listDirectoryEntries cannot read one, so callers check first.
17
+ */
18
+ export async function existsAsNonDirectory(targetPath) {
19
+ const stat = fs.statSync(targetPath, { throwIfNoEntry: false });
20
+ return stat !== undefined && !stat.isDirectory();
21
+ }
22
+ /** A directory's top-level entries, sorted; none when it does not exist. */
23
+ export async function listDirectoryEntries(targetPath) {
24
+ if (!fs.existsSync(targetPath)) {
25
+ return [];
26
+ }
27
+ return fs.readdirSync(targetPath).sort();
28
+ }
14
29
  export async function emptyDirectory(targetPath) {
15
30
  await fs.emptyDir(targetPath);
16
31
  }
package/dist/index.js CHANGED
@@ -14,6 +14,6 @@ if (majorVersion < MINIMUM_NODE_VERSION) {
14
14
  `Please upgrade your Node.js version.`);
15
15
  process.exit(1);
16
16
  }
17
- const { createCLI } = await import('./cli.js');
18
- await createCLI().parseAsync(process.argv);
17
+ const { runCLI } = await import('./cli.js');
18
+ await runCLI();
19
19
  export {};
@@ -13,12 +13,32 @@ export interface CLIOptions {
13
13
  skills?: boolean;
14
14
  telemetry?: boolean;
15
15
  yes?: boolean;
16
+ overwrite?: boolean;
16
17
  }
18
+ /**
19
+ * What create() does with the target directory once every prompt is answered:
20
+ * write into it as is ('empty'), remove what it holds first ('overwrite'), or
21
+ * nothing at all ('stop', with the reason already shown).
22
+ */
23
+ export type ExistingDirectoryPlan = 'empty' | 'overwrite' | 'stop';
17
24
  export declare class ProjectCreator {
18
25
  private templatesDir;
19
26
  constructor(templatesDir?: string);
20
27
  getProjectName(projectDir?: string): Promise<string | null>;
21
- checkExistingDirectory(targetPath: string, targetDir: string): Promise<boolean>;
28
+ /**
29
+ * Decides what happens to an existing target directory without touching it:
30
+ * create() empties it only after the last prompt, so a Ctrl+C at any later
31
+ * question leaves the user's files where they were (#945). Only --overwrite
32
+ * or a yes at the prompt removes files. -y never does, and neither -y nor a
33
+ * missing terminal can ask, so both stop at a non-empty directory instead.
34
+ * A file at the target path stops the run too, whatever the flags say.
35
+ */
36
+ checkExistingDirectory(targetPath: string, targetDir: string, options?: CLIOptions): Promise<ExistingDirectoryPlan>;
37
+ /**
38
+ * Every flag's value is checked here, before any prompt runs or anything on
39
+ * disk changes, so a typo can never cost the user an emptied directory (#945).
40
+ */
41
+ validateOptions(options?: CLIOptions): string | null;
22
42
  getTemplatePath(template: string): string;
23
43
  copyTemplate(template: string, targetPath: string): Promise<void>;
24
44
  updateIndexHtmlTitle(targetPath: string, projectName: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA6BA,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBV,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA+IV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgJtE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
1
+ {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA8BA,OAAO,EAQL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AAEnE,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAajE;;;;;;;OAOG;IACG,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,qBAAqB,CAAC;IA2BjC;;;OAGG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;IAmBpD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBV,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA4JV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAmItE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
@@ -2,12 +2,12 @@ import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import chalk from 'chalk';
4
4
  import fs from 'fs-extra';
5
- import { checkDirectoryExists, isDirectoryEmpty, emptyDirectory, ensureDirectory, copyDirectory, updatePackageJson, } from './file-system.js';
5
+ import { checkDirectoryExists, listDirectoryEntries, existsAsNonDirectory, emptyDirectory, ensureDirectory, copyDirectory, updatePackageJson, } from './file-system.js';
6
6
  import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIconLibrary, promptBulmaFlavor, promptInstallSkills, promptTelemetryConsent, } from './prompts.js';
7
7
  import { reportScaffold } from './telemetry.js';
8
8
  import { displayHeader, displaySuccess, displayError, displayCancelled, } from './display.js';
9
- import { validateProjectName } from './validators.js';
10
- import { MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, } from './constants.js';
9
+ import { validateProjectName, toValidPackageName } from './validators.js';
10
+ import { MESSAGES, TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, } from './constants.js';
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
  // npm strips literal `.gitignore` files from published tarballs, so the
13
13
  // templates track `_gitignore` instead and the scaffolder renames it back
@@ -31,18 +31,54 @@ export class ProjectCreator {
31
31
  }
32
32
  return promptProjectName();
33
33
  }
34
- async checkExistingDirectory(targetPath, targetDir) {
35
- const exists = await checkDirectoryExists(targetPath);
36
- const isEmpty = await isDirectoryEmpty(targetPath);
37
- if (exists && !isEmpty) {
38
- const shouldOverwrite = await promptOverwriteDirectory(targetDir);
39
- if (!shouldOverwrite) {
40
- return false;
41
- }
42
- console.log(chalk.yellow(`\n Emptying ${targetDir}...`));
43
- await emptyDirectory(targetPath);
34
+ /**
35
+ * Decides what happens to an existing target directory without touching it:
36
+ * create() empties it only after the last prompt, so a Ctrl+C at any later
37
+ * question leaves the user's files where they were (#945). Only --overwrite
38
+ * or a yes at the prompt removes files. -y never does, and neither -y nor a
39
+ * missing terminal can ask, so both stop at a non-empty directory instead.
40
+ * A file at the target path stops the run too, whatever the flags say.
41
+ */
42
+ async checkExistingDirectory(targetPath, targetDir, options) {
43
+ if (await existsAsNonDirectory(targetPath)) {
44
+ displayError(MESSAGES.NOT_A_DIRECTORY(targetDir));
45
+ return 'stop';
46
+ }
47
+ const entries = await listDirectoryEntries(targetPath);
48
+ if (entries.length === 0) {
49
+ return 'empty';
50
+ }
51
+ if (options?.overwrite) {
52
+ return 'overwrite';
53
+ }
54
+ if (options?.yes || !process.stdin.isTTY) {
55
+ displayError(MESSAGES.DIRECTORY_NOT_EMPTY_REFUSED(targetDir, entries));
56
+ return 'stop';
57
+ }
58
+ if (!(await promptOverwriteDirectory(targetDir, entries))) {
59
+ displayCancelled();
60
+ return 'stop';
44
61
  }
45
- return true;
62
+ return 'overwrite';
63
+ }
64
+ /**
65
+ * Every flag's value is checked here, before any prompt runs or anything on
66
+ * disk changes, so a typo can never cost the user an emptied directory (#945).
67
+ */
68
+ validateOptions(options) {
69
+ const validTemplates = TEMPLATES.map(t => t.name);
70
+ if (options?.template && !validTemplates.includes(options.template)) {
71
+ return `Invalid template: ${options.template}. Valid options are: ${validTemplates.join(', ')}`;
72
+ }
73
+ const validFlavors = BULMA_FLAVORS.map(f => f.name);
74
+ if (options?.bulma && !validFlavors.includes(options.bulma)) {
75
+ return `Invalid Bulma flavor: ${options.bulma}. Valid options are: ${validFlavors.join(', ')}`;
76
+ }
77
+ const validLibraries = ICON_LIBRARIES.map(lib => lib.name);
78
+ if (options?.icon && !validLibraries.includes(options.icon)) {
79
+ return `Invalid icon library: ${options.icon}. Valid options are: ${validLibraries.join(', ')}`;
80
+ }
81
+ return null;
46
82
  }
47
83
  getTemplatePath(template) {
48
84
  return path.join(this.templatesDir, template);
@@ -193,8 +229,10 @@ export class ProjectCreator {
193
229
  const mainFilePath = path.join(targetPath, 'src', mainFileName);
194
230
  if (fs.existsSync(mainFilePath)) {
195
231
  let content = await fs.readFile(mainFilePath, 'utf8');
196
- // Add the icon library import after the CSS imports
197
- const cssImportMatch = content.match(/import\s+['"](?:@allxsmith\/bestax-bulma\/(?:bestax|extras)\.css|bulma\/css\/.*?)['"]\s*;?/);
232
+ // Add the icon library import after the bestax stylesheet import,
233
+ // whichever one the flavor wrote: bestax.css for complete, a
234
+ // versions/bestax-*.css file for every other flavor (#946).
235
+ const cssImportMatch = content.match(/import\s+['"](?:@allxsmith\/bestax-bulma\/[^'"]+\.css|bulma\/css\/.*?)['"]\s*;?/);
198
236
  if (cssImportMatch) {
199
237
  const insertPosition = cssImportMatch.index + cssImportMatch[0].length;
200
238
  content =
@@ -204,6 +242,11 @@ export class ProjectCreator {
204
242
  content.slice(insertPosition);
205
243
  await fs.writeFile(mainFilePath, content);
206
244
  }
245
+ else {
246
+ // Without its stylesheet every icon renders blank or as its
247
+ // name, so say so rather than finish as if it worked (#946).
248
+ console.log(chalk.yellow(MESSAGES.ICON_CSS_NOT_ADDED(`src/${mainFileName}`, library.importStatement)));
249
+ }
207
250
  }
208
251
  }
209
252
  }
@@ -306,6 +349,11 @@ export class ProjectCreator {
306
349
  }
307
350
  async create(projectDir, options) {
308
351
  displayHeader();
352
+ const optionsError = this.validateOptions(options);
353
+ if (optionsError) {
354
+ displayError(optionsError);
355
+ process.exit(1);
356
+ }
309
357
  // Get project name
310
358
  const targetDir = await this.getProjectName(projectDir);
311
359
  if (!targetDir) {
@@ -315,30 +363,15 @@ export class ProjectCreator {
315
363
  }
316
364
  const targetPath = path.resolve(process.cwd(), targetDir);
317
365
  const projectName = path.basename(targetPath);
318
- // Check existing directory (skip prompt if --yes is provided)
319
- const canContinue = options?.yes
320
- ? true
321
- : await this.checkExistingDirectory(targetPath, targetDir);
322
- if (!canContinue) {
323
- displayCancelled();
366
+ // Decide about an existing directory now, but empty it only after the
367
+ // last prompt below (#945).
368
+ const directoryPlan = await this.checkExistingDirectory(targetPath, targetDir, options);
369
+ if (directoryPlan === 'stop') {
324
370
  process.exit(1);
325
371
  }
326
- // If --yes and directory exists, empty it
327
- if (options?.yes &&
328
- (await checkDirectoryExists(targetPath)) &&
329
- !(await isDirectoryEmpty(targetPath))) {
330
- console.log(chalk.yellow(`\n Emptying ${targetDir}...`));
331
- await emptyDirectory(targetPath);
332
- }
333
372
  // Select template (use option or prompt)
334
373
  let template;
335
374
  if (options?.template) {
336
- // Validate the provided template
337
- const validTemplates = ['vite', 'vite-ts'];
338
- if (!validTemplates.includes(options.template)) {
339
- displayError(`Invalid template: ${options.template}. Valid options are: ${validTemplates.join(', ')}`);
340
- process.exit(1);
341
- }
342
375
  template = options.template;
343
376
  }
344
377
  else if (options?.yes) {
@@ -354,12 +387,6 @@ export class ProjectCreator {
354
387
  // Select Bulma flavor (use option or prompt)
355
388
  let bulmaFlavor;
356
389
  if (options?.bulma) {
357
- // Validate the provided Bulma flavor
358
- const validFlavors = BULMA_FLAVORS.map(f => f.name);
359
- if (!validFlavors.includes(options.bulma)) {
360
- displayError(`Invalid Bulma flavor: ${options.bulma}. Valid options are: ${validFlavors.join(', ')}`);
361
- process.exit(1);
362
- }
363
390
  bulmaFlavor = options.bulma;
364
391
  }
365
392
  else if (options?.yes) {
@@ -375,12 +402,6 @@ export class ProjectCreator {
375
402
  // Select icon library (use option or prompt)
376
403
  let iconLibrary;
377
404
  if (options?.icon) {
378
- // Validate the provided icon library
379
- const validLibraries = ICON_LIBRARIES.map(lib => lib.name);
380
- if (!validLibraries.includes(options.icon)) {
381
- displayError(`Invalid icon library: ${options.icon}. Valid options are: ${validLibraries.join(', ')}`);
382
- process.exit(1);
383
- }
384
405
  iconLibrary = options.icon;
385
406
  }
386
407
  else if (options?.yes) {
@@ -403,13 +424,25 @@ export class ProjectCreator {
403
424
  }
404
425
  else {
405
426
  installSkills = await promptInstallSkills();
427
+ if (installSkills === null) {
428
+ displayCancelled();
429
+ process.exit(1);
430
+ }
406
431
  }
407
- // Create project
408
- console.log();
409
- console.log(chalk.green(MESSAGES.CREATING_PROJECT(targetPath)));
410
432
  try {
433
+ if (directoryPlan === 'overwrite') {
434
+ console.log(chalk.yellow(MESSAGES.EMPTYING_DIRECTORY(targetDir)));
435
+ await emptyDirectory(targetPath);
436
+ }
437
+ // Create project
438
+ console.log();
439
+ console.log(chalk.green(MESSAGES.CREATING_PROJECT(targetPath)));
440
+ const packageName = toValidPackageName(projectName);
441
+ if (packageName !== projectName) {
442
+ console.log(chalk.dim(MESSAGES.PACKAGE_NAME_NORMALIZED(packageName)));
443
+ }
411
444
  await this.copyTemplate(template, targetPath);
412
- await updatePackageJson(targetPath, projectName);
445
+ await updatePackageJson(targetPath, packageName);
413
446
  await this.updateIndexHtmlTitle(targetPath, projectName);
414
447
  await this.setupBulmaFlavor(targetPath, bulmaFlavor, template);
415
448
  await this.setupIconLibrary(targetPath, iconLibrary, template);
package/dist/prompts.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  export declare function promptProjectName(): Promise<string | null>;
2
- export declare function promptOverwriteDirectory(targetDir: string): Promise<boolean>;
3
- export declare function promptInstallSkills(): Promise<boolean>;
2
+ /** Asks to remove `entries` from targetDir, naming them, since a yes deletes them. */
3
+ export declare function promptOverwriteDirectory(targetDir: string, entries: string[]): Promise<boolean>;
4
+ /**
5
+ * Returns null when the user cancels (Ctrl+C), like every other scaffold
6
+ * question, so the caller stops instead of reading the cancel as "no" (#950).
7
+ */
8
+ export declare function promptInstallSkills(): Promise<boolean | null>;
4
9
  export declare function promptTemplate(): Promise<string | null>;
5
10
  export declare function promptIconLibrary(): Promise<string | null>;
6
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAyBA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU5D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAchE;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA2BtE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhE"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAyBA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,sFAAsF;AACtF,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAUnE;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAchE;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA2BtE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhE"}
package/dist/prompts.js CHANGED
@@ -25,16 +25,21 @@ export async function promptProjectName() {
25
25
  });
26
26
  return response.projectName || null;
27
27
  }
28
- export async function promptOverwriteDirectory(targetDir) {
28
+ /** Asks to remove `entries` from targetDir, naming them, since a yes deletes them. */
29
+ export async function promptOverwriteDirectory(targetDir, entries) {
29
30
  ensureInteractive();
30
31
  const response = await prompts({
31
32
  type: 'confirm',
32
33
  name: 'overwrite',
33
- message: MESSAGES.DIRECTORY_NOT_EMPTY(targetDir),
34
+ message: MESSAGES.DIRECTORY_NOT_EMPTY(targetDir, entries),
34
35
  initial: false,
35
36
  });
36
37
  return response.overwrite === true;
37
38
  }
39
+ /**
40
+ * Returns null when the user cancels (Ctrl+C), like every other scaffold
41
+ * question, so the caller stops instead of reading the cancel as "no" (#950).
42
+ */
38
43
  export async function promptInstallSkills() {
39
44
  ensureInteractive();
40
45
  const response = await prompts({
@@ -43,7 +48,7 @@ export async function promptInstallSkills() {
43
48
  message: PROMPT_MESSAGES.INSTALL_SKILLS,
44
49
  initial: true,
45
50
  });
46
- return response.skills === true;
51
+ return typeof response.skills === 'boolean' ? response.skills : null;
47
52
  }
48
53
  export async function promptTemplate() {
49
54
  ensureInteractive();
@@ -1,3 +1,10 @@
1
1
  export declare function validateProjectName(value: string): boolean | string;
2
2
  export declare function isValidProjectName(name: string): boolean;
3
+ /**
4
+ * The package.json name for a project directory. npm rejects capitals and a
5
+ * leading "_" or ".", so the name is lower-cased the way `npm init` does and
6
+ * those leading characters are dropped, as create-vite does (#950). The
7
+ * directory itself keeps the name as typed.
8
+ */
9
+ export declare function toValidPackageName(projectName: string): string;
3
10
  //# sourceMappingURL=validators.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAqBnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAyBnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D"}