@ant.sh/colony 0.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/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/cjs/cli.js +281 -0
- package/dist/cjs/cli.js.map +7 -0
- package/dist/cjs/index.js +383 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parser.js +319 -0
- package/dist/cjs/parser.js.map +7 -0
- package/dist/cjs/providers/aws.js +115 -0
- package/dist/cjs/providers/aws.js.map +7 -0
- package/dist/cjs/providers/openbao.js +49 -0
- package/dist/cjs/providers/openbao.js.map +7 -0
- package/dist/cjs/providers/vault-base.js +98 -0
- package/dist/cjs/providers/vault-base.js.map +7 -0
- package/dist/cjs/providers/vault.js +49 -0
- package/dist/cjs/providers/vault.js.map +7 -0
- package/dist/cjs/resolver.js +247 -0
- package/dist/cjs/resolver.js.map +7 -0
- package/dist/cjs/secrets.js +238 -0
- package/dist/cjs/secrets.js.map +7 -0
- package/dist/cjs/strings.js +99 -0
- package/dist/cjs/strings.js.map +7 -0
- package/dist/cjs/util.js +74 -0
- package/dist/cjs/util.js.map +7 -0
- package/dist/esm/cli.js +281 -0
- package/dist/esm/cli.js.map +7 -0
- package/dist/esm/index.d.ts +342 -0
- package/dist/esm/index.js +347 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/parser.js +286 -0
- package/dist/esm/parser.js.map +7 -0
- package/dist/esm/providers/aws.js +82 -0
- package/dist/esm/providers/aws.js.map +7 -0
- package/dist/esm/providers/openbao.js +26 -0
- package/dist/esm/providers/openbao.js.map +7 -0
- package/dist/esm/providers/vault-base.js +75 -0
- package/dist/esm/providers/vault-base.js.map +7 -0
- package/dist/esm/providers/vault.js +26 -0
- package/dist/esm/providers/vault.js.map +7 -0
- package/dist/esm/resolver.js +224 -0
- package/dist/esm/resolver.js.map +7 -0
- package/dist/esm/secrets.js +209 -0
- package/dist/esm/secrets.js.map +7 -0
- package/dist/esm/strings.js +75 -0
- package/dist/esm/strings.js.map +7 -0
- package/dist/esm/util.js +47 -0
- package/dist/esm/util.js.map +7 -0
- package/package.json +66 -0
- package/src/cli.js +353 -0
- package/src/index.d.ts +342 -0
- package/src/index.js +473 -0
- package/src/parser.js +381 -0
- package/src/providers/aws.js +112 -0
- package/src/providers/openbao.js +32 -0
- package/src/providers/vault-base.js +92 -0
- package/src/providers/vault.js +31 -0
- package/src/resolver.js +286 -0
- package/src/secrets.js +313 -0
- package/src/strings.js +84 -0
- package/src/util.js +49 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ant.sh/colony",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scope-based config loader for Node.js with rules, interpolation, and multi-environment support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/esm/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/esm/index.d.ts",
|
|
12
|
+
"import": "./dist/esm/index.js",
|
|
13
|
+
"require": "./dist/cjs/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"colony": "./src/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/",
|
|
21
|
+
"src/*.js",
|
|
22
|
+
"src/*.d.ts",
|
|
23
|
+
"src/providers/",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "node build.js",
|
|
29
|
+
"build:playground": "node scripts/build-playground.js",
|
|
30
|
+
"test": "node --test test/*.test.js",
|
|
31
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
32
|
+
"lint": "node ./src/cli.js lint --entry ./examples/config/app.colony",
|
|
33
|
+
"prepublishOnly": "npm run build && npm test"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"config",
|
|
37
|
+
"configuration",
|
|
38
|
+
"settings",
|
|
39
|
+
"environment",
|
|
40
|
+
"scope-based",
|
|
41
|
+
"rules",
|
|
42
|
+
"interpolation",
|
|
43
|
+
"multi-environment",
|
|
44
|
+
"cli"
|
|
45
|
+
],
|
|
46
|
+
"author": "Zaxuhe",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/Zaxuhe/colony.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://colony.ant.sh",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/Zaxuhe/colony/issues"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"fast-glob": "^3.3.2",
|
|
61
|
+
"json5": "^2.2.3"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"esbuild": "^0.27.2"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadColony, validateColony, dryRunIncludes, diffColony, lintColony } from "./index.js";
|
|
3
|
+
import { getByPath } from "./util.js";
|
|
4
|
+
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const args = { _: [] };
|
|
7
|
+
for (let i = 2; i < argv.length; i++) {
|
|
8
|
+
const a = argv[i];
|
|
9
|
+
if (a.startsWith("--")) {
|
|
10
|
+
const k = a.slice(2);
|
|
11
|
+
const v = (i + 1 < argv.length && !argv[i + 1].startsWith("--")) ? argv[++i] : true;
|
|
12
|
+
args[k] = v;
|
|
13
|
+
} else {
|
|
14
|
+
args._.push(a);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return args;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseCtx(s) {
|
|
21
|
+
const out = {};
|
|
22
|
+
if (!s || s === true) return out;
|
|
23
|
+
const parts = String(s).split(/\s+/).filter(Boolean);
|
|
24
|
+
for (const p of parts) {
|
|
25
|
+
const idx = p.indexOf("=");
|
|
26
|
+
if (idx === -1) continue;
|
|
27
|
+
out[p.slice(0, idx)] = p.slice(idx + 1);
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function printUsage() {
|
|
33
|
+
console.error(`Usage:
|
|
34
|
+
colony print --entry ./config/app.colony [options]
|
|
35
|
+
colony validate --entry ./config/app.colony
|
|
36
|
+
colony dry-run --entry ./config/app.colony
|
|
37
|
+
colony diff --entry ./config/app.colony --ctx1 "env=dev" --ctx2 "env=prod"
|
|
38
|
+
colony keys --entry ./config/app.colony [--ctx "..."]
|
|
39
|
+
colony env --entry ./config/app.colony [--ctx "..."]
|
|
40
|
+
colony lint --entry ./config/app.colony
|
|
41
|
+
|
|
42
|
+
Commands:
|
|
43
|
+
print Resolve and print the configuration
|
|
44
|
+
validate Check syntax of all colony files without resolving
|
|
45
|
+
dry-run List all files that would be included
|
|
46
|
+
diff Compare configs between two contexts
|
|
47
|
+
keys List all config keys in dot notation
|
|
48
|
+
env Output config as KEY=value for shell sourcing
|
|
49
|
+
lint Check for potential issues (unused rules, shadows, etc.)
|
|
50
|
+
|
|
51
|
+
Options for 'print':
|
|
52
|
+
--entry <file> Entry colony file (required)
|
|
53
|
+
--dims <d1,d2,...> Dimension names (comma-separated)
|
|
54
|
+
--ctx "k1=v1 k2=v2" Context values (space-separated key=value pairs)
|
|
55
|
+
--format <json|env> Output format (default: json)
|
|
56
|
+
--query <key.path> Extract specific value (like jq)
|
|
57
|
+
--explain <key.path> Show which rule set a specific key
|
|
58
|
+
--base-path <dir> Restrict includes to this directory (security)
|
|
59
|
+
--allowed-env <v1,v2> Whitelist of allowed env vars (security)
|
|
60
|
+
--allowed-vars <v1,v2> Whitelist of allowed custom vars (security)
|
|
61
|
+
--max-file-size <bytes> Maximum file size for includes (security)
|
|
62
|
+
--warn-skipped Warn when skipping already-visited includes
|
|
63
|
+
--show-warnings Show all warnings after output
|
|
64
|
+
--strict Exit with error if there are any warnings
|
|
65
|
+
|
|
66
|
+
Options for 'diff':
|
|
67
|
+
--entry <file> Entry colony file (required)
|
|
68
|
+
--dims <d1,d2,...> Dimension names (comma-separated)
|
|
69
|
+
--ctx1 "k1=v1 ..." First context
|
|
70
|
+
--ctx2 "k1=v1 ..." Second context
|
|
71
|
+
--format <json|text> Output format (default: text)
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function formatDiff(diff, format = "text") {
|
|
76
|
+
if (format === "json") {
|
|
77
|
+
return JSON.stringify(diff, null, 2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const lines = [];
|
|
81
|
+
|
|
82
|
+
if (diff.added.length > 0) {
|
|
83
|
+
lines.push("Added:");
|
|
84
|
+
for (const key of diff.added) {
|
|
85
|
+
lines.push(` + ${key}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (diff.removed.length > 0) {
|
|
90
|
+
if (lines.length > 0) lines.push("");
|
|
91
|
+
lines.push("Removed:");
|
|
92
|
+
for (const key of diff.removed) {
|
|
93
|
+
lines.push(` - ${key}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (diff.changed.length > 0) {
|
|
98
|
+
if (lines.length > 0) lines.push("");
|
|
99
|
+
lines.push("Changed:");
|
|
100
|
+
for (const { key, from, to } of diff.changed) {
|
|
101
|
+
lines.push(` ~ ${key}`);
|
|
102
|
+
lines.push(` from: ${JSON.stringify(from)}`);
|
|
103
|
+
lines.push(` to: ${JSON.stringify(to)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (lines.length === 0) {
|
|
108
|
+
return "No differences found.";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return lines.join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Format config as KEY=value for shell sourcing
|
|
116
|
+
*/
|
|
117
|
+
function formatAsEnv(cfg, prefix = "") {
|
|
118
|
+
const lines = [];
|
|
119
|
+
|
|
120
|
+
function flatten(obj, currentPrefix) {
|
|
121
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
122
|
+
const envKey = currentPrefix
|
|
123
|
+
? `${currentPrefix}_${key}`.toUpperCase().replace(/[^A-Z0-9_]/g, "_")
|
|
124
|
+
: key.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
125
|
+
|
|
126
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
127
|
+
flatten(value, envKey);
|
|
128
|
+
} else {
|
|
129
|
+
const envValue = typeof value === "string"
|
|
130
|
+
? value
|
|
131
|
+
: JSON.stringify(value);
|
|
132
|
+
// Escape single quotes for shell
|
|
133
|
+
const escaped = envValue.replace(/'/g, "'\\''");
|
|
134
|
+
lines.push(`${envKey}='${escaped}'`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
flatten(cfg, prefix);
|
|
140
|
+
return lines.sort().join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
(async () => {
|
|
144
|
+
try {
|
|
145
|
+
const args = parseArgs(process.argv);
|
|
146
|
+
const cmd = args._[0];
|
|
147
|
+
|
|
148
|
+
if (!cmd || cmd === "help" || args.help) {
|
|
149
|
+
printUsage();
|
|
150
|
+
process.exit(cmd === "help" || args.help ? 0 : 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const entry = args.entry;
|
|
154
|
+
if (!entry) {
|
|
155
|
+
console.error("Error: Missing --entry\n");
|
|
156
|
+
printUsage();
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle validate command
|
|
161
|
+
if (cmd === "validate") {
|
|
162
|
+
const result = await validateColony(entry);
|
|
163
|
+
if (result.valid) {
|
|
164
|
+
console.log(`✓ All ${result.files.length} file(s) valid:`);
|
|
165
|
+
for (const f of result.files) {
|
|
166
|
+
console.log(` ${f}`);
|
|
167
|
+
}
|
|
168
|
+
process.exit(0);
|
|
169
|
+
} else {
|
|
170
|
+
console.error(`✗ Validation failed with ${result.errors.length} error(s):`);
|
|
171
|
+
for (const e of result.errors) {
|
|
172
|
+
console.error(`\n ${e.file}:`);
|
|
173
|
+
console.error(` ${e.error}`);
|
|
174
|
+
}
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle dry-run command
|
|
180
|
+
if (cmd === "dry-run") {
|
|
181
|
+
const files = await dryRunIncludes(entry);
|
|
182
|
+
console.log(`Files that would be included (${files.length}):`);
|
|
183
|
+
for (const f of files) {
|
|
184
|
+
console.log(` ${f}`);
|
|
185
|
+
}
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle diff command
|
|
190
|
+
if (cmd === "diff") {
|
|
191
|
+
if (!args.ctx1 || !args.ctx2) {
|
|
192
|
+
console.error("Error: diff command requires both --ctx1 and --ctx2\n");
|
|
193
|
+
printUsage();
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const dims = typeof args.dims === "string"
|
|
198
|
+
? args.dims.split(",").map((s) => s.trim()).filter(Boolean)
|
|
199
|
+
: undefined;
|
|
200
|
+
|
|
201
|
+
const ctx1 = parseCtx(args.ctx1);
|
|
202
|
+
const ctx2 = parseCtx(args.ctx2);
|
|
203
|
+
|
|
204
|
+
const result = await diffColony({ entry, dims, ctx1, ctx2 });
|
|
205
|
+
|
|
206
|
+
const format = args.format || "text";
|
|
207
|
+
console.log(formatDiff(result.diff, format));
|
|
208
|
+
|
|
209
|
+
// Exit with code 1 if there are differences (useful for CI)
|
|
210
|
+
const hasDiffs = result.diff.added.length > 0 ||
|
|
211
|
+
result.diff.removed.length > 0 ||
|
|
212
|
+
result.diff.changed.length > 0;
|
|
213
|
+
process.exit(hasDiffs ? 1 : 0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle lint command
|
|
217
|
+
if (cmd === "lint") {
|
|
218
|
+
const dims = typeof args.dims === "string"
|
|
219
|
+
? args.dims.split(",").map((s) => s.trim()).filter(Boolean)
|
|
220
|
+
: undefined;
|
|
221
|
+
|
|
222
|
+
const result = await lintColony({ entry, dims });
|
|
223
|
+
|
|
224
|
+
if (result.issues.length === 0) {
|
|
225
|
+
console.log("✓ No issues found");
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.error(`Found ${result.issues.length} issue(s):\n`);
|
|
230
|
+
for (const issue of result.issues) {
|
|
231
|
+
const icon = issue.severity === "error" ? "✗" : "⚠";
|
|
232
|
+
console.error(`${icon} [${issue.type}] ${issue.message}`);
|
|
233
|
+
if (issue.file) {
|
|
234
|
+
console.error(` at ${issue.file}:${issue.line || 0}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
process.exit(result.issues.some((i) => i.severity === "error") ? 1 : 0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Handle keys command
|
|
241
|
+
if (cmd === "keys") {
|
|
242
|
+
const dims = typeof args.dims === "string"
|
|
243
|
+
? args.dims.split(",").map((s) => s.trim()).filter(Boolean)
|
|
244
|
+
: undefined;
|
|
245
|
+
|
|
246
|
+
const ctx = parseCtx(args.ctx);
|
|
247
|
+
const cfg = await loadColony({ entry, dims, ctx });
|
|
248
|
+
|
|
249
|
+
const keys = cfg.keys();
|
|
250
|
+
for (const key of keys) {
|
|
251
|
+
console.log(key);
|
|
252
|
+
}
|
|
253
|
+
process.exit(0);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Handle env command
|
|
257
|
+
if (cmd === "env") {
|
|
258
|
+
const dims = typeof args.dims === "string"
|
|
259
|
+
? args.dims.split(",").map((s) => s.trim()).filter(Boolean)
|
|
260
|
+
: undefined;
|
|
261
|
+
|
|
262
|
+
const ctx = parseCtx(args.ctx);
|
|
263
|
+
const cfg = await loadColony({ entry, dims, ctx });
|
|
264
|
+
|
|
265
|
+
console.log(formatAsEnv(cfg.toJSON(), args.prefix || ""));
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Handle print command
|
|
270
|
+
if (cmd !== "print") {
|
|
271
|
+
console.error(`Error: Unknown command "${cmd}"\n`);
|
|
272
|
+
printUsage();
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const dims = typeof args.dims === "string"
|
|
277
|
+
? args.dims.split(",").map((s) => s.trim()).filter(Boolean)
|
|
278
|
+
: undefined;
|
|
279
|
+
|
|
280
|
+
const ctx = parseCtx(args.ctx);
|
|
281
|
+
|
|
282
|
+
// Build sandbox options
|
|
283
|
+
const sandbox = {};
|
|
284
|
+
if (args["base-path"]) {
|
|
285
|
+
sandbox.basePath = args["base-path"];
|
|
286
|
+
}
|
|
287
|
+
if (args["allowed-env"]) {
|
|
288
|
+
sandbox.allowedEnvVars = args["allowed-env"].split(",").map((s) => s.trim()).filter(Boolean);
|
|
289
|
+
}
|
|
290
|
+
if (args["allowed-vars"]) {
|
|
291
|
+
sandbox.allowedVars = args["allowed-vars"].split(",").map((s) => s.trim()).filter(Boolean);
|
|
292
|
+
}
|
|
293
|
+
if (args["max-file-size"]) {
|
|
294
|
+
sandbox.maxFileSize = parseInt(args["max-file-size"], 10);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const warnOnSkippedIncludes = !!args["warn-skipped"];
|
|
298
|
+
|
|
299
|
+
const cfg = await loadColony({ entry, dims, ctx, sandbox, warnOnSkippedIncludes });
|
|
300
|
+
|
|
301
|
+
// Handle --query option
|
|
302
|
+
if (args.query) {
|
|
303
|
+
const value = getByPath(cfg, args.query);
|
|
304
|
+
if (value === undefined) {
|
|
305
|
+
console.error(`Key not found: ${args.query}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
if (typeof value === "object") {
|
|
309
|
+
console.log(JSON.stringify(value, null, 2));
|
|
310
|
+
} else {
|
|
311
|
+
console.log(value);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
// Format output
|
|
315
|
+
const format = args.format || "json";
|
|
316
|
+
if (format === "env") {
|
|
317
|
+
console.log(formatAsEnv(cfg.toJSON(), args.prefix || ""));
|
|
318
|
+
} else {
|
|
319
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Handle --strict flag
|
|
324
|
+
if (args.strict && cfg._warnings?.length > 0) {
|
|
325
|
+
console.error(`\n✗ Strict mode: ${cfg._warnings.length} warning(s) found:`);
|
|
326
|
+
for (const w of cfg._warnings) {
|
|
327
|
+
console.error(` [${w.type}] ${w.message}`);
|
|
328
|
+
}
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Show warnings if requested
|
|
333
|
+
if (args["show-warnings"] && cfg._warnings?.length > 0) {
|
|
334
|
+
console.error(`\nWarnings (${cfg._warnings.length}):`);
|
|
335
|
+
for (const w of cfg._warnings) {
|
|
336
|
+
console.error(` [${w.type}] ${w.message}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (typeof args.explain === "string") {
|
|
341
|
+
const info = cfg.explain(args.explain);
|
|
342
|
+
console.error(`\nExplain ${args.explain}:`);
|
|
343
|
+
console.error(info ? JSON.stringify(info, null, 2) : "(no matching rule / unset)");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error(`Error: ${err.message}`);
|
|
348
|
+
if (process.env.DEBUG) {
|
|
349
|
+
console.error(err.stack);
|
|
350
|
+
}
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
})();
|