stackkit-cli 0.1.0 → 0.1.2
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/add.js +1 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.js +2 -2
- package/dist/commands/list.js.map +1 -1
- package/modules/auth/nextauth/files/app-router/api/auth/[...nextauth]/route.ts +6 -0
- package/modules/auth/nextauth/files/lib/auth.ts +82 -0
- package/modules/auth/nextauth/files/pages-router/api/auth/[...nextauth].ts +4 -0
- package/modules/auth/nextauth/module.json +50 -0
- package/package.json +7 -1
- package/templates/next-prisma-postgres-shadcn/.env.example +5 -0
- package/templates/next-prisma-postgres-shadcn/.eslintrc.json +7 -0
- package/templates/next-prisma-postgres-shadcn/.prettierrc +8 -0
- package/templates/next-prisma-postgres-shadcn/README.md +79 -0
- package/templates/next-prisma-postgres-shadcn/app/api/health/route.ts +25 -0
- package/templates/next-prisma-postgres-shadcn/app/globals.css +1 -0
- package/templates/next-prisma-postgres-shadcn/app/layout.tsx +22 -0
- package/templates/next-prisma-postgres-shadcn/app/page.tsx +29 -0
- package/templates/next-prisma-postgres-shadcn/lib/db.ts +14 -0
- package/templates/next-prisma-postgres-shadcn/lib/env.ts +15 -0
- package/templates/next-prisma-postgres-shadcn/next.config.ts +7 -0
- package/templates/next-prisma-postgres-shadcn/package.json +32 -0
- package/templates/next-prisma-postgres-shadcn/prisma/schema.prisma +20 -0
- package/templates/next-prisma-postgres-shadcn/public/.gitkeep +1 -0
- package/templates/next-prisma-postgres-shadcn/template.json +18 -0
- package/templates/next-prisma-postgres-shadcn/tsconfig.json +32 -0
- package/src/commands/add.ts +0 -261
- package/src/commands/init.ts +0 -182
- package/src/commands/list.ts +0 -124
- package/src/index.ts +0 -53
- package/src/types/index.ts +0 -71
- package/src/utils/code-inject.ts +0 -85
- package/src/utils/detect.ts +0 -89
- package/src/utils/env-editor.ts +0 -127
- package/src/utils/files.ts +0 -59
- package/src/utils/json-editor.ts +0 -64
- package/src/utils/logger.ts +0 -62
- package/src/utils/package-manager.ts +0 -85
- package/tsconfig.json +0 -9
package/src/utils/code-inject.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/detect.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/env-editor.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/files.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/json-editor.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/logger.ts
DELETED
|
@@ -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
|
-
}
|