scaffy-tool 0.2.0 → 1.0.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.
Files changed (45) hide show
  1. package/README.md +20 -25
  2. package/cli.js +14 -35
  3. package/core/detector.js +5 -31
  4. package/core/executor.js +5 -27
  5. package/core/interviewer.js +4 -11
  6. package/core/plugin-validator.js +1 -1
  7. package/core/registry.js +37 -25
  8. package/core/utils.js +2 -19
  9. package/package.json +11 -11
  10. package/registry/go/gin/plugin.json +23 -0
  11. package/registry/go/gin/v1/questions.js +19 -0
  12. package/registry/go/gin/v1/scaffold.js +196 -0
  13. package/registry/index.json +2 -2
  14. package/registry/javascript/expressjs/plugin.json +44 -0
  15. package/registry/javascript/expressjs/v4/questions.js +43 -0
  16. package/registry/javascript/expressjs/v4/scaffold.js +236 -0
  17. package/registry/javascript/nestjs/plugin.json +2 -2
  18. package/registry/javascript/nestjs/v10/questions.js +4 -2
  19. package/registry/javascript/nestjs/v10/scaffold.js +1 -1
  20. package/registry/javascript/nestjs/v11/questions.js +61 -0
  21. package/registry/javascript/nestjs/v11/scaffold.js +94 -0
  22. package/registry/javascript/nextjs/plugin.json +44 -0
  23. package/registry/javascript/nextjs/v14/questions.js +44 -0
  24. package/registry/javascript/nextjs/v14/scaffold.js +57 -0
  25. package/registry/javascript/vuejs/v3/questions.js +4 -2
  26. package/registry/javascript/vuejs/v3/scaffold.js +3 -10
  27. package/registry/php/laravel/plugin.json +2 -2
  28. package/registry/php/laravel/v11/questions.js +1 -1
  29. package/registry/php/laravel/v11/scaffold.js +1 -1
  30. package/registry/php/laravel/v12/questions.js +45 -0
  31. package/registry/php/laravel/v12/scaffold.js +46 -0
  32. package/registry/php/laravel/v13/questions.js +28 -0
  33. package/registry/php/laravel/v13/scaffold.js +46 -0
  34. package/registry/php/laravel/v13/test/.gitkeep +0 -0
  35. package/registry/php/laravel/v13/test/questions.test.js +48 -0
  36. package/registry/php/laravel/v13/test/scaffold.test.js +105 -0
  37. package/registry/php/symfony/plugin.json +35 -0
  38. package/registry/php/symfony/v7/questions.js +32 -0
  39. package/registry/php/symfony/v7/scaffold.js +86 -0
  40. package/registry/python/django/plugin.json +35 -0
  41. package/registry/python/django/v5/questions.js +24 -0
  42. package/registry/python/django/v5/scaffold.js +107 -0
  43. package/registry/python/fastapi/plugin.json +35 -0
  44. package/registry/python/fastapi/v1/questions.js +25 -0
  45. package/registry/python/fastapi/v1/scaffold.js +180 -0
@@ -2,8 +2,8 @@
2
2
  "name": "NestJS",
3
3
  "alias": ["nestjs", "nest"],
4
4
  "language": "javascript",
5
- "latest": "v10",
6
- "versions": ["v10"],
5
+ "latest": "v11",
6
+ "versions": ["v11", "v10"],
7
7
  "description": "A progressive Node.js framework for building efficient and scalable server-side applications",
8
8
  "requires": [
9
9
  {
@@ -1,7 +1,9 @@
1
- const { detectAvailableChoices } = require('../../../../core/detector');
1
+ import { detectAvailableChoices } from '../../../../core/detector.js';
2
+ import { createRequire } from 'module';
3
+ const require = createRequire(import.meta.url);
2
4
  const pluginMeta = require('../plugin.json');
3
5
 
4
- module.exports = async () => {
6
+ export default async () => {
5
7
  const availableManagers = await detectAvailableChoices(
6
8
  pluginMeta.packageManagerQuestion.choices
7
9
  );
@@ -1,4 +1,4 @@
1
- module.exports = async (answers, utils) => {
1
+ export default async (answers, utils) => {
2
2
  const { projectName, packageManager, database, auth, docker } = answers;
3
3
 
4
4
  // ─── Step 1 — Install NestJS CLI + Create Project ──
@@ -0,0 +1,61 @@
1
+ import { detectAvailableChoices } from '../../../../core/detector.js';
2
+ import { createRequire } from 'module';
3
+ const require = createRequire(import.meta.url);
4
+ const pluginMeta = require('../plugin.json');
5
+
6
+ export default async () => {
7
+ const availableManagers = await detectAvailableChoices(
8
+ pluginMeta.packageManagerQuestion.choices
9
+ );
10
+
11
+ return [
12
+ {
13
+ type: 'input',
14
+ name: 'projectName',
15
+ message: 'Project name:',
16
+ default: 'my-nest-app',
17
+ validate: input => {
18
+ if (!input.trim()) return 'Project name is required';
19
+ if (!/^[a-z0-9-_]+$/.test(input)) {
20
+ return 'Only lowercase letters, numbers, hyphens and underscores';
21
+ }
22
+ return true;
23
+ },
24
+ },
25
+ {
26
+ type: 'list',
27
+ name: 'packageManager',
28
+ message: pluginMeta.packageManagerQuestion.message,
29
+ choices: availableManagers.map(m => ({
30
+ name: m.name,
31
+ value: m.value,
32
+ })),
33
+ default: availableManagers[0].value,
34
+ },
35
+ {
36
+ type: 'list',
37
+ name: 'database',
38
+ message: 'Database:',
39
+ choices: [
40
+ { name: 'None', value: 'none' },
41
+ { name: 'PostgreSQL (TypeORM)', value: 'postgres' },
42
+ { name: 'MySQL (TypeORM)', value: 'mysql' },
43
+ { name: 'MongoDB (Mongoose)', value: 'mongodb' },
44
+ { name: 'SQLite (TypeORM)', value: 'sqlite' },
45
+ ],
46
+ default: 'none',
47
+ },
48
+ {
49
+ type: 'confirm',
50
+ name: 'auth',
51
+ message: 'Add JWT authentication (Passport)?',
52
+ default: false,
53
+ },
54
+ {
55
+ type: 'confirm',
56
+ name: 'docker',
57
+ message: 'Add Dockerfile?',
58
+ default: false,
59
+ },
60
+ ];
61
+ };
@@ -0,0 +1,94 @@
1
+ export default async (answers, utils) => {
2
+ const { projectName, packageManager, database, auth, docker } = answers;
3
+
4
+ // ─── Step 1 — Install NestJS CLI + Create Project ──
5
+ utils.title('Creating NestJS v11 Project');
6
+ utils.step(1, `Scaffolding ${projectName}...`);
7
+
8
+ await utils.run(
9
+ `npx @nestjs/cli@11 new ${projectName} --package-manager ${packageManager} --skip-git`
10
+ );
11
+
12
+ // ─── Step 2 — Database ────────────────────────────
13
+ if (database !== 'none') {
14
+ utils.step(2, `Installing ${database} database packages...`);
15
+
16
+ if (database === 'mongodb') {
17
+ await utils.runInProject(
18
+ projectName,
19
+ `${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} @nestjs/mongoose mongoose`
20
+ );
21
+ } else {
22
+ // TypeORM based (postgres, mysql, sqlite)
23
+ const dbDriver = {
24
+ postgres: 'pg',
25
+ mysql: 'mysql2',
26
+ sqlite: 'sqlite3',
27
+ };
28
+
29
+ await utils.runInProject(
30
+ projectName,
31
+ `${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} @nestjs/typeorm typeorm ${dbDriver[database]}`
32
+ );
33
+ }
34
+ }
35
+
36
+ // ─── Step 3 — Auth ────────────────────────────────
37
+ if (auth) {
38
+ utils.step(3, 'Installing Passport JWT authentication...');
39
+ await utils.runInProject(
40
+ projectName,
41
+ `${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} @nestjs/passport passport passport-jwt @nestjs/jwt`
42
+ );
43
+ await utils.runInProject(
44
+ projectName,
45
+ `${packageManager} ${packageManager === 'yarn' ? 'add' : 'install'} -D @types/passport-jwt`
46
+ );
47
+ }
48
+
49
+ // ─── Step 4 — Docker ──────────────────────────────
50
+ if (docker) {
51
+ utils.step(4, 'Adding Dockerfile...');
52
+
53
+ await utils.createFile(
54
+ `${projectName}/Dockerfile`,
55
+ `FROM node:20-alpine AS builder
56
+ WORKDIR /app
57
+ COPY package*.json ./
58
+ RUN npm ci
59
+ COPY . .
60
+ RUN npm run build
61
+
62
+ FROM node:20-alpine AS production
63
+ WORKDIR /app
64
+ COPY package*.json ./
65
+ RUN npm ci --only=production
66
+ COPY --from=builder /app/dist ./dist
67
+ EXPOSE 3000
68
+ CMD ["node", "dist/main"]
69
+ `
70
+ );
71
+
72
+ await utils.createFile(
73
+ `${projectName}/.dockerignore`,
74
+ `node_modules
75
+ dist
76
+ .git
77
+ .env
78
+ `
79
+ );
80
+ }
81
+
82
+ // ─── Done ─────────────────────────────────────────
83
+ utils.success(`✅ NestJS v11 project ready!`);
84
+ utils.log(` cd ${projectName}`);
85
+
86
+ if (docker) {
87
+ utils.log(` docker build -t ${projectName} .`);
88
+ utils.log(` docker run -p 3000:3000 ${projectName}`);
89
+ } else {
90
+ utils.log(` ${packageManager} run start:dev`);
91
+ }
92
+
93
+ utils.log('');
94
+ };
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "NextJS",
3
+ "alias": ["next", "nextjs"],
4
+ "language": "javascript",
5
+ "latest": "v14",
6
+ "versions": ["v14"],
7
+ "description": "The React framework for production — SSR, SSG, and App Router",
8
+ "requires": [
9
+ {
10
+ "tool": "node",
11
+ "checkCommand": "node --version",
12
+ "parseVersion": "v([0-9]+\\.[0-9]+\\.[0-9]+)",
13
+ "minVersion": "18.0.0",
14
+ "installGuide": {
15
+ "mac": "brew install node",
16
+ "linux": "sudo apt install nodejs",
17
+ "windows": "https://nodejs.org",
18
+ "docs": "https://nodejs.org"
19
+ }
20
+ },
21
+ {
22
+ "tool": "npm",
23
+ "checkCommand": "npm --version",
24
+ "parseVersion": "([0-9]+\\.[0-9]+\\.[0-9]+)",
25
+ "minVersion": "8.0.0",
26
+ "installGuide": {
27
+ "mac": "brew install node",
28
+ "linux": "sudo apt install npm",
29
+ "windows": "https://nodejs.org",
30
+ "docs": "https://docs.npmjs.com"
31
+ }
32
+ }
33
+ ],
34
+ "packageManagerQuestion": {
35
+ "tool": "packageManager",
36
+ "message": "Package manager:",
37
+ "choices": [
38
+ { "name": "npm", "value": "npm", "checkCommand": "npm --version" },
39
+ { "name": "yarn", "value": "yarn", "checkCommand": "yarn --version" },
40
+ { "name": "pnpm", "value": "pnpm", "checkCommand": "pnpm --version" }
41
+ ]
42
+ },
43
+ "maintainer": "community"
44
+ }
@@ -0,0 +1,44 @@
1
+ import { detectAvailableChoices } from '../../../../core/detector.js';
2
+ import { createRequire } from 'module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const pluginMeta = require('../plugin.json');
6
+
7
+ export default async () => {
8
+ const availableManagers = await detectAvailableChoices(
9
+ pluginMeta.packageManagerQuestion.choices
10
+ );
11
+
12
+ return [
13
+ {
14
+ type: 'list',
15
+ name: 'packageManager',
16
+ message: pluginMeta.packageManagerQuestion.message,
17
+ choices: availableManagers,
18
+ },
19
+ {
20
+ type: 'confirm',
21
+ name: 'typescript',
22
+ message: 'Use TypeScript?',
23
+ default: true,
24
+ },
25
+ {
26
+ type: 'confirm',
27
+ name: 'tailwind',
28
+ message: 'Use Tailwind CSS?',
29
+ default: true,
30
+ },
31
+ {
32
+ type: 'confirm',
33
+ name: 'appRouter',
34
+ message: 'Use App Router? (recommended)',
35
+ default: true,
36
+ },
37
+ {
38
+ type: 'confirm',
39
+ name: 'srcDir',
40
+ message: 'Use src/ directory?',
41
+ default: false,
42
+ },
43
+ ];
44
+ };
@@ -0,0 +1,57 @@
1
+ export default async (answers, utils) => {
2
+ const {
3
+ projectName,
4
+ packageManager,
5
+ typescript,
6
+ tailwind,
7
+ appRouter,
8
+ srcDir,
9
+ } = answers;
10
+
11
+ utils.title('Creating NextJS v14 Project');
12
+
13
+ // ─── Step 1: Build flags ───────────────────────────────
14
+ utils.step(1, 'Configuring create-next-app flags');
15
+
16
+ const flags = [
17
+ typescript ? '--typescript' : '--no-typescript',
18
+ tailwind ? '--tailwind' : '--no-tailwind',
19
+ appRouter ? '--app' : '--no-app',
20
+ srcDir ? '--src-dir' : '--no-src-dir',
21
+ `--use-${packageManager}`,
22
+ '--no-eslint',
23
+ '--skip-install',
24
+ ].join(' ');
25
+
26
+ // ─── Step 2: Run create-next-app ──────────────────────
27
+ utils.step(2, 'Running create-next-app@14');
28
+ await utils.run(`npx create-next-app@14 ${projectName} ${flags}`);
29
+
30
+ // ─── Step 3: Install dependencies ─────────────────────
31
+ utils.step(3, 'Installing dependencies');
32
+ const installCmd = packageManager === 'yarn' ? 'install' : 'install';
33
+ await utils.runInProject(projectName, `${packageManager} ${installCmd}`);
34
+
35
+ // ─── Step 4: .env.local ───────────────────────────────
36
+ utils.step(4, 'Generating environment files');
37
+
38
+ const envLocal = `# Environment Variables
39
+ # Add your environment variables here
40
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
41
+ `;
42
+ await utils.createFile(`${projectName}/.env.local`, envLocal);
43
+ await utils.createFile(`${projectName}/.env.example`, envLocal);
44
+
45
+ // ─── Step 5: .gitignore addition ──────────────────────
46
+ await utils.appendToFile(
47
+ `${projectName}/.gitignore`,
48
+ '\n# Environment\n.env.local\n.env*.local\n'
49
+ );
50
+
51
+ utils.success(`NextJS v14 project "${projectName}" created successfully!`);
52
+ utils.log(` cd ${projectName}`);
53
+ utils.log(
54
+ ` ${packageManager} ${packageManager === 'yarn' ? 'dev' : 'run dev'}`
55
+ );
56
+ utils.log(` App running at http://localhost:3000`);
57
+ };
@@ -1,7 +1,9 @@
1
- const { detectAvailableChoices } = require('../../../../core/detector');
1
+ import { detectAvailableChoices } from '../../../../core/detector.js';
2
+ import { createRequire } from 'module';
3
+ const require = createRequire(import.meta.url);
2
4
  const pluginMeta = require('../plugin.json');
3
5
 
4
- module.exports = async () => {
6
+ export default async () => {
5
7
  const availableManagers = await detectAvailableChoices(
6
8
  pluginMeta.packageManagerQuestion.choices
7
9
  );
@@ -1,13 +1,6 @@
1
- module.exports = async (answers, utils) => {
2
- const {
3
- projectName,
4
- packageManager,
5
- router,
6
- pinia,
7
- vitest,
8
- typescript,
9
- eslint,
10
- } = answers;
1
+ export default async (answers, utils) => {
2
+ const { projectName, packageManager, router, pinia, typescript, eslint } =
3
+ answers;
11
4
 
12
5
  // ─── Step 1 — Build create-vue flags ──────────────
13
6
  utils.title('Creating VueJS v3 Project');
@@ -2,8 +2,8 @@
2
2
  "name": "Laravel",
3
3
  "alias": ["laravel"],
4
4
  "language": "php",
5
- "latest": "v11",
6
- "versions": ["v11", "v10"],
5
+ "latest": "v13",
6
+ "versions": ["v13", "v12", "v11", "v10"],
7
7
  "description": "The PHP Framework for Web Artisans",
8
8
  "requires": [
9
9
  {
@@ -1,4 +1,4 @@
1
- module.exports = [
1
+ export default [
2
2
  {
3
3
  type: 'input',
4
4
  name: 'projectName',
@@ -1,4 +1,4 @@
1
- module.exports = async (answers, utils) => {
1
+ export default async (answers, utils) => {
2
2
  const { projectName, starterKit, database, docker } = answers;
3
3
 
4
4
  // ─── Step 1 — Create Laravel Project ──────────────
@@ -0,0 +1,45 @@
1
+ export default [
2
+ {
3
+ type: 'input',
4
+ name: 'projectName',
5
+ message: 'Project name?',
6
+ default: 'my-laravel-app',
7
+ validate: value => {
8
+ if (!value || !value.trim()) {
9
+ return 'Project name is required';
10
+ }
11
+ if (!/^[a-z0-9_-]+$/.test(value)) {
12
+ return 'Project name can only contain lowercase letters, numbers, underscores, and hyphens';
13
+ }
14
+ return true;
15
+ },
16
+ },
17
+ {
18
+ type: 'list',
19
+ name: 'starterKit',
20
+ message: 'Starter kit?',
21
+ choices: [
22
+ { name: 'None', value: 'none' },
23
+ { name: 'Breeze', value: 'breeze' },
24
+ { name: 'Jetstream', value: 'jetstream' },
25
+ ],
26
+ default: 'none',
27
+ },
28
+ {
29
+ type: 'list',
30
+ name: 'database',
31
+ message: 'Database?',
32
+ choices: [
33
+ { name: 'SQLite', value: 'sqlite' },
34
+ { name: 'MySQL', value: 'mysql' },
35
+ { name: 'PostgreSQL', value: 'pgsql' },
36
+ ],
37
+ default: 'sqlite',
38
+ },
39
+ {
40
+ type: 'confirm',
41
+ name: 'docker',
42
+ message: 'Include Docker config (Laravel Sail)?',
43
+ default: false,
44
+ },
45
+ ];
@@ -0,0 +1,46 @@
1
+ export default async (answers, utils) => {
2
+ const { projectName, starterKit, database, docker } = answers;
3
+
4
+ utils.title('Creating Laravel v12 Project');
5
+
6
+ utils.step(1, 'Running Laravel installer');
7
+ await utils.run(
8
+ `composer create-project laravel/laravel:^12.0 ${projectName}`
9
+ );
10
+
11
+ if (starterKit === 'breeze') {
12
+ utils.step(2, 'Installing Laravel Breeze');
13
+ await utils.runInProject(
14
+ projectName,
15
+ 'composer require laravel/breeze --dev'
16
+ );
17
+ await utils.runInProject(projectName, 'php artisan breeze:install');
18
+ }
19
+
20
+ if (starterKit === 'jetstream') {
21
+ utils.step(2, 'Installing Laravel Jetstream');
22
+ await utils.runInProject(projectName, 'composer require laravel/jetstream');
23
+ await utils.runInProject(
24
+ projectName,
25
+ 'php artisan jetstream:install livewire'
26
+ );
27
+ }
28
+
29
+ utils.step(3, 'Configuring database');
30
+ await utils.setEnv(projectName, {
31
+ DB_CONNECTION: database,
32
+ DB_DATABASE:
33
+ database === 'sqlite' ? 'database/database.sqlite' : projectName,
34
+ });
35
+
36
+ if (docker) {
37
+ utils.step(4, 'Installing Laravel Sail');
38
+ await utils.runInProject(
39
+ projectName,
40
+ 'composer require laravel/sail --dev'
41
+ );
42
+ await utils.runInProject(projectName, 'php artisan sail:install');
43
+ }
44
+
45
+ utils.success(`Laravel v12 project "${projectName}" created successfully!`);
46
+ };
@@ -0,0 +1,28 @@
1
+ export default [
2
+ {
3
+ type: 'list',
4
+ name: 'starterKit',
5
+ message: 'Starter kit?',
6
+ choices: [
7
+ { name: 'None', value: 'none' },
8
+ { name: 'Breeze', value: 'breeze' },
9
+ { name: 'Jetstream', value: 'jetstream' },
10
+ ],
11
+ },
12
+ {
13
+ type: 'list',
14
+ name: 'database',
15
+ message: 'Database?',
16
+ choices: [
17
+ { name: 'SQLite', value: 'sqlite' },
18
+ { name: 'MySQL', value: 'mysql' },
19
+ { name: 'PostgreSQL', value: 'pgsql' },
20
+ ],
21
+ },
22
+ {
23
+ type: 'confirm',
24
+ name: 'docker',
25
+ message: 'Include Docker config (Laravel Sail)?',
26
+ default: false,
27
+ },
28
+ ];
@@ -0,0 +1,46 @@
1
+ export default async (answers, utils) => {
2
+ const { projectName, starterKit, database, docker } = answers;
3
+
4
+ utils.title('Creating Laravel v13 Project');
5
+
6
+ utils.step(1, 'Running Laravel installer');
7
+ await utils.run(
8
+ `composer create-project laravel/laravel:^13.0 ${projectName}`
9
+ );
10
+
11
+ if (starterKit === 'breeze') {
12
+ utils.step(2, 'Installing Laravel Breeze');
13
+ await utils.runInProject(
14
+ projectName,
15
+ 'composer require laravel/breeze --dev'
16
+ );
17
+ await utils.runInProject(projectName, 'php artisan breeze:install');
18
+ }
19
+
20
+ if (starterKit === 'jetstream') {
21
+ utils.step(2, 'Installing Laravel Jetstream');
22
+ await utils.runInProject(projectName, 'composer require laravel/jetstream');
23
+ await utils.runInProject(
24
+ projectName,
25
+ 'php artisan jetstream:install livewire'
26
+ );
27
+ }
28
+
29
+ utils.step(3, 'Configuring database');
30
+ await utils.setEnv(projectName, {
31
+ DB_CONNECTION: database,
32
+ DB_DATABASE:
33
+ database === 'sqlite' ? 'database/database.sqlite' : projectName,
34
+ });
35
+
36
+ if (docker) {
37
+ utils.step(4, 'Installing Laravel Sail');
38
+ await utils.runInProject(
39
+ projectName,
40
+ 'composer require laravel/sail --dev'
41
+ );
42
+ await utils.runInProject(projectName, 'php artisan sail:install');
43
+ }
44
+
45
+ utils.success(`Laravel v13 project "${projectName}" created successfully!`);
46
+ };
File without changes
@@ -0,0 +1,48 @@
1
+ import questions from '../questions.js';
2
+
3
+ describe('Laravel v13 questions plugin', () => {
4
+ test('returns expected question set', () => {
5
+ expect(Array.isArray(questions)).toBe(true);
6
+
7
+ const projectNameQuestion = questions.find(q => q.name === 'projectName');
8
+ expect(projectNameQuestion).toBeDefined();
9
+ expect(projectNameQuestion.default).toBe('my-laravel-app');
10
+
11
+ const starterKitQuestion = questions.find(q => q.name === 'starterKit');
12
+ expect(starterKitQuestion).toBeDefined();
13
+ expect(Array.isArray(starterKitQuestion.choices)).toBe(true);
14
+ expect(starterKitQuestion.choices.length).toBe(3);
15
+ expect(starterKitQuestion.default).toBe('none');
16
+
17
+ const databaseQuestion = questions.find(q => q.name === 'database');
18
+ expect(databaseQuestion).toBeDefined();
19
+ expect(Array.isArray(databaseQuestion.choices)).toBe(true);
20
+ expect(databaseQuestion.default).toBe('sqlite');
21
+
22
+ const dockerQuestion = questions.find(q => q.name === 'docker');
23
+ expect(dockerQuestion).toBeDefined();
24
+ expect(dockerQuestion.type).toBe('confirm');
25
+ expect(dockerQuestion.default).toBe(false);
26
+ });
27
+
28
+ test('project name validator rejects empty input', () => {
29
+ const projectNameQuestion = questions.find(q => q.name === 'projectName');
30
+ expect(projectNameQuestion.validate(' ')).toBe(
31
+ 'Project name is required'
32
+ );
33
+ });
34
+
35
+ test('project name validator rejects invalid characters', () => {
36
+ const projectNameQuestion = questions.find(q => q.name === 'projectName');
37
+ const result = projectNameQuestion.validate('MyProject');
38
+ expect(typeof result).toBe('string');
39
+ expect(result).toContain('lowercase');
40
+ });
41
+
42
+ test('project name validator accepts valid names', () => {
43
+ const projectNameQuestion = questions.find(q => q.name === 'projectName');
44
+ expect(projectNameQuestion.validate('my-laravel-app')).toBe(true);
45
+ expect(projectNameQuestion.validate('my_laravel_app')).toBe(true);
46
+ expect(projectNameQuestion.validate('mylaravelapp123')).toBe(true);
47
+ });
48
+ });