@reliverse/rempts-core 1.6.1 → 2.3.2
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 +398 -102
- package/dist/cli.d.ts +32 -0
- package/dist/cli.js +731 -0
- package/dist/config-loader.d.ts +42 -0
- package/dist/config-loader.js +20 -0
- package/dist/config.d.ts +99 -0
- package/dist/config.js +188 -0
- package/dist/file-loader.d.ts +43 -0
- package/dist/file-loader.js +199 -0
- package/dist/global-flags.d.ts +36 -0
- package/dist/global-flags.js +36 -0
- package/dist/mod.d.ts +13 -0
- package/dist/mod.js +19 -0
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +137 -0
- package/dist/plugin/context.d.ts +13 -0
- package/dist/plugin/context.js +53 -0
- package/dist/plugin/create.d.ts +92 -0
- package/dist/plugin/create.js +61 -0
- package/dist/plugin/loader.d.ts +12 -0
- package/dist/plugin/loader.js +65 -0
- package/dist/plugin/manager.d.ts +53 -0
- package/dist/plugin/manager.js +135 -0
- package/dist/plugin/mod.d.ts +10 -0
- package/dist/plugin/mod.js +27 -0
- package/dist/plugin/store.d.ts +45 -0
- package/dist/plugin/store.js +60 -0
- package/dist/plugin/testing.d.ts +38 -0
- package/dist/plugin/testing.js +175 -0
- package/dist/plugin/types.d.ts +146 -0
- package/dist/tui/registry.d.ts +8 -0
- package/dist/tui/registry.js +10 -0
- package/dist/tui/types.d.ts +58 -0
- package/dist/tui/types.js +10 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.js +25 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/merge.d.ts +13 -0
- package/dist/utils/merge.js +25 -0
- package/dist/utils/mod.d.ts +6 -0
- package/dist/utils/mod.js +2 -0
- package/dist/utils/type-helpers.d.ts +41 -0
- package/dist/utils/type-helpers.js +0 -0
- package/dist/validation.d.ts +30 -0
- package/dist/validation.js +121 -0
- package/package.json +47 -44
- package/src/cli.ts +1049 -0
- package/src/config-loader.ts +71 -0
- package/src/config.ts +270 -0
- package/src/file-loader.ts +346 -0
- package/src/global-flags.ts +50 -0
- package/src/mod.ts +74 -0
- package/src/parser.ts +212 -0
- package/src/plugin/context.ts +88 -0
- package/src/plugin/create.ts +174 -0
- package/src/plugin/loader.ts +111 -0
- package/src/plugin/manager.ts +244 -0
- package/src/plugin/mod.ts +51 -0
- package/src/plugin/store.ts +124 -0
- package/src/plugin/testing.ts +236 -0
- package/src/plugin/types.ts +206 -0
- package/src/tui/registry.ts +22 -0
- package/src/tui/types.ts +79 -0
- package/src/types.ts +285 -0
- package/src/utils/logger.ts +43 -0
- package/src/utils/merge.ts +54 -0
- package/src/utils/mod.ts +7 -0
- package/src/utils/type-helpers.ts +151 -0
- package/src/validation.ts +177 -0
- package/LICENSE +0 -21
- package/bin/core-impl/anykey/anykey-mod.d.ts +0 -12
- package/bin/core-impl/anykey/anykey-mod.js +0 -125
- package/bin/core-impl/date/date.d.ts +0 -2
- package/bin/core-impl/date/date.js +0 -236
- package/bin/core-impl/editor/editor-mod.d.ts +0 -25
- package/bin/core-impl/editor/editor-mod.js +0 -896
- package/bin/core-impl/figures/figures-mod.d.ts +0 -233
- package/bin/core-impl/figures/figures-mod.js +0 -286
- package/bin/core-impl/figures/figures.test.d.ts +0 -1
- package/bin/core-impl/figures/figures.test.js +0 -474
- package/bin/core-impl/input/confirm-prompt.d.ts +0 -5
- package/bin/core-impl/input/confirm-prompt.js +0 -173
- package/bin/core-impl/input/input-prompt.d.ts +0 -16
- package/bin/core-impl/input/input-prompt.js +0 -370
- package/bin/core-impl/launcher/_parser.d.ts +0 -2
- package/bin/core-impl/launcher/_parser.js +0 -122
- package/bin/core-impl/launcher/_utils.d.ts +0 -8
- package/bin/core-impl/launcher/_utils.js +0 -29
- package/bin/core-impl/launcher/args.d.ts +0 -3
- package/bin/core-impl/launcher/args.js +0 -89
- package/bin/core-impl/launcher/command.d.ts +0 -8
- package/bin/core-impl/launcher/command.js +0 -68
- package/bin/core-impl/launcher/launcher-mod.d.ts +0 -8
- package/bin/core-impl/launcher/launcher-mod.js +0 -34
- package/bin/core-impl/launcher/usage.d.ts +0 -3
- package/bin/core-impl/launcher/usage.js +0 -104
- package/bin/core-impl/msg-fmt/colors.d.ts +0 -30
- package/bin/core-impl/msg-fmt/colors.js +0 -42
- package/bin/core-impl/msg-fmt/logger.d.ts +0 -17
- package/bin/core-impl/msg-fmt/logger.js +0 -106
- package/bin/core-impl/msg-fmt/mapping.d.ts +0 -3
- package/bin/core-impl/msg-fmt/mapping.js +0 -49
- package/bin/core-impl/msg-fmt/messages.d.ts +0 -35
- package/bin/core-impl/msg-fmt/messages.js +0 -314
- package/bin/core-impl/msg-fmt/terminal.d.ts +0 -15
- package/bin/core-impl/msg-fmt/terminal.js +0 -59
- package/bin/core-impl/msg-fmt/variants.d.ts +0 -11
- package/bin/core-impl/msg-fmt/variants.js +0 -52
- package/bin/core-impl/next-steps/next-steps.d.ts +0 -14
- package/bin/core-impl/next-steps/next-steps.js +0 -24
- package/bin/core-impl/number/number-mod.d.ts +0 -28
- package/bin/core-impl/number/number-mod.js +0 -197
- package/bin/core-impl/results/results.d.ts +0 -7
- package/bin/core-impl/results/results.js +0 -27
- package/bin/core-impl/select/multiselect-prompt.d.ts +0 -2
- package/bin/core-impl/select/multiselect-prompt.js +0 -341
- package/bin/core-impl/select/nummultiselect-prompt.d.ts +0 -6
- package/bin/core-impl/select/nummultiselect-prompt.js +0 -105
- package/bin/core-impl/select/numselect-prompt.d.ts +0 -7
- package/bin/core-impl/select/numselect-prompt.js +0 -115
- package/bin/core-impl/select/select-prompt.d.ts +0 -33
- package/bin/core-impl/select/select-prompt.js +0 -302
- package/bin/core-impl/select/toggle-prompt.d.ts +0 -5
- package/bin/core-impl/select/toggle-prompt.js +0 -208
- package/bin/core-impl/st-end/end.d.ts +0 -2
- package/bin/core-impl/st-end/end.js +0 -42
- package/bin/core-impl/st-end/start.d.ts +0 -17
- package/bin/core-impl/st-end/start.js +0 -66
- package/bin/core-impl/task/progress.d.ts +0 -2
- package/bin/core-impl/task/progress.js +0 -57
- package/bin/core-impl/task/spinner.d.ts +0 -15
- package/bin/core-impl/task/spinner.js +0 -110
- package/bin/core-impl/utils/colorize.d.ts +0 -2
- package/bin/core-impl/utils/colorize.js +0 -134
- package/bin/core-impl/utils/errors.d.ts +0 -1
- package/bin/core-impl/utils/errors.js +0 -15
- package/bin/core-impl/utils/prevent.d.ts +0 -10
- package/bin/core-impl/utils/prevent.js +0 -69
- package/bin/core-impl/utils/prompt-end.d.ts +0 -8
- package/bin/core-impl/utils/prompt-end.js +0 -33
- package/bin/core-impl/utils/stream-text.d.ts +0 -18
- package/bin/core-impl/utils/stream-text.js +0 -136
- package/bin/core-impl/utils/system.d.ts +0 -6
- package/bin/core-impl/utils/system.js +0 -7
- package/bin/core-impl/utils/validate.d.ts +0 -22
- package/bin/core-impl/utils/validate.js +0 -17
- package/bin/core-impl/visual/animate/animate.d.ts +0 -14
- package/bin/core-impl/visual/animate/animate.js +0 -64
- package/bin/core-impl/visual/ascii-art/ascii-art.d.ts +0 -6
- package/bin/core-impl/visual/ascii-art/ascii-art.js +0 -12
- package/bin/core-types.d.ts +0 -434
- package/bin/main.d.ts +0 -41
- package/bin/main.js +0 -96
- /package/{bin/core-types.js → dist/plugin/types.js} +0 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { relico } from "@reliverse/relico";
|
|
4
|
+
import { getDotPath, SchemaError } from "@standard-schema/utils";
|
|
5
|
+
import { remptsConfigSchema, remptsConfigStrictSchema } from "./config.js";
|
|
6
|
+
import { loadConfig } from "./config-loader.js";
|
|
7
|
+
import { createFileCommandLoader } from "./file-loader.js";
|
|
8
|
+
import { GLOBAL_FLAGS } from "./global-flags.js";
|
|
9
|
+
import { parseArgs } from "./parser.js";
|
|
10
|
+
import {
|
|
11
|
+
createPluginManager,
|
|
12
|
+
loadPlugins,
|
|
13
|
+
runAfterCommand,
|
|
14
|
+
runBeforeCommand,
|
|
15
|
+
runConfigResolved,
|
|
16
|
+
runSetup
|
|
17
|
+
} from "./plugin/manager.js";
|
|
18
|
+
import { getTuiRenderer } from "./tui/registry.js";
|
|
19
|
+
export async function createApp(options = {}) {
|
|
20
|
+
const {
|
|
21
|
+
config: configOverride,
|
|
22
|
+
defaultCommand,
|
|
23
|
+
autoInit = true,
|
|
24
|
+
configDir: customConfigDir,
|
|
25
|
+
entryFile
|
|
26
|
+
} = options;
|
|
27
|
+
let configDir = customConfigDir || process.cwd();
|
|
28
|
+
if (!customConfigDir) {
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const cwdIndex = args.indexOf("--cwd");
|
|
31
|
+
if (cwdIndex !== -1 && cwdIndex + 1 < args.length && args[cwdIndex + 1]) {
|
|
32
|
+
const cwdArg = args[cwdIndex + 1];
|
|
33
|
+
configDir = cwdArg.startsWith("/") ? cwdArg : resolve(process.cwd(), cwdArg);
|
|
34
|
+
args.splice(cwdIndex, 2);
|
|
35
|
+
process.argv = [process.argv[0] || "", process.argv[1] || "", ...args];
|
|
36
|
+
} else {
|
|
37
|
+
try {
|
|
38
|
+
await loadConfig(process.cwd());
|
|
39
|
+
configDir = process.cwd();
|
|
40
|
+
} catch {
|
|
41
|
+
const monorepoConfigDir = resolve(process.cwd(), "apps/dler");
|
|
42
|
+
try {
|
|
43
|
+
await loadConfig(monorepoConfigDir);
|
|
44
|
+
configDir = monorepoConfigDir;
|
|
45
|
+
} catch {
|
|
46
|
+
configDir = process.cwd();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let loadedConfigData = null;
|
|
52
|
+
try {
|
|
53
|
+
loadedConfigData = await loadConfig(configDir);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
const finalConfigOverride = (() => {
|
|
57
|
+
let baseConfig = loadedConfigData || configOverride;
|
|
58
|
+
if (loadedConfigData && configOverride) {
|
|
59
|
+
const { plugins: overridePlugins, ...overrideRest } = configOverride;
|
|
60
|
+
baseConfig = {
|
|
61
|
+
...loadedConfigData,
|
|
62
|
+
...overrideRest,
|
|
63
|
+
// Override plugins only if explicitly provided in configOverride
|
|
64
|
+
...overridePlugins !== void 0 ? { plugins: overridePlugins } : {}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function resolveConfigPaths(obj, baseDir) {
|
|
68
|
+
if (typeof obj === "string" && obj.startsWith("./")) {
|
|
69
|
+
return resolve(baseDir, obj);
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(obj)) {
|
|
72
|
+
return obj.map((item) => resolveConfigPaths(item, baseDir));
|
|
73
|
+
}
|
|
74
|
+
if (obj && typeof obj === "object") {
|
|
75
|
+
const result = {};
|
|
76
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
77
|
+
result[key] = resolveConfigPaths(value, baseDir);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
return obj;
|
|
82
|
+
}
|
|
83
|
+
if (baseConfig) {
|
|
84
|
+
baseConfig = resolveConfigPaths(baseConfig, configDir);
|
|
85
|
+
}
|
|
86
|
+
if (baseConfig?.commands?.directory && typeof baseConfig.commands.directory === "string") {
|
|
87
|
+
baseConfig.commands.directory = baseConfig.commands.directory.startsWith(".") ? resolve(configDir, baseConfig.commands.directory) : baseConfig.commands.directory;
|
|
88
|
+
}
|
|
89
|
+
return baseConfig;
|
|
90
|
+
})();
|
|
91
|
+
const cli = await createCLI(finalConfigOverride || {}, entryFile);
|
|
92
|
+
if (autoInit) {
|
|
93
|
+
await cli.init();
|
|
94
|
+
}
|
|
95
|
+
if (defaultCommand) {
|
|
96
|
+
const originalRun = cli.run.bind(cli);
|
|
97
|
+
cli.run = async (argv = process.argv.slice(2)) => {
|
|
98
|
+
if (argv.length === 0 || argv[0]?.startsWith("-")) {
|
|
99
|
+
process.argv.splice(2, 0, defaultCommand);
|
|
100
|
+
argv = [defaultCommand, ...argv];
|
|
101
|
+
} else if (argv[0] && !argv[0].startsWith("-") && argv[0] !== defaultCommand) {
|
|
102
|
+
process.argv.splice(2, 0, defaultCommand);
|
|
103
|
+
argv = [defaultCommand, ...argv];
|
|
104
|
+
}
|
|
105
|
+
return originalRun(argv);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return cli;
|
|
109
|
+
}
|
|
110
|
+
function getEntryFileFromStack() {
|
|
111
|
+
try {
|
|
112
|
+
const stack = new Error("Stack trace").stack;
|
|
113
|
+
if (!stack) return void 0;
|
|
114
|
+
const lines = stack.split("\n");
|
|
115
|
+
for (let i = 2; i < lines.length; i++) {
|
|
116
|
+
const line = lines[i];
|
|
117
|
+
if (!line) continue;
|
|
118
|
+
const match = line.match(/\(?(file:\/\/\/?|)([^:]+\.(ts|js|mjs)):\d+:\d+\)?/);
|
|
119
|
+
if (match) {
|
|
120
|
+
const filePath = match[2];
|
|
121
|
+
if (filePath && !filePath.includes("rempts-core") && !filePath.includes("node_modules")) {
|
|
122
|
+
if (filePath.startsWith("file://")) {
|
|
123
|
+
return fileURLToPath(filePath);
|
|
124
|
+
}
|
|
125
|
+
return filePath;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch (_error) {
|
|
130
|
+
}
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
export async function createCLI(configOverride, entryFile) {
|
|
134
|
+
let loadedConfigData = null;
|
|
135
|
+
try {
|
|
136
|
+
loadedConfigData = await loadConfig();
|
|
137
|
+
} catch {
|
|
138
|
+
loadedConfigData = null;
|
|
139
|
+
}
|
|
140
|
+
let baseConfig = loadedConfigData || remptsConfigSchema.assert(configOverride || {});
|
|
141
|
+
let cmdsDir;
|
|
142
|
+
const detectedEntryFile = entryFile || getEntryFileFromStack();
|
|
143
|
+
if (detectedEntryFile) {
|
|
144
|
+
const entryFilePath = detectedEntryFile.startsWith("file://") ? fileURLToPath(detectedEntryFile) : resolve(detectedEntryFile);
|
|
145
|
+
const entryDir = dirname(entryFilePath);
|
|
146
|
+
cmdsDir = join(entryDir, "cmds");
|
|
147
|
+
} else {
|
|
148
|
+
if (baseConfig.commands?.directory) {
|
|
149
|
+
cmdsDir = resolve(baseConfig.commands.directory);
|
|
150
|
+
} else {
|
|
151
|
+
cmdsDir = join(process.cwd(), "cmds");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
baseConfig = {
|
|
155
|
+
...baseConfig,
|
|
156
|
+
commands: {
|
|
157
|
+
...baseConfig.commands,
|
|
158
|
+
directory: cmdsDir
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const loadedConfig = baseConfig;
|
|
162
|
+
const mergedConfig = {
|
|
163
|
+
...loadedConfig,
|
|
164
|
+
...configOverride,
|
|
165
|
+
// Deep merge plugins arrays
|
|
166
|
+
plugins: configOverride?.plugins || loadedConfig.plugins || []
|
|
167
|
+
};
|
|
168
|
+
let fullConfig;
|
|
169
|
+
try {
|
|
170
|
+
if (mergedConfig.name && mergedConfig.version) {
|
|
171
|
+
fullConfig = remptsConfigStrictSchema.assert(mergedConfig);
|
|
172
|
+
} else {
|
|
173
|
+
const minimalConfig = {
|
|
174
|
+
name: mergedConfig.name || "cli",
|
|
175
|
+
version: mergedConfig.version || "1.0.0",
|
|
176
|
+
...mergedConfig
|
|
177
|
+
};
|
|
178
|
+
fullConfig = remptsConfigStrictSchema.assert(minimalConfig);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
"[rempts] Invalid config: " + (error instanceof Error ? error.message : String(error))
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const commands = /* @__PURE__ */ new Map();
|
|
186
|
+
const commandSources = /* @__PURE__ */ new Map();
|
|
187
|
+
const subcommandIndex = /* @__PURE__ */ new Map();
|
|
188
|
+
const beforeHooks = [];
|
|
189
|
+
const afterHooks = [];
|
|
190
|
+
let globalHookContext = {};
|
|
191
|
+
function getTerminalInfo() {
|
|
192
|
+
const isInteractive = process.stdout.isTTY;
|
|
193
|
+
const isCI = !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS);
|
|
194
|
+
return {
|
|
195
|
+
width: process.stdout.columns || 80,
|
|
196
|
+
height: process.stdout.rows || 24,
|
|
197
|
+
isInteractive,
|
|
198
|
+
isCI,
|
|
199
|
+
supportsColor: isInteractive && !isCI && process.env.TERM !== "dumb",
|
|
200
|
+
supportsMouse: isInteractive && !isCI && process.env.TERM_PROGRAM !== "Apple_Terminal"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const pluginManagerState = createPluginManager();
|
|
204
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0) {
|
|
205
|
+
await loadPlugins(pluginManagerState, mergedConfig.plugins);
|
|
206
|
+
const { config: updatedConfig, commands: pluginCommands } = await runSetup(
|
|
207
|
+
pluginManagerState,
|
|
208
|
+
fullConfig
|
|
209
|
+
);
|
|
210
|
+
fullConfig = remptsConfigStrictSchema.assert(updatedConfig);
|
|
211
|
+
if (pluginCommands.length > 0) {
|
|
212
|
+
console.warn(
|
|
213
|
+
"Warning: Plugin command registration is deprecated. Rempts is file-based only - register commands via file structure: <cmds-dir>/<cmd-name>/cmd.{ts,js,mjs}"
|
|
214
|
+
);
|
|
215
|
+
pluginCommands.forEach((cmd) => {
|
|
216
|
+
const cmdName = cmd.name || "unknown";
|
|
217
|
+
if (cmdName === "unknown") {
|
|
218
|
+
console.warn("Skipping plugin command without name - use file-based commands instead");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
registerCommand(cmdName, cmd, [], "directory");
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const resolvedConfig = {
|
|
226
|
+
name: fullConfig.name,
|
|
227
|
+
version: fullConfig.version,
|
|
228
|
+
description: fullConfig.description || "",
|
|
229
|
+
commands: fullConfig.commands || {},
|
|
230
|
+
build: fullConfig.build || {
|
|
231
|
+
targets: ["native"],
|
|
232
|
+
compress: false,
|
|
233
|
+
minify: false,
|
|
234
|
+
sourcemap: true
|
|
235
|
+
},
|
|
236
|
+
dev: fullConfig.dev || {
|
|
237
|
+
watch: true,
|
|
238
|
+
inspect: false
|
|
239
|
+
},
|
|
240
|
+
test: fullConfig.test || {
|
|
241
|
+
pattern: ["**/*.test.ts", "**/*.spec.ts"],
|
|
242
|
+
coverage: false,
|
|
243
|
+
watch: false
|
|
244
|
+
},
|
|
245
|
+
workspace: fullConfig.workspace || {
|
|
246
|
+
versionStrategy: "fixed"
|
|
247
|
+
},
|
|
248
|
+
release: fullConfig.release || {
|
|
249
|
+
npm: true,
|
|
250
|
+
github: false,
|
|
251
|
+
tagFormat: "v{{version}}",
|
|
252
|
+
conventionalCommits: true
|
|
253
|
+
},
|
|
254
|
+
plugins: fullConfig.plugins || []
|
|
255
|
+
};
|
|
256
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0) {
|
|
257
|
+
await runConfigResolved(pluginManagerState, resolvedConfig);
|
|
258
|
+
}
|
|
259
|
+
function registerCommand(name, cmd, path = [], source = "directory") {
|
|
260
|
+
const fullName = [...path, name].join(" ");
|
|
261
|
+
if (commands.has(fullName)) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
commands.set(fullName, cmd);
|
|
265
|
+
commandSources.set(fullName, source);
|
|
266
|
+
const nameParts = fullName.split(" ");
|
|
267
|
+
const validParts = nameParts.filter((part) => part.length > 0);
|
|
268
|
+
for (let i = 0; i < validParts.length - 1; i++) {
|
|
269
|
+
const parentName = validParts.slice(0, i + 1).join(" ");
|
|
270
|
+
if (!subcommandIndex.has(parentName)) {
|
|
271
|
+
subcommandIndex.set(parentName, /* @__PURE__ */ new Set());
|
|
272
|
+
}
|
|
273
|
+
subcommandIndex.get(parentName).add(fullName);
|
|
274
|
+
}
|
|
275
|
+
if (cmd.alias) {
|
|
276
|
+
const aliases = Array.isArray(cmd.alias) ? cmd.alias : [cmd.alias];
|
|
277
|
+
aliases.forEach((alias) => {
|
|
278
|
+
const aliasPath = [...path, alias].join(" ");
|
|
279
|
+
if (commands.has(aliasPath)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
commands.set(aliasPath, cmd);
|
|
283
|
+
commandSources.set(aliasPath, source);
|
|
284
|
+
const aliasParts = aliasPath.split(" ").filter((part) => part.length > 0);
|
|
285
|
+
for (let i = 0; i < aliasParts.length - 1; i++) {
|
|
286
|
+
const parentName = aliasParts.slice(0, i + 1).join(" ");
|
|
287
|
+
if (!subcommandIndex.has(parentName)) {
|
|
288
|
+
subcommandIndex.set(parentName, /* @__PURE__ */ new Set());
|
|
289
|
+
}
|
|
290
|
+
subcommandIndex.get(parentName).add(fullName);
|
|
291
|
+
}
|
|
292
|
+
const subcommandNames = subcommandIndex.get(fullName);
|
|
293
|
+
if (subcommandNames) {
|
|
294
|
+
if (!subcommandIndex.has(aliasPath)) {
|
|
295
|
+
subcommandIndex.set(aliasPath, /* @__PURE__ */ new Set());
|
|
296
|
+
}
|
|
297
|
+
for (const subCmdName of subcommandNames) {
|
|
298
|
+
subcommandIndex.get(aliasPath).add(subCmdName);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function findCommand(args) {
|
|
305
|
+
for (let i = args.length; i > 0; i--) {
|
|
306
|
+
const cmdPath = args.slice(0, i).join(" ");
|
|
307
|
+
const command = commands.get(cmdPath);
|
|
308
|
+
if (command) {
|
|
309
|
+
return { command, commandName: cmdPath, remainingArgs: args.slice(i) };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return { command: void 0, commandName: void 0, remainingArgs: args };
|
|
313
|
+
}
|
|
314
|
+
function showHelp(cmdName, cmd, path = []) {
|
|
315
|
+
if (cmd && cmdName) {
|
|
316
|
+
const fullPath = [...path, cmdName].join(" ");
|
|
317
|
+
console.log(relico.bold(`Usage: ${fullConfig.name} ${fullPath} [options]`));
|
|
318
|
+
console.log(`
|
|
319
|
+
${relico.dim(cmd.description)}`);
|
|
320
|
+
if (cmd.options && Object.keys(cmd.options).length > 0) {
|
|
321
|
+
console.log(`
|
|
322
|
+
${relico.bold("Options:")}`);
|
|
323
|
+
for (const [name, opt] of Object.entries(cmd.options)) {
|
|
324
|
+
const option = opt;
|
|
325
|
+
const flag = `--${name}${option.short ? `, -${option.short}` : ""}`;
|
|
326
|
+
const description = option.description || "";
|
|
327
|
+
console.log(` ${relico.yellow(flag.padEnd(20))} ${relico.dim(description)}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const subcommandNames = subcommandIndex.get(fullPath);
|
|
331
|
+
const subCommands = [];
|
|
332
|
+
if (subcommandNames) {
|
|
333
|
+
const parentDepth = fullPath.split(" ").length;
|
|
334
|
+
for (const subCmdFullName of subcommandNames) {
|
|
335
|
+
if (subCmdFullName.split(" ").length === parentDepth + 1) {
|
|
336
|
+
const subCmdName = subCmdFullName.slice(fullPath.length + 1);
|
|
337
|
+
const command = commands.get(subCmdFullName);
|
|
338
|
+
if (command) {
|
|
339
|
+
subCommands.push({ name: subCmdName, command });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (subCommands.length > 0) {
|
|
345
|
+
console.log(`
|
|
346
|
+
${relico.bold("Subcommands:")}`);
|
|
347
|
+
for (const { name: subCmdName, command: subCmd } of subCommands) {
|
|
348
|
+
console.log(` ${relico.green(subCmdName.padEnd(20))} ${relico.dim(subCmd.description)}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
console.log(relico.bold(relico.cyan(`${fullConfig.name} v${fullConfig.version}`)));
|
|
353
|
+
if (fullConfig.description) {
|
|
354
|
+
console.log(relico.dim(fullConfig.description));
|
|
355
|
+
}
|
|
356
|
+
console.log(`
|
|
357
|
+
${relico.bold("Commands:")}`);
|
|
358
|
+
for (const [name, command] of commands) {
|
|
359
|
+
if (!(name.includes(" ") || command.alias?.includes(name))) {
|
|
360
|
+
console.log(` ${relico.green(name.padEnd(20))} ${relico.dim(command.description)}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function ensureRenderAvailable(commandName, command) {
|
|
366
|
+
if (!command.render) {
|
|
367
|
+
throw new Error(`Command ${commandName} does not support TUI rendering.`);
|
|
368
|
+
}
|
|
369
|
+
if (!getTuiRenderer()) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`TUI renderer not registered. Import '@reliverse/rempts-tui/register' or call registerTuiRenderer before running commands with render.`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async function loadFromConfig() {
|
|
376
|
+
if (fullConfig.commands?.directory) {
|
|
377
|
+
try {
|
|
378
|
+
const cmdsDir2 = fullConfig.commands.directory;
|
|
379
|
+
const fileLoader = createFileCommandLoader();
|
|
380
|
+
const commandTree = await fileLoader.loadFromDirectory(cmdsDir2);
|
|
381
|
+
const fileCommands = await fileLoader.loadCommandsFromTree(commandTree);
|
|
382
|
+
fileCommands.forEach(({ name, command }) => {
|
|
383
|
+
registerCommand(name, command, [], "directory");
|
|
384
|
+
});
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error(
|
|
387
|
+
`Failed to load commands from directory ${fullConfig.commands.directory}:`,
|
|
388
|
+
error
|
|
389
|
+
);
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async function runCommandInternal(commandName, command, argv, providedFlags) {
|
|
395
|
+
let context;
|
|
396
|
+
let resultParsed;
|
|
397
|
+
try {
|
|
398
|
+
const mergedOptions = {
|
|
399
|
+
...GLOBAL_FLAGS,
|
|
400
|
+
...command.options || {}
|
|
401
|
+
};
|
|
402
|
+
const parsed = providedFlags ? (() => {
|
|
403
|
+
return parseArgs([], mergedOptions, commandName).then(
|
|
404
|
+
(p) => (Object.assign(p.flags, providedFlags), p)
|
|
405
|
+
);
|
|
406
|
+
})() : parseArgs(argv, mergedOptions, commandName);
|
|
407
|
+
resultParsed = await parsed;
|
|
408
|
+
const { prompt, spinner } = await import("@reliverse/rempts-utils");
|
|
409
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0) {
|
|
410
|
+
context = await runBeforeCommand(
|
|
411
|
+
pluginManagerState,
|
|
412
|
+
commandName,
|
|
413
|
+
command,
|
|
414
|
+
providedFlags ? [] : resultParsed.positional,
|
|
415
|
+
resultParsed.flags
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
if (beforeHooks.length > 0) {
|
|
419
|
+
globalHookContext = {};
|
|
420
|
+
const hookContext = {
|
|
421
|
+
flags: resultParsed.flags,
|
|
422
|
+
store: context?.store?.getState() || {},
|
|
423
|
+
env: process.env,
|
|
424
|
+
cwd: process.cwd(),
|
|
425
|
+
set: (key, value) => {
|
|
426
|
+
globalHookContext[key] = value;
|
|
427
|
+
},
|
|
428
|
+
get: (key) => {
|
|
429
|
+
return globalHookContext[key];
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
for (const hook of beforeHooks) {
|
|
433
|
+
await hook(hookContext);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const terminalInfo = getTerminalInfo();
|
|
437
|
+
const globalFlags = resultParsed.flags;
|
|
438
|
+
const runtimeInfo = {
|
|
439
|
+
startTime: Date.now(),
|
|
440
|
+
args: providedFlags ? [] : argv,
|
|
441
|
+
command: commandName
|
|
442
|
+
};
|
|
443
|
+
let render = false;
|
|
444
|
+
if (command.render) {
|
|
445
|
+
if (globalFlags["no-tui"]) {
|
|
446
|
+
render = false;
|
|
447
|
+
} else if (globalFlags.tui || globalFlags.interactive) {
|
|
448
|
+
render = true;
|
|
449
|
+
} else {
|
|
450
|
+
render = terminalInfo.isInteractive && !terminalInfo.isCI;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (render) {
|
|
454
|
+
ensureRenderAvailable(commandName, command);
|
|
455
|
+
await getTuiRenderer()?.({
|
|
456
|
+
command,
|
|
457
|
+
flags: resultParsed.flags,
|
|
458
|
+
positional: resultParsed.positional,
|
|
459
|
+
shell: Bun.$,
|
|
460
|
+
env: process.env,
|
|
461
|
+
cwd: process.cwd(),
|
|
462
|
+
prompt,
|
|
463
|
+
spinner,
|
|
464
|
+
colors: relico,
|
|
465
|
+
terminal: terminalInfo,
|
|
466
|
+
runtime: runtimeInfo,
|
|
467
|
+
...context ? { context } : {},
|
|
468
|
+
...Object.keys(globalHookContext).length > 0 ? { hooks: globalHookContext } : {}
|
|
469
|
+
});
|
|
470
|
+
} else {
|
|
471
|
+
if (!command.handler) {
|
|
472
|
+
throw new Error("Command does not provide a handler for non-TUI execution");
|
|
473
|
+
}
|
|
474
|
+
const typedFlags = resultParsed.flags;
|
|
475
|
+
await command.handler({
|
|
476
|
+
flags: typedFlags,
|
|
477
|
+
positional: resultParsed.positional,
|
|
478
|
+
shell: Bun.$,
|
|
479
|
+
env: process.env,
|
|
480
|
+
cwd: process.cwd(),
|
|
481
|
+
prompt,
|
|
482
|
+
spinner,
|
|
483
|
+
colors: relico,
|
|
484
|
+
terminal: terminalInfo,
|
|
485
|
+
runtime: runtimeInfo,
|
|
486
|
+
...context ? { context } : {},
|
|
487
|
+
...Object.keys(globalHookContext).length > 0 ? { hooks: globalHookContext } : {}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0 && context) {
|
|
491
|
+
await runAfterCommand(pluginManagerState, context, { exitCode: 0 });
|
|
492
|
+
}
|
|
493
|
+
if (afterHooks.length > 0) {
|
|
494
|
+
const hookContext = {
|
|
495
|
+
flags: resultParsed.flags,
|
|
496
|
+
store: context?.store?.getState() || {},
|
|
497
|
+
env: process.env,
|
|
498
|
+
cwd: process.cwd(),
|
|
499
|
+
set: () => {
|
|
500
|
+
},
|
|
501
|
+
// Not used in after hooks
|
|
502
|
+
get: () => void 0,
|
|
503
|
+
// Not used in after hooks
|
|
504
|
+
exitCode: 0
|
|
505
|
+
};
|
|
506
|
+
for (const hook of afterHooks) {
|
|
507
|
+
await hook(hookContext);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0 && context) {
|
|
512
|
+
await runAfterCommand(pluginManagerState, context, { exitCode: 1 });
|
|
513
|
+
}
|
|
514
|
+
if (afterHooks.length > 0) {
|
|
515
|
+
const hookContext = {
|
|
516
|
+
flags: resultParsed?.flags || {},
|
|
517
|
+
store: context?.store?.getState() || {},
|
|
518
|
+
env: process.env,
|
|
519
|
+
cwd: process.cwd(),
|
|
520
|
+
set: () => {
|
|
521
|
+
},
|
|
522
|
+
// Not used in after hooks
|
|
523
|
+
get: () => void 0,
|
|
524
|
+
// Not used in after hooks
|
|
525
|
+
exitCode: 1,
|
|
526
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
527
|
+
};
|
|
528
|
+
for (const hook of afterHooks) {
|
|
529
|
+
await hook(hookContext);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (error instanceof SchemaError) {
|
|
533
|
+
console.error(relico.red("Validation Error:"));
|
|
534
|
+
const generalErrors = [];
|
|
535
|
+
const fieldErrors = {};
|
|
536
|
+
for (const issue of error.issues) {
|
|
537
|
+
const path = getDotPath(issue);
|
|
538
|
+
if (path) {
|
|
539
|
+
if (!fieldErrors[path]) {
|
|
540
|
+
fieldErrors[path] = [];
|
|
541
|
+
}
|
|
542
|
+
fieldErrors[path].push(issue.message);
|
|
543
|
+
} else {
|
|
544
|
+
generalErrors.push(issue.message);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
for (const [field, messages] of Object.entries(fieldErrors)) {
|
|
548
|
+
console.error(relico.dim(` ${field}:`));
|
|
549
|
+
for (const message of messages) {
|
|
550
|
+
console.error(relico.dim(` \u2022 ${message}`));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
for (const message of generalErrors) {
|
|
554
|
+
console.error(relico.dim(` \u2022 ${message}`));
|
|
555
|
+
}
|
|
556
|
+
process.exit(1);
|
|
557
|
+
} else if (error instanceof Error) {
|
|
558
|
+
console.error(relico.red(`Error: ${error.message}`));
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const api = {
|
|
565
|
+
// Internal method for command registration (not part of public API)
|
|
566
|
+
command(name, cmd) {
|
|
567
|
+
registerCommand(name, cmd, [], "directory");
|
|
568
|
+
},
|
|
569
|
+
async init() {
|
|
570
|
+
await loadFromConfig();
|
|
571
|
+
},
|
|
572
|
+
async run(argv = process.argv.slice(2)) {
|
|
573
|
+
if (argv.length === 0) {
|
|
574
|
+
showHelp(void 0, void 0, []);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const separatorIndex = argv.indexOf("--");
|
|
578
|
+
const commandArgs = separatorIndex >= 0 ? argv.slice(0, separatorIndex) : argv;
|
|
579
|
+
const passthroughArgs = separatorIndex >= 0 ? argv.slice(separatorIndex + 1) : [];
|
|
580
|
+
if (commandArgs.includes("--version") || commandArgs.includes("-v")) {
|
|
581
|
+
console.log(relico.bold(relico.cyan(`${fullConfig.name} v${fullConfig.version}`)));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
|
|
585
|
+
const helpIndex = Math.max(commandArgs.indexOf("--help"), commandArgs.indexOf("-h"));
|
|
586
|
+
const cmdArgs = commandArgs.slice(0, helpIndex);
|
|
587
|
+
if (cmdArgs.length === 0) {
|
|
588
|
+
showHelp(void 0, void 0, []);
|
|
589
|
+
} else {
|
|
590
|
+
const { command: command2, commandName: commandName2, remainingArgs: _remainingArgs } = findCommand(cmdArgs);
|
|
591
|
+
if (command2 && commandName2) {
|
|
592
|
+
const pathParts = commandName2.split(" ").slice(0, -1);
|
|
593
|
+
showHelp(commandName2, command2, pathParts);
|
|
594
|
+
} else {
|
|
595
|
+
console.error(`Unknown command: ${cmdArgs.join(" ")}`);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const { command, commandName, remainingArgs } = findCommand(commandArgs);
|
|
602
|
+
if (!(command && commandName)) {
|
|
603
|
+
console.error(`Unknown command: ${commandArgs[0]}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
const hasSubcommands = subcommandIndex.has(commandName) && Array.from(subcommandIndex.get(commandName)).some(
|
|
607
|
+
(name) => name.split(" ").length === commandName.split(" ").length + 1
|
|
608
|
+
);
|
|
609
|
+
if (!(command.handler || command.render) && hasSubcommands) {
|
|
610
|
+
const pathParts = commandName.split(" ").slice(0, -1);
|
|
611
|
+
showHelp(commandName, command, pathParts);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (command.handler || command.render) {
|
|
615
|
+
const allArgs = [...remainingArgs, ...passthroughArgs];
|
|
616
|
+
await runCommandInternal(commandName, command, allArgs);
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
async execute(commandName, argsOrOptions, options) {
|
|
620
|
+
const commandPath = commandName.replace(/\//g, " ").split(" ");
|
|
621
|
+
const { command, commandName: foundCommandName, remainingArgs } = findCommand(commandPath);
|
|
622
|
+
if (!(command && foundCommandName)) {
|
|
623
|
+
throw new Error(`Command '${commandName}' not found`);
|
|
624
|
+
}
|
|
625
|
+
let finalArgs = [];
|
|
626
|
+
let finalOptions = {};
|
|
627
|
+
if (argsOrOptions && !Array.isArray(argsOrOptions)) {
|
|
628
|
+
finalOptions = argsOrOptions;
|
|
629
|
+
} else if (Array.isArray(argsOrOptions) && options) {
|
|
630
|
+
finalArgs = argsOrOptions;
|
|
631
|
+
finalOptions = options;
|
|
632
|
+
} else if (Array.isArray(argsOrOptions)) {
|
|
633
|
+
finalArgs = argsOrOptions;
|
|
634
|
+
}
|
|
635
|
+
if (Object.keys(finalOptions).length > 0) {
|
|
636
|
+
const mergedOptions = {
|
|
637
|
+
...GLOBAL_FLAGS,
|
|
638
|
+
...command.options || {}
|
|
639
|
+
};
|
|
640
|
+
const parsed = await parseArgs([], mergedOptions, foundCommandName);
|
|
641
|
+
Object.assign(parsed.flags, finalOptions);
|
|
642
|
+
const { prompt, spinner } = await import("@reliverse/rempts-utils");
|
|
643
|
+
let context;
|
|
644
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0) {
|
|
645
|
+
context = await runBeforeCommand(
|
|
646
|
+
pluginManagerState,
|
|
647
|
+
foundCommandName,
|
|
648
|
+
command,
|
|
649
|
+
[],
|
|
650
|
+
parsed.flags
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
if (beforeHooks.length > 0) {
|
|
654
|
+
globalHookContext = {};
|
|
655
|
+
const hookContext = {
|
|
656
|
+
flags: parsed.flags,
|
|
657
|
+
store: context?.store?.getState() || {},
|
|
658
|
+
env: process.env,
|
|
659
|
+
cwd: process.cwd(),
|
|
660
|
+
set: (key, value) => {
|
|
661
|
+
globalHookContext[key] = value;
|
|
662
|
+
},
|
|
663
|
+
get: (key) => {
|
|
664
|
+
return globalHookContext[key];
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
for (const hook of beforeHooks) {
|
|
668
|
+
await hook(hookContext);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const runtimeInfo = {
|
|
672
|
+
startTime: Date.now(),
|
|
673
|
+
args: [],
|
|
674
|
+
command: foundCommandName
|
|
675
|
+
};
|
|
676
|
+
const terminalInfo = getTerminalInfo();
|
|
677
|
+
if (command.handler) {
|
|
678
|
+
const typedFlags = parsed.flags;
|
|
679
|
+
await command.handler({
|
|
680
|
+
flags: typedFlags,
|
|
681
|
+
positional: [],
|
|
682
|
+
shell: Bun.$,
|
|
683
|
+
env: process.env,
|
|
684
|
+
cwd: process.cwd(),
|
|
685
|
+
prompt,
|
|
686
|
+
spinner,
|
|
687
|
+
colors: relico,
|
|
688
|
+
terminal: terminalInfo,
|
|
689
|
+
runtime: runtimeInfo,
|
|
690
|
+
...context ? { context } : {},
|
|
691
|
+
...Object.keys(globalHookContext).length > 0 ? { hooks: globalHookContext } : {}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
if (mergedConfig.plugins && mergedConfig.plugins.length > 0 && context) {
|
|
695
|
+
await runAfterCommand(pluginManagerState, context, { exitCode: 0 });
|
|
696
|
+
}
|
|
697
|
+
if (afterHooks.length > 0) {
|
|
698
|
+
const hookContext = {
|
|
699
|
+
flags: parsed.flags,
|
|
700
|
+
store: context?.store?.getState() || {},
|
|
701
|
+
env: process.env,
|
|
702
|
+
cwd: process.cwd(),
|
|
703
|
+
set: () => {
|
|
704
|
+
},
|
|
705
|
+
// Not used in after hooks
|
|
706
|
+
get: () => void 0,
|
|
707
|
+
// Not used in after hooks
|
|
708
|
+
exitCode: 0
|
|
709
|
+
};
|
|
710
|
+
for (const hook of afterHooks) {
|
|
711
|
+
await hook(hookContext);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const args = finalArgs.length > 0 ? finalArgs : argsOrOptions || [];
|
|
717
|
+
const foundCommand = command;
|
|
718
|
+
const finalArgsToUse = [...remainingArgs, ...args];
|
|
719
|
+
if (foundCommand.handler || foundCommand.render) {
|
|
720
|
+
await runCommandInternal(foundCommandName, foundCommand, finalArgsToUse);
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
before(hook) {
|
|
724
|
+
beforeHooks.push(hook);
|
|
725
|
+
},
|
|
726
|
+
after(hook) {
|
|
727
|
+
afterHooks.push(hook);
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
return api;
|
|
731
|
+
}
|