@lampx83/create-ai-portal 1.0.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.
- package/README.md +40 -0
- package/bin.js +126 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# create-ai-portal
|
|
2
|
+
|
|
3
|
+
Tạo project AI-Portal mới bằng một lệnh (giống `create-strapi-app`).
|
|
4
|
+
|
|
5
|
+
## Cách dùng
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @lampx83/create-ai-portal@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Tạo thư mục `ai-portal-app` trong thư mục hiện tại.
|
|
12
|
+
|
|
13
|
+
Hoặc chỉ định tên thư mục:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @lampx83/create-ai-portal@latest my-portal
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
CLI sẽ:
|
|
20
|
+
|
|
21
|
+
1. Tải bản mới nhất của [AI-Portal](https://github.com/Lampx83/AI-Portal) từ GitHub.
|
|
22
|
+
2. Giải nén vào thư mục bạn chọn.
|
|
23
|
+
3. Tạo file `.env` từ `.env.example`.
|
|
24
|
+
4. Hỏi có chạy Docker ngay không; nếu có thì chạy `docker compose up -d`.
|
|
25
|
+
|
|
26
|
+
## Yêu cầu
|
|
27
|
+
|
|
28
|
+
- **Node.js 18+** (để chạy `npx @lampx83/create-ai-portal`).
|
|
29
|
+
- **Docker & Docker Compose** (để chạy ứng dụng).
|
|
30
|
+
|
|
31
|
+
## Publish lên npm
|
|
32
|
+
|
|
33
|
+
Từ thư mục `create-ai-portal`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm login
|
|
37
|
+
npm publish
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Sau đó mọi người có thể chạy `npx @lampx83/create-ai-portal@latest` mà không cần clone repo. Scoped package cần: `npm publish --access public`.
|
package/bin.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const tar = require('tar');
|
|
8
|
+
|
|
9
|
+
const TEMPLATE_URL = 'https://github.com/Lampx83/AI-Portal/archive/refs/heads/main.tar.gz';
|
|
10
|
+
const TEMPLATE_ROOT_FOLDER = 'AI-Portal-main';
|
|
11
|
+
|
|
12
|
+
async function download(url) {
|
|
13
|
+
const res = await fetch(url);
|
|
14
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
15
|
+
return Buffer.from(await res.arrayBuffer());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ask(question, defaultAnswer = 'n') {
|
|
19
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
const def = defaultAnswer === 'y' ? 'Y/n' : 'y/N';
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
rl.question(`${question} (${def}) `, (answer) => {
|
|
23
|
+
rl.close();
|
|
24
|
+
const a = (answer || defaultAnswer).trim().toLowerCase();
|
|
25
|
+
resolve(a === 'y' || a === 'yes');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
const projectName = process.argv[2] || 'ai-portal-app';
|
|
32
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
33
|
+
|
|
34
|
+
console.log('\n create-ai-portal\n');
|
|
35
|
+
console.log(' Creating a new AI-Portal project in:', targetDir);
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
if (fs.existsSync(targetDir)) {
|
|
39
|
+
console.error(`Error: Directory "${projectName}" already exists.`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tmpDir = path.join(process.cwd(), `.create-ai-portal-${Date.now()}`);
|
|
44
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
process.stdout.write(' Downloading AI-Portal template... ');
|
|
48
|
+
const buf = await download(TEMPLATE_URL);
|
|
49
|
+
const tarball = path.join(tmpDir, 'template.tar.gz');
|
|
50
|
+
fs.writeFileSync(tarball, buf);
|
|
51
|
+
console.log('done.');
|
|
52
|
+
|
|
53
|
+
process.stdout.write(' Extracting... ');
|
|
54
|
+
await tar.x({ file: tarball, cwd: tmpDir });
|
|
55
|
+
const extracted = path.join(tmpDir, TEMPLATE_ROOT_FOLDER);
|
|
56
|
+
if (!fs.existsSync(extracted)) {
|
|
57
|
+
throw new Error(`Expected folder "${TEMPLATE_ROOT_FOLDER}" not found after extract.`);
|
|
58
|
+
}
|
|
59
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
60
|
+
copyRecursive(extracted, targetDir);
|
|
61
|
+
console.log('done.');
|
|
62
|
+
|
|
63
|
+
const envExample = path.join(targetDir, '.env.example');
|
|
64
|
+
const envPath = path.join(targetDir, '.env');
|
|
65
|
+
if (fs.existsSync(envExample)) {
|
|
66
|
+
fs.copyFileSync(envExample, envPath);
|
|
67
|
+
console.log(' Created .env from .env.example.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Remove create-ai-portal from the scaffold (user doesn't need it in their project)
|
|
71
|
+
const scaffoldCli = path.join(targetDir, 'create-ai-portal');
|
|
72
|
+
if (fs.existsSync(scaffoldCli)) {
|
|
73
|
+
fs.rmSync(scaffoldCli, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
if (fs.existsSync(tmpDir)) {
|
|
77
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(' Success! AI-Portal project created.');
|
|
83
|
+
console.log('');
|
|
84
|
+
|
|
85
|
+
const runDocker = await ask(' Start with Docker now?', 'y');
|
|
86
|
+
if (runDocker) {
|
|
87
|
+
const docker = spawnSync('docker', ['compose', 'up', '-d'], {
|
|
88
|
+
cwd: targetDir,
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
shell: true,
|
|
91
|
+
});
|
|
92
|
+
if (docker.status !== 0) {
|
|
93
|
+
console.log('\n Docker Compose failed or not installed. Run manually:');
|
|
94
|
+
console.log(` cd ${projectName} && docker compose up -d`);
|
|
95
|
+
} else {
|
|
96
|
+
console.log('\n Frontend: http://localhost:3000');
|
|
97
|
+
console.log(' Backend: http://localhost:3001');
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
console.log(' Next steps:');
|
|
101
|
+
console.log(` cd ${projectName}`);
|
|
102
|
+
console.log(' # Edit .env (NEXTAUTH_SECRET, POSTGRES_PASSWORD, OPENAI_API_KEY, ...)');
|
|
103
|
+
console.log(' docker compose up -d');
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(' Then open http://localhost:3000');
|
|
106
|
+
}
|
|
107
|
+
console.log('');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function copyRecursive(src, dest) {
|
|
111
|
+
const stat = fs.statSync(src);
|
|
112
|
+
if (stat.isDirectory()) {
|
|
113
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
114
|
+
for (const name of fs.readdirSync(src)) {
|
|
115
|
+
if (name === '.git') continue;
|
|
116
|
+
copyRecursive(path.join(src, name), path.join(dest, name));
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
fs.copyFileSync(src, dest);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main().catch((err) => {
|
|
124
|
+
console.error('\n Error:', err.message);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lampx83/create-ai-portal",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Scaffold a new AI-Portal project with one command (like create-strapi-app)",
|
|
5
|
+
"main": "bin.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-ai-portal": "bin.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai-portal",
|
|
14
|
+
"scaffold",
|
|
15
|
+
"create-app"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Lampx83/AI-Portal.git",
|
|
22
|
+
"directory": "create-ai-portal"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"tar": "^7.4.0"
|
|
26
|
+
}
|
|
27
|
+
}
|