create-nevela 0.4.2 → 0.4.3
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/index.mjs +68 -12
- package/package.json +1 -1
- package/template/laravel/src/Nevela.php +1 -1
- package/template/web/package.json +1 -1
- package/tool.mjs +45 -20
package/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// No dependencies on purpose: this runs before anything is installed.
|
|
8
8
|
|
|
9
|
-
import { spawn } from 'node:child_process';
|
|
9
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
10
10
|
import crypto from 'node:crypto';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import os from 'node:os';
|
|
@@ -369,17 +369,62 @@ async function inProject(name, args) {
|
|
|
369
369
|
process.exit(status);
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
+
/**
|
|
373
|
+
* How the copy that is running was installed, or null when it isn't the installed command.
|
|
374
|
+
* The folder it was started through is passed as well as the one its files are in: under
|
|
375
|
+
* pnpm 11 only the first says it is pnpm's.
|
|
376
|
+
*/
|
|
377
|
+
function installed() {
|
|
378
|
+
if (fromRepository) return null;
|
|
379
|
+
const startedFrom = process.argv[1] ? path.dirname(path.resolve(process.argv[1])) : here;
|
|
380
|
+
return globalInstall(here, fs.existsSync, startedFrom);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** What typing `nevela` runs now, as "x.y.z", or null when that can't be found out. */
|
|
384
|
+
function versionOnPath() {
|
|
385
|
+
const answer = spawnCommand('nevela', ['--version'], { stdio: 'pipe', encoding: 'utf8' });
|
|
386
|
+
return /^\d+\.\d+\.\d+$/m.exec(`${answer.stdout ?? ''}`)?.[0] ?? null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** The launcher of the nevela command in `dir`: the file the shell runs. */
|
|
390
|
+
const launcherIn = (dir) => path.join(dir, windows ? 'nevela.cmd' : 'nevela');
|
|
391
|
+
|
|
392
|
+
/** The version of the nevela command in `dir`, as "x.y.z", or null when it won't say. */
|
|
393
|
+
function copyVersion(dir) {
|
|
394
|
+
const file = launcherIn(dir);
|
|
395
|
+
const answer = windows
|
|
396
|
+
? spawnSync(`"${file}" --version`, { shell: true, stdio: 'pipe', encoding: 'utf8' })
|
|
397
|
+
: spawnSync(file, ['--version'], { stdio: 'pipe', encoding: 'utf8' });
|
|
398
|
+
return /^\d+\.\d+\.\d+$/m.exec(`${answer.stdout ?? ''}`)?.[0] ?? null;
|
|
399
|
+
}
|
|
400
|
+
|
|
372
401
|
/**
|
|
373
402
|
* Say so when the nevela command is installed more than once. Only the first on the PATH
|
|
374
403
|
* ever runs, so an update to one of the others looks like an update that did nothing.
|
|
404
|
+
* The one to keep is the newest, which is not always the one that runs.
|
|
375
405
|
*/
|
|
376
406
|
function otherCopies() {
|
|
377
|
-
const copies = copiesOnPath();
|
|
407
|
+
const copies = copiesOnPath().map((dir) => ({ dir, version: copyVersion(dir) }));
|
|
378
408
|
if (copies.length < 2) return;
|
|
409
|
+
let keep = copies[0];
|
|
410
|
+
for (const copy of copies) {
|
|
411
|
+
if (copy.version && (!keep.version || isNewer(copy.version, keep.version))) keep = copy;
|
|
412
|
+
}
|
|
413
|
+
const width = Math.max(...copies.map((copy) => copy.dir.length));
|
|
379
414
|
console.log(`\n ${red('!')} The nevela command is installed ${copies.length} times. Typing ${bold('nevela')} runs the first of these:`);
|
|
380
|
-
for (const
|
|
381
|
-
console.log(
|
|
382
|
-
|
|
415
|
+
for (const copy of copies) console.log(` ${copy.dir.padEnd(width)} ${copy.version ? `v${copy.version}` : ''}`);
|
|
416
|
+
console.log(keep === copies[0]
|
|
417
|
+
? ` Keep that one and remove the rest, so there is one to keep up to date:`
|
|
418
|
+
: ` The one that runs is not the newest. Remove the others, and ${bold(`v${keep.version}`)} is what ${bold('nevela')} runs:`);
|
|
419
|
+
const launcher = (dir) => {
|
|
420
|
+
try {
|
|
421
|
+
return fs.readFileSync(launcherIn(dir), 'utf8').slice(0, 4000);
|
|
422
|
+
} catch {
|
|
423
|
+
return '';
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
const removals = new Set(copies.filter((copy) => copy !== keep).map((copy) => removeCommand(copy.dir, launcher(copy.dir))));
|
|
427
|
+
for (const removal of removals) console.log(` ${bold(removal)}`);
|
|
383
428
|
}
|
|
384
429
|
|
|
385
430
|
/** The version of Nevela an app has installed, read from the package, or null. */
|
|
@@ -395,7 +440,7 @@ function appVersion(project) {
|
|
|
395
440
|
/** `nevela version`, from anywhere: the command's version, and the app's when in one. */
|
|
396
441
|
function showVersion() {
|
|
397
442
|
const project = findProject(process.cwd());
|
|
398
|
-
const install =
|
|
443
|
+
const install = installed();
|
|
399
444
|
console.log(`\n ${indigo(bold('Nevela command'))} ${bold(`v${VERSION}`)}${install ? ` ${dim(`installed with ${install.manager}`)}` : ''}`);
|
|
400
445
|
if (project) {
|
|
401
446
|
const app = appVersion(project);
|
|
@@ -459,7 +504,7 @@ async function askName() {
|
|
|
459
504
|
*/
|
|
460
505
|
async function updateTool(args) {
|
|
461
506
|
const project = findProject(process.cwd());
|
|
462
|
-
const install =
|
|
507
|
+
const install = installed();
|
|
463
508
|
|
|
464
509
|
if (!install) {
|
|
465
510
|
// Run through npx or `pnpm dlx`, which fetch the latest every time, started by an
|
|
@@ -485,18 +530,29 @@ async function updateTool(args) {
|
|
|
485
530
|
if (latest === null) {
|
|
486
531
|
// Not the same as being up to date, so it isn't reported as that.
|
|
487
532
|
console.log(` ${red('!')} Couldn't reach npm to see whether there is a newer nevela than ${bold(VERSION)}. Nothing was changed.`);
|
|
488
|
-
console.log(` Try again when you are online, or run: ${install.command.join(' ')}`);
|
|
533
|
+
console.log(` Try again when you are online, or run: ${install.command().join(' ')}`);
|
|
489
534
|
process.exitCode = 1;
|
|
490
535
|
} else if (!newer) {
|
|
491
536
|
console.log(` ${green(`✔ Already on the latest version (v${VERSION}). Nothing to do.`)}`);
|
|
492
537
|
} else {
|
|
493
|
-
|
|
494
|
-
const [command, ...rest] = install.command;
|
|
538
|
+
// The version by number, not "latest": pnpm holds "latest" back for a day.
|
|
539
|
+
const [command, ...rest] = install.command(newer);
|
|
540
|
+
console.log(` Updating v${VERSION} → ${bold(`v${newer}`)} ${dim(`(${[command, ...rest].join(' ')})`)}\n`);
|
|
495
541
|
const result = spawnCommand(command, rest, { stdio: 'inherit' });
|
|
496
542
|
if (result.error || result.status !== 0) {
|
|
497
|
-
fail(`Couldn't update the nevela command.`, `Run it yourself to see why: ${
|
|
543
|
+
fail(`Couldn't update the nevela command.`, `Run it yourself to see why: ${[command, ...rest].join(' ')}`);
|
|
544
|
+
}
|
|
545
|
+
// The package manager saying so isn't the same as the command being newer: ask it.
|
|
546
|
+
const now = versionOnPath();
|
|
547
|
+
if (now === newer || now === null) {
|
|
548
|
+
console.log(`\n ${green(`✔ Updated to v${newer}.`)} ${dim('Every nevela command, and every app you create from now on, uses it.')}`);
|
|
549
|
+
} else {
|
|
550
|
+
console.log(`\n ${red('!')} ${install.manager} installed ${bold(`v${newer}`)}, but typing ${bold('nevela')} still runs ${bold(`v${now}`)}.`);
|
|
551
|
+
if (copiesOnPath().length < 2) {
|
|
552
|
+
console.log(` Open a new terminal and run ${bold('nevela version')}. If it still says v${now}, the copy that runs was installed another way.`);
|
|
553
|
+
}
|
|
554
|
+
process.exitCode = 1;
|
|
498
555
|
}
|
|
499
|
-
console.log(`\n ${green(`✔ Updated to v${newer}.`)} ${dim('Every nevela command, and every app you create from now on, uses it.')}`);
|
|
500
556
|
}
|
|
501
557
|
otherCopies();
|
|
502
558
|
if (project) {
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ use Nevela\Laravel\Support\Naming;
|
|
|
18
18
|
final class Nevela
|
|
19
19
|
{
|
|
20
20
|
/** Written by `pnpm release`; the same number as the repository's git tag. */
|
|
21
|
-
public const VERSION = '0.4.
|
|
21
|
+
public const VERSION = '0.4.3';
|
|
22
22
|
|
|
23
23
|
/** @var array<string, Descriptor> */
|
|
24
24
|
private static array $descriptors = [];
|
package/tool.mjs
CHANGED
|
@@ -6,43 +6,64 @@
|
|
|
6
6
|
// nevela upgrade the app you are in: its Nevela package and its dashboard
|
|
7
7
|
//
|
|
8
8
|
// Updating the command means asking the package manager that installed it to install the
|
|
9
|
-
//
|
|
9
|
+
// newest version. Which one that was is read from where this file is running from.
|
|
10
10
|
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* The command that installs create-nevela globally, for each package manager.
|
|
15
|
+
*
|
|
16
|
+
* `nevela update` names the exact version it found on npm, not "latest": pnpm holds a
|
|
17
|
+
* version back for a day after it is published, so `pnpm add -g create-nevela@latest`
|
|
18
|
+
* installs yesterday's. Asked for a version by number, it installs that one.
|
|
19
|
+
*/
|
|
14
20
|
const INSTALL = {
|
|
15
|
-
npm: ['npm', 'install', '-g',
|
|
16
|
-
pnpm: ['pnpm', 'add', '-g',
|
|
17
|
-
yarn: ['yarn', 'global', 'add',
|
|
18
|
-
bun: ['bun', 'add', '-g',
|
|
21
|
+
npm: (version) => ['npm', 'install', '-g', `create-nevela@${version}`],
|
|
22
|
+
pnpm: (version) => ['pnpm', 'add', '-g', `create-nevela@${version}`],
|
|
23
|
+
yarn: (version) => ['yarn', 'global', 'add', `create-nevela@${version}`],
|
|
24
|
+
bun: (version) => ['bun', 'add', '-g', `create-nevela@${version}`],
|
|
19
25
|
};
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
|
-
* How the running copy was installed, from the folder it is in
|
|
28
|
+
* How the running copy was installed, from the folder it is in and the folder it was
|
|
29
|
+
* started through.
|
|
30
|
+
*
|
|
31
|
+
* Those two differ under pnpm 11, which keeps the files of every package in its store and
|
|
32
|
+
* leaves a link to them in its global folder. Node reports the store; the command was
|
|
33
|
+
* started through the global folder. Going by the store alone, a pnpm install looked like
|
|
34
|
+
* an npm one, and `nevela update` updated a copy that was not the one running.
|
|
23
35
|
*
|
|
24
|
-
* Returns the package manager and
|
|
36
|
+
* Returns the package manager and `command(version)`, which updates it, or null when this copy
|
|
25
37
|
* isn't the installed command: it was fetched for a single run (npx, pnpm dlx, a
|
|
26
38
|
* self-update), it is the repository itself, or it is a dependency in some project's own
|
|
27
39
|
* node_modules. Updating the global command would not change the copy that is running in
|
|
28
40
|
* any of those cases.
|
|
29
41
|
*
|
|
30
|
-
* @param {string} dir The folder this package
|
|
42
|
+
* @param {string} dir The folder this package's files are in
|
|
31
43
|
* @param {(file: string) => boolean} exists Whether a file exists; replaceable in tests
|
|
44
|
+
* @param {string} startedFrom The folder it was started through, when that is another
|
|
32
45
|
*/
|
|
33
|
-
export function globalInstall(dir, exists = fs.existsSync) {
|
|
34
|
-
const
|
|
46
|
+
export function globalInstall(dir, exists = fs.existsSync, startedFrom = dir) {
|
|
47
|
+
const tidy = (folder) => folder.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
48
|
+
const plain = tidy(dir);
|
|
35
49
|
const where = plain.toLowerCase();
|
|
50
|
+
const started = tidy(startedFrom).toLowerCase();
|
|
51
|
+
const found = (manager) => ({ manager, command: (version = 'latest') => INSTALL[manager](version) });
|
|
36
52
|
|
|
37
53
|
// Fetched for one run and thrown away afterwards.
|
|
38
|
-
if (/\/_npx\/|\/dlx[-/]|\/\.?bunx-|\/xfs-[0-9a-f]+\/|\/create-nevela-\d[^/]*\/package$/.test(
|
|
54
|
+
if ([where, started].some((folder) => /\/_npx\/|\/dlx[-/]|\/\.?bunx-|\/xfs-[0-9a-f]+\/|\/create-nevela-\d[^/]*\/package$/.test(folder))) return null;
|
|
39
55
|
// Installed, as opposed to checked out: it sits in somebody's node_modules.
|
|
40
56
|
if (!/\/node_modules\/create-nevela$/.test(where)) return null;
|
|
41
57
|
|
|
42
58
|
// pnpm, yarn and bun each keep global packages in a folder of their own.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
for (const folder of [started, where]) {
|
|
60
|
+
if (!/\/node_modules\/create-nevela$/.test(folder)) continue;
|
|
61
|
+
if (/\/pnpm\/global\/|\/global\/v?\d+\/([^/]+\/)?node_modules\/create-nevela$/.test(folder)) return found('pnpm');
|
|
62
|
+
if (/\/\.bun\/install\/global\//.test(folder)) return found('bun');
|
|
63
|
+
if (/\/yarn\/(data\/)?global\/|\/\.config\/yarn\/global\//.test(folder)) return found('yarn');
|
|
64
|
+
}
|
|
65
|
+
// In pnpm's store, and not reached through its global folder: a project's dependency.
|
|
66
|
+
if (/\/store\/v?\d+\/links\//.test(where)) return null;
|
|
46
67
|
|
|
47
68
|
// npm's global folder is a plain node_modules, like a project's. Two things tell them
|
|
48
69
|
// apart: a project has a package.json beside its node_modules, and a dependency of a
|
|
@@ -51,7 +72,7 @@ export function globalInstall(dir, exists = fs.existsSync) {
|
|
|
51
72
|
const owner = plain.slice(0, where.lastIndexOf('/node_modules/'));
|
|
52
73
|
if (exists(`${owner}/package.json`)) return null;
|
|
53
74
|
|
|
54
|
-
return
|
|
75
|
+
return found('npm');
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
/**
|
|
@@ -90,14 +111,18 @@ export function copiesOnPath(env = process.env, { exists = fs.existsSync, real =
|
|
|
90
111
|
return found;
|
|
91
112
|
}
|
|
92
113
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
114
|
+
/**
|
|
115
|
+
* What removes the copy whose command is in `dir`, judged by the folder and by the
|
|
116
|
+
* launcher in it: a small script that names the folder the package itself is in.
|
|
117
|
+
*/
|
|
118
|
+
export function removeCommand(dir, launcher = '') {
|
|
95
119
|
const where = dir.replace(/\\/g, '/').toLowerCase();
|
|
96
|
-
|
|
120
|
+
const points = launcher.replace(/\\/g, '/').toLowerCase();
|
|
121
|
+
if (/\/pnpm(\/|$)/.test(where) || /\/global\/v?\d+\/([^/]+\/)?node_modules\/create-nevela\//.test(points)) return 'pnpm remove -g create-nevela';
|
|
97
122
|
if (/\/\.bun\//.test(where)) return 'bun remove -g create-nevela';
|
|
98
123
|
if (/\/yarn\/|\/\.yarn\//.test(where)) return 'yarn global remove create-nevela';
|
|
99
124
|
return 'npm uninstall -g create-nevela';
|
|
100
125
|
}
|
|
101
126
|
|
|
102
127
|
/** What to type to get the `nevela` command, for someone who doesn't have it yet. */
|
|
103
|
-
export const INSTALL_COMMAND =
|
|
128
|
+
export const INSTALL_COMMAND = 'npm install -g create-nevela';
|