@bussolabs/closeyourit-cli 0.22.0 → 0.23.1
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 +44 -3
- package/dist/base.d.ts +23 -0
- package/dist/base.js +64 -4
- package/dist/commands/assistant/ask.d.ts +24 -0
- package/dist/commands/assistant/ask.js +112 -0
- package/dist/commands/assistant/conversations/create.d.ts +9 -0
- package/dist/commands/assistant/conversations/create.js +25 -0
- package/dist/commands/assistant/conversations/delete.d.ts +12 -0
- package/dist/commands/assistant/conversations/delete.js +25 -0
- package/dist/commands/assistant/conversations/list.d.ts +10 -0
- package/dist/commands/assistant/conversations/list.js +30 -0
- package/dist/commands/assistant/conversations/show.d.ts +9 -0
- package/dist/commands/assistant/conversations/show.js +25 -0
- package/dist/commands/errors/assign.d.ts +14 -0
- package/dist/commands/errors/assign.js +52 -0
- package/dist/commands/errors/bulk-triage.d.ts +11 -0
- package/dist/commands/errors/bulk-triage.js +41 -0
- package/dist/commands/errors/delete.d.ts +13 -0
- package/dist/commands/errors/delete.js +28 -0
- package/dist/commands/errors/grouping-rules/create.d.ts +15 -0
- package/dist/commands/errors/grouping-rules/create.js +44 -0
- package/dist/commands/errors/grouping-rules/delete.d.ts +13 -0
- package/dist/commands/errors/grouping-rules/delete.js +27 -0
- package/dist/commands/errors/grouping-rules/list.d.ts +10 -0
- package/dist/commands/errors/grouping-rules/list.js +28 -0
- package/dist/commands/errors/grouping-rules/update.d.ts +18 -0
- package/dist/commands/errors/grouping-rules/update.js +43 -0
- package/dist/commands/errors/merge.d.ts +14 -0
- package/dist/commands/errors/merge.js +39 -0
- package/dist/commands/errors/split.d.ts +13 -0
- package/dist/commands/errors/split.js +34 -0
- package/dist/commands/invitations/create.js +9 -2
- package/dist/commands/invitations/resend.js +8 -2
- package/dist/commands/kb/attach.d.ts +12 -0
- package/dist/commands/kb/attach.js +49 -0
- package/dist/commands/kb/attachment-download.d.ts +14 -0
- package/dist/commands/kb/attachment-download.js +38 -0
- package/dist/commands/kb/attachments.d.ts +9 -0
- package/dist/commands/kb/attachments.js +36 -0
- package/dist/commands/kb/create.d.ts +4 -1
- package/dist/commands/kb/create.js +41 -5
- package/dist/commands/kb/detach.d.ts +13 -0
- package/dist/commands/kb/detach.js +26 -0
- package/dist/commands/kb/show.js +3 -0
- package/dist/commands/kb/update.d.ts +4 -0
- package/dist/commands/kb/update.js +69 -21
- package/dist/commands/metrics/bulk-triage.d.ts +11 -0
- package/dist/commands/metrics/bulk-triage.js +44 -0
- package/dist/commands/service-accounts/show.d.ts +9 -0
- package/dist/commands/service-accounts/show.js +26 -0
- package/dist/commands/service-accounts/tokens/list.d.ts +12 -0
- package/dist/commands/service-accounts/tokens/list.js +40 -0
- package/dist/commands/service-accounts/update.d.ts +31 -0
- package/dist/commands/service-accounts/update.js +105 -0
- package/dist/commands/shared/delete.d.ts +12 -0
- package/dist/commands/shared/delete.js +30 -0
- package/dist/commands/shared/list.d.ts +6 -0
- package/dist/commands/shared/list.js +45 -0
- package/dist/commands/shared/set.d.ts +14 -0
- package/dist/commands/shared/set.js +41 -0
- package/dist/commands/tickets/ask.d.ts +13 -0
- package/dist/commands/tickets/ask.js +81 -0
- package/dist/commands/tickets/dependencies/add.d.ts +15 -0
- package/dist/commands/tickets/dependencies/add.js +62 -0
- package/dist/commands/tickets/dependencies/list.d.ts +12 -0
- package/dist/commands/tickets/dependencies/list.js +37 -0
- package/dist/commands/tickets/dependencies/remove.d.ts +14 -0
- package/dist/commands/tickets/dependencies/remove.js +59 -0
- package/dist/commands/tickets/work-context.d.ts +15 -0
- package/dist/commands/tickets/work-context.js +130 -0
- package/dist/commands/vault/requests/approve.d.ts +9 -0
- package/dist/commands/vault/requests/approve.js +40 -0
- package/dist/commands/vault/requests/list.d.ts +6 -0
- package/dist/commands/vault/requests/list.js +29 -0
- package/dist/commands/vault/requests/reject.d.ts +12 -0
- package/dist/commands/vault/requests/reject.js +52 -0
- package/dist/commands/workload/create.d.ts +15 -0
- package/dist/commands/workload/create.js +45 -0
- package/dist/commands/workload/delete.d.ts +12 -0
- package/dist/commands/workload/delete.js +26 -0
- package/dist/commands/workload/list.d.ts +9 -0
- package/dist/commands/workload/list.js +29 -0
- package/dist/commands/workload/participant/add.d.ts +10 -0
- package/dist/commands/workload/participant/add.js +27 -0
- package/dist/commands/workload/participant/remove.d.ts +10 -0
- package/dist/commands/workload/participant/remove.js +23 -0
- package/dist/commands/workload/promote.d.ts +17 -0
- package/dist/commands/workload/promote.js +58 -0
- package/dist/commands/workload/show.d.ts +9 -0
- package/dist/commands/workload/show.js +24 -0
- package/dist/commands/workload/update.d.ts +17 -0
- package/dist/commands/workload/update.js +45 -0
- package/dist/errors/error-codes.d.ts +17 -0
- package/dist/errors/error-codes.js +36 -0
- package/dist/lib/api.d.ts +7 -1
- package/dist/lib/api.js +8 -0
- package/dist/lib/assistant.d.ts +29 -0
- package/dist/lib/assistant.js +76 -0
- package/dist/lib/bulk-triage.d.ts +35 -0
- package/dist/lib/bulk-triage.js +55 -0
- package/dist/lib/grouping-rules.d.ts +28 -0
- package/dist/lib/grouping-rules.js +39 -0
- package/dist/lib/knowledge.d.ts +15 -0
- package/dist/lib/knowledge.js +36 -0
- package/dist/lib/output.js +10 -0
- package/dist/lib/poll.d.ts +51 -0
- package/dist/lib/poll.js +59 -0
- package/dist/lib/service-accounts.d.ts +65 -0
- package/dist/lib/service-accounts.js +132 -0
- package/dist/lib/vault-requests.d.ts +38 -0
- package/dist/lib/vault-requests.js +125 -0
- package/dist/lib/workload.d.ts +37 -0
- package/dist/lib/workload.js +64 -0
- package/oclif.manifest.json +6290 -4001
- package/opencli.json +2164 -549
- package/package.json +27 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const base_1 = require("../../base");
|
|
4
|
+
const output_1 = require("../../lib/output");
|
|
5
|
+
const paginate_1 = require("../../lib/paginate");
|
|
6
|
+
/**
|
|
7
|
+
* Environment codes carried by a shared-variable row, or `[]` when the payload does not say.
|
|
8
|
+
*
|
|
9
|
+
* A shared variable holds one value per environment, but `SharedSecretVariableSerializer` today
|
|
10
|
+
* ships only `{id, name, description, created_at, updated_at}` — the per-environment matrix is not
|
|
11
|
+
* in the list payload. Read it defensively rather than print an always-empty column: the day the
|
|
12
|
+
* backend adds `environments`, `shared list` shows it without a CLI change (CYCL-41).
|
|
13
|
+
*/
|
|
14
|
+
function environmentCodes(row) {
|
|
15
|
+
if (!Array.isArray(row.environments))
|
|
16
|
+
return [];
|
|
17
|
+
return row.environments
|
|
18
|
+
.map((env) => (env !== null && typeof env === 'object' ? env.code : env))
|
|
19
|
+
.map((code) => String(code ?? ''))
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
class SharedList extends base_1.BaseCommand {
|
|
23
|
+
static description = 'List the organization shared secret names (one row per variable, every page). Never prints values.';
|
|
24
|
+
static examples = ['<%= config.bin %> shared list', '<%= config.bin %> shared list --json'];
|
|
25
|
+
async run() {
|
|
26
|
+
await this.parse(SharedList);
|
|
27
|
+
const data = await (0, paginate_1.fetchAllPages)(this.api, '/cli/v1/shared_secrets');
|
|
28
|
+
if (!this.jsonEnabled()) {
|
|
29
|
+
// ENVIRONMENTS shows up only when at least one row carries it, like the STATUS column of
|
|
30
|
+
// `kb list`: a column of blanks would read as "this variable is on no environment".
|
|
31
|
+
const showEnvironments = data.some((secret) => environmentCodes(secret).length > 0);
|
|
32
|
+
const headers = showEnvironments ? ['NAME', 'ENVIRONMENTS', 'DESCRIPTION', 'UPDATED'] : ['NAME', 'DESCRIPTION', 'UPDATED'];
|
|
33
|
+
const rows = data.map((secret) => {
|
|
34
|
+
const row = [String(secret.name ?? '')];
|
|
35
|
+
if (showEnvironments)
|
|
36
|
+
row.push(environmentCodes(secret).join(', '));
|
|
37
|
+
row.push(String(secret.description ?? ''), String(secret.updated_at ?? ''));
|
|
38
|
+
return row;
|
|
39
|
+
});
|
|
40
|
+
this.log((0, output_1.renderTable)(headers, rows));
|
|
41
|
+
}
|
|
42
|
+
return { data, meta: (0, paginate_1.aggregatedMeta)(data.length) };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.default = SharedList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SharedSet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
assignment: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
value: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
environment: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../base");
|
|
5
|
+
class SharedSet extends base_1.BaseCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
assignment: core_1.Args.string({ description: 'NAME=VALUE, or just NAME with --value', required: true }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Set (create or update) an organization shared secret for one environment. Projects delegated on that environment read the new value.';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> shared set -e production SENTRY_DSN=https://key@sentry.io/42',
|
|
12
|
+
'<%= config.bin %> shared set -e staging API_KEY --value s3cr3t --description "Stripe key"',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
// No --project on purpose: a shared variable belongs to the organization and reaches projects
|
|
16
|
+
// through delegation, so scoping it to one project at write time would be a lie.
|
|
17
|
+
...base_1.environmentFlag,
|
|
18
|
+
value: core_1.Flags.string({ description: 'Secret value (use when the arg is just NAME)' }),
|
|
19
|
+
description: core_1.Flags.string({ description: 'Optional description' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { args, flags } = await this.parse(SharedSet);
|
|
23
|
+
const eq = args.assignment.indexOf('=');
|
|
24
|
+
const name = eq === -1 ? args.assignment : args.assignment.slice(0, eq);
|
|
25
|
+
const value = eq === -1 ? flags.value : args.assignment.slice(eq + 1);
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
this.error('Provide a value: use NAME=VALUE or pass --value', { exit: 1 });
|
|
28
|
+
}
|
|
29
|
+
const res = await this.api.post('/cli/v1/shared_secrets', {
|
|
30
|
+
environment: flags.environment,
|
|
31
|
+
name,
|
|
32
|
+
value,
|
|
33
|
+
description: flags.description,
|
|
34
|
+
});
|
|
35
|
+
if (!this.jsonEnabled()) {
|
|
36
|
+
this.log(`Shared secret ${String(name).toUpperCase()} saved (${flags.environment}).`);
|
|
37
|
+
}
|
|
38
|
+
return res;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.default = SharedSet;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class TicketsAsk extends BaseCommand {
|
|
3
|
+
static strict: boolean;
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static args: {
|
|
7
|
+
question: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../base");
|
|
5
|
+
const error_codes_1 = require("../../errors/error-codes");
|
|
6
|
+
const api_1 = require("../../lib/api");
|
|
7
|
+
const output_1 = require("../../lib/output");
|
|
8
|
+
const poll_1 = require("../../lib/poll");
|
|
9
|
+
class TicketsAsk extends base_1.BaseCommand {
|
|
10
|
+
// Variadic question: every positional word is joined into a single natural-language question.
|
|
11
|
+
static strict = false;
|
|
12
|
+
static description = 'Ask a natural-language question about the tickets you can see; the AI answers and cites the tickets it used (may take a few seconds)';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> tickets ask what is left before the next release',
|
|
15
|
+
'<%= config.bin %> tickets ask "has anyone reported this crash before?" --json',
|
|
16
|
+
];
|
|
17
|
+
// Non-required: with strict = false the arg validation is off, so we guard the empty case ourselves.
|
|
18
|
+
static args = {
|
|
19
|
+
question: core_1.Args.string({ description: 'The question (all positional words are joined)' }),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
timeout: core_1.Flags.integer({
|
|
23
|
+
description: 'Seconds to wait for the answer before giving up',
|
|
24
|
+
default: poll_1.DEFAULT_TIMEOUT_SECONDS,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { argv, flags } = await this.parse(TicketsAsk);
|
|
29
|
+
const question = argv.join(' ').trim();
|
|
30
|
+
if (question === '') {
|
|
31
|
+
this.error('Provide a question (e.g. "tickets ask what is left before the next release").', { exit: 2 });
|
|
32
|
+
}
|
|
33
|
+
const invalidTimeout = (0, poll_1.timeoutError)(flags.timeout);
|
|
34
|
+
if (invalidTimeout)
|
|
35
|
+
this.error(invalidTimeout, { exit: 2 });
|
|
36
|
+
if (!this.jsonEnabled())
|
|
37
|
+
this.log('Asking your tickets (AI), this may take a few seconds…');
|
|
38
|
+
// The question is answered elsewhere: this call only queues it and hands back the id to follow.
|
|
39
|
+
const queued = await this.api.post('/cli/v1/tickets/ask', { question });
|
|
40
|
+
const requestId = queued.data?.request_id;
|
|
41
|
+
if (typeof requestId !== 'string' || requestId === '') {
|
|
42
|
+
throw new api_1.ApiRequestError(502, error_codes_1.ErrorCodes.Ask.failed, 'The server accepted the question without a request id: there is nothing to follow.');
|
|
43
|
+
}
|
|
44
|
+
const final = await (0, poll_1.pollUntil)(async () => (await this.api.get(`/cli/v1/ai/requests/${encodeURIComponent(requestId)}`)).data ?? {}, (state) => state.status !== 'pending', {
|
|
45
|
+
timeoutMs: flags.timeout * 1000,
|
|
46
|
+
// Not "ask again": the question is still being worked on as request `${requestId}`, and
|
|
47
|
+
// repeating it would queue a second one instead of picking that answer up. Waiting longer
|
|
48
|
+
// is the way through.
|
|
49
|
+
timeoutMessage: `No answer within ${flags.timeout}s: request ${requestId} is still being worked on. ` +
|
|
50
|
+
'Ask again with a longer --timeout (asking now would queue a second question, not collect this one).',
|
|
51
|
+
});
|
|
52
|
+
if (final.status === 'failed') {
|
|
53
|
+
throw new api_1.ApiRequestError(502, final.error?.code ?? error_codes_1.ErrorCodes.Ask.failed, final.error?.message ?? 'The question could not be answered (temporary problem upstream); try again in a moment.');
|
|
54
|
+
}
|
|
55
|
+
// Anything that is not `pending` ended the wait, but only `done` carries a result: a missing or
|
|
56
|
+
// unknown status would otherwise print as a successful empty answer.
|
|
57
|
+
if (final.status !== 'done') {
|
|
58
|
+
throw new api_1.ApiRequestError(502, error_codes_1.ErrorCodes.Ask.failed, `The server reported a request state this CLI does not know ("${final.status ?? 'none'}"): treat the answer as missing.`);
|
|
59
|
+
}
|
|
60
|
+
const result = final.result ?? {};
|
|
61
|
+
if (!this.jsonEnabled()) {
|
|
62
|
+
if (result.insufficient) {
|
|
63
|
+
this.log('The tickets you can see do not have enough to answer this confidently.');
|
|
64
|
+
}
|
|
65
|
+
const answer = String(result.answer ?? '').trim();
|
|
66
|
+
if (answer !== '')
|
|
67
|
+
this.log((0, output_1.sanitizeMultiline)(result.answer));
|
|
68
|
+
// The tickets the answer stands on: an answer that names its sources can be checked, one that
|
|
69
|
+
// does not asks to be believed.
|
|
70
|
+
const tickets = Array.isArray(result.tickets) ? result.tickets : [];
|
|
71
|
+
if (tickets.length > 0) {
|
|
72
|
+
this.log('\n🎫 Tickets:');
|
|
73
|
+
for (const ticket of tickets) {
|
|
74
|
+
this.log(` ${(0, output_1.sanitize)(ticket.code)} · ${(0, output_1.sanitize)(ticket.title)} · ${(0, output_1.sanitize)(ticket.status_label)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { data: { request_id: requestId, status: final.status, result } };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.default = TicketsAsk;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class TicketsDependenciesAdd extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
blocker: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
/** Il blocker si nomina col suo codice; l'elenco lo espone su `code`, salvo che sia oscurato. */
|
|
14
|
+
private dependencyExists;
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../../base");
|
|
5
|
+
const api_1 = require("../../../lib/api");
|
|
6
|
+
/** Il server risponde così quando l'arco che si sta creando non è ammesso — doppione compreso. */
|
|
7
|
+
const NOT_ALLOWED = 'R422-TICKET-017';
|
|
8
|
+
class TicketsDependenciesAdd extends base_1.BaseCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. CYRA-132)', required: true }),
|
|
11
|
+
blocker: core_1.Args.string({ description: 'Prerequisite ticket id or code — may live in another project', required: true }),
|
|
12
|
+
};
|
|
13
|
+
static description = 'Add a prerequisite to a ticket: the blocker has to be finished before this ticket can start';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> tickets dependencies add CYRA-132 CYAU-83 --project closeyourit-rails',
|
|
16
|
+
'while read -r a b; do <%= config.bin %> tickets dependencies add "$a" "$b" -p closeyourit-rails; done < ordine.txt',
|
|
17
|
+
];
|
|
18
|
+
static flags = { ...base_1.projectFlag };
|
|
19
|
+
async run() {
|
|
20
|
+
const { args, flags } = await this.parse(TicketsDependenciesAdd);
|
|
21
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
22
|
+
const base = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/dependencies`;
|
|
23
|
+
// Gemello della guardia in `remove`: con l'argomento vuoto la rilettura combacerebbe con una riga
|
|
24
|
+
// oscurata (`code: null` vale "") e questo comando direbbe «già presente» uscendo 0 — un errore
|
|
25
|
+
// vero che passa per lavoro fatto, proprio dentro il file che si rilancia.
|
|
26
|
+
if (args.blocker.trim() === '') {
|
|
27
|
+
this.error('Il prerequisito va nominato: passa il suo codice, anche di un altro prodotto (es. CYAU-83).', { exit: 2 });
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const res = await this.api.post(base, { blocker: args.blocker });
|
|
31
|
+
if (!this.jsonEnabled())
|
|
32
|
+
this.log(`prerequisito aggiunto: ${args.id} ← ${args.blocker}`);
|
|
33
|
+
return res.data;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Rilanciare il file intero deve essere sicuro: un legame già scritto non è un errore, è il
|
|
37
|
+
// lavoro già fatto. Ma `R422-TICKET-017` non vuol dire solo «c'è già» — è la risposta a ogni
|
|
38
|
+
// arco non ammesso, e trattarla come «va bene» in blocco farebbe passare per riuscite anche le
|
|
39
|
+
// righe rifiutate per un altro motivo. Quindi si rilegge l'elenco: se l'arco c'è davvero si esce
|
|
40
|
+
// 0, altrimenti l'errore propaga.
|
|
41
|
+
//
|
|
42
|
+
// Il ciclo (R422-TICKET-013) e il prerequisito inesistente (R404-TICKET-015) propagano sempre:
|
|
43
|
+
// lì la riga è sbagliata e va vista, non ingoiata.
|
|
44
|
+
if (!(error instanceof api_1.ApiRequestError) || error.code !== NOT_ALLOWED)
|
|
45
|
+
throw error;
|
|
46
|
+
const gia = await this.dependencyExists(base, args.blocker);
|
|
47
|
+
if (!gia)
|
|
48
|
+
throw error;
|
|
49
|
+
if (!this.jsonEnabled())
|
|
50
|
+
this.log(`prerequisito già presente: ${args.id} ← ${args.blocker}`);
|
|
51
|
+
return { ticket: args.id, blocker: args.blocker, alreadyPresent: true };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Il blocker si nomina col suo codice; l'elenco lo espone su `code`, salvo che sia oscurato. */
|
|
55
|
+
async dependencyExists(base, blocker) {
|
|
56
|
+
const res = await this.api.get(base);
|
|
57
|
+
const cercato = blocker.toLowerCase();
|
|
58
|
+
return (res.data ?? []).some((row) => (typeof row.code === 'string' && row.code.toLowerCase() === cercato) ||
|
|
59
|
+
(typeof row.blocker_id === 'string' && row.blocker_id === blocker));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.default = TicketsDependenciesAdd;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class TicketsDependenciesList extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../../base");
|
|
5
|
+
const output_1 = require("../../../lib/output");
|
|
6
|
+
class TicketsDependenciesList extends base_1.BaseCommand {
|
|
7
|
+
static args = {
|
|
8
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. CYRA-132)', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'List the prerequisites of a ticket: what has to be finished before this one can start';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> tickets dependencies list CYRA-132 --project closeyourit-rails',
|
|
13
|
+
'<%= config.bin %> tickets dependencies list CYRA-132 -p closeyourit-rails --json',
|
|
14
|
+
];
|
|
15
|
+
static flags = { ...base_1.projectFlag };
|
|
16
|
+
async run() {
|
|
17
|
+
const { args, flags } = await this.parse(TicketsDependenciesList);
|
|
18
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
19
|
+
const path = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/dependencies`;
|
|
20
|
+
const res = await this.api.get(path);
|
|
21
|
+
const rows = res.data ?? [];
|
|
22
|
+
if (!this.jsonEnabled()) {
|
|
23
|
+
// Un prerequisito in un prodotto che il tuo accesso non vede resta VISIBILE come fatto — esiste,
|
|
24
|
+
// ed è concluso o no — ma senza codice né titolo. È la stessa regola della pagina web: nascondere
|
|
25
|
+
// anche l'esistenza direbbe «questo ticket non è bloccato», che è falso e porta a partire.
|
|
26
|
+
this.log((0, output_1.renderTable)(['ID', 'BLOCKER', 'TITLE', 'STATUS', 'DONE'], rows.map((row) => [
|
|
27
|
+
(0, output_1.sanitize)(row.id ?? ''),
|
|
28
|
+
row.hidden === true ? '(nascosto)' : (0, output_1.sanitize)(row.code ?? ''),
|
|
29
|
+
row.hidden === true ? '(fuori dal tuo accesso)' : (0, output_1.sanitize)(row.title ?? ''),
|
|
30
|
+
(0, output_1.sanitize)(row.status?.label ?? row.status?.code ?? ''),
|
|
31
|
+
row.done === true ? 'sì' : 'no',
|
|
32
|
+
])));
|
|
33
|
+
}
|
|
34
|
+
return rows;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.default = TicketsDependenciesList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class TicketsDependenciesRemove extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
blocker: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
private resolveDependencyId;
|
|
14
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../../base");
|
|
5
|
+
class TicketsDependenciesRemove extends base_1.BaseCommand {
|
|
6
|
+
static args = {
|
|
7
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. CYRA-132)', required: true }),
|
|
8
|
+
blocker: core_1.Args.string({
|
|
9
|
+
description: 'Prerequisite to remove: its ticket code (e.g. CYAU-83) or the dependency id',
|
|
10
|
+
required: true,
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
static description = 'Remove a prerequisite from a ticket';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> tickets dependencies remove CYRA-132 CYAU-83 --project closeyourit-rails',
|
|
16
|
+
];
|
|
17
|
+
static flags = { ...base_1.projectFlag };
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(TicketsDependenciesRemove);
|
|
20
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
21
|
+
const base = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/dependencies`;
|
|
22
|
+
// Il server vuole l'UUID della dipendenza, che nessun comando ti ha mai mostrato: si accetta anche
|
|
23
|
+
// il CODICE del prerequisito — quello che hai scritto per aggiungerlo — e lo si risolve leggendo
|
|
24
|
+
// l'elenco. Nessun --confirm: l'arco si ricrea identico con una riga, e la riprovabilità è lo scopo
|
|
25
|
+
// di tutto questo lavoro.
|
|
26
|
+
// Un prerequisito NASCOSTO ha `code: null`, che confrontato come stringa vale "". Un argomento
|
|
27
|
+
// vuoto diventa quindi "" e combacerebbe con la prima riga oscurata: `remove <ticket> ""`
|
|
28
|
+
// cancellerebbe un prerequisito che chi lancia non ha nemmeno il diritto di vedere, in silenzio.
|
|
29
|
+
if (args.blocker.trim() === '') {
|
|
30
|
+
this.error('Il prerequisito da togliere va nominato: passa il suo codice (es. CYAU-83) o l\'id della dipendenza.', { exit: 2 });
|
|
31
|
+
}
|
|
32
|
+
const dependencyId = await this.resolveDependencyId(base, args.blocker);
|
|
33
|
+
if (!dependencyId) {
|
|
34
|
+
// Nessuna DELETE alla cieca: senza aver trovato l'arco, mandarla vorrebbe dire chiedere al server
|
|
35
|
+
// di cancellare un id che non abbiamo, e leggere il suo 404 come se fosse la nostra risposta.
|
|
36
|
+
this.error(`Nessun prerequisito «${args.blocker}» su ${args.id}: niente da togliere.`, { exit: 2 });
|
|
37
|
+
}
|
|
38
|
+
await this.api.delete(`${base}/${encodeURIComponent(dependencyId)}`);
|
|
39
|
+
if (!this.jsonEnabled())
|
|
40
|
+
this.log(`prerequisito rimosso: ${args.id} ← ${args.blocker}`);
|
|
41
|
+
return { ticket: args.id, blocker: args.blocker, removed: dependencyId };
|
|
42
|
+
}
|
|
43
|
+
async resolveDependencyId(base, blocker) {
|
|
44
|
+
const res = await this.api.get(base);
|
|
45
|
+
const righe = res.data ?? [];
|
|
46
|
+
const cercato = blocker.toLowerCase();
|
|
47
|
+
// Prima per codice del prerequisito, poi per id della dipendenza: chi ha già l'id (lo stampa
|
|
48
|
+
// `dependencies list`) non deve essere costretto a passare dal codice, che per un prerequisito
|
|
49
|
+
// oscurato non esiste nemmeno.
|
|
50
|
+
// `code` ASSENTE non combacia mai, con nessun argomento: è la riga oscurata, e l'unico modo di
|
|
51
|
+
// toglierla è nominarne l'id — quello che `dependencies list` stampa anche per lei.
|
|
52
|
+
const perCodice = righe.find((row) => typeof row.code === 'string' && row.code.toLowerCase() === cercato);
|
|
53
|
+
if (perCodice?.id)
|
|
54
|
+
return String(perCodice.id);
|
|
55
|
+
const perId = righe.find((row) => String(row.id ?? '') === blocker || String(row.blocker_id ?? '') === blocker);
|
|
56
|
+
return perId?.id ? String(perId.id) : null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.default = TicketsDependenciesRemove;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class TicketsWorkContext extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
full: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
/** Le due liste congelate: stessa tabella di `guidance show`, così il confronto con l'oggi è immediato. */
|
|
14
|
+
private renderPayload;
|
|
15
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../base");
|
|
5
|
+
const api_1 = require("../../lib/api");
|
|
6
|
+
const guidance_1 = require("../../lib/guidance");
|
|
7
|
+
const output_1 = require("../../lib/output");
|
|
8
|
+
class TicketsWorkContext extends base_1.BaseCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
id: core_1.Args.string({ description: 'Ticket id or code (e.g. DRFL-3)', required: true }),
|
|
11
|
+
};
|
|
12
|
+
static description = "Show the work context frozen when an automation host claimed the ticket: the references and procedures actually delivered back then, never the project's current guidance";
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> tickets work-context <ticket-id> --project acme-api',
|
|
15
|
+
'<%= config.bin %> tickets work-context DRFL-3 -p driverone-flutter',
|
|
16
|
+
'<%= config.bin %> tickets work-context DRFL-3 -p driverone-flutter --full',
|
|
17
|
+
'<%= config.bin %> tickets work-context DRFL-3 -p driverone-flutter --json',
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
...base_1.projectFlag,
|
|
21
|
+
full: core_1.Flags.boolean({
|
|
22
|
+
description: 'Also print the frozen text of every reference and procedure, not just the table',
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { args, flags } = await this.parse(TicketsWorkContext);
|
|
27
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
28
|
+
const path = `/cli/v1/projects/${encodeURIComponent(projectId)}/tickets/${encodeURIComponent(args.id)}/work_context`;
|
|
29
|
+
// Una sola lettura, e sull'endpoint dello snapshot: mai un ripiego su /guidance. La guidance del
|
|
30
|
+
// progetto può essere cambiata dopo la presa in carico, e mostrarla al posto della fotografia
|
|
31
|
+
// significherebbe rispondere a un audit con la configurazione di oggi (CYCL-47, scenario 1).
|
|
32
|
+
let res;
|
|
33
|
+
try {
|
|
34
|
+
res = await this.api.get(path);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw describeWorkContextError(error, args.id);
|
|
38
|
+
}
|
|
39
|
+
if (!this.jsonEnabled()) {
|
|
40
|
+
const snapshot = res.data ?? {};
|
|
41
|
+
this.log(`Work context frozen when ${(0, output_1.sanitize)(args.id)} was claimed from the agent queue — not the project's current guidance (that is \`guidance show\`).`);
|
|
42
|
+
this.log((0, output_1.renderRecord)(snapshotSummary(snapshot)));
|
|
43
|
+
this.renderPayload(snapshot.payload, flags.full);
|
|
44
|
+
}
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
/** Le due liste congelate: stessa tabella di `guidance show`, così il confronto con l'oggi è immediato. */
|
|
48
|
+
renderPayload(payload, full) {
|
|
49
|
+
const rendered = (0, guidance_1.renderGuidance)(payload);
|
|
50
|
+
if (!rendered) {
|
|
51
|
+
this.log('\nThis snapshot froze no references and no procedures.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.log(rendered);
|
|
55
|
+
if (!full) {
|
|
56
|
+
this.log('\nPass --full to read the frozen text of each reference and procedure.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Il testo integrale è il cuore dell'audit (le istruzioni consegnate, non solo le loro chiavi),
|
|
60
|
+
// ma su una guidance ricca sono migliaia di caratteri: sta dietro --full per non trasformare
|
|
61
|
+
// ogni lettura rapida in un muro di testo. In --json c'è sempre tutto, comunque.
|
|
62
|
+
const body = renderFrozenText(payload);
|
|
63
|
+
if (body)
|
|
64
|
+
this.log(body);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.default = TicketsWorkContext;
|
|
68
|
+
/** Intestazione della fotografia: chi l'ha ricevuta, quando, con quale versione di schema e digest. */
|
|
69
|
+
function snapshotSummary(snapshot) {
|
|
70
|
+
return {
|
|
71
|
+
snapshot_id: snapshot.id ?? null,
|
|
72
|
+
captured_at: snapshot.generated_at ?? null,
|
|
73
|
+
captured_by: snapshot.actor_name ?? null,
|
|
74
|
+
payload_version: snapshot.payload_version ?? null,
|
|
75
|
+
digest: snapshot.digest ?? null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Le entry di una lista del payload, o [] se il backend non l'ha mandata. */
|
|
79
|
+
function entries(value) {
|
|
80
|
+
return Array.isArray(value) ? value : [];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Il testo congelato di ogni entry che ne ha uno: le `instructions` di una reference, il `content`
|
|
84
|
+
* di una procedure. Ritorna '' quando nessuna entry porta testo, così `--full` su uno snapshot di
|
|
85
|
+
* sole chiavi non stampa un titolo su una sezione vuota.
|
|
86
|
+
*/
|
|
87
|
+
function renderFrozenText(payload) {
|
|
88
|
+
if (!payload || typeof payload !== 'object')
|
|
89
|
+
return '';
|
|
90
|
+
const value = payload;
|
|
91
|
+
const blocks = [
|
|
92
|
+
...entries(value.references).map((entry) => frozenBlock(entry, entry.instructions)),
|
|
93
|
+
...entries(value.procedures).map((entry) => frozenBlock(entry, entry.content)),
|
|
94
|
+
].filter((block) => block.length > 0);
|
|
95
|
+
if (blocks.length === 0)
|
|
96
|
+
return '';
|
|
97
|
+
return `${(0, output_1.section)('Frozen text', '🧊')}\n${blocks.join('\n')}`;
|
|
98
|
+
}
|
|
99
|
+
/** Una entry col suo testo: riga di intestazione (chiave · livello · posizione) e testo per intero. */
|
|
100
|
+
function frozenBlock(entry, text) {
|
|
101
|
+
const body = String(text ?? '');
|
|
102
|
+
if (body.trim() === '')
|
|
103
|
+
return '';
|
|
104
|
+
const head = [entry.key, entry.level, entry.location].filter((part) => part !== undefined && part !== null && part !== '');
|
|
105
|
+
return `\n── ${head.map((part) => (0, output_1.sanitize)(part)).join(' · ')}\n${(0, output_1.sanitizeMultiline)(body)}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Riscrive i due rifiuti che un lettore fraintenderebbe, lasciando intatto tutto il resto (codice
|
|
109
|
+
* del server compreso, così uno script continua a distinguerli):
|
|
110
|
+
*
|
|
111
|
+
* - `R404-TICKET-004` non è «ticket non trovato»: il ticket c'è, è la fotografia a mancare. Dire
|
|
112
|
+
* «mai preso in carico» sarebbe però una bugia comoda: lo snapshot lo scatta SOLO la presa in
|
|
113
|
+
* carico dalla coda degli host automatici (Agents::TicketQueues::Claim), quindi un ticket preso a
|
|
114
|
+
* mano — con `tickets lease claim` o assegnandolo — non ne ha uno, e nemmeno i ticket presi in
|
|
115
|
+
* carico prima che la fotografia esistesse. Il messaggio dice cosa manca e perché può mancare,
|
|
116
|
+
* così chi legge non conclude che nessuno ci abbia mai lavorato.
|
|
117
|
+
* - il 403 arriva come «Permesso negato» e basta: qui la chiave che manca è `tickets.audit.view`,
|
|
118
|
+
* che è dedicata al materiale d'audit — vedere il ticket, o la sua guidance, non basta.
|
|
119
|
+
*/
|
|
120
|
+
function describeWorkContextError(error, ticketRef) {
|
|
121
|
+
if (!(error instanceof api_1.ApiRequestError))
|
|
122
|
+
return error;
|
|
123
|
+
if (error.status === 404 && error.code === 'R404-TICKET-004') {
|
|
124
|
+
return new api_1.ApiRequestError(error.status, error.code, `No work context was captured for ticket ${ticketRef}: the snapshot is taken only when an automation host claims the ticket from the queue, so a manual claim — or one older than the snapshot itself — leaves none behind.`, error.details);
|
|
125
|
+
}
|
|
126
|
+
if (error.status === 403) {
|
|
127
|
+
return new api_1.ApiRequestError(error.status, error.code, `${error.message}. Reading a work context needs the tickets.audit.view permission: it is audit material, seeing the ticket is not enough.`, error.details);
|
|
128
|
+
}
|
|
129
|
+
return error;
|
|
130
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base';
|
|
2
|
+
export default class VaultRequestsApprove extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<unknown>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../../base");
|
|
5
|
+
const output_1 = require("../../../lib/output");
|
|
6
|
+
const vault_requests_1 = require("../../../lib/vault-requests");
|
|
7
|
+
class VaultRequestsApprove extends base_1.BaseCommand {
|
|
8
|
+
// No --confirm here, unlike the destructive deletions of this CLI: "approve" IS the confirmation —
|
|
9
|
+
// the command exists to say yes, and asking twice would only push the reviewer back to the website
|
|
10
|
+
// this command replaces. The success line names what was applied and where, so an approval on the
|
|
11
|
+
// wrong id is visible at once.
|
|
12
|
+
static description = 'Approve a pending secret change request: the change it carries is applied to the vault. Whoever asked for it cannot approve it (four-eyes), and a service account never can.';
|
|
13
|
+
static examples = [
|
|
14
|
+
'<%= config.bin %> vault requests approve <id>',
|
|
15
|
+
'<%= config.bin %> vault requests approve <id> --json',
|
|
16
|
+
];
|
|
17
|
+
static args = {
|
|
18
|
+
id: core_1.Args.string({ description: 'Change request id (from "vault requests list")', required: true }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { args } = await this.parse(VaultRequestsApprove);
|
|
22
|
+
let res;
|
|
23
|
+
try {
|
|
24
|
+
res = await this.api.post(`${vault_requests_1.CHANGE_REQUESTS_PATH}/${encodeURIComponent(args.id)}/approve`);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw (0, vault_requests_1.describeDecisionError)(error);
|
|
28
|
+
}
|
|
29
|
+
const decided = (0, vault_requests_1.withoutValue)(res.data ?? {});
|
|
30
|
+
if (!this.jsonEnabled()) {
|
|
31
|
+
const label = (0, vault_requests_1.changeRequestLabel)(decided);
|
|
32
|
+
this.log(label ? `Change request approved: ${label} is applied.` : 'Change request approved.');
|
|
33
|
+
const summary = (0, vault_requests_1.changeRequestSummary)(decided);
|
|
34
|
+
if (Object.keys(summary).length > 0)
|
|
35
|
+
this.log((0, output_1.renderRecord)(summary));
|
|
36
|
+
}
|
|
37
|
+
return { ...res, data: decided };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.default = VaultRequestsApprove;
|