@outfitter/cli 0.5.3 → 1.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/README.md +105 -2
- package/dist/actions.d.ts +5 -2
- package/dist/actions.js +2 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +8 -1
- package/dist/command.d.ts +3 -43
- package/dist/command.js +241 -13
- package/dist/envelope.d.ts +5 -0
- package/dist/envelope.js +160 -0
- package/dist/flags.d.ts +5 -189
- package/dist/flags.js +5 -1
- package/dist/hints.d.ts +34 -0
- package/dist/hints.js +26 -0
- package/dist/index.d.ts +3 -2
- package/dist/input.d.ts +3 -124
- package/dist/input.js +14 -359
- package/dist/internal/envelope-helpers.d.ts +4 -0
- package/dist/internal/envelope-helpers.js +24 -0
- package/dist/internal/envelope-types.d.ts +3 -0
- package/dist/internal/envelope-types.js +1 -0
- package/dist/internal/flag-builders.d.ts +3 -0
- package/dist/internal/flag-builders.js +155 -0
- package/dist/internal/flag-types.d.ts +3 -0
- package/dist/internal/flag-types.js +13 -0
- package/dist/internal/hint-action-graph.d.ts +5 -0
- package/dist/internal/hint-action-graph.js +11 -0
- package/dist/internal/hint-command-tree.d.ts +5 -0
- package/dist/internal/hint-command-tree.js +9 -0
- package/dist/internal/hint-error-recovery.d.ts +2 -0
- package/dist/internal/hint-error-recovery.js +7 -0
- package/dist/internal/hint-types.d.ts +4 -0
- package/dist/internal/hint-types.js +1 -0
- package/dist/internal/input-helpers.d.ts +18 -0
- package/dist/internal/input-helpers.js +11 -0
- package/dist/internal/input-normalization.d.ts +3 -0
- package/dist/internal/input-normalization.js +9 -0
- package/dist/internal/input-parsers.d.ts +3 -0
- package/dist/internal/input-parsers.js +19 -0
- package/dist/internal/input-security.d.ts +22 -0
- package/dist/internal/input-security.js +11 -0
- package/dist/internal/output-formatting.d.ts +3 -0
- package/dist/internal/output-formatting.js +21 -0
- package/dist/internal/presets.d.ts +3 -0
- package/dist/{shared/@outfitter/cli-pdb7znbq.js → internal/presets.js} +49 -223
- package/dist/internal/schema-commands.d.ts +3 -0
- package/dist/{shared/@outfitter/cli-5vtr4bdt.js → internal/schema-commands.js} +8 -99
- package/dist/internal/schema-formatting.d.ts +2 -0
- package/dist/internal/schema-formatting.js +7 -0
- package/dist/internal/schema-types.d.ts +2 -0
- package/dist/internal/schema-types.js +1 -0
- package/dist/output.d.ts +4 -3
- package/dist/output.js +13 -166
- package/dist/pagination.d.ts +1 -1
- package/dist/query.d.ts +84 -2
- package/dist/query.js +8 -45
- package/dist/schema-input.d.ts +80 -0
- package/dist/schema-input.js +15 -0
- package/dist/schema.d.ts +4 -1
- package/dist/schema.js +1 -1
- package/dist/shared/@outfitter/cli-10wxfc78.d.ts +45 -0
- package/dist/shared/@outfitter/cli-16wg5mka.d.ts +71 -0
- package/dist/shared/@outfitter/cli-1q5redaj.js +267 -0
- package/dist/shared/@outfitter/cli-2dfxs239.js +98 -0
- package/dist/shared/@outfitter/cli-30mt7c5w.d.ts +112 -0
- package/dist/shared/@outfitter/cli-3jta1h1h.js +134 -0
- package/dist/shared/@outfitter/cli-4h85mpth.js +76 -0
- package/dist/shared/@outfitter/cli-6shkwxdc.js +28 -0
- package/dist/shared/@outfitter/cli-89335n9a.js +16 -0
- package/dist/shared/@outfitter/cli-8999qjdd.js +3 -0
- package/dist/shared/@outfitter/cli-8cfxdady.js +60 -0
- package/dist/shared/@outfitter/cli-bcajqy33.d.ts +25 -0
- package/dist/shared/@outfitter/cli-c09332vm.d.ts +39 -0
- package/dist/shared/@outfitter/cli-cgha038c.d.ts +3 -0
- package/dist/shared/@outfitter/{cli-zahqsaby.js → cli-d40m2x1d.js} +19 -3
- package/dist/shared/@outfitter/cli-dg0cz7rw.js +127 -0
- package/dist/shared/@outfitter/cli-dv8kk4jw.d.ts +24 -0
- package/dist/shared/@outfitter/cli-g43887b7.js +20 -0
- package/dist/shared/@outfitter/cli-gqtkhgw4.js +52 -0
- package/dist/shared/@outfitter/cli-h4ejpmjs.d.ts +104 -0
- package/dist/shared/@outfitter/cli-htzez8v2.js +70 -0
- package/dist/shared/@outfitter/cli-hvg2m5gf.js +79 -0
- package/dist/shared/@outfitter/cli-n54zs151.d.ts +78 -0
- package/dist/shared/@outfitter/cli-nbpgw7z7.d.ts +15 -0
- package/dist/shared/@outfitter/cli-nkt399zf.d.ts +94 -0
- package/dist/shared/@outfitter/cli-pmd04gtv.d.ts +60 -0
- package/dist/shared/@outfitter/{cli-xy3gs50c.d.ts → cli-q6csxmeh.d.ts} +19 -12
- package/dist/shared/@outfitter/cli-qcskd96y.d.ts +11 -0
- package/dist/shared/@outfitter/cli-ry7btmy4.js +118 -0
- package/dist/shared/@outfitter/cli-sy99pjyj.js +32 -0
- package/dist/shared/@outfitter/cli-tm2fzngs.d.ts +23 -0
- package/dist/shared/@outfitter/cli-vvvhjwks.js +106 -0
- package/dist/shared/@outfitter/cli-wjv7g1aq.d.ts +16 -0
- package/dist/shared/@outfitter/{cli-98aa9104.d.ts → cli-x6qr7bnd.d.ts} +338 -16
- package/dist/shared/@outfitter/cli-xde45xcc.d.ts +53 -0
- package/dist/shared/@outfitter/cli-xw8ys1je.d.ts +123 -0
- package/dist/shared/@outfitter/cli-yfewnyc2.d.ts +43 -0
- package/dist/shared/@outfitter/cli-zkzj0q4q.js +99 -0
- package/dist/shared/@outfitter/cli-zv3ah6f0.js +3 -0
- package/dist/streaming.d.ts +47 -0
- package/dist/streaming.js +13 -0
- package/dist/truncation.d.ts +104 -0
- package/dist/truncation.js +111 -0
- package/dist/types.d.ts +2 -2
- package/dist/verbs.d.ts +1 -1
- package/package.json +55 -25
- package/dist/shared/@outfitter/cli-n1k0d23k.d.ts +0 -33
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/internal/input-helpers.ts
|
|
3
|
+
async function readStdin() {
|
|
4
|
+
const chunks = [];
|
|
5
|
+
for await (const chunk of process.stdin) {
|
|
6
|
+
if (typeof chunk === "string") {
|
|
7
|
+
chunks.push(chunk);
|
|
8
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
9
|
+
chunks.push(chunk.toString("utf-8"));
|
|
10
|
+
} else if (chunk instanceof Uint8Array) {
|
|
11
|
+
chunks.push(Buffer.from(chunk).toString("utf-8"));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return chunks.join("");
|
|
15
|
+
}
|
|
16
|
+
function splitIds(input) {
|
|
17
|
+
return input.split(",").flatMap((part) => part.trim().split(/\s+/)).map((id) => id.trim()).filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
async function isDirectory(dirPath) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await Bun.$`test -d ${dirPath}`.quiet();
|
|
22
|
+
return result.exitCode === 0;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { readStdin, splitIds, isDirectory };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
unwrapZodField
|
|
4
|
+
} from "./cli-3jta1h1h.js";
|
|
5
|
+
|
|
6
|
+
// packages/cli/src/hints.ts
|
|
7
|
+
function schemaHintParams(schema) {
|
|
8
|
+
const params = {};
|
|
9
|
+
for (const [fieldName, field] of Object.entries(schema.shape)) {
|
|
10
|
+
const info = unwrapZodField(field);
|
|
11
|
+
params[fieldName] = info.description ?? info.baseType;
|
|
12
|
+
}
|
|
13
|
+
return params;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { schemaHintParams };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { composePresets, createPreset, createSchemaPreset, isSchemaPreset } from "../../internal/flag-types.js";
|
|
2
|
+
export { booleanFlagPreset, enumFlagPreset, numberFlagPreset, stringListFlagPreset } from "../../internal/flag-builders.js";
|
|
3
|
+
export { colorPreset, cwdPreset, dryRunPreset, executionPreset, forcePreset, interactionPreset, paginationPreset, projectionPreset, strictPreset, timeWindowPreset, verbosePreset } from "../../internal/presets.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/internal/flag-types.ts
|
|
3
|
+
var PRESET_IDS = Symbol("presetIds");
|
|
4
|
+
function createPreset(config) {
|
|
5
|
+
const preset = {
|
|
6
|
+
id: config.id,
|
|
7
|
+
options: config.options,
|
|
8
|
+
resolve: config.resolve
|
|
9
|
+
};
|
|
10
|
+
preset[PRESET_IDS] = [config.id];
|
|
11
|
+
return preset;
|
|
12
|
+
}
|
|
13
|
+
function createSchemaPreset(config) {
|
|
14
|
+
return {
|
|
15
|
+
id: config.id,
|
|
16
|
+
schema: config.schema,
|
|
17
|
+
resolve: config.resolve
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function isSchemaPreset(preset) {
|
|
21
|
+
return "schema" in preset && !("options" in preset);
|
|
22
|
+
}
|
|
23
|
+
function getPresetIds(preset) {
|
|
24
|
+
const ids = preset[PRESET_IDS];
|
|
25
|
+
return ids && ids.length > 0 ? ids : [preset.id];
|
|
26
|
+
}
|
|
27
|
+
function composePresets(...presets) {
|
|
28
|
+
const seen = new Set;
|
|
29
|
+
const mergedIds = [];
|
|
30
|
+
const mergedOptions = [];
|
|
31
|
+
const resolvers = [];
|
|
32
|
+
for (const preset of presets) {
|
|
33
|
+
const presetIds = getPresetIds(preset);
|
|
34
|
+
if (presetIds.every((id) => seen.has(id)))
|
|
35
|
+
continue;
|
|
36
|
+
for (const id of presetIds) {
|
|
37
|
+
if (seen.has(id))
|
|
38
|
+
continue;
|
|
39
|
+
seen.add(id);
|
|
40
|
+
mergedIds.push(id);
|
|
41
|
+
}
|
|
42
|
+
mergedOptions.push(...preset.options);
|
|
43
|
+
resolvers.push(preset.resolve);
|
|
44
|
+
}
|
|
45
|
+
const composed = {
|
|
46
|
+
id: mergedIds.join("+"),
|
|
47
|
+
options: mergedOptions,
|
|
48
|
+
resolve: (flags) => {
|
|
49
|
+
let result = {};
|
|
50
|
+
for (const resolver of resolvers) {
|
|
51
|
+
result = { ...result, ...resolver(flags) };
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
composed[PRESET_IDS] = mergedIds;
|
|
57
|
+
return composed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { createPreset, createSchemaPreset, isSchemaPreset, composePresets };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CollectIdsOptions } from "./cli-x6qr7bnd.js";
|
|
2
|
+
/**
|
|
3
|
+
* Collect IDs from various input formats.
|
|
4
|
+
*
|
|
5
|
+
* Handles space-separated, comma-separated, repeated flags, @file, and stdin.
|
|
6
|
+
*
|
|
7
|
+
* @param input - Raw input from CLI arguments
|
|
8
|
+
* @param options - Collection options
|
|
9
|
+
* @returns Array of collected IDs
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // All these produce the same result:
|
|
14
|
+
* // wm show id1 id2 id3
|
|
15
|
+
* // wm show id1,id2,id3
|
|
16
|
+
* // wm show --ids id1 --ids id2
|
|
17
|
+
* // wm show @ids.txt
|
|
18
|
+
* const ids = await collectIds(args.ids, {
|
|
19
|
+
* allowFile: true,
|
|
20
|
+
* allowStdin: true,
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare function collectIds(input: string | readonly string[], options?: CollectIdsOptions): Promise<string[]>;
|
|
25
|
+
export { collectIds };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RunHandlerOptions } from "./cli-30mt7c5w.js";
|
|
2
|
+
/**
|
|
3
|
+
* Bridge the full CommandBuilder lifecycle: context factory → handler
|
|
4
|
+
* invocation → Result unwrap → envelope construction → output
|
|
5
|
+
* formatting → exit code mapping.
|
|
6
|
+
*
|
|
7
|
+
* On success:
|
|
8
|
+
* - Builds a `{ ok: true, command, result, hints? }` envelope
|
|
9
|
+
* - Outputs to stdout (JSON or human-readable)
|
|
10
|
+
* - Returns normally (exit code 0)
|
|
11
|
+
*
|
|
12
|
+
* On error:
|
|
13
|
+
* - Builds a `{ ok: false, command, error: { category, message }, hints? }` envelope
|
|
14
|
+
* - Outputs to stderr (JSON or human-readable)
|
|
15
|
+
* - Exits with mapped exit code from error taxonomy
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* command("deploy")
|
|
20
|
+
* .input(z.object({ env: z.string() }))
|
|
21
|
+
* .action(async ({ input }) => {
|
|
22
|
+
* await runHandler({
|
|
23
|
+
* command: "deploy",
|
|
24
|
+
* handler: async (input) => deployService(input),
|
|
25
|
+
* input,
|
|
26
|
+
* format: outputMode,
|
|
27
|
+
* hints: (result, input) => [
|
|
28
|
+
* { description: "Check status", command: `deploy status --env ${input.env}` },
|
|
29
|
+
* ],
|
|
30
|
+
* });
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function runHandler<
|
|
35
|
+
TInput = unknown,
|
|
36
|
+
TOutput = unknown,
|
|
37
|
+
TContext = unknown
|
|
38
|
+
>(options: RunHandlerOptions<TInput, TOutput, TContext>): Promise<void>;
|
|
39
|
+
export { runHandler };
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
buildCommandTree
|
|
4
|
+
} from "./cli-htzez8v2.js";
|
|
5
|
+
import {
|
|
6
|
+
output
|
|
7
|
+
} from "./cli-4h85mpth.js";
|
|
8
|
+
|
|
2
9
|
// packages/cli/src/cli.ts
|
|
3
10
|
import { Command } from "commander";
|
|
4
11
|
function isCommanderHelp(error) {
|
|
@@ -13,7 +20,7 @@ function createCLI(config) {
|
|
|
13
20
|
return;
|
|
14
21
|
}
|
|
15
22
|
if (bridgedJsonEnvPrevious === undefined) {
|
|
16
|
-
process.env["OUTFITTER_JSON"]
|
|
23
|
+
delete process.env["OUTFITTER_JSON"];
|
|
17
24
|
} else {
|
|
18
25
|
process.env["OUTFITTER_JSON"] = bridgedJsonEnvPrevious;
|
|
19
26
|
}
|
|
@@ -24,10 +31,10 @@ function createCLI(config) {
|
|
|
24
31
|
if (config.description) {
|
|
25
32
|
program.description(config.description);
|
|
26
33
|
}
|
|
27
|
-
program.option("--json", "Output as JSON"
|
|
34
|
+
program.option("--json", "Output as JSON");
|
|
28
35
|
program.hook("preAction", (thisCommand) => {
|
|
29
36
|
const allOpts = thisCommand.optsWithGlobals();
|
|
30
|
-
if (allOpts["json"] && !bridgedJsonEnvActive) {
|
|
37
|
+
if (allOpts["json"] === true && !bridgedJsonEnvActive) {
|
|
31
38
|
bridgedJsonEnvPrevious = process.env["OUTFITTER_JSON"];
|
|
32
39
|
process.env["OUTFITTER_JSON"] = "1";
|
|
33
40
|
bridgedJsonEnvActive = true;
|
|
@@ -38,6 +45,15 @@ function createCLI(config) {
|
|
|
38
45
|
});
|
|
39
46
|
const exit = config.onExit ?? ((code) => void process.exit(code));
|
|
40
47
|
program.exitOverride();
|
|
48
|
+
program.action(async () => {
|
|
49
|
+
const isJsonMode = program.opts()["json"] === true || process.env["OUTFITTER_JSON"] === "1" || process.env["OUTFITTER_JSONL"] === "1" || !process.stdout.isTTY;
|
|
50
|
+
if (isJsonMode) {
|
|
51
|
+
const tree = buildCommandTree(program);
|
|
52
|
+
await output(tree, "json");
|
|
53
|
+
} else {
|
|
54
|
+
program.outputHelp();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
41
57
|
const parse = async (argv) => {
|
|
42
58
|
try {
|
|
43
59
|
await program.parseAsync(argv ?? process.argv);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/internal/output-formatting.ts
|
|
3
|
+
import {
|
|
4
|
+
safeStringify as contractsSafeStringify,
|
|
5
|
+
exitCodeMap
|
|
6
|
+
} from "@outfitter/contracts";
|
|
7
|
+
var DEFAULT_EXIT_CODE = 1;
|
|
8
|
+
function detectMode(format) {
|
|
9
|
+
if (format) {
|
|
10
|
+
return format;
|
|
11
|
+
}
|
|
12
|
+
const envJsonl = process.env["OUTFITTER_JSONL"];
|
|
13
|
+
const envJson = process.env["OUTFITTER_JSON"];
|
|
14
|
+
if (envJsonl === "1")
|
|
15
|
+
return "jsonl";
|
|
16
|
+
if (envJson === "1")
|
|
17
|
+
return "json";
|
|
18
|
+
if (envJsonl === "0" || envJson === "0")
|
|
19
|
+
return "human";
|
|
20
|
+
return "human";
|
|
21
|
+
}
|
|
22
|
+
function cliStringify(value, pretty) {
|
|
23
|
+
const wrappedValue = value === undefined ? null : value;
|
|
24
|
+
return contractsSafeStringify(wrappedValue, pretty ? 2 : undefined);
|
|
25
|
+
}
|
|
26
|
+
function formatHuman(data) {
|
|
27
|
+
if (data === null || data === undefined) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
if (typeof data === "string") {
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
if (typeof data === "number" || typeof data === "boolean") {
|
|
34
|
+
return String(data);
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(data)) {
|
|
37
|
+
return data.map((item) => formatHuman(item)).join(`
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
if (typeof data === "object") {
|
|
41
|
+
const lines = [];
|
|
42
|
+
for (const [key, value] of Object.entries(data)) {
|
|
43
|
+
lines.push(`${key}: ${formatHuman(value)}`);
|
|
44
|
+
}
|
|
45
|
+
return lines.join(`
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
return String(data);
|
|
49
|
+
}
|
|
50
|
+
function getErrorProperties(error) {
|
|
51
|
+
const errorObj = error;
|
|
52
|
+
return {
|
|
53
|
+
_tag: errorObj._tag,
|
|
54
|
+
category: errorObj.category,
|
|
55
|
+
context: errorObj.context
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function isValidCategory(category) {
|
|
59
|
+
return category in exitCodeMap;
|
|
60
|
+
}
|
|
61
|
+
function getExitCode(error) {
|
|
62
|
+
const { category } = getErrorProperties(error);
|
|
63
|
+
if (category !== undefined && isValidCategory(category)) {
|
|
64
|
+
return exitCodeMap[category];
|
|
65
|
+
}
|
|
66
|
+
return DEFAULT_EXIT_CODE;
|
|
67
|
+
}
|
|
68
|
+
function serializeErrorToJson(error) {
|
|
69
|
+
const { _tag, category, context } = getErrorProperties(error);
|
|
70
|
+
const result = {
|
|
71
|
+
message: error.message
|
|
72
|
+
};
|
|
73
|
+
if (_tag !== undefined) {
|
|
74
|
+
result._tag = _tag;
|
|
75
|
+
}
|
|
76
|
+
if (category !== undefined) {
|
|
77
|
+
result.category = category;
|
|
78
|
+
}
|
|
79
|
+
if (context !== undefined) {
|
|
80
|
+
result.context = context;
|
|
81
|
+
}
|
|
82
|
+
return JSON.stringify(result);
|
|
83
|
+
}
|
|
84
|
+
function formatErrorHuman(error) {
|
|
85
|
+
const { _tag } = getErrorProperties(error);
|
|
86
|
+
if (_tag) {
|
|
87
|
+
return `${_tag}: ${error.message}`;
|
|
88
|
+
}
|
|
89
|
+
return error.message;
|
|
90
|
+
}
|
|
91
|
+
function applyOutputTruncation(data, truncation) {
|
|
92
|
+
if (!truncation || !Array.isArray(data)) {
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
const rawOffset = truncation.offset;
|
|
96
|
+
const offset = typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.max(0, rawOffset) : 0;
|
|
97
|
+
const rawLimit = truncation.limit;
|
|
98
|
+
if (typeof rawLimit !== "number" || !Number.isFinite(rawLimit)) {
|
|
99
|
+
return data;
|
|
100
|
+
}
|
|
101
|
+
const limit = Math.max(0, rawLimit);
|
|
102
|
+
return data.slice(offset, offset + limit);
|
|
103
|
+
}
|
|
104
|
+
function writeWithBackpressure(stream, data) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
const canContinue = stream.write(data, (error) => {
|
|
107
|
+
if (error)
|
|
108
|
+
reject(error);
|
|
109
|
+
});
|
|
110
|
+
if (canContinue) {
|
|
111
|
+
resolve();
|
|
112
|
+
} else {
|
|
113
|
+
const onDrain = () => {
|
|
114
|
+
stream.removeListener("error", onError);
|
|
115
|
+
resolve();
|
|
116
|
+
};
|
|
117
|
+
const onError = (err) => {
|
|
118
|
+
stream.removeListener("drain", onDrain);
|
|
119
|
+
reject(err);
|
|
120
|
+
};
|
|
121
|
+
stream.once("drain", onDrain);
|
|
122
|
+
stream.once("error", onError);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { detectMode, cliStringify, formatHuman, getExitCode, serializeErrorToJson, formatErrorHuman, applyOutputTruncation, writeWithBackpressure };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BooleanFlagPresetConfig, EnumFlagPresetConfig, FlagPreset, NumberFlagPresetConfig, StringListFlagPresetConfig } from "./cli-x6qr7bnd.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generic boolean custom-flag builder.
|
|
4
|
+
*
|
|
5
|
+
* Supports normal sources and negated sources so `--no-foo` patterns can
|
|
6
|
+
* resolve consistently across Commander flag-shape differences.
|
|
7
|
+
*/
|
|
8
|
+
declare function booleanFlagPreset<TKey extends string>(config: BooleanFlagPresetConfig<TKey>): FlagPreset<{ [K in TKey] : boolean }>;
|
|
9
|
+
/**
|
|
10
|
+
* Generic enum custom-flag builder.
|
|
11
|
+
*/
|
|
12
|
+
declare function enumFlagPreset<
|
|
13
|
+
TKey extends string,
|
|
14
|
+
TValue extends string
|
|
15
|
+
>(config: EnumFlagPresetConfig<TKey, TValue>): FlagPreset<{ [K in TKey] : TValue }>;
|
|
16
|
+
/**
|
|
17
|
+
* Generic number custom-flag builder.
|
|
18
|
+
*/
|
|
19
|
+
declare function numberFlagPreset<TKey extends string>(config: NumberFlagPresetConfig<TKey>): FlagPreset<{ [K in TKey] : number }>;
|
|
20
|
+
/**
|
|
21
|
+
* Generic string-list custom-flag builder.
|
|
22
|
+
*/
|
|
23
|
+
declare function stringListFlagPreset<TKey extends string>(config: StringListFlagPresetConfig<TKey>): FlagPreset<{ [K in TKey] : string[] | undefined }>;
|
|
24
|
+
export { booleanFlagPreset, enumFlagPreset, numberFlagPreset, stringListFlagPreset };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
cliStringify
|
|
4
|
+
} from "./cli-dg0cz7rw.js";
|
|
5
|
+
|
|
6
|
+
// packages/cli/src/streaming.ts
|
|
7
|
+
function writeNdjsonLine(data) {
|
|
8
|
+
process.stdout.write(`${cliStringify(data)}
|
|
9
|
+
`);
|
|
10
|
+
}
|
|
11
|
+
function writeStreamEnvelope(envelope) {
|
|
12
|
+
writeNdjsonLine(envelope);
|
|
13
|
+
}
|
|
14
|
+
function createNdjsonProgress(_commandName) {
|
|
15
|
+
return (event) => {
|
|
16
|
+
writeNdjsonLine(event);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { writeNdjsonLine, writeStreamEnvelope, createNdjsonProgress };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
isSecurePath
|
|
4
|
+
} from "./cli-sy99pjyj.js";
|
|
5
|
+
import {
|
|
6
|
+
readStdin,
|
|
7
|
+
splitIds
|
|
8
|
+
} from "./cli-6shkwxdc.js";
|
|
9
|
+
|
|
10
|
+
// packages/cli/src/internal/input-normalization.ts
|
|
11
|
+
async function collectIds(input, options) {
|
|
12
|
+
const { allowFile = true, allowStdin = true } = options ?? {};
|
|
13
|
+
const ids = [];
|
|
14
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
15
|
+
for (const item of inputs) {
|
|
16
|
+
if (!item)
|
|
17
|
+
continue;
|
|
18
|
+
if (item.startsWith("@")) {
|
|
19
|
+
const filePath = item.slice(1);
|
|
20
|
+
if (filePath === "-") {
|
|
21
|
+
if (!allowStdin) {
|
|
22
|
+
throw new Error("Reading from stdin is not allowed");
|
|
23
|
+
}
|
|
24
|
+
const stdinContent = await readStdin();
|
|
25
|
+
const stdinIds = stdinContent.split(`
|
|
26
|
+
`).map((line) => line.trim()).filter(Boolean);
|
|
27
|
+
ids.push(...stdinIds);
|
|
28
|
+
} else {
|
|
29
|
+
if (!allowFile) {
|
|
30
|
+
throw new Error("File references are not allowed");
|
|
31
|
+
}
|
|
32
|
+
if (!isSecurePath(filePath, true)) {
|
|
33
|
+
throw new Error(`Security error: path traversal not allowed: ${filePath}`);
|
|
34
|
+
}
|
|
35
|
+
const file = Bun.file(filePath);
|
|
36
|
+
const exists = await file.exists();
|
|
37
|
+
if (!exists) {
|
|
38
|
+
throw new Error(`File not found: ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
const content = await file.text();
|
|
41
|
+
const fileIds = content.split(`
|
|
42
|
+
`).map((line) => line.trim()).filter(Boolean);
|
|
43
|
+
ids.push(...fileIds);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
ids.push(...splitIds(item));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return [...new Set(ids)];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { collectIds };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ExpandFileOptions, FilterExpression, KeyValuePair, ParseGlobOptions, Range, SortCriteria } from "./cli-x6qr7bnd.js";
|
|
2
|
+
import { ValidationError } from "@outfitter/contracts";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
/**
|
|
5
|
+
* Expand @file references to file contents.
|
|
6
|
+
*
|
|
7
|
+
* If the input starts with @, reads the file and returns its contents.
|
|
8
|
+
* Otherwise, returns the input unchanged.
|
|
9
|
+
*
|
|
10
|
+
* @param input - Raw input that may be a @file reference
|
|
11
|
+
* @param options - Expansion options
|
|
12
|
+
* @returns File contents or original input
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // wm create @template.md
|
|
17
|
+
* const content = await expandFileArg(args.content);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function expandFileArg(input: string, options?: ExpandFileOptions): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Parse and expand glob patterns.
|
|
23
|
+
*
|
|
24
|
+
* Uses Bun.Glob with workspace constraints.
|
|
25
|
+
*
|
|
26
|
+
* @param pattern - Glob pattern to expand
|
|
27
|
+
* @param options - Glob options
|
|
28
|
+
* @returns Array of matched file paths
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // wm index "src/**\/*.ts"
|
|
33
|
+
* const files = await parseGlob(args.pattern, {
|
|
34
|
+
* cwd: workspaceRoot,
|
|
35
|
+
* ignore: ["node_modules/**"],
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function parseGlob(pattern: string, options?: ParseGlobOptions): Promise<string[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Parse key=value pairs from CLI input.
|
|
42
|
+
*
|
|
43
|
+
* @param input - Raw input containing key=value pairs
|
|
44
|
+
* @returns Array of parsed key-value pairs
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* // --set key=value --set key2=value2
|
|
49
|
+
* // --set key=value,key2=value2
|
|
50
|
+
* const pairs = parseKeyValue(args.set);
|
|
51
|
+
* // => [{ key: "key", value: "value" }, { key: "key2", value: "value2" }]
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function parseKeyValue(input: string | readonly string[]): Result<KeyValuePair[], InstanceType<typeof ValidationError>>;
|
|
55
|
+
/**
|
|
56
|
+
* Parse range inputs (numeric or date).
|
|
57
|
+
*
|
|
58
|
+
* @param input - Range string (e.g., "1-10" or "2024-01-01..2024-12-31")
|
|
59
|
+
* @param type - Type of range to parse
|
|
60
|
+
* @returns Parsed range
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* parseRange("1-10", "number");
|
|
65
|
+
* // => Result<{ type: "number", min: 1, max: 10 }, ValidationError>
|
|
66
|
+
*
|
|
67
|
+
* parseRange("2024-01-01..2024-12-31", "date");
|
|
68
|
+
* // => Result<{ type: "date", start: Date, end: Date }, ValidationError>
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare function parseRange(input: string, type: "number" | "date"): Result<Range, InstanceType<typeof ValidationError>>;
|
|
72
|
+
/**
|
|
73
|
+
* Parse filter expressions from CLI input.
|
|
74
|
+
*
|
|
75
|
+
* @param input - Filter string (e.g., "status:active,priority:high")
|
|
76
|
+
* @returns Array of parsed filter expressions
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* parseFilter("status:active,priority:high");
|
|
81
|
+
* // => Result<[
|
|
82
|
+
* // { field: "status", value: "active" },
|
|
83
|
+
* // { field: "priority", value: "high" }
|
|
84
|
+
* // ], ValidationError>
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function parseFilter(input: string): Result<FilterExpression[], InstanceType<typeof ValidationError>>;
|
|
88
|
+
/**
|
|
89
|
+
* Parse sort specification from CLI input.
|
|
90
|
+
*
|
|
91
|
+
* @param input - Sort string (e.g., "modified:desc,title:asc")
|
|
92
|
+
* @returns Array of parsed sort criteria
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* parseSortSpec("modified:desc,title:asc");
|
|
97
|
+
* // => Result<[
|
|
98
|
+
* // { field: "modified", direction: "desc" },
|
|
99
|
+
* // { field: "title", direction: "asc" }
|
|
100
|
+
* // ], ValidationError>
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function parseSortSpec(input: string): Result<SortCriteria[], InstanceType<typeof ValidationError>>;
|
|
104
|
+
export { expandFileArg, parseGlob, parseKeyValue, parseRange, parseFilter, parseSortSpec };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/internal/hint-command-tree.ts
|
|
3
|
+
function isHiddenCommand(cmd) {
|
|
4
|
+
return cmd.hidden === true;
|
|
5
|
+
}
|
|
6
|
+
function buildCommandNode(cmd) {
|
|
7
|
+
const options = cmd.options.filter((opt) => !opt.hidden).map((opt) => {
|
|
8
|
+
const entry = {
|
|
9
|
+
flags: opt.flags,
|
|
10
|
+
description: opt.description
|
|
11
|
+
};
|
|
12
|
+
if (opt.defaultValue !== undefined) {
|
|
13
|
+
return { ...entry, defaultValue: opt.defaultValue };
|
|
14
|
+
}
|
|
15
|
+
if (opt.required) {
|
|
16
|
+
return { ...entry, required: true };
|
|
17
|
+
}
|
|
18
|
+
return entry;
|
|
19
|
+
});
|
|
20
|
+
const subcommands = cmd.commands.filter((sub) => !isHiddenCommand(sub)).map((sub) => buildCommandNode(sub));
|
|
21
|
+
const metadata = cmd.__metadata;
|
|
22
|
+
const node = {
|
|
23
|
+
name: cmd.name()
|
|
24
|
+
};
|
|
25
|
+
const description = cmd.description();
|
|
26
|
+
if (description) {
|
|
27
|
+
return {
|
|
28
|
+
...node,
|
|
29
|
+
description,
|
|
30
|
+
...metadata ? { metadata } : {},
|
|
31
|
+
...options.length > 0 ? { options } : {},
|
|
32
|
+
...subcommands.length > 0 ? { subcommands } : {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
...node,
|
|
37
|
+
...metadata ? { metadata } : {},
|
|
38
|
+
...options.length > 0 ? { options } : {},
|
|
39
|
+
...subcommands.length > 0 ? { subcommands } : {}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function buildCommandTree(program) {
|
|
43
|
+
const version = program.version() ?? "0.0.0";
|
|
44
|
+
const commands = program.commands.filter((cmd) => !isHiddenCommand(cmd)).map((cmd) => buildCommandNode(cmd));
|
|
45
|
+
const description = program.description();
|
|
46
|
+
const tree = {
|
|
47
|
+
name: program.name(),
|
|
48
|
+
version,
|
|
49
|
+
...description ? { description } : {},
|
|
50
|
+
commands
|
|
51
|
+
};
|
|
52
|
+
return tree;
|
|
53
|
+
}
|
|
54
|
+
function commandTreeHints(tree) {
|
|
55
|
+
const hints = [];
|
|
56
|
+
function walkCommands(nodes, pathPrefix) {
|
|
57
|
+
for (const node of nodes) {
|
|
58
|
+
const fullCommand = `${pathPrefix} ${node.name}`.trim();
|
|
59
|
+
const description = node.description ?? `Run ${node.name} command`;
|
|
60
|
+
hints.push({ description, command: fullCommand });
|
|
61
|
+
if (node.subcommands && node.subcommands.length > 0) {
|
|
62
|
+
walkCommands(node.subcommands, fullCommand);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
walkCommands(tree.commands, tree.name);
|
|
67
|
+
return hints;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { buildCommandTree, commandTreeHints };
|