@goflag/cli 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/dist/cli.js ADDED
@@ -0,0 +1,612 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Logger,
4
+ exitCode,
5
+ renderSummaryTerminal,
6
+ renderTerminal,
7
+ runAudit,
8
+ summarize
9
+ } from "./chunk-75OFMTOC.js";
10
+
11
+ // src/report/diff.ts
12
+ function collectFindings(report) {
13
+ const out = [];
14
+ for (const link of report.brokenLinks) {
15
+ out.push({
16
+ id: link.id,
17
+ kind: "brokenLink",
18
+ severity: "error",
19
+ summary: `${link.verdict} link \u2192 ${link.href}`,
20
+ pageUrl: link.pageUrl
21
+ });
22
+ }
23
+ for (const page of report.unreachablePages) {
24
+ out.push({
25
+ id: page.id,
26
+ kind: "unreachablePage",
27
+ severity: "error",
28
+ summary: `${page.status || "network error"} on ${page.url}`,
29
+ pageUrl: page.url
30
+ });
31
+ }
32
+ for (const hole of report.missingTranslations.holes) {
33
+ out.push({
34
+ id: hole.id,
35
+ kind: "translationHole",
36
+ severity: "warning",
37
+ summary: `${hole.route} missing ${hole.missingLocales.join(", ")}`
38
+ });
39
+ }
40
+ for (const issue of report.missingTranslations.reciprocity) {
41
+ out.push({
42
+ id: issue.id,
43
+ kind: "reciprocity",
44
+ severity: "warning",
45
+ summary: `${issue.code} on ${issue.url}`,
46
+ pageUrl: issue.url
47
+ });
48
+ }
49
+ for (const issue of report.seoIssues) {
50
+ out.push({
51
+ id: issue.id,
52
+ kind: "seo",
53
+ severity: issue.severity,
54
+ summary: `${issue.ruleId} on ${issue.pageUrl}`,
55
+ pageUrl: issue.pageUrl
56
+ });
57
+ }
58
+ for (const issue of report.siteIssues) {
59
+ out.push({
60
+ id: issue.id,
61
+ kind: "site",
62
+ severity: issue.severity,
63
+ summary: `${issue.ruleId} on ${issue.pageUrl}`,
64
+ pageUrl: issue.pageUrl
65
+ });
66
+ }
67
+ return out;
68
+ }
69
+ var SEVERITY_RANK = { error: 0, warning: 1, info: 2 };
70
+ function sortEntries(entries) {
71
+ return [...entries].sort(
72
+ (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || a.kind.localeCompare(b.kind) || a.summary.localeCompare(b.summary)
73
+ );
74
+ }
75
+ function totalFindings(report) {
76
+ return collectFindings(report).length;
77
+ }
78
+ function diffReports(baseline, current) {
79
+ const before = new Map(collectFindings(baseline).map((e) => [e.id, e]));
80
+ const now = new Map(collectFindings(current).map((e) => [e.id, e]));
81
+ const added = [];
82
+ let unchanged = 0;
83
+ for (const [id, entry] of now) {
84
+ if (before.has(id)) unchanged += 1;
85
+ else added.push(entry);
86
+ }
87
+ const resolved = [];
88
+ for (const [id, entry] of before) {
89
+ if (!now.has(id)) resolved.push(entry);
90
+ }
91
+ return {
92
+ baseline: { url: baseline.url, finishedAt: baseline.finishedAt },
93
+ added: sortEntries(added),
94
+ resolved: sortEntries(resolved),
95
+ unchanged
96
+ };
97
+ }
98
+ function diffExitCode(diff, failOn, debt) {
99
+ if (debt?.max !== void 0 && debt.total > debt.max) return 1;
100
+ if (failOn === "never") return 0;
101
+ const threshold = failOn === "error" ? 0 : 1;
102
+ const blocking = diff.added.filter((e) => SEVERITY_RANK[e.severity] <= threshold);
103
+ return blocking.length > 0 ? 1 : 0;
104
+ }
105
+
106
+ // src/report/render-diff.ts
107
+ var ANSI = {
108
+ reset: "\x1B[0m",
109
+ bold: "\x1B[1m",
110
+ dim: "\x1B[2m",
111
+ red: "\x1B[31m",
112
+ green: "\x1B[32m",
113
+ yellow: "\x1B[33m",
114
+ cyan: "\x1B[36m"
115
+ };
116
+ function paint(enabled) {
117
+ return (code, text) => enabled ? `${code}${text}${ANSI.reset}` : text;
118
+ }
119
+ function severityTag(c, severity) {
120
+ if (severity === "error") return c(ANSI.red, "error");
121
+ if (severity === "warning") return c(ANSI.yellow, "warn ");
122
+ return c(ANSI.dim, "info ");
123
+ }
124
+ var LIST_LIMIT = 20;
125
+ function listEntries(entries, marker, markerColor, c) {
126
+ const lines = [];
127
+ for (const entry of entries.slice(0, LIST_LIMIT)) {
128
+ const where = entry.pageUrl && !entry.summary.includes(entry.pageUrl) ? c(ANSI.dim, ` on ${entry.pageUrl}`) : "";
129
+ lines.push(
130
+ ` ${c(markerColor, marker)} ${severityTag(c, entry.severity)} ${c(ANSI.dim, entry.kind)} ${entry.summary}${where}`
131
+ );
132
+ }
133
+ if (entries.length > LIST_LIMIT) {
134
+ lines.push(` ${c(ANSI.dim, `\u2026 +${entries.length - LIST_LIMIT} more`)}`);
135
+ }
136
+ return lines;
137
+ }
138
+ function baselineAgeDays(finishedAt, now) {
139
+ const taken = Date.parse(finishedAt);
140
+ if (Number.isNaN(taken)) return null;
141
+ return Math.max(0, Math.floor((now - taken) / 864e5));
142
+ }
143
+ function renderDiffTerminal(diff, options = {}) {
144
+ const c = paint(options.color ?? false);
145
+ const lines = [];
146
+ const debt = diff.unchanged;
147
+ const verdict = diff.added.length > 0 ? c(ANSI.red, "REGRESSION") : debt > 0 ? c(ANSI.yellow, "REGRESSION GATE") : c(ANSI.green, "CLEAN");
148
+ const age = baselineAgeDays(diff.baseline.finishedAt, options.now ?? Date.now());
149
+ lines.push("");
150
+ lines.push(`${c(ANSI.bold, "goflag")} ${c(ANSI.dim, "--regressions-only")}`);
151
+ lines.push(
152
+ `${verdict} ${diff.added.length} new \xB7 ` + (debt > 0 ? c(ANSI.bold, `${debt} known findings NOT gating this build`) : c(ANSI.dim, "no known findings")) + (diff.resolved.length > 0 ? c(ANSI.dim, ` \xB7 ${diff.resolved.length} resolved`) : "")
153
+ );
154
+ lines.push(
155
+ c(
156
+ ANSI.dim,
157
+ `baseline ${diff.baseline.url} \u2014 taken ${diff.baseline.finishedAt}` + (age === null ? "" : age === 0 ? " (today)" : ` (${age} day${age === 1 ? "" : "s"} ago)`)
158
+ )
159
+ );
160
+ lines.push("");
161
+ if (diff.added.length > 0) {
162
+ lines.push(c(ANSI.bold, "New findings"));
163
+ lines.push(...listEntries(diff.added, "+", ANSI.red, c));
164
+ lines.push("");
165
+ }
166
+ if (diff.resolved.length > 0) {
167
+ lines.push(c(ANSI.bold, "Resolved"));
168
+ lines.push(...listEntries(diff.resolved, "-", ANSI.green, c));
169
+ lines.push("");
170
+ }
171
+ if (diff.added.length === 0 && diff.resolved.length === 0) {
172
+ lines.push(
173
+ c(
174
+ ANSI.dim,
175
+ debt > 0 ? `Nothing moved since the baseline \u2014 ${debt} findings still open. Run without --regressions-only to see them.` : "Nothing moved since the baseline."
176
+ )
177
+ );
178
+ lines.push("");
179
+ }
180
+ return lines.join("\n");
181
+ }
182
+
183
+ // src/lib/runner/dev-server.ts
184
+ import { spawn } from "child_process";
185
+ async function answers(url, allowInsecureTls) {
186
+ const previous = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
187
+ if (allowInsecureTls) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
188
+ try {
189
+ await fetch(url, { redirect: "manual" });
190
+ return true;
191
+ } catch {
192
+ return false;
193
+ } finally {
194
+ if (allowInsecureTls) {
195
+ if (previous === void 0) delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
196
+ else process.env.NODE_TLS_REJECT_UNAUTHORIZED = previous;
197
+ }
198
+ }
199
+ }
200
+ function terminate(child) {
201
+ return new Promise((resolve) => {
202
+ if (child.exitCode !== null || child.signalCode !== null) {
203
+ resolve();
204
+ return;
205
+ }
206
+ const killGroup = (signal) => {
207
+ try {
208
+ if (child.pid) process.kill(-child.pid, signal);
209
+ else child.kill(signal);
210
+ } catch {
211
+ }
212
+ };
213
+ const force = setTimeout(() => killGroup("SIGKILL"), 5e3);
214
+ child.once("exit", () => {
215
+ clearTimeout(force);
216
+ resolve();
217
+ });
218
+ killGroup("SIGTERM");
219
+ });
220
+ }
221
+ async function startServer(options) {
222
+ const timeoutMs = options.timeoutMs ?? 6e4;
223
+ const intervalMs = options.intervalMs ?? 250;
224
+ const allowInsecureTls = options.allowInsecureTls === true;
225
+ const cwd = options.cwd ?? process.cwd();
226
+ const child = spawn(options.command, {
227
+ cwd,
228
+ shell: true,
229
+ // Own process group, so `terminate` can take the whole tree down.
230
+ detached: true,
231
+ stdio: ["ignore", "pipe", "pipe"]
232
+ });
233
+ let exited;
234
+ child.once("exit", (code, signal) => {
235
+ exited = { code, signal };
236
+ });
237
+ const tail = [];
238
+ const record = (chunk) => {
239
+ const text = chunk.toString();
240
+ options.onOutput?.(text);
241
+ tail.push(text);
242
+ if (tail.length > 40) tail.shift();
243
+ };
244
+ child.stdout?.on("data", record);
245
+ child.stderr?.on("data", record);
246
+ const server = { stop: () => terminate(child) };
247
+ const deadline = Date.now() + timeoutMs;
248
+ for (; ; ) {
249
+ if (exited) {
250
+ await server.stop();
251
+ const why = exited.signal ? `signal ${exited.signal}` : `exit code ${exited.code}`;
252
+ throw new Error(
253
+ `--start command exited before ${options.url} answered (${why}).
254
+ command: ${options.command}
255
+ in: ${cwd}
256
+ ${tail.join("").trimEnd()}`
257
+ );
258
+ }
259
+ if (await answers(options.url, allowInsecureTls)) return server;
260
+ if (Date.now() >= deadline) {
261
+ await server.stop();
262
+ throw new Error(
263
+ `--start command did not serve ${options.url} within ${timeoutMs}ms.
264
+ command: ${options.command}
265
+ in: ${cwd}
266
+ Raise --start-timeout, check the command listens on that URL, or point --start-cwd at the directory it should run in.`
267
+ );
268
+ }
269
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
270
+ }
271
+ }
272
+
273
+ // src/cli-args.ts
274
+ var HELP = `goflag \u2014 audit a site for broken links, missing translations, and SEO metadata
275
+
276
+ Usage:
277
+ goflag <url> [options]
278
+
279
+ Options:
280
+ --json Print the JSON report to stdout (nothing else).
281
+ --summary, -s Roll findings up (dedup by link/rule/code). Pairs
282
+ with --json for a compact, agent-friendly payload.
283
+ --report <file> Write the (full) JSON report to <file>.
284
+ --depth <n> Crawl depth (0 = entry page only). Default: 2.
285
+ --max-pages <n> Hard cap on pages crawled. Default: 200.
286
+ --include <glob> Only crawl paths matching <glob> (repeatable).
287
+ --exclude <glob> Skip paths matching <glob> (repeatable).
288
+ --locales <list> Comma-separated locales the site serves, e.g.
289
+ "fr,en,pt-br". Authoritative: overrides what the
290
+ sitemap and crawl suggest, and makes a locale the
291
+ site does not serve yet show up as missing.
292
+ --ignore-holes <glob> Route (locale-free) that is deliberately not
293
+ translated everywhere, so its gaps are not reported
294
+ as missing translations (repeatable).
295
+ --no-sitemap Do not discover the sitemap; crawl from <url> only.
296
+ Discovery is on by default because link-only crawling
297
+ cannot find locales a site never links to.
298
+ --fail-on <level> Exit 1 at or above this severity: warning (default),
299
+ error, or never.
300
+ --regressions-only Weaken the gate: fail only on findings that are NEW
301
+ relative to --baseline. Known findings stop blocking
302
+ the build \u2014 a green run no longer means a clean site.
303
+ Requires --baseline.
304
+ --baseline <file> Stored report to compare against. Use with
305
+ --regressions-only.
306
+ --max-debt <n> Fail when the site carries more than <n> findings in
307
+ total, whether new or known. Lower it as you fix, to
308
+ stop a baseline from fossilising.
309
+ --start <cmd> Boot <cmd>, wait for <url> to answer, audit, then
310
+ stop it. Use to gate a merge on the built app before
311
+ it ships.
312
+ --start-cwd <dir> Directory to run --start in. Defaults to the current
313
+ one; set it when auditing a monorepo package from the
314
+ repository root.
315
+ --start-timeout <ms> How long to wait for --start to answer. Default: 60000.
316
+ --no-external Do not probe off-origin (external) links.
317
+ --static Static HTML only; never launch headless Chromium.
318
+ --allow-insecure-tls Accept self-signed / invalid TLS (localhost, tunnels).
319
+ --timeout <ms> Per-request timeout in ms. Default: 8000.
320
+ --verbose, -V Log every page as it is analyzed.
321
+ --quiet, -q Suppress the live progress output.
322
+ --no-color Disable coloured output.
323
+ -h, --help Show this help.
324
+ -v, --version Show the version.
325
+
326
+ Exit codes: 0 clean, 1 findings found, 2 fatal error.`;
327
+ var FAIL_ON_LEVELS = ["warning", "error", "never"];
328
+ function parseArgs(argv) {
329
+ const parsed = {
330
+ json: false,
331
+ summary: false,
332
+ color: process.stdout.isTTY === true && !process.env.NO_COLOR,
333
+ help: false,
334
+ version: false,
335
+ logMode: "compact",
336
+ failOn: "warning",
337
+ regressionsOnly: false,
338
+ startTimeoutMs: 6e4,
339
+ options: { include: [], exclude: [] }
340
+ };
341
+ const next = (i, flag) => {
342
+ const value = argv[i + 1];
343
+ if (value === void 0) throw new Error(`${flag} requires a value`);
344
+ return value;
345
+ };
346
+ for (let i = 0; i < argv.length; i++) {
347
+ const arg = argv[i];
348
+ switch (arg) {
349
+ case "-h":
350
+ case "--help":
351
+ parsed.help = true;
352
+ break;
353
+ case "-v":
354
+ case "--version":
355
+ parsed.version = true;
356
+ break;
357
+ case "--json":
358
+ parsed.json = true;
359
+ break;
360
+ case "-s":
361
+ case "--summary":
362
+ parsed.summary = true;
363
+ break;
364
+ case "--no-color":
365
+ parsed.color = false;
366
+ break;
367
+ case "-V":
368
+ case "--verbose":
369
+ parsed.logMode = "verbose";
370
+ break;
371
+ case "-q":
372
+ case "--quiet":
373
+ parsed.logMode = "quiet";
374
+ break;
375
+ case "--static":
376
+ parsed.options.static = true;
377
+ break;
378
+ case "--allow-insecure-tls":
379
+ parsed.options.allowInsecureTls = true;
380
+ break;
381
+ case "--no-external":
382
+ parsed.options.checkExternal = false;
383
+ break;
384
+ case "--no-sitemap":
385
+ parsed.options.noSitemap = true;
386
+ break;
387
+ case "--ignore-holes":
388
+ parsed.options.ignoreHoles = [...parsed.options.ignoreHoles ?? [], next(i, arg)];
389
+ i++;
390
+ break;
391
+ case "--locales": {
392
+ const value = next(i, arg);
393
+ const tags = value.split(",").map((t) => t.trim()).filter(Boolean);
394
+ if (tags.length === 0) throw new Error(`${arg} expects at least one locale`);
395
+ parsed.options.locales = [...parsed.options.locales ?? [], ...tags];
396
+ i++;
397
+ break;
398
+ }
399
+ case "--fail-on": {
400
+ const value = next(i, arg);
401
+ if (!FAIL_ON_LEVELS.includes(value)) {
402
+ throw new Error(`${arg} expects one of: ${FAIL_ON_LEVELS.join(", ")}`);
403
+ }
404
+ parsed.failOn = value;
405
+ i++;
406
+ break;
407
+ }
408
+ case "--start":
409
+ parsed.start = next(i, arg);
410
+ i++;
411
+ break;
412
+ case "--start-timeout":
413
+ parsed.startTimeoutMs = toInt(next(i, arg), arg);
414
+ i++;
415
+ break;
416
+ case "--start-cwd":
417
+ parsed.startCwd = next(i, arg);
418
+ i++;
419
+ break;
420
+ case "--report":
421
+ parsed.report = next(i, arg);
422
+ i++;
423
+ break;
424
+ case "--baseline":
425
+ parsed.baseline = next(i, arg);
426
+ i++;
427
+ break;
428
+ case "--regressions-only":
429
+ parsed.regressionsOnly = true;
430
+ break;
431
+ case "--max-debt":
432
+ parsed.maxDebt = toInt(next(i, arg), arg);
433
+ i++;
434
+ break;
435
+ case "--depth":
436
+ parsed.options.depth = toInt(next(i, arg), arg);
437
+ i++;
438
+ break;
439
+ case "--max-pages":
440
+ parsed.options.maxPages = toInt(next(i, arg), arg);
441
+ i++;
442
+ break;
443
+ case "--timeout":
444
+ parsed.options.timeoutMs = toInt(next(i, arg), arg);
445
+ i++;
446
+ break;
447
+ case "--include":
448
+ parsed.options.include.push(next(i, arg));
449
+ i++;
450
+ break;
451
+ case "--exclude":
452
+ parsed.options.exclude.push(next(i, arg));
453
+ i++;
454
+ break;
455
+ default:
456
+ if (arg && arg.startsWith("-")) throw new Error(`unknown option: ${arg}`);
457
+ if (arg && !parsed.url) parsed.url = arg;
458
+ else if (arg) throw new Error(`unexpected argument: ${arg}`);
459
+ }
460
+ }
461
+ if (parsed.baseline && !parsed.regressionsOnly) {
462
+ throw new Error(
463
+ "--baseline weakens the gate, so it must be requested explicitly: add --regressions-only"
464
+ );
465
+ }
466
+ if (parsed.regressionsOnly && !parsed.baseline) {
467
+ throw new Error("--regressions-only needs a --baseline <file> to compare against");
468
+ }
469
+ return parsed;
470
+ }
471
+ function toInt(value, flag) {
472
+ const n = Number(value);
473
+ if (!Number.isInteger(n) || n < 0) throw new Error(`${flag} expects a non-negative integer`);
474
+ return n;
475
+ }
476
+
477
+ // src/cli.ts
478
+ async function readVersion() {
479
+ try {
480
+ const { readFileSync } = await import("fs");
481
+ const { fileURLToPath } = await import("url");
482
+ const { dirname, join } = await import("path");
483
+ const here = dirname(fileURLToPath(import.meta.url));
484
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
485
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
486
+ } catch {
487
+ return "0.0.0";
488
+ }
489
+ }
490
+ async function main() {
491
+ let args;
492
+ try {
493
+ args = parseArgs(process.argv.slice(2));
494
+ } catch (err) {
495
+ process.stderr.write(`goflag: ${err.message}
496
+
497
+ ${HELP}
498
+ `);
499
+ return 2;
500
+ }
501
+ if (args.help) {
502
+ process.stdout.write(`${HELP}
503
+ `);
504
+ return 0;
505
+ }
506
+ if (args.version) {
507
+ process.stdout.write(`${await readVersion()}
508
+ `);
509
+ return 0;
510
+ }
511
+ if (!args.url) {
512
+ process.stderr.write(`goflag: missing <url>
513
+
514
+ ${HELP}
515
+ `);
516
+ return 2;
517
+ }
518
+ const logger = new Logger({
519
+ stream: process.stderr,
520
+ mode: args.logMode,
521
+ color: process.stderr.isTTY === true && !process.env.NO_COLOR
522
+ });
523
+ let server;
524
+ let report;
525
+ try {
526
+ if (args.start) {
527
+ logger.note(`goflag: starting \`${args.start}\` \u2026`);
528
+ server = await startServer({
529
+ command: args.start,
530
+ cwd: args.startCwd,
531
+ url: args.url,
532
+ timeoutMs: args.startTimeoutMs,
533
+ allowInsecureTls: args.options.allowInsecureTls,
534
+ onOutput: args.logMode === "verbose" ? (chunk) => process.stderr.write(chunk) : void 0
535
+ });
536
+ }
537
+ logger.note(`goflag: auditing ${args.url} \u2026`);
538
+ report = await runAudit(args.url, { ...args.options, onProgress: logger.onProgress });
539
+ logger.stop();
540
+ } catch (err) {
541
+ logger.stop();
542
+ process.stderr.write(`goflag: ${err.message}
543
+ `);
544
+ return 2;
545
+ } finally {
546
+ await server?.stop();
547
+ }
548
+ if (args.baseline) {
549
+ try {
550
+ const { readFileSync } = await import("fs");
551
+ const baseline = JSON.parse(readFileSync(args.baseline, "utf8"));
552
+ report.diff = diffReports(baseline, report);
553
+ } catch (err) {
554
+ process.stderr.write(
555
+ `goflag: could not read baseline ${args.baseline}: ${err.message}
556
+ `
557
+ );
558
+ return 2;
559
+ }
560
+ }
561
+ if (args.report) {
562
+ const { writeFileSync } = await import("fs");
563
+ writeFileSync(args.report, `${JSON.stringify(report, null, 2)}
564
+ `, "utf8");
565
+ process.stderr.write(`goflag: report written to ${args.report}
566
+ `);
567
+ }
568
+ if (report.diff && !args.json) {
569
+ process.stdout.write(`${renderDiffTerminal(report.diff, { color: args.color })}
570
+ `);
571
+ const total2 = totalFindings(report);
572
+ if (args.maxDebt !== void 0 && total2 > args.maxDebt) {
573
+ process.stderr.write(
574
+ `goflag: ${total2} findings exceeds the --max-debt budget of ${args.maxDebt}.
575
+ `
576
+ );
577
+ }
578
+ return diffExitCode(report.diff, args.failOn, { total: total2, max: args.maxDebt });
579
+ }
580
+ if (args.summary) {
581
+ const summary = summarize(report);
582
+ process.stdout.write(
583
+ args.json ? `${JSON.stringify(summary, null, 2)}
584
+ ` : `${renderSummaryTerminal(summary, { color: args.color })}
585
+ `
586
+ );
587
+ } else {
588
+ process.stdout.write(
589
+ args.json ? `${JSON.stringify(report, null, 2)}
590
+ ` : `${renderTerminal(report, { color: args.color })}
591
+ `
592
+ );
593
+ }
594
+ const total = totalFindings(report);
595
+ if (args.maxDebt !== void 0 && total > args.maxDebt) {
596
+ process.stderr.write(
597
+ `goflag: ${total} findings exceeds the --max-debt budget of ${args.maxDebt}.
598
+ `
599
+ );
600
+ return 1;
601
+ }
602
+ if (report.diff) {
603
+ return diffExitCode(report.diff, args.failOn, { total, max: args.maxDebt });
604
+ }
605
+ return exitCode(report, args.failOn);
606
+ }
607
+ main().then((code) => process.exit(code)).catch((err) => {
608
+ process.stderr.write(`goflag: unexpected error: ${err.stack ?? err}
609
+ `);
610
+ process.exit(2);
611
+ });
612
+ //# sourceMappingURL=cli.js.map