create-bestax 4.1.2 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -151,6 +151,18 @@ A scaffolder runs with write access to your filesystem and picks your starting d
151
151
 
152
152
  Full detail: [`SECURITY.md`](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) · [Security guide](https://bestax.io/docs/guides/security)
153
153
 
154
+ ## Telemetry
155
+
156
+ `create-bestax` can send one **anonymous** usage event after a successful
157
+ scaffold — only if you opt in when asked (once, at the end of a scaffold). The
158
+ event is just the choices you made — template, Bulma flavor, icon library,
159
+ skills, package manager — plus the CLI version, Node major version, and OS
160
+ platform; never names, IPs, machine IDs, paths, or file contents, and no
161
+ identifier exists that could link two events together. Opt out any time with
162
+ `--no-telemetry`, `BESTAX_TELEMETRY=0`, or `DO_NOT_TRACK=1`.
163
+
164
+ Full disclosure of every field and control: [Telemetry guide](https://bestax.io/docs/guides/telemetry)
165
+
154
166
  ## Publishing
155
167
 
156
168
  This package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.
package/dist/cli.d.ts CHANGED
@@ -16,5 +16,5 @@ 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(): void;
19
+ export declare function runCLI(): Promise<void>;
20
20
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,IAAI,cAAc,EAChC,iBAAiB,IAAI,iBAAiB,EACtC,iBAAiB,IAAI,iBAAiB,GACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAGf;AAcD,wBAAgB,SAAS,IAAI,OAAO,CA0BnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAgB,MAAM,IAAI,IAAI,CAG7B"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACtE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGzE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,IAAI,cAAc,EAChC,iBAAiB,IAAI,iBAAiB,EACtC,iBAAiB,IAAI,iBAAiB,GACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,wBAAsB,cAAc,CAClC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,aAAa,CACjC,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAGf;AAcD,wBAAgB,SAAS,IAAI,OAAO,CA+BnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5C"}
package/dist/cli.js CHANGED
@@ -59,21 +59,23 @@ export function createCLI() {
59
59
  .option('-i, --icon <library>', 'icon library (none, fontawesome, mdi, ionicons, material-icons, material-symbols)')
60
60
  .option('--skills', 'install the bestax AI skills into .claude/skills')
61
61
  .option('--no-skills', 'do not install the bestax AI skills')
62
+ .option('--telemetry', 'enable anonymous usage telemetry (https://bestax.io/docs/guides/telemetry)')
63
+ .option('--no-telemetry', 'disable anonymous usage telemetry')
62
64
  .option('-y, --yes', 'skip prompts and use defaults or provided options')
63
- .action((projectDir, options) => {
64
- projectCreator.create(projectDir, options);
65
+ .action(async (projectDir, options) => {
66
+ await projectCreator.create(projectDir, options);
65
67
  });
66
68
  return program;
67
69
  }
68
70
  export function isMainModule(importMetaUrl, argv1) {
69
71
  return importMetaUrl === `file://${argv1}`;
70
72
  }
71
- export function runCLI() {
73
+ export async function runCLI() {
72
74
  const program = createCLI();
73
- program.parse();
75
+ await program.parseAsync();
74
76
  }
75
77
  // Only run if this is the main module
76
78
  /* istanbul ignore next */
77
79
  if (isMainModule(import.meta.url, process.argv[1])) {
78
- runCLI();
80
+ await runCLI();
79
81
  }
@@ -23,6 +23,10 @@ export declare const MESSAGES: {
23
23
  readonly NEXT_STEPS: "Next steps:";
24
24
  readonly HAPPY_CODING: "Happy coding! 🎉";
25
25
  readonly SKILLS_ADDED: "✔ Installed bestax AI skills into .claude/skills/ (+ CLAUDE.md, .claude/launch.json)";
26
+ readonly TELEMETRY_NOTICE: string;
27
+ readonly TELEMETRY_ACK_ON: "Thanks! Opt out anytime with --no-telemetry.";
28
+ readonly TELEMETRY_ACK_OFF: "No problem — we won't ask again.";
29
+ readonly TELEMETRY_ACK_UNSAVED: "Couldn't save your choice (config dir not writable) — you may be asked again.";
26
30
  };
27
31
  export declare const LAUNCH_JSON: string;
28
32
  export declare const PROMPTS: {
@@ -31,6 +35,7 @@ export declare const PROMPTS: {
31
35
  readonly SELECT_ICON_LIBRARY: "Would you like to add an icon library?";
32
36
  readonly SELECT_BULMA_FLAVOR: "Which Bulma CSS flavor would you like to use?";
33
37
  readonly INSTALL_SKILLS: "Install the bestax AI skills (.claude/skills) for Claude Code and other agents?";
38
+ readonly TELEMETRY_CONSENT: "Share anonymous usage stats?";
34
39
  };
35
40
  export declare const CONFIG_PROVIDER_ICON_VALUES: Record<string, string>;
36
41
  export interface ClaudeMdOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAoIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;;;;;CAiB7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAQV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAoIF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
package/dist/constants.js CHANGED
@@ -24,6 +24,15 @@ export const MESSAGES = {
24
24
  NEXT_STEPS: 'Next steps:',
25
25
  HAPPY_CODING: 'Happy coding! 🎉',
26
26
  SKILLS_ADDED: '✔ Installed bestax AI skills into .claude/skills/ (+ CLAUDE.md, .claude/launch.json)',
27
+ TELEMETRY_NOTICE: 'Help improve bestax — share anonymous usage stats?\n' +
28
+ 'Sends only the options you chose (template, Bulma flavor, icon library,\n' +
29
+ 'skills, package manager) plus CLI version, Node major, and OS name.\n' +
30
+ 'Never your name, paths, project names, or any personal data.\n' +
31
+ ' Details & opt-out: https://bestax.io/docs/guides/telemetry\n' +
32
+ ' Feedback welcome: https://github.com/allxsmith/bestax/issues',
33
+ TELEMETRY_ACK_ON: 'Thanks! Opt out anytime with --no-telemetry.',
34
+ TELEMETRY_ACK_OFF: "No problem — we won't ask again.",
35
+ TELEMETRY_ACK_UNSAVED: "Couldn't save your choice (config dir not writable) — you may be asked again.",
27
36
  };
28
37
  // Dev-server manifest written into .claude/ with the skills opt-in so Claude
29
38
  // Code's browser preview (`preview_start`) can boot the app by name instead of
@@ -49,6 +58,7 @@ export const PROMPTS = {
49
58
  SELECT_ICON_LIBRARY: 'Would you like to add an icon library?',
50
59
  SELECT_BULMA_FLAVOR: 'Which Bulma CSS flavor would you like to use?',
51
60
  INSTALL_SKILLS: 'Install the bestax AI skills (.claude/skills) for Claude Code and other agents?',
61
+ TELEMETRY_CONSENT: 'Share anonymous usage stats?',
52
62
  };
53
63
  // Icon libraries that require a `ConfigProvider iconLibrary` wrapper, mapped
54
64
  // to the exact prop value the scaffolder writes into main.tsx/jsx ('none' and
@@ -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,CA2BtD;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;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
@@ -30,6 +30,8 @@ export function displaySuccess(targetDir) {
30
30
  console.log(chalk.yellow(`${figures.star} If you enjoy using bestax-bulma, please star us on GitHub!`));
31
31
  console.log(chalk.dim(' https://github.com/allxsmith/bestax'));
32
32
  console.log();
33
+ console.log(chalk.dim(' Ideas or problems? Open an issue: https://github.com/allxsmith/bestax/issues'));
34
+ console.log();
33
35
  }
34
36
  export function displayError(message) {
35
37
  console.error(chalk.red(`${figures.cross} ${message}`));
package/dist/index.js CHANGED
@@ -15,6 +15,5 @@ if (majorVersion < MINIMUM_NODE_VERSION) {
15
15
  process.exit(1);
16
16
  }
17
17
  const { createCLI } = await import('./cli.js');
18
- const program = createCLI();
19
- program.parse();
18
+ await createCLI().parseAsync(process.argv);
20
19
  export {};
@@ -1,4 +1,5 @@
1
1
  export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';
2
+ export declare const KNOWN_PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
2
3
  /** Detect the package manager that invoked the CLI from npm_config_user_agent
3
4
  * (e.g. "pnpm/9.12.0 npm/? node/v22 darwin arm64"). Falls back to npm. */
4
5
  export declare function detectPackageManager(userAgent?: string | undefined): PackageManager;
@@ -1 +1 @@
1
- {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../src/package-manager.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAI7D;2EAC2E;AAC3E,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,MAAM,GAAG,SAA6C,GAChE,cAAc,CAKhB"}
1
+ {"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../src/package-manager.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAI7D,eAAO,MAAM,sBAAsB,yCAA0C,CAAC;AAG9E;2EAC2E;AAC3E,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,MAAM,GAAG,SAA6C,GAChE,cAAc,CAKhB"}
@@ -1,4 +1,7 @@
1
- const KNOWN = ['npm', 'pnpm', 'yarn', 'bun'];
1
+ // Exported so check:conformance can compare it against the telemetry
2
+ // worker's allowlist by importing it, rather than regex-scraping the source.
3
+ export const KNOWN_PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'];
4
+ const KNOWN = KNOWN_PACKAGE_MANAGERS;
2
5
  /** Detect the package manager that invoked the CLI from npm_config_user_agent
3
6
  * (e.g. "pnpm/9.12.0 npm/? node/v22 darwin arm64"). Falls back to npm. */
4
7
  export function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
@@ -11,6 +11,7 @@ export interface CLIOptions {
11
11
  bulma?: string;
12
12
  icon?: string;
13
13
  skills?: boolean;
14
+ telemetry?: boolean;
14
15
  yes?: boolean;
15
16
  }
16
17
  export declare class ProjectCreator {
@@ -28,5 +29,15 @@ export declare class ProjectCreator {
28
29
  setupIconLibrary(targetPath: string, iconLibrary: string, template: string): Promise<void>;
29
30
  setupConfigProvider(targetPath: string, bulmaFlavor: string, iconLibrary: string, template: string): Promise<void>;
30
31
  create(projectDir?: string, options?: CLIOptions): Promise<void>;
32
+ /**
33
+ * Consent + beacon after a successful scaffold. Only success events are
34
+ * reported, the beacon is awaited (so no process.exit(1) site can kill an
35
+ * in-flight request), and nothing in here may throw or fail the scaffold.
36
+ * The prompt is skipped for -y, an explicit flag, DO_NOT_TRACK, or a
37
+ * missing TTY on EITHER end — with stdout redirected the question would
38
+ * render into the log while the CLI blocked on an invisible prompt
39
+ * (#192: the non-interactive path must never hang).
40
+ */
41
+ private handleTelemetry;
31
42
  }
32
43
  //# sourceMappingURL=project-creator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA2BA,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBV,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgJV,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;CAyIvE"}
1
+ {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA6BA,OAAO,EAOL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBV,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA+IV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAgJtE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
@@ -3,7 +3,8 @@ import { fileURLToPath } from 'url';
3
3
  import chalk from 'chalk';
4
4
  import fs from 'fs-extra';
5
5
  import { checkDirectoryExists, isDirectoryEmpty, emptyDirectory, ensureDirectory, copyDirectory, updatePackageJson, } from './file-system.js';
6
- import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIconLibrary, promptBulmaFlavor, promptInstallSkills, } from './prompts.js';
6
+ import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIconLibrary, promptBulmaFlavor, promptInstallSkills, promptTelemetryConsent, } from './prompts.js';
7
+ import { reportScaffold } from './telemetry.js';
7
8
  import { displayHeader, displaySuccess, displayError, displayCancelled, } from './display.js';
8
9
  import { validateProjectName } from './validators.js';
9
10
  import { MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, } from './constants.js';
@@ -158,9 +159,8 @@ export class ProjectCreator {
158
159
  // integrity attributes are deliberately not added: the ionicons ESM
159
160
  // loader dynamically imports per-icon chunks that cannot carry
160
161
  // integrity hashes, so entry-file SRI would give false assurance.
161
- const ioniconScripts = ` <!-- Ionicons -->
162
+ const ioniconScripts = ` <!-- Ionicons (v8 is ESM-only; no legacy nomodule fallback exists) -->
162
163
  <script type="module" src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.esm.js"></script>
163
- <script nomodule src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.js"></script>
164
164
  `;
165
165
  htmlContent =
166
166
  htmlContent.slice(0, insertPosition) +
@@ -421,10 +421,51 @@ export class ProjectCreator {
421
421
  });
422
422
  }
423
423
  displaySuccess(targetDir);
424
+ await this.handleTelemetry(options, {
425
+ template,
426
+ bulmaFlavor,
427
+ iconLibrary,
428
+ skills: installSkills,
429
+ });
424
430
  }
425
431
  catch (error) {
426
432
  displayError(error.message);
427
433
  process.exit(1);
428
434
  }
429
435
  }
436
+ /**
437
+ * Consent + beacon after a successful scaffold. Only success events are
438
+ * reported, the beacon is awaited (so no process.exit(1) site can kill an
439
+ * in-flight request), and nothing in here may throw or fail the scaffold.
440
+ * The prompt is skipped for -y, an explicit flag, DO_NOT_TRACK, or a
441
+ * missing TTY on EITHER end — with stdout redirected the question would
442
+ * render into the log while the CLI blocked on an invisible prompt
443
+ * (#192: the non-interactive path must never hang).
444
+ */
445
+ async handleTelemetry(options, choices) {
446
+ try {
447
+ await reportScaffold(choices, options?.telemetry, {
448
+ interactive: process.stdin.isTTY === true &&
449
+ process.stdout.isTTY === true &&
450
+ options?.yes !== true,
451
+ promptConsent: promptTelemetryConsent,
452
+ onDecided: (enabled, persisted) => {
453
+ // Worded on whether the write actually stuck: promising "we won't
454
+ // ask again" after a swallowed write failure would be false.
455
+ if (!persisted) {
456
+ console.log(chalk.dim(MESSAGES.TELEMETRY_ACK_UNSAVED));
457
+ }
458
+ else if (enabled) {
459
+ console.log(chalk.dim(MESSAGES.TELEMETRY_ACK_ON));
460
+ }
461
+ else {
462
+ console.log(chalk.dim(MESSAGES.TELEMETRY_ACK_OFF));
463
+ }
464
+ },
465
+ });
466
+ }
467
+ catch {
468
+ // Telemetry must never affect the scaffold's outcome.
469
+ }
470
+ }
430
471
  }
package/dist/prompts.d.ts CHANGED
@@ -3,5 +3,13 @@ export declare function promptOverwriteDirectory(targetDir: string): Promise<boo
3
3
  export declare function promptInstallSkills(): Promise<boolean>;
4
4
  export declare function promptTemplate(): Promise<string | null>;
5
5
  export declare function promptIconLibrary(): Promise<string | null>;
6
+ /**
7
+ * Consent for anonymous usage telemetry. Deliberately does NOT call
8
+ * ensureInteractive(): consent is optional, so without a TTY the caller skips
9
+ * the question instead of failing the run (#192). Returns null when the user
10
+ * cancels (Ctrl-C) — a cancel is not an answer and nothing is persisted, but
11
+ * the interrupt is honored via exit code 130 rather than swallowed.
12
+ */
13
+ export declare function promptTelemetryConsent(): Promise<boolean | null>;
6
14
  export declare function promptBulmaFlavor(): Promise<string | null>;
7
15
  //# sourceMappingURL=prompts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAwBA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU5D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAchE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhE"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAyBA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWhE;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAU5D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAchE;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA2BtE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhE"}
package/dist/prompts.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import prompts from 'prompts';
2
+ import chalk from 'chalk';
2
3
  import { TEMPLATES, DEFAULT_PROJECT_NAME, PROMPTS as PROMPT_MESSAGES, MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, } from './constants.js';
3
4
  import { validateProjectName } from './validators.js';
4
5
  import { displayError } from './display.js';
@@ -71,6 +72,37 @@ export async function promptIconLibrary() {
71
72
  });
72
73
  return response.iconLibrary || null;
73
74
  }
75
+ /**
76
+ * Consent for anonymous usage telemetry. Deliberately does NOT call
77
+ * ensureInteractive(): consent is optional, so without a TTY the caller skips
78
+ * the question instead of failing the run (#192). Returns null when the user
79
+ * cancels (Ctrl-C) — a cancel is not an answer and nothing is persisted, but
80
+ * the interrupt is honored via exit code 130 rather than swallowed.
81
+ */
82
+ export async function promptTelemetryConsent() {
83
+ console.log();
84
+ console.log(chalk.gray(MESSAGES.TELEMETRY_NOTICE));
85
+ let cancelled = false;
86
+ const response = await prompts({
87
+ type: 'confirm',
88
+ name: 'telemetry',
89
+ message: PROMPT_MESSAGES.TELEMETRY_CONSENT,
90
+ initial: false,
91
+ }, {
92
+ onCancel: () => {
93
+ cancelled = true;
94
+ },
95
+ });
96
+ if (cancelled) {
97
+ // A cancel is not an answer, but it IS an interrupt: without this, a ^C
98
+ // at the question would be swallowed and the run would exit 0 as if it
99
+ // had never been interrupted. 130 = 128 + SIGINT, the shell convention.
100
+ // The scaffold itself already succeeded, so no abrupt process.exit here.
101
+ process.exitCode = 130;
102
+ return null;
103
+ }
104
+ return typeof response.telemetry === 'boolean' ? response.telemetry : null;
105
+ }
74
106
  export async function promptBulmaFlavor() {
75
107
  ensureInteractive();
76
108
  const response = await prompts({
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared CLI telemetry kernel (disclosure: https://bestax.io/docs/guides/telemetry).
3
+ *
4
+ * Copied byte-for-byte into create-bestax and bestax-migrate — both publish
5
+ * standalone, so this cannot be a workspace package. `check:conformance
6
+ * --only=telemetry-core` fails if the copies diverge. Edit this file in
7
+ * create-bestax and copy it over the migrate one.
8
+ *
9
+ * No identifier of any kind is generated. Undecided means off. Every failure
10
+ * here is silent: telemetry must never affect the CLI's outcome.
11
+ */
12
+ export type TelemetryDecision = 'on' | 'off' | 'undecided';
13
+ export type TelemetrySource = 'flag' | 'dnt' | 'env' | 'config' | 'default';
14
+ export interface ResolvedTelemetry {
15
+ decision: TelemetryDecision;
16
+ source: TelemetrySource;
17
+ }
18
+ /** Shared across the bestax CLI family so consent is asked at most once. */
19
+ export declare function telemetryConfigPath(): string;
20
+ /**
21
+ * Precedence: flag (persisted by the caller) → DO_NOT_TRACK → BESTAX_TELEMETRY
22
+ * (per-run, never persisted) → config file → undecided (off).
23
+ */
24
+ export declare function resolveTelemetry(flag?: boolean): Promise<ResolvedTelemetry>;
25
+ /**
26
+ * Returns whether the decision was actually written: callers word their
27
+ * acknowledgement on it, because promising "we won't ask again" after a
28
+ * swallowed write failure would be false on every future run.
29
+ */
30
+ export declare function persistTelemetryDecision(enabled: boolean, decidedBy: string): Promise<boolean>;
31
+ export declare function sendTelemetry(payload: unknown): Promise<void>;
32
+ export declare function getToolVersion(): string;
33
+ export interface ReportRunOptions {
34
+ /** True only when a consent question could actually be answered. */
35
+ interactive: boolean;
36
+ /** Owns the consent UI up to the answer (notice, question); null = cancelled. */
37
+ promptConsent: () => Promise<boolean | null>;
38
+ /**
39
+ * Called after a prompted answer has been (attempted to be) saved, so the
40
+ * acknowledgement can tell the truth about whether it will stick.
41
+ */
42
+ onDecided?: (enabled: boolean, persisted: boolean) => void;
43
+ }
44
+ export declare function reportRun(flag: boolean | undefined, toolName: string, options: ReportRunOptions, payload: unknown): Promise<void>;
45
+ //# sourceMappingURL=telemetry-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-core.d.ts","sourceRoot":"","sources":["../src/telemetry-core.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,KAAK,GAAG,WAAW,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,MAAM,EAAE,eAAe,CAAC;CACzB;AAYD,4EAA4E;AAC5E,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AA6BD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,CAAC,EAAE,OAAO,GACb,OAAO,CAAC,iBAAiB,CAAC,CAyB5B;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWnE;AAED,wBAAgB,cAAc,IAAI,MAAM,CAUvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,WAAW,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,aAAa,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC7C;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;CAC5D;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAqCf"}
@@ -0,0 +1,141 @@
1
+ import { mkdir, readFile, writeFile } from 'fs/promises';
2
+ import { homedir } from 'os';
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { readFileSync } from 'fs';
6
+ const DEFAULT_ENDPOINT = 'https://bestax.io/api/t';
7
+ const TIMEOUT_MS = 1500;
8
+ /** Shared across the bestax CLI family so consent is asked at most once. */
9
+ export function telemetryConfigPath() {
10
+ const base = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
11
+ return join(base, 'bestax', 'telemetry.json');
12
+ }
13
+ async function readConfig() {
14
+ try {
15
+ const parsed = JSON.parse(await readFile(telemetryConfigPath(), 'utf-8'));
16
+ // Consent fails closed: only a well-formed v1 record is trusted. A bare
17
+ // {"enabled":true}, an array, or an unknown future schema version reads
18
+ // as "never asked" (prompt again) — never as an opt-in.
19
+ if (parsed !== null &&
20
+ typeof parsed === 'object' &&
21
+ !Array.isArray(parsed) &&
22
+ parsed.version === 1) {
23
+ return parsed;
24
+ }
25
+ }
26
+ catch {
27
+ // Missing or corrupt config reads as "never asked".
28
+ }
29
+ return null;
30
+ }
31
+ function doNotTrack() {
32
+ const dnt = process.env.DO_NOT_TRACK;
33
+ return dnt !== undefined && dnt !== '' && dnt !== '0';
34
+ }
35
+ /**
36
+ * Precedence: flag (persisted by the caller) → DO_NOT_TRACK → BESTAX_TELEMETRY
37
+ * (per-run, never persisted) → config file → undecided (off).
38
+ */
39
+ export async function resolveTelemetry(flag) {
40
+ if (flag !== undefined) {
41
+ return {
42
+ decision: flag ? 'on' : 'off',
43
+ source: 'flag',
44
+ };
45
+ }
46
+ if (doNotTrack()) {
47
+ return { decision: 'off', source: 'dnt' };
48
+ }
49
+ const env = process.env.BESTAX_TELEMETRY;
50
+ if (env === '0' || env === '1') {
51
+ return {
52
+ decision: env === '1' ? 'on' : 'off',
53
+ source: 'env',
54
+ };
55
+ }
56
+ const config = await readConfig();
57
+ if (config && typeof config.enabled === 'boolean') {
58
+ return {
59
+ decision: config.enabled ? 'on' : 'off',
60
+ source: 'config',
61
+ };
62
+ }
63
+ return { decision: 'undecided', source: 'default' };
64
+ }
65
+ /**
66
+ * Returns whether the decision was actually written: callers word their
67
+ * acknowledgement on it, because promising "we won't ask again" after a
68
+ * swallowed write failure would be false on every future run.
69
+ */
70
+ export async function persistTelemetryDecision(enabled, decidedBy) {
71
+ try {
72
+ const config = {
73
+ version: 1,
74
+ enabled,
75
+ decidedAt: new Date().toISOString(),
76
+ decidedBy,
77
+ };
78
+ const filePath = telemetryConfigPath();
79
+ await mkdir(dirname(filePath), { recursive: true });
80
+ await writeFile(filePath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
81
+ return true;
82
+ }
83
+ catch {
84
+ // An unwritable config dir must not break the CLI.
85
+ return false;
86
+ }
87
+ }
88
+ export async function sendTelemetry(payload) {
89
+ try {
90
+ await fetch(process.env.BESTAX_TELEMETRY_ENDPOINT || DEFAULT_ENDPOINT, {
91
+ method: 'POST',
92
+ headers: { 'content-type': 'application/json' },
93
+ body: JSON.stringify(payload),
94
+ signal: AbortSignal.timeout(TIMEOUT_MS),
95
+ });
96
+ }
97
+ catch {
98
+ // Fire-and-forget: the response (or its absence) is never surfaced.
99
+ }
100
+ }
101
+ export function getToolVersion() {
102
+ try {
103
+ const dir = dirname(fileURLToPath(import.meta.url));
104
+ const packageJson = JSON.parse(readFileSync(join(dir, '..', 'package.json'), 'utf-8'));
105
+ return packageJson.version || '0.0.0';
106
+ }
107
+ catch {
108
+ return '0.0.0';
109
+ }
110
+ }
111
+ export async function reportRun(flag, toolName, options, payload) {
112
+ const resolved = await resolveTelemetry(flag);
113
+ let decision = resolved.decision;
114
+ if (resolved.source === 'flag') {
115
+ // Flags are the scripted equivalent of the prompt: decided once,
116
+ // remembered — EXCEPT under DO_NOT_TRACK, where the flag still applies to
117
+ // this one run (an explicit ask on the command line wins) but is never
118
+ // written down, so a copied command containing --telemetry cannot enable
119
+ // telemetry beyond the run it was typed for.
120
+ if (!doNotTrack()) {
121
+ const persisted = await persistTelemetryDecision(decision === 'on', `${toolName}@${getToolVersion()}`);
122
+ // A silently unsaved flag is worse than an unsaved answer: with an
123
+ // opposite value already in the shared config, --no-telemetry would
124
+ // apply to this run only and the next family run would re-enable.
125
+ if (!persisted) {
126
+ options.onDecided?.(decision === 'on', false);
127
+ }
128
+ }
129
+ }
130
+ else if (decision === 'undecided' && options.interactive) {
131
+ const answer = await options.promptConsent();
132
+ if (answer !== null) {
133
+ const persisted = await persistTelemetryDecision(answer, `${toolName}@${getToolVersion()}`);
134
+ decision = answer ? 'on' : 'off';
135
+ options.onDecided?.(answer, persisted);
136
+ }
137
+ }
138
+ if (decision === 'on') {
139
+ await sendTelemetry(payload);
140
+ }
141
+ }
@@ -0,0 +1,31 @@
1
+ import type { ReportRunOptions } from './telemetry-core.js';
2
+ /**
3
+ * create-bestax payload + reporter. The kernel lives in telemetry-core.ts
4
+ * (copied into bestax-migrate; see that file). Envelope and props are closed
5
+ * enums, a version string, or a bounded integer — never paths, project names,
6
+ * or free text.
7
+ */
8
+ export interface ScaffoldChoices {
9
+ template: string;
10
+ bulmaFlavor: string;
11
+ iconLibrary: string;
12
+ skills: boolean;
13
+ }
14
+ export interface ScaffoldPayload {
15
+ v: 1;
16
+ tool: 'create-bestax';
17
+ event: 'scaffold';
18
+ toolVersion: string;
19
+ nodeMajor: number;
20
+ platform: string;
21
+ props: {
22
+ template: string;
23
+ bulmaFlavor: string;
24
+ iconLibrary: string;
25
+ skills: boolean;
26
+ packageManager: string;
27
+ };
28
+ }
29
+ export declare function buildScaffoldPayload(choices: ScaffoldChoices): ScaffoldPayload;
30
+ export declare function reportScaffold(choices: ScaffoldChoices, flag: boolean | undefined, options: ReportRunOptions): Promise<void>;
31
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,OAAO,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,GACvB,eAAe,CAgBjB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC,CAOf"}
@@ -0,0 +1,22 @@
1
+ import { detectPackageManager } from './package-manager.js';
2
+ import { getToolVersion, reportRun } from './telemetry-core.js';
3
+ export function buildScaffoldPayload(choices) {
4
+ return {
5
+ v: 1,
6
+ tool: 'create-bestax',
7
+ event: 'scaffold',
8
+ toolVersion: getToolVersion(),
9
+ nodeMajor: Number(process.versions.node.split('.')[0]),
10
+ platform: process.platform,
11
+ props: {
12
+ template: choices.template,
13
+ bulmaFlavor: choices.bulmaFlavor,
14
+ iconLibrary: choices.iconLibrary,
15
+ skills: choices.skills,
16
+ packageManager: detectPackageManager(),
17
+ },
18
+ };
19
+ }
20
+ export async function reportScaffold(choices, flag, options) {
21
+ await reportRun(flag, 'create-bestax', options, buildScaffoldPayload(choices));
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "4.1.2",
3
+ "version": "4.2.1",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -74,5 +74,5 @@
74
74
  "clean": "rimraf dist",
75
75
  "release": "npx semantic-release"
76
76
  },
77
- "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://socket.dev/api/badge/npm/package/create-bestax)](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[![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# Alternative naming\nnpm create bestax-bulma@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\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (Complete or Minimal)\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\n**Arguments:**\n\n- `[project-directory]` - Optional project directory name\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**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\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## 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"
77
+ "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://socket.dev/api/badge/npm/package/create-bestax)](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[![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# Alternative naming\nnpm create bestax-bulma@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\n2. Select a framework (Vite or Vite + TypeScript)\n3. Choose a Bulma CSS flavor (Complete or Minimal)\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\n**Arguments:**\n\n- `[project-directory]` - Optional project directory name\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**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\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"
78
78
  }
@@ -52,12 +52,11 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
52
52
  type="module"
53
53
  src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.esm.js"
54
54
  ></script>
55
- <script
56
- nomodule
57
- src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.js"
58
- ></script>
59
55
  ```
60
56
 
57
+ Ionicons v8 is ESM-only — there is no legacy `nomodule` bundle, so a single
58
+ `type="module"` tag is all that is needed.
59
+
61
60
  - **Scaffold flag:** `--icon ionicons` — which maps to `iconLibrary="ion"`. Passing
62
61
  `'ionicons'` as the `library`/`iconLibrary` value renders nothing.
63
62
  - **Names:** kebab-case: `rocket`, `heart`, `settings`.
@@ -6,40 +6,40 @@ codemod converts it; **Flagged** = it leaves a `TODO(bestax-migrate)` comment (s
6
6
 
7
7
  ## Top-level components
8
8
 
9
- | RBC | bestax | Notes |
10
- | -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------- |
11
- | `Block` | `Block` | Auto |
12
- | `Box` | `Box` | Auto |
13
- | `Breadcrumb` | `Breadcrumb` | Auto; `align` → `alignment` (`center`→`centered`) |
14
- | `Button` | `Button` | Auto; `<Button remove/>` → `<Delete/>`; see prop map |
15
- | `Card` | `Card` | Auto |
16
- | `Columns` | `Columns` | Auto; `breakpoint="mobile"`→`isMobile`, `multiline`→`isMultiline`, … |
17
- | `Container` | `Container` | Auto; `breakpoint="fluid"`→`fluid`, `max`→`isMax` |
18
- | `Content` | `Content` | Auto |
19
- | `Dropdown` | `Dropdown` | Structure auto; controlled `value`/`onChange` flagged |
20
- | `Element` | — | Flagged: no generic element; use a semantic component + helper props |
21
- | `Footer` | `Footer` | Auto |
22
- | `Form.*` | flat imports | See Form table below |
23
- | `Heading` | `Title` / `SubTitle` / `<p class="heading">` | Auto; `subtitle` picks `SubTitle`, `heading` picks the plain element |
24
- | `Hero` | `Hero` | Auto; `hasNavbar`→`fullheightWithNavbar`; `halfheight`/`gradient` flagged |
25
- | `Icon` | `Icon` | `<i class="fas fa-x">` children → `name`/`library`/`variant` props; else flagged |
26
- | `Image` | `Image` | Auto; numeric `size={128}` → `size="128x128"`, `rounded`→`isRounded` |
27
- | `Level` | `Level` | Auto; `breakpoint="mobile"`→`isMobile` |
28
- | `Loader` | plain `<div className="loader">` | Auto (Bulma v1 still ships the class) |
29
- | `Media` | `Media` | Auto |
30
- | `Menu` | `Menu` | Auto |
31
- | `Message` | `Message` | Auto; `size` flagged |
32
- | `Modal` | `Modal` | `show`→`active`; `closeOnEsc`/`closeOnBlur`/`showClose` flagged (defaults cover them) |
33
- | `Navbar` | `Navbar` | Auto; see dropdown note below |
34
- | `Notification` | `Notification` | Auto; `light`→`isLight` |
35
- | `Pagination` | `Pagination` | `onChange`→`onPageChange`; `delta`/labels/`showFirstLast`/`autoHide` flagged |
36
- | `Panel` | `Panel` | Auto |
37
- | `Progress` | `Progress` | Auto |
38
- | `Section` | `Section` | Auto |
39
- | `Table` | `Table` | Auto; `size="fullwidth"`→`isFullwidth`, `striped`→`isStriped`, … |
40
- | `Tabs` | `Tabs` | Auto; children wrapped in `Tabs.List`; `type="toggle-rounded"`→`toggle rounded` |
41
- | `Tag` | `Tag` | Auto; `remove`→`isDelete`, `rounded`→`isRounded` |
42
- | `Tile` | — | Flagged: Bulma v1 replaced tiles with `Grid`/`Cell` |
9
+ | RBC | bestax | Notes |
10
+ | -------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
11
+ | `Block` | `Block` | Auto |
12
+ | `Box` | `Box` | Auto |
13
+ | `Breadcrumb` | `Breadcrumb` | Auto; `align` → `alignment` (`center`→`centered`) |
14
+ | `Button` | `Button` | Auto for literal `remove` (by value, not presence — truthy → `<Delete/>`, `remove={false}` stays `Button`); dynamic flagged |
15
+ | `Card` | `Card` | Auto |
16
+ | `Columns` | `Columns` | Auto; `breakpoint="mobile"`→`isMobile`, `multiline`→`isMultiline`, … |
17
+ | `Container` | `Container` | Auto; `breakpoint="fluid"`→`fluid`, `max`→`isMax` |
18
+ | `Content` | `Content` | Auto |
19
+ | `Dropdown` | `Dropdown` | Structure auto; controlled `value`/`onChange` flagged |
20
+ | `Element` | — | Flagged: no generic element; use a semantic component + helper props |
21
+ | `Footer` | `Footer` | Auto |
22
+ | `Form.*` | flat imports | See Form table below |
23
+ | `Heading` | `Title` / `SubTitle` / `<p class="heading">` | Auto for literal `subtitle`/`heading` (by value, not presence — `subtitle={false}` is `Title`); dynamic value flagged |
24
+ | `Hero` | `Hero` | Auto; `hasNavbar`→`fullheightWithNavbar`; `halfheight`/`gradient` flagged |
25
+ | `Icon` | `Icon` | `<i class="fas fa-x">` children → `name`/`library`/`variant` props; else flagged |
26
+ | `Image` | `Image` | Auto; numeric `size={128}` → `size="128x128"`, `rounded`→`isRounded` |
27
+ | `Level` | `Level` | Auto; `breakpoint="mobile"`→`isMobile` |
28
+ | `Loader` | plain `<div className="loader">` | Auto (Bulma v1 still ships the class) |
29
+ | `Media` | `Media` | Auto |
30
+ | `Menu` | `Menu` | Auto |
31
+ | `Message` | `Message` | Auto; `size` flagged |
32
+ | `Modal` | `Modal` | `show`→`active`; `closeOnEsc`/`closeOnBlur`/`showClose` flagged (defaults cover them) |
33
+ | `Navbar` | `Navbar` | Auto; see dropdown note below |
34
+ | `Notification` | `Notification` | Auto; `light`→`isLight` |
35
+ | `Pagination` | `Pagination` | `onChange`→`onPageChange`; `delta`/labels/`showFirstLast`/`autoHide` flagged |
36
+ | `Panel` | `Panel` | Auto |
37
+ | `Progress` | `Progress` | Auto |
38
+ | `Section` | `Section` | Auto |
39
+ | `Table` | `Table` | Auto; `size="fullwidth"`→`isFullwidth`, `striped`→`isStriped`, … |
40
+ | `Tabs` | `Tabs` | Auto; children wrapped in `Tabs.List`; `type="toggle-rounded"`→`toggle rounded` |
41
+ | `Tag` | `Tag` | Auto; `remove`→`isDelete`, `rounded`→`isRounded` |
42
+ | `Tile` | — | Flagged: Bulma v1 replaced tiles with `Grid`/`Cell` |
43
43
 
44
44
  ## Compound sub-components
45
45
 
@@ -22,13 +22,18 @@ finish what it flagged.
22
22
 
23
23
  ```scss
24
24
  @use 'bulma/sass' with (
25
- $primary: #ff6b35,
26
- $family-primary: 'Nunito',
27
- sans-serif
28
- );
25
+ $primary: #ff6b35,
26
+ $family-primary: (
27
+ 'Nunito',
28
+ sans-serif,
29
+ )
30
+ );
29
31
  @use '@allxsmith/bestax-bulma/scss/extras';
30
32
  ```
31
33
 
34
+ A comma-valued override (a font-family stack) is wrapped in parens so Dart Sass
35
+ reads it as one list value, not extra arguments to `with (…)`.
36
+
32
37
  0.9 `_all` aggregator imports (`bulma/sass/elements/_all`) became directory modules
33
38
  (`@use 'bulma/sass/elements';`). Relative node_modules paths
34
39
  (`@import '../../node_modules/bulma/bulma'`, common under Parcel) keep their prefix,
@@ -38,6 +43,31 @@ finish what it flagged.
38
43
  **dart-sass ≥ 1.79** — the codemod's node-sass replacement installs that, but check
39
44
  bundler-pinned older versions (Parcel's sass transformer pins 1.66).
40
45
 
46
+ react-bulma-components' own stylesheet — any `react-bulma-components/…` specifier,
47
+ bare, `~`-prefixed, or a relative `node_modules/` path, covering the documented v3
48
+ entry points (`src/index.sass`, `dist/react-bulma-components(.min).css`) as well as
49
+ deep partials and extensionless forms — is not a third-party extension; it's the
50
+ library being migrated away from. It targets Bulma 0.9, not the v1 your components now
51
+ use, and (unless `--no-deps` is passed) `package.json` no longer lists it, so the
52
+ import can never resolve once installed. Every `--css` mode rewrites it into a real
53
+ Bulma root rather than leaving a known-broken import — the **same shape the
54
+ `@import 'bulma/…'` root path emits**, so a file that starts from RBC and one that
55
+ starts from a Bulma import converge: `bestax` (default) emits
56
+ `@use 'bulma/sass';` (folding any leading `$var` overrides above the import into
57
+ `with (…)`) plus `@use '@allxsmith/bestax-bulma/scss/extras';`; `bulma` emits plain
58
+ `@use 'bulma/sass';`; `keep` does the same with a TODO explaining the replacement
59
+ (worded to match whether `--no-deps` kept the package). It deliberately does **not**
60
+ emit the hard-configured `scss/bestax` bundle here — that bundle can't carry the
61
+ user's own theme vars, and reconfiguring `bulma/sass` when another file already
62
+ configures it is a hard Sass error. If the file already has its own `bulma/…` root
63
+ (a `@use 'bulma/sass'` of its own, or a Bulma `@import` the codemod converts), the RBC
64
+ **root/index** line (`src/index`, the bundled `dist/*.css`, or the bare specifier) is
65
+ dropped instead of emitting a second root; in `bestax` mode the extras are added once
66
+ alongside that existing root. A dropped RBC **deep partial** (e.g.
67
+ `src/components/navbar.sass`) is different — `bulma/sass` doesn't necessarily carry a
68
+ given partial's styles, so it gets a `// TODO(bestax-migrate)` and a report entry
69
+ ("port any styles it carried beyond Bulma's own by hand") rather than vanishing silently.
70
+
41
71
  - **package.json**: `react-bulma-components` removed, `@allxsmith/bestax-bulma` added,
42
72
  `bulma` bumped to `^1.0.4` (or added when sources still import `bulma/…` directly),
43
73
  and dead `node-sass` replaced with dart `sass`. Run the package manager's install
@@ -63,6 +93,8 @@ elements,form,components,grid,layout,helpers,themes}` with leaf partials like
63
93
  compatibility. Class-based usage (`className="is-checkradio"`) keeps needing the
64
94
  extension; usage that migrated to bestax components (Radio, Checkbox, the advanced
65
95
  form controls) is already styled by the bestax extras, so the import can go.
96
+ react-bulma-components' own stylesheet is never flagged this way (see above) — only
97
+ packages actually named `bulma-*` are.
66
98
 
67
99
  ## Choosing a CSS flavor (optional)
68
100
 
@@ -124,9 +124,9 @@ same rule as the extras section below).
124
124
 
125
125
  ## Extras component variables
126
126
 
127
- Every "Beyond Bulma" extra registers its own `--bulma-<component>-*` variables. They are
128
- registered on the component's **own selector** (`.avatar`, `.dialog`, `.tooltip`, … —
129
- `.bestax-avatar` etc. with the prefixed CSS flavor), not on `:root`. A value set on a wrapping
127
+ Every "Beyond Bulma" extra registers its own `--bulma-<component>-*` variables. Most are
128
+ registered on the component's **own selector** (`.avatar`, `.dialog`, … — `.bestax-avatar`
129
+ etc. with the prefixed CSS flavor), not on `:root`. A value set on a wrapping
130
130
  ancestor — including `Theme`'s `bulmaVars` on a wrapping `Theme` — is only _inherited_ and
131
131
  always loses to the component-level declaration, so it will NOT take effect. Working overrides
132
132
  target the component's own element instead: redeclare on the component's own class in your CSS
@@ -135,6 +135,20 @@ target the component's own element instead: redeclare on the component's own cla
135
135
  (`.avatar.big-avatar { --bulma-avatar-size: 3.5rem; }`), or set it via the component's `style`
136
136
  prop. Several default to core theme vars above, so they already flow through a custom theme.
137
137
 
138
+ Two exceptions where that advice silently fails, because the declarations sit somewhere a
139
+ lone class or the `style` prop cannot outrank:
140
+
141
+ - **Compound selectors** — LinkButton's variables are declared on `.button.link-button`
142
+ (specificity 0-2-0). A single class added via `className` is 0-1-0 and loses regardless of
143
+ stylesheet order. Override with the `style` prop, or in CSS loaded after the library
144
+ styles with a selector that matches or exceeds the compound:
145
+ `.button.link-button { --bulma-link-button-ghost-color: … }`.
146
+ - **Constituent elements** — Tooltip's variables (all but `--bulma-tooltip-dashed-color`)
147
+ are declared on `.tooltip-content`, and Sidebar's `--bulma-sidebar-overlay-background` on
148
+ `.sidebar-background`. Values set via `className` or `style` land on the component root and
149
+ are only inherited by the constituent, so they lose to its own declaration — target the
150
+ declaring element in your CSS: `.tooltip-content { --bulma-tooltip-background: … }`.
151
+
138
152
  ### Avatar / Avatars / Badge
139
153
 
140
154
  | Variable | Default |
@@ -238,6 +252,8 @@ own-selector override rule applies.
238
252
 
239
253
  ### LinkButton
240
254
 
255
+ Declared on the compound `.button.link-button` — see the compound-selector exception above.
256
+
241
257
  `--bulma-link-button-ghost-color`, `--bulma-link-button-ghost-hover-color`,
242
258
  `--bulma-link-button-transition-duration`, `--bulma-link-button-underline-offset`
243
259
 
@@ -378,6 +394,9 @@ own-selector override rule applies.
378
394
 
379
395
  ### Tooltip
380
396
 
397
+ All but `--bulma-tooltip-dashed-color` are declared on `.tooltip-content` — see the
398
+ constituent-element exception above.
399
+
381
400
  `--bulma-tooltip-arrow-margin`, `--bulma-tooltip-arrow-size`, `--bulma-tooltip-background`,
382
401
  `--bulma-tooltip-color`, `--bulma-tooltip-dashed-color`, `--bulma-tooltip-font-size`,
383
402
  `--bulma-tooltip-font-weight`, `--bulma-tooltip-line-height`, `--bulma-tooltip-max-width`,