create-pardx-scaffold 0.1.5 → 0.1.6
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/package.json
CHANGED
|
@@ -59,6 +59,13 @@ function main() {
|
|
|
59
59
|
copied++;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// 确保脚手架模板根目录包含 .gitignore
|
|
63
|
+
const gitignoreSrc = path.join(REPO_ROOT, '.gitignore');
|
|
64
|
+
const gitignoreDest = path.join(TEMPLATE_ROOT, '.gitignore');
|
|
65
|
+
if (fs.existsSync(gitignoreSrc)) {
|
|
66
|
+
fs.copyFileSync(gitignoreSrc, gitignoreDest);
|
|
67
|
+
}
|
|
68
|
+
|
|
62
69
|
console.log('export-scaffold: copied', copied, 'files to packages/create-pardx-scaffold/template/');
|
|
63
70
|
}
|
|
64
71
|
|
|
@@ -44,7 +44,10 @@ const config = {
|
|
|
44
44
|
authorName: '',
|
|
45
45
|
authorEmail: '',
|
|
46
46
|
databaseUrl: '',
|
|
47
|
+
readDatabaseUrl: '',
|
|
47
48
|
redisUrl: '',
|
|
49
|
+
rabbitmqUrl: '',
|
|
50
|
+
baseHost: '127.0.0.1',
|
|
48
51
|
apiPort: '',
|
|
49
52
|
webPort: '',
|
|
50
53
|
};
|
|
@@ -96,11 +99,26 @@ async function main() {
|
|
|
96
99
|
);
|
|
97
100
|
config.databaseUrl = config.databaseUrl.trim() || 'postgresql://user:password@localhost:5432/dbname';
|
|
98
101
|
|
|
102
|
+
config.readDatabaseUrl = await question(
|
|
103
|
+
`${colors.cyan}Read Database URL${colors.reset} [same as Database URL]: `
|
|
104
|
+
);
|
|
105
|
+
config.readDatabaseUrl = config.readDatabaseUrl.trim() || config.databaseUrl;
|
|
106
|
+
|
|
99
107
|
config.redisUrl = await question(
|
|
100
108
|
`${colors.cyan}Redis URL${colors.reset} [redis://localhost:6379]: `
|
|
101
109
|
);
|
|
102
110
|
config.redisUrl = config.redisUrl.trim() || 'redis://localhost:6379';
|
|
103
111
|
|
|
112
|
+
config.rabbitmqUrl = await question(
|
|
113
|
+
`${colors.cyan}RabbitMQ URL${colors.reset} [amqp://localhost:5672]: `
|
|
114
|
+
);
|
|
115
|
+
config.rabbitmqUrl = config.rabbitmqUrl.trim() || 'amqp://localhost:5672';
|
|
116
|
+
|
|
117
|
+
config.baseHost = await question(
|
|
118
|
+
`${colors.cyan}Base Host${colors.reset} [127.0.0.1]: `
|
|
119
|
+
);
|
|
120
|
+
config.baseHost = config.baseHost.trim() || '127.0.0.1';
|
|
121
|
+
|
|
104
122
|
rl.close();
|
|
105
123
|
|
|
106
124
|
// Display configuration summary
|
|
@@ -110,8 +128,11 @@ async function main() {
|
|
|
110
128
|
console.log(` Author: ${config.authorName} <${config.authorEmail}>`);
|
|
111
129
|
console.log(` API Port: ${config.apiPort}`);
|
|
112
130
|
console.log(` Web Port: ${config.webPort}`);
|
|
131
|
+
console.log(` Base Host: ${config.baseHost}`);
|
|
113
132
|
console.log(` Database: ${config.databaseUrl}`);
|
|
133
|
+
console.log(` Read DB: ${config.readDatabaseUrl}`);
|
|
114
134
|
console.log(` Redis: ${config.redisUrl}`);
|
|
135
|
+
console.log(` RabbitMQ: ${config.rabbitmqUrl}`);
|
|
115
136
|
|
|
116
137
|
// Apply configuration
|
|
117
138
|
log.header('\n🔧 Applying Configuration');
|
|
@@ -158,27 +179,9 @@ async function applyConfiguration() {
|
|
|
158
179
|
});
|
|
159
180
|
log.success('API package.json updated');
|
|
160
181
|
|
|
161
|
-
// Create .env files
|
|
162
|
-
log.info('Creating environment files...');
|
|
163
|
-
|
|
164
|
-
path.join(rootDir, 'apps/web/.env.local'),
|
|
165
|
-
{
|
|
166
|
-
NEXT_PUBLIC_API_BASE_URL: `http://localhost:${config.apiPort}/api`,
|
|
167
|
-
}
|
|
168
|
-
);
|
|
169
|
-
createEnvFile(
|
|
170
|
-
path.join(rootDir, 'apps/api/.env'),
|
|
171
|
-
{
|
|
172
|
-
NODE_ENV: 'development',
|
|
173
|
-
PORT: config.apiPort,
|
|
174
|
-
HOST: '0.0.0.0',
|
|
175
|
-
DATABASE_URL: config.databaseUrl,
|
|
176
|
-
REDIS_URL: config.redisUrl,
|
|
177
|
-
JWT_SECRET: generateRandomSecret(),
|
|
178
|
-
JWT_EXPIRES_IN: '7d',
|
|
179
|
-
CORS_ORIGIN: `http://localhost:${config.webPort}`,
|
|
180
|
-
}
|
|
181
|
-
);
|
|
182
|
+
// Create .env files from .env.example
|
|
183
|
+
log.info('Creating environment files from .env.example...');
|
|
184
|
+
createEnvFromExample(rootDir, config);
|
|
182
185
|
log.success('Environment files created');
|
|
183
186
|
|
|
184
187
|
// Update README
|
|
@@ -198,11 +201,124 @@ function updatePackageJson(filePath, updates) {
|
|
|
198
201
|
fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
/**
|
|
205
|
+
* 解析 .env.example 文件,提取 KEY=VALUE 行(含注释、空行)
|
|
206
|
+
*/
|
|
207
|
+
function parseEnvExample(content) {
|
|
208
|
+
const lines = [];
|
|
209
|
+
const vars = new Map();
|
|
210
|
+
for (const line of content.split(/\n/)) {
|
|
211
|
+
const trimmed = line.trimEnd();
|
|
212
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
213
|
+
lines.push({ type: 'raw', value: trimmed });
|
|
214
|
+
} else {
|
|
215
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
216
|
+
if (match) {
|
|
217
|
+
const key = match[1];
|
|
218
|
+
let value = match[2];
|
|
219
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
220
|
+
value = value.slice(1, -1);
|
|
221
|
+
}
|
|
222
|
+
lines.push({ type: 'var', key, raw: trimmed });
|
|
223
|
+
vars.set(key, value);
|
|
224
|
+
} else {
|
|
225
|
+
lines.push({ type: 'raw', value: trimmed });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { lines, vars };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 根据 apps/api/.env.example 和 apps/web/.env.example 生成 .env 文件
|
|
234
|
+
*/
|
|
235
|
+
function createEnvFromExample(rootDir, config) {
|
|
236
|
+
const apiExamplePath = path.join(rootDir, 'apps/api/.env.example');
|
|
237
|
+
const webExamplePath = path.join(rootDir, 'apps/web/.env.example');
|
|
238
|
+
|
|
239
|
+
// 变量替换映射(apps/api),参考 apps/api/.env.example
|
|
240
|
+
const apiReplacements = {
|
|
241
|
+
BASE_HOST: config.baseHost,
|
|
242
|
+
DATABASE_URL: config.databaseUrl,
|
|
243
|
+
READ_DATABASE_URL: config.readDatabaseUrl,
|
|
244
|
+
REDIS_URL: config.redisUrl,
|
|
245
|
+
RABBITMQ_URL: config.rabbitmqUrl,
|
|
246
|
+
API_BASE_URL: `http://localhost:${config.apiPort}/api`,
|
|
247
|
+
INTERNAL_API_BASE_URL: `http://127.0.0.1:${config.apiPort}/api`,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
if (fs.existsSync(apiExamplePath)) {
|
|
251
|
+
const content = fs.readFileSync(apiExamplePath, 'utf8');
|
|
252
|
+
const { lines, vars } = parseEnvExample(content);
|
|
253
|
+
const outLines = [];
|
|
254
|
+
for (const item of lines) {
|
|
255
|
+
if (item.type === 'raw') {
|
|
256
|
+
outLines.push(item.value);
|
|
257
|
+
} else {
|
|
258
|
+
let val = apiReplacements[item.key];
|
|
259
|
+
if (val === undefined && item.key === 'RABBITMQ_EVENTS_URL') {
|
|
260
|
+
const orig = vars.get(item.key) || '';
|
|
261
|
+
val = orig.replace(/\$\{BASE_HOST\}/g, config.baseHost);
|
|
262
|
+
} else if (val === undefined) {
|
|
263
|
+
val = null;
|
|
264
|
+
}
|
|
265
|
+
const final = val !== null && val !== undefined ? `${item.key}=${val}` : item.raw;
|
|
266
|
+
outLines.push(final);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const apiEnvPath = path.join(rootDir, 'apps/api/.env');
|
|
270
|
+
fs.writeFileSync(apiEnvPath, outLines.join('\n') + '\n');
|
|
271
|
+
|
|
272
|
+
// 追加 JWT_SECRET(若 .env.example 中无)
|
|
273
|
+
const apiEnvContent = fs.readFileSync(apiEnvPath, 'utf8');
|
|
274
|
+
if (!/^JWT_SECRET=/m.test(apiEnvContent)) {
|
|
275
|
+
fs.appendFileSync(apiEnvPath, `\n# JWT\nJWT_SECRET=${generateRandomSecret()}\nJWT_EXPIRES_IN=7d\n`);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
createEnvFile(path.join(rootDir, 'apps/api/.env'), {
|
|
279
|
+
NODE_ENV: 'development',
|
|
280
|
+
DATABASE_URL: config.databaseUrl,
|
|
281
|
+
REDIS_URL: config.redisUrl,
|
|
282
|
+
RABBITMQ_URL: config.rabbitmqUrl,
|
|
283
|
+
JWT_SECRET: generateRandomSecret(),
|
|
284
|
+
API_BASE_URL: `http://localhost:${config.apiPort}/api`,
|
|
285
|
+
INTERNAL_API_BASE_URL: `http://127.0.0.1:${config.apiPort}/api`,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// apps/web
|
|
290
|
+
const webReplacements = {
|
|
291
|
+
NEXT_PUBLIC_API_BASE_URL: `http://localhost:${config.apiPort}/api`,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
if (fs.existsSync(webExamplePath)) {
|
|
295
|
+
const content = fs.readFileSync(webExamplePath, 'utf8');
|
|
296
|
+
const { lines } = parseEnvExample(content);
|
|
297
|
+
const outLines = [];
|
|
298
|
+
for (const item of lines) {
|
|
299
|
+
if (item.type === 'raw') {
|
|
300
|
+
outLines.push(item.value);
|
|
301
|
+
} else {
|
|
302
|
+
const val = webReplacements[item.key] ?? null;
|
|
303
|
+
const final = val !== null ? `${item.key}=${val}` : item.raw;
|
|
304
|
+
outLines.push(final);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
fs.writeFileSync(
|
|
308
|
+
path.join(rootDir, 'apps/web/.env.local'),
|
|
309
|
+
outLines.join('\n') + '\n'
|
|
310
|
+
);
|
|
311
|
+
} else {
|
|
312
|
+
createEnvFile(path.join(rootDir, 'apps/web/.env.local'), {
|
|
313
|
+
NEXT_PUBLIC_API_BASE_URL: `http://localhost:${config.apiPort}/api`,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
201
318
|
function createEnvFile(filePath, variables) {
|
|
202
319
|
const content = Object.entries(variables)
|
|
203
320
|
.map(([key, value]) => `${key}=${value}`)
|
|
204
321
|
.join('\n') + '\n';
|
|
205
|
-
|
|
206
322
|
fs.writeFileSync(filePath, content);
|
|
207
323
|
}
|
|
208
324
|
|
|
@@ -213,9 +329,9 @@ function updateReadme(filePath) {
|
|
|
213
329
|
}
|
|
214
330
|
|
|
215
331
|
let content = fs.readFileSync(filePath, 'utf8');
|
|
216
|
-
content = content.replace(/# PardxAI Monorepo Scaffold
|
|
332
|
+
content = content.replace(/# PardxAI Monorepo Scaffold[^\n]*/, `# ${config.projectName}`);
|
|
217
333
|
content = content.replace(
|
|
218
|
-
/A production-ready monorepo scaffold
|
|
334
|
+
/A comprehensive production-ready monorepo scaffold with complete implementations\./,
|
|
219
335
|
config.projectDescription
|
|
220
336
|
);
|
|
221
337
|
fs.writeFileSync(filePath, content);
|