tfv 5.0.1 → 6.1.0
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/.github/workflows/publish.yml +43 -3
- package/README.md +253 -122
- package/demo.gif +0 -0
- package/demo.tape +230 -0
- package/index.js +0 -4
- package/lib/commands/apply.js +8 -3
- package/lib/commands/current.js +25 -0
- package/lib/commands/destroy.js +8 -3
- package/lib/commands/doctor.js +20 -0
- package/lib/commands/exec.js +33 -0
- package/lib/commands/fmt.js +26 -0
- package/lib/commands/init.js +26 -0
- package/lib/commands/install.js +22 -13
- package/lib/commands/list.js +20 -11
- package/lib/commands/pin.js +26 -0
- package/lib/commands/plan.js +8 -3
- package/lib/commands/prune.js +41 -0
- package/lib/commands/remove.js +17 -12
- package/lib/commands/shell-init.js +25 -0
- package/lib/commands/switch.js +28 -7
- package/lib/commands/upgrade.js +26 -0
- package/lib/commands/use.js +17 -13
- package/lib/commands/validate.js +21 -0
- package/lib/modules/current.js +52 -0
- package/lib/modules/doctor.js +160 -0
- package/lib/modules/exec.js +36 -0
- package/lib/modules/install.js +155 -89
- package/lib/modules/list.js +66 -105
- package/lib/modules/pin.js +35 -0
- package/lib/modules/prune.js +100 -0
- package/lib/modules/ps1.js +37 -29
- package/lib/modules/remote.js +68 -15
- package/lib/modules/remove.js +35 -21
- package/lib/modules/shell-init.js +226 -0
- package/lib/modules/switch.js +125 -41
- package/lib/modules/terraform-command.js +49 -67
- package/lib/modules/upgrade.js +93 -0
- package/lib/modules/use.js +58 -54
- package/lib/utils/formatVersions.js +57 -5
- package/lib/utils/paths.js +156 -0
- package/lib/utils/postInstall.js +37 -13
- package/lib/utils/store.js +17 -6
- package/package.json +11 -9
- package/test/extractTargets.test.js +75 -0
- package/test/formatVersions.test.js +126 -0
- package/test/moduleImports.test.js +45 -0
- package/test/paths.test.js +69 -0
- package/test/versionResolution.test.js +92 -0
package/lib/commands/switch.js
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {autoSwitch} = require('../modules/switch');
|
|
3
|
+
const { autoSwitch } = require('../modules/switch');
|
|
4
4
|
|
|
5
5
|
exports.command = 'auto-switch'
|
|
6
6
|
exports.aliases = ['as']
|
|
7
7
|
exports.desc = 'Auto-detect and switch to your project terraform version'
|
|
8
8
|
exports.builder = (yargs) => {
|
|
9
9
|
return yargs
|
|
10
|
-
.option('
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
.option('provider', {
|
|
11
|
+
alias: 'p',
|
|
12
|
+
describe: 'Provider: terraform (default) or tofu/opentofu',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: 'terraform',
|
|
13
15
|
})
|
|
14
|
-
.
|
|
16
|
+
.option('silent', {
|
|
17
|
+
alias: 's',
|
|
18
|
+
describe: 'Suppress output (used by shell hooks)',
|
|
19
|
+
type: 'boolean',
|
|
20
|
+
default: false,
|
|
21
|
+
})
|
|
22
|
+
.epilog([
|
|
23
|
+
'Reads version from (in priority order):',
|
|
24
|
+
' 1. .terraform-version file',
|
|
25
|
+
' 2. terraform.tfstate',
|
|
26
|
+
' 3. required_version in .tf files',
|
|
27
|
+
'Examples:',
|
|
28
|
+
' tfv auto-switch',
|
|
29
|
+
' tfv as --provider tofu',
|
|
30
|
+
].join('\n'))
|
|
15
31
|
}
|
|
16
32
|
|
|
17
|
-
exports.handler = async () => {
|
|
18
|
-
|
|
33
|
+
exports.handler = async (argv) => {
|
|
34
|
+
const { silent, provider } = argv;
|
|
35
|
+
if (silent) {
|
|
36
|
+
console.log = () => {};
|
|
37
|
+
console.warn = () => {};
|
|
38
|
+
}
|
|
39
|
+
await autoSwitch({ silent }, provider);
|
|
19
40
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { upgrade } = require('../modules/upgrade');
|
|
4
|
+
|
|
5
|
+
exports.command = 'upgrade [ver]'
|
|
6
|
+
exports.desc = 'Upgrade to the latest patch version in the active (or given) series'
|
|
7
|
+
exports.builder = (yargs) => {
|
|
8
|
+
return yargs
|
|
9
|
+
.option('provider', {
|
|
10
|
+
alias: 'p',
|
|
11
|
+
describe: 'Provider: terraform (default) or tofu/opentofu',
|
|
12
|
+
type: 'string',
|
|
13
|
+
default: 'terraform',
|
|
14
|
+
})
|
|
15
|
+
.epilog([
|
|
16
|
+
'Examples:',
|
|
17
|
+
' tfv upgrade Upgrade active 1.6.x → latest 1.6.x patch',
|
|
18
|
+
' tfv upgrade 1.8 Install + use latest 1.8.x',
|
|
19
|
+
' tfv upgrade latest Install + use absolute latest version',
|
|
20
|
+
' tfv upgrade --provider tofu',
|
|
21
|
+
].join('\n'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
exports.handler = async (argv) => {
|
|
25
|
+
await upgrade(argv.ver, argv.provider);
|
|
26
|
+
}
|
package/lib/commands/use.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {use} = require('../modules/use');
|
|
3
|
+
const { use } = require('../modules/use');
|
|
5
4
|
|
|
6
|
-
exports.command = 'use <
|
|
7
|
-
exports.desc = 'Switch to a specified terraform version
|
|
5
|
+
exports.command = 'use <ver>'
|
|
6
|
+
exports.desc = 'Switch to a specified terraform or opentofu version'
|
|
8
7
|
exports.builder = (yargs) => {
|
|
9
8
|
return yargs
|
|
10
|
-
.option('
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
.option('provider', {
|
|
10
|
+
alias: 'p',
|
|
11
|
+
describe: 'Provider: terraform (default) or tofu/opentofu',
|
|
12
|
+
type: 'string',
|
|
13
|
+
default: 'terraform',
|
|
13
14
|
})
|
|
14
|
-
.epilog(
|
|
15
|
+
.epilog([
|
|
16
|
+
'Examples:',
|
|
17
|
+
' tfv use 1.7.3',
|
|
18
|
+
' tfv use latest',
|
|
19
|
+
' tfv use 1.7.3 --provider tofu',
|
|
20
|
+
].join('\n'))
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
exports.handler = async () => {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
await use(version);
|
|
23
|
+
exports.handler = async (argv) => {
|
|
24
|
+
const { ver, provider } = argv;
|
|
25
|
+
await use(ver, provider);
|
|
21
26
|
}
|
|
22
|
-
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { runTerraformCommand } = require('../modules/terraform-command');
|
|
4
|
+
|
|
5
|
+
exports.command = 'validate'
|
|
6
|
+
exports.desc = 'Run terraform validate. Accepts all terraform flags after --'
|
|
7
|
+
exports.builder = (yargs) => {
|
|
8
|
+
return yargs
|
|
9
|
+
.option('provider', {
|
|
10
|
+
alias: 'p',
|
|
11
|
+
describe: 'Provider: terraform (default) or tofu/opentofu',
|
|
12
|
+
type: 'string',
|
|
13
|
+
default: 'terraform',
|
|
14
|
+
})
|
|
15
|
+
.epilog('Example: tfv validate -- -json')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.handler = async (argv) => {
|
|
19
|
+
const extraArgs = argv._.slice(1);
|
|
20
|
+
await runTerraformCommand('validate', null, extraArgs, argv.provider);
|
|
21
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { existsSync, readFileSync } = require('fs');
|
|
2
|
+
const { spawnSync } = require('child_process');
|
|
3
|
+
const { normalizeProvider, getCliName, getPaths, getBinTarget, checkPathConflict } = require('../utils/paths');
|
|
4
|
+
const { P_END, P_OK, P_INFO, P_WARN, P_ERROR } = require('../utils/colors');
|
|
5
|
+
|
|
6
|
+
exports.current = async (providerArg = 'terraform') => {
|
|
7
|
+
try {
|
|
8
|
+
const provider = normalizeProvider(providerArg);
|
|
9
|
+
const paths = getPaths();
|
|
10
|
+
|
|
11
|
+
let activeVersion = null;
|
|
12
|
+
if (existsSync(paths.active)) {
|
|
13
|
+
const active = JSON.parse(readFileSync(paths.active, 'utf-8'));
|
|
14
|
+
activeVersion = active[provider];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!activeVersion) {
|
|
18
|
+
console.log(`${P_WARN}No active ${provider} version set.${P_END}`);
|
|
19
|
+
console.log(`Run: ${P_OK}tfv use <version>${providerArg === 'terraform' ? '' : ` --provider ${providerArg}`}${P_END}`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Confirm binary is present and get its real version output
|
|
24
|
+
const binary = getBinTarget(provider);
|
|
25
|
+
const cliName = getCliName(provider);
|
|
26
|
+
|
|
27
|
+
console.log(`${P_INFO}Active ${provider} version: ${P_OK}${activeVersion}${P_END}`);
|
|
28
|
+
console.log(`${P_INFO}Binary: ${binary}${P_END}`);
|
|
29
|
+
|
|
30
|
+
if (existsSync(binary)) {
|
|
31
|
+
const result = spawnSync(binary, ['version'], { stdio: 'pipe', encoding: 'utf-8' });
|
|
32
|
+
if (result.stdout) {
|
|
33
|
+
const firstLine = result.stdout.split('\n')[0];
|
|
34
|
+
console.log(`${P_INFO}Reported: ${firstLine}${P_END}`);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`${P_WARN}Binary not found at ${binary}. Re-run: tfv use ${activeVersion}${providerArg === 'terraform' ? '' : ` --provider ${providerArg}`}${P_END}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if a conflicting installation shadows tfv's binary
|
|
41
|
+
const conflict = checkPathConflict(provider);
|
|
42
|
+
if (conflict.ok) {
|
|
43
|
+
console.log(`${P_OK}PATH OK — '${cliName}' resolves to tfv-managed binary${P_END}`);
|
|
44
|
+
} else {
|
|
45
|
+
console.log(`${P_WARN}PATH CONFLICT — '${cliName}' resolves to: ${conflict.resolved}${P_END}`);
|
|
46
|
+
console.log(`${P_WARN}Expected: ${conflict.expected}${P_END}`);
|
|
47
|
+
console.log(`${P_INFO}Run ${P_OK}tfv upgrade${P_END}${P_INFO} to re-anchor the PATH automatically.${P_END}`);
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.log(`${P_ERROR}Error: ${err.message}${P_END}`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const { existsSync, readFileSync, readdirSync } = require('fs');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const { platform, homedir } = require('os');
|
|
5
|
+
const { getPaths, getBinTarget, getProviderStore, getCliName, normalizeProvider, checkPathConflict } = require('../utils/paths');
|
|
6
|
+
const { P_END, P_OK, P_ERROR, P_WARN, P_INFO } = require('../utils/colors');
|
|
7
|
+
|
|
8
|
+
const SHELL_CONFIGS = [
|
|
9
|
+
join(homedir(), '.zshrc'),
|
|
10
|
+
join(homedir(), '.bashrc'),
|
|
11
|
+
join(homedir(), '.bash_profile'),
|
|
12
|
+
join(homedir(), '.profile'),
|
|
13
|
+
];
|
|
14
|
+
const PATH_MARKER = '# tfv - terraform version manager';
|
|
15
|
+
|
|
16
|
+
const ok = (label, detail) => { console.log(` ${P_OK}✔${P_END} ${label}`); if (detail) console.log(` ${P_INFO}${detail}${P_END}`); };
|
|
17
|
+
const fail = (label, hint) => { console.log(` ${P_ERROR}✘${P_END} ${label}`); if (hint) console.log(` ${P_WARN}${hint}${P_END}`); };
|
|
18
|
+
const info = (label, detail) => { console.log(` ${P_INFO}–${P_END} ${label}`); if (detail) console.log(` ${P_INFO}${detail}${P_END}`); };
|
|
19
|
+
|
|
20
|
+
exports.doctor = async () => {
|
|
21
|
+
const paths = getPaths();
|
|
22
|
+
const isWin = platform() === 'win32';
|
|
23
|
+
let issues = 0;
|
|
24
|
+
|
|
25
|
+
console.log(`\n${P_INFO}tfv doctor${P_END}\n`);
|
|
26
|
+
|
|
27
|
+
// ── Store ──────────────────────────────────────────────────────────────────
|
|
28
|
+
console.log(`${P_OK}Store${P_END}`);
|
|
29
|
+
for (const p of ['terraform', 'opentofu']) {
|
|
30
|
+
const store = getProviderStore(p);
|
|
31
|
+
if (existsSync(store)) {
|
|
32
|
+
ok(`~/.tfv/store/${p}/ exists`);
|
|
33
|
+
} else {
|
|
34
|
+
fail(`~/.tfv/store/${p}/ missing`, 'Run: tfv install latest' + (p === 'opentofu' ? ' --provider tofu' : ''));
|
|
35
|
+
issues++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Active versions & binaries ─────────────────────────────────────────────
|
|
40
|
+
console.log(`\n${P_OK}Active versions${P_END}`);
|
|
41
|
+
let activeData = { terraform: null, opentofu: null };
|
|
42
|
+
if (existsSync(paths.active)) {
|
|
43
|
+
try { activeData = JSON.parse(readFileSync(paths.active, 'utf-8')); } catch {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const p of ['terraform', 'opentofu']) {
|
|
47
|
+
const cli = getCliName(p);
|
|
48
|
+
const binary = getBinTarget(p);
|
|
49
|
+
const active = activeData[p];
|
|
50
|
+
|
|
51
|
+
const store = getProviderStore(p);
|
|
52
|
+
const hasInstalled = existsSync(store) &&
|
|
53
|
+
readdirSync(store).filter(f => f !== 'arch.json').length > 0;
|
|
54
|
+
|
|
55
|
+
if (!active) {
|
|
56
|
+
if (hasInstalled) {
|
|
57
|
+
fail(`${cli}: no active version set`, `Run: tfv use <version>${p === 'opentofu' ? ' --provider tofu' : ''}`);
|
|
58
|
+
issues++;
|
|
59
|
+
} else {
|
|
60
|
+
info(`${cli}: not set up (no versions installed)`);
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
ok(`${cli}: active version is ${active}`);
|
|
66
|
+
|
|
67
|
+
if (!existsSync(binary)) {
|
|
68
|
+
fail(`${cli}: binary missing at ${binary}`, `Run: tfv use ${active}${p === 'opentofu' ? ' --provider tofu' : ''}`);
|
|
69
|
+
issues++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
ok(`${cli}: binary exists`);
|
|
74
|
+
|
|
75
|
+
const result = spawnSync(binary, ['version'], { stdio: 'pipe', encoding: 'utf-8' });
|
|
76
|
+
if (result.status === 0 && result.stdout) {
|
|
77
|
+
ok(`${cli}: executes (${result.stdout.split('\n')[0].trim()})`);
|
|
78
|
+
} else {
|
|
79
|
+
fail(`${cli}: binary failed to execute`, result.stderr ? result.stderr.trim() : 'Unknown error');
|
|
80
|
+
issues++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── PATH ──────────────────────────────────────────────────────────────────
|
|
85
|
+
console.log(`\n${P_OK}PATH${P_END}`);
|
|
86
|
+
|
|
87
|
+
if (isWin) {
|
|
88
|
+
info('PATH check skipped on Windows (managed via User PATH registry key)');
|
|
89
|
+
} else {
|
|
90
|
+
const pathEntries = (process.env.PATH || '').split(':');
|
|
91
|
+
const tfvBin = paths.bin;
|
|
92
|
+
|
|
93
|
+
if (pathEntries.includes(tfvBin)) {
|
|
94
|
+
ok('~/.tfv/bin is in PATH');
|
|
95
|
+
|
|
96
|
+
const tfvIdx = pathEntries.indexOf(tfvBin);
|
|
97
|
+
const systemDirs = ['/usr/bin', '/usr/local/bin', '/opt/homebrew/bin'];
|
|
98
|
+
const systemIdxs = systemDirs
|
|
99
|
+
.map(d => pathEntries.indexOf(d))
|
|
100
|
+
.filter(i => i >= 0);
|
|
101
|
+
const firstSystem = systemIdxs.length ? Math.min(...systemIdxs) : Infinity;
|
|
102
|
+
|
|
103
|
+
if (firstSystem < Infinity) {
|
|
104
|
+
if (tfvIdx < firstSystem) {
|
|
105
|
+
ok('~/.tfv/bin precedes system dirs in PATH');
|
|
106
|
+
} else {
|
|
107
|
+
fail('~/.tfv/bin comes AFTER system dirs in PATH', 'Run: tfv upgrade to re-anchor PATH');
|
|
108
|
+
issues++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
fail('~/.tfv/bin is not in PATH', 'Run: tfv upgrade to re-anchor PATH');
|
|
113
|
+
issues++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const p of ['terraform', 'opentofu']) {
|
|
117
|
+
const active = activeData[p];
|
|
118
|
+
if (!active) continue;
|
|
119
|
+
const cli = getCliName(p);
|
|
120
|
+
const conflict = checkPathConflict(p);
|
|
121
|
+
if (conflict.ok) {
|
|
122
|
+
ok(`'${cli}' resolves to tfv-managed binary`);
|
|
123
|
+
} else {
|
|
124
|
+
fail(`'${cli}' resolves to wrong binary`, `Got: ${conflict.resolved} Expected: ${conflict.expected}`);
|
|
125
|
+
issues++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const configsWithMarker = SHELL_CONFIGS.filter(f => {
|
|
130
|
+
if (!existsSync(f)) return false;
|
|
131
|
+
try { return readFileSync(f, 'utf-8').includes(PATH_MARKER); } catch { return false; }
|
|
132
|
+
});
|
|
133
|
+
if (configsWithMarker.length > 0) {
|
|
134
|
+
ok(`PATH block found in shell config`, configsWithMarker.map(f => f.replace(homedir(), '~')).join(', '));
|
|
135
|
+
} else {
|
|
136
|
+
fail('PATH block not found in any shell config', `Add: export PATH="$HOME/.tfv/bin:$PATH" to ~/.zshrc or ~/.bashrc`);
|
|
137
|
+
issues++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Cache ─────────────────────────────────────────────────────────────────
|
|
142
|
+
console.log(`\n${P_OK}Cache${P_END}`);
|
|
143
|
+
for (const p of ['terraform', 'opentofu']) {
|
|
144
|
+
const cacheFile = join(paths.cache, `${p}-versions.json`);
|
|
145
|
+
if (existsSync(cacheFile)) {
|
|
146
|
+
ok(`${p} version cache exists`);
|
|
147
|
+
} else {
|
|
148
|
+
info(`${p} version cache not yet created`, 'Will be built on next: tfv list --remote' + (p === 'opentofu' ? ' --provider tofu' : ''));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Summary ───────────────────────────────────────────────────────────────
|
|
153
|
+
console.log();
|
|
154
|
+
if (issues === 0) {
|
|
155
|
+
console.log(`${P_OK}All checks passed. tfv is healthy.${P_END}\n`);
|
|
156
|
+
} else {
|
|
157
|
+
console.log(`${P_ERROR}${issues} issue(s) found. See details above.${P_END}\n`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { existsSync } = require('fs');
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
const { normalizeProvider, getCliName, getVersionFile } = require('../utils/paths');
|
|
4
|
+
const { P_END, P_OK, P_ERROR, P_WARN, P_INFO } = require('../utils/colors');
|
|
5
|
+
|
|
6
|
+
exports.exec = async (version, extraArgs = [], providerArg = 'terraform') => {
|
|
7
|
+
try {
|
|
8
|
+
const provider = normalizeProvider(providerArg);
|
|
9
|
+
const cli = getCliName(provider);
|
|
10
|
+
const binary = getVersionFile(provider, version);
|
|
11
|
+
|
|
12
|
+
if (!existsSync(binary)) {
|
|
13
|
+
console.log(`${P_ERROR}${provider} ${version} is not installed.${P_END}`);
|
|
14
|
+
console.log(`Run: ${P_OK}tfv install ${version}${provider === 'opentofu' ? ' --provider tofu' : ''}${P_END}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(`${P_INFO}Using ${provider} ${version} (active version unchanged)${P_END}`);
|
|
19
|
+
if (extraArgs.length > 0) {
|
|
20
|
+
console.log(`${P_OK}Running: ${cli} ${extraArgs.join(' ')}${P_END}\n`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const tf = spawn(binary, extraArgs, { stdio: 'inherit', cwd: process.cwd() });
|
|
24
|
+
|
|
25
|
+
tf.on('error', (err) => {
|
|
26
|
+
console.log(`${P_ERROR}Error executing ${cli} ${version}: ${err.message}${P_END}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
tf.on('close', code => process.exit(code ?? 0));
|
|
31
|
+
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.log(`${P_ERROR}Error: ${err.message}${P_END}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
};
|