mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
fix(config): tighten patch command semantics
This commit is contained in:
@@ -108,8 +108,8 @@ cat > discord.patch.json5 <<'JSON5'
|
||||
},
|
||||
}
|
||||
JSON5
|
||||
openclaw config apply --file ./discord.patch.json5 --dry-run
|
||||
openclaw config apply --file ./discord.patch.json5
|
||||
openclaw config patch --file ./discord.patch.json5 --dry-run
|
||||
openclaw config patch --file ./discord.patch.json5
|
||||
openclaw gateway
|
||||
```
|
||||
|
||||
@@ -150,7 +150,7 @@ openclaw gateway
|
||||
DISCORD_BOT_TOKEN=...
|
||||
```
|
||||
|
||||
For scripted or remote setup, write the same JSON5 block with `openclaw config apply --file ./discord.patch.json5 --dry-run` and then rerun without `--dry-run`. Plaintext `token` values are supported. SecretRef values are also supported for `channels.discord.token` across env/file/exec providers. See [Secrets Management](/gateway/secrets).
|
||||
For scripted or remote setup, write the same JSON5 block with `openclaw config patch --file ./discord.patch.json5 --dry-run` and then rerun without `--dry-run`. Plaintext `token` values are supported. SecretRef values are also supported for `channels.discord.token` across env/file/exec providers. See [Secrets Management](/gateway/secrets).
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -53,8 +53,8 @@ cat > slack.socket.patch.json5 <<'JSON5'
|
||||
},
|
||||
}
|
||||
JSON5
|
||||
openclaw config apply --file ./slack.socket.patch.json5 --dry-run
|
||||
openclaw config apply --file ./slack.socket.patch.json5
|
||||
openclaw config patch --file ./slack.socket.patch.json5 --dry-run
|
||||
openclaw config patch --file ./slack.socket.patch.json5
|
||||
```
|
||||
|
||||
Env fallback (default account only):
|
||||
@@ -109,8 +109,8 @@ cat > slack.http.patch.json5 <<'JSON5'
|
||||
},
|
||||
}
|
||||
JSON5
|
||||
openclaw config apply --file ./slack.http.patch.json5 --dry-run
|
||||
openclaw config apply --file ./slack.http.patch.json5
|
||||
openclaw config patch --file ./slack.http.patch.json5 --dry-run
|
||||
openclaw config patch --file ./slack.http.patch.json5
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw config` (get/set/apply/unset/file/schema/validate)"
|
||||
summary: "CLI reference for `openclaw config` (get/set/patch/unset/file/schema/validate)"
|
||||
read_when:
|
||||
- You want to read or edit config non-interactively
|
||||
title: "Config"
|
||||
sidebarTitle: "Config"
|
||||
---
|
||||
|
||||
Config helpers for non-interactive edits in `openclaw.json`: get/set/apply/unset/file/schema/validate values by path and print the active config file. Run without a subcommand to open the configure wizard (same as `openclaw configure`).
|
||||
Config helpers for non-interactive edits in `openclaw.json`: get/set/patch/unset/file/schema/validate values by path and print the active config file. Run without a subcommand to open the configure wizard (same as `openclaw configure`).
|
||||
|
||||
## Root options
|
||||
|
||||
@@ -31,7 +31,7 @@ openclaw config set agents.list[0].tools.exec.node "node-id-or-name"
|
||||
openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json --merge
|
||||
openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN
|
||||
openclaw config set secrets.providers.vaultfile --provider-source file --provider-path /etc/openclaw/secrets.json --provider-mode json
|
||||
openclaw config apply --file ./openclaw.patch.json5 --dry-run
|
||||
openclaw config patch --file ./openclaw.patch.json5 --dry-run
|
||||
openclaw config unset plugins.entries.brave.config.webSearch.apiKey
|
||||
openclaw config set channels.discord.token --ref-provider default --ref-source env --ref-id DISCORD_BOT_TOKEN --dry-run
|
||||
openclaw config validate
|
||||
@@ -166,20 +166,20 @@ SecretRef assignments are rejected on unsupported runtime-mutable surfaces (for
|
||||
|
||||
Batch parsing always uses the batch payload (`--batch-json`/`--batch-file`) as the source of truth. `--strict-json` / `--json` do not change batch parsing behavior.
|
||||
|
||||
## `config apply`
|
||||
## `config patch`
|
||||
|
||||
Use `config apply` when you want to paste or pipe a config-shaped patch instead of running many path-based `config set` commands. The input is a JSON5 object. Objects merge recursively, arrays and scalar values replace the target value, and `null` deletes the target path.
|
||||
Use `config patch` when you want to paste or pipe a config-shaped patch instead of running many path-based `config set` commands. The input is a JSON5 object. Objects merge recursively, arrays and scalar values replace the target value, and `null` deletes the target path.
|
||||
|
||||
```bash
|
||||
openclaw config apply --file ./openclaw.patch.json5 --dry-run
|
||||
openclaw config apply --file ./openclaw.patch.json5
|
||||
openclaw config patch --file ./openclaw.patch.json5 --dry-run
|
||||
openclaw config patch --file ./openclaw.patch.json5
|
||||
```
|
||||
|
||||
You can also pipe a patch over stdin, which is useful for remote setup scripts:
|
||||
|
||||
```bash
|
||||
ssh openclaw-host 'openclaw config apply --stdin --dry-run' < ./openclaw.patch.json5
|
||||
ssh openclaw-host 'openclaw config apply --stdin' < ./openclaw.patch.json5
|
||||
ssh openclaw-host 'openclaw config patch --stdin --dry-run' < ./openclaw.patch.json5
|
||||
ssh openclaw-host 'openclaw config patch --stdin' < ./openclaw.patch.json5
|
||||
```
|
||||
|
||||
Example patch:
|
||||
@@ -217,7 +217,7 @@ Example patch:
|
||||
Use `--replace-path <path>` when one object or array must become exactly the provided value instead of being recursively patched:
|
||||
|
||||
```bash
|
||||
openclaw config apply --file ./discord.patch.json5 --replace-path 'channels.discord.guilds["123"].channels'
|
||||
openclaw config patch --file ./discord.patch.json5 --replace-path 'channels.discord.guilds["123"].channels'
|
||||
```
|
||||
|
||||
`--dry-run` runs schema and SecretRef resolvability checks without writing. Exec-backed SecretRefs are skipped by default during dry-run; add `--allow-exec` when you intentionally want dry-run to execute provider commands.
|
||||
|
||||
@@ -115,7 +115,7 @@ Approve devices with `openclaw devices list` and `openclaw devices approve <requ
|
||||
|
||||
## Remote channel setup
|
||||
|
||||
For remote hosts, prefer one `config apply` patch over many SSH calls to `config set`. Keep real tokens in the VM environment or `~/.openclaw/.env`, and put only SecretRefs in `openclaw.json`.
|
||||
For remote hosts, prefer one `config patch` call over many SSH calls to `config set`. Keep real tokens in the VM environment or `~/.openclaw/.env`, and put only SecretRefs in `openclaw.json`.
|
||||
|
||||
On the VM, make the service environment contain the secrets it needs:
|
||||
|
||||
@@ -167,15 +167,15 @@ From your local machine, create a patch file and pipe it to the VM:
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh <vm-name>.exe.xyz 'openclaw config apply --stdin --dry-run' < ./openclaw.remote.patch.json5
|
||||
ssh <vm-name>.exe.xyz 'openclaw config apply --stdin' < ./openclaw.remote.patch.json5
|
||||
ssh <vm-name>.exe.xyz 'openclaw config patch --stdin --dry-run' < ./openclaw.remote.patch.json5
|
||||
ssh <vm-name>.exe.xyz 'openclaw config patch --stdin' < ./openclaw.remote.patch.json5
|
||||
ssh <vm-name>.exe.xyz 'openclaw gateway restart && openclaw health'
|
||||
```
|
||||
|
||||
Use `--replace-path` when a nested allowlist should become exactly the patch value, for example when replacing a Discord channel allowlist:
|
||||
|
||||
```bash
|
||||
ssh <vm-name>.exe.xyz 'openclaw config apply --stdin --replace-path "channels.discord.guilds[\"123\"].channels"' < ./discord.patch.json5
|
||||
ssh <vm-name>.exe.xyz 'openclaw config patch --stdin --replace-path "channels.discord.guilds[\"123\"].channels"' < ./discord.patch.json5
|
||||
```
|
||||
|
||||
## Remote access
|
||||
|
||||
@@ -1384,7 +1384,7 @@ describe("config cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("applies a config patch object in one write", async () => {
|
||||
it("patches config from one object in one write", async () => {
|
||||
const resolved = {
|
||||
secrets: {
|
||||
providers: {
|
||||
@@ -1403,7 +1403,7 @@ describe("config cli", () => {
|
||||
|
||||
const pathname = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-config-apply-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
|
||||
`openclaw-config-patch-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
pathname,
|
||||
@@ -1435,7 +1435,7 @@ describe("config cli", () => {
|
||||
"utf8",
|
||||
);
|
||||
try {
|
||||
await runConfigCommand(["config", "apply", "--file", pathname]);
|
||||
await runConfigCommand(["config", "patch", "--file", pathname]);
|
||||
} finally {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
}
|
||||
@@ -1462,7 +1462,7 @@ describe("config cli", () => {
|
||||
).toEqual({ source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" });
|
||||
});
|
||||
|
||||
it("dry-runs config apply and resolves changed SecretRefs", async () => {
|
||||
it("dry-runs config patch and resolves changed SecretRefs", async () => {
|
||||
const resolved = {
|
||||
secrets: {
|
||||
providers: {
|
||||
@@ -1474,7 +1474,7 @@ describe("config cli", () => {
|
||||
|
||||
const pathname = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-config-apply-dry-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
|
||||
`openclaw-config-patch-dry-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
pathname,
|
||||
@@ -1488,7 +1488,7 @@ describe("config cli", () => {
|
||||
"utf8",
|
||||
);
|
||||
try {
|
||||
await runConfigCommand(["config", "apply", "--file", pathname, "--dry-run"]);
|
||||
await runConfigCommand(["config", "patch", "--file", pathname, "--dry-run"]);
|
||||
} finally {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
}
|
||||
@@ -1501,7 +1501,7 @@ describe("config cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("dry-runs nested SecretRefs inside config apply replacements", async () => {
|
||||
it("dry-runs nested SecretRefs inside config patch replacements", async () => {
|
||||
const resolved = {
|
||||
secrets: {
|
||||
providers: {
|
||||
@@ -1519,7 +1519,7 @@ describe("config cli", () => {
|
||||
|
||||
const pathname = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-config-apply-nested-ref-${Date.now()}-${Math.random()
|
||||
`openclaw-config-patch-nested-ref-${Date.now()}-${Math.random()
|
||||
.toString(16)
|
||||
.slice(2)}.json5`,
|
||||
);
|
||||
@@ -1541,7 +1541,7 @@ describe("config cli", () => {
|
||||
await expect(
|
||||
runConfigCommand([
|
||||
"config",
|
||||
"apply",
|
||||
"patch",
|
||||
"--file",
|
||||
pathname,
|
||||
"--replace-path",
|
||||
@@ -1560,17 +1560,17 @@ describe("config cli", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects config apply --json without dry-run", async () => {
|
||||
await expect(runConfigCommand(["config", "apply", "--stdin", "--json"])).rejects.toThrow(
|
||||
it("rejects config patch --json without dry-run", async () => {
|
||||
await expect(runConfigCommand(["config", "patch", "--stdin", "--json"])).rejects.toThrow(
|
||||
"__exit__:1",
|
||||
);
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining("config apply mode error: --json requires --dry-run."),
|
||||
expect.stringContaining("config patch mode error: --json requires --dry-run."),
|
||||
);
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("supports replace-path and null deletes in config apply", async () => {
|
||||
it("supports replace-path and null deletes in config patch", async () => {
|
||||
const resolved = {
|
||||
channels: {
|
||||
slack: {
|
||||
@@ -1591,7 +1591,7 @@ describe("config cli", () => {
|
||||
|
||||
const pathname = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-config-apply-replace-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
|
||||
`openclaw-config-patch-replace-${Date.now()}-${Math.random().toString(16).slice(2)}.json5`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
pathname,
|
||||
@@ -1616,7 +1616,7 @@ describe("config cli", () => {
|
||||
try {
|
||||
await runConfigCommand([
|
||||
"config",
|
||||
"apply",
|
||||
"patch",
|
||||
"--file",
|
||||
pathname,
|
||||
"--replace-path",
|
||||
@@ -1641,6 +1641,47 @@ describe("config cli", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects unused config patch replace paths", async () => {
|
||||
const pathname = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-config-patch-unused-replace-${Date.now()}-${Math.random()
|
||||
.toString(16)
|
||||
.slice(2)}.json5`,
|
||||
);
|
||||
fs.writeFileSync(
|
||||
pathname,
|
||||
JSON.stringify({
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
try {
|
||||
await expect(
|
||||
runConfigCommand([
|
||||
"config",
|
||||
"patch",
|
||||
"--file",
|
||||
pathname,
|
||||
"--replace-path",
|
||||
"channels.discord.guilds",
|
||||
]),
|
||||
).rejects.toThrow("__exit__:1");
|
||||
} finally {
|
||||
fs.rmSync(pathname, { force: true });
|
||||
}
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"config patch mode error: --replace-path channels.discord.guilds did not match any value in the input patch.",
|
||||
),
|
||||
);
|
||||
expect(mockWriteConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects malformed batch entries with mixed operation keys", async () => {
|
||||
await expect(
|
||||
runConfigCommand([
|
||||
|
||||
@@ -74,7 +74,7 @@ type ConfigSetOperation = {
|
||||
touchedProviderAlias?: string;
|
||||
assignedRef?: SecretRef;
|
||||
};
|
||||
type ConfigApplyOptions = {
|
||||
type ConfigPatchOptions = {
|
||||
file?: string | undefined;
|
||||
stdin?: boolean | undefined;
|
||||
dryRun?: boolean | undefined;
|
||||
@@ -105,10 +105,10 @@ const CONFIG_SET_EXAMPLE_PROVIDER = formatCliCommand(
|
||||
const CONFIG_SET_EXAMPLE_BATCH = formatCliCommand(
|
||||
"openclaw config set --batch-file ./config-set.batch.json --dry-run",
|
||||
);
|
||||
const CONFIG_APPLY_EXAMPLE_FILE = formatCliCommand(
|
||||
"openclaw config apply --file ./openclaw.patch.json5 --dry-run",
|
||||
const CONFIG_PATCH_EXAMPLE_FILE = formatCliCommand(
|
||||
"openclaw config patch --file ./openclaw.patch.json5 --dry-run",
|
||||
);
|
||||
const CONFIG_APPLY_EXAMPLE_STDIN = formatCliCommand("openclaw config apply --stdin");
|
||||
const CONFIG_PATCH_EXAMPLE_STDIN = formatCliCommand("openclaw config patch --stdin");
|
||||
const CONFIG_SET_DESCRIPTION = [
|
||||
"Set config values by path (value mode, ref/provider builder mode, or batch JSON mode).",
|
||||
"Examples:",
|
||||
@@ -117,14 +117,15 @@ const CONFIG_SET_DESCRIPTION = [
|
||||
CONFIG_SET_EXAMPLE_PROVIDER,
|
||||
CONFIG_SET_EXAMPLE_BATCH,
|
||||
].join("\n");
|
||||
const CONFIG_APPLY_DESCRIPTION = [
|
||||
"Apply a JSON5 config patch object in one validated write.",
|
||||
const CONFIG_PATCH_DESCRIPTION = [
|
||||
"Patch config from a JSON5 object in one validated write.",
|
||||
"Objects merge recursively, arrays/scalars replace, and null deletes a path.",
|
||||
"Examples:",
|
||||
CONFIG_APPLY_EXAMPLE_FILE,
|
||||
CONFIG_APPLY_EXAMPLE_STDIN,
|
||||
CONFIG_PATCH_EXAMPLE_FILE,
|
||||
CONFIG_PATCH_EXAMPLE_STDIN,
|
||||
].join("\n");
|
||||
const CONFIG_SET_POLICY_ERROR_MAX_ISSUES = 5;
|
||||
const CONFIG_PATCH_STDIN_MAX_BYTES = 1024 * 1024;
|
||||
|
||||
class ConfigSetDryRunValidationError extends Error {
|
||||
constructor(readonly result: ConfigSetDryRunResult) {
|
||||
@@ -908,24 +909,37 @@ function parseBatchOperations(entries: ConfigSetBatchEntry[]): ConfigSetOperatio
|
||||
return operations;
|
||||
}
|
||||
|
||||
function configApplyModeError(message: string): Error {
|
||||
return new Error(`config apply mode error: ${message}`);
|
||||
function configPatchModeError(message: string): Error {
|
||||
return new Error(`config patch mode error: ${message}`);
|
||||
}
|
||||
|
||||
async function readStdinText(): Promise<string> {
|
||||
let raw = "";
|
||||
let bytes = 0;
|
||||
if (process.stdin.isTTY) {
|
||||
throw configPatchModeError(
|
||||
"--stdin refuses to read from an interactive terminal; pipe input or use --file <path>.",
|
||||
);
|
||||
}
|
||||
process.stdin.setEncoding("utf8");
|
||||
for await (const chunk of process.stdin) {
|
||||
raw += String(chunk);
|
||||
const text = String(chunk);
|
||||
bytes += Buffer.byteLength(text, "utf8");
|
||||
if (bytes > CONFIG_PATCH_STDIN_MAX_BYTES) {
|
||||
throw configPatchModeError(
|
||||
`--stdin input exceeds ${CONFIG_PATCH_STDIN_MAX_BYTES} bytes; use --file <path> for larger patches.`,
|
||||
);
|
||||
}
|
||||
raw += text;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
async function readConfigApplyPatch(opts: ConfigApplyOptions): Promise<unknown> {
|
||||
async function readConfigPatchInput(opts: ConfigPatchOptions): Promise<unknown> {
|
||||
const file = normalizeOptionalString(opts.file);
|
||||
const stdin = Boolean(opts.stdin);
|
||||
if (Boolean(file) === stdin) {
|
||||
throw configApplyModeError("provide exactly one of --file <path> or --stdin.");
|
||||
throw configPatchModeError("provide exactly one of --file <path> or --stdin.");
|
||||
}
|
||||
const sourceLabel = stdin ? "--stdin" : "--file";
|
||||
const raw = stdin ? await readStdinText() : fs.readFileSync(file as string, "utf8");
|
||||
@@ -940,8 +954,8 @@ function parseReplacePaths(paths: string[] | undefined): PathSegment[][] {
|
||||
return (paths ?? []).map((path) => parseRequiredPath(path));
|
||||
}
|
||||
|
||||
function matchesAnyPath(path: PathSegment[], candidates: PathSegment[][]): boolean {
|
||||
return candidates.some((candidate) => pathEquals(path, candidate));
|
||||
function pathKey(path: PathSegment[]): string {
|
||||
return JSON.stringify(path);
|
||||
}
|
||||
|
||||
function buildDeleteOperation(path: PathSegment[]): ConfigSetOperation {
|
||||
@@ -980,17 +994,21 @@ function buildApplyValueOperation(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function buildConfigApplyOperations(params: {
|
||||
function buildConfigPatchOperations(params: {
|
||||
patch: unknown;
|
||||
replacePaths: PathSegment[][];
|
||||
}): ConfigSetOperation[] {
|
||||
if (!isPlainRecord(params.patch)) {
|
||||
throw configApplyModeError("input must be a JSON5 object patch.");
|
||||
throw configPatchModeError("input must be a JSON5 object patch.");
|
||||
}
|
||||
const operations: ConfigSetOperation[] = [];
|
||||
const replacePathKeys = new Set(params.replacePaths.map(pathKey));
|
||||
const matchedReplacePathKeys = new Set<string>();
|
||||
const visit = (value: unknown, path: PathSegment[]) => {
|
||||
validatePathSegments(path);
|
||||
if (path.length > 0 && matchesAnyPath(path, params.replacePaths)) {
|
||||
const replacementKey = pathKey(path);
|
||||
if (path.length > 0 && replacePathKeys.has(replacementKey)) {
|
||||
matchedReplacePathKeys.add(replacementKey);
|
||||
operations.push(
|
||||
value === null
|
||||
? buildDeleteOperation(path)
|
||||
@@ -1013,14 +1031,22 @@ function buildConfigApplyOperations(params: {
|
||||
return;
|
||||
}
|
||||
if (path.length === 0) {
|
||||
throw configApplyModeError("input must contain at least one config key.");
|
||||
throw configPatchModeError("input must contain at least one config key.");
|
||||
}
|
||||
operations.push(buildApplyValueOperation({ path, value }));
|
||||
};
|
||||
|
||||
visit(params.patch, []);
|
||||
const unusedReplacePath = params.replacePaths.find(
|
||||
(path) => !matchedReplacePathKeys.has(pathKey(path)),
|
||||
);
|
||||
if (unusedReplacePath) {
|
||||
throw configPatchModeError(
|
||||
`--replace-path ${toDotPath(unusedReplacePath)} did not match any value in the input patch.`,
|
||||
);
|
||||
}
|
||||
if (operations.length === 0) {
|
||||
throw configApplyModeError("input patch did not contain any config updates.");
|
||||
throw configPatchModeError("input patch did not contain any config updates.");
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
@@ -1588,20 +1614,20 @@ export async function runConfigSet(opts: {
|
||||
}
|
||||
}
|
||||
|
||||
export async function runConfigApply(opts: {
|
||||
cliOptions: ConfigApplyOptions;
|
||||
export async function runConfigPatch(opts: {
|
||||
cliOptions: ConfigPatchOptions;
|
||||
runtime?: RuntimeEnv;
|
||||
}) {
|
||||
const runtime = opts.runtime ?? defaultRuntime;
|
||||
try {
|
||||
if (opts.cliOptions.allowExec && !opts.cliOptions.dryRun) {
|
||||
throw configApplyModeError("--allow-exec requires --dry-run.");
|
||||
throw configPatchModeError("--allow-exec requires --dry-run.");
|
||||
}
|
||||
if (opts.cliOptions.json && !opts.cliOptions.dryRun) {
|
||||
throw configApplyModeError("--json requires --dry-run.");
|
||||
throw configPatchModeError("--json requires --dry-run.");
|
||||
}
|
||||
const patch = await readConfigApplyPatch(opts.cliOptions);
|
||||
const operations = buildConfigApplyOperations({
|
||||
const patch = await readConfigPatchInput(opts.cliOptions);
|
||||
const operations = buildConfigPatchOperations({
|
||||
patch,
|
||||
replacePaths: parseReplacePaths(opts.cliOptions.replacePath),
|
||||
});
|
||||
@@ -1880,8 +1906,8 @@ export function registerConfigCli(program: Command) {
|
||||
});
|
||||
|
||||
cmd
|
||||
.command("apply")
|
||||
.description(CONFIG_APPLY_DESCRIPTION)
|
||||
.command("patch")
|
||||
.description(CONFIG_PATCH_DESCRIPTION)
|
||||
.option("--file <path>", "Read a JSON5 config patch object from file")
|
||||
.option("--stdin", "Read a JSON5 config patch object from stdin", false)
|
||||
.option(
|
||||
@@ -1901,8 +1927,8 @@ export function registerConfigCli(program: Command) {
|
||||
(value: string, previous: string[]) => [...previous, value],
|
||||
[] as string[],
|
||||
)
|
||||
.action(async (opts: ConfigApplyOptions) => {
|
||||
await runConfigApply({ cliOptions: opts });
|
||||
.action(async (opts: ConfigPatchOptions) => {
|
||||
await runConfigPatch({ cliOptions: opts });
|
||||
});
|
||||
|
||||
cmd
|
||||
|
||||
Reference in New Issue
Block a user