create-bestax 4.2.13 → 4.2.15

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 (42) hide show
  1. package/README.md +11 -7
  2. package/dist/cli.d.ts +11 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +17 -2
  5. package/dist/constants.d.ts +9 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +102 -31
  8. package/dist/index.js +2 -2
  9. package/dist/project-creator.d.ts +19 -0
  10. package/dist/project-creator.d.ts.map +1 -1
  11. package/dist/project-creator.js +91 -38
  12. package/package.json +3 -3
  13. package/templates/skills/bestax-custom-component/examples/stat-card.tsx +2 -2
  14. package/templates/skills/bestax-form/SKILL.md +17 -7
  15. package/templates/skills/bestax-form/references/api.md +10 -10
  16. package/templates/skills/bestax-form/references/patterns.md +9 -1
  17. package/templates/skills/bestax-icons/SKILL.md +8 -6
  18. package/templates/skills/bestax-icons/examples/icon-usage.tsx +3 -3
  19. package/templates/skills/bestax-icons/references/icon-libraries.md +12 -7
  20. package/templates/skills/bestax-layout-scaffold/examples/app-shell.tsx +1 -1
  21. package/templates/skills/bestax-layout-scaffold/examples/card-grid.tsx +0 -1
  22. package/templates/skills/bestax-layout-scaffold/examples/centered.tsx +0 -1
  23. package/templates/skills/bestax-layout-scaffold/examples/content-page.tsx +0 -1
  24. package/templates/skills/bestax-layout-scaffold/examples/landing.tsx +1 -1
  25. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +13 -4
  26. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +10 -1
  27. package/templates/skills/bestax-optimize/SKILL.md +10 -6
  28. package/templates/skills/bestax-optimize/references/modular-build.md +13 -8
  29. package/templates/skills/bestax-theming/examples/dark-mode.tsx +1 -1
  30. package/templates/skills/bestax-theming/examples/theme-config.tsx +2 -2
  31. package/templates/skills/bestax-theming/references/css-variables.md +3 -1
  32. package/templates/vite/README.md +5 -6
  33. package/templates/vite/src/App.css +14 -1
  34. package/templates/vite/src/App.jsx +128 -100
  35. package/templates/vite/src/main.jsx +3 -2
  36. package/templates/vite-ts/README.md +5 -6
  37. package/templates/vite-ts/src/App.css +14 -1
  38. package/templates/vite-ts/src/App.tsx +128 -100
  39. package/templates/vite-ts/src/main.tsx +3 -2
  40. package/templates/vite-ts/tsconfig.app.json +25 -0
  41. package/templates/vite-ts/tsconfig.json +6 -25
  42. package/templates/vite-ts/tsconfig.node.json +2 -2
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
@@ -47,7 +44,7 @@ You'll be asked to:
47
44
  1. Enter a project name (and, if that directory already holds files, confirm removing them;
48
45
  nothing is removed until you have answered the last question)
49
46
  2. Select a framework (Vite or Vite + TypeScript)
50
- 3. Choose a Bulma CSS flavor (Complete or Minimal)
47
+ 3. Choose a Bulma CSS flavor (the `--bulma` options below)
51
48
  4. Select an icon library (Font Awesome, Material Icons, etc.)
52
49
  5. Choose whether to install the bestax AI skills into `.claude/skills/`
53
50
 
@@ -56,9 +53,13 @@ Every prompt has a flag equivalent, so the whole flow is scriptable and CI-frien
56
53
  ### Command Line Options
57
54
 
58
55
  ```bash
59
- npm create bestax@latest [project-directory] [options]
56
+ npm create bestax@latest [project-directory] -- [options]
60
57
  ```
61
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
+
62
63
  **Arguments:**
63
64
 
64
65
  - `[project-directory]` - Optional project directory name. It also becomes the `package.json`
@@ -104,10 +105,13 @@ npm create bestax@latest [project-directory] [options]
104
105
 
105
106
  ```bash
106
107
  # Create a TypeScript project with Font Awesome icons
107
- 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
108
109
 
109
110
  # Use all defaults (skip prompts)
110
- 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
111
115
  ```
112
116
 
113
117
  ## Templates
package/dist/cli.d.ts CHANGED
@@ -16,5 +16,15 @@ export declare function copyTemplate(templatePath: string, targetPath: string):
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,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,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
@@ -71,9 +71,24 @@ export function createCLI() {
71
71
  export function isMainModule(importMetaUrl, argv1) {
72
72
  return importMetaUrl === `file://${argv1}`;
73
73
  }
74
- 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) {
75
90
  const program = createCLI();
76
- await program.parseAsync();
91
+ await program.parseAsync(dropForwardedSeparator(argv));
77
92
  }
78
93
  // Only run if this is the main module
79
94
  /* istanbul ignore next */
@@ -66,6 +66,15 @@ export interface BulmaFlavor {
66
66
  color: typeof chalk.yellow;
67
67
  importStatement: string;
68
68
  needsPrefix?: boolean;
69
+ noHelpers?: boolean;
69
70
  }
70
71
  export declare const BULMA_FLAVORS: BulmaFlavor[];
72
+ export declare const CSS_ORDER_COMMENT: string;
73
+ export interface StarterClass {
74
+ prop: string;
75
+ className: string;
76
+ declaration: string;
77
+ }
78
+ export declare const NO_HELPERS_STARTER_CLASSES: StarterClass[];
79
+ export declare const NO_HELPERS_APP_CSS: string;
71
80
  //# sourceMappingURL=constants.d.ts.map
@@ -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;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"}
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"}
package/dist/constants.js CHANGED
@@ -87,6 +87,36 @@ export const CONFIG_PROVIDER_ICON_VALUES = {
87
87
  'material-icons': 'material-icons',
88
88
  'material-symbols': 'material-symbols',
89
89
  };
90
+ // The house style opens with the inline-style table for a flavor that has the
91
+ // helper classes, and with the named-class rule for one that does not.
92
+ const HELPER_HOUSE_STYLE = `**Never inline \`style={{}}\`** — the components accept helper props that cover the common
93
+ cases. Before writing \`style\`, translate each declaration with this table:
94
+
95
+ | Inline style you're about to write | Helper props instead |
96
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
97
+ | \`marginTop: '1rem'\` (any margin/padding) | \`mt="4"\` — \`m\`/\`mt\`/\`mx\`/\`p\`/\`py\`/… scale: \`1\`=0.25rem, \`2\`=0.5rem, \`3\`=0.75rem, \`4\`=1rem, \`5\`=1.5rem, \`6\`=3rem (nearest step) |
98
+ | \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
99
+ | \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
100
+ | \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
101
+ | \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
102
+ | \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
103
+ | \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
104
+ | \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
105
+ | \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
106
+ | \`display: 'none'\` | \`visibility="hidden"\`, or responsive \`display*\` props (\`displayMobile\`, \`displayTablet\`, …) |
107
+ | \`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"\`) |
108
+
109
+ - Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
110
+ on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
111
+ ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
112
+ \`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
113
+ the semantic \`color\` prop, not \`bgColor\`.
114
+ - No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
115
+ \`src/App.css\` and pass it via \`className\` — still never inline \`style\`.`;
116
+ const NO_HELPERS_HOUSE_STYLE = `**Never inline \`style={{}}\`**, and don't reach for helper props in its place either:
117
+ this app's flavor leaves out Bulma's helper classes, so \`mt="4"\`, \`textAlign="centered"\` and \`flexGrow="1"\`
118
+ render nothing. Write a named class in \`src/App.css\`, which loads after Bulma's CSS, and pass it via \`className\`.
119
+ `;
90
120
  export const CLAUDE_MD = (projectName, { bulmaFlavor, iconLibrary }) => {
91
121
  const flavor = BULMA_FLAVORS.find(f => f.name === bulmaFlavor);
92
122
  const icon = ICON_LIBRARIES.find(lib => lib.name === iconLibrary);
@@ -99,6 +129,13 @@ export const CLAUDE_MD = (projectName, { bulmaFlavor, iconLibrary }) => {
99
129
  setupLines.push(`- Every Bulma class carries the \`bestax-\` prefix and the app is wrapped in ` +
100
130
  `\`<ConfigProvider classPrefix="bestax-">\` — custom CSS selectors must match the prefix.`);
101
131
  }
132
+ if (flavor?.noHelpers) {
133
+ setupLines.push(`- This flavor leaves out Bulma's helper classes, so **helper props that add those classes render nothing**: ` +
134
+ `\`mt\`, \`gap\`, \`textAlign\`, \`textColor\`, \`display\` and \`flexGrow\` still add ` +
135
+ `class names, but no rule matches them. \`skeleton\` still works, because the flavor keeps ` +
136
+ `Bulma's skeleton styles. The starter page lays itself out with named classes in ` +
137
+ `\`src/App.css\` instead.`);
138
+ }
102
139
  const providerIconValue = CONFIG_PROVIDER_ICON_VALUES[iconLibrary];
103
140
  setupLines.push(iconLibrary === 'none'
104
141
  ? `- No icon library is installed — add one before using \`<Icon>\` ` +
@@ -107,7 +144,17 @@ export const CLAUDE_MD = (projectName, { bulmaFlavor, iconLibrary }) => {
107
144
  (providerIconValue
108
145
  ? ` The app is already wrapped in \`<ConfigProvider iconLibrary="${providerIconValue}">\` ` +
109
146
  `— extend that provider rather than adding a second one.`
110
- : ''));
147
+ : '') +
148
+ (icon?.setupInstructions ? ` ${icon.setupInstructions}` : ''));
149
+ const houseStyleOpening = flavor?.noHelpers
150
+ ? NO_HELPERS_HOUSE_STYLE
151
+ : HELPER_HOUSE_STYLE;
152
+ // The wrapper elements are only a way out where their helper props render.
153
+ const utilityClassRule = flavor?.noHelpers
154
+ ? `- Don't hand-write Bulma utility classes either: this flavor doesn't ship them, so a
155
+ \`has-text-…\` class renders nothing, and the named class above is what works.`
156
+ : `- Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
157
+ take the same helper props: \`Span\`, \`Paragraph\`, \`Strong\`, not \`<span className="has-text-…">\`.`;
111
158
  return `# ${projectName}
112
159
 
113
160
  This app is built with [\`@allxsmith/bestax-bulma\`](https://bestax.io) — React components for
@@ -119,36 +166,12 @@ ${setupLines.join('\n')}
119
166
 
120
167
  ## House style
121
168
 
122
- **Never inline \`style={{}}\`** — the components accept helper props that cover the common
123
- cases. Before writing \`style\`, translate each declaration with this table:
124
-
125
- | Inline style you're about to write | Helper props instead |
126
- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
127
- | \`marginTop: '1rem'\` (any margin/padding) | \`mt="4"\` — \`m\`/\`mt\`/\`mx\`/\`p\`/\`py\`/… scale: \`1\`=0.25rem, \`2\`=0.5rem, \`3\`=0.75rem, \`4\`=1rem, \`5\`=1.5rem, \`6\`=3rem (nearest step) |
128
- | \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
129
- | \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
130
- | \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
131
- | \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
132
- | \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
133
- | \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
134
- | \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
135
- | \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
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"\`) |
138
-
139
- - Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
140
- on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
141
- ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
142
- \`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
143
- the semantic \`color\` prop, not \`bgColor\`.
144
- - No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
145
- \`src/App.css\` and pass it via \`className\` — still never inline \`style\`.
146
- - Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
147
- take the same helper props: \`Span\`, \`Paragraph\`, \`Strong\`, not \`<span className="has-text-…">\`.
169
+ ${houseStyleOpening}
170
+ ${utilityClassRule}
148
171
  The one exception: companion classes Bulma requires on \`<html>\`/\`<body>\` (e.g.
149
172
  \`has-navbar-fixed-top\` with \`Navbar fixed="top"\`) are hand-added in \`index.html\` — no
150
173
  component renders those elements.
151
- - Compound sub-parts (\`Card.*\`, \`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take \`className\` + HTML
174
+ - Compound sub-parts (\`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take \`className\` + HTML
152
175
  attributes and their own few props — no Bulma helper props, no \`as\`/\`href\`: nest a
153
176
  \`Link\`/\`Span\` inside instead. \`Tabs.Tab\` and \`Tabs.Content.Item\` each require \`index={i}\`,
154
177
  and \`Tabs.Tab\` has built-in \`icon\`/\`disabled\` props — no nested \`Icon\` needed.
@@ -250,15 +273,30 @@ export const ICON_LIBRARIES = [
250
273
  color: chalk.yellow,
251
274
  packageName: 'material-icons',
252
275
  packageVersion: '^1.13.14',
253
- importStatement: "import 'material-icons';",
276
+ // The package's bare import puts the filled, outlined, round, sharp and
277
+ // two-tone fonts into the build. Filled is the style Icon renders by
278
+ // default, so it is the only one imported.
279
+ importStatement: "import 'material-icons/iconfont/filled.css';",
280
+ setupInstructions: 'Only the filled style is loaded (`material-icons/iconfont/filled.css`, the variant `<Icon>` ' +
281
+ 'uses by default). Import `outlined.css`, `round.css`, `sharp.css` or `two-tone.css` from ' +
282
+ '`material-icons/iconfont/` in `src/main.*` before using those variants.',
254
283
  },
255
284
  {
256
285
  name: 'material-symbols',
257
286
  display: 'Material Symbols',
258
287
  color: chalk.magenta,
259
288
  packageName: 'material-symbols',
260
- packageVersion: '^0.45.2',
261
- importStatement: "import 'material-symbols';",
289
+ // 0.x, so the caret holds the minor: this follows the newest minor
290
+ // bestax-bulma's material-symbols peer range admits, not the newest
291
+ // published one, or npm refuses the install as a peer conflict.
292
+ packageVersion: '^0.46.0',
293
+ // The package's bare import puts the outlined, rounded and sharp fonts
294
+ // into the build. Outlined is the style Icon renders by default, so it is
295
+ // the only one imported.
296
+ importStatement: "import 'material-symbols/outlined.css';",
297
+ setupInstructions: 'Only the outlined style is loaded (`material-symbols/outlined.css`, the variant `<Icon>` ' +
298
+ 'uses by default). Import `material-symbols/rounded.css` or `sharp.css` in `src/main.*` ' +
299
+ 'before using `variant="rounded"` or `"sharp"`.',
262
300
  },
263
301
  ];
264
302
  export const BULMA_FLAVORS = [
@@ -283,6 +321,7 @@ export const BULMA_FLAVORS = [
283
321
  description: 'Core components only, no utility classes — helper props need them (~67 KB gzip)',
284
322
  color: chalk.yellow,
285
323
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-helpers.css';",
324
+ noHelpers: true,
286
325
  },
287
326
  {
288
327
  name: 'no-helpers-prefixed',
@@ -291,6 +330,7 @@ export const BULMA_FLAVORS = [
291
330
  color: chalk.magenta,
292
331
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-helpers-prefixed.css';",
293
332
  needsPrefix: true,
333
+ noHelpers: true,
294
334
  },
295
335
  {
296
336
  name: 'no-dark-mode',
@@ -300,3 +340,34 @@ export const BULMA_FLAVORS = [
300
340
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-dark-mode.css';",
301
341
  },
302
342
  ];
343
+ // Sits above the stylesheet import in the templates' entry files, and the
344
+ // scaffolder writes it back when it swaps in a flavor's stylesheet, so the
345
+ // reason for the import order survives. The no-helpers classes depend on it.
346
+ export const CSS_ORDER_COMMENT = "// Bestax's stylesheet loads before the app's own CSS, so a named class in\n" +
347
+ '// src/App.css wins over a Bulma rule of the same weight.';
348
+ // Every helper prop the starter App uses, with the named class that stands in
349
+ // for it under a `noHelpers` flavor, where the prop would render nothing. The
350
+ // scaffolder swaps each prop for its class and writes the rules into
351
+ // src/App.css, so the starter keeps its layout and shows that flavor's way of
352
+ // styling. A helper prop added to the starter needs a row here.
353
+ export const NO_HELPERS_STARTER_CLASSES = [
354
+ {
355
+ prop: 'textAlign="centered"',
356
+ className: 'page-title',
357
+ declaration: 'text-align: center;',
358
+ },
359
+ {
360
+ prop: 'display="flex"',
361
+ className: 'card-column',
362
+ declaration: 'display: flex;',
363
+ },
364
+ {
365
+ prop: 'flexGrow="1"',
366
+ className: 'card-fill',
367
+ declaration: 'flex-grow: 1;',
368
+ },
369
+ ];
370
+ export const NO_HELPERS_APP_CSS = '\n/* This flavor of Bulma leaves out the helper classes, so helper props such\n' +
371
+ ' as textAlign and flexGrow render nothing here. Named classes like these do\n' +
372
+ ' the job instead: write one, then pass it with className. */\n' +
373
+ NO_HELPERS_STARTER_CLASSES.map(({ className, declaration }) => `.${className} {\n ${declaration}\n}\n`).join('\n');
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 {};
@@ -42,9 +42,28 @@ export declare class ProjectCreator {
42
42
  getTemplatePath(template: string): string;
43
43
  copyTemplate(template: string, targetPath: string): Promise<void>;
44
44
  updateIndexHtmlTitle(targetPath: string, projectName: string): Promise<void>;
45
+ /**
46
+ * The template README is written with pnpm commands and lists no icon
47
+ * library. Rewrite the commands for the package manager that ran the CLI
48
+ * (the one the success message names too) and list the icon library the
49
+ * user picked, if any.
50
+ */
51
+ updateReadme(targetPath: string, iconLibrary: string): Promise<void>;
45
52
  setupSkills(targetPath: string, projectName: string, options: ClaudeMdOptions): Promise<void>;
46
53
  setupBulmaFlavor(targetPath: string, bulmaFlavor: string, template: string): Promise<void>;
54
+ /**
55
+ * A flavor without Bulma's helper classes leaves the starter's helper props
56
+ * rendering nothing, so its title loses its centering and its cards their
57
+ * equal heights. Swap each prop for the named class in
58
+ * NO_HELPERS_STARTER_CLASSES and append the classes' rules to src/App.css.
59
+ */
60
+ replaceStarterHelperProps(targetPath: string, template: string): Promise<void>;
47
61
  private getIconName;
62
+ /**
63
+ * The `iconProps` entries for a starter card icon. Decorative, since the
64
+ * card title beside it says the same thing, so it is hidden from screen
65
+ * readers rather than announced as "icon".
66
+ */
48
67
  private getIconProps;
49
68
  setupIconLibrary(targetPath: string, iconLibrary: string, template: string): Promise<void>;
50
69
  setupConfigProvider(targetPath: string, bulmaFlavor: string, iconLibrary: string, template: string): Promise<void>;
@@ -1 +1 @@
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"}
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"}
@@ -7,7 +7,8 @@ import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIcon
7
7
  import { reportScaffold } from './telemetry.js';
8
8
  import { displayHeader, displaySuccess, displayError, displayCancelled, } from './display.js';
9
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';
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
+ import { detectPackageManager } from './package-manager.js';
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
  // npm strips literal `.gitignore` files from published tarballs, so the
13
14
  // templates track `_gitignore` instead and the scaffolder renames it back
@@ -100,6 +101,25 @@ export class ProjectCreator {
100
101
  await fs.writeFile(indexHtmlPath, updated);
101
102
  }
102
103
  }
104
+ /**
105
+ * The template README is written with pnpm commands and lists no icon
106
+ * library. Rewrite the commands for the package manager that ran the CLI
107
+ * (the one the success message names too) and list the icon library the
108
+ * user picked, if any.
109
+ */
110
+ async updateReadme(targetPath, iconLibrary) {
111
+ const readmePath = path.join(targetPath, 'README.md');
112
+ if (!fs.existsSync(readmePath))
113
+ return;
114
+ const pm = detectPackageManager();
115
+ let readme = await fs.readFile(readmePath, 'utf8');
116
+ readme = readme.replace(/\bpnpm (install|run)\b/g, `${pm} $1`);
117
+ const library = ICON_LIBRARIES.find(lib => lib.name === iconLibrary);
118
+ if (library && iconLibrary !== 'none') {
119
+ readme = readme.replace(/^(- .*bestax-bulma component library)$/m, `$1\n- 🎯 ${library.display} icons`);
120
+ }
121
+ await fs.writeFile(readmePath, readme);
122
+ }
103
123
  async setupSkills(targetPath, projectName, options) {
104
124
  const skillsSrc = path.join(this.templatesDir, 'skills');
105
125
  if (!(await checkDirectoryExists(skillsSrc))) {
@@ -122,11 +142,13 @@ export class ProjectCreator {
122
142
  const mainFilePath = path.join(targetPath, 'src', mainFileName);
123
143
  if (fs.existsSync(mainFilePath)) {
124
144
  let content = await fs.readFile(mainFilePath, 'utf8');
125
- // Replace the default bestax CSS import with the selected flavor
126
- const bestaxImportRegex = /\/\/.*\n\s*import\s+['"]@allxsmith\/bestax-bulma\/bestax\.css['"]\s*;?/;
145
+ // Replace the default bestax CSS import, and the comment block directly
146
+ // above it, with the selected flavor's import under the comment that
147
+ // says why the stylesheets load before the app's own CSS.
148
+ const bestaxImportRegex = /(?:[ \t]*\/\/[^\n]*\n)+[ \t]*import\s+['"]@allxsmith\/bestax-bulma\/bestax\.css['"]\s*;?/;
127
149
  const bestaxImportSimpleRegex = /import\s+['"]@allxsmith\/bestax-bulma\/bestax\.css['"]\s*;?/;
128
150
  if (bestaxImportRegex.test(content)) {
129
- content = content.replace(bestaxImportRegex, '// Import CSS\n' + flavor.importStatement);
151
+ content = content.replace(bestaxImportRegex, CSS_ORDER_COMMENT + '\n' + flavor.importStatement);
130
152
  }
131
153
  else if (bestaxImportSimpleRegex.test(content)) {
132
154
  content = content.replace(bestaxImportSimpleRegex, flavor.importStatement);
@@ -145,6 +167,29 @@ export class ProjectCreator {
145
167
  }
146
168
  await fs.writeFile(mainFilePath, content);
147
169
  }
170
+ if (flavor.noHelpers) {
171
+ await this.replaceStarterHelperProps(targetPath, template);
172
+ }
173
+ }
174
+ /**
175
+ * A flavor without Bulma's helper classes leaves the starter's helper props
176
+ * rendering nothing, so its title loses its centering and its cards their
177
+ * equal heights. Swap each prop for the named class in
178
+ * NO_HELPERS_STARTER_CLASSES and append the classes' rules to src/App.css.
179
+ */
180
+ async replaceStarterHelperProps(targetPath, template) {
181
+ const appFileName = template.includes('-ts') ? 'App.tsx' : 'App.jsx';
182
+ const appFilePath = path.join(targetPath, 'src', appFileName);
183
+ const appCssPath = path.join(targetPath, 'src', 'App.css');
184
+ if (!fs.existsSync(appFilePath) || !fs.existsSync(appCssPath))
185
+ return;
186
+ let appContent = await fs.readFile(appFilePath, 'utf8');
187
+ for (const { prop, className } of NO_HELPERS_STARTER_CLASSES) {
188
+ appContent = appContent.split(prop).join(`className="${className}"`);
189
+ }
190
+ await fs.writeFile(appFilePath, appContent);
191
+ const css = await fs.readFile(appCssPath, 'utf8');
192
+ await fs.writeFile(appCssPath, css.replace(/\n*$/, '\n') + NO_HELPERS_APP_CSS);
148
193
  }
149
194
  getIconName(iconLibrary, iconType) {
150
195
  const iconMappings = {
@@ -168,13 +213,20 @@ export class ProjectCreator {
168
213
  };
169
214
  return iconMappings[iconLibrary]?.[iconType] || '';
170
215
  }
216
+ /**
217
+ * The `iconProps` entries for a starter card icon. Decorative, since the
218
+ * card title beside it says the same thing, so it is hidden from screen
219
+ * readers rather than announced as "icon".
220
+ */
171
221
  getIconProps(iconLibrary, iconType) {
172
222
  const iconName = this.getIconName(iconLibrary, iconType);
173
223
  if (!iconName)
174
- return '';
175
- return iconLibrary === 'fontawesome'
176
- ? `name="${iconName}" variant="solid"`
177
- : `name="${iconName}"`;
224
+ return [];
225
+ return [
226
+ `name: '${iconName}'`,
227
+ ...(iconLibrary === 'fontawesome' ? [`variant: 'solid'`] : []),
228
+ `'aria-hidden': 'true'`,
229
+ ];
178
230
  }
179
231
  async setupIconLibrary(targetPath, iconLibrary, template) {
180
232
  if (iconLibrary === 'none')
@@ -251,48 +303,48 @@ export class ProjectCreator {
251
303
  }
252
304
  }
253
305
  }
254
- // Now add Icon components to App.jsx/App.tsx (for all icon libraries)
306
+ // Now add an icon to each card title in App.jsx/App.tsx (for all icon
307
+ // libraries). IconText spaces the icon from its text with Bulma's own
308
+ // .icon-text rule rather than a margin helper, so the gap holds in the
309
+ // no-helpers flavors too.
255
310
  const isTypeScript = template.includes('-ts');
256
311
  const appFileName = isTypeScript ? 'App.tsx' : 'App.jsx';
257
312
  const appFilePath = path.join(targetPath, 'src', appFileName);
258
313
  if (fs.existsSync(appFilePath)) {
259
314
  let appContent = await fs.readFile(appFilePath, 'utf8');
260
- // Add Icon import to the bestax-bulma imports
315
+ // Add IconText to the bestax-bulma imports
261
316
  const bulmaImportRegex = /(import\s+\{[\s\S]*?)(}\s+from\s+['"]@allxsmith\/bestax-bulma['"])/;
262
- if (bulmaImportRegex.test(appContent) && !appContent.includes('Icon')) {
263
- // Check if the import ends with a comma or not
317
+ if (bulmaImportRegex.test(appContent) &&
318
+ !/\bIconText\b/.test(appContent)) {
264
319
  const importMatch = appContent.match(bulmaImportRegex);
265
320
  if (importMatch) {
266
321
  const beforeClosingBrace = importMatch[1];
267
- // Remove any trailing comma and whitespace, then add Icon properly
322
+ // Remove any trailing comma and whitespace, then add IconText properly
268
323
  const cleanedImport = beforeClosingBrace.replace(/,?\s*$/, '');
269
- appContent = appContent.replace(bulmaImportRegex, cleanedImport + ',\n Icon$2');
324
+ appContent = appContent.replace(bulmaImportRegex, cleanedImport + ',\n IconText,\n$2');
270
325
  }
271
326
  }
272
- // Add icon examples in the Cards section
273
- // Find the Quick Start Card and add an icon
274
- const quickStartRegex = /(Card\.Header\.Title>\s*Quick Start\s*<\/Card\.Header\.Title>)/;
275
- if (quickStartRegex.test(appContent)) {
276
- const iconProps = this.getIconProps(iconLibrary, 'rocket');
277
- if (iconProps) {
278
- appContent = appContent.replace(quickStartRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Quick Start\n </Card.Header.Title>`);
279
- }
280
- }
281
- // Add icon to Documentation Card
282
- const docsRegex = /(Card\.Header\.Title>\s*Documentation\s*<\/Card\.Header\.Title>)/;
283
- if (docsRegex.test(appContent)) {
284
- const iconProps = this.getIconProps(iconLibrary, 'book');
285
- if (iconProps) {
286
- appContent = appContent.replace(docsRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Documentation\n </Card.Header.Title>`);
287
- }
288
- }
289
- // Add icon to Examples Card
290
- const examplesRegex = /(Card\.Header\.Title>\s*Examples\s*<\/Card\.Header\.Title>)/;
291
- if (examplesRegex.test(appContent)) {
292
- const iconProps = this.getIconProps(iconLibrary, 'code');
293
- if (iconProps) {
294
- appContent = appContent.replace(examplesRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Examples\n </Card.Header.Title>`);
295
- }
327
+ const cardIcons = [
328
+ ['rocket', 'Quick Start'],
329
+ ['book', 'Documentation'],
330
+ ['code', 'Examples'],
331
+ ];
332
+ for (const [iconType, title] of cardIcons) {
333
+ const iconProps = this.getIconProps(iconLibrary, iconType);
334
+ if (iconProps.length === 0)
335
+ continue;
336
+ const titleRegex = new RegExp(`^([ \\t]*)(<Card\\.Header\\.Title[^>]*>)\\s*${title}\\s*(</Card\\.Header\\.Title>)`, 'm');
337
+ appContent = appContent.replace(titleRegex, (_match, indent, open, close) => [
338
+ `${indent}${open}`,
339
+ `${indent} <IconText`,
340
+ `${indent} iconProps={{`,
341
+ ...iconProps.map(entry => `${indent} ${entry},`),
342
+ `${indent} }}`,
343
+ `${indent} >`,
344
+ `${indent} ${title}`,
345
+ `${indent} </IconText>`,
346
+ `${indent}${close}`,
347
+ ].join('\n'));
296
348
  }
297
349
  await fs.writeFile(appFilePath, appContent);
298
350
  }
@@ -444,6 +496,7 @@ export class ProjectCreator {
444
496
  await this.copyTemplate(template, targetPath);
445
497
  await updatePackageJson(targetPath, packageName);
446
498
  await this.updateIndexHtmlTitle(targetPath, projectName);
499
+ await this.updateReadme(targetPath, iconLibrary);
447
500
  await this.setupBulmaFlavor(targetPath, bulmaFlavor, template);
448
501
  await this.setupIconLibrary(targetPath, iconLibrary, template);
449
502
  await this.setupConfigProvider(targetPath, bulmaFlavor, iconLibrary, template);