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