@qui-cli/core 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/LICENSE +674 -0
- package/README.md +130 -0
- package/dist/Core.d.ts +15 -0
- package/dist/Core.js +58 -0
- package/dist/Help.d.ts +9 -0
- package/dist/Help.js +26 -0
- package/dist/JackSpeak.d.ts +9 -0
- package/dist/JackSpeak.js +44 -0
- package/dist/Positionals.d.ts +47 -0
- package/dist/Positionals.js +203 -0
- package/dist/Usage.d.ts +1 -0
- package/dist/Usage.js +13 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +9 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @qui-cli/core
|
|
2
|
+
|
|
3
|
+
Core features of @qui-cli/qui-cli
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.com/package/@qui-cli/core)
|
|
6
|
+
[](https://nodejs.org/api/esm.html)
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @qui-cli/core
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { Core } from '@qui-cli/core';
|
|
18
|
+
import { Colors } from '@qui-cli/colors';
|
|
19
|
+
import { Log } from '@qui-cli/log';
|
|
20
|
+
|
|
21
|
+
// register custom arguments
|
|
22
|
+
await Core.init({
|
|
23
|
+
opt: {
|
|
24
|
+
foo: {
|
|
25
|
+
description: 'bar'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// process user-provided command-line arguments
|
|
31
|
+
const { values, positionals } = await Core.run();
|
|
32
|
+
|
|
33
|
+
// use Log and Colors
|
|
34
|
+
Log.debug(values.foo);
|
|
35
|
+
Log.debug(positionals.length);
|
|
36
|
+
Log.info(
|
|
37
|
+
`This is a ${Colors.value('value')} and a ${Colors.quotedValue('"quoted value"')}.`
|
|
38
|
+
);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
See [examples](https://github.com/battis/qui-cli/tree/main/examples#readme) for common use cases.
|
|
42
|
+
|
|
43
|
+
## Core Plugins
|
|
44
|
+
|
|
45
|
+
Three core plugins are registered automatically to provide consistent functionality.
|
|
46
|
+
|
|
47
|
+
### `jackspeak`
|
|
48
|
+
|
|
49
|
+
Manages any custom [jackspeak](https://www.npmjs.com/package/jackspeak#user-content-jackoptions-jackoptions----jack) options. As with any other plugin, it can be configured via `Core.configure()`:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
await Core.configure({
|
|
53
|
+
jackspeak: {
|
|
54
|
+
envPrefix: 'MY_APP'
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `positionals`
|
|
60
|
+
|
|
61
|
+
Provides per-plugin positonal argument management and documentation. Individual plugins can require named positional arguments, which are collected and documented by `usage()` and accessible throgh the `Positionals` plugin.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { Core, Positionals } from '@qui-cli/core';
|
|
65
|
+
|
|
66
|
+
Positionals.require({
|
|
67
|
+
my_number: {
|
|
68
|
+
description: 'How far left to move',
|
|
69
|
+
validate: (v?: string) => !isNaN(v) || 'my_number must be a numeric value'
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await Core.run();
|
|
74
|
+
|
|
75
|
+
console.log(Positionals.get('my_number'));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Positional arguments are processed in the order in which plugins are registered. Thus, if plugin A requires positional arguments `a1` and `a2`, plugn B requires positional arguments `b1` and `b2` and depends on plugin C which requires positional argument `c1`, the resulting positional argument order would be: `a1 a2 c1 b1 b2`.
|
|
79
|
+
|
|
80
|
+
Unnamed arguments can also be required, however this is a dicey proposition to implement within a plugin and is likely best reserved for independent apps that consume plugins, as the `min` and `max` number of positional arguments check against the currently registered list of required named positonal arguments for validity (as well as their own respective values). In general, configuring unnamed positional arguments is best done before any required named positional arguments are defined.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// require at least 2 and no more than 5 unnamed positionals:
|
|
84
|
+
// arg0 arg1 [arg2 arg3 arg4]
|
|
85
|
+
Positionals.configure({ min: 2, max: 5 });
|
|
86
|
+
|
|
87
|
+
//. nemo arg0 arg1 [arg2 arg3 arg4]
|
|
88
|
+
Positionals.require({ nemo: { description: 'I have a name!' } });
|
|
89
|
+
|
|
90
|
+
// fails: nemo is required, must be at least 1
|
|
91
|
+
Positionals.setMinArg(0);
|
|
92
|
+
|
|
93
|
+
// succeeds
|
|
94
|
+
Positionals.setMinArg(6);
|
|
95
|
+
|
|
96
|
+
// fails: is less than current min
|
|
97
|
+
Positionals.setMaxArg(4);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `help`
|
|
101
|
+
|
|
102
|
+
Provides consistent `--help` (`-h`) flag for all commands that displays usage. No confiuration.
|
|
103
|
+
|
|
104
|
+
## Configuration: `configure(config?: Configuration): void`
|
|
105
|
+
|
|
106
|
+
Programmatic configuration to set defaults before generating user-facing usage documentation or processing user-provided command line arguments.
|
|
107
|
+
|
|
108
|
+
Invoking `Core.configure()` triggers the `configure()` hook for all registered plugins.
|
|
109
|
+
|
|
110
|
+
Refer to [@qui-cli/plugin](https://www.npmjs.com/package/@qui-cli/plugin#user-content-configuration) for further details on plugin configuration.
|
|
111
|
+
|
|
112
|
+
## Options: `options(): Options`
|
|
113
|
+
|
|
114
|
+
Generate command-line options for jackspeak initialization and user-facing usage documentation.
|
|
115
|
+
|
|
116
|
+
Invoking `Core.options()` merges the `options()` hooks of all registered plugins.
|
|
117
|
+
|
|
118
|
+
Refer to [@qui-cli/plugin](https://www.npmjs.com/package/@qui-cli/plugin#user-content-options) for further details on plugin options.
|
|
119
|
+
|
|
120
|
+
The `--help` (`-h`) flag is appended to output user-readable usage information to the command-line.
|
|
121
|
+
|
|
122
|
+
## Initialization: `init(options?: Options): Arguments`
|
|
123
|
+
|
|
124
|
+
Initialize the app with user-provided command-line arguments, processed based on the result of `options()`, returning the [processed user-provided command-line arguments from jackspeak](https://www.npmjs.com/package/jackspeak#user-content-jackparseargs-string--processargv--positionals-string-values-optionsresults-).
|
|
125
|
+
|
|
126
|
+
Invoking `Core.init()` also initializes the `init()` hook for all registered plugins.
|
|
127
|
+
|
|
128
|
+
Refer to [@qui-cli/plugin](https://www.npmjs.com/package/@qui-cli/plugin#user-content-initialization) for further details on plugin initialization.
|
|
129
|
+
|
|
130
|
+
Additional app-specific command-line options can be provided with an `Options` object as described in [@qui-cli/plugin](https://www.npmjs.com/package/@qui-cli/plugin#user-content-options).
|
package/dist/Core.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
2
|
+
import * as JackSpeak from './JackSpeak.js';
|
|
3
|
+
export { Options } from '@qui-cli/plugin';
|
|
4
|
+
export * from './Usage.js';
|
|
5
|
+
export type Configuration = Plugin.Registrar.Configuration & {
|
|
6
|
+
/** @deprecated Use `jackspeak` property */
|
|
7
|
+
core?: JackSpeak.Configuration & {
|
|
8
|
+
/** @deprecated Use `Positional` plugin */
|
|
9
|
+
requirePositionals?: boolean | number;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function configure(config?: Configuration): Promise<void>;
|
|
13
|
+
export declare function options(externalOptions?: Plugin.Options): Promise<Plugin.Options>;
|
|
14
|
+
export declare function init(externalOptions?: Plugin.Options): Promise<Plugin.Arguments<Plugin.Options>>;
|
|
15
|
+
export declare function run(externalOptions?: Plugin.Options): Promise<Plugin.AccumulatedResults | undefined>;
|
package/dist/Core.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as Colors from '@qui-cli/colors/dist/Colors.js';
|
|
2
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
3
|
+
import * as JackSpeak from './JackSpeak.js';
|
|
4
|
+
import * as Positionals from './Positionals.js';
|
|
5
|
+
import { usage } from './Usage.js';
|
|
6
|
+
export * from './Usage.js';
|
|
7
|
+
let initialized = false;
|
|
8
|
+
export async function configure(config = {}) {
|
|
9
|
+
const { core = {}, jackspeak: jackOptions, positionals = {} } = config;
|
|
10
|
+
const { requirePositionals, ...deprecated } = core;
|
|
11
|
+
const jackspeak = {
|
|
12
|
+
...deprecated,
|
|
13
|
+
allowPositionals: !!Positionals.requirePositionalsIsDeprecatedAndShouldNotBeUsed(positionals, requirePositionals),
|
|
14
|
+
...jackOptions
|
|
15
|
+
};
|
|
16
|
+
if (jackspeak.allowPositionals === false) {
|
|
17
|
+
positionals.max = 0;
|
|
18
|
+
}
|
|
19
|
+
await Plugin.Registrar.configure({ positionals, jackspeak });
|
|
20
|
+
}
|
|
21
|
+
export async function options(externalOptions = {}) {
|
|
22
|
+
/*
|
|
23
|
+
* TODO automate default value documentation
|
|
24
|
+
Issue URL: https://github.com/battis/qui-cli/issues/38
|
|
25
|
+
* Including parsing `env` (#34) and `secret` (#33) fields
|
|
26
|
+
*/
|
|
27
|
+
return Plugin.mergeOptions(await Plugin.Registrar.options(), externalOptions);
|
|
28
|
+
}
|
|
29
|
+
export async function init(externalOptions) {
|
|
30
|
+
if (initialized) {
|
|
31
|
+
throw new Error(`Already initialized with user-provided command line arguments.`);
|
|
32
|
+
}
|
|
33
|
+
for (const plugin of Plugin.Registrar.registered()) {
|
|
34
|
+
if (plugin.options) {
|
|
35
|
+
JackSpeak.args(await plugin.options());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (externalOptions) {
|
|
39
|
+
JackSpeak.args(externalOptions);
|
|
40
|
+
}
|
|
41
|
+
const args = JackSpeak.parse();
|
|
42
|
+
await Plugin.Registrar.init(args);
|
|
43
|
+
initialized = true;
|
|
44
|
+
return args;
|
|
45
|
+
}
|
|
46
|
+
export async function run(externalOptions) {
|
|
47
|
+
try {
|
|
48
|
+
if (!initialized) {
|
|
49
|
+
await init(externalOptions);
|
|
50
|
+
}
|
|
51
|
+
return await Plugin.Registrar.run();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
const error = e;
|
|
55
|
+
console.log(`${Colors.error(error.message)}\n\n${usage()}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/Help.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
2
|
+
export type Configuration = Plugin.Configuration & {
|
|
3
|
+
help?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare const name = "core.help";
|
|
6
|
+
export declare function configure(config?: Configuration): void;
|
|
7
|
+
export declare function options(): Plugin.Options;
|
|
8
|
+
export declare function init({ values }: Plugin.ExpectedArguments<typeof options>): void;
|
|
9
|
+
export declare function run(): void;
|
package/dist/Help.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
2
|
+
import * as Core from './Core.js';
|
|
3
|
+
export const name = 'core.help';
|
|
4
|
+
let help = false;
|
|
5
|
+
export function configure(config = {}) {
|
|
6
|
+
help = Plugin.hydrate(config.help, help);
|
|
7
|
+
}
|
|
8
|
+
export function options() {
|
|
9
|
+
return {
|
|
10
|
+
flag: {
|
|
11
|
+
help: {
|
|
12
|
+
description: 'Get usage information',
|
|
13
|
+
short: 'h'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function init({ values }) {
|
|
19
|
+
configure(values);
|
|
20
|
+
}
|
|
21
|
+
export function run() {
|
|
22
|
+
if (help) {
|
|
23
|
+
console.log(Core.usage());
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
2
|
+
import { Jack, JackOptions } from 'jackspeak';
|
|
3
|
+
export type Configuration = Plugin.Configuration & JackOptions;
|
|
4
|
+
export declare const name = "jackspeak";
|
|
5
|
+
export declare function jack(): Jack<{}>;
|
|
6
|
+
export declare function configure(config?: Configuration): void;
|
|
7
|
+
export declare function args(options: Plugin.Options): void;
|
|
8
|
+
export declare function parse(): import("jackspeak").Parsed<{}>;
|
|
9
|
+
export declare function usage(): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Jack } from 'jackspeak';
|
|
2
|
+
export const name = 'jackspeak';
|
|
3
|
+
let instance = undefined;
|
|
4
|
+
export function jack() {
|
|
5
|
+
if (!instance) {
|
|
6
|
+
configure();
|
|
7
|
+
}
|
|
8
|
+
if (!instance) {
|
|
9
|
+
throw new Error(`JackSpeak configuration failed.`);
|
|
10
|
+
}
|
|
11
|
+
return instance;
|
|
12
|
+
}
|
|
13
|
+
export function configure(config = {}) {
|
|
14
|
+
instance = new Jack(config);
|
|
15
|
+
}
|
|
16
|
+
export function args(options) {
|
|
17
|
+
for (const key in options) {
|
|
18
|
+
if (key === 'man') {
|
|
19
|
+
for (const paragraph of options[key]) {
|
|
20
|
+
if (paragraph.level) {
|
|
21
|
+
jack().heading(paragraph.text, paragraph.level, {
|
|
22
|
+
pre: paragraph.pre
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
jack().description(paragraph.text, { pre: paragraph.pre });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (key === 'fields') {
|
|
31
|
+
jack().addFields({ ...options[key] });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// @ts-expect-error 7053 typing is hard
|
|
35
|
+
jack()[key]({ ...options[key] });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function parse() {
|
|
40
|
+
return jack().parse();
|
|
41
|
+
}
|
|
42
|
+
export function usage() {
|
|
43
|
+
return jack().usage();
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
2
|
+
type PositionalConfig = {
|
|
3
|
+
description?: string;
|
|
4
|
+
hint?: string;
|
|
5
|
+
validate?: (v?: string) => boolean | string;
|
|
6
|
+
};
|
|
7
|
+
type PositionalConfigSet = Record<string, PositionalConfig>;
|
|
8
|
+
export type Configuration = Plugin.Configuration & {
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const name = "positionals";
|
|
13
|
+
/** @deprecated Do not use, included for backwards compatibility */
|
|
14
|
+
export declare function requirePositionalsIsDeprecatedAndShouldNotBeUsed(positionals: Configuration, arg?: boolean | number): number;
|
|
15
|
+
export declare function configure(config?: Configuration): void;
|
|
16
|
+
export declare function require(positionalConfigSet: PositionalConfigSet): void;
|
|
17
|
+
export declare function minimumArgCount(): number;
|
|
18
|
+
export declare function requireAtLeast(minimumArgs: number): void;
|
|
19
|
+
export declare function maximumArgCount(): number | undefined;
|
|
20
|
+
export declare function requireNoMoreThan(maximumArgs: number): void;
|
|
21
|
+
export declare function allowOptionalArgs(): void;
|
|
22
|
+
export declare function allowOnlyNamedArgs(): void;
|
|
23
|
+
export declare function minimumUnnamedArgCount(): number;
|
|
24
|
+
/**
|
|
25
|
+
* Caution: this should not be set within plugins due to potential confusion and
|
|
26
|
+
* overlap!
|
|
27
|
+
*
|
|
28
|
+
* @param minUnnamed Number of optional args to allow
|
|
29
|
+
*/
|
|
30
|
+
export declare function requireAtLeastUnnamedArgs(minUnnamed: number): void;
|
|
31
|
+
export declare function maximumUnnamedCount(): number | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Caution: this should not be set within plugins due to potential confusion and
|
|
34
|
+
* overlap!
|
|
35
|
+
*
|
|
36
|
+
* @param maxUnnamed Number of optional args to allow
|
|
37
|
+
*/
|
|
38
|
+
export declare function requireNoMoreThanUnnamedArgs(maxUnnamed: number): void;
|
|
39
|
+
export declare function init(args: Plugin.ExpectedArguments<() => Plugin.Options>): void;
|
|
40
|
+
export declare function usageArgs(): string;
|
|
41
|
+
export declare function usage(usage: string): string;
|
|
42
|
+
export declare function run(): void;
|
|
43
|
+
export declare function get(positionalArgName: string): string | undefined;
|
|
44
|
+
export declare function namedArgs(): Record<string, string | undefined>;
|
|
45
|
+
export declare function unnamedArgs(): (string | undefined)[];
|
|
46
|
+
export declare function unnamedArg(i: number): string | undefined;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as Colors from '@qui-cli/colors/dist/Colors.js';
|
|
2
|
+
import * as Plugin from '@qui-cli/plugin';
|
|
3
|
+
import wrapAnsi from 'wrap-ansi';
|
|
4
|
+
export const name = 'positionals';
|
|
5
|
+
let min = 0;
|
|
6
|
+
let max = undefined;
|
|
7
|
+
const configSet = {};
|
|
8
|
+
let positionals = [];
|
|
9
|
+
/** @deprecated Do not use, included for backwards compatibility */
|
|
10
|
+
export function requirePositionalsIsDeprecatedAndShouldNotBeUsed(positionals, arg) {
|
|
11
|
+
if (arg !== undefined &&
|
|
12
|
+
positionals.min === undefined &&
|
|
13
|
+
positionals.max === undefined) {
|
|
14
|
+
if (typeof arg === 'number') {
|
|
15
|
+
requireAtLeast(arg);
|
|
16
|
+
requireNoMoreThan(arg);
|
|
17
|
+
}
|
|
18
|
+
else if (arg) {
|
|
19
|
+
requireAtLeast(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return min;
|
|
23
|
+
}
|
|
24
|
+
export function configure(config = {}) {
|
|
25
|
+
requireAtLeast(Plugin.hydrate(config.min, min));
|
|
26
|
+
const m = Plugin.hydrate(config.max, max);
|
|
27
|
+
if (m !== undefined) {
|
|
28
|
+
requireNoMoreThan(m);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function require(positionalConfigSet) {
|
|
32
|
+
const names = Object.keys(positionalConfigSet);
|
|
33
|
+
for (const name of names) {
|
|
34
|
+
if (name in configSet) {
|
|
35
|
+
throw new Error(`A positional argument named ${name} has already been defined and cannot be redefined.`);
|
|
36
|
+
}
|
|
37
|
+
configSet[name] = positionalConfigSet[name];
|
|
38
|
+
}
|
|
39
|
+
min += names.length;
|
|
40
|
+
if (max !== undefined) {
|
|
41
|
+
max += names.length;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function names() {
|
|
45
|
+
return Object.keys(configSet);
|
|
46
|
+
}
|
|
47
|
+
function namedCount() {
|
|
48
|
+
return names().length;
|
|
49
|
+
}
|
|
50
|
+
export function minimumArgCount() {
|
|
51
|
+
return min;
|
|
52
|
+
}
|
|
53
|
+
export function requireAtLeast(minimumArgs) {
|
|
54
|
+
if (minimumArgs < 0) {
|
|
55
|
+
throw new Error(`Cannot require fewer than 0 positional args.`);
|
|
56
|
+
}
|
|
57
|
+
if (max !== undefined && minimumArgs > max) {
|
|
58
|
+
throw new Error(`Cannot require min ${minimumArgs} positional args, maximum ${max} required positional args are configured.`);
|
|
59
|
+
}
|
|
60
|
+
min = minimumArgs;
|
|
61
|
+
}
|
|
62
|
+
export function maximumArgCount() {
|
|
63
|
+
return max;
|
|
64
|
+
}
|
|
65
|
+
export function requireNoMoreThan(maximumArgs) {
|
|
66
|
+
if (maximumArgs >= min && maximumArgs >= namedCount()) {
|
|
67
|
+
max = maximumArgs;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
throw new Error(`Cannot require max ${max} positional args, minimum ${namedCount()} required positional args are configured.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function allowOptionalArgs() {
|
|
74
|
+
max = undefined;
|
|
75
|
+
}
|
|
76
|
+
export function allowOnlyNamedArgs() {
|
|
77
|
+
requireNoMoreThan(min);
|
|
78
|
+
}
|
|
79
|
+
export function minimumUnnamedArgCount() {
|
|
80
|
+
return Math.max(0, min - namedCount());
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Caution: this should not be set within plugins due to potential confusion and
|
|
84
|
+
* overlap!
|
|
85
|
+
*
|
|
86
|
+
* @param minUnnamed Number of optional args to allow
|
|
87
|
+
*/
|
|
88
|
+
export function requireAtLeastUnnamedArgs(minUnnamed) {
|
|
89
|
+
if (max && min + minUnnamed > max) {
|
|
90
|
+
throw new Error(`Cannot require min ${minUnnamed} unnamed positional args, maximum ${max - namedCount()} unnamed positioal args are configure.`);
|
|
91
|
+
}
|
|
92
|
+
requireAtLeast(min + minUnnamed);
|
|
93
|
+
}
|
|
94
|
+
export function maximumUnnamedCount() {
|
|
95
|
+
return max ? max - namedCount() : undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Caution: this should not be set within plugins due to potential confusion and
|
|
99
|
+
* overlap!
|
|
100
|
+
*
|
|
101
|
+
* @param maxUnnamed Number of optional args to allow
|
|
102
|
+
*/
|
|
103
|
+
export function requireNoMoreThanUnnamedArgs(maxUnnamed) {
|
|
104
|
+
if (maxUnnamed < 0) {
|
|
105
|
+
throw new Error(`Cannot require a negative number of unnamed positional args.`);
|
|
106
|
+
}
|
|
107
|
+
if (maxUnnamed < min - namedCount()) {
|
|
108
|
+
throw new Error(`Cannot require max ${maxUnnamed} unnamed positional args, minimum ${min - namedCount()} unnamed positional args are configured.`);
|
|
109
|
+
}
|
|
110
|
+
requireNoMoreThan(namedCount() + maxUnnamed);
|
|
111
|
+
}
|
|
112
|
+
export function init(args) {
|
|
113
|
+
positionals = [...args.positionals];
|
|
114
|
+
}
|
|
115
|
+
function unnamed(args, count) {
|
|
116
|
+
for (let i = 0; i < count; i++) {
|
|
117
|
+
args.push(`arg${i}`);
|
|
118
|
+
}
|
|
119
|
+
return args;
|
|
120
|
+
}
|
|
121
|
+
function optional(args, required) {
|
|
122
|
+
if (required < args.length) {
|
|
123
|
+
args[required] = `[${args[required]}`;
|
|
124
|
+
args[args.length - 1] = `${args[args.length - 1]}]`;
|
|
125
|
+
}
|
|
126
|
+
return args;
|
|
127
|
+
}
|
|
128
|
+
function ellipsis(args, start, end) {
|
|
129
|
+
if (end - start > 3) {
|
|
130
|
+
args.splice(start + 1, end - start - 2, '...');
|
|
131
|
+
}
|
|
132
|
+
return args;
|
|
133
|
+
}
|
|
134
|
+
export function usageArgs() {
|
|
135
|
+
let args = names();
|
|
136
|
+
args = unnamed(args, (max || min) - namedCount());
|
|
137
|
+
if (!max) {
|
|
138
|
+
args.push('...');
|
|
139
|
+
}
|
|
140
|
+
args = optional(args, min);
|
|
141
|
+
if (max) {
|
|
142
|
+
args = ellipsis(args, min, args.length);
|
|
143
|
+
}
|
|
144
|
+
args = ellipsis(args, namedCount(), min);
|
|
145
|
+
return args.map((arg) => Colors.positionalArg(arg)).join(' ');
|
|
146
|
+
}
|
|
147
|
+
function wrap(text, indent) {
|
|
148
|
+
return wrapAnsi(text, process.stdout.columns - indent, { wordWrap: true })
|
|
149
|
+
.split('\n')
|
|
150
|
+
.map((line) => {
|
|
151
|
+
for (let i = 0; i < indent; i++) {
|
|
152
|
+
line = ' ' + line;
|
|
153
|
+
}
|
|
154
|
+
return line;
|
|
155
|
+
})
|
|
156
|
+
.join('\n');
|
|
157
|
+
}
|
|
158
|
+
export function usage(usage) {
|
|
159
|
+
const pre = usage.slice(0, usage.indexOf('\n') + 1);
|
|
160
|
+
const cmd = usage.slice(pre.length, usage.indexOf('\n\n'));
|
|
161
|
+
const post = usage.slice(pre.length + cmd.length);
|
|
162
|
+
return `${pre}${wrap(`${cmd
|
|
163
|
+
.split('\n')
|
|
164
|
+
.map((token) => token.trim())
|
|
165
|
+
.join(' ')} ${usageArgs()}`, 2)}${post}`;
|
|
166
|
+
}
|
|
167
|
+
export function run() {
|
|
168
|
+
const s = positionals.length !== 1 ? 's' : '';
|
|
169
|
+
if (positionals.length < min) {
|
|
170
|
+
throw new Error(`Expected at least ${min} positional arguments, received ${positionals.length} positional argument${s}.`);
|
|
171
|
+
}
|
|
172
|
+
if (max !== undefined && positionals.length > max) {
|
|
173
|
+
throw new Error(`Expected no more than ${max} positional arguments, received ${positionals.length} positional argument${s}.`);
|
|
174
|
+
}
|
|
175
|
+
names().forEach((name, i) => {
|
|
176
|
+
if (configSet[name].validate) {
|
|
177
|
+
const message = configSet[name].validate(positionals[i]);
|
|
178
|
+
if (!message || typeof message === 'string') {
|
|
179
|
+
throw new Error(`Positional argument '${name}' (arg${i}) is not valid${!message ? '.' : `: ${message}`}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export function get(positionalArgName) {
|
|
185
|
+
const i = names().indexOf(positionalArgName);
|
|
186
|
+
if (i < 0) {
|
|
187
|
+
throw new Error(`'${positionalArgName}' is not a defined positional argument.`);
|
|
188
|
+
}
|
|
189
|
+
return positionals[i];
|
|
190
|
+
}
|
|
191
|
+
export function namedArgs() {
|
|
192
|
+
const args = {};
|
|
193
|
+
for (let i = 0; i < namedCount(); i++) {
|
|
194
|
+
args[names()[i]] = positionals[i];
|
|
195
|
+
}
|
|
196
|
+
return args;
|
|
197
|
+
}
|
|
198
|
+
export function unnamedArgs() {
|
|
199
|
+
return positionals.slice(namedCount());
|
|
200
|
+
}
|
|
201
|
+
export function unnamedArg(i) {
|
|
202
|
+
return unnamedArgs()[i];
|
|
203
|
+
}
|
package/dist/Usage.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usage(): string;
|
package/dist/Usage.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as Colors from '@qui-cli/colors/dist/Colors.js';
|
|
2
|
+
import * as JackSpeak from './JackSpeak.js';
|
|
3
|
+
import * as Positionals from './Positionals.js';
|
|
4
|
+
function commandColor(usage) {
|
|
5
|
+
const pre = usage.slice(0, usage.indexOf('\n ') + 3);
|
|
6
|
+
const cmd = usage.slice(pre.length, usage.indexOf(' ', pre.length));
|
|
7
|
+
const terms = usage.slice(pre.length + cmd.length, usage.indexOf('-', pre.length + cmd.length) - 1);
|
|
8
|
+
const post = usage.slice(pre.length + cmd.length + terms.length);
|
|
9
|
+
return `${pre}${Colors.command(`${Colors.keyword(cmd)}${terms}`)}${post}`;
|
|
10
|
+
}
|
|
11
|
+
export function usage() {
|
|
12
|
+
return Positionals.usage(commandColor(JackSpeak.usage()));
|
|
13
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { register } from '@qui-cli/plugin';
|
|
2
|
+
import * as Help from './Help.js';
|
|
3
|
+
import * as JackSpeak from './JackSpeak.js';
|
|
4
|
+
import * as Positionals from './Positionals.js';
|
|
5
|
+
await register(Help);
|
|
6
|
+
await register(JackSpeak);
|
|
7
|
+
await register(Positionals);
|
|
8
|
+
export * as Core from './Core.js';
|
|
9
|
+
export { Help, JackSpeak, Positionals };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qui-cli/core",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Core features of @qui-cli/qui-cli",
|
|
5
|
+
"homepage": "https://github.com/battis/qui-cli/tree/main/packages/core#readme",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/battis/qui-cli.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"license": "GPL-3.0",
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "Seth Battis",
|
|
14
|
+
"url": "https://github.com/battis"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"jackspeak": "^4.1.1",
|
|
21
|
+
"wrap-ansi": "^9.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@tsconfig/node20": "^20.1.6",
|
|
25
|
+
"@types/node": "^24.1.0",
|
|
26
|
+
"commit-and-tag-version": "^12.5.2",
|
|
27
|
+
"del-cli": "^6.0.0",
|
|
28
|
+
"npm-run-all": "^4.1.5",
|
|
29
|
+
"typescript": "^5.9.2",
|
|
30
|
+
"@qui-cli/colors": "3.0.0",
|
|
31
|
+
"@qui-cli/plugin": "3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@qui-cli/colors": "2.x",
|
|
35
|
+
"@qui-cli/plugin": "^2.4"
|
|
36
|
+
},
|
|
37
|
+
"target": "node",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"clean": "del ./dist",
|
|
40
|
+
"build": "run-s build:*",
|
|
41
|
+
"build:clean": "run-s clean",
|
|
42
|
+
"build:compile": "tsc",
|
|
43
|
+
"release": "commit-and-tag-version"
|
|
44
|
+
}
|
|
45
|
+
}
|