forgestack-os-cli 0.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/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +78 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/generators/api.d.ts +3 -0
- package/dist/generators/api.d.ts.map +1 -0
- package/dist/generators/api.js +346 -0
- package/dist/generators/api.js.map +1 -0
- package/dist/generators/auth.d.ts +2 -0
- package/dist/generators/auth.d.ts.map +1 -0
- package/dist/generators/auth.js +371 -0
- package/dist/generators/auth.js.map +1 -0
- package/dist/generators/backend.d.ts +2 -0
- package/dist/generators/backend.d.ts.map +1 -0
- package/dist/generators/backend.js +875 -0
- package/dist/generators/backend.js.map +1 -0
- package/dist/generators/common.d.ts +2 -0
- package/dist/generators/common.d.ts.map +1 -0
- package/dist/generators/common.js +354 -0
- package/dist/generators/common.js.map +1 -0
- package/dist/generators/database.d.ts +2 -0
- package/dist/generators/database.d.ts.map +1 -0
- package/dist/generators/database.js +157 -0
- package/dist/generators/database.js.map +1 -0
- package/dist/generators/docker.d.ts +2 -0
- package/dist/generators/docker.d.ts.map +1 -0
- package/dist/generators/docker.js +181 -0
- package/dist/generators/docker.js.map +1 -0
- package/dist/generators/frontend-helpers.d.ts +3 -0
- package/dist/generators/frontend-helpers.d.ts.map +1 -0
- package/dist/generators/frontend-helpers.js +23 -0
- package/dist/generators/frontend-helpers.js.map +1 -0
- package/dist/generators/frontend.d.ts +2 -0
- package/dist/generators/frontend.d.ts.map +1 -0
- package/dist/generators/frontend.js +735 -0
- package/dist/generators/frontend.js.map +1 -0
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +59 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/nextjs-helpers.d.ts +6 -0
- package/dist/generators/nextjs-helpers.d.ts.map +1 -0
- package/dist/generators/nextjs-helpers.js +216 -0
- package/dist/generators/nextjs-helpers.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompts.d.ts +2 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +107 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/validators.d.ts +2 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +48 -0
- package/dist/utils/validators.js.map +1 -0
- package/package.json +49 -0
- package/src/commands/create.ts +82 -0
- package/src/generators/api.ts +353 -0
- package/src/generators/auth.ts +406 -0
- package/src/generators/backend.ts +927 -0
- package/src/generators/common.ts +377 -0
- package/src/generators/database.ts +165 -0
- package/src/generators/docker.ts +185 -0
- package/src/generators/frontend.ts +783 -0
- package/src/generators/index.ts +64 -0
- package/src/index.ts +27 -0
- package/src/types.ts +16 -0
- package/src/utils/logger.ts +31 -0
- package/src/utils/prompts.ts +105 -0
- package/src/utils/validators.ts +50 -0
- package/tests/validators.test.ts +69 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { StackConfig } from '../types';
|
|
4
|
+
import { logger } from '../utils/logger';
|
|
5
|
+
import { generateFrontend } from './frontend';
|
|
6
|
+
import { generateBackend } from './backend';
|
|
7
|
+
import { generateDatabase } from './database';
|
|
8
|
+
import { generateAuth } from './auth';
|
|
9
|
+
import { generateDocker } from './docker';
|
|
10
|
+
import { generateCommon } from './common';
|
|
11
|
+
|
|
12
|
+
export async function generateProject(config: StackConfig, targetDir: string) {
|
|
13
|
+
const spinner = logger.spinner('Creating project structure...');
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Create project directory
|
|
17
|
+
await fs.ensureDir(targetDir);
|
|
18
|
+
|
|
19
|
+
// Create subdirectories
|
|
20
|
+
const frontendDir = path.join(targetDir, 'frontend');
|
|
21
|
+
const backendDir = path.join(targetDir, 'backend');
|
|
22
|
+
|
|
23
|
+
await fs.ensureDir(frontendDir);
|
|
24
|
+
await fs.ensureDir(backendDir);
|
|
25
|
+
|
|
26
|
+
spinner.succeed('Project structure created');
|
|
27
|
+
|
|
28
|
+
// Generate common files (root package.json, README, .gitignore, etc.)
|
|
29
|
+
const commonSpinner = logger.spinner('Generating common files...');
|
|
30
|
+
await generateCommon(config, targetDir);
|
|
31
|
+
commonSpinner.succeed('Common files generated');
|
|
32
|
+
|
|
33
|
+
// Generate frontend
|
|
34
|
+
const frontendSpinner = logger.spinner(`Generating ${config.frontend} frontend...`);
|
|
35
|
+
await generateFrontend(config, frontendDir);
|
|
36
|
+
frontendSpinner.succeed('Frontend generated');
|
|
37
|
+
|
|
38
|
+
// Generate backend
|
|
39
|
+
const backendSpinner = logger.spinner(`Generating ${config.backend} backend...`);
|
|
40
|
+
await generateBackend(config, backendDir);
|
|
41
|
+
backendSpinner.succeed('Backend generated');
|
|
42
|
+
|
|
43
|
+
// Generate database setup
|
|
44
|
+
const dbSpinner = logger.spinner(`Setting up ${config.database} database...`);
|
|
45
|
+
await generateDatabase(config, backendDir);
|
|
46
|
+
dbSpinner.succeed('Database setup complete');
|
|
47
|
+
|
|
48
|
+
// Generate auth integration
|
|
49
|
+
const authSpinner = logger.spinner(`Integrating ${config.auth} authentication...`);
|
|
50
|
+
await generateAuth(config, frontendDir, backendDir);
|
|
51
|
+
authSpinner.succeed('Authentication integrated');
|
|
52
|
+
|
|
53
|
+
// Generate Docker configuration if requested
|
|
54
|
+
if (config.docker) {
|
|
55
|
+
const dockerSpinner = logger.spinner('Generating Docker configuration...');
|
|
56
|
+
await generateDocker(config, targetDir);
|
|
57
|
+
dockerSpinner.succeed('Docker configuration generated');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
spinner.fail('Failed to generate project');
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
|
|
5
|
+
import { createCommand } from './commands/create';
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('forgestack')
|
|
11
|
+
.description('ForgeStack OS - One platform. Any stack. Production-ready.')
|
|
12
|
+
.version('0.1.0');
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.command('create <project-name>')
|
|
16
|
+
.description('Create a new full-stack SaaS application')
|
|
17
|
+
.action(createCommand)
|
|
18
|
+
.option('--frontend <framework>', 'Frontend framework')
|
|
19
|
+
.option('--backend <framework>', 'Backend framework')
|
|
20
|
+
.option('--auth <provider>', 'Auth provider')
|
|
21
|
+
.option('--database <db>', 'Database')
|
|
22
|
+
.option('--api <style>', 'API style')
|
|
23
|
+
.option('--docker', 'Include Docker configuration')
|
|
24
|
+
.option('--no-docker', 'Skip Docker configuration')
|
|
25
|
+
.option('--multi-tenant', 'Enable multi-tenancy');
|
|
26
|
+
|
|
27
|
+
program.parse();
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface StackConfig {
|
|
2
|
+
projectName: string;
|
|
3
|
+
frontend: 'react-vite' | 'nextjs' | 'vue-vite' | 'sveltekit';
|
|
4
|
+
backend: 'express' | 'fastify' | 'nestjs' | 'bun-elysia' | 'go-fiber';
|
|
5
|
+
auth: 'jwt' | 'clerk' | 'supabase' | 'authjs' | 'firebase';
|
|
6
|
+
database: 'postgresql' | 'mongodb' | 'mysql' | 'sqlite' | 'supabase-db';
|
|
7
|
+
apiStyle: 'rest' | 'graphql' | 'trpc';
|
|
8
|
+
docker: boolean;
|
|
9
|
+
multiTenant: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ValidationResult {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
errors: string[];
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora, { Ora } from 'ora';
|
|
3
|
+
|
|
4
|
+
export const logger = {
|
|
5
|
+
info: (message: string) => {
|
|
6
|
+
console.log(chalk.blue('ℹ'), message);
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
success: (message: string) => {
|
|
10
|
+
console.log(chalk.green('✔'), message);
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
error: (message: string) => {
|
|
14
|
+
console.log(chalk.red('✖'), message);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
warning: (message: string) => {
|
|
18
|
+
console.log(chalk.yellow('⚠'), message);
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
title: (message: string) => {
|
|
22
|
+
console.log(chalk.bold.cyan(`\n${message}\n`));
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
spinner: (text: string): Ora => {
|
|
26
|
+
return ora({
|
|
27
|
+
text,
|
|
28
|
+
color: 'cyan',
|
|
29
|
+
}).start();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { StackConfig } from '../types';
|
|
3
|
+
|
|
4
|
+
export async function promptForStack(projectName: string, options: any = {}): Promise<StackConfig> {
|
|
5
|
+
console.log('\n');
|
|
6
|
+
|
|
7
|
+
// If options are provided, merge them with defaults and return
|
|
8
|
+
// This allows non-interactive mode
|
|
9
|
+
if (options.frontend || options.backend || options.auth || options.database) {
|
|
10
|
+
return {
|
|
11
|
+
projectName,
|
|
12
|
+
frontend: options.frontend || 'react-vite',
|
|
13
|
+
backend: options.backend || 'express',
|
|
14
|
+
auth: options.auth || 'jwt',
|
|
15
|
+
database: options.database || 'postgresql',
|
|
16
|
+
apiStyle: options.api // 'api' option maps to 'apiStyle' in config
|
|
17
|
+
? (options.api === 'trpc' ? 'trpc' : options.api === 'graphql' ? 'graphql' : 'rest')
|
|
18
|
+
: 'rest',
|
|
19
|
+
docker: options.docker !== false, // Default to true unless --no-docker
|
|
20
|
+
multiTenant: !!options.multiTenant,
|
|
21
|
+
} as StackConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const answers = await inquirer.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: 'list',
|
|
27
|
+
name: 'frontend',
|
|
28
|
+
message: 'Choose your frontend framework:',
|
|
29
|
+
choices: [
|
|
30
|
+
{ name: 'React + Vite (Recommended)', value: 'react-vite' },
|
|
31
|
+
{ name: 'Next.js (App Router)', value: 'nextjs' },
|
|
32
|
+
{ name: 'Vue + Vite', value: 'vue-vite' },
|
|
33
|
+
{ name: 'SvelteKit', value: 'sveltekit' },
|
|
34
|
+
],
|
|
35
|
+
default: 'react-vite',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'backend',
|
|
40
|
+
message: 'Choose your backend framework:',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'Node.js + Express (Recommended)', value: 'express' },
|
|
43
|
+
{ name: 'Node.js + Fastify', value: 'fastify' },
|
|
44
|
+
{ name: 'NestJS', value: 'nestjs' },
|
|
45
|
+
{ name: 'Bun + Elysia', value: 'bun-elysia' },
|
|
46
|
+
{ name: 'Go + Fiber (Experimental)', value: 'go-fiber' },
|
|
47
|
+
],
|
|
48
|
+
default: 'express',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'list',
|
|
52
|
+
name: 'auth',
|
|
53
|
+
message: 'Choose your authentication provider:',
|
|
54
|
+
choices: [
|
|
55
|
+
{ name: 'Built-in JWT Auth (Free)', value: 'jwt' },
|
|
56
|
+
{ name: 'Clerk', value: 'clerk' },
|
|
57
|
+
{ name: 'Supabase Auth', value: 'supabase' },
|
|
58
|
+
{ name: 'Auth.js (NextAuth)', value: 'authjs' },
|
|
59
|
+
{ name: 'Firebase Auth', value: 'firebase' },
|
|
60
|
+
],
|
|
61
|
+
default: 'jwt',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: 'list',
|
|
65
|
+
name: 'database',
|
|
66
|
+
message: 'Choose your database:',
|
|
67
|
+
choices: [
|
|
68
|
+
{ name: 'PostgreSQL + Prisma (Recommended)', value: 'postgresql' },
|
|
69
|
+
{ name: 'MongoDB + Mongoose', value: 'mongodb' },
|
|
70
|
+
{ name: 'MySQL + Prisma', value: 'mysql' },
|
|
71
|
+
{ name: 'SQLite (Local)', value: 'sqlite' },
|
|
72
|
+
{ name: 'Supabase DB', value: 'supabase-db' },
|
|
73
|
+
],
|
|
74
|
+
default: 'postgresql',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'list',
|
|
78
|
+
name: 'apiStyle',
|
|
79
|
+
message: 'Choose your API style:',
|
|
80
|
+
choices: [
|
|
81
|
+
{ name: 'REST (Recommended)', value: 'rest' },
|
|
82
|
+
{ name: 'GraphQL', value: 'graphql' },
|
|
83
|
+
{ name: 'tRPC (Type-safe)', value: 'trpc' },
|
|
84
|
+
],
|
|
85
|
+
default: 'rest',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: 'confirm',
|
|
89
|
+
name: 'docker',
|
|
90
|
+
message: 'Include Docker configuration?',
|
|
91
|
+
default: true,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'confirm',
|
|
95
|
+
name: 'multiTenant',
|
|
96
|
+
message: 'Enable multi-tenancy support?',
|
|
97
|
+
default: false,
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
projectName,
|
|
103
|
+
...answers,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { StackConfig, ValidationResult } from '../types';
|
|
2
|
+
import validateNpmPackageName from 'validate-npm-package-name';
|
|
3
|
+
|
|
4
|
+
export function validateStackConfig(config: StackConfig): ValidationResult {
|
|
5
|
+
const errors: string[] = [];
|
|
6
|
+
const warnings: string[] = [];
|
|
7
|
+
|
|
8
|
+
// Validate project name
|
|
9
|
+
const nameValidation = validateNpmPackageName(config.projectName);
|
|
10
|
+
if (!nameValidation.validForNewPackages) {
|
|
11
|
+
errors.push(`Invalid project name: ${nameValidation.errors?.join(', ')}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// tRPC requires TypeScript backend
|
|
15
|
+
if (config.apiStyle === 'trpc') {
|
|
16
|
+
if (config.backend === 'go-fiber') {
|
|
17
|
+
errors.push('tRPC is not compatible with Go backend. Please choose a TypeScript backend.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// GraphQL compatibility
|
|
22
|
+
if (config.apiStyle === 'graphql') {
|
|
23
|
+
if (config.backend === 'bun-elysia' || config.backend === 'go-fiber') {
|
|
24
|
+
warnings.push('GraphQL support for this backend is experimental.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Supabase auth requires Supabase DB
|
|
29
|
+
if (config.auth === 'supabase' && config.database !== 'supabase-db') {
|
|
30
|
+
warnings.push('Supabase Auth works best with Supabase DB. Consider using Supabase DB for full integration.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Go backend is experimental
|
|
34
|
+
if (config.backend === 'go-fiber') {
|
|
35
|
+
warnings.push('Go + Fiber backend is experimental and may have limited features.');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Multi-tenancy warnings
|
|
39
|
+
if (config.multiTenant) {
|
|
40
|
+
if (config.database === 'sqlite') {
|
|
41
|
+
warnings.push('SQLite with multi-tenancy is not recommended for production use.');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
valid: errors.length === 0,
|
|
47
|
+
errors,
|
|
48
|
+
warnings,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateStackConfig } from '../src/utils/validators';
|
|
3
|
+
|
|
4
|
+
describe('Stack Validation', () => {
|
|
5
|
+
it('should validate compatible stacks', () => {
|
|
6
|
+
const config = {
|
|
7
|
+
projectName: 'test-app',
|
|
8
|
+
frontend: 'react-vite' as const,
|
|
9
|
+
backend: 'express' as const,
|
|
10
|
+
auth: 'jwt' as const,
|
|
11
|
+
database: 'postgresql' as const,
|
|
12
|
+
apiStyle: 'rest' as const,
|
|
13
|
+
docker: true,
|
|
14
|
+
multiTenant: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const result = validateStackConfig(config);
|
|
18
|
+
expect(result.valid).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should reject Next.js with Express (incompatible)', () => {
|
|
22
|
+
const config = {
|
|
23
|
+
projectName: 'test-app',
|
|
24
|
+
frontend: 'nextjs' as const,
|
|
25
|
+
backend: 'express' as const,
|
|
26
|
+
auth: 'jwt' as const,
|
|
27
|
+
database: 'postgresql' as const,
|
|
28
|
+
apiStyle: 'rest' as const,
|
|
29
|
+
docker: true,
|
|
30
|
+
multiTenant: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result = validateStackConfig(config);
|
|
34
|
+
expect(result.valid).toBe(false);
|
|
35
|
+
expect(result.errors).toContain('Next.js works best with NestJS backend');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should validate GraphQL API style', () => {
|
|
39
|
+
const config = {
|
|
40
|
+
projectName: 'test-app',
|
|
41
|
+
frontend: 'react-vite' as const,
|
|
42
|
+
backend: 'express' as const,
|
|
43
|
+
auth: 'jwt' as const,
|
|
44
|
+
database: 'postgresql' as const,
|
|
45
|
+
apiStyle: 'graphql' as const,
|
|
46
|
+
docker: true,
|
|
47
|
+
multiTenant: false,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = validateStackConfig(config);
|
|
51
|
+
expect(result.valid).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should validate tRPC API style', () => {
|
|
55
|
+
const config = {
|
|
56
|
+
projectName: 'test-app',
|
|
57
|
+
frontend: 'nextjs' as const,
|
|
58
|
+
backend: 'nestjs' as const,
|
|
59
|
+
auth: 'clerk' as const,
|
|
60
|
+
database: 'postgresql' as const,
|
|
61
|
+
apiStyle: 'trpc' as const,
|
|
62
|
+
docker: true,
|
|
63
|
+
multiTenant: true,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = validateStackConfig(config);
|
|
67
|
+
expect(result.valid).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
package/tsc_output.txt
ADDED
|
Binary file
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"types": [
|
|
7
|
+
"node",
|
|
8
|
+
"vitest/globals"
|
|
9
|
+
],
|
|
10
|
+
"noUnusedLocals": false,
|
|
11
|
+
"noUnusedParameters": false
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"src/**/*"
|
|
15
|
+
],
|
|
16
|
+
"exclude": [
|
|
17
|
+
"node_modules",
|
|
18
|
+
"dist",
|
|
19
|
+
"**/*.test.ts"
|
|
20
|
+
]
|
|
21
|
+
}
|