@selvajs/cli 4.7.4-beta.0 → 4.8.0-beta.10
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 +2 -2
- package/package.json +7 -4
- package/src/checks/boot.js +149 -0
- package/src/checks/config.js +97 -0
- package/src/checks/result.js +27 -0
- package/src/cli.js +1 -1
- package/src/commands/create.js +38 -55
- package/src/commands/doctor.js +143 -145
- package/src/commands/init.js +2 -20
- package/src/commands/keys.js +3 -17
- package/src/commands/migrate.js +169 -98
- package/src/commands/pm2.js +50 -5
- package/src/deployment-package.js +102 -0
- package/src/env.js +65 -0
- package/src/node-range.js +66 -0
- package/src/paths.js +66 -2
- package/src/prompts.js +0 -2
- package/bin/create.js +0 -7
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Node version-range matching, used by `selva doctor` to check an installed
|
|
2
|
+
// runtime and by the scaffolder to refuse a too-old shell before writing files.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Narrow `engines.node` range check — `>=X`, `^`, `~`, `||`, and bare/x-ranges.
|
|
6
|
+
* Returns null when unparseable so a misread never reports a false failure.
|
|
7
|
+
*
|
|
8
|
+
* Mirrors `satisfiesRange` in @selvajs/server/ops. Duplicated because the CLI is
|
|
9
|
+
* dependency-free by design (it scaffolds the deployment that installs the
|
|
10
|
+
* runtime, so it cannot import from it).
|
|
11
|
+
*/
|
|
12
|
+
export function satisfiesNodeRange(version, range) {
|
|
13
|
+
const core = /^v?(\d+)\.(\d+)\.(\d+)/.exec(String(version).trim());
|
|
14
|
+
if (!core) return null;
|
|
15
|
+
const v = [Number(core[1]), Number(core[2]), Number(core[3])];
|
|
16
|
+
|
|
17
|
+
let anyParsed = false;
|
|
18
|
+
for (const alt of String(range).split('||')) {
|
|
19
|
+
const clauses = alt.trim().split(/\s+/).filter(Boolean);
|
|
20
|
+
if (!clauses.length) continue;
|
|
21
|
+
|
|
22
|
+
let all = true;
|
|
23
|
+
let parsed = true;
|
|
24
|
+
for (const clause of clauses) {
|
|
25
|
+
const m = /^(>=|<=|>|<|\^|~|=)?\s*v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?/.exec(clause);
|
|
26
|
+
if (!m) {
|
|
27
|
+
parsed = false;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const op = m[1] ?? '=';
|
|
31
|
+
const num = (s) => (s == null || s === 'x' || s === '*' ? 0 : Number(s));
|
|
32
|
+
const t = [Number(m[2]), num(m[3]), num(m[4])];
|
|
33
|
+
let cmp = 0;
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
if (v[i] > t[i]) {
|
|
36
|
+
cmp = 1;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (v[i] < t[i]) {
|
|
40
|
+
cmp = -1;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let ok;
|
|
45
|
+
if (op === '>=') ok = cmp >= 0;
|
|
46
|
+
else if (op === '>') ok = cmp > 0;
|
|
47
|
+
else if (op === '<=') ok = cmp <= 0;
|
|
48
|
+
else if (op === '<') ok = cmp < 0;
|
|
49
|
+
else if (op === '^') ok = cmp >= 0 && v[0] === t[0];
|
|
50
|
+
else if (op === '~') ok = cmp >= 0 && v[0] === t[0] && v[1] === t[1];
|
|
51
|
+
else
|
|
52
|
+
ok =
|
|
53
|
+
v[0] === t[0] &&
|
|
54
|
+
(m[3] == null || m[3] === 'x' || m[3] === '*' || v[1] === t[1]) &&
|
|
55
|
+
(m[4] == null || m[4] === 'x' || m[4] === '*' || v[2] === t[2]);
|
|
56
|
+
if (!ok) {
|
|
57
|
+
all = false;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!parsed) continue;
|
|
62
|
+
anyParsed = true;
|
|
63
|
+
if (all) return true;
|
|
64
|
+
}
|
|
65
|
+
return anyParsed ? false : null;
|
|
66
|
+
}
|
package/src/paths.js
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
-
import { resolve, join } from 'node:path';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve, join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The Node floor Selva requires, read from this CLI's own `engines.node`.
|
|
7
|
+
*
|
|
8
|
+
* Never hardcode it: the CLI, runtime, and server all declare the same range in
|
|
9
|
+
* package.json, and a copy in code silently keeps enforcing the old floor after
|
|
10
|
+
* a bump. Falls back to null (skip the check) rather than guessing a number —
|
|
11
|
+
* refusing to scaffold on a wrong guess is worse than not checking.
|
|
12
|
+
*/
|
|
13
|
+
export function requiredNodeRange() {
|
|
14
|
+
try {
|
|
15
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
17
|
+
const range = pkg.engines?.node;
|
|
18
|
+
return typeof range === 'string' ? range : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This CLI's own version, written into `.selva-version` so a later `selva
|
|
26
|
+
* migrate` can tell which scaffold layout it is looking at.
|
|
27
|
+
*
|
|
28
|
+
* Read from package.json for the same reason as `requiredNodeRange`: a literal
|
|
29
|
+
* in code stops tracking the release the moment someone forgets to bump it, and
|
|
30
|
+
* the marker's whole job is to be accurate about which CLI wrote the directory.
|
|
31
|
+
*/
|
|
32
|
+
export function scaffoldVersion() {
|
|
33
|
+
try {
|
|
34
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
36
|
+
return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
|
37
|
+
} catch {
|
|
38
|
+
return '0.0.0';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A scaffolding template shipped inside the installed runtime. The CLI cannot
|
|
44
|
+
* import from `@selvajs/selva` — it is what installs it — so it reads the files
|
|
45
|
+
* out of node_modules after install.
|
|
46
|
+
*/
|
|
47
|
+
export function runtimeTemplatePath(dir, name) {
|
|
48
|
+
return join(dir, 'node_modules', '@selvajs', 'selva', 'templates', name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The `.env` template to merge operator values into: the runtime's `.env.example`
|
|
53
|
+
* when installed, otherwise the deployment's own `.env` so its comments and
|
|
54
|
+
* ordering survive a rewrite.
|
|
55
|
+
*
|
|
56
|
+
* Returns `''` when neither exists rather than throwing — `mergeEnv` appends
|
|
57
|
+
* every value to an empty template, so the operator gets a usable file instead
|
|
58
|
+
* of an ENOENT from a command that was only asked to change one key.
|
|
59
|
+
*/
|
|
60
|
+
export function readEnvTemplate(dir) {
|
|
61
|
+
const template = runtimeTemplatePath(dir, '.env.example');
|
|
62
|
+
if (existsSync(template)) return readFileSync(template, 'utf8');
|
|
63
|
+
const env = join(dir, '.env');
|
|
64
|
+
if (existsSync(env)) return readFileSync(env, 'utf8');
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
3
67
|
|
|
4
68
|
// A deployment directory must contain an .env and an ecosystem.config.cjs.
|
|
5
69
|
// Anything else (selva.config.js, node_modules) is "should be there" but the
|
package/src/prompts.js
CHANGED