@indigoai-us/hq-cli 5.77.1 → 5.77.3
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 +10 -0
- package/dist/commands/auth.js +25 -6
- package/dist/commands/company.js +9 -3
- package/dist/commands/files.js +4 -4
- package/package.json +1 -1
- package/src/commands/auth.test.ts +82 -0
- package/src/commands/auth.ts +32 -9
- package/src/commands/company.test.ts +25 -0
- package/src/commands/company.ts +21 -5
- package/src/commands/files-recovery.test.ts +13 -4
- package/src/commands/files.ts +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.77.2]
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq auth login` now recognizes callback-port collisions and provides recovery
|
|
10
|
+
guidance instead of surfacing a raw bind exception.
|
|
11
|
+
- `hq company settings set --accounting-enabled <true|false>` now forwards the
|
|
12
|
+
accounting setting to the server, restoring the documented accounting
|
|
13
|
+
activation path.
|
|
14
|
+
|
|
5
15
|
## [5.77.1]
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/dist/commands/auth.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
import { browserLogin, clearCachedTokens, loadCachedTokens, isExpiring, isMachineIdentity, loadMachineCreds, CognitoAuthError, } from "@indigoai-us/hq-cloud";
|
|
18
|
-
import { refreshCachedSession, } from "../utils/cognito-session.js";
|
|
18
|
+
import { DEFAULT_COGNITO, refreshCachedSession, } from "../utils/cognito-session.js";
|
|
19
19
|
import { cognitoConfigForLoginProvider } from "../utils/login-provider.js";
|
|
20
20
|
/**
|
|
21
21
|
* Decode the (unverified) ID token payload for display purposes only.
|
|
@@ -46,6 +46,17 @@ function machineIdentityLabel() {
|
|
|
46
46
|
const creds = loadMachineCreds();
|
|
47
47
|
return creds ? `machine identity ${creds.username}` : "machine identity";
|
|
48
48
|
}
|
|
49
|
+
function isCallbackPortCollision(error) {
|
|
50
|
+
if (!error || typeof error !== "object")
|
|
51
|
+
return false;
|
|
52
|
+
const { code, message } = error;
|
|
53
|
+
return code === "EADDRINUSE" || message?.includes("EADDRINUSE") === true;
|
|
54
|
+
}
|
|
55
|
+
function callbackPortCollisionGuidance(port) {
|
|
56
|
+
return (` The browser-login callback port (127.0.0.1:${port}) is already in use. ` +
|
|
57
|
+
"Another `hq auth login` may still be waiting for browser sign-in. Finish that login, " +
|
|
58
|
+
"or stop its terminal/process, then retry.");
|
|
59
|
+
}
|
|
49
60
|
export function registerAuthCommands(program) {
|
|
50
61
|
const authCmd = program
|
|
51
62
|
.command("auth")
|
|
@@ -72,13 +83,21 @@ export function registerAuthCommands(program) {
|
|
|
72
83
|
console.log(chalk.dim(` Token cached at ~/.hq/cognito-tokens.json (expires ${tokens.expiresAt})`));
|
|
73
84
|
}
|
|
74
85
|
catch (err) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
const callbackPortCollision = isCallbackPortCollision(err);
|
|
87
|
+
const msg = callbackPortCollision
|
|
88
|
+
? "Browser-login callback port is already in use."
|
|
89
|
+
: err instanceof CognitoAuthError
|
|
78
90
|
? err.message
|
|
79
|
-
:
|
|
91
|
+
: err instanceof Error
|
|
92
|
+
? err.message
|
|
93
|
+
: String(err);
|
|
80
94
|
console.error(chalk.red(`Login failed: ${msg}`));
|
|
81
|
-
|
|
95
|
+
if (callbackPortCollision) {
|
|
96
|
+
console.error(chalk.dim(callbackPortCollisionGuidance(DEFAULT_COGNITO.port ?? 8765)));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.error(chalk.dim(" If you do not have an account, sign up at https://onboarding.indigo-hq.com"));
|
|
100
|
+
}
|
|
82
101
|
process.exit(1);
|
|
83
102
|
}
|
|
84
103
|
});
|
package/dist/commands/company.js
CHANGED
|
@@ -25,9 +25,10 @@ export function registerCompanyCommand(program) {
|
|
|
25
25
|
settings
|
|
26
26
|
.command("set")
|
|
27
27
|
.description("Set per-company settings (PUT /company-settings). Require at least one " +
|
|
28
|
-
"of --crm-enabled / --ontology-enabled.")
|
|
28
|
+
"of --crm-enabled / --ontology-enabled / --accounting-enabled.")
|
|
29
29
|
.option("--crm-enabled <true|false>", "Enable/disable the native CRM for this company")
|
|
30
30
|
.option("--ontology-enabled <true|false>", "Enable/disable the ontology gardener for this company")
|
|
31
|
+
.option("--accounting-enabled <true|false>", "Enable/disable the accounting pack for this company")
|
|
31
32
|
.action(async (opts) => {
|
|
32
33
|
try {
|
|
33
34
|
const companySlug = company.opts().company;
|
|
@@ -35,8 +36,10 @@ export function registerCompanyCommand(program) {
|
|
|
35
36
|
console.error(chalk.red("Error: --company <slug> is required."));
|
|
36
37
|
process.exit(1);
|
|
37
38
|
}
|
|
38
|
-
if (opts.crmEnabled === undefined &&
|
|
39
|
-
|
|
39
|
+
if (opts.crmEnabled === undefined &&
|
|
40
|
+
opts.ontologyEnabled === undefined &&
|
|
41
|
+
opts.accountingEnabled === undefined) {
|
|
42
|
+
console.error(chalk.red("Error: provide at least one of --crm-enabled / --ontology-enabled / --accounting-enabled."));
|
|
40
43
|
process.exit(1);
|
|
41
44
|
}
|
|
42
45
|
const body = {};
|
|
@@ -46,6 +49,9 @@ export function registerCompanyCommand(program) {
|
|
|
46
49
|
if (opts.ontologyEnabled !== undefined) {
|
|
47
50
|
body.ontologyEnabled = parseBoolFlag(opts.ontologyEnabled, "--ontology-enabled");
|
|
48
51
|
}
|
|
52
|
+
if (opts.accountingEnabled !== undefined) {
|
|
53
|
+
body.accountingEnabled = parseBoolFlag(opts.accountingEnabled, "--accounting-enabled");
|
|
54
|
+
}
|
|
49
55
|
const token = await ensureCognitoToken();
|
|
50
56
|
const companyUid = await getEntityUid(token, { companySlug });
|
|
51
57
|
body.companyUid = companyUid;
|
package/dist/commands/files.js
CHANGED
|
@@ -381,7 +381,7 @@ export function registerFilesCommand(program) {
|
|
|
381
381
|
});
|
|
382
382
|
files
|
|
383
383
|
.command("versions <path>")
|
|
384
|
-
.description("List prior content versions and delete markers for one exact vault key. Use --personal for your personal vault.")
|
|
384
|
+
.description("List prior content versions and delete markers for one exact vault key. Restore a listed version with restore --version-id <id>. Use --personal for your personal vault.")
|
|
385
385
|
.option("--personal", "Target your own personal vault instead of a company vault (mutually exclusive with --company)")
|
|
386
386
|
.action(async (path, opts) => {
|
|
387
387
|
try {
|
|
@@ -397,9 +397,9 @@ export function registerFilesCommand(program) {
|
|
|
397
397
|
});
|
|
398
398
|
files
|
|
399
399
|
.command("restore <path>")
|
|
400
|
-
.description("Restore a prior version or undelete an exact vault key. Prompts before overwriting unless --yes. Use --personal for your personal vault.")
|
|
400
|
+
.description("Restore a prior version with --version-id or undelete an exact vault key. Prompts before overwriting unless --yes. Use --personal for your personal vault.")
|
|
401
401
|
.option("--personal", "Target your own personal vault instead of a company vault (mutually exclusive with --company)")
|
|
402
|
-
.option("--version <id>", "Content version ID to restore")
|
|
402
|
+
.option("--version-id <id>", "Content version ID to restore")
|
|
403
403
|
.option("-y, --yes", "Skip the overwrite confirmation prompt (for scripts)")
|
|
404
404
|
.action(async (path, opts) => {
|
|
405
405
|
try {
|
|
@@ -408,7 +408,7 @@ export function registerFilesCommand(program) {
|
|
|
408
408
|
assertRecoveryScope(personal, companySlug);
|
|
409
409
|
await runFilesRestore({
|
|
410
410
|
key: path,
|
|
411
|
-
versionId: opts.
|
|
411
|
+
versionId: opts.versionId,
|
|
412
412
|
yes: opts.yes === true,
|
|
413
413
|
personal,
|
|
414
414
|
companySlug,
|
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
browserLogin: vi.fn(),
|
|
6
|
+
isExpiring: vi.fn(),
|
|
7
|
+
isMachineIdentity: vi.fn(),
|
|
8
|
+
loadCachedTokens: vi.fn(),
|
|
9
|
+
loadMachineCreds: vi.fn(),
|
|
10
|
+
CognitoAuthError: class TestCognitoAuthError extends Error {},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("@indigoai-us/hq-cloud", () => ({
|
|
14
|
+
browserLogin: mocks.browserLogin,
|
|
15
|
+
clearCachedTokens: vi.fn(),
|
|
16
|
+
isExpiring: mocks.isExpiring,
|
|
17
|
+
isMachineIdentity: mocks.isMachineIdentity,
|
|
18
|
+
loadCachedTokens: mocks.loadCachedTokens,
|
|
19
|
+
loadMachineCreds: mocks.loadMachineCreds,
|
|
20
|
+
CognitoAuthError: mocks.CognitoAuthError,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock("../utils/cognito-session.js", () => ({
|
|
24
|
+
DEFAULT_COGNITO: {
|
|
25
|
+
region: "us-east-1",
|
|
26
|
+
userPoolDomain: "vault-indigo-hq-prod",
|
|
27
|
+
clientId: "client-123",
|
|
28
|
+
port: 8765,
|
|
29
|
+
identityProvider: "Google",
|
|
30
|
+
},
|
|
31
|
+
refreshCachedSession: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
import { registerAuthCommands } from "./auth.js";
|
|
35
|
+
|
|
36
|
+
class ProcessExitError extends Error {
|
|
37
|
+
constructor(readonly code: number) {
|
|
38
|
+
super(`process.exit(${code})`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("hq auth login", () => {
|
|
43
|
+
it("REGRESSION: explains how to recover when the callback port is held by another login", async () => {
|
|
44
|
+
mocks.isMachineIdentity.mockReturnValue(false);
|
|
45
|
+
mocks.loadCachedTokens.mockReturnValue(undefined);
|
|
46
|
+
mocks.browserLogin.mockRejectedValue(
|
|
47
|
+
Object.assign(new Error("listen EADDRINUSE: address already in use 127.0.0.1:8765"), {
|
|
48
|
+
code: "EADDRINUSE",
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const errors: string[] = [];
|
|
53
|
+
const errorSpy = vi
|
|
54
|
+
.spyOn(console, "error")
|
|
55
|
+
.mockImplementation((...args: unknown[]) => errors.push(args.map(String).join(" ")));
|
|
56
|
+
const exitSpy = vi
|
|
57
|
+
.spyOn(process, "exit")
|
|
58
|
+
.mockImplementation(((code?: number) => {
|
|
59
|
+
throw new ProcessExitError(code ?? 0);
|
|
60
|
+
}) as never);
|
|
61
|
+
const program = new Command();
|
|
62
|
+
program.exitOverride();
|
|
63
|
+
registerAuthCommands(program);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await expect(program.parseAsync(["auth", "login"], { from: "user" })).rejects.toMatchObject({
|
|
67
|
+
code: 1,
|
|
68
|
+
});
|
|
69
|
+
} finally {
|
|
70
|
+
errorSpy.mockRestore();
|
|
71
|
+
exitSpy.mockRestore();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const output = errors.join("\n");
|
|
75
|
+
expect(output).toContain("Login failed: Browser-login callback port is already in use.");
|
|
76
|
+
expect(output).not.toContain("EADDRINUSE");
|
|
77
|
+
expect(output).toContain("callback port (127.0.0.1:8765) is already in use");
|
|
78
|
+
expect(output).toContain("Another `hq auth login` may still be waiting");
|
|
79
|
+
expect(output).toContain("stop its terminal/process, then retry");
|
|
80
|
+
expect(output).not.toContain("sign up at");
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/commands/auth.ts
CHANGED
|
@@ -66,6 +66,20 @@ function machineIdentityLabel(): string {
|
|
|
66
66
|
return creds ? `machine identity ${creds.username}` : "machine identity";
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function isCallbackPortCollision(error: unknown): boolean {
|
|
70
|
+
if (!error || typeof error !== "object") return false;
|
|
71
|
+
const { code, message } = error as NodeJS.ErrnoException;
|
|
72
|
+
return code === "EADDRINUSE" || message?.includes("EADDRINUSE") === true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function callbackPortCollisionGuidance(port: number): string {
|
|
76
|
+
return (
|
|
77
|
+
` The browser-login callback port (127.0.0.1:${port}) is already in use. ` +
|
|
78
|
+
"Another `hq auth login` may still be waiting for browser sign-in. Finish that login, " +
|
|
79
|
+
"or stop its terminal/process, then retry."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
export function registerAuthCommands(program: Command): void {
|
|
70
84
|
const authCmd = program
|
|
71
85
|
.command("auth")
|
|
@@ -105,18 +119,27 @@ export function registerAuthCommands(program: Command): void {
|
|
|
105
119
|
chalk.dim(` Token cached at ~/.hq/cognito-tokens.json (expires ${tokens.expiresAt})`),
|
|
106
120
|
);
|
|
107
121
|
} catch (err) {
|
|
122
|
+
const callbackPortCollision = isCallbackPortCollision(err);
|
|
108
123
|
const msg =
|
|
109
|
-
|
|
110
|
-
?
|
|
111
|
-
: err instanceof
|
|
124
|
+
callbackPortCollision
|
|
125
|
+
? "Browser-login callback port is already in use."
|
|
126
|
+
: err instanceof CognitoAuthError
|
|
112
127
|
? err.message
|
|
113
|
-
:
|
|
128
|
+
: err instanceof Error
|
|
129
|
+
? err.message
|
|
130
|
+
: String(err);
|
|
114
131
|
console.error(chalk.red(`Login failed: ${msg}`));
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
132
|
+
if (callbackPortCollision) {
|
|
133
|
+
console.error(
|
|
134
|
+
chalk.dim(callbackPortCollisionGuidance(DEFAULT_COGNITO.port ?? 8765)),
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
console.error(
|
|
138
|
+
chalk.dim(
|
|
139
|
+
" If you do not have an account, sign up at https://onboarding.indigo-hq.com",
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
120
143
|
process.exit(1);
|
|
121
144
|
}
|
|
122
145
|
});
|
|
@@ -104,6 +104,31 @@ describe("hq company settings set", () => {
|
|
|
104
104
|
expect(sent.ontologyEnabled).toBeUndefined();
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it("PUTs accountingEnabled=true with the resolved companyUid", async () => {
|
|
108
|
+
fetchSpy.mockResolvedValueOnce(
|
|
109
|
+
jsonResponse(200, { companySettings: { accountingEnabled: true } }),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await run([
|
|
113
|
+
"company",
|
|
114
|
+
"settings",
|
|
115
|
+
"set",
|
|
116
|
+
"--company",
|
|
117
|
+
"acme",
|
|
118
|
+
"--accounting-enabled",
|
|
119
|
+
"true",
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
123
|
+
const sent = JSON.parse(fetchSpy.mock.calls[0][1]?.body as string);
|
|
124
|
+
expect(sent).toMatchObject({
|
|
125
|
+
companyUid: "cmp_acme",
|
|
126
|
+
accountingEnabled: true,
|
|
127
|
+
});
|
|
128
|
+
expect(sent.crmEnabled).toBeUndefined();
|
|
129
|
+
expect(sent.ontologyEnabled).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
107
132
|
it("PUTs both flags when both are supplied", async () => {
|
|
108
133
|
fetchSpy.mockResolvedValueOnce(jsonResponse(200, {}));
|
|
109
134
|
|
package/src/commands/company.ts
CHANGED
|
@@ -7,8 +7,9 @@ import { vaultApiFetch, getEntityUid } from "../utils/vault-api.js";
|
|
|
7
7
|
* `hq company settings set` — owner-only per-company settings toggles
|
|
8
8
|
* (agency group-grants US-007 / hq-native-crm US-003). Wraps
|
|
9
9
|
* `PUT /company-settings`, setting the runtime-flippable `crmEnabled` /
|
|
10
|
-
* `ontologyEnabled` flags so an owner can enable the
|
|
11
|
-
* ontology gardener
|
|
10
|
+
* `ontologyEnabled` / `accountingEnabled` flags so an owner can enable the
|
|
11
|
+
* native CRM, ontology gardener, or accounting pack for a company without a
|
|
12
|
+
* redeploy.
|
|
12
13
|
*
|
|
13
14
|
* Conventions mirror `secrets.ts`: ensureCognitoToken() → getEntityUid() →
|
|
14
15
|
* vaultApiFetch() → error-check → chalk output.
|
|
@@ -17,6 +18,7 @@ import { vaultApiFetch, getEntityUid } from "../utils/vault-api.js";
|
|
|
17
18
|
interface SettingsSetFlags {
|
|
18
19
|
crmEnabled?: string;
|
|
19
20
|
ontologyEnabled?: string;
|
|
21
|
+
accountingEnabled?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -45,7 +47,7 @@ export function registerCompanyCommand(program: Command): void {
|
|
|
45
47
|
.command("set")
|
|
46
48
|
.description(
|
|
47
49
|
"Set per-company settings (PUT /company-settings). Require at least one " +
|
|
48
|
-
"of --crm-enabled / --ontology-enabled.",
|
|
50
|
+
"of --crm-enabled / --ontology-enabled / --accounting-enabled.",
|
|
49
51
|
)
|
|
50
52
|
.option(
|
|
51
53
|
"--crm-enabled <true|false>",
|
|
@@ -55,6 +57,10 @@ export function registerCompanyCommand(program: Command): void {
|
|
|
55
57
|
"--ontology-enabled <true|false>",
|
|
56
58
|
"Enable/disable the ontology gardener for this company",
|
|
57
59
|
)
|
|
60
|
+
.option(
|
|
61
|
+
"--accounting-enabled <true|false>",
|
|
62
|
+
"Enable/disable the accounting pack for this company",
|
|
63
|
+
)
|
|
58
64
|
.action(async (opts: SettingsSetFlags) => {
|
|
59
65
|
try {
|
|
60
66
|
const companySlug = company.opts().company as string | undefined;
|
|
@@ -62,10 +68,14 @@ export function registerCompanyCommand(program: Command): void {
|
|
|
62
68
|
console.error(chalk.red("Error: --company <slug> is required."));
|
|
63
69
|
process.exit(1);
|
|
64
70
|
}
|
|
65
|
-
if (
|
|
71
|
+
if (
|
|
72
|
+
opts.crmEnabled === undefined &&
|
|
73
|
+
opts.ontologyEnabled === undefined &&
|
|
74
|
+
opts.accountingEnabled === undefined
|
|
75
|
+
) {
|
|
66
76
|
console.error(
|
|
67
77
|
chalk.red(
|
|
68
|
-
"Error: provide at least one of --crm-enabled / --ontology-enabled.",
|
|
78
|
+
"Error: provide at least one of --crm-enabled / --ontology-enabled / --accounting-enabled.",
|
|
69
79
|
),
|
|
70
80
|
);
|
|
71
81
|
process.exit(1);
|
|
@@ -81,6 +91,12 @@ export function registerCompanyCommand(program: Command): void {
|
|
|
81
91
|
"--ontology-enabled",
|
|
82
92
|
);
|
|
83
93
|
}
|
|
94
|
+
if (opts.accountingEnabled !== undefined) {
|
|
95
|
+
body.accountingEnabled = parseBoolFlag(
|
|
96
|
+
opts.accountingEnabled,
|
|
97
|
+
"--accounting-enabled",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
84
100
|
|
|
85
101
|
const token = await ensureCognitoToken();
|
|
86
102
|
const companyUid = await getEntityUid(token, { companySlug });
|
|
@@ -308,18 +308,20 @@ describe("hq files trash", () => {
|
|
|
308
308
|
});
|
|
309
309
|
|
|
310
310
|
describe("hq files recovery command help", () => {
|
|
311
|
-
it("documents the finalized --version and --prefix option forms", () => {
|
|
311
|
+
it("documents the finalized --version-id and --prefix option forms", () => {
|
|
312
312
|
const program = buildProgram();
|
|
313
313
|
const files = program.commands.find((command) => command.name() === "files");
|
|
314
314
|
const restore = files?.commands.find((command) => command.name() === "restore");
|
|
315
315
|
const trash = files?.commands.find((command) => command.name() === "trash");
|
|
316
316
|
|
|
317
317
|
expect(files?.helpInformation()).toContain("versions [options] <path>");
|
|
318
|
-
expect(
|
|
318
|
+
expect(files?.helpInformation()).toContain("restore --version-id <id>");
|
|
319
|
+
expect(restore?.helpInformation()).toContain("--version-id <id>");
|
|
320
|
+
expect(restore?.helpInformation()).not.toContain("--version <id>");
|
|
319
321
|
expect(trash?.helpInformation()).toContain("--prefix <prefix>");
|
|
320
322
|
});
|
|
321
323
|
|
|
322
|
-
it("passes --version to the restore request", async () => {
|
|
324
|
+
it("passes --version-id to the restore request", async () => {
|
|
323
325
|
fetchSpy.mockResolvedValueOnce(membershipResponse());
|
|
324
326
|
fetchSpy.mockResolvedValueOnce(
|
|
325
327
|
jsonResponse(200, {
|
|
@@ -331,7 +333,14 @@ describe("hq files recovery command help", () => {
|
|
|
331
333
|
);
|
|
332
334
|
|
|
333
335
|
await buildProgram().parseAsync(
|
|
334
|
-
[
|
|
336
|
+
[
|
|
337
|
+
"files",
|
|
338
|
+
"restore",
|
|
339
|
+
"notes/a.md",
|
|
340
|
+
"--version-id",
|
|
341
|
+
"old-version",
|
|
342
|
+
"--yes",
|
|
343
|
+
],
|
|
335
344
|
{ from: "user" },
|
|
336
345
|
);
|
|
337
346
|
|
package/src/commands/files.ts
CHANGED
|
@@ -515,7 +515,7 @@ export function registerFilesCommand(program: Command): Command {
|
|
|
515
515
|
files
|
|
516
516
|
.command("versions <path>")
|
|
517
517
|
.description(
|
|
518
|
-
"List prior content versions and delete markers for one exact vault key. Use --personal for your personal vault.",
|
|
518
|
+
"List prior content versions and delete markers for one exact vault key. Restore a listed version with restore --version-id <id>. Use --personal for your personal vault.",
|
|
519
519
|
)
|
|
520
520
|
.option(
|
|
521
521
|
"--personal",
|
|
@@ -539,18 +539,18 @@ export function registerFilesCommand(program: Command): Command {
|
|
|
539
539
|
files
|
|
540
540
|
.command("restore <path>")
|
|
541
541
|
.description(
|
|
542
|
-
"Restore a prior version or undelete an exact vault key. Prompts before overwriting unless --yes. Use --personal for your personal vault.",
|
|
542
|
+
"Restore a prior version with --version-id or undelete an exact vault key. Prompts before overwriting unless --yes. Use --personal for your personal vault.",
|
|
543
543
|
)
|
|
544
544
|
.option(
|
|
545
545
|
"--personal",
|
|
546
546
|
"Target your own personal vault instead of a company vault (mutually exclusive with --company)",
|
|
547
547
|
)
|
|
548
|
-
.option("--version <id>", "Content version ID to restore")
|
|
548
|
+
.option("--version-id <id>", "Content version ID to restore")
|
|
549
549
|
.option("-y, --yes", "Skip the overwrite confirmation prompt (for scripts)")
|
|
550
550
|
.action(
|
|
551
551
|
async (
|
|
552
552
|
path: string,
|
|
553
|
-
opts: { personal?: boolean; yes?: boolean;
|
|
553
|
+
opts: { personal?: boolean; yes?: boolean; versionId?: string },
|
|
554
554
|
) => {
|
|
555
555
|
try {
|
|
556
556
|
const companySlug = files.opts().company as string | undefined;
|
|
@@ -558,7 +558,7 @@ export function registerFilesCommand(program: Command): Command {
|
|
|
558
558
|
assertRecoveryScope(personal, companySlug);
|
|
559
559
|
await runFilesRestore({
|
|
560
560
|
key: path,
|
|
561
|
-
versionId: opts.
|
|
561
|
+
versionId: opts.versionId,
|
|
562
562
|
yes: opts.yes === true,
|
|
563
563
|
personal,
|
|
564
564
|
companySlug,
|