create-bestax 4.2.16 → 4.2.18

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 (55) hide show
  1. package/README.md +13 -5
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +1 -1
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +7 -2
  7. package/dist/display.d.ts +9 -0
  8. package/dist/display.d.ts.map +1 -1
  9. package/dist/display.js +19 -1
  10. package/dist/file-system.d.ts +16 -3
  11. package/dist/file-system.d.ts.map +1 -1
  12. package/dist/file-system.js +41 -5
  13. package/dist/project-creator.d.ts +16 -1
  14. package/dist/project-creator.d.ts.map +1 -1
  15. package/dist/project-creator.js +35 -6
  16. package/dist/prompts.d.ts +1 -0
  17. package/dist/prompts.d.ts.map +1 -1
  18. package/dist/prompts.js +3 -2
  19. package/dist/validators.d.ts +8 -0
  20. package/dist/validators.d.ts.map +1 -1
  21. package/dist/validators.js +18 -1
  22. package/package.json +7 -7
  23. package/templates/skills/bestax-custom-component/SKILL.md +36 -1
  24. package/templates/skills/bestax-custom-component/references/api.md +36 -14
  25. package/templates/skills/bestax-custom-component/references/component-catalog.md +10 -2
  26. package/templates/skills/bestax-custom-component/references/library-contributor.md +4 -1
  27. package/templates/skills/bestax-form/SKILL.md +46 -30
  28. package/templates/skills/bestax-form/references/api.md +15 -10
  29. package/templates/skills/bestax-form/references/patterns.md +11 -6
  30. package/templates/skills/bestax-icons/SKILL.md +8 -1
  31. package/templates/skills/bestax-icons/references/icon-libraries.md +35 -2
  32. package/templates/skills/bestax-layout-scaffold/SKILL.md +54 -4
  33. package/templates/skills/bestax-layout-scaffold/references/archetypes.md +57 -0
  34. package/templates/skills/bestax-layout-scaffold/references/layout-components.md +3 -3
  35. package/templates/skills/bestax-migrate/SKILL.md +23 -2
  36. package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +34 -8
  37. package/templates/skills/bestax-migrate/references/bloomer/unmappables.md +18 -0
  38. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +9 -4
  39. package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +1 -1
  40. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +317 -4
  41. package/templates/skills/bestax-migrate/references/css-migration.md +5 -1
  42. package/templates/skills/bestax-migrate/references/rbx/prop-map.md +31 -7
  43. package/templates/skills/bestax-migrate/references/rbx/unmappables.md +41 -14
  44. package/templates/skills/bestax-migrate/references/react-bulma-components/component-map.md +3 -3
  45. package/templates/skills/bestax-migrate/references/react-bulma-components/prop-map.md +22 -0
  46. package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +33 -8
  47. package/templates/skills/bestax-optimize/SKILL.md +2 -0
  48. package/templates/skills/bestax-optimize/references/modular-build.md +23 -12
  49. package/templates/skills/bestax-theming/SKILL.md +4 -1
  50. package/templates/skills/bestax-theming/references/css-variables.md +130 -2
  51. package/templates/skills/bestax-theming/references/themeable-components.md +8 -2
  52. package/templates/vite-ts/package.json +1 -0
  53. package/templates/vite-ts/tsconfig.app.json +10 -9
  54. package/templates/vite-ts/tsconfig.node.json +15 -5
  55. package/templates/vite-ts/src/vite-env.d.ts +0 -1
package/README.md CHANGED
@@ -41,8 +41,8 @@ npm create bestax@latest
41
41
 
42
42
  You'll be asked to:
43
43
 
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)
44
+ 1. Enter a project name or path (and, if that directory already holds files, confirm removing
45
+ them; nothing is removed until you have answered the last question)
46
46
  2. Select a framework (Vite or Vite + TypeScript)
47
47
  3. Choose a Bulma CSS flavor (the `--bulma` options below)
48
48
  4. Select an icon library (Font Awesome, Material Icons, etc.)
@@ -62,8 +62,12 @@ with or without it.
62
62
 
63
63
  **Arguments:**
64
64
 
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.
65
+ - `[project-directory]` - Optional project directory: a name such as `my-app`, or a path,
66
+ absolute or relative, such as `../apps/my-app`, whose missing folders are created. Its last
67
+ part is the project name, held to the same rules as a bare name. It becomes the page title as
68
+ typed, and the `package.json` name lower-cased the way `npm init` does and without a leading
69
+ `_`, since npm rejects both. A path ending in `.` or `..` is refused, like a bare `.` or `..`,
70
+ so name the directory you want.
67
71
 
68
72
  **Options:**
69
73
 
@@ -99,7 +103,8 @@ with or without it.
99
103
  directory under `-y` alone, so a script that relied on that now needs `--overwrite`.
100
104
 
101
105
  - `--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.
106
+ Every other flag is checked first, so a typo stops the run before anything is deleted. It
107
+ never empties the current directory or a folder that holds it, however the path is written.
103
108
 
104
109
  **Example:**
105
110
 
@@ -110,6 +115,9 @@ npm create bestax@latest my-app -- -t vite-ts -i fontawesome -b complete
110
115
  # Use all defaults (skip prompts)
111
116
  npm create bestax@latest my-app -- -y
112
117
 
118
+ # Scaffold into a path; the package is named my-app
119
+ npm create bestax@latest ../apps/my-app -- -y
120
+
113
121
  # The same with bun
114
122
  bun create bestax@latest my-app -- -y
115
123
  ```
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,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"}
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,CAyCnC;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
@@ -53,7 +53,7 @@ export function createCLI() {
53
53
  .name('create-bestax')
54
54
  .description('Create a new bestax-bulma project')
55
55
  .version(getPackageVersion())
56
- .argument('[project-directory]', 'project directory to create')
56
+ .argument('[project-directory]', 'project directory to create, a name or a path (its last part names the package)')
57
57
  .option('-t, --template <template>', 'template to use (vite, vite-ts)')
58
58
  .option('-b, --bulma <flavor>', 'Bulma CSS flavor (complete, prefixed, no-helpers, no-helpers-prefixed, no-dark-mode)')
59
59
  .option('-i, --icon <library>', 'icon library (none, fontawesome, mdi, ionicons, material-icons, material-symbols)')
@@ -15,11 +15,13 @@ export declare const MESSAGES: {
15
15
  readonly PROJECT_NAME_INVALID_CHARS: "Project name can only contain letters, numbers, dots, dashes and underscores";
16
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
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 PROJECT_NAME_FROM_PATH: (message: string, name: string) => string;
18
19
  readonly PACKAGE_NAME_NORMALIZED: (name: string) => string;
19
20
  readonly OPERATION_CANCELLED: "✖ Operation cancelled";
20
21
  readonly NO_TTY: string;
21
22
  readonly DIRECTORY_NOT_EMPTY: (dir: string, entries: string[]) => string;
22
23
  readonly NOT_A_DIRECTORY: (dir: string) => string;
24
+ readonly DIRECTORY_HOLDS_CWD: (dir: string) => string;
23
25
  readonly DIRECTORY_NOT_EMPTY_REFUSED: (dir: string, entries: string[]) => string;
24
26
  readonly EMPTYING_DIRECTORY: (dir: string) => string;
25
27
  readonly CREATING_PROJECT: (path: string) => string;
@@ -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;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;AAkCD,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MA8HF,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;IAGzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA8DvC,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;IAGtB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA8CtC,CAAC;AAKF,eAAO,MAAM,iBAAiB,QAE+B,CAAC;AAE9D,MAAM,WAAW,YAAY;IAE3B,IAAI,EAAE,MAAM,CAAC;IAEb,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD,eAAO,MAAM,0BAA0B,EAAE,YAAY,EAgBpD,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAMjB,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;;;;;;+CASe,MAAM,QAAQ,MAAM;6CAEtB,MAAM;;;wCAQX,MAAM,WAAW,MAAM,EAAE;oCAE7B,MAAM;wCAIF,MAAM;gDAKE,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;AAkCD,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MA8HF,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;IAGzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA8DvC,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;IAGtB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA8CtC,CAAC;AAKF,eAAO,MAAM,iBAAiB,QAE+B,CAAC;AAE9D,MAAM,WAAW,YAAY;IAE3B,IAAI,EAAE,MAAM,CAAC;IAEb,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD,eAAO,MAAM,0BAA0B,EAAE,YAAY,EAgBpD,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAMjB,CAAC"}
package/dist/constants.js CHANGED
@@ -18,6 +18,7 @@ export const MESSAGES = {
18
18
  PROJECT_NAME_INVALID_CHARS: 'Project name can only contain letters, numbers, dots, dashes and underscores',
19
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
20
  PROJECT_NAME_NO_PACKAGE_NAME: 'Project name must contain a letter, number or dash (npm package names cannot start with "_" or ".")',
21
+ PROJECT_NAME_FROM_PATH: (message, name) => `${message} (the project name is "${name}", the last part of the path)`,
21
22
  PACKAGE_NAME_NORMALIZED: (name) => ` package.json name: ${name} (npm package names are lower-case and cannot start with "_")`,
22
23
  OPERATION_CANCELLED: '✖ Operation cancelled',
23
24
  NO_TTY: 'No interactive terminal detected — cannot prompt for input.\n' +
@@ -25,7 +26,11 @@ export const MESSAGES = {
25
26
  ' npm create bestax@latest my-app -- -t vite-ts -b complete -i none -y\n' +
26
27
  'Run with --help to see all options.',
27
28
  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
+ NOT_A_DIRECTORY: (dir) => `${dir} already exists and is not a directory, so nothing was written. Choose another project name or path.`,
30
+ // #310: the working directory, or a folder holding it, under another
31
+ // spelling than "." or "..", such as "../<its name>" or its absolute path.
32
+ DIRECTORY_HOLDS_CWD: (dir) => `Directory ${dir} is or holds the current directory, so it is never emptied, even with --overwrite, and nothing was written.\n` +
33
+ ' Choose a new or empty directory.',
29
34
  // Shown instead of the question above when it cannot be asked: under -y, or
30
35
  // without a terminal. -y never answers it (#945), so this must not suggest -y.
31
36
  DIRECTORY_NOT_EMPTY_REFUSED: (dir, entries) => `Directory ${dir} is not empty (${listEntries(entries)}), so nothing was written.\n` +
@@ -98,7 +103,7 @@ cases. Before writing \`style\`, translate each declaration with this table:
98
103
  | \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
99
104
  | \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
100
105
  | \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
101
- | \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
106
+ | \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest). On \`Title\`/\`SubTitle\`, \`as\` sets the level and \`size\` picks it too, so an \`<h3>\` at size 4 is \`as="h3" textSize="4"\` |
102
107
  | \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
103
108
  | \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
104
109
  | \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
package/dist/display.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  export declare function displayHeader(): void;
2
+ /**
3
+ * value written so that pasting it into a shell gives back exactly value. The
4
+ * project name is held to safe characters, but a parent folder in a path can
5
+ * hold anything (#1154). POSIX shells get single quotes, which keep spaces, $
6
+ * and backticks literal, with each single quote inside written '\''. Windows
7
+ * gets double quotes, the quoting cmd and PowerShell share, since cmd has no
8
+ * single quotes.
9
+ */
10
+ export declare function quoteShellArgument(value: string, platform?: typeof process.platform): string;
2
11
  export declare function displaySuccess(targetDir: string): void;
3
12
  export declare function displayError(message: string): void;
4
13
  export declare function displayInfo(message: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiCtD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
1
+ {"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAOD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,OAAO,OAAO,CAAC,QAA2B,GACnD,MAAM,CAKR;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiCtD;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
package/dist/display.js CHANGED
@@ -9,6 +9,24 @@ export function displayHeader() {
9
9
  console.log(chalk.cyan('━'.repeat(50)));
10
10
  console.log();
11
11
  }
12
+ // Characters no shell reads specially, so a path made only of them pastes as
13
+ // it is. Windows adds its backslash separator.
14
+ const SHELL_SAFE = /^[\w@%+=:,./-]+$/;
15
+ const WINDOWS_SHELL_SAFE = /^[\w@%+=:,./\\-]+$/;
16
+ /**
17
+ * value written so that pasting it into a shell gives back exactly value. The
18
+ * project name is held to safe characters, but a parent folder in a path can
19
+ * hold anything (#1154). POSIX shells get single quotes, which keep spaces, $
20
+ * and backticks literal, with each single quote inside written '\''. Windows
21
+ * gets double quotes, the quoting cmd and PowerShell share, since cmd has no
22
+ * single quotes.
23
+ */
24
+ export function quoteShellArgument(value, platform = process.platform) {
25
+ if (platform === 'win32') {
26
+ return WINDOWS_SHELL_SAFE.test(value) ? value : `"${value}"`;
27
+ }
28
+ return SHELL_SAFE.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
29
+ }
12
30
  export function displaySuccess(targetDir) {
13
31
  console.log();
14
32
  console.log(chalk.green(MESSAGES.PROJECT_CREATED));
@@ -18,7 +36,7 @@ export function displaySuccess(targetDir) {
18
36
  // Mirror the package manager that invoked the CLI — `<pm> install` and
19
37
  // `<pm> run dev` are valid for all four supported PMs.
20
38
  const pm = detectPackageManager();
21
- console.log(chalk.cyan(` cd ${targetDir}`));
39
+ console.log(chalk.cyan(` cd ${quoteShellArgument(targetDir)}`));
22
40
  console.log(chalk.cyan(` ${pm} install`));
23
41
  console.log(chalk.cyan(` ${pm} run dev`));
24
42
  console.log();
@@ -1,10 +1,23 @@
1
1
  export declare function checkDirectoryExists(targetPath: string): Promise<boolean>;
2
2
  export declare function isDirectoryEmpty(targetPath: string): Promise<boolean>;
3
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.
4
+ * What stands in the way of creating targetPath as a directory: the nearest
5
+ * of it and its parents that exists, when that is not a directory. That is a
6
+ * file at the target itself, or a file where a parent folder should be, as in
7
+ * `notes.txt/login` (#1154). null when the nearest existing entry is a
8
+ * directory. listDirectoryEntries cannot read a file, so callers check first.
9
+ *
10
+ * It never throws. throwIfNoEntry covers a missing entry, and a path under a
11
+ * file fails with ENOTDIR on some Node versions, so any failed stat moves on
12
+ * to the parent, where the file that caused it is found.
6
13
  */
7
- export declare function existsAsNonDirectory(targetPath: string): Promise<boolean>;
14
+ export declare function findNonDirectoryInPath(targetPath: string): Promise<string | null>;
15
+ /**
16
+ * targetPath with its symbolic links resolved, so two spellings of one
17
+ * directory compare equal (macOS's /tmp is /private/tmp). Returned resolved
18
+ * but otherwise as given when that fails, as it does for a missing path.
19
+ */
20
+ export declare function realPath(targetPath: string): Promise<string>;
8
21
  /** A directory's top-level entries, sorted; none when it does not exist. */
9
22
  export declare function listDirectoryEntries(targetPath: string): Promise<string[]>;
10
23
  export declare function emptyDirectory(targetPath: 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;;;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"}
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;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBxB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMlE;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"}
@@ -12,12 +12,48 @@ export async function isDirectoryEmpty(targetPath) {
12
12
  return files.length === 0;
13
13
  }
14
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.
15
+ * What stands in the way of creating targetPath as a directory: the nearest
16
+ * of it and its parents that exists, when that is not a directory. That is a
17
+ * file at the target itself, or a file where a parent folder should be, as in
18
+ * `notes.txt/login` (#1154). null when the nearest existing entry is a
19
+ * directory. listDirectoryEntries cannot read a file, so callers check first.
20
+ *
21
+ * It never throws. throwIfNoEntry covers a missing entry, and a path under a
22
+ * file fails with ENOTDIR on some Node versions, so any failed stat moves on
23
+ * to the parent, where the file that caused it is found.
17
24
  */
18
- export async function existsAsNonDirectory(targetPath) {
19
- const stat = fs.statSync(targetPath, { throwIfNoEntry: false });
20
- return stat !== undefined && !stat.isDirectory();
25
+ export async function findNonDirectoryInPath(targetPath) {
26
+ let current = path.resolve(targetPath);
27
+ for (;;) {
28
+ let stat;
29
+ try {
30
+ stat = fs.statSync(current, { throwIfNoEntry: false });
31
+ }
32
+ catch {
33
+ stat = undefined;
34
+ }
35
+ if (stat) {
36
+ return stat.isDirectory() ? null : current;
37
+ }
38
+ const parent = path.dirname(current);
39
+ if (parent === current) {
40
+ return null;
41
+ }
42
+ current = parent;
43
+ }
44
+ }
45
+ /**
46
+ * targetPath with its symbolic links resolved, so two spellings of one
47
+ * directory compare equal (macOS's /tmp is /private/tmp). Returned resolved
48
+ * but otherwise as given when that fails, as it does for a missing path.
49
+ */
50
+ export async function realPath(targetPath) {
51
+ try {
52
+ return fs.realpathSync(targetPath);
53
+ }
54
+ catch {
55
+ return path.resolve(targetPath);
56
+ }
21
57
  }
22
58
  /** A directory's top-level entries, sorted; none when it does not exist. */
23
59
  export async function listDirectoryEntries(targetPath) {
@@ -24,6 +24,10 @@ export type ExistingDirectoryPlan = 'empty' | 'overwrite' | 'stop';
24
24
  export declare class ProjectCreator {
25
25
  private templatesDir;
26
26
  constructor(templatesDir?: string);
27
+ /**
28
+ * The project directory, as typed or answered: a name or a path, absolute
29
+ * or relative, whose last segment must be a valid project name (#1154).
30
+ */
27
31
  getProjectName(projectDir?: string): Promise<string | null>;
28
32
  /**
29
33
  * Decides what happens to an existing target directory without touching it:
@@ -31,9 +35,20 @@ export declare class ProjectCreator {
31
35
  * question leaves the user's files where they were (#945). Only --overwrite
32
36
  * or a yes at the prompt removes files. -y never does, and neither -y nor a
33
37
  * 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.
38
+ * Whatever the flags say, the run stops at a file on the target path or in
39
+ * place of one of its parents, and before emptying the working directory or
40
+ * one that holds it (#310), however the path spells it. create() calls this
41
+ * straight after the project name, so neither waits for the other prompts.
35
42
  */
36
43
  checkExistingDirectory(targetPath: string, targetDir: string, options?: CLIOptions): Promise<ExistingDirectoryPlan>;
44
+ /**
45
+ * Whether targetPath is the working directory or one of its ancestors, with
46
+ * symbolic links resolved on both sides. Emptying either deletes the folder
47
+ * the user is standing in, which the leading-dot rule refuses for "." and
48
+ * "..", and a path such as "../<its name>" or its absolute spelling reaches
49
+ * the same folder (#310, #1154).
50
+ */
51
+ private holdsWorkingDirectory;
37
52
  /**
38
53
  * Every flag's value is checked here, before any prompt runs or anything on
39
54
  * disk changes, so a typo can never cost the user an emptied directory (#945).
@@ -1 +1 @@
1
- {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA8BA,OAAO,EAWL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAYxB,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;IAgBhB;;;;;OAKG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpE,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;IAyDhB;;;;;OAKG;IACG,yBAAyB,CAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmBhB,OAAO,CAAC,WAAW;IA2BnB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAcd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAsJV,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;IAoItE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
1
+ {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA+BA,OAAO,EAWL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAYxB,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;IAIjC;;;OAGG;IACG,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAajE;;;;;;;;;;OAUG;IACG,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,qBAAqB,CAAC;IAqCjC;;;;;;OAMG;YACW,qBAAqB;IAanC;;;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;IAgBhB;;;;;OAKG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpE,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;IAyDhB;;;;;OAKG;IACG,yBAAyB,CAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmBhB,OAAO,CAAC,WAAW;IA2BnB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAcd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAsJV,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;IAuItE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
@@ -2,11 +2,11 @@ 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, listDirectoryEntries, existsAsNonDirectory, emptyDirectory, ensureDirectory, copyDirectory, updatePackageJson, } from './file-system.js';
5
+ import { checkDirectoryExists, listDirectoryEntries, findNonDirectoryInPath, realPath, 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, toValidPackageName } from './validators.js';
9
+ import { validateProjectDirectory, toValidPackageName } from './validators.js';
10
10
  import { MESSAGES, TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, NO_HELPERS_STARTER_CLASSES, NO_HELPERS_APP_CSS, CSS_ORDER_COMMENT, } from './constants.js';
11
11
  import { detectPackageManager } from './package-manager.js';
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -21,9 +21,13 @@ export class ProjectCreator {
21
21
  constructor(templatesDir) {
22
22
  this.templatesDir = templatesDir || path.resolve(__dirname, '../templates');
23
23
  }
24
+ /**
25
+ * The project directory, as typed or answered: a name or a path, absolute
26
+ * or relative, whose last segment must be a valid project name (#1154).
27
+ */
24
28
  async getProjectName(projectDir) {
25
29
  if (projectDir) {
26
- const validation = validateProjectName(projectDir);
30
+ const validation = validateProjectDirectory(projectDir);
27
31
  if (validation !== true) {
28
32
  console.log(chalk.red(`✖ ${validation}`));
29
33
  return null;
@@ -38,17 +42,25 @@ export class ProjectCreator {
38
42
  * question leaves the user's files where they were (#945). Only --overwrite
39
43
  * or a yes at the prompt removes files. -y never does, and neither -y nor a
40
44
  * missing terminal can ask, so both stop at a non-empty directory instead.
41
- * A file at the target path stops the run too, whatever the flags say.
45
+ * Whatever the flags say, the run stops at a file on the target path or in
46
+ * place of one of its parents, and before emptying the working directory or
47
+ * one that holds it (#310), however the path spells it. create() calls this
48
+ * straight after the project name, so neither waits for the other prompts.
42
49
  */
43
50
  async checkExistingDirectory(targetPath, targetDir, options) {
44
- if (await existsAsNonDirectory(targetPath)) {
45
- displayError(MESSAGES.NOT_A_DIRECTORY(targetDir));
51
+ const blocker = await findNonDirectoryInPath(targetPath);
52
+ if (blocker !== null) {
53
+ displayError(MESSAGES.NOT_A_DIRECTORY(blocker === path.resolve(targetPath) ? targetDir : blocker));
46
54
  return 'stop';
47
55
  }
48
56
  const entries = await listDirectoryEntries(targetPath);
49
57
  if (entries.length === 0) {
50
58
  return 'empty';
51
59
  }
60
+ if (await this.holdsWorkingDirectory(targetPath)) {
61
+ displayError(MESSAGES.DIRECTORY_HOLDS_CWD(targetDir));
62
+ return 'stop';
63
+ }
52
64
  if (options?.overwrite) {
53
65
  return 'overwrite';
54
66
  }
@@ -62,6 +74,20 @@ export class ProjectCreator {
62
74
  }
63
75
  return 'overwrite';
64
76
  }
77
+ /**
78
+ * Whether targetPath is the working directory or one of its ancestors, with
79
+ * symbolic links resolved on both sides. Emptying either deletes the folder
80
+ * the user is standing in, which the leading-dot rule refuses for "." and
81
+ * "..", and a path such as "../<its name>" or its absolute spelling reaches
82
+ * the same folder (#310, #1154).
83
+ */
84
+ async holdsWorkingDirectory(targetPath) {
85
+ const relative = path.relative(await realPath(targetPath), await realPath(process.cwd()));
86
+ return (relative === '' ||
87
+ (!path.isAbsolute(relative) &&
88
+ relative !== '..' &&
89
+ !relative.startsWith(`..${path.sep}`)));
90
+ }
65
91
  /**
66
92
  * Every flag's value is checked here, before any prompt runs or anything on
67
93
  * disk changes, so a typo can never cost the user an emptied directory (#945).
@@ -413,6 +439,9 @@ export class ProjectCreator {
413
439
  process.exit(1);
414
440
  return; // TypeScript flow control
415
441
  }
442
+ // An absolute path is used as it is and a relative one from the working
443
+ // directory. Its last segment names the package and the page title, and
444
+ // copyTemplate creates it and any missing parent after the last prompt.
416
445
  const targetPath = path.resolve(process.cwd(), targetDir);
417
446
  const projectName = path.basename(targetPath);
418
447
  // Decide about an existing directory now, but empty it only after the
package/dist/prompts.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** Takes a name or a path, the same as the project-directory argument. */
1
2
  export declare function promptProjectName(): Promise<string | null>;
2
3
  /** Asks to remove `entries` from targetDir, naming them, since a yes deletes them. */
3
4
  export declare function promptOverwriteDirectory(targetDir: string, entries: string[]): Promise<boolean>;
@@ -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,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"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAyBA,0EAA0E;AAC1E,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
@@ -1,7 +1,7 @@
1
1
  import prompts from 'prompts';
2
2
  import chalk from 'chalk';
3
3
  import { TEMPLATES, DEFAULT_PROJECT_NAME, PROMPTS as PROMPT_MESSAGES, MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, } from './constants.js';
4
- import { validateProjectName } from './validators.js';
4
+ import { validateProjectDirectory } from './validators.js';
5
5
  import { displayError } from './display.js';
6
6
  /**
7
7
  * Guard against hanging forever in non-interactive environments (CI, agents,
@@ -14,6 +14,7 @@ function ensureInteractive() {
14
14
  process.exit(1);
15
15
  }
16
16
  }
17
+ /** Takes a name or a path, the same as the project-directory argument. */
17
18
  export async function promptProjectName() {
18
19
  ensureInteractive();
19
20
  const response = await prompts({
@@ -21,7 +22,7 @@ export async function promptProjectName() {
21
22
  name: 'projectName',
22
23
  message: PROMPT_MESSAGES.PROJECT_NAME,
23
24
  initial: DEFAULT_PROJECT_NAME,
24
- validate: validateProjectName,
25
+ validate: validateProjectDirectory,
25
26
  });
26
27
  return response.projectName || null;
27
28
  }
@@ -1,5 +1,13 @@
1
1
  export declare function validateProjectName(value: string): boolean | string;
2
2
  export declare function isValidProjectName(name: string): boolean;
3
+ /**
4
+ * The project directory may be a name or a path, absolute or relative, as
5
+ * create-vite takes it (#1154). Its last segment is the project name, the one
6
+ * that names the package and the page title, so that is what is validated, by
7
+ * the same rules as a bare name: a path ending in "." or ".." still fails the
8
+ * leading-dot rule above. A rejected path names the segment it was judged by.
9
+ */
10
+ export declare function validateProjectDirectory(targetDir: string): boolean | string;
3
11
  /**
4
12
  * The package.json name for a project directory. npm rejects capitals and a
5
13
  * leading "_" or ".", so the name is lower-cased the way `npm init` does and
@@ -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,CAyBnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D"}
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAOA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CA2BnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAM5E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D"}
@@ -1,3 +1,4 @@
1
+ import path from 'path';
1
2
  import { PROJECT_NAME_REGEX, MAX_PROJECT_NAME_LENGTH, MESSAGES, } from './constants.js';
2
3
  export function validateProjectName(value) {
3
4
  if (!value) {
@@ -8,7 +9,9 @@ export function validateProjectName(value) {
8
9
  }
9
10
  // The regex below allows dots, so "." and ".." would pass and resolve to
10
11
  // the current/parent directory — which --overwrite would then empty. Rejecting
11
- // every leading-dot name closes that hole ("/" and "\" are already blocked).
12
+ // every leading-dot name closes that hole for these spellings ("/" and "\" are
13
+ // already blocked). A path reaching the same directory another way, such as
14
+ // "../<its name>", is refused where it resolves, in checkExistingDirectory.
12
15
  if (value.startsWith('.')) {
13
16
  return MESSAGES.PROJECT_NAME_DOT;
14
17
  }
@@ -23,6 +26,20 @@ export function validateProjectName(value) {
23
26
  export function isValidProjectName(name) {
24
27
  return validateProjectName(name) === true;
25
28
  }
29
+ /**
30
+ * The project directory may be a name or a path, absolute or relative, as
31
+ * create-vite takes it (#1154). Its last segment is the project name, the one
32
+ * that names the package and the page title, so that is what is validated, by
33
+ * the same rules as a bare name: a path ending in "." or ".." still fails the
34
+ * leading-dot rule above. A rejected path names the segment it was judged by.
35
+ */
36
+ export function validateProjectDirectory(targetDir) {
37
+ const projectName = path.basename(targetDir);
38
+ const validation = validateProjectName(projectName);
39
+ return typeof validation === 'string' && projectName !== targetDir
40
+ ? MESSAGES.PROJECT_NAME_FROM_PATH(validation, projectName)
41
+ : validation;
42
+ }
26
43
  /**
27
44
  * The package.json name for a project directory. npm rejects capitals and a
28
45
  * leading "_" or ".", so the name is lower-cased the way `npm init` does and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "4.2.16",
3
+ "version": "4.2.18",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,21 +41,21 @@
41
41
  "prompts": "^2.4.2",
42
42
  "react": "^18.0.0 || ^19.0.0",
43
43
  "react-dom": "^18.0.0 || ^19.0.0",
44
- "@allxsmith/bestax-bulma": "^5.27.5"
44
+ "@allxsmith/bestax-bulma": "^5.37.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@jest/globals": "^30.0.0",
48
- "@playwright/test": "^1.62.1",
48
+ "@playwright/test": "^1.63.0",
49
49
  "@types/fs-extra": "^11.0.4",
50
50
  "@types/jest": "^30.0.0",
51
- "@types/node": "^26.3.0",
51
+ "@types/node": "^26.6.4",
52
52
  "@types/prompts": "^2.4.9",
53
53
  "@types/wait-on": "^5.3.4",
54
54
  "jest": "^30.2.0",
55
55
  "semver": "^7.8.5",
56
- "ts-jest": "^29.4.12",
56
+ "ts-jest": "^29.4.14",
57
57
  "typescript": "^6.0.3",
58
- "wait-on": "^9.1.0"
58
+ "wait-on": "^9.5.1"
59
59
  },
60
60
  "publishConfig": {
61
61
  "access": "public"
@@ -78,5 +78,5 @@
78
78
  "clean": "rimraf dist",
79
79
  "release": "npx semantic-release"
80
80
  },
81
- "readme": "# create-bestax\n\n[![npm version](https://img.shields.io/npm/v/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![npm downloads](https://img.shields.io/npm/dm/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://badge.socket.dev/npm/package/create-bestax/latest)](https://socket.dev/npm/package/create-bestax/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14361/badge)](https://www.bestpractices.dev/projects/14361)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/create-bestax#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nThe scaffolder for [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — spin up a Vite app pre-wired for the **Bulma v1** React component library in one command. Picks your framework (JS or TypeScript), CSS flavor, and icon library, and can drop in the bestax **AI skills** so an agent like Claude Code knows the library from the first prompt.\n\nPart of the [bestax monorepo](https://github.com/allxsmith/bestax) — see also [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) for the components themselves.\n\n## Requirements\n\n- **Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the\n CLI exits immediately with an explicit upgrade message.\n- npm 10 or newer (ships with Node 22), or yarn/pnpm.\n\n## Usage\n\n### Quick Start\n\n```bash\n# Using npm\nnpm create bestax@latest my-app\n\n# Using npx\nnpx create-bestax@latest my-app\n```\n\n### Interactive Mode\n\nRunning without arguments will prompt you for all options:\n\n```bash\nnpm create bestax@latest\n```\n\nYou'll be asked to:\n\n1. Enter a project name (and, if that directory already holds files, confirm removing them;\n nothing is removed until you have answered the last question)\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (the `--bulma` options below)\n4. Select an icon library (Font Awesome, Material Icons, etc.)\n5. Choose whether to install the bestax AI skills into `.claude/skills/`\n\nEvery prompt has a flag equivalent, so the whole flow is scriptable and CI-friendly — see the options below.\n\n### Command Line Options\n\n```bash\nnpm create bestax@latest [project-directory] -- [options]\n```\n\nPut the options after `--`. npm needs it to pass them on at all, and so does bun, whose\n`bun create` rejects short flags such as `-t` and `-y` itself. pnpm and Yarn 1 accept the options\nwith or without it.\n\n**Arguments:**\n\n- `[project-directory]` - Optional project directory name. It also becomes the `package.json`\n name, lower-cased the way `npm init` does and without a leading `_`, since npm rejects both.\n\n**Options:**\n\n- `-t, --template <template>` - Template to use: `vite` or `vite-ts` \n **Default:** `vite`\n- `-b, --bulma <flavor>` - Bulma CSS flavor:\n - `complete` - Full Bulma CSS with all components and helpers\n - `prefixed` - Prefixed version for compatibility\n - `no-helpers` - Without helper classes\n - `no-helpers-prefixed` - Prefixed without helpers\n - `no-dark-mode` - Without dark mode support \n **Default:** `complete`\n- `-i, --icon <library>` - Icon library:\n - `none` - No icon library\n - `fontawesome` - Font Awesome\n - `mdi` - Material Design Icons\n - `ionicons` - Ionicons\n - `material-icons` - Material Icons\n - `material-symbols` - Material Symbols \n **Default:** `none`\n- `--skills` / `--no-skills` - Install (or skip) the bestax AI skills into\n `.claude/skills/` (with a `CLAUDE.md`) so a Claude Code session picks them up\n automatically. When omitted you're prompted; with `-y` the default is to install.\n- `-y, --yes` - Skip prompts and use defaults or provided options. \n When used, the following defaults are selected unless overridden by flags:\n - Template: `vite`\n - Bulma flavor: `complete`\n - Icon library: `none`\n - AI skills: installed (pass `--no-skills` to opt out)\n\n `-y` never deletes files. If the target directory is not empty, the run stops, lists what is\n there, and changes nothing; add `--overwrite` to empty it first. Earlier versions emptied the\n directory under `-y` alone, so a script that relied on that now needs `--overwrite`.\n\n- `--overwrite` - Remove existing files if the target directory is not empty, without asking.\n Every other flag is checked first, so a typo stops the run before anything is deleted.\n\n**Example:**\n\n```bash\n# Create a TypeScript project with Font Awesome icons\nnpm create bestax@latest my-app -- -t vite-ts -i fontawesome -b complete\n\n# Use all defaults (skip prompts)\nnpm create bestax@latest my-app -- -y\n\n# The same with bun\nbun create bestax@latest my-app -- -y\n```\n\n## Templates\n\nAvailable templates:\n\n- `vite` - Vite + JavaScript\n- `vite-ts` - Vite + TypeScript\n\nEach template includes:\n\n- Pre-configured bestax-bulma integration (React 19)\n- The latest `@allxsmith/bestax-bulma`, which ships Bulma v1 automatically\n- Icon library support (Font Awesome, Material Design, etc.)\n- Sample components demonstrating library usage\n- Development and build scripts\n\n## For AI Tools\n\nScaffolding for an AI agent? Pass `--skills` (or accept the prompt) to drop the bestax\nAgent Skills and a `CLAUDE.md` into the new project so Claude Code, Cursor, or Copilot\nbuild the bestax way from the start. The library also ships LLM-optimized docs:\n\n- [LLMs guide](https://bestax.io/docs/guides/llms) — using bestax with Claude Code, Cursor, Copilot\n- [Agent Skills](https://bestax.io/docs/skills/intro) — teach your agent the bestax conventions\n- [llms.txt](https://bestax.io/llms.txt) (curated index) · [llms-full.txt](https://bestax.io/llms-full.txt) (full docs)\n\n## Development\n\n```bash\nnpm run build # Build the CLI\nnpm run dev # Watch mode\nnpm test # Run tests\nnpm run lint # Lint CLI source code\nnpm run typecheck # Type check CLI source code\n```\n\n**Note on Templates:** Template files in `templates/` are excluded from linting. They should be manually validated by scaffolding a test project and running lint/build there before releasing.\n\n## Hardened by default\n\nA scaffolder runs with write access to your filesystem and picks your starting dependencies, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/create-bestax#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, so a compromised action release can't roll silently into a build of this CLI.\n- **CodeQL, Dependency Review, and Dependabot** run continuously, alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`create-bestax` can send one **anonymous** usage event after a successful\nscaffold — only if you opt in when asked (once, at the end of a scaffold). The\nevent is just the choices you made — template, Bulma flavor, icon library,\nskills, package manager — plus the CLI version, Node major version, and OS\nplatform; never names, IPs, machine IDs, paths, or file contents, and no\nidentifier exists that could link two events together. Opt out any time with\n`--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## Publishing\n\nThis package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.\n"
81
+ "readme": "# create-bestax\n\n[![npm version](https://img.shields.io/npm/v/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![npm downloads](https://img.shields.io/npm/dm/create-bestax.svg)](https://www.npmjs.com/package/create-bestax)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Socket Badge](https://badge.socket.dev/npm/package/create-bestax/latest)](https://socket.dev/npm/package/create-bestax/overview)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/allxsmith/bestax/badge)](https://scorecard.dev/viewer/?uri=github.com/allxsmith/bestax)\n[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/14361/badge)](https://www.bestpractices.dev/projects/14361)\n[![npm provenance](https://img.shields.io/badge/npm-provenance-3fb950.svg)](https://www.npmjs.com/package/create-bestax#provenance)\n[![Security policy](https://img.shields.io/badge/security-policy-blue.svg)](https://github.com/allxsmith/bestax/blob/main/SECURITY.md)\n\nThe scaffolder for [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) — spin up a Vite app pre-wired for the **Bulma v1** React component library in one command. Picks your framework (JS or TypeScript), CSS flavor, and icon library, and can drop in the bestax **AI skills** so an agent like Claude Code knows the library from the first prompt.\n\nPart of the [bestax monorepo](https://github.com/allxsmith/bestax) — see also [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) for the components themselves.\n\n## Requirements\n\n- **Node.js 22 or newer.** Node 18 and 20 are both past end-of-life. On an older runtime the\n CLI exits immediately with an explicit upgrade message.\n- npm 10 or newer (ships with Node 22), or yarn/pnpm.\n\n## Usage\n\n### Quick Start\n\n```bash\n# Using npm\nnpm create bestax@latest my-app\n\n# Using npx\nnpx create-bestax@latest my-app\n```\n\n### Interactive Mode\n\nRunning without arguments will prompt you for all options:\n\n```bash\nnpm create bestax@latest\n```\n\nYou'll be asked to:\n\n1. Enter a project name or path (and, if that directory already holds files, confirm removing\n them; nothing is removed until you have answered the last question)\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (the `--bulma` options below)\n4. Select an icon library (Font Awesome, Material Icons, etc.)\n5. Choose whether to install the bestax AI skills into `.claude/skills/`\n\nEvery prompt has a flag equivalent, so the whole flow is scriptable and CI-friendly — see the options below.\n\n### Command Line Options\n\n```bash\nnpm create bestax@latest [project-directory] -- [options]\n```\n\nPut the options after `--`. npm needs it to pass them on at all, and so does bun, whose\n`bun create` rejects short flags such as `-t` and `-y` itself. pnpm and Yarn 1 accept the options\nwith or without it.\n\n**Arguments:**\n\n- `[project-directory]` - Optional project directory: a name such as `my-app`, or a path,\n absolute or relative, such as `../apps/my-app`, whose missing folders are created. Its last\n part is the project name, held to the same rules as a bare name. It becomes the page title as\n typed, and the `package.json` name lower-cased the way `npm init` does and without a leading\n `_`, since npm rejects both. A path ending in `.` or `..` is refused, like a bare `.` or `..`,\n so name the directory you want.\n\n**Options:**\n\n- `-t, --template <template>` - Template to use: `vite` or `vite-ts` \n **Default:** `vite`\n- `-b, --bulma <flavor>` - Bulma CSS flavor:\n - `complete` - Full Bulma CSS with all components and helpers\n - `prefixed` - Prefixed version for compatibility\n - `no-helpers` - Without helper classes\n - `no-helpers-prefixed` - Prefixed without helpers\n - `no-dark-mode` - Without dark mode support \n **Default:** `complete`\n- `-i, --icon <library>` - Icon library:\n - `none` - No icon library\n - `fontawesome` - Font Awesome\n - `mdi` - Material Design Icons\n - `ionicons` - Ionicons\n - `material-icons` - Material Icons\n - `material-symbols` - Material Symbols \n **Default:** `none`\n- `--skills` / `--no-skills` - Install (or skip) the bestax AI skills into\n `.claude/skills/` (with a `CLAUDE.md`) so a Claude Code session picks them up\n automatically. When omitted you're prompted; with `-y` the default is to install.\n- `-y, --yes` - Skip prompts and use defaults or provided options. \n When used, the following defaults are selected unless overridden by flags:\n - Template: `vite`\n - Bulma flavor: `complete`\n - Icon library: `none`\n - AI skills: installed (pass `--no-skills` to opt out)\n\n `-y` never deletes files. If the target directory is not empty, the run stops, lists what is\n there, and changes nothing; add `--overwrite` to empty it first. Earlier versions emptied the\n directory under `-y` alone, so a script that relied on that now needs `--overwrite`.\n\n- `--overwrite` - Remove existing files if the target directory is not empty, without asking.\n Every other flag is checked first, so a typo stops the run before anything is deleted. It\n never empties the current directory or a folder that holds it, however the path is written.\n\n**Example:**\n\n```bash\n# Create a TypeScript project with Font Awesome icons\nnpm create bestax@latest my-app -- -t vite-ts -i fontawesome -b complete\n\n# Use all defaults (skip prompts)\nnpm create bestax@latest my-app -- -y\n\n# Scaffold into a path; the package is named my-app\nnpm create bestax@latest ../apps/my-app -- -y\n\n# The same with bun\nbun create bestax@latest my-app -- -y\n```\n\n## Templates\n\nAvailable templates:\n\n- `vite` - Vite + JavaScript\n- `vite-ts` - Vite + TypeScript\n\nEach template includes:\n\n- Pre-configured bestax-bulma integration (React 19)\n- The latest `@allxsmith/bestax-bulma`, which ships Bulma v1 automatically\n- Icon library support (Font Awesome, Material Design, etc.)\n- Sample components demonstrating library usage\n- Development and build scripts\n\n## For AI Tools\n\nScaffolding for an AI agent? Pass `--skills` (or accept the prompt) to drop the bestax\nAgent Skills and a `CLAUDE.md` into the new project so Claude Code, Cursor, or Copilot\nbuild the bestax way from the start. The library also ships LLM-optimized docs:\n\n- [LLMs guide](https://bestax.io/docs/guides/llms) — using bestax with Claude Code, Cursor, Copilot\n- [Agent Skills](https://bestax.io/docs/skills/intro) — teach your agent the bestax conventions\n- [llms.txt](https://bestax.io/llms.txt) (curated index) · [llms-full.txt](https://bestax.io/llms-full.txt) (full docs)\n\n## Development\n\n```bash\nnpm run build # Build the CLI\nnpm run dev # Watch mode\nnpm test # Run tests\nnpm run lint # Lint CLI source code\nnpm run typecheck # Type check CLI source code\n```\n\n**Note on Templates:** Template files in `templates/` are excluded from linting. They should be manually validated by scaffolding a test project and running lint/build there before releasing.\n\n## Hardened by default\n\nA scaffolder runs with write access to your filesystem and picks your starting dependencies, so how it is built and published matters:\n\n- **Signed provenance** — every release carries a sigstore attestation linking the tarball to the exact commit and CI run that built it. Check the **Provenance** section on the [npm page](https://www.npmjs.com/package/create-bestax#provenance), or run `npm audit signatures`.\n- **npm OIDC trusted publishing** — short-lived, per-run credentials; no long-lived `NPM_TOKEN` exists to be stolen. Release commits and tags are GPG-signed.\n- **Socket.dev scans every PR** for malware, install scripts, obfuscated code, and privilege escalation before it can reach `main`.\n- **Dependencies are a deliberate act** — install scripts are blocked unless individually allow-listed, freshly published versions are refused for 3 days, and CI installs only what the reviewed lockfile resolves.\n- **Every GitHub Action is pinned to a full commit SHA**, so a compromised action release can't roll silently into a build of this CLI.\n- **CodeQL, Dependency Review, and Dependabot** run continuously, alongside a high-severity `pnpm audit` gate.\n- **Layered AI review before merge** — [CodeRabbit](https://coderabbit.ai) plus an independent adversarial Claude review (a different model from the one writing AI-authored changes), on top of required green CI, an approving review, and a human merge.\n\nFull detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)\n\n## Telemetry\n\n`create-bestax` can send one **anonymous** usage event after a successful\nscaffold — only if you opt in when asked (once, at the end of a scaffold). The\nevent is just the choices you made — template, Bulma flavor, icon library,\nskills, package manager — plus the CLI version, Node major version, and OS\nplatform; never names, IPs, machine IDs, paths, or file contents, and no\nidentifier exists that could link two events together. Opt out any time with\n`--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.\n\nFull disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)\n\n## Publishing\n\nThis package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.\n"
82
82
  }