create-bestax 1.1.0
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 +35 -0
- package/dist/__mocks__/chalk.d.ts +14 -0
- package/dist/__mocks__/chalk.d.ts.map +1 -0
- package/dist/__mocks__/chalk.js +13 -0
- package/dist/__mocks__/prompts.d.ts +3 -0
- package/dist/__mocks__/prompts.d.ts.map +1 -0
- package/dist/__mocks__/prompts.js +3 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/constants.d.ts +48 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +108 -0
- package/dist/display.d.ts +6 -0
- package/dist/display.d.ts.map +1 -0
- package/dist/display.js +34 -0
- package/dist/file-system.d.ts +9 -0
- package/dist/file-system.d.ts.map +1 -0
- package/dist/file-system.js +39 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/project-creator.d.ts +28 -0
- package/dist/project-creator.d.ts.map +1 -0
- package/dist/project-creator.js +380 -0
- package/dist/prompts.d.ts +6 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +60 -0
- package/dist/validators.d.ts +3 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +16 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# create-bestax
|
|
2
|
+
|
|
3
|
+
CLI tool for scaffolding new bestax-bulma projects.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-bestax my-app
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Templates
|
|
12
|
+
|
|
13
|
+
Available templates:
|
|
14
|
+
- `vite` - Vite + JavaScript
|
|
15
|
+
- `vite-ts` - Vite + TypeScript
|
|
16
|
+
- `nextjs` - Next.js (coming soon)
|
|
17
|
+
- `nextjs-ts` - Next.js + TypeScript (coming soon)
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run build # Build the CLI
|
|
23
|
+
npm run dev # Watch mode
|
|
24
|
+
npm test # Run tests
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Publishing
|
|
28
|
+
|
|
29
|
+
This package uses semantic-release with scope-based rules. Only commits with `feat(create-bestax)` or `fix(create-bestax)` will trigger releases.
|
|
30
|
+
|
|
31
|
+
## Known Issues
|
|
32
|
+
|
|
33
|
+
**Templates Location**: Currently templates exist in both `/templates/` (root) and `/create-bestax/templates/`. The package.json references `templates` which will look for `/create-bestax/templates/` when published to npm. The root `/templates/` directory should be removed to avoid confusion, but is currently committed in the monorepo root.
|
|
34
|
+
|
|
35
|
+
**Workaround**: The CLI code uses `path.resolve(__dirname, '../../templates')` which correctly resolves to the package's templates folder when running from the compiled dist.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const chalk: {
|
|
2
|
+
yellow: (str: string) => string;
|
|
3
|
+
blue: (str: string) => string;
|
|
4
|
+
cyan: (str: string) => string;
|
|
5
|
+
magenta: (str: string) => string;
|
|
6
|
+
green: (str: string) => string;
|
|
7
|
+
red: (str: string) => string;
|
|
8
|
+
dim: (str: string) => string;
|
|
9
|
+
bold: {
|
|
10
|
+
cyan: (str: string) => string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export default chalk;
|
|
14
|
+
//# sourceMappingURL=chalk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chalk.d.ts","sourceRoot":"","sources":["../../src/__mocks__/chalk.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,KAAK;kBACK,MAAM;gBACR,MAAM;gBACN,MAAM;mBACH,MAAM;iBACR,MAAM;eACR,MAAM;eACN,MAAM;;oBAEH,MAAM;;CAErB,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/__mocks__/prompts.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,OAAO,+DAAY,CAAC;AAE1B,eAAe,OAAO,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { ProjectCreator, type CLIOptions } from './project-creator.js';
|
|
4
|
+
export { validateProjectName } from './validators.js';
|
|
5
|
+
export { TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS } from './constants.js';
|
|
6
|
+
export type { ProjectConfig, CLIOptions } from './project-creator.js';
|
|
7
|
+
export type { Template, IconLibrary, BulmaFlavor } from './constants.js';
|
|
8
|
+
export { ProjectCreator };
|
|
9
|
+
export { displayHeader, displaySuccess } from './display.js';
|
|
10
|
+
export { promptProjectName, promptOverwriteDirectory, promptTemplate as selectTemplate, promptIconLibrary as selectIconLibrary, promptBulmaFlavor as selectBulmaFlavor, } from './prompts.js';
|
|
11
|
+
export { checkDirectoryExists, isDirectoryEmpty, copyDirectory, updatePackageJson, } from './file-system.js';
|
|
12
|
+
export declare function getProjectName(projectDir?: string): Promise<string | null>;
|
|
13
|
+
export declare function checkExistingDirectory(targetPath: string, targetDir: string): Promise<boolean>;
|
|
14
|
+
export declare function getTemplatePath(template: string): string;
|
|
15
|
+
export declare function copyTemplate(templatePath: string, targetPath: string): Promise<void>;
|
|
16
|
+
export declare function createProject(projectDir?: string, options?: CLIOptions): Promise<void>;
|
|
17
|
+
export declare function createCLI(): Command;
|
|
18
|
+
export declare function isMainModule(importMetaUrl: string, argv1: string): boolean;
|
|
19
|
+
export declare function runCLI(): void;
|
|
20
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,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;AAED,wBAAgB,SAAS,IAAI,OAAO,CAwBnC;AAED,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAgB,MAAM,IAAI,IAAI,CAG7B"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { ProjectCreator } from './project-creator.js';
|
|
4
|
+
import { copyDirectory } from './file-system.js';
|
|
5
|
+
// Re-export for backward compatibility with tests
|
|
6
|
+
export { validateProjectName } from './validators.js';
|
|
7
|
+
export { TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS } from './constants.js';
|
|
8
|
+
// Re-export individual functions for testing
|
|
9
|
+
export { ProjectCreator };
|
|
10
|
+
export { displayHeader, displaySuccess } from './display.js';
|
|
11
|
+
export { promptProjectName, promptOverwriteDirectory, promptTemplate as selectTemplate, promptIconLibrary as selectIconLibrary, promptBulmaFlavor as selectBulmaFlavor, } from './prompts.js';
|
|
12
|
+
export { checkDirectoryExists, isDirectoryEmpty, copyDirectory, updatePackageJson, } from './file-system.js';
|
|
13
|
+
// Helper function for tests
|
|
14
|
+
export async function getProjectName(projectDir) {
|
|
15
|
+
const projectCreator = new ProjectCreator();
|
|
16
|
+
return projectCreator.getProjectName(projectDir);
|
|
17
|
+
}
|
|
18
|
+
export async function checkExistingDirectory(targetPath, targetDir) {
|
|
19
|
+
const projectCreator = new ProjectCreator();
|
|
20
|
+
return projectCreator.checkExistingDirectory(targetPath, targetDir);
|
|
21
|
+
}
|
|
22
|
+
export function getTemplatePath(template) {
|
|
23
|
+
const projectCreator = new ProjectCreator();
|
|
24
|
+
return projectCreator.getTemplatePath(template);
|
|
25
|
+
}
|
|
26
|
+
export async function copyTemplate(templatePath, targetPath) {
|
|
27
|
+
// Direct pass-through to copyDirectory
|
|
28
|
+
return copyDirectory(templatePath, targetPath);
|
|
29
|
+
}
|
|
30
|
+
export async function createProject(projectDir, options) {
|
|
31
|
+
const projectCreator = new ProjectCreator();
|
|
32
|
+
return projectCreator.create(projectDir, options);
|
|
33
|
+
}
|
|
34
|
+
export function createCLI() {
|
|
35
|
+
const program = new Command();
|
|
36
|
+
const projectCreator = new ProjectCreator();
|
|
37
|
+
program
|
|
38
|
+
.name('create-bestax')
|
|
39
|
+
.description('Create a new bestax-bulma project')
|
|
40
|
+
.version('0.1.0')
|
|
41
|
+
.argument('[project-directory]', 'project directory to create')
|
|
42
|
+
.option('-t, --template <template>', 'template to use (vite, vite-ts)')
|
|
43
|
+
.option('-b, --bulma <flavor>', 'Bulma CSS flavor (complete, prefixed, no-helpers, no-helpers-prefixed, no-dark-mode)')
|
|
44
|
+
.option('-i, --icon <library>', 'icon library (none, fontawesome, mdi, ionicons, material-icons, material-symbols)')
|
|
45
|
+
.option('-y, --yes', 'skip prompts and use defaults or provided options')
|
|
46
|
+
.action((projectDir, options) => {
|
|
47
|
+
projectCreator.create(projectDir, options);
|
|
48
|
+
});
|
|
49
|
+
return program;
|
|
50
|
+
}
|
|
51
|
+
export function isMainModule(importMetaUrl, argv1) {
|
|
52
|
+
return importMetaUrl === `file://${argv1}`;
|
|
53
|
+
}
|
|
54
|
+
export function runCLI() {
|
|
55
|
+
const program = createCLI();
|
|
56
|
+
program.parse();
|
|
57
|
+
}
|
|
58
|
+
// Only run if this is the main module
|
|
59
|
+
/* istanbul ignore next */
|
|
60
|
+
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
61
|
+
runCLI();
|
|
62
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export interface Template {
|
|
3
|
+
name: string;
|
|
4
|
+
display: string;
|
|
5
|
+
color: typeof chalk.yellow;
|
|
6
|
+
}
|
|
7
|
+
export declare const TEMPLATES: Template[];
|
|
8
|
+
export declare const DEFAULT_PROJECT_NAME = "my-bestax-app";
|
|
9
|
+
export declare const MAX_PROJECT_NAME_LENGTH = 214;
|
|
10
|
+
export declare const PROJECT_NAME_REGEX: RegExp;
|
|
11
|
+
export declare const MESSAGES: {
|
|
12
|
+
readonly PROJECT_NAME_REQUIRED: "Project name is required";
|
|
13
|
+
readonly PROJECT_NAME_TOO_LONG: "Project name too long";
|
|
14
|
+
readonly PROJECT_NAME_INVALID_CHARS: "Project name can only contain letters, numbers, dots, dashes and underscores";
|
|
15
|
+
readonly OPERATION_CANCELLED: "✖ Operation cancelled";
|
|
16
|
+
readonly DIRECTORY_NOT_EMPTY: (dir: string) => string;
|
|
17
|
+
readonly EMPTYING_DIRECTORY: (dir: string) => string;
|
|
18
|
+
readonly CREATING_PROJECT: (path: string) => string;
|
|
19
|
+
readonly TEMPLATE_NOT_FOUND: (template: string) => string;
|
|
20
|
+
readonly PROJECT_CREATED: "✔ Done! Project created successfully.";
|
|
21
|
+
readonly NEXT_STEPS: "Next steps:";
|
|
22
|
+
readonly HAPPY_CODING: "Happy coding! 🎉";
|
|
23
|
+
};
|
|
24
|
+
export declare const PROMPTS: {
|
|
25
|
+
readonly PROJECT_NAME: "Project name:";
|
|
26
|
+
readonly SELECT_FRAMEWORK: "Select a framework:";
|
|
27
|
+
readonly SELECT_ICON_LIBRARY: "Would you like to add an icon library?";
|
|
28
|
+
readonly SELECT_BULMA_FLAVOR: "Which Bulma CSS flavor would you like to use?";
|
|
29
|
+
};
|
|
30
|
+
export interface IconLibrary {
|
|
31
|
+
name: string;
|
|
32
|
+
display: string;
|
|
33
|
+
color: typeof chalk.yellow;
|
|
34
|
+
packageName?: string;
|
|
35
|
+
importStatement?: string;
|
|
36
|
+
setupInstructions?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const ICON_LIBRARIES: IconLibrary[];
|
|
39
|
+
export interface BulmaFlavor {
|
|
40
|
+
name: string;
|
|
41
|
+
display: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
color: typeof chalk.yellow;
|
|
44
|
+
importStatement: string;
|
|
45
|
+
needsPrefix?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare const BULMA_FLAVORS: BulmaFlavor[];
|
|
48
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +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;;;;;wCAMQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;CAI7B,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;CAKV,CAAC;AAEX,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;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EAyCvC,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,EAuCtC,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export const TEMPLATES = [
|
|
3
|
+
{ name: 'vite', display: 'Vite', color: chalk.yellow },
|
|
4
|
+
{ name: 'vite-ts', display: 'Vite + TypeScript', color: chalk.blue },
|
|
5
|
+
];
|
|
6
|
+
export const DEFAULT_PROJECT_NAME = 'my-bestax-app';
|
|
7
|
+
export const MAX_PROJECT_NAME_LENGTH = 214;
|
|
8
|
+
export const PROJECT_NAME_REGEX = /^[a-zA-Z0-9-._]+$/;
|
|
9
|
+
export const MESSAGES = {
|
|
10
|
+
PROJECT_NAME_REQUIRED: 'Project name is required',
|
|
11
|
+
PROJECT_NAME_TOO_LONG: 'Project name too long',
|
|
12
|
+
PROJECT_NAME_INVALID_CHARS: 'Project name can only contain letters, numbers, dots, dashes and underscores',
|
|
13
|
+
OPERATION_CANCELLED: '✖ Operation cancelled',
|
|
14
|
+
DIRECTORY_NOT_EMPTY: (dir) => `Directory ${chalk.yellow(dir)} is not empty. Remove existing files and continue?`,
|
|
15
|
+
EMPTYING_DIRECTORY: (dir) => `\n Emptying ${dir}...`,
|
|
16
|
+
CREATING_PROJECT: (path) => `✔ Creating project in ${chalk.bold(path)}`,
|
|
17
|
+
TEMPLATE_NOT_FOUND: (template) => `Template not found at ${template}`,
|
|
18
|
+
PROJECT_CREATED: '✔ Done! Project created successfully.',
|
|
19
|
+
NEXT_STEPS: 'Next steps:',
|
|
20
|
+
HAPPY_CODING: 'Happy coding! 🎉',
|
|
21
|
+
};
|
|
22
|
+
export const PROMPTS = {
|
|
23
|
+
PROJECT_NAME: 'Project name:',
|
|
24
|
+
SELECT_FRAMEWORK: 'Select a framework:',
|
|
25
|
+
SELECT_ICON_LIBRARY: 'Would you like to add an icon library?',
|
|
26
|
+
SELECT_BULMA_FLAVOR: 'Which Bulma CSS flavor would you like to use?',
|
|
27
|
+
};
|
|
28
|
+
export const ICON_LIBRARIES = [
|
|
29
|
+
{
|
|
30
|
+
name: 'none',
|
|
31
|
+
display: "None (I'll add icons later)",
|
|
32
|
+
color: chalk.gray,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'fontawesome',
|
|
36
|
+
display: 'Font Awesome',
|
|
37
|
+
color: chalk.blue,
|
|
38
|
+
packageName: '@fortawesome/fontawesome-free',
|
|
39
|
+
importStatement: "import '@fortawesome/fontawesome-free/css/all.min.css';",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'mdi',
|
|
43
|
+
display: 'Material Design Icons',
|
|
44
|
+
color: chalk.cyan,
|
|
45
|
+
packageName: '@mdi/font',
|
|
46
|
+
importStatement: "import '@mdi/font/css/materialdesignicons.min.css';",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'ionicons',
|
|
50
|
+
display: 'Ionicons',
|
|
51
|
+
color: chalk.green,
|
|
52
|
+
// Note: ionicons doesn't need packageName or importStatement
|
|
53
|
+
// as it's loaded via CDN in index.html
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'material-icons',
|
|
57
|
+
display: 'Google Material Icons',
|
|
58
|
+
color: chalk.yellow,
|
|
59
|
+
packageName: 'material-icons',
|
|
60
|
+
importStatement: "import 'material-icons';",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'material-symbols',
|
|
64
|
+
display: 'Material Symbols',
|
|
65
|
+
color: chalk.magenta,
|
|
66
|
+
packageName: 'material-symbols',
|
|
67
|
+
importStatement: "import 'material-symbols';",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
export const BULMA_FLAVORS = [
|
|
71
|
+
{
|
|
72
|
+
name: 'complete',
|
|
73
|
+
display: 'Complete (Recommended)',
|
|
74
|
+
description: 'Full Bulma CSS with all components and helpers',
|
|
75
|
+
color: chalk.green,
|
|
76
|
+
importStatement: "import 'bulma/css/bulma.min.css';",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'prefixed',
|
|
80
|
+
display: 'Prefixed',
|
|
81
|
+
description: 'All classes prefixed with "bulma-" to avoid conflicts',
|
|
82
|
+
color: chalk.blue,
|
|
83
|
+
importStatement: "import 'bulma/css/versions/bulma-prefixed.min.css';",
|
|
84
|
+
needsPrefix: true,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'no-helpers',
|
|
88
|
+
display: 'No Helpers',
|
|
89
|
+
description: 'Core components only, no utility classes',
|
|
90
|
+
color: chalk.yellow,
|
|
91
|
+
importStatement: "import 'bulma/css/versions/bulma-no-helpers.min.css';",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'no-helpers-prefixed',
|
|
95
|
+
display: 'No Helpers, Prefixed',
|
|
96
|
+
description: 'Core components only with "bulma-" prefix',
|
|
97
|
+
color: chalk.magenta,
|
|
98
|
+
importStatement: "import 'bulma/css/versions/bulma-no-helpers-prefixed.min.css';",
|
|
99
|
+
needsPrefix: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'no-dark-mode',
|
|
103
|
+
display: 'No Dark Mode',
|
|
104
|
+
description: 'Light mode only, smaller bundle size',
|
|
105
|
+
color: chalk.cyan,
|
|
106
|
+
importStatement: "import 'bulma/css/versions/bulma-no-dark-mode.min.css';",
|
|
107
|
+
},
|
|
108
|
+
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function displayHeader(): void;
|
|
2
|
+
export declare function displaySuccess(targetDir: string): void;
|
|
3
|
+
export declare function displayError(message: string): void;
|
|
4
|
+
export declare function displayInfo(message: string): void;
|
|
5
|
+
export declare function displayCancelled(): void;
|
|
6
|
+
//# sourceMappingURL=display.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"AAGA,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAmBtD;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
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { MESSAGES } from './constants.js';
|
|
3
|
+
export function displayHeader() {
|
|
4
|
+
console.log();
|
|
5
|
+
console.log(chalk.cyan('━'.repeat(50)));
|
|
6
|
+
console.log(chalk.cyan(chalk.bold(' 🐝 Create Bestax App')));
|
|
7
|
+
console.log(chalk.cyan('━'.repeat(50)));
|
|
8
|
+
console.log();
|
|
9
|
+
}
|
|
10
|
+
export function displaySuccess(targetDir) {
|
|
11
|
+
console.log();
|
|
12
|
+
console.log(chalk.green(MESSAGES.PROJECT_CREATED));
|
|
13
|
+
console.log();
|
|
14
|
+
console.log(chalk.bold(MESSAGES.NEXT_STEPS));
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(chalk.cyan(` cd ${targetDir}`));
|
|
17
|
+
console.log(chalk.cyan(' npm install'));
|
|
18
|
+
console.log(chalk.cyan(' npm run dev'));
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(chalk.dim(MESSAGES.HAPPY_CODING));
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk.yellow('⭐ If you enjoy using bestax-bulma, please star us on GitHub!'));
|
|
23
|
+
console.log(chalk.dim(' https://github.com/allxsmith/bestax'));
|
|
24
|
+
console.log();
|
|
25
|
+
}
|
|
26
|
+
export function displayError(message) {
|
|
27
|
+
console.error(chalk.red(`✖ ${message}`));
|
|
28
|
+
}
|
|
29
|
+
export function displayInfo(message) {
|
|
30
|
+
console.log(chalk.yellow(message));
|
|
31
|
+
}
|
|
32
|
+
export function displayCancelled() {
|
|
33
|
+
console.log(chalk.red(MESSAGES.OPERATION_CANCELLED));
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function checkDirectoryExists(targetPath: string): Promise<boolean>;
|
|
2
|
+
export declare function isDirectoryEmpty(targetPath: string): Promise<boolean>;
|
|
3
|
+
export declare function emptyDirectory(targetPath: string): Promise<void>;
|
|
4
|
+
export declare function ensureDirectory(targetPath: string): Promise<void>;
|
|
5
|
+
export declare function copyDirectory(source: string, destination: string): Promise<void>;
|
|
6
|
+
export declare function readJsonFile<T = unknown>(filePath: string): Promise<T>;
|
|
7
|
+
export declare function writeJsonFile(filePath: string, data: unknown, spaces?: number): Promise<void>;
|
|
8
|
+
export declare function updatePackageJson(targetPath: string, projectName: string): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=file-system.d.ts.map
|
|
@@ -0,0 +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,GAClB,OAAO,CAAC,IAAI,CAAC,CAKf;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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { MESSAGES } from './constants.js';
|
|
4
|
+
export async function checkDirectoryExists(targetPath) {
|
|
5
|
+
return fs.existsSync(targetPath);
|
|
6
|
+
}
|
|
7
|
+
export async function isDirectoryEmpty(targetPath) {
|
|
8
|
+
if (!fs.existsSync(targetPath)) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
const files = fs.readdirSync(targetPath);
|
|
12
|
+
return files.length === 0;
|
|
13
|
+
}
|
|
14
|
+
export async function emptyDirectory(targetPath) {
|
|
15
|
+
await fs.emptyDir(targetPath);
|
|
16
|
+
}
|
|
17
|
+
export async function ensureDirectory(targetPath) {
|
|
18
|
+
await fs.ensureDir(targetPath);
|
|
19
|
+
}
|
|
20
|
+
export async function copyDirectory(source, destination) {
|
|
21
|
+
if (!(await checkDirectoryExists(source))) {
|
|
22
|
+
throw new Error(MESSAGES.TEMPLATE_NOT_FOUND(source));
|
|
23
|
+
}
|
|
24
|
+
await fs.copy(source, destination);
|
|
25
|
+
}
|
|
26
|
+
export async function readJsonFile(filePath) {
|
|
27
|
+
return fs.readJson(filePath);
|
|
28
|
+
}
|
|
29
|
+
export async function writeJsonFile(filePath, data, spaces = 2) {
|
|
30
|
+
await fs.writeJson(filePath, data, { spaces });
|
|
31
|
+
}
|
|
32
|
+
export async function updatePackageJson(targetPath, projectName) {
|
|
33
|
+
const packageJsonPath = path.join(targetPath, 'package.json');
|
|
34
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
35
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
36
|
+
packageJson.name = projectName;
|
|
37
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ProjectConfig {
|
|
2
|
+
projectName: string;
|
|
3
|
+
template: string;
|
|
4
|
+
targetPath: string;
|
|
5
|
+
iconLibrary?: string;
|
|
6
|
+
bulmaFlavor?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CLIOptions {
|
|
9
|
+
template?: string;
|
|
10
|
+
bulma?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
yes?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class ProjectCreator {
|
|
15
|
+
private templatesDir;
|
|
16
|
+
constructor(templatesDir?: string);
|
|
17
|
+
getProjectName(projectDir?: string): Promise<string | null>;
|
|
18
|
+
checkExistingDirectory(targetPath: string, targetDir: string): Promise<boolean>;
|
|
19
|
+
getTemplatePath(template: string): string;
|
|
20
|
+
copyTemplate(template: string, targetPath: string): Promise<void>;
|
|
21
|
+
setupBulmaFlavor(targetPath: string, bulmaFlavor: string, template: string): Promise<void>;
|
|
22
|
+
private getIconName;
|
|
23
|
+
private getIconProps;
|
|
24
|
+
setupIconLibrary(targetPath: string, iconLibrary: string, template: string): Promise<void>;
|
|
25
|
+
setupConfigProvider(targetPath: string, bulmaFlavor: string, iconLibrary: string, template: string): Promise<void>;
|
|
26
|
+
create(projectDir?: string, options?: CLIOptions): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=project-creator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA8BA,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,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAK3B,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,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAyChB,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;IA0IV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAwEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAwHvE"}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { checkDirectoryExists, isDirectoryEmpty, emptyDirectory, ensureDirectory, copyDirectory, updatePackageJson, } from './file-system.js';
|
|
6
|
+
import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIconLibrary, promptBulmaFlavor, } from './prompts.js';
|
|
7
|
+
import { displayHeader, displaySuccess, displayError, displayCancelled, } from './display.js';
|
|
8
|
+
import { validateProjectName } from './validators.js';
|
|
9
|
+
import { MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS } from './constants.js';
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
export class ProjectCreator {
|
|
12
|
+
templatesDir;
|
|
13
|
+
constructor(templatesDir) {
|
|
14
|
+
this.templatesDir =
|
|
15
|
+
templatesDir || path.resolve(__dirname, '../../templates');
|
|
16
|
+
}
|
|
17
|
+
async getProjectName(projectDir) {
|
|
18
|
+
if (projectDir) {
|
|
19
|
+
const validation = validateProjectName(projectDir);
|
|
20
|
+
if (validation !== true) {
|
|
21
|
+
console.log(chalk.red(`✖ ${validation}`));
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return projectDir;
|
|
25
|
+
}
|
|
26
|
+
return promptProjectName();
|
|
27
|
+
}
|
|
28
|
+
async checkExistingDirectory(targetPath, targetDir) {
|
|
29
|
+
const exists = await checkDirectoryExists(targetPath);
|
|
30
|
+
const isEmpty = await isDirectoryEmpty(targetPath);
|
|
31
|
+
if (exists && !isEmpty) {
|
|
32
|
+
const shouldOverwrite = await promptOverwriteDirectory(targetDir);
|
|
33
|
+
if (!shouldOverwrite) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
console.log(chalk.yellow(`\n Emptying ${targetDir}...`));
|
|
37
|
+
await emptyDirectory(targetPath);
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
getTemplatePath(template) {
|
|
42
|
+
return path.join(this.templatesDir, template);
|
|
43
|
+
}
|
|
44
|
+
async copyTemplate(template, targetPath) {
|
|
45
|
+
const templatePath = this.getTemplatePath(template);
|
|
46
|
+
await ensureDirectory(targetPath);
|
|
47
|
+
await copyDirectory(templatePath, targetPath);
|
|
48
|
+
}
|
|
49
|
+
async setupBulmaFlavor(targetPath, bulmaFlavor, template) {
|
|
50
|
+
const flavor = BULMA_FLAVORS.find(f => f.name === bulmaFlavor);
|
|
51
|
+
if (!flavor) {
|
|
52
|
+
console.log(`Warning: Bulma flavor '${bulmaFlavor}' not found in BULMA_FLAVORS`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const isTypeScript = template.includes('-ts');
|
|
56
|
+
const mainFileName = isTypeScript ? 'main.tsx' : 'main.jsx';
|
|
57
|
+
const mainFilePath = path.join(targetPath, 'src', mainFileName);
|
|
58
|
+
if (fs.existsSync(mainFilePath)) {
|
|
59
|
+
let content = await fs.readFile(mainFilePath, 'utf8');
|
|
60
|
+
// Replace the default Bulma import with the selected flavor
|
|
61
|
+
const bulmaImportRegex = /import\s+['"]bulma\/css\/bulma\.min\.css['"]\s*;?/;
|
|
62
|
+
if (bulmaImportRegex.test(content)) {
|
|
63
|
+
content = content.replace(bulmaImportRegex, flavor.importStatement);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// If no Bulma import found, add it after React import
|
|
67
|
+
const reactImportMatch = content.match(/import\s+.*\s+from\s+['"]react['"]/);
|
|
68
|
+
if (reactImportMatch) {
|
|
69
|
+
const insertPosition = reactImportMatch.index + reactImportMatch[0].length;
|
|
70
|
+
content =
|
|
71
|
+
content.slice(0, insertPosition) +
|
|
72
|
+
'\n' +
|
|
73
|
+
flavor.importStatement +
|
|
74
|
+
content.slice(insertPosition);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await fs.writeFile(mainFilePath, content);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
getIconName(iconLibrary, iconType) {
|
|
81
|
+
const iconMappings = {
|
|
82
|
+
fontawesome: { rocket: 'rocket', book: 'book', code: 'code' },
|
|
83
|
+
mdi: {
|
|
84
|
+
rocket: 'rocket-launch',
|
|
85
|
+
book: 'book-open-page-variant',
|
|
86
|
+
code: 'code-tags',
|
|
87
|
+
},
|
|
88
|
+
ionicons: { rocket: 'rocket', book: 'book', code: 'code-slash' },
|
|
89
|
+
'material-icons': {
|
|
90
|
+
rocket: 'rocket_launch',
|
|
91
|
+
book: 'menu_book',
|
|
92
|
+
code: 'code',
|
|
93
|
+
},
|
|
94
|
+
'material-symbols': {
|
|
95
|
+
rocket: 'rocket_launch',
|
|
96
|
+
book: 'menu_book',
|
|
97
|
+
code: 'code',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
return iconMappings[iconLibrary]?.[iconType] || '';
|
|
101
|
+
}
|
|
102
|
+
getIconProps(iconLibrary, iconType) {
|
|
103
|
+
const iconName = this.getIconName(iconLibrary, iconType);
|
|
104
|
+
if (!iconName)
|
|
105
|
+
return '';
|
|
106
|
+
return iconLibrary === 'fontawesome'
|
|
107
|
+
? `name="${iconName}" variant="solid"`
|
|
108
|
+
: `name="${iconName}"`;
|
|
109
|
+
}
|
|
110
|
+
async setupIconLibrary(targetPath, iconLibrary, template) {
|
|
111
|
+
if (iconLibrary === 'none')
|
|
112
|
+
return;
|
|
113
|
+
const library = ICON_LIBRARIES.find(lib => lib.name === iconLibrary);
|
|
114
|
+
if (!library)
|
|
115
|
+
return;
|
|
116
|
+
// Special handling for ionicons - add to index.html instead of package.json
|
|
117
|
+
if (iconLibrary === 'ionicons') {
|
|
118
|
+
const indexHtmlPath = path.join(targetPath, 'index.html');
|
|
119
|
+
if (fs.existsSync(indexHtmlPath)) {
|
|
120
|
+
let htmlContent = await fs.readFile(indexHtmlPath, 'utf8');
|
|
121
|
+
// Add ionicons scripts before the closing </head> tag
|
|
122
|
+
const headEndMatch = htmlContent.match(/<\/head>/);
|
|
123
|
+
if (headEndMatch) {
|
|
124
|
+
const insertPosition = headEndMatch.index;
|
|
125
|
+
const ioniconScripts = ` <!-- Ionicons -->
|
|
126
|
+
<script type="module" src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.esm.js"></script>
|
|
127
|
+
<script nomodule src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.js"></script>
|
|
128
|
+
`;
|
|
129
|
+
htmlContent =
|
|
130
|
+
htmlContent.slice(0, insertPosition) +
|
|
131
|
+
ioniconScripts +
|
|
132
|
+
htmlContent.slice(insertPosition);
|
|
133
|
+
await fs.writeFile(indexHtmlPath, htmlContent);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (!library.packageName) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// For non-ionicons libraries, add the package to package.json dependencies
|
|
141
|
+
if (iconLibrary !== 'ionicons') {
|
|
142
|
+
const packageJsonPath = path.join(targetPath, 'package.json');
|
|
143
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
144
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
145
|
+
if (!packageJson.dependencies) {
|
|
146
|
+
packageJson.dependencies = {};
|
|
147
|
+
}
|
|
148
|
+
if (library.packageName) {
|
|
149
|
+
packageJson.dependencies[library.packageName] = 'latest';
|
|
150
|
+
}
|
|
151
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
152
|
+
// Add import statement to main file
|
|
153
|
+
if (library.importStatement) {
|
|
154
|
+
const isTypeScript = template.includes('-ts');
|
|
155
|
+
const mainFileName = isTypeScript ? 'main.tsx' : 'main.jsx';
|
|
156
|
+
const mainFilePath = path.join(targetPath, 'src', mainFileName);
|
|
157
|
+
if (fs.existsSync(mainFilePath)) {
|
|
158
|
+
let content = await fs.readFile(mainFilePath, 'utf8');
|
|
159
|
+
// Add the icon library import before the Bulma CSS import
|
|
160
|
+
const bulmaImportMatch = content.match(/import\s+['"]bulma\/css\/.*?['"]/);
|
|
161
|
+
if (bulmaImportMatch) {
|
|
162
|
+
const insertPosition = bulmaImportMatch.index;
|
|
163
|
+
content =
|
|
164
|
+
content.slice(0, insertPosition) +
|
|
165
|
+
library.importStatement +
|
|
166
|
+
'\n' +
|
|
167
|
+
content.slice(insertPosition);
|
|
168
|
+
await fs.writeFile(mainFilePath, content);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Now add Icon components to App.jsx/App.tsx (for all icon libraries)
|
|
175
|
+
const isTypeScript = template.includes('-ts');
|
|
176
|
+
const appFileName = isTypeScript ? 'App.tsx' : 'App.jsx';
|
|
177
|
+
const appFilePath = path.join(targetPath, 'src', appFileName);
|
|
178
|
+
if (fs.existsSync(appFilePath)) {
|
|
179
|
+
let appContent = await fs.readFile(appFilePath, 'utf8');
|
|
180
|
+
// Add Icon import to the bestax-bulma imports
|
|
181
|
+
const bulmaImportRegex = /(import\s+\{[\s\S]*?)(}\s+from\s+['"]@allxsmith\/bestax-bulma['"])/;
|
|
182
|
+
if (bulmaImportRegex.test(appContent) && !appContent.includes('Icon')) {
|
|
183
|
+
// Check if the import ends with a comma or not
|
|
184
|
+
const importMatch = appContent.match(bulmaImportRegex);
|
|
185
|
+
if (importMatch) {
|
|
186
|
+
const beforeClosingBrace = importMatch[1];
|
|
187
|
+
// Remove any trailing comma and whitespace, then add Icon properly
|
|
188
|
+
const cleanedImport = beforeClosingBrace.replace(/,?\s*$/, '');
|
|
189
|
+
appContent = appContent.replace(bulmaImportRegex, cleanedImport + ',\n Icon$2');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Add icon examples in the Cards section
|
|
193
|
+
// Find the Quick Start Card and add an icon
|
|
194
|
+
const quickStartRegex = /(Card\.Header\.Title>\s*Quick Start\s*<\/Card\.Header\.Title>)/;
|
|
195
|
+
if (quickStartRegex.test(appContent)) {
|
|
196
|
+
const iconProps = this.getIconProps(iconLibrary, 'rocket');
|
|
197
|
+
if (iconProps) {
|
|
198
|
+
appContent = appContent.replace(quickStartRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Quick Start\n </Card.Header.Title>`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Add icon to Documentation Card
|
|
202
|
+
const docsRegex = /(Card\.Header\.Title>\s*Documentation\s*<\/Card\.Header\.Title>)/;
|
|
203
|
+
if (docsRegex.test(appContent)) {
|
|
204
|
+
const iconProps = this.getIconProps(iconLibrary, 'book');
|
|
205
|
+
if (iconProps) {
|
|
206
|
+
appContent = appContent.replace(docsRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Documentation\n </Card.Header.Title>`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Add icon to Examples Card
|
|
210
|
+
const examplesRegex = /(Card\.Header\.Title>\s*Examples\s*<\/Card\.Header\.Title>)/;
|
|
211
|
+
if (examplesRegex.test(appContent)) {
|
|
212
|
+
const iconProps = this.getIconProps(iconLibrary, 'code');
|
|
213
|
+
if (iconProps) {
|
|
214
|
+
appContent = appContent.replace(examplesRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Examples\n </Card.Header.Title>`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
await fs.writeFile(appFilePath, appContent);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async setupConfigProvider(targetPath, bulmaFlavor, iconLibrary, template) {
|
|
221
|
+
const flavor = BULMA_FLAVORS.find(f => f.name === bulmaFlavor);
|
|
222
|
+
const needsPrefix = flavor?.needsPrefix || false;
|
|
223
|
+
// Don't add ConfigProvider for 'none' or 'fontawesome' (default)
|
|
224
|
+
const needsIconLibrary = iconLibrary !== 'none' && iconLibrary !== 'fontawesome';
|
|
225
|
+
// Only add ConfigProvider if we need prefix or non-default icon library
|
|
226
|
+
if (!needsPrefix && !needsIconLibrary)
|
|
227
|
+
return;
|
|
228
|
+
const isTypeScript = template.includes('-ts');
|
|
229
|
+
const mainFileName = isTypeScript ? 'main.tsx' : 'main.jsx';
|
|
230
|
+
const mainFilePath = path.join(targetPath, 'src', mainFileName);
|
|
231
|
+
if (fs.existsSync(mainFilePath)) {
|
|
232
|
+
let content = await fs.readFile(mainFilePath, 'utf8');
|
|
233
|
+
// Add ConfigProvider import from bestax-bulma
|
|
234
|
+
const configProviderImport = "import { ConfigProvider } from '@allxsmith/bestax-bulma';";
|
|
235
|
+
// Check if ConfigProvider is already imported
|
|
236
|
+
if (!content.includes('ConfigProvider')) {
|
|
237
|
+
// Add import after the App import line
|
|
238
|
+
const appImportMatch = content.match(/import\s+App\s+from\s+['"]\.\/App\.(jsx|tsx)?['"]\s*;?/);
|
|
239
|
+
if (appImportMatch) {
|
|
240
|
+
const insertPosition = appImportMatch.index + appImportMatch[0].length;
|
|
241
|
+
content =
|
|
242
|
+
content.slice(0, insertPosition) +
|
|
243
|
+
'\n' +
|
|
244
|
+
configProviderImport +
|
|
245
|
+
content.slice(insertPosition);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Build ConfigProvider props
|
|
249
|
+
const configProps = [];
|
|
250
|
+
if (needsPrefix) {
|
|
251
|
+
configProps.push('classPrefix="bulma-"');
|
|
252
|
+
}
|
|
253
|
+
if (needsIconLibrary) {
|
|
254
|
+
// Map the icon library name to the correct value for ConfigProvider
|
|
255
|
+
const iconLibraryMap = {
|
|
256
|
+
mdi: 'mdi',
|
|
257
|
+
ionicons: 'ion',
|
|
258
|
+
'material-icons': 'material-icons',
|
|
259
|
+
'material-symbols': 'material-symbols',
|
|
260
|
+
};
|
|
261
|
+
const iconLibraryValue = iconLibraryMap[iconLibrary];
|
|
262
|
+
if (iconLibraryValue) {
|
|
263
|
+
configProps.push(`iconLibrary="${iconLibraryValue}"`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Wrap <App /> with ConfigProvider in the render call
|
|
267
|
+
const appRegex = /<App\s*\/>/;
|
|
268
|
+
if (appRegex.test(content)) {
|
|
269
|
+
const propsString = configProps.length > 0 ? ' ' + configProps.join(' ') : '';
|
|
270
|
+
content = content.replace(appRegex, `<ConfigProvider${propsString}>\n <App />\n </ConfigProvider>`);
|
|
271
|
+
}
|
|
272
|
+
await fs.writeFile(mainFilePath, content);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async create(projectDir, options) {
|
|
276
|
+
displayHeader();
|
|
277
|
+
// Get project name
|
|
278
|
+
const targetDir = await this.getProjectName(projectDir);
|
|
279
|
+
if (!targetDir) {
|
|
280
|
+
displayCancelled();
|
|
281
|
+
process.exit(1);
|
|
282
|
+
return; // TypeScript flow control
|
|
283
|
+
}
|
|
284
|
+
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
285
|
+
const projectName = path.basename(targetPath);
|
|
286
|
+
// Check existing directory (skip prompt if --yes is provided)
|
|
287
|
+
const canContinue = options?.yes
|
|
288
|
+
? true
|
|
289
|
+
: await this.checkExistingDirectory(targetPath, targetDir);
|
|
290
|
+
if (!canContinue) {
|
|
291
|
+
displayCancelled();
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
// If --yes and directory exists, empty it
|
|
295
|
+
if (options?.yes &&
|
|
296
|
+
(await checkDirectoryExists(targetPath)) &&
|
|
297
|
+
!(await isDirectoryEmpty(targetPath))) {
|
|
298
|
+
console.log(chalk.yellow(`\n Emptying ${targetDir}...`));
|
|
299
|
+
await emptyDirectory(targetPath);
|
|
300
|
+
}
|
|
301
|
+
// Select template (use option or prompt)
|
|
302
|
+
let template;
|
|
303
|
+
if (options?.template) {
|
|
304
|
+
// Validate the provided template
|
|
305
|
+
const validTemplates = ['vite', 'vite-ts'];
|
|
306
|
+
if (!validTemplates.includes(options.template)) {
|
|
307
|
+
displayError(`Invalid template: ${options.template}. Valid options are: ${validTemplates.join(', ')}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
template = options.template;
|
|
311
|
+
}
|
|
312
|
+
else if (options?.yes) {
|
|
313
|
+
template = 'vite'; // default template
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
template = await promptTemplate();
|
|
317
|
+
if (!template) {
|
|
318
|
+
displayCancelled();
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Select Bulma flavor (use option or prompt)
|
|
323
|
+
let bulmaFlavor;
|
|
324
|
+
if (options?.bulma) {
|
|
325
|
+
// Validate the provided Bulma flavor
|
|
326
|
+
const validFlavors = BULMA_FLAVORS.map(f => f.name);
|
|
327
|
+
if (!validFlavors.includes(options.bulma)) {
|
|
328
|
+
displayError(`Invalid Bulma flavor: ${options.bulma}. Valid options are: ${validFlavors.join(', ')}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
bulmaFlavor = options.bulma;
|
|
332
|
+
}
|
|
333
|
+
else if (options?.yes) {
|
|
334
|
+
bulmaFlavor = 'complete'; // default flavor
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
bulmaFlavor = await promptBulmaFlavor();
|
|
338
|
+
if (bulmaFlavor === null) {
|
|
339
|
+
displayCancelled();
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Select icon library (use option or prompt)
|
|
344
|
+
let iconLibrary;
|
|
345
|
+
if (options?.icon) {
|
|
346
|
+
// Validate the provided icon library
|
|
347
|
+
const validLibraries = ICON_LIBRARIES.map(lib => lib.name);
|
|
348
|
+
if (!validLibraries.includes(options.icon)) {
|
|
349
|
+
displayError(`Invalid icon library: ${options.icon}. Valid options are: ${validLibraries.join(', ')}`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
iconLibrary = options.icon;
|
|
353
|
+
}
|
|
354
|
+
else if (options?.yes) {
|
|
355
|
+
iconLibrary = 'none'; // default icon library
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
iconLibrary = await promptIconLibrary();
|
|
359
|
+
if (iconLibrary === null) {
|
|
360
|
+
displayCancelled();
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Create project
|
|
365
|
+
console.log();
|
|
366
|
+
console.log(chalk.green(MESSAGES.CREATING_PROJECT(targetPath)));
|
|
367
|
+
try {
|
|
368
|
+
await this.copyTemplate(template, targetPath);
|
|
369
|
+
await updatePackageJson(targetPath, projectName);
|
|
370
|
+
await this.setupBulmaFlavor(targetPath, bulmaFlavor, template);
|
|
371
|
+
await this.setupIconLibrary(targetPath, iconLibrary, template);
|
|
372
|
+
await this.setupConfigProvider(targetPath, bulmaFlavor, iconLibrary, template);
|
|
373
|
+
displaySuccess(targetDir);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
displayError(error.message);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function promptProjectName(): Promise<string | null>;
|
|
2
|
+
export declare function promptOverwriteDirectory(targetDir: string): Promise<boolean>;
|
|
3
|
+
export declare function promptTemplate(): Promise<string | null>;
|
|
4
|
+
export declare function promptIconLibrary(): Promise<string | null>;
|
|
5
|
+
export declare function promptBulmaFlavor(): Promise<string | null>;
|
|
6
|
+
//# sourceMappingURL=prompts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAWA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUhE;AAED,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAY7D;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAahE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAehE"}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import { TEMPLATES, DEFAULT_PROJECT_NAME, PROMPTS as PROMPT_MESSAGES, MESSAGES, ICON_LIBRARIES, BULMA_FLAVORS, } from './constants.js';
|
|
3
|
+
import { validateProjectName } from './validators.js';
|
|
4
|
+
export async function promptProjectName() {
|
|
5
|
+
const response = await prompts({
|
|
6
|
+
type: 'text',
|
|
7
|
+
name: 'projectName',
|
|
8
|
+
message: PROMPT_MESSAGES.PROJECT_NAME,
|
|
9
|
+
initial: DEFAULT_PROJECT_NAME,
|
|
10
|
+
validate: validateProjectName,
|
|
11
|
+
});
|
|
12
|
+
return response.projectName || null;
|
|
13
|
+
}
|
|
14
|
+
export async function promptOverwriteDirectory(targetDir) {
|
|
15
|
+
const response = await prompts({
|
|
16
|
+
type: 'confirm',
|
|
17
|
+
name: 'overwrite',
|
|
18
|
+
message: MESSAGES.DIRECTORY_NOT_EMPTY(targetDir),
|
|
19
|
+
initial: false,
|
|
20
|
+
});
|
|
21
|
+
return response.overwrite === true;
|
|
22
|
+
}
|
|
23
|
+
export async function promptTemplate() {
|
|
24
|
+
const response = await prompts({
|
|
25
|
+
type: 'select',
|
|
26
|
+
name: 'template',
|
|
27
|
+
message: PROMPT_MESSAGES.SELECT_FRAMEWORK,
|
|
28
|
+
choices: TEMPLATES.map(template => ({
|
|
29
|
+
title: template.color(template.display),
|
|
30
|
+
value: template.name,
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
return response.template || null;
|
|
34
|
+
}
|
|
35
|
+
export async function promptIconLibrary() {
|
|
36
|
+
const response = await prompts({
|
|
37
|
+
type: 'select',
|
|
38
|
+
name: 'iconLibrary',
|
|
39
|
+
message: PROMPT_MESSAGES.SELECT_ICON_LIBRARY,
|
|
40
|
+
choices: ICON_LIBRARIES.map(lib => ({
|
|
41
|
+
title: lib.color(lib.display),
|
|
42
|
+
value: lib.name,
|
|
43
|
+
})),
|
|
44
|
+
initial: 0, // Default to 'none'
|
|
45
|
+
});
|
|
46
|
+
return response.iconLibrary || null;
|
|
47
|
+
}
|
|
48
|
+
export async function promptBulmaFlavor() {
|
|
49
|
+
const response = await prompts({
|
|
50
|
+
type: 'select',
|
|
51
|
+
name: 'bulmaFlavor',
|
|
52
|
+
message: PROMPT_MESSAGES.SELECT_BULMA_FLAVOR,
|
|
53
|
+
choices: BULMA_FLAVORS.map(flavor => ({
|
|
54
|
+
title: flavor.color(`${flavor.display}${flavor.description ? ` - ${flavor.description}` : ''}`),
|
|
55
|
+
value: flavor.name,
|
|
56
|
+
})),
|
|
57
|
+
initial: 0, // Default to 'complete' (recommended)
|
|
58
|
+
});
|
|
59
|
+
return response.bulmaFlavor || null;
|
|
60
|
+
}
|
|
@@ -0,0 +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,CAcnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PROJECT_NAME_REGEX, MAX_PROJECT_NAME_LENGTH, MESSAGES, } from './constants.js';
|
|
2
|
+
export function validateProjectName(value) {
|
|
3
|
+
if (!value) {
|
|
4
|
+
return MESSAGES.PROJECT_NAME_REQUIRED;
|
|
5
|
+
}
|
|
6
|
+
if (value.length > MAX_PROJECT_NAME_LENGTH) {
|
|
7
|
+
return MESSAGES.PROJECT_NAME_TOO_LONG;
|
|
8
|
+
}
|
|
9
|
+
if (!PROJECT_NAME_REGEX.test(value)) {
|
|
10
|
+
return MESSAGES.PROJECT_NAME_INVALID_CHARS;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
export function isValidProjectName(name) {
|
|
15
|
+
return validateProjectName(name) === true;
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-bestax",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Create a new bestax-bulma project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-bestax": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest",
|
|
17
|
+
"test:watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch",
|
|
18
|
+
"test:coverage": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --coverage",
|
|
19
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
22
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
|
23
|
+
"clean": "rimraf dist",
|
|
24
|
+
"release": "npx semantic-release"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"bestax",
|
|
28
|
+
"bulma",
|
|
29
|
+
"react",
|
|
30
|
+
"create",
|
|
31
|
+
"cli",
|
|
32
|
+
"scaffold"
|
|
33
|
+
],
|
|
34
|
+
"author": "Alex Smith",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/allxsmith/bestax.git",
|
|
39
|
+
"directory": "create-bestax"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://bestax.io",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/allxsmith/bestax/issues"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"chalk": "^5.3.0",
|
|
47
|
+
"commander": "^12.0.0",
|
|
48
|
+
"fs-extra": "^11.2.0",
|
|
49
|
+
"prompts": "^2.4.2"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/fs-extra": "^11.0.4",
|
|
53
|
+
"@types/jest": "^29.5.12",
|
|
54
|
+
"@types/node": "^20.11.20",
|
|
55
|
+
"@types/prompts": "^2.4.9",
|
|
56
|
+
"jest": "^29.7.0",
|
|
57
|
+
"ts-jest": "^29.1.2",
|
|
58
|
+
"typescript": "^5.3.3"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18.0.0"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
}
|
|
66
|
+
}
|