create-paljs 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.
- package/bin/index.js +9 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +271 -0
- package/dist/templates.d.ts +9 -0
- package/dist/templates.js +26 -0
- package/package.json +44 -0
package/bin/index.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare class CreatePal {
|
|
2
|
+
private projectName;
|
|
3
|
+
private projectDir;
|
|
4
|
+
private selectedTemplate;
|
|
5
|
+
private packageManager;
|
|
6
|
+
private gitInit;
|
|
7
|
+
private skipInstall;
|
|
8
|
+
private verbose;
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
private detectPackageManager;
|
|
11
|
+
private showHelp;
|
|
12
|
+
private printBanner;
|
|
13
|
+
private prompt;
|
|
14
|
+
private promptChoice;
|
|
15
|
+
private downloadTemplate;
|
|
16
|
+
private prepareProject;
|
|
17
|
+
private installDependencies;
|
|
18
|
+
private initGit;
|
|
19
|
+
private printSuccess;
|
|
20
|
+
private spinner;
|
|
21
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import gradient from 'gradient-string';
|
|
2
|
+
import { downloadTemplate } from 'giget';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, cpSync, rmSync, readdirSync } from 'fs';
|
|
5
|
+
import { join, isAbsolute } from 'path';
|
|
6
|
+
import { templates, getTemplateByAlias } from './templates.js';
|
|
7
|
+
const VERSION = '1.0.0';
|
|
8
|
+
export class CreatePal {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.projectName = '';
|
|
11
|
+
this.projectDir = '';
|
|
12
|
+
this.selectedTemplate = '';
|
|
13
|
+
this.packageManager = 'npm';
|
|
14
|
+
this.gitInit = false;
|
|
15
|
+
this.skipInstall = false;
|
|
16
|
+
this.verbose = false;
|
|
17
|
+
}
|
|
18
|
+
async run() {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === '--template' || arg === '-t') {
|
|
23
|
+
this.selectedTemplate = args[++i];
|
|
24
|
+
}
|
|
25
|
+
else if (arg === '--git' || arg === '-g') {
|
|
26
|
+
this.gitInit = true;
|
|
27
|
+
}
|
|
28
|
+
else if (arg === '--skip-install') {
|
|
29
|
+
this.skipInstall = true;
|
|
30
|
+
}
|
|
31
|
+
else if (arg === '--verbose' || arg === '-v') {
|
|
32
|
+
this.verbose = true;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === '--help' || arg === '-h') {
|
|
35
|
+
this.showHelp();
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
else if (!arg.startsWith('-')) {
|
|
39
|
+
this.projectName = arg;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.packageManager = this.detectPackageManager();
|
|
43
|
+
this.printBanner();
|
|
44
|
+
if (!this.projectName) {
|
|
45
|
+
this.projectName = await this.prompt('project name', 'my-pal-app');
|
|
46
|
+
}
|
|
47
|
+
this.projectDir = isAbsolute(this.projectName)
|
|
48
|
+
? this.projectName
|
|
49
|
+
: join(process.cwd(), this.projectName);
|
|
50
|
+
if (!this.selectedTemplate) {
|
|
51
|
+
this.selectedTemplate = await this.promptChoice('Select a starter kit', templates.map((t) => ({ name: `${t.name} - ${t.hint}`, value: t.alias })));
|
|
52
|
+
}
|
|
53
|
+
const template = getTemplateByAlias(this.selectedTemplate);
|
|
54
|
+
if (!template) {
|
|
55
|
+
throw new Error(`Unknown template: ${this.selectedTemplate}`);
|
|
56
|
+
}
|
|
57
|
+
await this.downloadTemplate(template.source);
|
|
58
|
+
await this.prepareProject();
|
|
59
|
+
if (!this.skipInstall) {
|
|
60
|
+
await this.installDependencies();
|
|
61
|
+
}
|
|
62
|
+
if (this.gitInit) {
|
|
63
|
+
await this.initGit();
|
|
64
|
+
}
|
|
65
|
+
this.printSuccess();
|
|
66
|
+
}
|
|
67
|
+
detectPackageManager() {
|
|
68
|
+
const npmExecPath = process.env['npm_execpath'];
|
|
69
|
+
const npmConfig = process.env['npm_config_user_agent'];
|
|
70
|
+
if (npmConfig?.includes('pnpm'))
|
|
71
|
+
return 'pnpm';
|
|
72
|
+
if (npmConfig?.includes('yarn'))
|
|
73
|
+
return 'yarn';
|
|
74
|
+
if (npmConfig?.includes('bun'))
|
|
75
|
+
return 'bun';
|
|
76
|
+
if (npmExecPath?.includes('pnpm'))
|
|
77
|
+
return 'pnpm';
|
|
78
|
+
if (npmExecPath?.includes('yarn'))
|
|
79
|
+
return 'yarn';
|
|
80
|
+
if (npmExecPath?.includes('bun'))
|
|
81
|
+
return 'bun';
|
|
82
|
+
return 'npm';
|
|
83
|
+
}
|
|
84
|
+
showHelp() {
|
|
85
|
+
console.log(`
|
|
86
|
+
Usage: create-pal [project-name] [options]
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
-t, --template <name> Starter kit template (api, admin, client)
|
|
90
|
+
-g, --git Initialize git repository
|
|
91
|
+
--skip-install Skip installing dependencies
|
|
92
|
+
-v, --verbose Enable verbose mode
|
|
93
|
+
-h, --help Show this help message
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
create-pal my-project
|
|
97
|
+
create-pal my-project --template api
|
|
98
|
+
create-pal my-project --git
|
|
99
|
+
create-pal my-project --skip-install
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
printBanner() {
|
|
103
|
+
const title = `
|
|
104
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
105
|
+
ā ā
|
|
106
|
+
ā āāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāā ā
|
|
107
|
+
ā āāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā āāāāā ā
|
|
108
|
+
ā āāāāāāāā āāāāāāā āāāāāāāā āāā āāāāāā āāāāāāāāāāā ā
|
|
109
|
+
ā āāāāāāāā āāāāā āāāāāāāā āāā āāāāāā āāāāāāāāāāā ā
|
|
110
|
+
ā āāāāāāāā āāā āāāāāāāā āāā āāāāāāāāāāā āāā āāā ā
|
|
111
|
+
ā āāāāāāāā āāā āāāāāāāā āāā āāāāāāāāāāā āāā ā
|
|
112
|
+
ā ā
|
|
113
|
+
ā The batteries-included full-stack framework ā
|
|
114
|
+
ā ā
|
|
115
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
116
|
+
`.trim();
|
|
117
|
+
console.log(gradient.pastel.multiline(title));
|
|
118
|
+
console.log();
|
|
119
|
+
}
|
|
120
|
+
async prompt(question, defaultValue) {
|
|
121
|
+
const readline = await import('readline');
|
|
122
|
+
const rl = readline.createInterface({
|
|
123
|
+
input: process.stdin,
|
|
124
|
+
output: process.stdout,
|
|
125
|
+
});
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
rl.question(`${question} (${defaultValue}): `, (answer) => {
|
|
128
|
+
rl.close();
|
|
129
|
+
resolve(answer.trim() || defaultValue);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async promptChoice(question, choices) {
|
|
134
|
+
console.log(`\n${question}:`);
|
|
135
|
+
choices.forEach((choice, index) => {
|
|
136
|
+
console.log(` ${index + 1}. ${choice.name}`);
|
|
137
|
+
});
|
|
138
|
+
console.log();
|
|
139
|
+
const answer = await this.prompt('Enter choice number', '1');
|
|
140
|
+
const index = parseInt(answer, 10) - 1;
|
|
141
|
+
if (index >= 0 && index < choices.length) {
|
|
142
|
+
return choices[index].value;
|
|
143
|
+
}
|
|
144
|
+
return choices[0].value;
|
|
145
|
+
}
|
|
146
|
+
async downloadTemplate(source) {
|
|
147
|
+
console.log(`\nš¦ Downloading template from: ${source}`);
|
|
148
|
+
if (existsSync(this.projectDir)) {
|
|
149
|
+
const files = readdirSync(this.projectDir);
|
|
150
|
+
if (files.length > 0) {
|
|
151
|
+
rmSync(this.projectDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
await downloadTemplate(source, {
|
|
155
|
+
dir: this.projectDir,
|
|
156
|
+
registry: false,
|
|
157
|
+
});
|
|
158
|
+
console.log('ā
Template downloaded successfully');
|
|
159
|
+
}
|
|
160
|
+
async prepareProject() {
|
|
161
|
+
console.log('\nš§ Preparing project...');
|
|
162
|
+
const pkgJsonPath = join(this.projectDir, 'package.json');
|
|
163
|
+
if (existsSync(pkgJsonPath)) {
|
|
164
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
165
|
+
pkgJson.name = this.projectName.replace(/[^a-z0-9-]/gi, '-').toLowerCase();
|
|
166
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
167
|
+
}
|
|
168
|
+
const envExample = join(this.projectDir, '.env.example');
|
|
169
|
+
const envFile = join(this.projectDir, '.env');
|
|
170
|
+
if (existsSync(envExample) && !existsSync(envFile)) {
|
|
171
|
+
cpSync(envExample, envFile);
|
|
172
|
+
}
|
|
173
|
+
const gitignore = join(this.projectDir, '.gitignore');
|
|
174
|
+
const gitignoreContent = `
|
|
175
|
+
node_modules/
|
|
176
|
+
dist/
|
|
177
|
+
build/
|
|
178
|
+
.turbo/
|
|
179
|
+
.env
|
|
180
|
+
*.log
|
|
181
|
+
coverage/
|
|
182
|
+
.vscode/
|
|
183
|
+
.idea/
|
|
184
|
+
`;
|
|
185
|
+
if (existsSync(gitignore)) {
|
|
186
|
+
const existing = readFileSync(gitignore, 'utf-8');
|
|
187
|
+
if (!existing.includes('node_modules/')) {
|
|
188
|
+
writeFileSync(gitignore, existing + gitignoreContent);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
writeFileSync(gitignore, gitignoreContent.trim() + '\n');
|
|
193
|
+
}
|
|
194
|
+
const lockFiles = [
|
|
195
|
+
join(this.projectDir, 'package-lock.json'),
|
|
196
|
+
join(this.projectDir, 'pnpm-lock.yaml'),
|
|
197
|
+
join(this.projectDir, 'yarn.lock'),
|
|
198
|
+
];
|
|
199
|
+
lockFiles.forEach((file) => {
|
|
200
|
+
if (existsSync(file)) {
|
|
201
|
+
rmSync(file);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
console.log('ā
Project prepared successfully');
|
|
205
|
+
}
|
|
206
|
+
async installDependencies() {
|
|
207
|
+
if (!existsSync(join(this.projectDir, 'package.json'))) {
|
|
208
|
+
console.log('ā ļø No package.json found, skipping install');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
console.log(`\nš¦ Installing dependencies using ${this.packageManager}...`);
|
|
212
|
+
const spinner = this.spinner('Installing');
|
|
213
|
+
try {
|
|
214
|
+
await execa(this.packageManager, ['install'], {
|
|
215
|
+
cwd: this.projectDir,
|
|
216
|
+
stdio: this.verbose ? 'inherit' : 'pipe',
|
|
217
|
+
});
|
|
218
|
+
spinner.stop();
|
|
219
|
+
console.log('ā
Dependencies installed successfully');
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
spinner.stop();
|
|
223
|
+
throw new Error(`Failed to install dependencies: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async initGit() {
|
|
227
|
+
console.log('\nš Initializing git repository...');
|
|
228
|
+
try {
|
|
229
|
+
await execa('git', ['init'], {
|
|
230
|
+
cwd: this.projectDir,
|
|
231
|
+
stdio: this.verbose ? 'inherit' : 'pipe',
|
|
232
|
+
});
|
|
233
|
+
console.log('ā
Git repository initialized');
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
console.log('ā ļø Failed to initialize git (git may not be installed)');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
printSuccess() {
|
|
240
|
+
const message = `
|
|
241
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
242
|
+
ā ā
|
|
243
|
+
ā ā
Your Pal project has been created successfully! ā
|
|
244
|
+
ā ā
|
|
245
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
246
|
+
|
|
247
|
+
Next steps:
|
|
248
|
+
cd ${this.projectName}
|
|
249
|
+
npm run dev
|
|
250
|
+
|
|
251
|
+
Documentation: https://github.com/paljs/paljs
|
|
252
|
+
`.trim();
|
|
253
|
+
console.log(gradient.pastel.multiline(message));
|
|
254
|
+
}
|
|
255
|
+
spinner(message) {
|
|
256
|
+
const frames = ['ā ', 'ā ', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ', 'ā '];
|
|
257
|
+
let i = 0;
|
|
258
|
+
const interval = setInterval(() => {
|
|
259
|
+
process.stdout.write(`\r${frames[i++ % frames.length]} ${message}`);
|
|
260
|
+
}, 80);
|
|
261
|
+
return {
|
|
262
|
+
stop: () => {
|
|
263
|
+
clearInterval(interval);
|
|
264
|
+
process.stdout.write('\r' + ' '.repeat(50) + '\r');
|
|
265
|
+
},
|
|
266
|
+
update: (msg) => {
|
|
267
|
+
process.stdout.write(`\r${msg}`);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface Template {
|
|
2
|
+
name: string;
|
|
3
|
+
alias: string;
|
|
4
|
+
hint: string;
|
|
5
|
+
source: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const templates: Template[];
|
|
8
|
+
export declare function getTemplateByAlias(alias: string): Template | undefined;
|
|
9
|
+
export declare function getTemplateByName(name: string): Template | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const templates = [
|
|
2
|
+
{
|
|
3
|
+
name: 'API',
|
|
4
|
+
alias: 'api',
|
|
5
|
+
hint: 'Type-safe REST API with authentication and database',
|
|
6
|
+
source: 'github:paljs/starter-kits#main:api',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Admin',
|
|
10
|
+
alias: 'admin',
|
|
11
|
+
hint: 'Admin dashboard with authentication and UI components',
|
|
12
|
+
source: 'github:paljs/starter-kits#main:admin',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'Client',
|
|
16
|
+
alias: 'client',
|
|
17
|
+
hint: 'Full-stack client application with frontend and API',
|
|
18
|
+
source: 'github:paljs/starter-kits#main:client',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
export function getTemplateByAlias(alias) {
|
|
22
|
+
return templates.find((t) => t.alias === alias);
|
|
23
|
+
}
|
|
24
|
+
export function getTemplateByName(name) {
|
|
25
|
+
return templates.find((t) => t.name.toLowerCase() === name.toLowerCase());
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-paljs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a new PalJS project",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-paljs": "./bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/paljs/paljs.git"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"bin"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"dev": "tsx src/index.ts",
|
|
21
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"gradient-string": "^3.0.0",
|
|
25
|
+
"giget": "^3.1.2",
|
|
26
|
+
"execa": "^9.5.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"tsx": "^4.0.0",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"paljs",
|
|
38
|
+
"framework",
|
|
39
|
+
"scaffold",
|
|
40
|
+
"create",
|
|
41
|
+
"boilerplate",
|
|
42
|
+
"rpal"
|
|
43
|
+
]
|
|
44
|
+
}
|