@karmaniverous/get-dotenv 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cliHost.cjs +172 -3
- package/dist/cliHost.d.cts +115 -3
- package/dist/cliHost.d.mts +115 -3
- package/dist/cliHost.d.ts +115 -3
- package/dist/cliHost.mjs +172 -4
- package/dist/getdotenv.cli.mjs +200 -4
- package/dist/index.cjs +25 -0
- package/dist/index.mjs +25 -0
- package/dist/plugins-aws.d.cts +98 -0
- package/dist/plugins-aws.d.mts +98 -0
- package/dist/plugins-aws.d.ts +98 -0
- package/dist/plugins-batch.d.cts +98 -0
- package/dist/plugins-batch.d.mts +98 -0
- package/dist/plugins-batch.d.ts +98 -0
- package/dist/plugins-init.d.cts +98 -0
- package/dist/plugins-init.d.mts +98 -0
- package/dist/plugins-init.d.ts +98 -0
- package/package.json +1 -1
package/dist/cliHost.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { ZodType } from 'zod';
|
|
2
1
|
import { Command } from 'commander';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scripts table shape (configurable shell type).
|
|
6
|
+
*/
|
|
7
|
+
type ScriptsTable<TShell extends string | boolean = string | boolean> = Record<string, string | {
|
|
8
|
+
cmd: string;
|
|
9
|
+
shell?: TShell;
|
|
10
|
+
}>;
|
|
3
11
|
|
|
4
12
|
/**
|
|
5
13
|
* A minimal representation of an environment key/value mapping.
|
|
@@ -95,6 +103,76 @@ interface GetDotenvOptions {
|
|
|
95
103
|
useConfigLoader?: boolean;
|
|
96
104
|
}
|
|
97
105
|
|
|
106
|
+
type Scripts = Record<string, string | {
|
|
107
|
+
cmd: string;
|
|
108
|
+
shell?: string | boolean;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Options passed programmatically to `getDotenvCli`.
|
|
112
|
+
*/
|
|
113
|
+
interface GetDotenvCliOptions extends Omit<GetDotenvOptions, 'paths' | 'vars'> {
|
|
114
|
+
/**
|
|
115
|
+
* Logs CLI internals when true.
|
|
116
|
+
*/
|
|
117
|
+
debug?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* When true, capture child stdout/stderr and re-emit after completion.
|
|
120
|
+
* Useful for tests/CI. Default behavior is streaming via stdio: 'inherit'.
|
|
121
|
+
*/
|
|
122
|
+
capture?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* A delimited string of paths to dotenv files.
|
|
125
|
+
*/
|
|
126
|
+
paths?: string;
|
|
127
|
+
/**
|
|
128
|
+
* A delimiter string with which to split `paths`. Only used if
|
|
129
|
+
* `pathsDelimiterPattern` is not provided.
|
|
130
|
+
*/
|
|
131
|
+
pathsDelimiter?: string;
|
|
132
|
+
/**
|
|
133
|
+
* A regular expression pattern with which to split `paths`. Supersedes
|
|
134
|
+
* `pathsDelimiter`.
|
|
135
|
+
*/
|
|
136
|
+
pathsDelimiterPattern?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Scripts that can be executed from the CLI, either individually or via the batch subcommand.
|
|
139
|
+
*/
|
|
140
|
+
scripts?: Scripts;
|
|
141
|
+
/**
|
|
142
|
+
* Determines how commands and scripts are executed. If `false` or
|
|
143
|
+
* `undefined`, commands are executed as plain Javascript using the default
|
|
144
|
+
* execa parser. If `true`, commands are executed using the default OS shell
|
|
145
|
+
* parser. Otherwise the user may provide a specific shell string (e.g.
|
|
146
|
+
* `/bin/bash`)
|
|
147
|
+
*/
|
|
148
|
+
shell?: string | boolean;
|
|
149
|
+
/**
|
|
150
|
+
* A delimited string of key-value pairs declaratively specifying variables &
|
|
151
|
+
* values to be loaded in addition to any dotenv files.
|
|
152
|
+
*/
|
|
153
|
+
vars?: string;
|
|
154
|
+
/**
|
|
155
|
+
* A string with which to split keys from values in `vars`. Only used if
|
|
156
|
+
* `varsDelimiterPattern` is not provided.
|
|
157
|
+
*/
|
|
158
|
+
varsAssignor?: string;
|
|
159
|
+
/**
|
|
160
|
+
* A regular expression pattern with which to split variable names from values
|
|
161
|
+
* in `vars`. Supersedes `varsAssignor`.
|
|
162
|
+
*/
|
|
163
|
+
varsAssignorPattern?: string;
|
|
164
|
+
/**
|
|
165
|
+
* A string with which to split `vars` into key-value pairs. Only used if
|
|
166
|
+
* `varsDelimiterPattern` is not provided.
|
|
167
|
+
*/
|
|
168
|
+
varsDelimiter?: string;
|
|
169
|
+
/**
|
|
170
|
+
* A regular expression pattern with which to split `vars` into key-value
|
|
171
|
+
* pairs. Supersedes `varsDelimiter`.
|
|
172
|
+
*/
|
|
173
|
+
varsDelimiterPattern?: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
98
176
|
/** * Per-invocation context shared with plugins and actions. */
|
|
99
177
|
type GetDotenvCliCtx<TOptions extends GetDotenvOptions = GetDotenvOptions> = {
|
|
100
178
|
optionsResolved: TOptions;
|
|
@@ -102,6 +180,7 @@ type GetDotenvCliCtx<TOptions extends GetDotenvOptions = GetDotenvOptions> = {
|
|
|
102
180
|
plugins?: Record<string, unknown>;
|
|
103
181
|
pluginConfigs?: Record<string, unknown>;
|
|
104
182
|
};
|
|
183
|
+
declare const HELP_HEADER_SYMBOL: unique symbol;
|
|
105
184
|
/**
|
|
106
185
|
* Plugin-first CLI host for get-dotenv. Extends Commander.Command.
|
|
107
186
|
*
|
|
@@ -114,10 +193,13 @@ type GetDotenvCliCtx<TOptions extends GetDotenvOptions = GetDotenvOptions> = {
|
|
|
114
193
|
* NOTE: This host is additive and does not alter the legacy CLI.
|
|
115
194
|
*/
|
|
116
195
|
declare class GetDotenvCli<TOptions extends GetDotenvOptions = GetDotenvOptions> extends Command {
|
|
196
|
+
#private;
|
|
117
197
|
/** Registered top-level plugins (composition happens via .use()) */
|
|
118
198
|
private _plugins;
|
|
119
199
|
/** One-time installation guard */
|
|
120
200
|
private _installed;
|
|
201
|
+
/** Optional header line to prepend in help output */
|
|
202
|
+
private [HELP_HEADER_SYMBOL];
|
|
121
203
|
constructor(alias?: string);
|
|
122
204
|
/**
|
|
123
205
|
* Resolve options (strict) and compute dotenv context. * Stores the context on the instance under a symbol.
|
|
@@ -127,9 +209,33 @@ declare class GetDotenvCli<TOptions extends GetDotenvOptions = GetDotenvOptions>
|
|
|
127
209
|
* Retrieve the current invocation context (if any).
|
|
128
210
|
*/
|
|
129
211
|
getCtx(): GetDotenvCliCtx<TOptions> | undefined;
|
|
212
|
+
/**
|
|
213
|
+
* Retrieve the merged root CLI options bag (if set by passOptions()).
|
|
214
|
+
* Downstream-safe: no generics required.
|
|
215
|
+
*/
|
|
216
|
+
getOptions(): GetDotenvCliOptions | undefined;
|
|
217
|
+
/** Internal: set the merged root options bag for this run. */
|
|
218
|
+
_setOptionsBag(bag: GetDotenvCliOptions): void;
|
|
130
219
|
/** * Convenience helper to create a namespaced subcommand.
|
|
131
220
|
*/
|
|
132
221
|
ns(name: string): Command;
|
|
222
|
+
/**
|
|
223
|
+
* Tag options added during the provided callback as 'app' for grouped help.
|
|
224
|
+
* Allows downstream apps to demarcate their root-level options.
|
|
225
|
+
*/
|
|
226
|
+
tagAppOptions<T>(fn: (root: Command) => T): T;
|
|
227
|
+
/**
|
|
228
|
+
* Branding helper: set CLI name/description/version and optional help header.
|
|
229
|
+
* If version is omitted and importMetaUrl is provided, attempts to read the
|
|
230
|
+
* nearest package.json version (best-effort; non-fatal on failure).
|
|
231
|
+
*/
|
|
232
|
+
brand(args: {
|
|
233
|
+
name?: string;
|
|
234
|
+
description?: string;
|
|
235
|
+
version?: string;
|
|
236
|
+
importMetaUrl?: string;
|
|
237
|
+
helpHeader?: string;
|
|
238
|
+
}): Promise<this>;
|
|
133
239
|
/**
|
|
134
240
|
* Register a plugin for installation (parent level).
|
|
135
241
|
* Installation occurs on first resolveAndLoad() (or explicit install()).
|
|
@@ -187,5 +293,11 @@ type DefineSpec = Omit<GetDotenvCliPlugin, 'children' | 'use'> & {
|
|
|
187
293
|
*/
|
|
188
294
|
declare const definePlugin: (spec: DefineSpec) => GetDotenvCliPlugin;
|
|
189
295
|
|
|
190
|
-
|
|
191
|
-
|
|
296
|
+
/**
|
|
297
|
+
* Helper to retrieve the merged root options bag from any action handler
|
|
298
|
+
* that only has access to thisCommand. Avoids structural casts.
|
|
299
|
+
*/
|
|
300
|
+
declare const readMergedOptions: (cmd: Command) => GetDotenvCliOptions | undefined;
|
|
301
|
+
|
|
302
|
+
export { GetDotenvCli, definePlugin, readMergedOptions };
|
|
303
|
+
export type { DefineSpec, GetDotenvCliCtx, GetDotenvCliOptions, GetDotenvCliPlugin, ScriptsTable };
|
package/dist/cliHost.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { packageDirectory } from 'package-directory';
|
|
4
|
+
import url, { fileURLToPath, pathToFileURL } from 'url';
|
|
4
5
|
import path, { join, extname } from 'path';
|
|
5
6
|
import { z } from 'zod';
|
|
6
|
-
import url, { fileURLToPath, pathToFileURL } from 'url';
|
|
7
7
|
import YAML from 'yaml';
|
|
8
8
|
import { nanoid } from 'nanoid';
|
|
9
9
|
import { parse } from 'dotenv';
|
|
@@ -981,6 +981,8 @@ const computeContext = async (customOptions, plugins, hostMetaUrl) => {
|
|
|
981
981
|
|
|
982
982
|
const HOST_META_URL = import.meta.url;
|
|
983
983
|
const CTX_SYMBOL = Symbol('GetDotenvCli.ctx');
|
|
984
|
+
const OPTS_SYMBOL = Symbol('GetDotenvCli.options');
|
|
985
|
+
const HELP_HEADER_SYMBOL = Symbol('GetDotenvCli.helpHeader');
|
|
984
986
|
/**
|
|
985
987
|
* Plugin-first CLI host for get-dotenv. Extends Commander.Command.
|
|
986
988
|
*
|
|
@@ -997,15 +999,33 @@ class GetDotenvCli extends Command {
|
|
|
997
999
|
_plugins = [];
|
|
998
1000
|
/** One-time installation guard */
|
|
999
1001
|
_installed = false;
|
|
1002
|
+
/** Optional header line to prepend in help output */
|
|
1003
|
+
[HELP_HEADER_SYMBOL];
|
|
1000
1004
|
constructor(alias = 'getdotenv') {
|
|
1001
1005
|
super(alias);
|
|
1002
1006
|
// Ensure subcommands that use passThroughOptions can be attached safely.
|
|
1003
1007
|
// Commander requires parent commands to enable positional options when a
|
|
1004
1008
|
// child uses passThroughOptions.
|
|
1005
1009
|
this.enablePositionalOptions();
|
|
1010
|
+
// Configure grouped help: show only base options in default "Options";
|
|
1011
|
+
// append App/Plugin sections after default help.
|
|
1012
|
+
this.configureHelp({
|
|
1013
|
+
visibleOptions: (cmd) => {
|
|
1014
|
+
const all = cmd.options ??
|
|
1015
|
+
[];
|
|
1016
|
+
return all.filter((opt) => {
|
|
1017
|
+
const group = opt.__group;
|
|
1018
|
+
return group === 'base';
|
|
1019
|
+
});
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
this.addHelpText('beforeAll', () => {
|
|
1023
|
+
const header = this[HELP_HEADER_SYMBOL];
|
|
1024
|
+
return header && header.length > 0 ? `${header}\n\n` : '';
|
|
1025
|
+
});
|
|
1026
|
+
this.addHelpText('afterAll', (ctx) => this.#renderOptionGroups(ctx.command));
|
|
1006
1027
|
// Skeleton preSubcommand hook: produce a context if absent, without
|
|
1007
|
-
// mutating process.env. The passOptions hook (when installed) will
|
|
1008
|
-
// compute the final context using merged CLI options; keeping
|
|
1028
|
+
// mutating process.env. The passOptions hook (when installed) will // compute the final context using merged CLI options; keeping
|
|
1009
1029
|
// loadProcess=false here avoids leaking dotenv values into the parent
|
|
1010
1030
|
// process env before subcommands execute.
|
|
1011
1031
|
this.hook('preSubcommand', async () => {
|
|
@@ -1037,11 +1057,96 @@ class GetDotenvCli extends Command {
|
|
|
1037
1057
|
getCtx() {
|
|
1038
1058
|
return this[CTX_SYMBOL];
|
|
1039
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Retrieve the merged root CLI options bag (if set by passOptions()).
|
|
1062
|
+
* Downstream-safe: no generics required.
|
|
1063
|
+
*/
|
|
1064
|
+
getOptions() {
|
|
1065
|
+
return this[OPTS_SYMBOL];
|
|
1066
|
+
}
|
|
1067
|
+
/** Internal: set the merged root options bag for this run. */
|
|
1068
|
+
_setOptionsBag(bag) {
|
|
1069
|
+
this[OPTS_SYMBOL] = bag;
|
|
1070
|
+
}
|
|
1040
1071
|
/** * Convenience helper to create a namespaced subcommand.
|
|
1041
1072
|
*/
|
|
1042
1073
|
ns(name) {
|
|
1043
1074
|
return this.command(name);
|
|
1044
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Tag options added during the provided callback as 'app' for grouped help.
|
|
1078
|
+
* Allows downstream apps to demarcate their root-level options.
|
|
1079
|
+
*/
|
|
1080
|
+
tagAppOptions(fn) {
|
|
1081
|
+
const root = this;
|
|
1082
|
+
const originalAddOption = root.addOption.bind(root);
|
|
1083
|
+
const originalOption = root.option.bind(root);
|
|
1084
|
+
const tagLatest = (cmd, group) => {
|
|
1085
|
+
const optsArr = cmd.options;
|
|
1086
|
+
if (Array.isArray(optsArr) && optsArr.length > 0) {
|
|
1087
|
+
const last = optsArr[optsArr.length - 1];
|
|
1088
|
+
last.__group = group;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
root.addOption = function patchedAdd(opt) {
|
|
1092
|
+
opt.__group = 'app';
|
|
1093
|
+
return originalAddOption(opt);
|
|
1094
|
+
};
|
|
1095
|
+
root.option = function patchedOption(...args) {
|
|
1096
|
+
const ret = originalOption(...args);
|
|
1097
|
+
tagLatest(this, 'app');
|
|
1098
|
+
return ret;
|
|
1099
|
+
};
|
|
1100
|
+
try {
|
|
1101
|
+
return fn(root);
|
|
1102
|
+
}
|
|
1103
|
+
finally {
|
|
1104
|
+
root.addOption = originalAddOption;
|
|
1105
|
+
root.option = originalOption;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Branding helper: set CLI name/description/version and optional help header.
|
|
1110
|
+
* If version is omitted and importMetaUrl is provided, attempts to read the
|
|
1111
|
+
* nearest package.json version (best-effort; non-fatal on failure).
|
|
1112
|
+
*/
|
|
1113
|
+
async brand(args) {
|
|
1114
|
+
const { name, description, version, importMetaUrl, helpHeader } = args;
|
|
1115
|
+
if (typeof name === 'string' && name.length > 0)
|
|
1116
|
+
this.name(name);
|
|
1117
|
+
if (typeof description === 'string')
|
|
1118
|
+
this.description(description);
|
|
1119
|
+
let v = version;
|
|
1120
|
+
if (!v && importMetaUrl) {
|
|
1121
|
+
try {
|
|
1122
|
+
const fromUrl = fileURLToPath(importMetaUrl);
|
|
1123
|
+
const pkgDir = await packageDirectory({ cwd: fromUrl });
|
|
1124
|
+
if (pkgDir) {
|
|
1125
|
+
const txt = await fs.readFile(`${pkgDir}/package.json`, 'utf-8');
|
|
1126
|
+
const pkg = JSON.parse(txt);
|
|
1127
|
+
if (pkg.version)
|
|
1128
|
+
v = pkg.version;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
// best-effort only
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (v)
|
|
1136
|
+
this.version(v);
|
|
1137
|
+
// Help header:
|
|
1138
|
+
// - If caller provides helpHeader, use it.
|
|
1139
|
+
// - Otherwise, when a version is known, default to "<name> v<version>".
|
|
1140
|
+
if (typeof helpHeader === 'string') {
|
|
1141
|
+
this[HELP_HEADER_SYMBOL] = helpHeader;
|
|
1142
|
+
}
|
|
1143
|
+
else if (v) {
|
|
1144
|
+
// Use the current command name (possibly overridden by 'name' above).
|
|
1145
|
+
const header = `${this.name()} v${v}`;
|
|
1146
|
+
this[HELP_HEADER_SYMBOL] = header;
|
|
1147
|
+
}
|
|
1148
|
+
return this;
|
|
1149
|
+
}
|
|
1045
1150
|
/**
|
|
1046
1151
|
* Register a plugin for installation (parent level).
|
|
1047
1152
|
* Installation occurs on first resolveAndLoad() (or explicit install()).
|
|
@@ -1080,6 +1185,69 @@ class GetDotenvCli extends Command {
|
|
|
1080
1185
|
for (const p of this._plugins)
|
|
1081
1186
|
await run(p);
|
|
1082
1187
|
}
|
|
1188
|
+
// Render App/Plugin grouped options appended after default help.
|
|
1189
|
+
#renderOptionGroups(cmd) {
|
|
1190
|
+
const all = cmd.options ?? [];
|
|
1191
|
+
const byGroup = new Map();
|
|
1192
|
+
for (const o of all) {
|
|
1193
|
+
const opt = o;
|
|
1194
|
+
const g = opt.__group;
|
|
1195
|
+
if (!g || g === 'base')
|
|
1196
|
+
continue; // base handled by default help
|
|
1197
|
+
const rows = byGroup.get(g) ?? [];
|
|
1198
|
+
rows.push({
|
|
1199
|
+
flags: opt.flags ?? '',
|
|
1200
|
+
description: opt.description ?? '',
|
|
1201
|
+
});
|
|
1202
|
+
byGroup.set(g, rows);
|
|
1203
|
+
}
|
|
1204
|
+
if (byGroup.size === 0)
|
|
1205
|
+
return '';
|
|
1206
|
+
const renderRows = (title, rows) => {
|
|
1207
|
+
const width = Math.min(40, rows.reduce((m, r) => Math.max(m, r.flags.length), 0));
|
|
1208
|
+
const lines = rows
|
|
1209
|
+
.map((r) => {
|
|
1210
|
+
const pad = ' '.repeat(Math.max(2, width - r.flags.length + 2));
|
|
1211
|
+
return ` ${r.flags}${pad}${r.description}`.trimEnd();
|
|
1212
|
+
})
|
|
1213
|
+
.join('\n');
|
|
1214
|
+
return `\n${title}:\n${lines}\n`;
|
|
1215
|
+
};
|
|
1216
|
+
let out = '';
|
|
1217
|
+
// App options (if any)
|
|
1218
|
+
const app = byGroup.get('app');
|
|
1219
|
+
if (app && app.length > 0) {
|
|
1220
|
+
out += renderRows('App options', app);
|
|
1221
|
+
}
|
|
1222
|
+
// Plugin groups sorted by id
|
|
1223
|
+
const pluginKeys = Array.from(byGroup.keys()).filter((k) => k.startsWith('plugin:'));
|
|
1224
|
+
pluginKeys.sort((a, b) => a.localeCompare(b));
|
|
1225
|
+
for (const k of pluginKeys) {
|
|
1226
|
+
const id = k.slice('plugin:'.length) || '(unknown)';
|
|
1227
|
+
const rows = byGroup.get(k) ?? [];
|
|
1228
|
+
if (rows.length > 0) {
|
|
1229
|
+
out += renderRows(`Plugin options — ${id}`, rows);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1083
1234
|
}
|
|
1084
1235
|
|
|
1085
|
-
|
|
1236
|
+
/**
|
|
1237
|
+
* Helper to retrieve the merged root options bag from any action handler
|
|
1238
|
+
* that only has access to thisCommand. Avoids structural casts.
|
|
1239
|
+
*/
|
|
1240
|
+
const readMergedOptions = (cmd) => {
|
|
1241
|
+
// Ascend to the root command
|
|
1242
|
+
let root = cmd;
|
|
1243
|
+
while (root.parent) {
|
|
1244
|
+
root = root.parent;
|
|
1245
|
+
}
|
|
1246
|
+
const hostAny = root;
|
|
1247
|
+
return typeof hostAny.getOptions === 'function'
|
|
1248
|
+
? hostAny.getOptions()
|
|
1249
|
+
: root
|
|
1250
|
+
.getDotenvCliOptions;
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
export { GetDotenvCli, definePlugin, readMergedOptions };
|
package/dist/getdotenv.cli.mjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { Command, Option } from 'commander';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
4
|
import { packageDirectory } from 'package-directory';
|
|
5
|
+
import url, { fileURLToPath, pathToFileURL } from 'url';
|
|
5
6
|
import path, { join, extname } from 'path';
|
|
6
7
|
import { z } from 'zod';
|
|
7
|
-
import url, { fileURLToPath, pathToFileURL } from 'url';
|
|
8
8
|
import YAML from 'yaml';
|
|
9
9
|
import { nanoid } from 'nanoid';
|
|
10
10
|
import { parse } from 'dotenv';
|
|
@@ -981,6 +981,8 @@ const computeContext = async (customOptions, plugins, hostMetaUrl) => {
|
|
|
981
981
|
|
|
982
982
|
const HOST_META_URL = import.meta.url;
|
|
983
983
|
const CTX_SYMBOL = Symbol('GetDotenvCli.ctx');
|
|
984
|
+
const OPTS_SYMBOL = Symbol('GetDotenvCli.options');
|
|
985
|
+
const HELP_HEADER_SYMBOL = Symbol('GetDotenvCli.helpHeader');
|
|
984
986
|
/**
|
|
985
987
|
* Plugin-first CLI host for get-dotenv. Extends Commander.Command.
|
|
986
988
|
*
|
|
@@ -997,15 +999,33 @@ class GetDotenvCli extends Command {
|
|
|
997
999
|
_plugins = [];
|
|
998
1000
|
/** One-time installation guard */
|
|
999
1001
|
_installed = false;
|
|
1002
|
+
/** Optional header line to prepend in help output */
|
|
1003
|
+
[HELP_HEADER_SYMBOL];
|
|
1000
1004
|
constructor(alias = 'getdotenv') {
|
|
1001
1005
|
super(alias);
|
|
1002
1006
|
// Ensure subcommands that use passThroughOptions can be attached safely.
|
|
1003
1007
|
// Commander requires parent commands to enable positional options when a
|
|
1004
1008
|
// child uses passThroughOptions.
|
|
1005
1009
|
this.enablePositionalOptions();
|
|
1010
|
+
// Configure grouped help: show only base options in default "Options";
|
|
1011
|
+
// append App/Plugin sections after default help.
|
|
1012
|
+
this.configureHelp({
|
|
1013
|
+
visibleOptions: (cmd) => {
|
|
1014
|
+
const all = cmd.options ??
|
|
1015
|
+
[];
|
|
1016
|
+
return all.filter((opt) => {
|
|
1017
|
+
const group = opt.__group;
|
|
1018
|
+
return group === 'base';
|
|
1019
|
+
});
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
this.addHelpText('beforeAll', () => {
|
|
1023
|
+
const header = this[HELP_HEADER_SYMBOL];
|
|
1024
|
+
return header && header.length > 0 ? `${header}\n\n` : '';
|
|
1025
|
+
});
|
|
1026
|
+
this.addHelpText('afterAll', (ctx) => this.#renderOptionGroups(ctx.command));
|
|
1006
1027
|
// Skeleton preSubcommand hook: produce a context if absent, without
|
|
1007
|
-
// mutating process.env. The passOptions hook (when installed) will
|
|
1008
|
-
// compute the final context using merged CLI options; keeping
|
|
1028
|
+
// mutating process.env. The passOptions hook (when installed) will // compute the final context using merged CLI options; keeping
|
|
1009
1029
|
// loadProcess=false here avoids leaking dotenv values into the parent
|
|
1010
1030
|
// process env before subcommands execute.
|
|
1011
1031
|
this.hook('preSubcommand', async () => {
|
|
@@ -1037,11 +1057,96 @@ class GetDotenvCli extends Command {
|
|
|
1037
1057
|
getCtx() {
|
|
1038
1058
|
return this[CTX_SYMBOL];
|
|
1039
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Retrieve the merged root CLI options bag (if set by passOptions()).
|
|
1062
|
+
* Downstream-safe: no generics required.
|
|
1063
|
+
*/
|
|
1064
|
+
getOptions() {
|
|
1065
|
+
return this[OPTS_SYMBOL];
|
|
1066
|
+
}
|
|
1067
|
+
/** Internal: set the merged root options bag for this run. */
|
|
1068
|
+
_setOptionsBag(bag) {
|
|
1069
|
+
this[OPTS_SYMBOL] = bag;
|
|
1070
|
+
}
|
|
1040
1071
|
/** * Convenience helper to create a namespaced subcommand.
|
|
1041
1072
|
*/
|
|
1042
1073
|
ns(name) {
|
|
1043
1074
|
return this.command(name);
|
|
1044
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Tag options added during the provided callback as 'app' for grouped help.
|
|
1078
|
+
* Allows downstream apps to demarcate their root-level options.
|
|
1079
|
+
*/
|
|
1080
|
+
tagAppOptions(fn) {
|
|
1081
|
+
const root = this;
|
|
1082
|
+
const originalAddOption = root.addOption.bind(root);
|
|
1083
|
+
const originalOption = root.option.bind(root);
|
|
1084
|
+
const tagLatest = (cmd, group) => {
|
|
1085
|
+
const optsArr = cmd.options;
|
|
1086
|
+
if (Array.isArray(optsArr) && optsArr.length > 0) {
|
|
1087
|
+
const last = optsArr[optsArr.length - 1];
|
|
1088
|
+
last.__group = group;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
root.addOption = function patchedAdd(opt) {
|
|
1092
|
+
opt.__group = 'app';
|
|
1093
|
+
return originalAddOption(opt);
|
|
1094
|
+
};
|
|
1095
|
+
root.option = function patchedOption(...args) {
|
|
1096
|
+
const ret = originalOption(...args);
|
|
1097
|
+
tagLatest(this, 'app');
|
|
1098
|
+
return ret;
|
|
1099
|
+
};
|
|
1100
|
+
try {
|
|
1101
|
+
return fn(root);
|
|
1102
|
+
}
|
|
1103
|
+
finally {
|
|
1104
|
+
root.addOption = originalAddOption;
|
|
1105
|
+
root.option = originalOption;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Branding helper: set CLI name/description/version and optional help header.
|
|
1110
|
+
* If version is omitted and importMetaUrl is provided, attempts to read the
|
|
1111
|
+
* nearest package.json version (best-effort; non-fatal on failure).
|
|
1112
|
+
*/
|
|
1113
|
+
async brand(args) {
|
|
1114
|
+
const { name, description, version, importMetaUrl, helpHeader } = args;
|
|
1115
|
+
if (typeof name === 'string' && name.length > 0)
|
|
1116
|
+
this.name(name);
|
|
1117
|
+
if (typeof description === 'string')
|
|
1118
|
+
this.description(description);
|
|
1119
|
+
let v = version;
|
|
1120
|
+
if (!v && importMetaUrl) {
|
|
1121
|
+
try {
|
|
1122
|
+
const fromUrl = fileURLToPath(importMetaUrl);
|
|
1123
|
+
const pkgDir = await packageDirectory({ cwd: fromUrl });
|
|
1124
|
+
if (pkgDir) {
|
|
1125
|
+
const txt = await fs.readFile(`${pkgDir}/package.json`, 'utf-8');
|
|
1126
|
+
const pkg = JSON.parse(txt);
|
|
1127
|
+
if (pkg.version)
|
|
1128
|
+
v = pkg.version;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
// best-effort only
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (v)
|
|
1136
|
+
this.version(v);
|
|
1137
|
+
// Help header:
|
|
1138
|
+
// - If caller provides helpHeader, use it.
|
|
1139
|
+
// - Otherwise, when a version is known, default to "<name> v<version>".
|
|
1140
|
+
if (typeof helpHeader === 'string') {
|
|
1141
|
+
this[HELP_HEADER_SYMBOL] = helpHeader;
|
|
1142
|
+
}
|
|
1143
|
+
else if (v) {
|
|
1144
|
+
// Use the current command name (possibly overridden by 'name' above).
|
|
1145
|
+
const header = `${this.name()} v${v}`;
|
|
1146
|
+
this[HELP_HEADER_SYMBOL] = header;
|
|
1147
|
+
}
|
|
1148
|
+
return this;
|
|
1149
|
+
}
|
|
1045
1150
|
/**
|
|
1046
1151
|
* Register a plugin for installation (parent level).
|
|
1047
1152
|
* Installation occurs on first resolveAndLoad() (or explicit install()).
|
|
@@ -1080,6 +1185,52 @@ class GetDotenvCli extends Command {
|
|
|
1080
1185
|
for (const p of this._plugins)
|
|
1081
1186
|
await run(p);
|
|
1082
1187
|
}
|
|
1188
|
+
// Render App/Plugin grouped options appended after default help.
|
|
1189
|
+
#renderOptionGroups(cmd) {
|
|
1190
|
+
const all = cmd.options ?? [];
|
|
1191
|
+
const byGroup = new Map();
|
|
1192
|
+
for (const o of all) {
|
|
1193
|
+
const opt = o;
|
|
1194
|
+
const g = opt.__group;
|
|
1195
|
+
if (!g || g === 'base')
|
|
1196
|
+
continue; // base handled by default help
|
|
1197
|
+
const rows = byGroup.get(g) ?? [];
|
|
1198
|
+
rows.push({
|
|
1199
|
+
flags: opt.flags ?? '',
|
|
1200
|
+
description: opt.description ?? '',
|
|
1201
|
+
});
|
|
1202
|
+
byGroup.set(g, rows);
|
|
1203
|
+
}
|
|
1204
|
+
if (byGroup.size === 0)
|
|
1205
|
+
return '';
|
|
1206
|
+
const renderRows = (title, rows) => {
|
|
1207
|
+
const width = Math.min(40, rows.reduce((m, r) => Math.max(m, r.flags.length), 0));
|
|
1208
|
+
const lines = rows
|
|
1209
|
+
.map((r) => {
|
|
1210
|
+
const pad = ' '.repeat(Math.max(2, width - r.flags.length + 2));
|
|
1211
|
+
return ` ${r.flags}${pad}${r.description}`.trimEnd();
|
|
1212
|
+
})
|
|
1213
|
+
.join('\n');
|
|
1214
|
+
return `\n${title}:\n${lines}\n`;
|
|
1215
|
+
};
|
|
1216
|
+
let out = '';
|
|
1217
|
+
// App options (if any)
|
|
1218
|
+
const app = byGroup.get('app');
|
|
1219
|
+
if (app && app.length > 0) {
|
|
1220
|
+
out += renderRows('App options', app);
|
|
1221
|
+
}
|
|
1222
|
+
// Plugin groups sorted by id
|
|
1223
|
+
const pluginKeys = Array.from(byGroup.keys()).filter((k) => k.startsWith('plugin:'));
|
|
1224
|
+
pluginKeys.sort((a, b) => a.localeCompare(b));
|
|
1225
|
+
for (const k of pluginKeys) {
|
|
1226
|
+
const id = k.slice('plugin:'.length) || '(unknown)';
|
|
1227
|
+
const rows = byGroup.get(k) ?? [];
|
|
1228
|
+
if (rows.length > 0) {
|
|
1229
|
+
out += renderRows(`Plugin options — ${id}`, rows);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
return out;
|
|
1233
|
+
}
|
|
1083
1234
|
}
|
|
1084
1235
|
|
|
1085
1236
|
/**
|
|
@@ -1087,6 +1238,28 @@ class GetDotenvCli extends Command {
|
|
|
1087
1238
|
* Uses provided defaults to render help labels without coupling to generators.
|
|
1088
1239
|
*/
|
|
1089
1240
|
const attachRootOptions = (program, defaults, opts) => {
|
|
1241
|
+
// Install temporary wrappers to tag all options added here as "base".
|
|
1242
|
+
const GROUP = 'base';
|
|
1243
|
+
const tagLatest = (cmd, group) => {
|
|
1244
|
+
const optsArr = cmd.options;
|
|
1245
|
+
if (Array.isArray(optsArr) && optsArr.length > 0) {
|
|
1246
|
+
const last = optsArr[optsArr.length - 1];
|
|
1247
|
+
last.__group = group;
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
const originalAddOption = program.addOption.bind(program);
|
|
1251
|
+
const originalOption = program.option.bind(program);
|
|
1252
|
+
program.addOption = function patchedAdd(opt) {
|
|
1253
|
+
// Tag before adding, in case consumers inspect the Option directly.
|
|
1254
|
+
opt.__group = GROUP;
|
|
1255
|
+
const ret = originalAddOption(opt);
|
|
1256
|
+
return ret;
|
|
1257
|
+
};
|
|
1258
|
+
program.option = function patchedOption(...args) {
|
|
1259
|
+
const ret = originalOption(...args);
|
|
1260
|
+
tagLatest(this, GROUP);
|
|
1261
|
+
return ret;
|
|
1262
|
+
};
|
|
1090
1263
|
const { defaultEnv, dotenvToken, dynamicPath, env, excludeDynamic, excludeEnv, excludeGlobal, excludePrivate, excludePublic, loadProcess, log, outputPath, paths, pathsDelimiter, pathsDelimiterPattern, privateToken, scripts, shell, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern, } = defaults ?? {};
|
|
1091
1264
|
const va = typeof defaults?.varsAssignor === 'string' ? defaults.varsAssignor : '=';
|
|
1092
1265
|
const vd = typeof defaults?.varsDelimiter === 'string' ? defaults.varsDelimiter : ' ';
|
|
@@ -1159,6 +1332,9 @@ const attachRootOptions = (program, defaults, opts) => {
|
|
|
1159
1332
|
.hideHelp());
|
|
1160
1333
|
// Diagnostics: opt-in tracing; optional variadic keys after the flag.
|
|
1161
1334
|
p = p.option('--trace [keys...]', 'emit diagnostics for child env composition (optional keys)');
|
|
1335
|
+
// Restore original methods to avoid tagging future additions outside base.
|
|
1336
|
+
program.addOption = originalAddOption;
|
|
1337
|
+
program.option = originalOption;
|
|
1162
1338
|
return p;
|
|
1163
1339
|
};
|
|
1164
1340
|
|
|
@@ -1298,6 +1474,8 @@ GetDotenvCli.prototype.passOptions = function (defaults) {
|
|
|
1298
1474
|
// Persist merged options for nested invocations (batch exec).
|
|
1299
1475
|
thisCommand.getDotenvCliOptions =
|
|
1300
1476
|
merged;
|
|
1477
|
+
// Also store on the host for downstream ergonomic accessors.
|
|
1478
|
+
this._setOptionsBag(merged);
|
|
1301
1479
|
// Build service options and compute context (always-on config loader path).
|
|
1302
1480
|
const serviceOptions = getDotenvCliOptions2Options(merged);
|
|
1303
1481
|
await this.resolveAndLoad(serviceOptions);
|
|
@@ -1309,6 +1487,7 @@ GetDotenvCli.prototype.passOptions = function (defaults) {
|
|
|
1309
1487
|
const { merged } = resolveCliOptions(raw, d, process.env.getDotenvCliOptions);
|
|
1310
1488
|
thisCommand.getDotenvCliOptions =
|
|
1311
1489
|
merged;
|
|
1490
|
+
this._setOptionsBag(merged);
|
|
1312
1491
|
// Avoid duplicate heavy work if a context is already present.
|
|
1313
1492
|
if (!this.getCtx()) {
|
|
1314
1493
|
const serviceOptions = getDotenvCliOptions2Options(merged);
|
|
@@ -2329,6 +2508,17 @@ const attachParentAlias = (cli, options, _cmd) => {
|
|
|
2329
2508
|
const desc = aliasSpec.description ??
|
|
2330
2509
|
'alias of cmd subcommand; provide command tokens (variadic)';
|
|
2331
2510
|
cli.option(aliasSpec.flags, desc);
|
|
2511
|
+
// Tag the just-added parent option for grouped help rendering.
|
|
2512
|
+
try {
|
|
2513
|
+
const optsArr = cli.options;
|
|
2514
|
+
if (Array.isArray(optsArr) && optsArr.length > 0) {
|
|
2515
|
+
const last = optsArr[optsArr.length - 1];
|
|
2516
|
+
last.__group = 'plugin:cmd';
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
catch {
|
|
2520
|
+
/* noop */
|
|
2521
|
+
}
|
|
2332
2522
|
// Shared alias executor for either preAction or preSubcommand hooks.
|
|
2333
2523
|
// Ensure we only execute once even if both hooks fire in a single parse.
|
|
2334
2524
|
let aliasHandled = false;
|
|
@@ -3059,7 +3249,13 @@ const initPlugin = (opts = {}) => definePlugin({
|
|
|
3059
3249
|
});
|
|
3060
3250
|
|
|
3061
3251
|
// Shipped CLI rebased on plugin-first host.
|
|
3062
|
-
const program = new GetDotenvCli('getdotenv')
|
|
3252
|
+
const program = new GetDotenvCli('getdotenv');
|
|
3253
|
+
// Brand the shipped CLI so help shows the package version (e.g., "getdotenv v5.0.0").
|
|
3254
|
+
await program.brand({
|
|
3255
|
+
importMetaUrl: import.meta.url,
|
|
3256
|
+
description: 'Base CLI.',
|
|
3257
|
+
});
|
|
3258
|
+
program
|
|
3063
3259
|
.attachRootOptions({ loadProcess: false })
|
|
3064
3260
|
.use(cmdPlugin({ asDefault: true, optionAlias: '-c, --cmd <command...>' }))
|
|
3065
3261
|
.use(batchPlugin())
|