neon 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/README.md +34 -27
- package/dist/commands/bootstrap.js +19 -17
- package/dist/commands/config.js +38 -17
- package/dist/commands/dev.js +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/commands/init.js +45 -302
- package/dist/commands/mcp.js +68 -41
- package/dist/commands/plugins.js +56 -35
- package/dist/commands/skills.js +56 -35
- package/dist/commands/snapshots.js +23 -7
- package/dist/index.js +8 -0
- package/dist/init/auth.js +84 -0
- package/dist/init/cancelled.js +41 -0
- package/dist/init/choices.js +10 -21
- package/dist/init/chrome.js +59 -24
- package/dist/init/copy.js +133 -0
- package/dist/init/detect.js +58 -0
- package/dist/init/funnel.js +115 -0
- package/dist/init/mode.js +30 -0
- package/dist/init/plan.js +135 -32
- package/dist/init/run.js +645 -0
- package/dist/init/tooling.js +104 -22
- package/dist/init/wizard.js +207 -48
- package/dist/mcp/plan.js +1 -1
- package/dist/mcp/wizard.js +1 -27
- package/dist/plugins/plan.js +1 -1
- package/dist/skills/plan.js +1 -1
- package/dist/skills/wizard.js +1 -25
- package/dist/test_utils/npm_env.js +26 -0
- package/dist/update_check_worker.js +47 -0
- package/dist/update_notifier.js +145 -0
- package/dist/utils/package_manager.js +8 -1
- package/dist/utils/service_picker.js +34 -8
- package/package.json +7 -5
- package/dist/init/child.js +0 -37
- package/dist/plugins/wizard.js +0 -36
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isCi } from "./env.js";
|
|
2
|
+
import { log } from "./log.js";
|
|
3
|
+
import { recommendedCliUpgradeCommand } from "./utils/package_manager.js";
|
|
4
|
+
import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import semver from "semver";
|
|
9
|
+
//#region src/update_notifier.ts
|
|
10
|
+
const CHECK_INTERVAL_MS = 864e5;
|
|
11
|
+
const NOTIFY_INTERVAL_MS = 3 * CHECK_INTERVAL_MS;
|
|
12
|
+
const CACHE_FILE = "update-check.json";
|
|
13
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
const parseUpdateCheckCache = (raw) => {
|
|
15
|
+
try {
|
|
16
|
+
const value = JSON.parse(raw);
|
|
17
|
+
if (!isRecord(value) || typeof value.checkedAt !== "number" || !Number.isFinite(value.checkedAt) || value.source !== "homebrew" && value.source !== "npm" || value.latestVersion !== void 0 && typeof value.latestVersion !== "string" || value.notifiedAt !== void 0 && (typeof value.notifiedAt !== "number" || !Number.isFinite(value.notifiedAt))) return;
|
|
18
|
+
return {
|
|
19
|
+
checkedAt: value.checkedAt,
|
|
20
|
+
source: value.source,
|
|
21
|
+
...value.latestVersion === void 0 ? {} : { latestVersion: value.latestVersion },
|
|
22
|
+
...value.notifiedAt === void 0 ? {} : { notifiedAt: value.notifiedAt }
|
|
23
|
+
};
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const readUpdateCheckCache = (cachePath) => {
|
|
29
|
+
try {
|
|
30
|
+
return parseUpdateCheckCache(readFileSync(cachePath, "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const writeUpdateCheckCache = (cachePath, cache) => {
|
|
36
|
+
const temporaryPath = `${cachePath}.${process.pid}.tmp`;
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(temporaryPath, `${JSON.stringify(cache)}\n`, { mode: 384 });
|
|
39
|
+
renameSync(temporaryPath, cachePath);
|
|
40
|
+
return true;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
try {
|
|
43
|
+
rmSync(temporaryPath, { force: true });
|
|
44
|
+
} catch {}
|
|
45
|
+
log.debug("Could not write the CLI update cache: %s", error instanceof Error ? error.message : String(error));
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const formatUpdateNotice = ({ currentVersion, latestVersion, upgradeCommand }) => {
|
|
50
|
+
if (semver.valid(currentVersion) === null || semver.valid(latestVersion) === null || !semver.gt(latestVersion, currentVersion)) return;
|
|
51
|
+
return `Neon CLI update available: ${currentVersion} → ${latestVersion}\n${upgradeCommand}`;
|
|
52
|
+
};
|
|
53
|
+
const shouldRefreshUpdateCheck = (cache, now) => cache === void 0 || now - cache.checkedAt >= CHECK_INTERVAL_MS;
|
|
54
|
+
const cacheForUpdateSource = (cache, source) => cache?.source === source ? cache : void 0;
|
|
55
|
+
const isCurrentBranchProbe = ({ commandPath, currentBranch }) => currentBranch && (commandPath[0] === "status" || commandPath[0] === "config" && commandPath[1] === "status");
|
|
56
|
+
const updateSource = (command) => command.startsWith("brew ") ? "homebrew" : "npm";
|
|
57
|
+
const spawnUpdateWorkerProcess = ({ cachePath, executablePath, source, workerPath }) => {
|
|
58
|
+
const child = spawn(executablePath, [
|
|
59
|
+
workerPath,
|
|
60
|
+
cachePath,
|
|
61
|
+
source
|
|
62
|
+
], {
|
|
63
|
+
detached: true,
|
|
64
|
+
stdio: "ignore",
|
|
65
|
+
windowsHide: true
|
|
66
|
+
});
|
|
67
|
+
child.on("error", (error) => {
|
|
68
|
+
log.debug("CLI update check failed to start: %s", error.message);
|
|
69
|
+
});
|
|
70
|
+
child.unref();
|
|
71
|
+
return child;
|
|
72
|
+
};
|
|
73
|
+
const spawnUpdateWorker = (cachePath, source) => {
|
|
74
|
+
const workerPath = fileURLToPath(new URL("./update_check_worker.js", import.meta.url));
|
|
75
|
+
if (!existsSync(workerPath)) return;
|
|
76
|
+
try {
|
|
77
|
+
spawnUpdateWorkerProcess({
|
|
78
|
+
cachePath,
|
|
79
|
+
executablePath: process.execPath,
|
|
80
|
+
source,
|
|
81
|
+
workerPath
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
log.debug("Could not start the CLI update check: %s", error instanceof Error ? error.message : String(error));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const isPackagedExecutable = () => "pkg" in process;
|
|
88
|
+
const shouldRunUpdateNotifier = ({ commandPath, currentBranch, disabled, isCi: runningInCi, isPackaged, output, stderrIsTty, stdoutIsTty }) => !runningInCi && !disabled && !isPackaged && output === "table" && stdoutIsTty && stderrIsTty && commandPath.length > 0 && commandPath[0] !== "completion" && !isCurrentBranchProbe({
|
|
89
|
+
commandPath,
|
|
90
|
+
currentBranch
|
|
91
|
+
});
|
|
92
|
+
const notifyIfUpdateAvailable = ({ commandPath, configDir, currentBranch, currentVersion, output }) => {
|
|
93
|
+
if (!shouldRunUpdateNotifier({
|
|
94
|
+
commandPath,
|
|
95
|
+
currentBranch,
|
|
96
|
+
disabled: "NO_UPDATE_NOTIFIER" in process.env,
|
|
97
|
+
isCi: isCi(),
|
|
98
|
+
isPackaged: isPackagedExecutable(),
|
|
99
|
+
output,
|
|
100
|
+
stderrIsTty: process.stderr.isTTY === true,
|
|
101
|
+
stdoutIsTty: process.stdout.isTTY === true
|
|
102
|
+
})) return;
|
|
103
|
+
const command = recommendedCliUpgradeCommand(fileURLToPath(import.meta.url));
|
|
104
|
+
if (command === void 0) return;
|
|
105
|
+
const cachePath = join(configDir, CACHE_FILE);
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const source = updateSource(command);
|
|
108
|
+
const cache = cacheForUpdateSource(readUpdateCheckCache(cachePath), source);
|
|
109
|
+
let nextCache = cache;
|
|
110
|
+
if (cache?.latestVersion !== void 0 && (cache.notifiedAt === void 0 || now - cache.notifiedAt >= NOTIFY_INTERVAL_MS)) {
|
|
111
|
+
const notice = formatUpdateNotice({
|
|
112
|
+
currentVersion,
|
|
113
|
+
latestVersion: cache.latestVersion,
|
|
114
|
+
upgradeCommand: command
|
|
115
|
+
});
|
|
116
|
+
if (notice !== void 0) {
|
|
117
|
+
log.warning("%s", notice);
|
|
118
|
+
nextCache = {
|
|
119
|
+
...cache,
|
|
120
|
+
notifiedAt: now
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!shouldRefreshUpdateCheck(cache, now)) {
|
|
125
|
+
if (nextCache !== cache && nextCache !== void 0) writeUpdateCheckCache(cachePath, nextCache);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
nextCache = {
|
|
129
|
+
...nextCache,
|
|
130
|
+
checkedAt: now,
|
|
131
|
+
source
|
|
132
|
+
};
|
|
133
|
+
if (writeUpdateCheckCache(cachePath, nextCache)) spawnUpdateWorker(cachePath, source);
|
|
134
|
+
};
|
|
135
|
+
const recordLatestVersion = (cachePath, source, latestVersion) => {
|
|
136
|
+
if (semver.valid(latestVersion) === null) return;
|
|
137
|
+
const cache = cacheForUpdateSource(readUpdateCheckCache(cachePath), source);
|
|
138
|
+
if (cache === void 0) return;
|
|
139
|
+
writeUpdateCheckCache(cachePath, {
|
|
140
|
+
...cache,
|
|
141
|
+
latestVersion
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
145
|
+
export { cacheForUpdateSource, formatUpdateNotice, notifyIfUpdateAvailable, parseUpdateCheckCache, recordLatestVersion, shouldRefreshUpdateCheck, shouldRunUpdateNotifier, spawnUpdateWorkerProcess, writeUpdateCheckCache };
|
|
@@ -159,6 +159,13 @@ const resolvePackageManager = (cwd) => inferPackageManager(cwd) ?? installedPack
|
|
|
159
159
|
* as {@link resolvePackageManager} minus the lockfile walk.
|
|
160
160
|
*/
|
|
161
161
|
const resolveInvokingPackageManager = () => detectInvokingPackageManager() ?? installedPackageManagers()[0] ?? "npm";
|
|
162
|
+
const recommendedCliUpgradeCommand = (modulePath) => {
|
|
163
|
+
const path = modulePath.replace(/\\/g, "/").toLowerCase();
|
|
164
|
+
if (path.includes("/cellar/neonctl/")) return "brew upgrade neonctl";
|
|
165
|
+
if (path.includes("/.bun/install/global/node_modules/neon/")) return "bun i -g neon@latest";
|
|
166
|
+
if (path.includes("/pnpm/global/") && path.includes("/.pnpm/neon@") && path.includes("/node_modules/neon/")) return "pnpm i -g neon@latest";
|
|
167
|
+
if (path.includes("/lib/node_modules/neon/") || path.includes("/npm/node_modules/neon/")) return "npm i -g neon@latest";
|
|
168
|
+
};
|
|
162
169
|
/**
|
|
163
170
|
* The argv for an install with `pm`: every dependency in the manifest when
|
|
164
171
|
* `packages` is empty, otherwise those packages added to it. npm spells the add
|
|
@@ -326,4 +333,4 @@ const runCommand = (cmd, args, cwd, env) => new Promise((resolvePromise) => {
|
|
|
326
333
|
});
|
|
327
334
|
});
|
|
328
335
|
//#endregion
|
|
329
|
-
export { DO_NOT_SUBSTITUTE_HINT, MISSING_BINARY_HINT, commandEnv, detectInvokingPackageManager, detectProjectPackageManager, formatExecCommand, formatInstallCommand, globalInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolveInvokingPackageManager, resolvePackageManager, runCommand };
|
|
336
|
+
export { DO_NOT_SUBSTITUTE_HINT, MISSING_BINARY_HINT, commandEnv, detectInvokingPackageManager, detectProjectPackageManager, formatExecCommand, formatInstallCommand, globalInstallCommand, inferPackageManager, installArgs, installedPackageManagers, recommendedCliUpgradeCommand, resolveInvokingPackageManager, resolvePackageManager, runCommand };
|
|
@@ -2,9 +2,30 @@ import { CONFIG_INIT_SERVICES } from "../config_template.js";
|
|
|
2
2
|
import prompts from "prompts";
|
|
3
3
|
//#region src/utils/service_picker.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
5
|
+
* Re-select Postgres after each render. `prompts` lets space unselect a selected
|
|
6
|
+
* row even when `disabled` is set, and `disabled` also strikethroughs the title.
|
|
7
|
+
*/
|
|
8
|
+
const keepPostgresSelected = (prompt) => {
|
|
9
|
+
if (typeof prompt !== "object" || prompt === null || !("value" in prompt)) return;
|
|
10
|
+
const rows = prompt.value;
|
|
11
|
+
if (!Array.isArray(rows)) return;
|
|
12
|
+
for (const row of rows) {
|
|
13
|
+
if (!isChoiceRow(row) || row.value !== "postgres") continue;
|
|
14
|
+
row.selected = true;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const isChoiceRow = (row) => typeof row === "object" && row !== null && "value" in row && "selected" in row;
|
|
18
|
+
const POSTGRES_SERVICE_CHOICE = {
|
|
19
|
+
value: "postgres",
|
|
20
|
+
title: "Postgres (always included)",
|
|
21
|
+
description: "Every Neon project includes a Postgres database.",
|
|
22
|
+
selected: true
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The picker's rows, in {@link CONFIG_INIT_SERVICES} order after the locked Postgres
|
|
26
|
+
* row. Titles use the product names from the CLI's README ("Managed Better Auth",
|
|
27
|
+
* "Object Storage") rather than the `neon.ts` field names, since this is the list a
|
|
28
|
+
* user reads before they've seen a policy.
|
|
8
29
|
*/
|
|
9
30
|
const CHOICES = [
|
|
10
31
|
{
|
|
@@ -41,7 +62,7 @@ const CHOICES = [
|
|
|
41
62
|
* Callers guard the TTY themselves (see `initCmd`); this function assumes it may prompt.
|
|
42
63
|
*/
|
|
43
64
|
const pickServicesInteractively = async () => {
|
|
44
|
-
const
|
|
65
|
+
const question = {
|
|
45
66
|
onState: (state) => {
|
|
46
67
|
if (state.aborted) {
|
|
47
68
|
process.stdout.write("\x1B[?25h");
|
|
@@ -49,18 +70,23 @@ const pickServicesInteractively = async () => {
|
|
|
49
70
|
process.exit(1);
|
|
50
71
|
}
|
|
51
72
|
},
|
|
73
|
+
onRender() {
|
|
74
|
+
keepPostgresSelected(this);
|
|
75
|
+
},
|
|
52
76
|
type: "multiselect",
|
|
53
77
|
name: "services",
|
|
54
78
|
message: "Which Neon services should neon.ts declare? (space to toggle, enter to confirm)",
|
|
55
79
|
instructions: false,
|
|
56
|
-
|
|
80
|
+
cursor: 1,
|
|
81
|
+
choices: [POSTGRES_SERVICE_CHOICE, ...CHOICES.map((choice) => ({
|
|
57
82
|
value: choice.value,
|
|
58
83
|
title: choice.title,
|
|
59
84
|
description: choice.description
|
|
60
|
-
}))
|
|
61
|
-
}
|
|
85
|
+
}))]
|
|
86
|
+
};
|
|
87
|
+
const { services } = await prompts(question);
|
|
62
88
|
if (!Array.isArray(services)) throw new Error("Aborted: no services selected.");
|
|
63
89
|
return CONFIG_INIT_SERVICES.filter((service) => services.includes(service));
|
|
64
90
|
};
|
|
65
91
|
//#endregion
|
|
66
|
-
export { pickServicesInteractively };
|
|
92
|
+
export { POSTGRES_SERVICE_CHOICE, keepPostgresSelected, pickServicesInteractively };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -58,14 +58,15 @@
|
|
|
58
58
|
"pg-protocol": "^1.14.0",
|
|
59
59
|
"picocolors": "^1.1.1",
|
|
60
60
|
"prompts": "2.4.2",
|
|
61
|
+
"semver": "7.7.2",
|
|
61
62
|
"undici": "^7.28.0",
|
|
62
63
|
"which": "3.0.1",
|
|
63
64
|
"yaml": "^2.9.0",
|
|
64
65
|
"yargs": "17.7.2",
|
|
65
66
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/config": "1.
|
|
67
|
-
"@neon/
|
|
68
|
-
"@neon/
|
|
67
|
+
"@neon/config-runtime": "1.6.3",
|
|
68
|
+
"@neon/config": "1.7.3",
|
|
69
|
+
"@neon/sdk": "6.1.0"
|
|
69
70
|
},
|
|
70
71
|
"optionalDependencies": {
|
|
71
72
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
"@types/express": "4.17.21",
|
|
83
84
|
"@types/node": "^20.19.0",
|
|
84
85
|
"@types/prompts": "2.4.9",
|
|
86
|
+
"@types/semver": "7.7.1",
|
|
85
87
|
"@types/which": "3.0.4",
|
|
86
88
|
"@types/yargs": "^17.0.35",
|
|
87
89
|
"@vitest/coverage-v8": "^3.0.9",
|
|
@@ -98,7 +100,7 @@
|
|
|
98
100
|
"typescript": "^5.9.0",
|
|
99
101
|
"vitest": "^3.0.9",
|
|
100
102
|
"@neon-internals/cli-core": "0.0.0",
|
|
101
|
-
"@neon-internals/env-core": "0.0.
|
|
103
|
+
"@neon-internals/env-core": "0.0.22",
|
|
102
104
|
"@neon/e2e-harness": "0.0.0",
|
|
103
105
|
"@neon/functions": "0.11.0"
|
|
104
106
|
},
|
package/dist/init/child.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
//#region src/init/child.ts
|
|
3
|
-
const AUTH_CHILD = /* @__PURE__ */ new Set([
|
|
4
|
-
"bootstrap",
|
|
5
|
-
"link",
|
|
6
|
-
"mcp",
|
|
7
|
-
"env"
|
|
8
|
-
]);
|
|
9
|
-
const initChildEnv = (command, overlay, base = process.env) => {
|
|
10
|
-
const child = { ...base };
|
|
11
|
-
if (command === void 0 || !AUTH_CHILD.has(command)) {
|
|
12
|
-
for (const key of Object.keys(child)) if (key.toUpperCase() === "NEON_API_KEY") delete child[key];
|
|
13
|
-
}
|
|
14
|
-
return overlay ? {
|
|
15
|
-
...child,
|
|
16
|
-
...overlay
|
|
17
|
-
} : child;
|
|
18
|
-
};
|
|
19
|
-
const spawnCliChild = async (argv, cwd, overlay) => {
|
|
20
|
-
const cli = process.argv[1];
|
|
21
|
-
if (!cli) throw new Error("Cannot re-exec the Neon CLI: process.argv[1] is missing.");
|
|
22
|
-
return new Promise((resolve) => {
|
|
23
|
-
const child = spawn(process.execPath, [cli, ...argv], {
|
|
24
|
-
cwd,
|
|
25
|
-
stdio: "inherit",
|
|
26
|
-
env: initChildEnv(argv[0], overlay)
|
|
27
|
-
});
|
|
28
|
-
child.on("error", () => {
|
|
29
|
-
resolve(false);
|
|
30
|
-
});
|
|
31
|
-
child.on("close", (code) => {
|
|
32
|
-
resolve(code === 0);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
};
|
|
36
|
-
//#endregion
|
|
37
|
-
export { AUTH_CHILD, initChildEnv, spawnCliChild };
|
package/dist/plugins/wizard.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "../mcp/agents.js";
|
|
2
|
-
import "../init/agents.js";
|
|
3
|
-
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
4
|
-
import { NEON_PLUGIN_NAME } from "./run.js";
|
|
5
|
-
import prompts from "prompts";
|
|
6
|
-
//#region src/plugins/wizard.ts
|
|
7
|
-
const restoreCursorOnAbort = (state) => {
|
|
8
|
-
if (state.aborted) {
|
|
9
|
-
process.stdout.write("\x1B[?25h");
|
|
10
|
-
process.stdout.write("\n");
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
const pluginsInstallSummary = (options) => {
|
|
15
|
-
const rows = [
|
|
16
|
-
["Scope", options.scope === "project" ? "project" : "user"],
|
|
17
|
-
["Agents", options.agents.map(getAgentDisplayName).join(", ")],
|
|
18
|
-
["Plugin", NEON_PLUGIN_NAME]
|
|
19
|
-
];
|
|
20
|
-
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
21
|
-
return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
|
|
22
|
-
};
|
|
23
|
-
const confirmPluginsInstall = async (options) => {
|
|
24
|
-
if (!canPickAgentsInteractively()) return true;
|
|
25
|
-
process.stdout.write(`\n${pluginsInstallSummary(options)}\n\n`);
|
|
26
|
-
const { ok } = await prompts({
|
|
27
|
-
onState: restoreCursorOnAbort,
|
|
28
|
-
type: "confirm",
|
|
29
|
-
name: "ok",
|
|
30
|
-
message: "Install the Neon plugin into these agents?",
|
|
31
|
-
initial: true
|
|
32
|
-
});
|
|
33
|
-
return ok === true;
|
|
34
|
-
};
|
|
35
|
-
//#endregion
|
|
36
|
-
export { confirmPluginsInstall, pluginsInstallSummary };
|