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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pardx-scaffold",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Scaffold a new project from PardxAI monorepo (git-tracked files)",
5
5
  "license": "MIT",
6
6
  "bin": "./cli.js",
@@ -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
- createEnvFile(
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/, `# ${config.projectName}`);
332
+ content = content.replace(/# PardxAI Monorepo Scaffold[^\n]*/, `# ${config.projectName}`);
217
333
  content = content.replace(
218
- /A production-ready monorepo scaffold based on PardxAI architecture\./,
334
+ /A comprehensive production-ready monorepo scaffold with complete implementations\./,
219
335
  config.projectDescription
220
336
  );
221
337
  fs.writeFileSync(filePath, content);