stackkit-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/modules/auth/nextauth/files/app-router/api/auth/[...nextauth]/route.ts +6 -0
  2. package/modules/auth/nextauth/files/lib/auth.ts +82 -0
  3. package/modules/auth/nextauth/files/pages-router/api/auth/[...nextauth].ts +4 -0
  4. package/modules/auth/nextauth/module.json +50 -0
  5. package/package.json +7 -1
  6. package/templates/next-prisma-postgres-shadcn/.env.example +5 -0
  7. package/templates/next-prisma-postgres-shadcn/.eslintrc.json +7 -0
  8. package/templates/next-prisma-postgres-shadcn/.prettierrc +8 -0
  9. package/templates/next-prisma-postgres-shadcn/README.md +79 -0
  10. package/templates/next-prisma-postgres-shadcn/app/api/health/route.ts +25 -0
  11. package/templates/next-prisma-postgres-shadcn/app/globals.css +1 -0
  12. package/templates/next-prisma-postgres-shadcn/app/layout.tsx +22 -0
  13. package/templates/next-prisma-postgres-shadcn/app/page.tsx +29 -0
  14. package/templates/next-prisma-postgres-shadcn/lib/db.ts +14 -0
  15. package/templates/next-prisma-postgres-shadcn/lib/env.ts +15 -0
  16. package/templates/next-prisma-postgres-shadcn/next.config.ts +7 -0
  17. package/templates/next-prisma-postgres-shadcn/package.json +32 -0
  18. package/templates/next-prisma-postgres-shadcn/prisma/schema.prisma +20 -0
  19. package/templates/next-prisma-postgres-shadcn/public/.gitkeep +1 -0
  20. package/templates/next-prisma-postgres-shadcn/template.json +18 -0
  21. package/templates/next-prisma-postgres-shadcn/tsconfig.json +32 -0
  22. package/src/commands/add.ts +0 -261
  23. package/src/commands/init.ts +0 -182
  24. package/src/commands/list.ts +0 -124
  25. package/src/index.ts +0 -53
  26. package/src/types/index.ts +0 -71
  27. package/src/utils/code-inject.ts +0 -85
  28. package/src/utils/detect.ts +0 -89
  29. package/src/utils/env-editor.ts +0 -127
  30. package/src/utils/files.ts +0 -59
  31. package/src/utils/json-editor.ts +0 -64
  32. package/src/utils/logger.ts +0 -62
  33. package/src/utils/package-manager.ts +0 -85
  34. package/tsconfig.json +0 -9
@@ -1,85 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
-
4
- const CODE_MARKER_START = (id: string) => `// StackKit:${id}:start`;
5
- const CODE_MARKER_END = (id: string) => `// StackKit:${id}:end`;
6
-
7
- export interface CodeInjection {
8
- id: string;
9
- code: string;
10
- description: string;
11
- }
12
-
13
- export async function injectCode(
14
- filePath: string,
15
- injection: CodeInjection,
16
- position: 'append' | 'prepend' | { after: string } | { before: string },
17
- options: { force?: boolean } = {}
18
- ): Promise<void> {
19
- if (!await fs.pathExists(filePath)) {
20
- throw new Error(`File not found: ${filePath}`);
21
- }
22
-
23
- let content = await fs.readFile(filePath, 'utf-8');
24
-
25
- // Check if already injected
26
- const startMarker = CODE_MARKER_START(injection.id);
27
- if (content.includes(startMarker) && !options.force) {
28
- return; // Already injected, skip
29
- }
30
-
31
- // Remove old injection if force is true
32
- if (options.force) {
33
- content = removeInjection(content, injection.id);
34
- }
35
-
36
- // Prepare the code block with markers
37
- const markedCode = `\n${startMarker}\n${injection.code}\n${CODE_MARKER_END(injection.id)}\n`;
38
-
39
- // Inject based on position
40
- if (position === 'append') {
41
- content += markedCode;
42
- } else if (position === 'prepend') {
43
- content = markedCode + content;
44
- } else if ('after' in position) {
45
- const index = content.indexOf(position.after);
46
- if (index === -1) {
47
- throw new Error(`Could not find marker: ${position.after}`);
48
- }
49
- const insertPos = index + position.after.length;
50
- content = content.slice(0, insertPos) + markedCode + content.slice(insertPos);
51
- } else if ('before' in position) {
52
- const index = content.indexOf(position.before);
53
- if (index === -1) {
54
- throw new Error(`Could not find marker: ${position.before}`);
55
- }
56
- content = content.slice(0, index) + markedCode + content.slice(index);
57
- }
58
-
59
- await fs.writeFile(filePath, content, 'utf-8');
60
- }
61
-
62
- export function removeInjection(content: string, id: string): string {
63
- const startMarker = CODE_MARKER_START(id);
64
- const endMarker = CODE_MARKER_END(id);
65
-
66
- const startIndex = content.indexOf(startMarker);
67
- if (startIndex === -1) {
68
- return content;
69
- }
70
-
71
- const endIndex = content.indexOf(endMarker, startIndex);
72
- if (endIndex === -1) {
73
- return content;
74
- }
75
-
76
- // Remove everything from start marker to end marker (inclusive)
77
- const before = content.slice(0, startIndex);
78
- const after = content.slice(endIndex + endMarker.length);
79
-
80
- return before + after;
81
- }
82
-
83
- export function hasInjection(content: string, id: string): boolean {
84
- return content.includes(CODE_MARKER_START(id));
85
- }
@@ -1,89 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { ProjectInfo } from '../types';
4
-
5
- export async function detectProjectInfo(targetDir: string): Promise<ProjectInfo> {
6
- const packageJsonPath = path.join(targetDir, 'package.json');
7
-
8
- if (!await fs.pathExists(packageJsonPath)) {
9
- throw new Error('No package.json found. This does not appear to be a Node.js project.');
10
- }
11
-
12
- const packageJson = await fs.readJSON(packageJsonPath);
13
-
14
- // Detect framework
15
- const isNextJs = packageJson.dependencies?.next || packageJson.devDependencies?.next;
16
- const framework = isNextJs ? 'nextjs' : 'unknown';
17
-
18
- if (framework === 'unknown') {
19
- throw new Error('Only Next.js projects are currently supported.');
20
- }
21
-
22
- // Detect router type
23
- const appDirExists = await fs.pathExists(path.join(targetDir, 'app'));
24
- const pagesDirExists = await fs.pathExists(path.join(targetDir, 'pages'));
25
- const srcAppDirExists = await fs.pathExists(path.join(targetDir, 'src', 'app'));
26
- const srcPagesDirExists = await fs.pathExists(path.join(targetDir, 'src', 'pages'));
27
-
28
- let router: 'app' | 'pages' | 'unknown' = 'unknown';
29
- if (appDirExists || srcAppDirExists) {
30
- router = 'app';
31
- } else if (pagesDirExists || srcPagesDirExists) {
32
- router = 'pages';
33
- }
34
-
35
- // Detect TypeScript vs JavaScript
36
- const tsconfigExists = await fs.pathExists(path.join(targetDir, 'tsconfig.json'));
37
- const language = tsconfigExists ? 'ts' : 'js';
38
-
39
- // Detect package manager
40
- const yarnLockExists = await fs.pathExists(path.join(targetDir, 'yarn.lock'));
41
- const pnpmLockExists = await fs.pathExists(path.join(targetDir, 'pnpm-lock.yaml'));
42
- let packageManager: 'npm' | 'yarn' | 'pnpm' = 'npm';
43
-
44
- if (pnpmLockExists) {
45
- packageManager = 'pnpm';
46
- } else if (yarnLockExists) {
47
- packageManager = 'yarn';
48
- }
49
-
50
- // Check for existing integrations
51
- const hasAuth = !!(
52
- packageJson.dependencies?.['next-auth'] ||
53
- packageJson.dependencies?.['@auth/core'] ||
54
- packageJson.dependencies?.['@clerk/nextjs'] ||
55
- packageJson.dependencies?.['@kinde-oss/kinde-auth-nextjs']
56
- );
57
-
58
- const hasPrisma = !!(
59
- packageJson.dependencies?.['@prisma/client'] ||
60
- packageJson.devDependencies?.['prisma']
61
- );
62
-
63
- return {
64
- framework,
65
- router,
66
- language,
67
- packageManager,
68
- hasAuth,
69
- hasPrisma,
70
- rootDir: targetDir,
71
- };
72
- }
73
-
74
- export function getRouterBasePath(projectInfo: ProjectInfo): string {
75
- const srcExists = fs.existsSync(path.join(projectInfo.rootDir, 'src'));
76
-
77
- if (projectInfo.router === 'app') {
78
- return srcExists ? 'src/app' : 'app';
79
- } else if (projectInfo.router === 'pages') {
80
- return srcExists ? 'src/pages' : 'pages';
81
- }
82
-
83
- throw new Error('Unknown router type');
84
- }
85
-
86
- export function getLibPath(projectInfo: ProjectInfo): string {
87
- const srcExists = fs.existsSync(path.join(projectInfo.rootDir, 'src'));
88
- return srcExists ? 'src/lib' : 'lib';
89
- }
@@ -1,127 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { logger } from './logger';
4
-
5
- const ENV_MARKER_START = '# StackKit:';
6
- const ENV_MARKER_END = '# End StackKit';
7
-
8
- export interface EnvVariable {
9
- key: string;
10
- value?: string;
11
- description: string;
12
- required: boolean;
13
- }
14
-
15
- export async function addEnvVariables(
16
- projectRoot: string,
17
- variables: EnvVariable[],
18
- options: { force?: boolean } = {}
19
- ): Promise<void> {
20
- const envExamplePath = path.join(projectRoot, '.env.example');
21
- const envPath = path.join(projectRoot, '.env');
22
-
23
- // Add to .env.example
24
- await appendToEnvFile(envExamplePath, variables, 'example', options);
25
-
26
- // Add to .env if it exists or create it
27
- const envExists = await fs.pathExists(envPath);
28
- if (envExists || options.force) {
29
- await appendToEnvFile(envPath, variables, 'local', options);
30
- }
31
-
32
- logger.success('Environment variables added');
33
- }
34
-
35
- async function appendToEnvFile(
36
- filePath: string,
37
- variables: EnvVariable[],
38
- fileType: 'example' | 'local',
39
- options: { force?: boolean } = {}
40
- ): Promise<void> {
41
- let content = '';
42
-
43
- if (await fs.pathExists(filePath)) {
44
- content = await fs.readFile(filePath, 'utf-8');
45
- }
46
-
47
- // Check if variables already exist
48
- const existingKeys = new Set<string>();
49
- const lines = content.split('\n');
50
-
51
- for (const line of lines) {
52
- const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
53
- if (match) {
54
- existingKeys.add(match[1]);
55
- }
56
- }
57
-
58
- const newVariables = variables.filter((v) => {
59
- if (existingKeys.has(v.key)) {
60
- if (!options.force) {
61
- logger.warn(`Variable ${v.key} already exists in ${filePath}`);
62
- return false;
63
- }
64
- }
65
- return true;
66
- });
67
-
68
- if (newVariables.length === 0) {
69
- return;
70
- }
71
-
72
- // Ensure file ends with newline
73
- if (content && !content.endsWith('\n')) {
74
- content += '\n';
75
- }
76
-
77
- // Add marker and variables
78
- content += '\n';
79
- content += `${ENV_MARKER_START} Added by StackKit\n`;
80
-
81
- for (const variable of newVariables) {
82
- if (variable.description) {
83
- content += `# ${variable.description}\n`;
84
- }
85
-
86
- const value = fileType === 'example' ? (variable.value || '') : (variable.value || '');
87
- content += `${variable.key}=${value}\n`;
88
- }
89
-
90
- content += `${ENV_MARKER_END}\n`;
91
-
92
- await fs.writeFile(filePath, content, 'utf-8');
93
- }
94
-
95
- export async function removeEnvVariables(
96
- projectRoot: string,
97
- keys: string[]
98
- ): Promise<void> {
99
- const envExamplePath = path.join(projectRoot, '.env.example');
100
- const envPath = path.join(projectRoot, '.env');
101
-
102
- await removeFromEnvFile(envExamplePath, keys);
103
-
104
- if (await fs.pathExists(envPath)) {
105
- await removeFromEnvFile(envPath, keys);
106
- }
107
- }
108
-
109
- async function removeFromEnvFile(filePath: string, keys: string[]): Promise<void> {
110
- if (!await fs.pathExists(filePath)) {
111
- return;
112
- }
113
-
114
- let content = await fs.readFile(filePath, 'utf-8');
115
- const lines = content.split('\n');
116
- const newLines: string[] = [];
117
-
118
- for (const line of lines) {
119
- const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
120
- if (match && keys.includes(match[1])) {
121
- continue; // Skip this line
122
- }
123
- newLines.push(line);
124
- }
125
-
126
- await fs.writeFile(filePath, newLines.join('\n'), 'utf-8');
127
- }
@@ -1,59 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { logger } from './logger';
4
-
5
- export async function copyTemplate(
6
- templatePath: string,
7
- targetPath: string,
8
- projectName: string
9
- ): Promise<void> {
10
- if (!await fs.pathExists(templatePath)) {
11
- throw new Error(`Template not found: ${templatePath}`);
12
- }
13
-
14
- // Create target directory
15
- await fs.ensureDir(targetPath);
16
-
17
- // Copy all files
18
- await fs.copy(templatePath, targetPath, {
19
- filter: (src) => {
20
- const basename = path.basename(src);
21
- // Skip template.json metadata file and node_modules
22
- return basename !== 'template.json' && basename !== 'node_modules';
23
- },
24
- });
25
-
26
- // Update package.json with project name
27
- const packageJsonPath = path.join(targetPath, 'package.json');
28
- if (await fs.pathExists(packageJsonPath)) {
29
- const packageJson = await fs.readJSON(packageJsonPath);
30
- packageJson.name = projectName;
31
- await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
32
- }
33
-
34
- logger.success(`Template copied to ${targetPath}`);
35
- }
36
-
37
- export async function createFile(
38
- targetPath: string,
39
- content: string,
40
- options: { force?: boolean } = {}
41
- ): Promise<void> {
42
- const exists = await fs.pathExists(targetPath);
43
-
44
- if (exists && !options.force) {
45
- logger.warn(`File already exists: ${targetPath} (use --force to overwrite)`);
46
- return;
47
- }
48
-
49
- await fs.ensureDir(path.dirname(targetPath));
50
- await fs.writeFile(targetPath, content, 'utf-8');
51
- }
52
-
53
- export async function readFile(filePath: string): Promise<string> {
54
- return fs.readFile(filePath, 'utf-8');
55
- }
56
-
57
- export async function fileExists(filePath: string): Promise<boolean> {
58
- return fs.pathExists(filePath);
59
- }
@@ -1,64 +0,0 @@
1
- import fs from 'fs-extra';
2
- import { logger } from './logger';
3
-
4
- export async function modifyJson(
5
- filePath: string,
6
- modifier: (json: any) => any,
7
- options: { create?: boolean; force?: boolean } = {}
8
- ): Promise<void> {
9
- const exists = await fs.pathExists(filePath);
10
-
11
- if (!exists && !options.create) {
12
- throw new Error(`File not found: ${filePath}`);
13
- }
14
-
15
- let json = {};
16
- if (exists) {
17
- json = await fs.readJSON(filePath);
18
- }
19
-
20
- const modified = modifier(json);
21
- await fs.writeJSON(filePath, modified, { spaces: 2 });
22
- }
23
-
24
- export async function addToPackageJson(
25
- filePath: string,
26
- section: 'dependencies' | 'devDependencies' | 'scripts',
27
- additions: Record<string, string>
28
- ): Promise<void> {
29
- await modifyJson(filePath, (json) => {
30
- json[section] = json[section] || {};
31
- Object.assign(json[section], additions);
32
- return json;
33
- });
34
- }
35
-
36
- export async function setJsonValue(
37
- filePath: string,
38
- path: string,
39
- value: any,
40
- options: { merge?: boolean } = {}
41
- ): Promise<void> {
42
- await modifyJson(filePath, (json) => {
43
- const keys = path.split('.');
44
- let current = json;
45
-
46
- for (let i = 0; i < keys.length - 1; i++) {
47
- const key = keys[i];
48
- if (!current[key]) {
49
- current[key] = {};
50
- }
51
- current = current[key];
52
- }
53
-
54
- const lastKey = keys[keys.length - 1];
55
-
56
- if (options.merge && typeof current[lastKey] === 'object' && typeof value === 'object') {
57
- current[lastKey] = { ...current[lastKey], ...value };
58
- } else {
59
- current[lastKey] = value;
60
- }
61
-
62
- return json;
63
- });
64
- }
@@ -1,62 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora, { Ora } from 'ora';
3
-
4
- export class Logger {
5
- private spinner: Ora | null = null;
6
-
7
- info(message: string): void {
8
- console.log(chalk.blue('ℹ'), message);
9
- }
10
-
11
- success(message: string): void {
12
- console.log(chalk.green('✔'), message);
13
- }
14
-
15
- error(message: string): void {
16
- console.log(chalk.red('✖'), message);
17
- }
18
-
19
- warn(message: string): void {
20
- console.log(chalk.yellow('⚠'), message);
21
- }
22
-
23
- log(message: string): void {
24
- console.log(message);
25
- }
26
-
27
- newLine(): void {
28
- console.log();
29
- }
30
-
31
- startSpinner(text: string): Ora {
32
- this.spinner = ora(text).start();
33
- return this.spinner;
34
- }
35
-
36
- stopSpinner(success = true, text?: string): void {
37
- if (this.spinner) {
38
- if (success) {
39
- this.spinner.succeed(text);
40
- } else {
41
- this.spinner.fail(text);
42
- }
43
- this.spinner = null;
44
- }
45
- }
46
-
47
- updateSpinner(text: string): void {
48
- if (this.spinner) {
49
- this.spinner.text = text;
50
- }
51
- }
52
-
53
- header(text: string): void {
54
- console.log(chalk.bold.cyan(text));
55
- }
56
-
57
- footer(): void {
58
- console.log();
59
- }
60
- }
61
-
62
- export const logger = new Logger();
@@ -1,85 +0,0 @@
1
- import { detect } from 'detect-package-manager';
2
- import execa from 'execa';
3
- import { logger } from './logger';
4
-
5
- export type PackageManager = 'npm' | 'yarn' | 'pnpm';
6
-
7
- export async function detectPackageManager(cwd: string): Promise<PackageManager> {
8
- try {
9
- const pm = await detect({ cwd });
10
- return pm as PackageManager;
11
- } catch {
12
- return 'npm';
13
- }
14
- }
15
-
16
- export async function installDependencies(
17
- cwd: string,
18
- pm: PackageManager,
19
- dev = false
20
- ): Promise<void> {
21
- const spinner = logger.startSpinner(`Installing dependencies with ${pm}...`);
22
-
23
- try {
24
- const args: string[] = [];
25
-
26
- if (pm === 'npm') {
27
- args.push('install');
28
- } else if (pm === 'yarn') {
29
- args.push('install');
30
- } else if (pm === 'pnpm') {
31
- args.push('install');
32
- }
33
-
34
- await execa(pm, args, { cwd, stdio: 'pipe' });
35
- spinner.succeed(`Dependencies installed successfully`);
36
- } catch (error) {
37
- spinner.fail(`Failed to install dependencies`);
38
- throw error;
39
- }
40
- }
41
-
42
- export async function addDependencies(
43
- cwd: string,
44
- pm: PackageManager,
45
- packages: string[],
46
- dev = false
47
- ): Promise<void> {
48
- if (packages.length === 0) return;
49
-
50
- const spinner = logger.startSpinner(
51
- `Adding ${dev ? 'dev ' : ''}dependencies: ${packages.join(', ')}...`
52
- );
53
-
54
- try {
55
- const args: string[] = [];
56
-
57
- if (pm === 'npm') {
58
- args.push('install', dev ? '--save-dev' : '--save', ...packages);
59
- } else if (pm === 'yarn') {
60
- args.push('add', dev ? '--dev' : '', ...packages);
61
- } else if (pm === 'pnpm') {
62
- args.push('add', dev ? '-D' : '', ...packages);
63
- }
64
-
65
- await execa(pm, args.filter(Boolean), { cwd, stdio: 'pipe' });
66
- spinner.succeed(`Dependencies added successfully`);
67
- } catch (error) {
68
- spinner.fail(`Failed to add dependencies`);
69
- throw error;
70
- }
71
- }
72
-
73
- export async function initGit(cwd: string): Promise<void> {
74
- const spinner = logger.startSpinner('Initializing git repository...');
75
-
76
- try {
77
- await execa('git', ['init'], { cwd });
78
- await execa('git', ['add', '.'], { cwd });
79
- await execa('git', ['commit', '-m', 'Initial commit from StackKit'], { cwd });
80
- spinner.succeed('Git repository initialized');
81
- } catch (error) {
82
- spinner.fail('Failed to initialize git repository');
83
- // Don't throw - git init is optional
84
- }
85
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist"]
9
- }