@westbayberry/dg 1.3.2 → 2.0.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.
Files changed (126) hide show
  1. package/LICENSE +1 -201
  2. package/NOTICE +1 -4
  3. package/README.md +293 -0
  4. package/dist/api/analyze.js +210 -0
  5. package/dist/audit/deep.js +180 -0
  6. package/dist/audit/detectors.js +247 -0
  7. package/dist/audit/events.js +41 -0
  8. package/dist/audit/rules.js +426 -0
  9. package/dist/audit-ui/AuditApp.js +39 -0
  10. package/dist/audit-ui/components/AuditHeader.js +24 -0
  11. package/dist/audit-ui/components/AuditResultsView.js +307 -0
  12. package/dist/audit-ui/components/DeepStatusRow.js +11 -0
  13. package/dist/audit-ui/export.js +85 -0
  14. package/dist/audit-ui/format.js +34 -0
  15. package/dist/audit-ui/launch.js +34 -0
  16. package/dist/auth/device-login.js +271 -0
  17. package/dist/auth/env-token.js +6 -0
  18. package/dist/auth/login-app.js +156 -0
  19. package/dist/auth/store.js +147 -0
  20. package/dist/bin/dg.js +71 -0
  21. package/dist/commands/audit.js +357 -0
  22. package/dist/commands/completion.js +116 -0
  23. package/dist/commands/config.js +99 -0
  24. package/dist/commands/doctor.js +39 -0
  25. package/dist/commands/explain.js +100 -0
  26. package/dist/commands/guard-commit.js +158 -0
  27. package/dist/commands/help.js +74 -0
  28. package/dist/commands/licenses.js +435 -0
  29. package/dist/commands/login.js +81 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/router.js +98 -0
  32. package/dist/commands/scan.js +18 -0
  33. package/dist/commands/service.js +475 -0
  34. package/dist/commands/setup.js +302 -0
  35. package/dist/commands/status.js +115 -0
  36. package/dist/commands/suggest.js +35 -0
  37. package/dist/commands/types.js +4 -0
  38. package/dist/commands/unavailable.js +11 -0
  39. package/dist/commands/uninstall.js +111 -0
  40. package/dist/commands/update.js +210 -0
  41. package/dist/commands/verify.js +151 -0
  42. package/dist/commands/version.js +22 -0
  43. package/dist/commands/wrap.js +55 -0
  44. package/dist/config/settings.js +302 -0
  45. package/dist/install-ui/LiveInstall.js +24 -0
  46. package/dist/install-ui/block-render.js +83 -0
  47. package/dist/install-ui/live-install-app.js +48 -0
  48. package/dist/install-ui/prompt.js +24 -0
  49. package/dist/launcher/classify.js +116 -0
  50. package/dist/launcher/env.js +53 -0
  51. package/dist/launcher/live-install.js +50 -0
  52. package/dist/launcher/output-redaction.js +77 -0
  53. package/dist/launcher/preflight-prompt.js +139 -0
  54. package/dist/launcher/resolve-real-binary.js +73 -0
  55. package/dist/launcher/run.js +417 -0
  56. package/dist/policy/evaluate.js +128 -0
  57. package/dist/presentation/mode.js +52 -0
  58. package/dist/presentation/theme.js +29 -0
  59. package/dist/proxy/buffer-budget.js +64 -0
  60. package/dist/proxy/ca.js +126 -0
  61. package/dist/proxy/classify-host.js +26 -0
  62. package/dist/proxy/enforcement.js +102 -0
  63. package/dist/proxy/metadata-map.js +336 -0
  64. package/dist/proxy/server.js +909 -0
  65. package/dist/proxy/upstream-proxy.js +102 -0
  66. package/dist/proxy/worker.js +39 -0
  67. package/dist/publish-set/collect.js +51 -0
  68. package/dist/publish-set/no-exec-shell.js +19 -0
  69. package/dist/publish-set/npm.js +109 -0
  70. package/dist/publish-set/pack.js +36 -0
  71. package/dist/publish-set/pypi.js +59 -0
  72. package/dist/runtime/cli.js +17 -0
  73. package/dist/runtime/first-run.js +60 -0
  74. package/dist/runtime/node-version.js +58 -0
  75. package/dist/runtime/nudges.js +105 -0
  76. package/dist/scan/analyze-worker.js +21 -0
  77. package/dist/scan/collect.js +153 -0
  78. package/dist/scan/command.js +159 -0
  79. package/dist/scan/discovery.js +209 -0
  80. package/dist/scan/render.js +240 -0
  81. package/dist/scan/scanner-report.js +82 -0
  82. package/dist/scan/staged.js +173 -0
  83. package/dist/scan/types.js +1 -0
  84. package/dist/scan-ui/LegacyApp.js +156 -0
  85. package/dist/scan-ui/alt-screen.js +84 -0
  86. package/dist/scan-ui/api-aliases.js +1 -0
  87. package/dist/scan-ui/components/ErrorView.js +23 -0
  88. package/dist/scan-ui/components/InteractiveResultsView.js +1166 -0
  89. package/dist/scan-ui/components/ProgressBar.js +89 -0
  90. package/dist/scan-ui/components/ProjectSelector.js +62 -0
  91. package/dist/scan-ui/components/ScoreHeader.js +20 -0
  92. package/dist/scan-ui/components/SetupBanner.js +13 -0
  93. package/dist/scan-ui/components/Spinner.js +4 -0
  94. package/dist/scan-ui/format-helpers.js +40 -0
  95. package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
  96. package/dist/scan-ui/hooks/useScan.js +113 -0
  97. package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
  98. package/dist/scan-ui/launch.js +27 -0
  99. package/dist/scan-ui/logo.js +91 -0
  100. package/dist/scan-ui/shims.js +30 -0
  101. package/dist/security/sanitize.js +28 -0
  102. package/dist/service/state.js +837 -0
  103. package/dist/service/trust-store.js +234 -0
  104. package/dist/service/worker.js +88 -0
  105. package/dist/setup/git-hook.js +244 -0
  106. package/dist/setup/optional-support.js +58 -0
  107. package/dist/setup/plan.js +899 -0
  108. package/dist/state/cleanup-registry.js +60 -0
  109. package/dist/state/index.js +5 -0
  110. package/dist/state/locks.js +161 -0
  111. package/dist/state/paths.js +24 -0
  112. package/dist/state/sessions.js +170 -0
  113. package/dist/state/store.js +50 -0
  114. package/dist/telemetry/events.js +40 -0
  115. package/dist/util/git.js +20 -0
  116. package/dist/util/tty-prompt.js +43 -0
  117. package/dist/verify/local.js +400 -0
  118. package/dist/verify/package-check.js +240 -0
  119. package/dist/verify/preflight.js +698 -0
  120. package/dist/verify/render.js +184 -0
  121. package/dist/verify/types.js +1 -0
  122. package/package.json +33 -50
  123. package/dist/index.mjs +0 -54141
  124. package/dist/postinstall.mjs +0 -731
  125. package/dist/python-hook/dg_pip_hook.pth +0 -1
  126. package/dist/python-hook/dg_pip_hook.py +0 -130
@@ -0,0 +1,210 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { npmExecutable } from "../publish-set/npm.js";
3
+ import { EXIT_USAGE } from "./types.js";
4
+ import { dgVersion } from "./version.js";
5
+ const PACKAGE_NAME = "@westbayberry/dg";
6
+ export const updateCommand = {
7
+ name: "update",
8
+ summary: "Check for dg CLI updates.",
9
+ usage: "dg update [--json]",
10
+ aliases: ["upgrade"],
11
+ flags: [{ flag: "--json", summary: "Machine-readable update info." }],
12
+ examples: ["dg update"],
13
+ details: [
14
+ "Checks the latest published dg version and prints the exact package-manager command to run.",
15
+ "The command does not run package managers, edit setup state, or mutate the installed npm package."
16
+ ],
17
+ handler: (context) => runUpdateCommand(context.args, context.commandPath[0] ?? "update")
18
+ };
19
+ function runUpdateCommand(args, commandName) {
20
+ const parsed = parseUpdateArgs(args);
21
+ if ("error" in parsed) {
22
+ return usageError(commandName, parsed.error);
23
+ }
24
+ const latestVersion = readLatestVersion();
25
+ const report = buildUpdateReport(latestVersion);
26
+ if (parsed.format === "json") {
27
+ return {
28
+ exitCode: report.status === "unknown" ? 1 : 0,
29
+ stdout: `${JSON.stringify(report, null, 2)}\n`,
30
+ stderr: ""
31
+ };
32
+ }
33
+ return {
34
+ exitCode: report.status === "unknown" ? 1 : 0,
35
+ stdout: renderUpdateText(report),
36
+ stderr: ""
37
+ };
38
+ }
39
+ function parseUpdateArgs(args) {
40
+ let format = "text";
41
+ for (const arg of args) {
42
+ if (!arg) {
43
+ return { error: "empty argument" };
44
+ }
45
+ if (arg === "--json") {
46
+ if (format !== "text") {
47
+ return { error: "choose only one output format" };
48
+ }
49
+ format = "json";
50
+ continue;
51
+ }
52
+ if (arg === "--yes" || arg === "-y") {
53
+ return { error: "--yes is not supported because dg update does not self-mutate; run the printed command explicitly" };
54
+ }
55
+ if (arg.startsWith("-")) {
56
+ return { error: `unknown option '${arg}'` };
57
+ }
58
+ return { error: "update does not accept positional arguments" };
59
+ }
60
+ return {
61
+ format
62
+ };
63
+ }
64
+ export function readLatestVersion(timeoutMs = 5000) {
65
+ const injected = process.env.DG_UPDATE_LATEST_VERSION;
66
+ if (injected) {
67
+ return injected;
68
+ }
69
+ const result = spawnSync(npmExecutable(), ["view", PACKAGE_NAME, "version", "--json"], {
70
+ encoding: "utf8",
71
+ env: {
72
+ ...process.env,
73
+ npm_config_ignore_scripts: "true"
74
+ },
75
+ timeout: timeoutMs
76
+ });
77
+ if (result.status !== 0) {
78
+ return null;
79
+ }
80
+ const raw = result.stdout.trim();
81
+ if (!raw) {
82
+ return null;
83
+ }
84
+ try {
85
+ const parsed = JSON.parse(raw);
86
+ return typeof parsed === "string" ? parsed : null;
87
+ }
88
+ catch {
89
+ return raw.replace(/^"|"$/gu, "");
90
+ }
91
+ }
92
+ function buildUpdateReport(latestVersion) {
93
+ if (!latestVersion) {
94
+ return {
95
+ currentVersion: dgVersion(),
96
+ latestVersion: null,
97
+ packageName: PACKAGE_NAME,
98
+ status: "unknown",
99
+ updateCommand: null
100
+ };
101
+ }
102
+ const available = compareVersions(latestVersion, dgVersion()) > 0;
103
+ return {
104
+ currentVersion: dgVersion(),
105
+ latestVersion,
106
+ packageName: PACKAGE_NAME,
107
+ status: available ? "available" : "current",
108
+ updateCommand: available ? `npm install -g ${PACKAGE_NAME}@${latestVersion}` : null
109
+ };
110
+ }
111
+ function renderUpdateText(report) {
112
+ const lines = [
113
+ "Dependency Guardian update",
114
+ `Current version: ${report.currentVersion}`
115
+ ];
116
+ if (report.status === "unknown") {
117
+ lines.push("Latest version: unknown");
118
+ lines.push("Status: registry metadata unavailable");
119
+ lines.push("No install command was run.");
120
+ return `${lines.join("\n")}\n`;
121
+ }
122
+ lines.push(`Latest version: ${report.latestVersion}`);
123
+ lines.push(`Status: ${report.status}`);
124
+ if (report.updateCommand) {
125
+ lines.push(`Run: ${report.updateCommand}`);
126
+ }
127
+ else {
128
+ lines.push("No update needed.");
129
+ }
130
+ lines.push("No package manager was executed.");
131
+ return `${lines.join("\n")}\n`;
132
+ }
133
+ export function compareVersions(left, right) {
134
+ const leftVersion = parseVersion(left);
135
+ const rightVersion = parseVersion(right);
136
+ const length = Math.max(leftVersion.release.length, rightVersion.release.length);
137
+ for (let index = 0; index < length; index += 1) {
138
+ const diff = (leftVersion.release[index] ?? 0) - (rightVersion.release[index] ?? 0);
139
+ if (diff !== 0) {
140
+ return diff;
141
+ }
142
+ }
143
+ return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
144
+ }
145
+ function parseVersion(version) {
146
+ const core = version.replace(/^v/u, "").split("+", 1)[0] ?? "";
147
+ const dashIndex = core.indexOf("-");
148
+ const releaseText = dashIndex === -1 ? core : core.slice(0, dashIndex);
149
+ const prereleaseText = dashIndex === -1 ? "" : core.slice(dashIndex + 1);
150
+ const release = releaseText
151
+ .split(".")
152
+ .map((part) => Number.parseInt(part, 10))
153
+ .filter((part) => Number.isFinite(part));
154
+ const prerelease = prereleaseText ? prereleaseText.split(".") : [];
155
+ return { release, prerelease };
156
+ }
157
+ function comparePrerelease(left, right) {
158
+ if (left.length === 0 && right.length === 0) {
159
+ return 0;
160
+ }
161
+ if (left.length === 0) {
162
+ return 1;
163
+ }
164
+ if (right.length === 0) {
165
+ return -1;
166
+ }
167
+ const length = Math.max(left.length, right.length);
168
+ for (let index = 0; index < length; index += 1) {
169
+ const leftId = left[index];
170
+ const rightId = right[index];
171
+ if (leftId === undefined) {
172
+ return -1;
173
+ }
174
+ if (rightId === undefined) {
175
+ return 1;
176
+ }
177
+ const diff = comparePrereleaseIdentifier(leftId, rightId);
178
+ if (diff !== 0) {
179
+ return diff;
180
+ }
181
+ }
182
+ return 0;
183
+ }
184
+ function comparePrereleaseIdentifier(left, right) {
185
+ const leftNumeric = /^\d+$/u.test(left);
186
+ const rightNumeric = /^\d+$/u.test(right);
187
+ if (leftNumeric && rightNumeric) {
188
+ return Number.parseInt(left, 10) - Number.parseInt(right, 10);
189
+ }
190
+ if (leftNumeric) {
191
+ return -1;
192
+ }
193
+ if (rightNumeric) {
194
+ return 1;
195
+ }
196
+ if (left < right) {
197
+ return -1;
198
+ }
199
+ if (left > right) {
200
+ return 1;
201
+ }
202
+ return 0;
203
+ }
204
+ function usageError(commandName, message) {
205
+ return {
206
+ exitCode: EXIT_USAGE,
207
+ stdout: "",
208
+ stderr: `dg ${commandName}: ${message}. Usage: dg ${commandName} [--json]\n`
209
+ };
210
+ }
@@ -0,0 +1,151 @@
1
+ import { EXIT_UNAVAILABLE, EXIT_USAGE } from "./types.js";
2
+ import { writeFileSync } from "node:fs";
3
+ import { existsSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { renderVerifyJson, renderVerifySarif, renderVerifyText } from "../verify/render.js";
6
+ import { resolvePresentation } from "../presentation/mode.js";
7
+ import { createTheme } from "../presentation/theme.js";
8
+ import { verifyLocalTarget } from "../verify/local.js";
9
+ import { isSupportedLockfilePath, verifyLockfile, verifyPackageSpec } from "../verify/preflight.js";
10
+ export const verifyCommand = {
11
+ name: "verify",
12
+ summary: "Verify a package spec, lockfile, or local artifact.",
13
+ usage: "dg verify <registry:package[@version]|path|lockfile> [--verbose] [--json|--sarif] [--output <path>]",
14
+ args: [
15
+ { name: "<target>", summary: "registry:package[@version] (npm:react), a local path, an artifact (.tgz/.whl), or a lockfile." }
16
+ ],
17
+ flags: [
18
+ { flag: "--verbose", summary: "Show every finding, not just the top ones (alias -v)." },
19
+ { flag: "--json", summary: "Machine-readable JSON report." },
20
+ { flag: "--sarif", summary: "SARIF report for code-scanning tools." },
21
+ { flag: "--output", value: "<path>", summary: "Write the report to a file instead of stdout (alias -o)." }
22
+ ],
23
+ examples: ["dg verify npm:react", "dg verify pypi:requests@2.31.0", "dg verify ./pkg.tgz --verbose", "dg verify package-lock.json --json"],
24
+ details: [
25
+ "dg verify npm:react (or pypi:requests, with an optional @version — defaults to latest) runs a real scanner check on a published package before you install it. This is a Pro/Team feature and requires dg login.",
26
+ "Local paths, workspaces, tgz/zip/wheel artifacts, and lockfiles are verified offline as advisory preflight (free); proxy enforcement remains authoritative for network artifact fetches."
27
+ ],
28
+ handler: (context) => {
29
+ const parsed = parseVerifyArgs(context.args);
30
+ if ("error" in parsed) {
31
+ return {
32
+ exitCode: EXIT_USAGE,
33
+ stdout: "",
34
+ stderr: `dg verify: ${parsed.error}. Usage: dg verify <spec|path|lockfile> [--verbose] [--json|--sarif] [--output <path>]\n`
35
+ };
36
+ }
37
+ let report;
38
+ try {
39
+ report = verifyTarget(parsed);
40
+ }
41
+ catch (error) {
42
+ return {
43
+ exitCode: EXIT_UNAVAILABLE,
44
+ stdout: "",
45
+ stderr: `dg verify could not verify ${parsed.target}: ${error instanceof Error ? error.message : "unknown verify error"}\n`
46
+ };
47
+ }
48
+ const rendered = renderVerifyReport(report, parsed.format, parsed.verbose);
49
+ if (parsed.outputPath) {
50
+ try {
51
+ writeFileSync(resolve(parsed.outputPath), rendered, "utf8");
52
+ }
53
+ catch (error) {
54
+ return {
55
+ exitCode: 1,
56
+ stdout: "",
57
+ stderr: `dg verify could not write ${parsed.outputPath}: ${error instanceof Error ? error.message : "unknown write error"}\n`
58
+ };
59
+ }
60
+ return {
61
+ exitCode: exitCodeForReport(report),
62
+ stdout: `Wrote ${parsed.format} verify report to ${parsed.outputPath}\n`,
63
+ stderr: ""
64
+ };
65
+ }
66
+ return {
67
+ exitCode: exitCodeForReport(report),
68
+ stdout: rendered,
69
+ stderr: ""
70
+ };
71
+ }
72
+ };
73
+ function parseVerifyArgs(args) {
74
+ let format = "text";
75
+ let outputPath = null;
76
+ let target = null;
77
+ let verbose = false;
78
+ for (let index = 0; index < args.length; index += 1) {
79
+ const arg = args[index];
80
+ if (!arg) {
81
+ return { error: "empty argument" };
82
+ }
83
+ if (arg === "--json") {
84
+ if (format !== "text") {
85
+ return { error: "choose only one output format" };
86
+ }
87
+ format = "json";
88
+ continue;
89
+ }
90
+ if (arg === "--sarif") {
91
+ if (format !== "text") {
92
+ return { error: "choose only one output format" };
93
+ }
94
+ format = "sarif";
95
+ continue;
96
+ }
97
+ if (arg === "--verbose" || arg === "-v") {
98
+ verbose = true;
99
+ continue;
100
+ }
101
+ if (arg === "--output" || arg === "-o") {
102
+ const next = args[index + 1];
103
+ if (!next) {
104
+ return { error: `${arg} requires a path` };
105
+ }
106
+ outputPath = next;
107
+ index += 1;
108
+ continue;
109
+ }
110
+ if (arg.startsWith("-")) {
111
+ return { error: `unknown option '${arg}'` };
112
+ }
113
+ if (target) {
114
+ return { error: "verify accepts exactly one target" };
115
+ }
116
+ target = arg;
117
+ }
118
+ if (!target) {
119
+ return { error: "missing target" };
120
+ }
121
+ return {
122
+ format,
123
+ outputPath,
124
+ target,
125
+ verbose
126
+ };
127
+ }
128
+ function verifyTarget(parsed) {
129
+ if (isSupportedLockfilePath(parsed.target)) {
130
+ return verifyLockfile(parsed.target);
131
+ }
132
+ if (existsSync(resolve(parsed.target))) {
133
+ return verifyLocalTarget(parsed.target);
134
+ }
135
+ return verifyPackageSpec(parsed.target);
136
+ }
137
+ function renderVerifyReport(report, format, verbose) {
138
+ if (format === "json") {
139
+ return renderVerifyJson(report);
140
+ }
141
+ if (format === "sarif") {
142
+ return renderVerifySarif(report);
143
+ }
144
+ return renderVerifyText(report, createTheme(resolvePresentation().color), verbose);
145
+ }
146
+ function exitCodeForReport(report) {
147
+ if (report.status === "block") {
148
+ return 2;
149
+ }
150
+ return report.status === "warn" || report.status === "error" ? 1 : 0;
151
+ }
@@ -0,0 +1,22 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ function readPackageVersion() {
4
+ const packageJsonPath = fileURLToPath(new URL("../../package.json", import.meta.url));
5
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
6
+ if (!parsed.version) {
7
+ throw new Error("package.json is missing a version field — reinstall @westbayberry/dg");
8
+ }
9
+ return parsed.version;
10
+ }
11
+ let cachedVersion;
12
+ export function dgVersion() {
13
+ cachedVersion ??= readPackageVersion();
14
+ return cachedVersion;
15
+ }
16
+ export function versionResult() {
17
+ return {
18
+ exitCode: 0,
19
+ stdout: `dg ${dgVersion()}\n`,
20
+ stderr: ""
21
+ };
22
+ }
@@ -0,0 +1,55 @@
1
+ import { runPackageManager } from "../launcher/run.js";
2
+ import { packageManagerNames } from "../launcher/classify.js";
3
+ import { optionalPackageManagerNames, optionalSupportGate } from "../setup/optional-support.js";
4
+ const gatedPackageManagers = optionalPackageManagerNames();
5
+ export const packageManagerCommandNames = packageManagerNames();
6
+ export function packageManagerCommands() {
7
+ return packageManagerCommandNames.map((name) => ({
8
+ name,
9
+ summary: `Run ${name} through dg prefix-mode routing.`,
10
+ usage: `dg ${name} [--dg-force-install] [...args]`,
11
+ args: [{ name: "[...args]", summary: `Arguments passed straight through to ${name}.` }],
12
+ flags: [{ flag: "--dg-force-install", summary: "Proceed past a dg block where your policy permits an override." }],
13
+ examples: [`dg ${name} <args>`, `dg ${name} <args> --dg-force-install`],
14
+ details: commandDetails(name),
15
+ handler: (context) => {
16
+ const parsed = parsePrefixControlArgs(context.args);
17
+ return runPackageManager(name, parsed.args, {
18
+ onStdout: (chunk) => process.stdout.write(chunk),
19
+ onStderr: (chunk) => process.stderr.write(chunk),
20
+ ...(parsed.forceOverride ? { forceOverride: parsed.forceOverride } : {})
21
+ });
22
+ }
23
+ }));
24
+ }
25
+ function commandDetails(name) {
26
+ const details = ["Protected fetch and install commands start enforcement; passthrough commands do not."];
27
+ if (gatedPackageManagers.includes(name)) {
28
+ details.push(optionalSupportGate(name).message);
29
+ }
30
+ else if (name === "yarn") {
31
+ details.push("This build claims Yarn classic routing only. Yarn Berry remains gated and unclaimed until its explicit support gate passes.");
32
+ }
33
+ else {
34
+ details.push("Support for this ecosystem is required before the install-firewall task can complete.");
35
+ }
36
+ return details;
37
+ }
38
+ function parsePrefixControlArgs(args) {
39
+ const childArgs = [];
40
+ let force = false;
41
+ for (const arg of args) {
42
+ if (arg === "--dg-force-install") {
43
+ force = true;
44
+ continue;
45
+ }
46
+ childArgs.push(arg);
47
+ }
48
+ if (!force) {
49
+ return { args: childArgs };
50
+ }
51
+ return {
52
+ args: childArgs,
53
+ forceOverride: { force: true }
54
+ };
55
+ }