@enfyra/create 0.1.3 → 0.1.5

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 CHANGED
@@ -10,10 +10,12 @@ The generated project contains:
10
10
 
11
11
  - `app/` - Enfyra Nuxt admin app
12
12
  - `server/` - Enfyra server
13
- - Root workspace scripts for running both together
13
+ - Root scripts for running both together
14
14
  - `app/.env` connected to `http://localhost:1105`
15
15
  - `server/.env` with `PORT=1105`
16
16
 
17
+ `app/` and `server/` are installed as separate applications. Their dependencies and lockfiles stay inside each folder; the root folder only coordinates scripts.
18
+
17
19
  The CLI prompts for:
18
20
 
19
21
  - Package manager: npm, yarn, or pnpm
@@ -18,9 +18,8 @@ function detectPackageManagers() {
18
18
  }
19
19
 
20
20
  function getWorkspaceRunCommand(packageManager, workspace, script) {
21
- if (packageManager === 'yarn') return `yarn --cwd ${workspace} ${script}`;
22
- if (packageManager === 'pnpm') return `pnpm --dir ${workspace} run ${script}`;
23
- return `npm run ${script} -w ${workspace}`;
21
+ const runCommand = packageManager === 'yarn' ? `yarn ${script}` : `${packageManager} run ${script}`;
22
+ return `(cd ${workspace} && ${runCommand})`;
24
23
  }
25
24
 
26
25
  function getPackageRunCommand(packageManager, script) {
@@ -7,7 +7,6 @@ const { downloadTemplate } = require('giget');
7
7
  const { generateEnvFiles } = require('./env-builder');
8
8
  const {
9
9
  getInstallCommand,
10
- getPackageExecCommand,
11
10
  getPackageRunCommand,
12
11
  getWorkspaceRunCommand,
13
12
  } = require('./package-managers');
@@ -36,14 +35,15 @@ async function createProject(config, projectPath) {
36
35
  });
37
36
  spinner.succeed(chalk.green('Server downloaded'));
38
37
 
39
- spinner.start(chalk.blue(`Preparing ${config.packageManager} workspaces...`));
38
+ spinner.start(chalk.blue(`Preparing ${config.packageManager} project...`));
40
39
  await removeTemplateFiles(projectPath);
41
40
  await cleanPackageManagerRestrictions(projectPath);
42
- await updateWorkspacePackageJson(projectPath, config);
43
- await writePackageManagerConfig(projectPath, config);
44
41
  await updateAppPackageJson(path.join(projectPath, 'app'), config);
45
42
  await updateServerPackageJson(path.join(projectPath, 'server'), config);
46
- spinner.succeed(chalk.green(`${config.packageManager} workspaces prepared`));
43
+ await updateWorkspacePackageJson(projectPath, config);
44
+ await writePackageManagerConfig(projectPath, config);
45
+ await writeRootScripts(projectPath, config);
46
+ spinner.succeed(chalk.green(`${config.packageManager} project prepared`));
47
47
 
48
48
  spinner.start(chalk.blue('Generating environment files...'));
49
49
  await generateEnvFiles(projectPath, config);
@@ -66,68 +66,62 @@ async function createProject(config, projectPath) {
66
66
  async function updateWorkspacePackageJson(projectPath, config) {
67
67
  const packageName = toPackageName(config.projectName);
68
68
  const packageManager = config.packageManager;
69
- const resolutions = await collectTemplateResolutions(projectPath);
70
- const serverReadyTarget = `tcp:localhost:${config.serverPort}`;
71
- const waitForServerCommand = getPackageExecCommand(packageManager, 'wait-on', [serverReadyTarget]);
72
69
  const packageJson = {
73
70
  name: packageName,
74
71
  version: '0.1.0',
75
72
  private: true,
76
- workspaces: [
77
- 'app',
78
- 'server',
79
- ],
80
73
  scripts: {
81
- dev: `concurrently -k -n server,app -c cyan,green "${getWorkspaceRunCommand(packageManager, 'server', 'dev')}" "${waitForServerCommand} && ${getWorkspaceRunCommand(packageManager, 'app', 'dev')}"`,
74
+ dev: 'node scripts/run-both.js dev',
82
75
  'dev:app': getWorkspaceRunCommand(packageManager, 'app', 'dev'),
83
76
  'dev:server': getWorkspaceRunCommand(packageManager, 'server', 'dev'),
84
77
  build: `${getWorkspaceRunCommand(packageManager, 'server', 'build')} && ${getWorkspaceRunCommand(packageManager, 'app', 'build')}`,
85
78
  'build:app': getWorkspaceRunCommand(packageManager, 'app', 'build'),
86
79
  'build:server': getWorkspaceRunCommand(packageManager, 'server', 'build'),
87
- start: `concurrently -k -n server,app -c cyan,green "${getWorkspaceRunCommand(packageManager, 'server', 'start')}" "${waitForServerCommand} && ${getWorkspaceRunCommand(packageManager, 'app', 'start')}"`,
80
+ start: 'node scripts/run-both.js start',
88
81
  'start:app': getWorkspaceRunCommand(packageManager, 'app', 'start'),
89
82
  'start:server': getWorkspaceRunCommand(packageManager, 'server', 'start'),
90
83
  serve: getPackageRunCommand(packageManager, 'start'),
91
84
  'preview:app': getWorkspaceRunCommand(packageManager, 'app', 'preview'),
92
85
  },
93
- devDependencies: {
94
- concurrently: '^9.2.1',
95
- 'wait-on': '^8.0.3',
96
- },
97
86
  };
98
87
 
99
88
  if (config.packageManagerVersion) {
100
89
  packageJson.packageManager = `${packageManager}@${config.packageManagerVersion}`;
101
90
  }
102
91
 
103
- if (packageManager === 'yarn' && Object.keys(resolutions).length > 0) {
104
- packageJson.resolutions = resolutions;
105
- }
106
-
107
92
  await fs.writeJson(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
108
93
  await fs.writeFile(path.join(projectPath, '.gitignore'), buildGitignore());
109
94
  await ensureWorkspaceGitignores(projectPath);
110
- if (packageManager === 'pnpm') {
111
- await fs.writeFile(path.join(projectPath, 'pnpm-workspace.yaml'), buildPnpmWorkspaceYaml(resolutions, config.packageManagerVersion));
112
- }
113
95
  }
114
96
 
115
97
  async function writePackageManagerConfig(projectPath, config) {
98
+ if (config.packageManager === 'pnpm') {
99
+ for (const workspace of ['app', 'server']) {
100
+ await fs.writeFile(
101
+ path.join(projectPath, workspace, 'pnpm-workspace.yaml'),
102
+ buildPnpmProjectConfig(config.packageManagerVersion),
103
+ );
104
+ }
105
+ return;
106
+ }
107
+
116
108
  if (config.packageManager !== 'yarn') return;
117
109
 
110
+ await fs.writeFile(path.join(projectPath, '.yarnrc.yml'), buildYarnConfig());
111
+ await fs.writeFile(path.join(projectPath, 'yarn.lock'), buildRootYarnLock(toPackageName(config.projectName)));
112
+
113
+ for (const workspace of ['app', 'server']) {
114
+ await fs.writeFile(path.join(projectPath, workspace, '.yarnrc.yml'), buildYarnConfig());
115
+ await fs.ensureFile(path.join(projectPath, workspace, 'yarn.lock'));
116
+ }
117
+ }
118
+
119
+ async function writeRootScripts(projectPath, config) {
120
+ const scriptsPath = path.join(projectPath, 'scripts');
121
+ await fs.ensureDir(scriptsPath);
118
122
  await fs.writeFile(
119
- path.join(projectPath, '.yarnrc.yml'),
120
- [
121
- 'approvedGitRepositories:',
122
- ' - "**"',
123
- '',
124
- 'enableScripts: true',
125
- '',
126
- 'nodeLinker: node-modules',
127
- '',
128
- 'npmMinimalAgeGate: 0',
129
- '',
130
- ].join('\n'),
123
+ path.join(scriptsPath, 'run-both.js'),
124
+ buildRunBothScript(config.packageManager, config.serverPort),
131
125
  );
132
126
  }
133
127
 
@@ -244,11 +238,16 @@ function ensureAppDirectDependencies(packageJson) {
244
238
  }
245
239
  }
246
240
 
247
- function installDependencies(projectPath, packageManager) {
241
+ async function installDependencies(projectPath, packageManager) {
242
+ await installWorkspaceDependencies(path.join(projectPath, 'server'), packageManager);
243
+ await installWorkspaceDependencies(path.join(projectPath, 'app'), packageManager);
244
+ }
245
+
246
+ function installWorkspaceDependencies(workspacePath, packageManager) {
248
247
  return new Promise((resolve, reject) => {
249
248
  const { command, args } = getInstallCommand(packageManager);
250
249
  const install = spawn(command, args, {
251
- cwd: projectPath,
250
+ cwd: workspacePath,
252
251
  stdio: 'pipe',
253
252
  });
254
253
 
@@ -269,7 +268,7 @@ function installDependencies(projectPath, packageManager) {
269
268
  return;
270
269
  }
271
270
 
272
- reject(new Error(stderr || stdout || `npm install failed with code ${code}`));
271
+ reject(new Error(stderr || stdout || `${command} install failed with code ${code}`));
273
272
  });
274
273
 
275
274
  install.on('error', (error) => {
@@ -278,6 +277,127 @@ function installDependencies(projectPath, packageManager) {
278
277
  });
279
278
  }
280
279
 
280
+ function buildYarnConfig() {
281
+ return [
282
+ 'approvedGitRepositories:',
283
+ ' - "**"',
284
+ '',
285
+ 'enableScripts: true',
286
+ '',
287
+ 'nodeLinker: node-modules',
288
+ '',
289
+ 'npmMinimalAgeGate: 0',
290
+ '',
291
+ ].join('\n');
292
+ }
293
+
294
+ function buildRootYarnLock(packageName) {
295
+ return [
296
+ '# This file is generated by @enfyra/create for root scripts.',
297
+ '',
298
+ '__metadata:',
299
+ ' version: 10',
300
+ ' cacheKey: 10c0',
301
+ '',
302
+ `"${packageName}@workspace:.":`,
303
+ ' version: 0.0.0-use.local',
304
+ ` resolution: "${packageName}@workspace:."`,
305
+ ' languageName: unknown',
306
+ ' linkType: soft',
307
+ '',
308
+ ].join('\n');
309
+ }
310
+
311
+ function buildRunBothScript(packageManager, serverPort) {
312
+ const escapedPackageManager = JSON.stringify(packageManager);
313
+ const escapedServerPort = JSON.stringify(serverPort);
314
+
315
+ return `const net = require('net');
316
+ const path = require('path');
317
+ const { spawn } = require('child_process');
318
+
319
+ const packageManager = ${escapedPackageManager};
320
+ const serverPort = ${escapedServerPort};
321
+ const script = process.argv[2] || 'dev';
322
+ const root = path.resolve(__dirname, '..');
323
+ const children = new Set();
324
+ let shuttingDown = false;
325
+
326
+ function runArgs(name) {
327
+ if (packageManager === 'yarn') return [name];
328
+ return ['run', name];
329
+ }
330
+
331
+ function spawnWorkspace(label, workspace, name) {
332
+ const child = spawn(packageManager, runArgs(name), {
333
+ cwd: path.join(root, workspace),
334
+ stdio: 'inherit',
335
+ shell: process.platform === 'win32',
336
+ });
337
+
338
+ children.add(child);
339
+ child.on('exit', (code, signal) => {
340
+ children.delete(child);
341
+ if (!shuttingDown && (code || signal)) {
342
+ console.error(\`\\n\${label} exited\${signal ? \` with signal \${signal}\` : \` with code \${code}\`}.\`);
343
+ shutdown(code || 1);
344
+ }
345
+ });
346
+
347
+ return child;
348
+ }
349
+
350
+ function waitForPort(port, host = '127.0.0.1', timeoutMs = 120000) {
351
+ const startedAt = Date.now();
352
+
353
+ return new Promise((resolve, reject) => {
354
+ const attempt = () => {
355
+ const socket = net.createConnection({ port, host });
356
+
357
+ socket.once('connect', () => {
358
+ socket.destroy();
359
+ resolve();
360
+ });
361
+
362
+ socket.once('error', () => {
363
+ socket.destroy();
364
+ if (Date.now() - startedAt >= timeoutMs) {
365
+ reject(new Error(\`Timed out waiting for server on port \${port}\`));
366
+ return;
367
+ }
368
+ setTimeout(attempt, 500);
369
+ });
370
+ };
371
+
372
+ attempt();
373
+ });
374
+ }
375
+
376
+ function shutdown(code = 0) {
377
+ if (shuttingDown) return;
378
+ shuttingDown = true;
379
+
380
+ for (const child of children) {
381
+ if (!child.killed) child.kill('SIGTERM');
382
+ }
383
+
384
+ setTimeout(() => process.exit(code), 250);
385
+ }
386
+
387
+ process.on('SIGINT', () => shutdown(0));
388
+ process.on('SIGTERM', () => shutdown(0));
389
+
390
+ (async () => {
391
+ spawnWorkspace('server', 'server', script);
392
+ await waitForPort(serverPort);
393
+ spawnWorkspace('app', 'app', script);
394
+ })().catch((error) => {
395
+ console.error(error.message);
396
+ shutdown(1);
397
+ });
398
+ `;
399
+ }
400
+
281
401
  function buildGitignore() {
282
402
  return [
283
403
  'node_modules',
@@ -313,21 +433,7 @@ function mergeGitignoreEntries(content, entries) {
313
433
  return `${lines.join('\n').replace(/\n+$/, '')}\n`;
314
434
  }
315
435
 
316
- async function collectTemplateResolutions(projectPath) {
317
- const resolutions = {};
318
-
319
- for (const workspace of ['app', 'server']) {
320
- const packageJsonPath = path.join(projectPath, workspace, 'package.json');
321
- if (!fs.existsSync(packageJsonPath)) continue;
322
-
323
- const packageJson = await fs.readJson(packageJsonPath);
324
- Object.assign(resolutions, packageJson.resolutions || {});
325
- }
326
-
327
- return resolutions;
328
- }
329
-
330
- function buildPnpmWorkspaceYaml(resolutions, pnpmVersion) {
436
+ function buildPnpmProjectConfig(pnpmVersion) {
331
437
  const allowedBuildDependencies = [
332
438
  '@parcel/watcher',
333
439
  'esbuild',
@@ -336,12 +442,7 @@ function buildPnpmWorkspaceYaml(resolutions, pnpmVersion) {
336
442
  'sharp',
337
443
  'vue-demi',
338
444
  ];
339
- const lines = [
340
- 'packages:',
341
- ' - app',
342
- ' - server',
343
- '',
344
- ];
445
+ const lines = [];
345
446
 
346
447
  if (getMajorVersion(pnpmVersion) >= 11) {
347
448
  lines.push('allowBuilds:');
@@ -355,14 +456,6 @@ function buildPnpmWorkspaceYaml(resolutions, pnpmVersion) {
355
456
  }
356
457
  }
357
458
 
358
- const overrideEntries = Object.entries(resolutions);
359
- if (overrideEntries.length > 0) {
360
- lines.push('', 'overrides:');
361
- for (const [name, version] of overrideEntries) {
362
- lines.push(` ${yamlQuote(name)}: ${yamlQuote(version)}`);
363
- }
364
- }
365
-
366
459
  return `${lines.join('\n')}\n`;
367
460
  }
368
461
 
@@ -388,7 +481,9 @@ module.exports = {
388
481
  cleanPackageManagerRestrictions,
389
482
  rewritePackageManagerScripts,
390
483
  toPackageName,
391
- buildPnpmWorkspaceYaml,
392
- ensureAppDirectDependencies,
393
484
  mergeGitignoreEntries,
485
+ ensureAppDirectDependencies,
486
+ buildPnpmProjectConfig,
487
+ buildRunBothScript,
488
+ buildRootYarnLock,
394
489
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/create",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Create a complete Enfyra app with frontend and server workspaces",
5
5
  "main": "index.js",
6
6
  "bin": {