@enfyra/create 0.1.4 → 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
@@ -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,81 +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) {
116
- if (config.packageManager === 'npm') {
117
- await fs.writeFile(
118
- path.join(projectPath, '.npmrc'),
119
- [
120
- 'install-strategy=nested',
121
- '',
122
- ].join('\n'),
123
- );
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
+ }
124
105
  return;
125
106
  }
126
107
 
127
108
  if (config.packageManager !== 'yarn') return;
128
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);
129
122
  await fs.writeFile(
130
- path.join(projectPath, '.yarnrc.yml'),
131
- [
132
- 'approvedGitRepositories:',
133
- ' - "**"',
134
- '',
135
- 'enableScripts: true',
136
- '',
137
- 'nodeLinker: node-modules',
138
- '',
139
- 'nmHoistingLimits: workspaces',
140
- '',
141
- 'npmMinimalAgeGate: 0',
142
- '',
143
- ].join('\n'),
123
+ path.join(scriptsPath, 'run-both.js'),
124
+ buildRunBothScript(config.packageManager, config.serverPort),
144
125
  );
145
126
  }
146
127
 
@@ -257,11 +238,16 @@ function ensureAppDirectDependencies(packageJson) {
257
238
  }
258
239
  }
259
240
 
260
- 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) {
261
247
  return new Promise((resolve, reject) => {
262
248
  const { command, args } = getInstallCommand(packageManager);
263
249
  const install = spawn(command, args, {
264
- cwd: projectPath,
250
+ cwd: workspacePath,
265
251
  stdio: 'pipe',
266
252
  });
267
253
 
@@ -282,7 +268,7 @@ function installDependencies(projectPath, packageManager) {
282
268
  return;
283
269
  }
284
270
 
285
- reject(new Error(stderr || stdout || `npm install failed with code ${code}`));
271
+ reject(new Error(stderr || stdout || `${command} install failed with code ${code}`));
286
272
  });
287
273
 
288
274
  install.on('error', (error) => {
@@ -291,6 +277,127 @@ function installDependencies(projectPath, packageManager) {
291
277
  });
292
278
  }
293
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
+
294
401
  function buildGitignore() {
295
402
  return [
296
403
  'node_modules',
@@ -326,21 +433,7 @@ function mergeGitignoreEntries(content, entries) {
326
433
  return `${lines.join('\n').replace(/\n+$/, '')}\n`;
327
434
  }
328
435
 
329
- async function collectTemplateResolutions(projectPath) {
330
- const resolutions = {};
331
-
332
- for (const workspace of ['app', 'server']) {
333
- const packageJsonPath = path.join(projectPath, workspace, 'package.json');
334
- if (!fs.existsSync(packageJsonPath)) continue;
335
-
336
- const packageJson = await fs.readJson(packageJsonPath);
337
- Object.assign(resolutions, packageJson.resolutions || {});
338
- }
339
-
340
- return resolutions;
341
- }
342
-
343
- function buildPnpmWorkspaceYaml(resolutions, pnpmVersion) {
436
+ function buildPnpmProjectConfig(pnpmVersion) {
344
437
  const allowedBuildDependencies = [
345
438
  '@parcel/watcher',
346
439
  'esbuild',
@@ -349,12 +442,7 @@ function buildPnpmWorkspaceYaml(resolutions, pnpmVersion) {
349
442
  'sharp',
350
443
  'vue-demi',
351
444
  ];
352
- const lines = [
353
- 'packages:',
354
- ' - app',
355
- ' - server',
356
- '',
357
- ];
445
+ const lines = [];
358
446
 
359
447
  if (getMajorVersion(pnpmVersion) >= 11) {
360
448
  lines.push('allowBuilds:');
@@ -368,14 +456,6 @@ function buildPnpmWorkspaceYaml(resolutions, pnpmVersion) {
368
456
  }
369
457
  }
370
458
 
371
- const overrideEntries = Object.entries(resolutions);
372
- if (overrideEntries.length > 0) {
373
- lines.push('', 'overrides:');
374
- for (const [name, version] of overrideEntries) {
375
- lines.push(` ${yamlQuote(name)}: ${yamlQuote(version)}`);
376
- }
377
- }
378
-
379
459
  return `${lines.join('\n')}\n`;
380
460
  }
381
461
 
@@ -401,7 +481,9 @@ module.exports = {
401
481
  cleanPackageManagerRestrictions,
402
482
  rewritePackageManagerScripts,
403
483
  toPackageName,
404
- buildPnpmWorkspaceYaml,
405
- ensureAppDirectDependencies,
406
484
  mergeGitignoreEntries,
485
+ ensureAppDirectDependencies,
486
+ buildPnpmProjectConfig,
487
+ buildRunBothScript,
488
+ buildRootYarnLock,
407
489
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/create",
3
- "version": "0.1.4",
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": {