vde-layout 1.0.1 → 1.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 +33 -7
- package/dist/index.mjs +566 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import {
|
|
3
|
+
import { defineCommand, parseArgs, renderUsage } from "citty";
|
|
4
4
|
import fs from "fs-extra";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import os from "node:os";
|
|
@@ -8,10 +8,11 @@ import * as YAML from "yaml";
|
|
|
8
8
|
import { parse } from "yaml";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
|
-
import chalk from "chalk";
|
|
11
|
+
import chalk, { Chalk } from "chalk";
|
|
12
12
|
import { execa } from "execa";
|
|
13
13
|
import { createInterface } from "node:readline/promises";
|
|
14
14
|
import { stdin, stdout } from "node:process";
|
|
15
|
+
import stringWidth from "string-width";
|
|
15
16
|
|
|
16
17
|
//#region src/utils/errors.ts
|
|
17
18
|
const ErrorCodes = {
|
|
@@ -114,6 +115,21 @@ const formatters = {
|
|
|
114
115
|
//#region src/models/schema.ts
|
|
115
116
|
const WindowModeSchema = z.enum(["new-window", "current-window"]);
|
|
116
117
|
const TerminalBackendSchema = z.enum(["tmux", "wezterm"]);
|
|
118
|
+
const SELECT_UI_MODES = ["auto", "fzf"];
|
|
119
|
+
const SELECT_SURFACE_MODES = [
|
|
120
|
+
"auto",
|
|
121
|
+
"inline",
|
|
122
|
+
"tmux-popup"
|
|
123
|
+
];
|
|
124
|
+
const SelectUiModeSchema = z.enum(SELECT_UI_MODES);
|
|
125
|
+
const SelectSurfaceModeSchema = z.enum(SELECT_SURFACE_MODES);
|
|
126
|
+
const SelectorFzfSchema = z.object({ extraArgs: z.array(z.string().min(1)).optional() }).strict();
|
|
127
|
+
const SelectorDefaultsSchema = z.object({
|
|
128
|
+
ui: SelectUiModeSchema.optional(),
|
|
129
|
+
surface: SelectSurfaceModeSchema.optional(),
|
|
130
|
+
tmuxPopupOpts: z.string().min(1).optional(),
|
|
131
|
+
fzf: SelectorFzfSchema.optional()
|
|
132
|
+
}).strict();
|
|
117
133
|
const TerminalPaneSchema = z.object({
|
|
118
134
|
name: z.string().min(1),
|
|
119
135
|
command: z.string().optional(),
|
|
@@ -145,7 +161,10 @@ const PresetSchema = z.object({
|
|
|
145
161
|
backend: TerminalBackendSchema.optional()
|
|
146
162
|
});
|
|
147
163
|
const ConfigSchema = z.object({
|
|
148
|
-
defaults: z.object({
|
|
164
|
+
defaults: z.object({
|
|
165
|
+
windowMode: WindowModeSchema.optional(),
|
|
166
|
+
selector: SelectorDefaultsSchema.optional()
|
|
167
|
+
}).optional(),
|
|
149
168
|
presets: z.record(PresetSchema)
|
|
150
169
|
});
|
|
151
170
|
|
|
@@ -248,7 +267,8 @@ const createConfigLoader = (options = {}) => {
|
|
|
248
267
|
const candidates = [];
|
|
249
268
|
const projectCandidate = findProjectConfigCandidate();
|
|
250
269
|
if (projectCandidate !== null) candidates.push(projectCandidate);
|
|
251
|
-
|
|
270
|
+
const defaultSearchPathGroups = buildDefaultSearchPathGroups();
|
|
271
|
+
candidates.push(...flattenSearchPathGroups(defaultSearchPathGroups));
|
|
252
272
|
return [...new Set(candidates)];
|
|
253
273
|
};
|
|
254
274
|
const loadConfig = async () => {
|
|
@@ -258,16 +278,16 @@ const createConfigLoader = (options = {}) => {
|
|
|
258
278
|
return validateYAML(await safeReadFile(filePath));
|
|
259
279
|
}
|
|
260
280
|
const searchPaths = computeCachedSearchPaths();
|
|
261
|
-
const
|
|
262
|
-
if (existingPaths.length === 0) throw createConfigError("Configuration file not found", ErrorCodes.CONFIG_NOT_FOUND, { searchPaths });
|
|
281
|
+
const globalPaths = await resolveFirstExistingPaths(buildDefaultSearchPathGroups());
|
|
263
282
|
const projectPath = findProjectConfigCandidate();
|
|
264
|
-
const
|
|
283
|
+
const projectConfigExists = projectPath !== null ? await fs.pathExists(projectPath) : false;
|
|
284
|
+
if (globalPaths.length === 0 && !projectConfigExists) throw createConfigError("Configuration file not found", ErrorCodes.CONFIG_NOT_FOUND, { searchPaths });
|
|
265
285
|
let mergedConfig = { presets: {} };
|
|
266
286
|
for (const globalPath of globalPaths) {
|
|
267
287
|
const config = validateYAML(await safeReadFile(globalPath));
|
|
268
288
|
mergedConfig = mergeConfigs(mergedConfig, config, emitWarning);
|
|
269
289
|
}
|
|
270
|
-
if (projectPath !== null &&
|
|
290
|
+
if (projectPath !== null && projectConfigExists) {
|
|
271
291
|
const config = validateYAML(await safeReadFile(projectPath));
|
|
272
292
|
mergedConfig = mergeConfigs(mergedConfig, config, emitWarning);
|
|
273
293
|
}
|
|
@@ -287,21 +307,36 @@ const createConfigLoader = (options = {}) => {
|
|
|
287
307
|
getSearchPaths: () => computeCachedSearchPaths()
|
|
288
308
|
};
|
|
289
309
|
};
|
|
290
|
-
const
|
|
291
|
-
const
|
|
310
|
+
const buildDefaultSearchPathGroups = () => {
|
|
311
|
+
const pathGroups = [];
|
|
292
312
|
const vdeConfigPath = process.env.VDE_CONFIG_PATH;
|
|
293
|
-
if (vdeConfigPath !== void 0)
|
|
313
|
+
if (vdeConfigPath !== void 0) pathGroups.push([path.join(vdeConfigPath, "layout.yml")]);
|
|
294
314
|
const homeDir = process.env.HOME ?? os.homedir();
|
|
295
315
|
const xdgConfigHome = process.env.XDG_CONFIG_HOME ?? path.join(homeDir, ".config");
|
|
296
|
-
|
|
316
|
+
pathGroups.push([path.join(xdgConfigHome, "vde", "layout", "config.yml"), path.join(xdgConfigHome, "vde", "layout.yml")]);
|
|
317
|
+
return pathGroups.map((group) => [...new Set(group)]);
|
|
318
|
+
};
|
|
319
|
+
const flattenSearchPathGroups = (pathGroups) => {
|
|
320
|
+
const paths = [];
|
|
321
|
+
for (const group of pathGroups) paths.push(...group);
|
|
297
322
|
return [...new Set(paths)];
|
|
298
323
|
};
|
|
324
|
+
const resolveFirstExistingPaths = async (pathGroups) => {
|
|
325
|
+
const existingPaths = await Promise.all(pathGroups.map(async (group) => findFirstExisting(group)));
|
|
326
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
327
|
+
const resolvedPaths = [];
|
|
328
|
+
for (const existingPath of existingPaths) if (existingPath !== null && !seenPaths.has(existingPath)) {
|
|
329
|
+
seenPaths.add(existingPath);
|
|
330
|
+
resolvedPaths.push(existingPath);
|
|
331
|
+
}
|
|
332
|
+
return resolvedPaths;
|
|
333
|
+
};
|
|
299
334
|
const findProjectConfigCandidate = () => {
|
|
300
335
|
let currentDir = process.cwd();
|
|
301
336
|
const { root } = path.parse(currentDir);
|
|
302
337
|
while (true) {
|
|
303
|
-
const
|
|
304
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
338
|
+
const candidates = [path.join(currentDir, ".vde", "layout", "config.yml"), path.join(currentDir, ".vde", "layout.yml")];
|
|
339
|
+
for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
|
|
305
340
|
if (currentDir === root) break;
|
|
306
341
|
const parent = path.dirname(currentDir);
|
|
307
342
|
if (parent === currentDir) break;
|
|
@@ -313,11 +348,6 @@ const findFirstExisting = async (paths) => {
|
|
|
313
348
|
for (const candidate of paths) if (await fs.pathExists(candidate)) return candidate;
|
|
314
349
|
return null;
|
|
315
350
|
};
|
|
316
|
-
const filterExistingPaths = async (paths) => {
|
|
317
|
-
const existing = [];
|
|
318
|
-
for (const candidate of paths) if (await fs.pathExists(candidate)) existing.push(candidate);
|
|
319
|
-
return existing;
|
|
320
|
-
};
|
|
321
351
|
const safeReadFile = async (filePath) => {
|
|
322
352
|
try {
|
|
323
353
|
return await fs.readFile(filePath, "utf8");
|
|
@@ -339,15 +369,39 @@ const mergeConfigs = (base, override, emitWarning) => {
|
|
|
339
369
|
const baseDefaults = base.defaults;
|
|
340
370
|
const overrideDefaults = override.defaults;
|
|
341
371
|
if (baseDefaults?.windowMode !== void 0 && overrideDefaults?.windowMode !== void 0 && baseDefaults.windowMode !== overrideDefaults.windowMode) emitWarning(`[vde-layout] defaults.windowMode conflict: "${baseDefaults.windowMode}" overridden by "${overrideDefaults.windowMode}"`);
|
|
372
|
+
const mergedSelectorDefaults = mergeSelectorDefaults({
|
|
373
|
+
baseSelector: baseDefaults?.selector,
|
|
374
|
+
overrideSelector: overrideDefaults?.selector,
|
|
375
|
+
emitWarning
|
|
376
|
+
});
|
|
342
377
|
const mergedDefaults = baseDefaults !== void 0 || overrideDefaults !== void 0 ? {
|
|
343
378
|
...baseDefaults ?? {},
|
|
344
|
-
...overrideDefaults ?? {}
|
|
379
|
+
...overrideDefaults ?? {},
|
|
380
|
+
...mergedSelectorDefaults !== void 0 ? { selector: mergedSelectorDefaults } : {}
|
|
345
381
|
} : void 0;
|
|
346
382
|
return mergedDefaults === void 0 ? { presets: mergedPresets } : {
|
|
347
383
|
defaults: mergedDefaults,
|
|
348
384
|
presets: mergedPresets
|
|
349
385
|
};
|
|
350
386
|
};
|
|
387
|
+
const mergeSelectorDefaults = ({ baseSelector, overrideSelector, emitWarning }) => {
|
|
388
|
+
if (baseSelector === void 0 && overrideSelector === void 0) return;
|
|
389
|
+
if (baseSelector?.ui !== void 0 && overrideSelector?.ui !== void 0 && baseSelector.ui !== overrideSelector.ui) emitWarning(`[vde-layout] defaults.selector.ui conflict: "${baseSelector.ui}" overridden by "${overrideSelector.ui}"`);
|
|
390
|
+
if (baseSelector?.surface !== void 0 && overrideSelector?.surface !== void 0 && baseSelector.surface !== overrideSelector.surface) emitWarning(`[vde-layout] defaults.selector.surface conflict: "${baseSelector.surface}" overridden by "${overrideSelector.surface}"`);
|
|
391
|
+
if (baseSelector?.tmuxPopupOpts !== void 0 && overrideSelector?.tmuxPopupOpts !== void 0 && baseSelector.tmuxPopupOpts !== overrideSelector.tmuxPopupOpts) emitWarning(`[vde-layout] defaults.selector.tmuxPopupOpts conflict: "${baseSelector.tmuxPopupOpts}" overridden by "${overrideSelector.tmuxPopupOpts}"`);
|
|
392
|
+
const baseExtraArgs = baseSelector?.fzf?.extraArgs;
|
|
393
|
+
const overrideExtraArgs = overrideSelector?.fzf?.extraArgs;
|
|
394
|
+
if (Array.isArray(baseExtraArgs) && Array.isArray(overrideExtraArgs) && JSON.stringify(baseExtraArgs) !== JSON.stringify(overrideExtraArgs)) emitWarning(`[vde-layout] defaults.selector.fzf.extraArgs conflict: global value overridden by project value`);
|
|
395
|
+
const mergedFzf = baseSelector?.fzf !== void 0 || overrideSelector?.fzf !== void 0 ? {
|
|
396
|
+
...baseSelector?.fzf ?? {},
|
|
397
|
+
...overrideSelector?.fzf ?? {}
|
|
398
|
+
} : void 0;
|
|
399
|
+
return {
|
|
400
|
+
...baseSelector ?? {},
|
|
401
|
+
...overrideSelector ?? {},
|
|
402
|
+
...mergedFzf !== void 0 ? { fzf: mergedFzf } : {}
|
|
403
|
+
};
|
|
404
|
+
};
|
|
351
405
|
|
|
352
406
|
//#endregion
|
|
353
407
|
//#region src/layout/preset.ts
|
|
@@ -2706,9 +2760,9 @@ const resolveWindowMode = ({ cli, preset, defaults }) => {
|
|
|
2706
2760
|
|
|
2707
2761
|
//#endregion
|
|
2708
2762
|
//#region src/cli/preset-execution.ts
|
|
2709
|
-
const executePreset = async ({ presetName, options, presetManager, createCommandExecutor, core, logger, handleError, handlePipelineFailure, output = (line) => console.log(line), cwd = process.cwd(), env = process.env }) => {
|
|
2763
|
+
const executePreset = async ({ presetName, options, skipLoadConfig = false, presetManager, createCommandExecutor, core, logger, handleError, handlePipelineFailure, output = (line) => console.log(line), cwd = process.cwd(), env = process.env }) => {
|
|
2710
2764
|
try {
|
|
2711
|
-
await presetManager.loadConfig();
|
|
2765
|
+
if (skipLoadConfig !== true) await presetManager.loadConfig();
|
|
2712
2766
|
const preset = typeof presetName === "string" && presetName.length > 0 ? presetManager.getPreset(presetName) : presetManager.getDefaultPreset();
|
|
2713
2767
|
const windowModeResolution = resolveWindowModeForPreset({
|
|
2714
2768
|
presetManager,
|
|
@@ -2794,57 +2848,518 @@ const resolveWindowModeForPreset = ({ presetManager, options, presetWindowMode }
|
|
|
2794
2848
|
});
|
|
2795
2849
|
};
|
|
2796
2850
|
|
|
2851
|
+
//#endregion
|
|
2852
|
+
//#region src/cli/select-args.ts
|
|
2853
|
+
const selectUiModes = SELECT_UI_MODES;
|
|
2854
|
+
const selectSurfaceModes = SELECT_SURFACE_MODES;
|
|
2855
|
+
const selectUiModeSet = new Set(selectUiModes);
|
|
2856
|
+
const selectSurfaceModeSet = new Set(selectSurfaceModes);
|
|
2857
|
+
const isSelectUiMode = (value) => {
|
|
2858
|
+
return selectUiModeSet.has(value);
|
|
2859
|
+
};
|
|
2860
|
+
const isSelectSurfaceMode = (value) => {
|
|
2861
|
+
return selectSurfaceModeSet.has(value);
|
|
2862
|
+
};
|
|
2863
|
+
const normalizeSelectArgs = (args) => {
|
|
2864
|
+
const normalized = [];
|
|
2865
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2866
|
+
const token = args[index];
|
|
2867
|
+
if (typeof token !== "string") continue;
|
|
2868
|
+
if (token === "--") {
|
|
2869
|
+
normalized.push(...args.slice(index));
|
|
2870
|
+
break;
|
|
2871
|
+
}
|
|
2872
|
+
if (typeof token === "string" && token.startsWith("--select=")) {
|
|
2873
|
+
const mode = token.slice(9);
|
|
2874
|
+
normalized.push("--select");
|
|
2875
|
+
if (mode.length > 0) normalized.push("--select-ui", mode);
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
if (token === "--select") {
|
|
2879
|
+
const nextToken = args[index + 1];
|
|
2880
|
+
if (typeof nextToken === "string" && isSelectUiMode(nextToken)) {
|
|
2881
|
+
normalized.push("--select", "--select-ui", nextToken);
|
|
2882
|
+
index += 1;
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
normalized.push(token);
|
|
2887
|
+
}
|
|
2888
|
+
return normalized;
|
|
2889
|
+
};
|
|
2890
|
+
const resolveSelectUiMode = (uiValue) => {
|
|
2891
|
+
if (uiValue === void 0) return "auto";
|
|
2892
|
+
if (isSelectUiMode(uiValue)) return uiValue;
|
|
2893
|
+
throw new Error(`Invalid value for --select-ui: "${uiValue}". Expected one of: ${selectUiModes.join(", ")}`);
|
|
2894
|
+
};
|
|
2895
|
+
const resolveSelectSurfaceMode = (surfaceValue) => {
|
|
2896
|
+
if (surfaceValue === void 0) return "auto";
|
|
2897
|
+
if (isSelectSurfaceMode(surfaceValue)) return surfaceValue;
|
|
2898
|
+
throw new Error(`Invalid value for --select-surface: "${surfaceValue}". Expected one of: ${selectSurfaceModes.join(", ")}`);
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2901
|
+
//#endregion
|
|
2902
|
+
//#region src/cli/preset-selector.ts
|
|
2903
|
+
const FZF_BINARY = "fzf";
|
|
2904
|
+
const FZF_CHECK_TIMEOUT_MS = 5e3;
|
|
2905
|
+
const MAX_PREVIEW_BASE64_LENGTH = 64 * 1024;
|
|
2906
|
+
const RESERVED_FZF_ARGS = new Set([
|
|
2907
|
+
"delimiter",
|
|
2908
|
+
"with-nth",
|
|
2909
|
+
"ansi",
|
|
2910
|
+
"preview",
|
|
2911
|
+
"preview-window",
|
|
2912
|
+
"tmux"
|
|
2913
|
+
]);
|
|
2914
|
+
const selectorChalk = new Chalk({ level: 1 });
|
|
2915
|
+
const sanitizeTsvCell = (value) => {
|
|
2916
|
+
return (value ?? "").replace(/[\t\r\n]+/g, " ");
|
|
2917
|
+
};
|
|
2918
|
+
const padDisplayCell = (value, width) => {
|
|
2919
|
+
const paddingLength = Math.max(0, width - stringWidth(value));
|
|
2920
|
+
return `${value}${" ".repeat(paddingLength)}`;
|
|
2921
|
+
};
|
|
2922
|
+
const buildPresetPreviewYaml = ({ presetKey, preset }) => {
|
|
2923
|
+
return YAML.stringify({ presets: { [presetKey]: preset } });
|
|
2924
|
+
};
|
|
2925
|
+
const defaultCheckFzfAvailability = async () => {
|
|
2926
|
+
try {
|
|
2927
|
+
await execa(FZF_BINARY, ["--version"], { timeout: FZF_CHECK_TIMEOUT_MS });
|
|
2928
|
+
return true;
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
const execaError = error;
|
|
2931
|
+
if (execaError.code === "ENOENT" || execaError.code === "ETIMEDOUT" || execaError.code === "ERR_EXECA_TIMEOUT" || execaError.timedOut === true) return false;
|
|
2932
|
+
throw error;
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
const defaultRunFzf = async ({ args, input, cwd, env }) => {
|
|
2936
|
+
return { stdout: (await execa(FZF_BINARY, args, {
|
|
2937
|
+
input,
|
|
2938
|
+
cwd,
|
|
2939
|
+
env,
|
|
2940
|
+
stderr: "inherit"
|
|
2941
|
+
})).stdout };
|
|
2942
|
+
};
|
|
2943
|
+
const ensureFzfAvailable = async (checkFzfAvailability) => {
|
|
2944
|
+
if (await checkFzfAvailability()) return;
|
|
2945
|
+
throw createEnvironmentError("fzf is required for preset selection UI", ErrorCodes.BACKEND_NOT_FOUND, {
|
|
2946
|
+
backend: "fzf",
|
|
2947
|
+
binary: FZF_BINARY
|
|
2948
|
+
});
|
|
2949
|
+
};
|
|
2950
|
+
const isTmuxSession = (env) => {
|
|
2951
|
+
return typeof env.TMUX === "string" && env.TMUX.length > 0;
|
|
2952
|
+
};
|
|
2953
|
+
const resolveSurfaceMode = ({ surfaceMode, env }) => {
|
|
2954
|
+
if (surfaceMode === "auto") return isTmuxSession(env) ? "tmux-popup" : "inline";
|
|
2955
|
+
return surfaceMode;
|
|
2956
|
+
};
|
|
2957
|
+
const validateExtraFzfArgs = (fzfExtraArgs) => {
|
|
2958
|
+
for (const arg of fzfExtraArgs) {
|
|
2959
|
+
if (typeof arg !== "string" || arg.length === 0) throw new Error("Empty value is not allowed for --fzf-arg");
|
|
2960
|
+
if (!arg.startsWith("--")) continue;
|
|
2961
|
+
const withoutPrefix = arg.slice(2);
|
|
2962
|
+
if (withoutPrefix.length === 0) continue;
|
|
2963
|
+
const optionName = withoutPrefix.split("=")[0];
|
|
2964
|
+
if (optionName !== void 0 && RESERVED_FZF_ARGS.has(optionName)) throw new Error(`--fzf-arg cannot override reserved fzf option: --${optionName}`);
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2967
|
+
const buildFzfArgs = ({ surfaceMode, tmuxPopupOptions, fzfExtraArgs, env }) => {
|
|
2968
|
+
validateExtraFzfArgs(fzfExtraArgs);
|
|
2969
|
+
const resolvedSurfaceMode = resolveSurfaceMode({
|
|
2970
|
+
surfaceMode,
|
|
2971
|
+
env
|
|
2972
|
+
});
|
|
2973
|
+
if (resolvedSurfaceMode === "tmux-popup" && isTmuxSession(env) !== true) throw new Error("tmux popup selector surface requires running inside tmux");
|
|
2974
|
+
return [
|
|
2975
|
+
"--delimiter=\\t",
|
|
2976
|
+
"--ansi",
|
|
2977
|
+
"--with-nth=2",
|
|
2978
|
+
"--prompt=preset> ",
|
|
2979
|
+
"--layout=reverse",
|
|
2980
|
+
"--height=80%",
|
|
2981
|
+
"--border",
|
|
2982
|
+
"--preview=node -e 'process.stdout.write(Buffer.from(process.argv[1], \"base64\").toString(\"utf8\"))' {3}",
|
|
2983
|
+
"--preview-window=right,60%,border-left,wrap",
|
|
2984
|
+
...resolvedSurfaceMode === "tmux-popup" ? [tmuxPopupOptions !== void 0 ? `--tmux=${tmuxPopupOptions}` : "--tmux"] : [],
|
|
2985
|
+
...fzfExtraArgs
|
|
2986
|
+
];
|
|
2987
|
+
};
|
|
2988
|
+
const toPresetRows = ({ presetInfos, presetManager }) => {
|
|
2989
|
+
const rows = presetInfos.map((presetInfo) => {
|
|
2990
|
+
const key = sanitizeTsvCell(presetInfo.key);
|
|
2991
|
+
const name = sanitizeTsvCell(presetInfo.name);
|
|
2992
|
+
const description = sanitizeTsvCell(presetInfo.description);
|
|
2993
|
+
const preset = presetManager.getPreset(presetInfo.key);
|
|
2994
|
+
const previewYaml = buildPresetPreviewYaml({
|
|
2995
|
+
presetKey: presetInfo.key,
|
|
2996
|
+
preset
|
|
2997
|
+
});
|
|
2998
|
+
const previewBase64 = Buffer.from(previewYaml, "utf8").toString("base64");
|
|
2999
|
+
if (previewBase64.length > MAX_PREVIEW_BASE64_LENGTH) throw new Error(`Preset preview is too large for fzf inline preview payload: "${presetInfo.key}" (${previewBase64.length} bytes)`);
|
|
3000
|
+
return {
|
|
3001
|
+
key,
|
|
3002
|
+
name,
|
|
3003
|
+
description,
|
|
3004
|
+
previewBase64
|
|
3005
|
+
};
|
|
3006
|
+
});
|
|
3007
|
+
const keyColumnWidth = rows.reduce((maxWidth, row) => Math.max(maxWidth, stringWidth(row.key)), 0);
|
|
3008
|
+
const nameColumnWidth = rows.reduce((maxWidth, row) => Math.max(maxWidth, stringWidth(row.name)), 0);
|
|
3009
|
+
return rows.map((row) => {
|
|
3010
|
+
const key = padDisplayCell(row.key, keyColumnWidth);
|
|
3011
|
+
const name = padDisplayCell(row.name, nameColumnWidth);
|
|
3012
|
+
const description = row.description.length > 0 ? row.description : selectorChalk.gray("(no description)");
|
|
3013
|
+
const display = `${selectorChalk.cyan(key)} ${selectorChalk.bold(name)} ${selectorChalk.dim(description)}`;
|
|
3014
|
+
return {
|
|
3015
|
+
key: row.key,
|
|
3016
|
+
name: row.name,
|
|
3017
|
+
description: row.description,
|
|
3018
|
+
display,
|
|
3019
|
+
previewBase64: row.previewBase64
|
|
3020
|
+
};
|
|
3021
|
+
});
|
|
3022
|
+
};
|
|
3023
|
+
const buildFzfInput = (rows) => {
|
|
3024
|
+
return rows.map((row, index) => {
|
|
3025
|
+
return [
|
|
3026
|
+
String(index),
|
|
3027
|
+
row.display,
|
|
3028
|
+
row.previewBase64
|
|
3029
|
+
].join(" ");
|
|
3030
|
+
}).join("\n");
|
|
3031
|
+
};
|
|
3032
|
+
const parseSelectedPresetName = ({ selectedLine, rows }) => {
|
|
3033
|
+
const trimmed = selectedLine.trim();
|
|
3034
|
+
if (trimmed.length === 0) return null;
|
|
3035
|
+
const idCell = trimmed.split(" ")[0];
|
|
3036
|
+
const id = Number(idCell);
|
|
3037
|
+
if (!Number.isInteger(id) || id < 0 || id >= rows.length) throw new Error("Invalid selection returned from fzf");
|
|
3038
|
+
return rows[id]?.key ?? null;
|
|
3039
|
+
};
|
|
3040
|
+
const runFzfSelector = async ({ rows, fzfArgs, runFzf, cwd, env }) => {
|
|
3041
|
+
try {
|
|
3042
|
+
const presetName = parseSelectedPresetName({
|
|
3043
|
+
selectedLine: (await runFzf({
|
|
3044
|
+
input: buildFzfInput(rows),
|
|
3045
|
+
args: [...fzfArgs],
|
|
3046
|
+
cwd,
|
|
3047
|
+
env
|
|
3048
|
+
})).stdout,
|
|
3049
|
+
rows
|
|
3050
|
+
});
|
|
3051
|
+
if (presetName === null) return { status: "cancelled" };
|
|
3052
|
+
return {
|
|
3053
|
+
status: "selected",
|
|
3054
|
+
presetName
|
|
3055
|
+
};
|
|
3056
|
+
} catch (error) {
|
|
3057
|
+
if (error.exitCode === 130) return { status: "cancelled" };
|
|
3058
|
+
throw error;
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
const selectPreset = async ({ uiMode, surfaceMode, tmuxPopupOptions, fzfExtraArgs = [], presetManager, logger, skipLoadConfig = false, cwd = process.cwd(), env = process.env, isInteractive = () => process.stdin.isTTY === true && process.stdout.isTTY === true && process.stderr.isTTY === true, checkFzfAvailability = defaultCheckFzfAvailability, runFzf = defaultRunFzf }) => {
|
|
3062
|
+
if (isInteractive() !== true) throw new Error("Preset selection requires an interactive terminal");
|
|
3063
|
+
await ensureFzfAvailable(checkFzfAvailability);
|
|
3064
|
+
if (skipLoadConfig !== true) await presetManager.loadConfig();
|
|
3065
|
+
const presetInfos = presetManager.listPresets();
|
|
3066
|
+
if (presetInfos.length === 0) throw new Error("No presets defined");
|
|
3067
|
+
const fzfArgs = buildFzfArgs({
|
|
3068
|
+
surfaceMode,
|
|
3069
|
+
tmuxPopupOptions,
|
|
3070
|
+
fzfExtraArgs,
|
|
3071
|
+
env
|
|
3072
|
+
});
|
|
3073
|
+
logger.debug(`Preset selection UI: ${uiMode}`);
|
|
3074
|
+
return runFzfSelector({
|
|
3075
|
+
rows: toPresetRows({
|
|
3076
|
+
presetInfos,
|
|
3077
|
+
presetManager
|
|
3078
|
+
}),
|
|
3079
|
+
fzfArgs,
|
|
3080
|
+
runFzf,
|
|
3081
|
+
cwd,
|
|
3082
|
+
env
|
|
3083
|
+
});
|
|
3084
|
+
};
|
|
3085
|
+
|
|
2797
3086
|
//#endregion
|
|
2798
3087
|
//#region src/cli/index.ts
|
|
3088
|
+
const backendValues = ["tmux", "wezterm"];
|
|
3089
|
+
const listCommandName = "list";
|
|
3090
|
+
const EXIT_CODE_CANCELLED = 130;
|
|
3091
|
+
const optionNamesAllowOptionLikeValue = new Set(["fzfArg", "fzf-arg"]);
|
|
3092
|
+
const toKebabCase = (value) => {
|
|
3093
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
3094
|
+
};
|
|
3095
|
+
const toOptionSpec = (kind, optionName) => {
|
|
3096
|
+
return {
|
|
3097
|
+
kind,
|
|
3098
|
+
allowOptionLikeValue: optionNamesAllowOptionLikeValue.has(optionName)
|
|
3099
|
+
};
|
|
3100
|
+
};
|
|
3101
|
+
const buildOptionSpecs = (argsDef) => {
|
|
3102
|
+
const longOptions = /* @__PURE__ */ new Map();
|
|
3103
|
+
const shortOptions = /* @__PURE__ */ new Map();
|
|
3104
|
+
for (const [argName, arg] of Object.entries(argsDef)) {
|
|
3105
|
+
if (arg.type === "positional") continue;
|
|
3106
|
+
const valueKind = arg.type === "boolean" ? "boolean" : "value";
|
|
3107
|
+
const kebabName = toKebabCase(argName);
|
|
3108
|
+
longOptions.set(argName, toOptionSpec(valueKind, argName));
|
|
3109
|
+
longOptions.set(kebabName, toOptionSpec(valueKind, kebabName));
|
|
3110
|
+
const aliases = "alias" in arg ? Array.isArray(arg.alias) ? arg.alias : typeof arg.alias === "string" ? [arg.alias] : [] : [];
|
|
3111
|
+
for (const alias of aliases) {
|
|
3112
|
+
if (alias.length === 1) {
|
|
3113
|
+
shortOptions.set(alias, toOptionSpec(valueKind, alias));
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
longOptions.set(alias, toOptionSpec(valueKind, alias));
|
|
3117
|
+
const kebabAlias = toKebabCase(alias);
|
|
3118
|
+
longOptions.set(kebabAlias, toOptionSpec(valueKind, kebabAlias));
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
return {
|
|
3122
|
+
longOptions,
|
|
3123
|
+
shortOptions
|
|
3124
|
+
};
|
|
3125
|
+
};
|
|
3126
|
+
const validateRawOptions = (args, optionSpecs) => {
|
|
3127
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
3128
|
+
const token = args[index];
|
|
3129
|
+
if (typeof token !== "string") continue;
|
|
3130
|
+
if (token === "--") break;
|
|
3131
|
+
if (!token.startsWith("-") || token === "-") continue;
|
|
3132
|
+
if (token.startsWith("--")) {
|
|
3133
|
+
const value = token.slice(2);
|
|
3134
|
+
if (value.length === 0) continue;
|
|
3135
|
+
const separatorIndex = value.indexOf("=");
|
|
3136
|
+
const rawOptionName = separatorIndex >= 0 ? value.slice(0, separatorIndex) : value;
|
|
3137
|
+
const optionName = rawOptionName.startsWith("no-") ? rawOptionName.slice(3) : rawOptionName;
|
|
3138
|
+
const optionSpec = optionSpecs.longOptions.get(optionName);
|
|
3139
|
+
const kind = optionSpec?.kind;
|
|
3140
|
+
if (kind === void 0) throw new Error(`Unknown option: --${rawOptionName}`);
|
|
3141
|
+
if (kind === "value") if (separatorIndex >= 0) {
|
|
3142
|
+
if (value.slice(separatorIndex + 1).length === 0) throw new Error(`Missing value for option: --${optionName}`);
|
|
3143
|
+
} else {
|
|
3144
|
+
const nextToken = args[index + 1];
|
|
3145
|
+
if (typeof nextToken !== "string" || nextToken.length === 0) throw new Error(`Missing value for option: --${optionName}`);
|
|
3146
|
+
if (nextToken.startsWith("-") && optionSpec?.allowOptionLikeValue !== true) throw new Error(`Missing value for option: --${optionName}`);
|
|
3147
|
+
index += 1;
|
|
3148
|
+
}
|
|
3149
|
+
continue;
|
|
3150
|
+
}
|
|
3151
|
+
const shortFlags = token.slice(1);
|
|
3152
|
+
for (let flagIndex = 0; flagIndex < shortFlags.length; flagIndex += 1) {
|
|
3153
|
+
const option = shortFlags[flagIndex];
|
|
3154
|
+
if (typeof option !== "string" || option.length === 0) continue;
|
|
3155
|
+
const optionSpec = optionSpecs.shortOptions.get(option);
|
|
3156
|
+
const kind = optionSpec?.kind;
|
|
3157
|
+
if (kind === void 0) throw new Error(`Unknown option: -${option}`);
|
|
3158
|
+
if (kind === "value") {
|
|
3159
|
+
if (flagIndex < shortFlags.length - 1) break;
|
|
3160
|
+
const nextToken = args[index + 1];
|
|
3161
|
+
if (typeof nextToken !== "string" || nextToken.length === 0) throw new Error(`Missing value for option: -${option}`);
|
|
3162
|
+
if (nextToken.startsWith("-") && optionSpec?.allowOptionLikeValue !== true) throw new Error(`Missing value for option: -${option}`);
|
|
3163
|
+
index += 1;
|
|
3164
|
+
break;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
};
|
|
3169
|
+
const getPositionals = (args) => {
|
|
3170
|
+
return args._.filter((value) => typeof value === "string");
|
|
3171
|
+
};
|
|
3172
|
+
const collectOptionValues = ({ args, optionNames }) => {
|
|
3173
|
+
const values = [];
|
|
3174
|
+
const optionNameSet = new Set(optionNames);
|
|
3175
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
3176
|
+
const token = args[index];
|
|
3177
|
+
if (typeof token !== "string") continue;
|
|
3178
|
+
if (token === "--") break;
|
|
3179
|
+
if (!token.startsWith("--")) continue;
|
|
3180
|
+
const eqIndex = token.indexOf("=");
|
|
3181
|
+
const rawName = eqIndex >= 0 ? token.slice(2, eqIndex) : token.slice(2);
|
|
3182
|
+
if (optionNameSet.has(rawName) !== true) continue;
|
|
3183
|
+
if (eqIndex >= 0) {
|
|
3184
|
+
values.push(token.slice(eqIndex + 1));
|
|
3185
|
+
continue;
|
|
3186
|
+
}
|
|
3187
|
+
const nextToken = args[index + 1];
|
|
3188
|
+
if (typeof nextToken === "string") {
|
|
3189
|
+
values.push(nextToken);
|
|
3190
|
+
index += 1;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
return values;
|
|
3194
|
+
};
|
|
2799
3195
|
const createCli = (options = {}) => {
|
|
2800
3196
|
const presetManager = options.presetManager ?? createPresetManager();
|
|
2801
3197
|
const createCommandExecutor = options.createCommandExecutor ?? ((opts) => {
|
|
2802
3198
|
if (opts.dryRun) return createDryRunExecutor({ verbose: opts.verbose });
|
|
2803
3199
|
return createRealExecutor({ verbose: opts.verbose });
|
|
2804
3200
|
});
|
|
3201
|
+
const selectPreset$1 = options.selectPreset ?? selectPreset;
|
|
2805
3202
|
const core = options.core ?? {
|
|
2806
3203
|
compilePreset,
|
|
2807
3204
|
compilePresetFromValue,
|
|
2808
3205
|
createLayoutPlan,
|
|
2809
3206
|
emitPlan
|
|
2810
3207
|
};
|
|
2811
|
-
const program = new Command();
|
|
2812
3208
|
const version = loadPackageVersion(createRequire(import.meta.url));
|
|
2813
3209
|
let logger = createLogger();
|
|
2814
3210
|
const errorHandlers = createCliErrorHandlers({ getLogger: () => logger });
|
|
2815
|
-
const
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
3211
|
+
const rootArgsDef = {
|
|
3212
|
+
preset: {
|
|
3213
|
+
type: "positional",
|
|
3214
|
+
description: "Preset name (defaults to \"default\" preset when omitted)",
|
|
3215
|
+
required: false
|
|
3216
|
+
},
|
|
3217
|
+
verbose: {
|
|
3218
|
+
type: "boolean",
|
|
3219
|
+
description: "Show detailed logs"
|
|
3220
|
+
},
|
|
3221
|
+
dryRun: {
|
|
3222
|
+
type: "boolean",
|
|
3223
|
+
description: "Display commands without executing"
|
|
3224
|
+
},
|
|
3225
|
+
backend: {
|
|
3226
|
+
type: "enum",
|
|
3227
|
+
options: [...backendValues],
|
|
3228
|
+
description: "Select terminal backend (tmux or wezterm)"
|
|
3229
|
+
},
|
|
3230
|
+
config: {
|
|
3231
|
+
type: "string",
|
|
3232
|
+
valueHint: "path",
|
|
3233
|
+
description: "Path to configuration file"
|
|
3234
|
+
},
|
|
3235
|
+
currentWindow: {
|
|
3236
|
+
type: "boolean",
|
|
3237
|
+
description: "Use the current tmux window for layout (kills other panes)"
|
|
3238
|
+
},
|
|
3239
|
+
newWindow: {
|
|
3240
|
+
type: "boolean",
|
|
3241
|
+
description: "Always create a new tmux window for layout"
|
|
3242
|
+
},
|
|
3243
|
+
select: {
|
|
3244
|
+
type: "boolean",
|
|
3245
|
+
description: "Select preset from interactive UI"
|
|
3246
|
+
},
|
|
3247
|
+
selectUi: {
|
|
3248
|
+
type: "enum",
|
|
3249
|
+
options: [...selectUiModes],
|
|
3250
|
+
description: "Select preset UI backend (auto or fzf)"
|
|
3251
|
+
},
|
|
3252
|
+
selectSurface: {
|
|
3253
|
+
type: "enum",
|
|
3254
|
+
options: [...selectSurfaceModes],
|
|
3255
|
+
description: "Select selector surface mode (auto, inline, or tmux-popup)"
|
|
3256
|
+
},
|
|
3257
|
+
selectTmuxPopupOpts: {
|
|
3258
|
+
type: "string",
|
|
3259
|
+
valueHint: "opts",
|
|
3260
|
+
description: "tmux popup options used for fzf --tmux=<opts> (example: 80%,70%)"
|
|
3261
|
+
},
|
|
3262
|
+
fzfArg: {
|
|
3263
|
+
type: "string",
|
|
3264
|
+
valueHint: "arg",
|
|
3265
|
+
description: "Additional argument passed to fzf selector (repeatable)"
|
|
3266
|
+
},
|
|
3267
|
+
help: {
|
|
3268
|
+
type: "boolean",
|
|
3269
|
+
alias: "h",
|
|
3270
|
+
description: "Show help"
|
|
3271
|
+
},
|
|
3272
|
+
version: {
|
|
3273
|
+
type: "boolean",
|
|
3274
|
+
alias: "v",
|
|
3275
|
+
description: "Show version"
|
|
3276
|
+
}
|
|
3277
|
+
};
|
|
3278
|
+
const listCommand = defineCommand({ meta: {
|
|
3279
|
+
name: listCommandName,
|
|
3280
|
+
description: "List available presets"
|
|
3281
|
+
} });
|
|
3282
|
+
const rootCommand = defineCommand({
|
|
3283
|
+
meta: {
|
|
3284
|
+
name: "vde-layout",
|
|
3285
|
+
description: "VDE (Vibrant Development Environment) Layout Manager - tmux pane layout management tool",
|
|
3286
|
+
version
|
|
3287
|
+
},
|
|
3288
|
+
args: rootArgsDef,
|
|
3289
|
+
subCommands: { [listCommandName]: listCommand }
|
|
3290
|
+
});
|
|
3291
|
+
const optionSpecs = buildOptionSpecs(rootArgsDef);
|
|
3292
|
+
const run = async (args = process.argv.slice(2)) => {
|
|
3293
|
+
logger = createLogger();
|
|
3294
|
+
try {
|
|
3295
|
+
const normalizedArgs = normalizeSelectArgs(args);
|
|
3296
|
+
validateRawOptions(normalizedArgs, optionSpecs);
|
|
3297
|
+
const parsedArgs = parseArgs(normalizedArgs, rootArgsDef);
|
|
3298
|
+
const fzfCliArgs = collectOptionValues({
|
|
3299
|
+
args: normalizedArgs,
|
|
3300
|
+
optionNames: ["fzf-arg", "fzfArg"]
|
|
3301
|
+
});
|
|
3302
|
+
const positionals = getPositionals(parsedArgs);
|
|
3303
|
+
const headPositional = positionals[0];
|
|
3304
|
+
if (parsedArgs.help === true) {
|
|
3305
|
+
const usage = headPositional === listCommandName ? await renderUsage(listCommand, rootCommand) : await renderUsage(rootCommand);
|
|
3306
|
+
console.log(`${usage}\n`);
|
|
3307
|
+
return 0;
|
|
3308
|
+
}
|
|
3309
|
+
if (parsedArgs.version === true) {
|
|
3310
|
+
console.log(version);
|
|
3311
|
+
return 0;
|
|
3312
|
+
}
|
|
2825
3313
|
logger = applyRuntimeOptions({
|
|
2826
|
-
runtimeOptions:
|
|
3314
|
+
runtimeOptions: {
|
|
3315
|
+
verbose: parsedArgs.verbose === true,
|
|
3316
|
+
config: typeof parsedArgs.config === "string" ? parsedArgs.config : void 0
|
|
3317
|
+
},
|
|
2827
3318
|
createLogger,
|
|
2828
3319
|
presetManager
|
|
2829
3320
|
});
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3321
|
+
if (headPositional === listCommandName) {
|
|
3322
|
+
const extraArgs = positionals.slice(1);
|
|
3323
|
+
if (extraArgs.length > 0) throw new Error(`too many arguments for '${listCommandName}'. Expected 0 arguments but got ${extraArgs.length}.`);
|
|
3324
|
+
return await listPresets({
|
|
3325
|
+
presetManager,
|
|
3326
|
+
logger,
|
|
3327
|
+
onError: errorHandlers.handleError
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
if (positionals.length > 1) throw new Error(`too many arguments. Expected at most 1 argument but got ${positionals.length}.`);
|
|
3331
|
+
if (parsedArgs.selectUi !== void 0 && parsedArgs.select !== true) throw new Error("--select-ui requires --select");
|
|
3332
|
+
if (parsedArgs.selectSurface !== void 0 && parsedArgs.select !== true) throw new Error("--select-surface requires --select");
|
|
3333
|
+
if (parsedArgs.selectTmuxPopupOpts !== void 0 && parsedArgs.select !== true) throw new Error("--select-tmux-popup-opts requires --select");
|
|
3334
|
+
if (fzfCliArgs.length > 0 && parsedArgs.select !== true) throw new Error("--fzf-arg requires --select");
|
|
3335
|
+
if (parsedArgs.select === true && typeof headPositional === "string" && headPositional.length > 0) throw new Error("Cannot use preset argument with --select");
|
|
3336
|
+
let resolvedPresetName = headPositional;
|
|
3337
|
+
let configLoaded = false;
|
|
3338
|
+
if (parsedArgs.select === true) {
|
|
3339
|
+
await presetManager.loadConfig();
|
|
3340
|
+
configLoaded = true;
|
|
3341
|
+
const selectorDefaults = presetManager.getDefaults()?.selector;
|
|
3342
|
+
const selection = await selectPreset$1({
|
|
3343
|
+
uiMode: resolveSelectUiMode(typeof parsedArgs.selectUi === "string" ? parsedArgs.selectUi : selectorDefaults?.ui),
|
|
3344
|
+
surfaceMode: resolveSelectSurfaceMode(typeof parsedArgs.selectSurface === "string" ? parsedArgs.selectSurface : selectorDefaults?.surface),
|
|
3345
|
+
tmuxPopupOptions: typeof parsedArgs.selectTmuxPopupOpts === "string" ? parsedArgs.selectTmuxPopupOpts : selectorDefaults?.tmuxPopupOpts,
|
|
3346
|
+
fzfExtraArgs: [...selectorDefaults?.fzf?.extraArgs ?? [], ...fzfCliArgs],
|
|
3347
|
+
presetManager,
|
|
3348
|
+
logger,
|
|
3349
|
+
skipLoadConfig: true
|
|
3350
|
+
});
|
|
3351
|
+
if (selection.status === "cancelled") return EXIT_CODE_CANCELLED;
|
|
3352
|
+
resolvedPresetName = selection.presetName;
|
|
3353
|
+
}
|
|
3354
|
+
return await executePreset({
|
|
3355
|
+
presetName: resolvedPresetName,
|
|
3356
|
+
skipLoadConfig: configLoaded,
|
|
2842
3357
|
options: {
|
|
2843
|
-
verbose:
|
|
2844
|
-
dryRun:
|
|
2845
|
-
currentWindow:
|
|
2846
|
-
newWindow:
|
|
2847
|
-
backend:
|
|
3358
|
+
verbose: parsedArgs.verbose === true,
|
|
3359
|
+
dryRun: parsedArgs.dryRun === true,
|
|
3360
|
+
currentWindow: parsedArgs.currentWindow === true,
|
|
3361
|
+
newWindow: parsedArgs.newWindow === true,
|
|
3362
|
+
backend: typeof parsedArgs.backend === "string" ? parsedArgs.backend : void 0
|
|
2848
3363
|
},
|
|
2849
3364
|
presetManager,
|
|
2850
3365
|
createCommandExecutor,
|
|
@@ -2853,20 +3368,9 @@ const createCli = (options = {}) => {
|
|
|
2853
3368
|
handleError: errorHandlers.handleError,
|
|
2854
3369
|
handlePipelineFailure: errorHandlers.handlePipelineFailure
|
|
2855
3370
|
});
|
|
2856
|
-
});
|
|
2857
|
-
};
|
|
2858
|
-
let lastExitCode = 0;
|
|
2859
|
-
setupProgram();
|
|
2860
|
-
const run = async (args = process.argv.slice(2)) => {
|
|
2861
|
-
lastExitCode = 0;
|
|
2862
|
-
logger = createLogger();
|
|
2863
|
-
try {
|
|
2864
|
-
await program.parseAsync(args, { from: "user" });
|
|
2865
3371
|
} catch (error) {
|
|
2866
|
-
if (error instanceof CommanderError) return error.exitCode;
|
|
2867
3372
|
return errorHandlers.handleError(error);
|
|
2868
3373
|
}
|
|
2869
|
-
return lastExitCode;
|
|
2870
3374
|
};
|
|
2871
3375
|
return { run };
|
|
2872
3376
|
};
|