@bussolabs/closeyourit-cli 0.18.0 → 0.20.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 +29 -5
- package/dist/base.d.ts +11 -0
- package/dist/base.js +31 -0
- package/dist/commands/alerts/notifications/read-all.js +3 -0
- package/dist/commands/alerts/preferences/show.js +2 -0
- package/dist/commands/ideas/convert.d.ts +2 -2
- package/dist/commands/ideas/convert.js +25 -8
- package/dist/commands/kb/create.js +29 -6
- package/dist/commands/kb/update.js +26 -5
- package/dist/commands/logout.js +3 -0
- package/dist/commands/org/show.js +2 -0
- package/dist/commands/personal/import.js +2 -2
- package/dist/commands/personal/run.js +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/secrets/import.js +2 -2
- package/dist/commands/seo/ignore.d.ts +12 -0
- package/dist/commands/seo/ignore.js +27 -0
- package/dist/commands/seo/list.d.ts +13 -0
- package/dist/commands/seo/list.js +52 -0
- package/dist/commands/seo/pages.d.ts +11 -0
- package/dist/commands/seo/pages.js +44 -0
- package/dist/commands/seo/promote.d.ts +9 -0
- package/dist/commands/seo/promote.js +22 -0
- package/dist/commands/seo/reopen.d.ts +9 -0
- package/dist/commands/seo/reopen.js +21 -0
- package/dist/commands/seo/rescan.d.ts +9 -0
- package/dist/commands/seo/rescan.js +21 -0
- package/dist/commands/seo/show.d.ts +9 -0
- package/dist/commands/seo/show.js +35 -0
- package/dist/commands/seo-sites/create.d.ts +14 -0
- package/dist/commands/seo-sites/create.js +43 -0
- package/dist/commands/seo-sites/delete.d.ts +9 -0
- package/dist/commands/seo-sites/delete.js +21 -0
- package/dist/commands/seo-sites/list.d.ts +10 -0
- package/dist/commands/seo-sites/list.js +38 -0
- package/dist/commands/seo-sites/update.d.ts +17 -0
- package/dist/commands/seo-sites/update.js +45 -0
- package/dist/commands/servers/ignored-containers.d.ts +15 -0
- package/dist/commands/servers/ignored-containers.js +51 -0
- package/dist/commands/tickets/comment.d.ts +14 -3
- package/dist/commands/tickets/comment.js +29 -1
- package/dist/commands/tickets/create.d.ts +6 -6
- package/dist/commands/tickets/create.js +21 -2
- package/dist/commands/tickets/report-show.d.ts +20 -0
- package/dist/commands/tickets/report-show.js +38 -0
- package/dist/commands/tickets/report-versions.d.ts +19 -0
- package/dist/commands/tickets/report-versions.js +40 -0
- package/dist/commands/tickets/report.d.ts +24 -0
- package/dist/commands/tickets/report.js +69 -0
- package/dist/commands/tickets/status.d.ts +1 -1
- package/dist/commands/tickets/status.js +14 -4
- package/dist/commands/tickets/update.d.ts +25 -6
- package/dist/commands/tickets/update.js +144 -9
- package/dist/commands/whoami.js +3 -0
- package/dist/errors/error-codes.d.ts +9 -0
- package/dist/errors/error-codes.js +14 -0
- package/dist/errors/input-error.d.ts +16 -0
- package/dist/errors/input-error.js +42 -0
- package/dist/lib/alert-events.d.ts +8 -1
- package/dist/lib/alert-events.js +37 -1
- package/dist/lib/limits.d.ts +59 -0
- package/dist/lib/limits.js +92 -0
- package/dist/lib/stdin.d.ts +1 -2
- package/dist/lib/stdin.js +1 -2
- package/dist/lib/subprocess.d.ts +1 -1
- package/dist/lib/subprocess.js +1 -1
- package/dist/lib/ticket-body.d.ts +22 -6
- package/dist/lib/ticket-body.js +71 -9
- package/dist/lib/ticket-lookup.d.ts +53 -0
- package/dist/lib/ticket-lookup.js +78 -0
- package/dist/lib/ticket-report.d.ts +7 -0
- package/dist/lib/ticket-report.js +14 -0
- package/oclif.manifest.json +3596 -2674
- package/opencli.json +698 -54
- package/package.json +8 -8
|
@@ -0,0 +1,44 @@
|
|
|
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 SeoPages extends base_1.BaseCommand {
|
|
7
|
+
static description = 'List the pages the SEO check has visited, with their state';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> seo pages',
|
|
10
|
+
'<%= config.bin %> seo pages --project acme-site',
|
|
11
|
+
'<%= config.bin %> seo pages --url /prezzi --json',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
project: core_1.Flags.string({ char: 'p', description: 'Filter by project id (UUID) or key' }),
|
|
15
|
+
url: core_1.Flags.string({ description: 'Filter by URL (partial match)' }),
|
|
16
|
+
...base_1.pageFlag,
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(SeoPages);
|
|
20
|
+
const query = new URLSearchParams({ page: String(flags.page) });
|
|
21
|
+
if (flags.project)
|
|
22
|
+
query.set('project_id', await this.resolveProjectId(flags.project));
|
|
23
|
+
if (flags.url)
|
|
24
|
+
query.set('url', flags.url);
|
|
25
|
+
const res = await this.api.get(`/cli/v1/seo/pages?${query.toString()}`);
|
|
26
|
+
const pages = res.data ?? [];
|
|
27
|
+
if (!this.jsonEnabled()) {
|
|
28
|
+
this.log((0, output_1.renderTable)(['PROJECT', 'PATH', 'STATUS', 'TITLE', 'H1', 'WORDS'], pages.map((page) => {
|
|
29
|
+
const headings = Array.isArray(page.h1s) ? page.h1s : [];
|
|
30
|
+
return [
|
|
31
|
+
String(page.project?.key ?? ''),
|
|
32
|
+
String(page.path ?? page.url ?? ''),
|
|
33
|
+
(0, output_1.statusCell)(page.status_code),
|
|
34
|
+
String(page.title ?? '—'),
|
|
35
|
+
// No h1 is the finding a green score never reports: it belongs in the column, not hidden.
|
|
36
|
+
headings.length === 0 ? 'missing' : String(headings.length),
|
|
37
|
+
String(page.word_count ?? 0),
|
|
38
|
+
];
|
|
39
|
+
})));
|
|
40
|
+
}
|
|
41
|
+
return res;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.default = SeoPages;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoPromote 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,22 @@
|
|
|
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 SeoPromote extends base_1.BaseCommand {
|
|
7
|
+
static description = 'Open a ticket from an SEO finding, evidence included';
|
|
8
|
+
static examples = ['<%= config.bin %> seo promote <issue-id>'];
|
|
9
|
+
static args = {
|
|
10
|
+
id: core_1.Args.string({ description: 'SEO finding id', required: true }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args } = await this.parse(SeoPromote);
|
|
14
|
+
// Idempotent server-side: promoting twice returns R422-SEO-001, never a second ticket.
|
|
15
|
+
const res = await this.api.put(`/cli/v1/seo/${args.id}/promotion`);
|
|
16
|
+
if (!this.jsonEnabled()) {
|
|
17
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
18
|
+
}
|
|
19
|
+
return res;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.default = SeoPromote;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoReopen 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,21 @@
|
|
|
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 SeoReopen extends base_1.BaseCommand {
|
|
6
|
+
static description = 'Reopen an ignored SEO finding: you changed your mind';
|
|
7
|
+
static examples = ['<%= config.bin %> seo reopen <issue-id>'];
|
|
8
|
+
static args = {
|
|
9
|
+
id: core_1.Args.string({ description: 'SEO finding id', required: true }),
|
|
10
|
+
};
|
|
11
|
+
async run() {
|
|
12
|
+
const { args } = await this.parse(SeoReopen);
|
|
13
|
+
// Reopening is deleting the ignore, not a verb of its own — hence DELETE on the same resource.
|
|
14
|
+
const res = await this.api.delete(`/cli/v1/seo/${args.id}/ignore`);
|
|
15
|
+
if (!this.jsonEnabled()) {
|
|
16
|
+
this.log(`SEO finding ${args.id} reopened.`);
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = SeoReopen;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoRescan extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
site: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<unknown>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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 SeoRescan extends base_1.BaseCommand {
|
|
6
|
+
static description = 'Check a site now, without waiting for the next scheduled run';
|
|
7
|
+
static examples = ['<%= config.bin %> seo rescan --site <site-id>'];
|
|
8
|
+
// Required: a check always targets one site (a project can declare one per environment).
|
|
9
|
+
static flags = {
|
|
10
|
+
site: core_1.Flags.string({ description: 'Site id (from `seo-sites list`)', required: true }),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(SeoRescan);
|
|
14
|
+
const res = await this.api.post('/cli/v1/seo/rescan', { site_id: flags.site });
|
|
15
|
+
if (!this.jsonEnabled()) {
|
|
16
|
+
this.log(`Check queued for site ${flags.site}.`);
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = SeoRescan;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoShow 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,35 @@
|
|
|
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
|
+
/** A nested object of the payload, or `{}` when the field is absent. */
|
|
7
|
+
function nested(data, key) {
|
|
8
|
+
const value = data[key];
|
|
9
|
+
return value && typeof value === 'object' ? value : {};
|
|
10
|
+
}
|
|
11
|
+
class SeoShow extends base_1.BaseCommand {
|
|
12
|
+
static description = 'Show a single SEO finding, with the evidence behind it';
|
|
13
|
+
static examples = ['<%= config.bin %> seo show <issue-id>'];
|
|
14
|
+
static args = {
|
|
15
|
+
id: core_1.Args.string({ description: 'SEO finding id', required: true }),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { args } = await this.parse(SeoShow);
|
|
19
|
+
const res = await this.api.get(`/cli/v1/seo/${args.id}`);
|
|
20
|
+
if (!this.jsonEnabled()) {
|
|
21
|
+
const data = res.data ?? {};
|
|
22
|
+
// Evidence in a block of its own: it is the reason the finding exists, and burying it among
|
|
23
|
+
// the metadata means reopening the browser to see what was actually found.
|
|
24
|
+
this.log((0, output_1.renderRecord)({ ...data, evidence: undefined }));
|
|
25
|
+
this.log((0, output_1.section)('Evidence', '🔎'));
|
|
26
|
+
this.log((0, output_1.renderRecord)(nested(data, 'evidence')));
|
|
27
|
+
this.log((0, output_1.section)('Site', '🌐'));
|
|
28
|
+
this.log((0, output_1.renderRecord)(nested(data, 'site')));
|
|
29
|
+
this.log((0, output_1.section)('Project', '📦'));
|
|
30
|
+
this.log((0, output_1.renderRecord)(nested(data, 'project')));
|
|
31
|
+
}
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.default = SeoShow;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoSitesCreate extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
url: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
environment: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
frequency: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
'max-pages': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
'no-sitemap': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<unknown>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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 SeoSitesCreate extends base_1.BaseCommand {
|
|
7
|
+
static description = 'Declare a site to keep an eye on (one per project and environment)';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> seo-sites create --project acme-site --url https://www.acme.example --environment <env-id>',
|
|
10
|
+
'<%= config.bin %> seo-sites create -p acme-site --url https://www.acme.example --environment <env-id> --frequency weekly --max-pages 50',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
...base_1.projectFlag,
|
|
14
|
+
url: core_1.Flags.string({ description: 'Public address the visit starts from', required: true }),
|
|
15
|
+
environment: core_1.Flags.string({ description: 'Environment id (must be declared by the project)', required: true }),
|
|
16
|
+
frequency: core_1.Flags.string({ description: 'How often to check it: daily (default) or weekly', options: ['daily', 'weekly'] }),
|
|
17
|
+
'max-pages': core_1.Flags.integer({ description: 'How many pages to visit at most each run' }),
|
|
18
|
+
'no-sitemap': core_1.Flags.boolean({ description: "Don't read the sitemap: start from the home page and follow links only" }),
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(SeoSitesCreate);
|
|
22
|
+
const projectId = await this.resolveProjectId(flags.project);
|
|
23
|
+
const body = {
|
|
24
|
+
project_id: projectId,
|
|
25
|
+
environment_id: flags.environment,
|
|
26
|
+
base_url: flags.url,
|
|
27
|
+
};
|
|
28
|
+
if (flags.frequency !== undefined)
|
|
29
|
+
body.frequency = flags.frequency;
|
|
30
|
+
if (flags['max-pages'] !== undefined)
|
|
31
|
+
body.max_pages = flags['max-pages'];
|
|
32
|
+
if (flags['no-sitemap'])
|
|
33
|
+
body.follow_sitemap = false;
|
|
34
|
+
const res = await this.api.post('/cli/v1/seo_sites', body);
|
|
35
|
+
if (!this.jsonEnabled()) {
|
|
36
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
37
|
+
// The first run starts on creation: without saying so, an empty list looks like a failure.
|
|
38
|
+
this.log('First check queued.');
|
|
39
|
+
}
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.default = SeoSitesCreate;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoSitesDelete 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,21 @@
|
|
|
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 SeoSitesDelete extends base_1.BaseCommand {
|
|
6
|
+
static description = 'Stop watching a site and remove its pages and findings';
|
|
7
|
+
static examples = ['<%= config.bin %> seo-sites delete <site-id>'];
|
|
8
|
+
static args = {
|
|
9
|
+
id: core_1.Args.string({ description: 'Site id', required: true }),
|
|
10
|
+
};
|
|
11
|
+
async run() {
|
|
12
|
+
const { args } = await this.parse(SeoSitesDelete);
|
|
13
|
+
const res = await this.api.delete(`/cli/v1/seo_sites/${args.id}`);
|
|
14
|
+
if (!this.jsonEnabled()) {
|
|
15
|
+
// Pages and findings go with it: to keep the history, pause the site instead (`update --disable`).
|
|
16
|
+
this.log(`Site ${args.id} removed, with its pages and findings.`);
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.default = SeoSitesDelete;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoSitesList extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<unknown>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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 SeoSitesList extends base_1.BaseCommand {
|
|
7
|
+
static description = 'List the sites under SEO check';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> seo-sites list',
|
|
10
|
+
'<%= config.bin %> seo-sites list --project acme-site --json',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
project: core_1.Flags.string({ char: 'p', description: 'Filter by project id (UUID) or key' }),
|
|
14
|
+
...base_1.pageFlag,
|
|
15
|
+
};
|
|
16
|
+
async run() {
|
|
17
|
+
const { flags } = await this.parse(SeoSitesList);
|
|
18
|
+
const query = new URLSearchParams({ page: String(flags.page) });
|
|
19
|
+
if (flags.project)
|
|
20
|
+
query.set('project_id', await this.resolveProjectId(flags.project));
|
|
21
|
+
const res = await this.api.get(`/cli/v1/seo_sites?${query.toString()}`);
|
|
22
|
+
const sites = res.data ?? [];
|
|
23
|
+
if (!this.jsonEnabled()) {
|
|
24
|
+
this.log((0, output_1.renderTable)(['ID', 'PROJECT', 'ENV', 'SITE', 'EVERY', 'OPEN', 'LAST CHECK'], sites.map((site) => [
|
|
25
|
+
String(site.id ?? ''),
|
|
26
|
+
String(site.project?.key ?? ''),
|
|
27
|
+
String(site.environment?.code ?? ''),
|
|
28
|
+
String(site.base_url ?? ''),
|
|
29
|
+
String(site.frequency ?? ''),
|
|
30
|
+
String(site.open_issues_count ?? 0),
|
|
31
|
+
// A failed run must never look like a successful one: the reason wins over the date.
|
|
32
|
+
site.last_error ? (0, output_1.statusCell)(`failed: ${site.last_error}`) : String(site.last_audited_at ?? 'never'),
|
|
33
|
+
])));
|
|
34
|
+
}
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.default = SeoSitesList;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class SeoSitesUpdate 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
|
+
static flags: {
|
|
9
|
+
url: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
frequency: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
'max-pages': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
sitemap: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
enable: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
disable: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<unknown>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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 SeoSitesUpdate extends base_1.BaseCommand {
|
|
7
|
+
static description = 'Change how a site is checked (project and environment are its identity: they never change)';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> seo-sites update <site-id> --frequency weekly',
|
|
10
|
+
'<%= config.bin %> seo-sites update <site-id> --max-pages 200 --disable',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ description: 'Site id', required: true }),
|
|
14
|
+
};
|
|
15
|
+
static flags = {
|
|
16
|
+
url: core_1.Flags.string({ description: 'Public address the visit starts from' }),
|
|
17
|
+
frequency: core_1.Flags.string({ description: 'How often to check it: daily or weekly', options: ['daily', 'weekly'] }),
|
|
18
|
+
'max-pages': core_1.Flags.integer({ description: 'How many pages to visit at most each run' }),
|
|
19
|
+
sitemap: core_1.Flags.boolean({ description: 'Read the sitemap', allowNo: true }),
|
|
20
|
+
enable: core_1.Flags.boolean({ description: 'Resume the checks', exclusive: ['disable'] }),
|
|
21
|
+
disable: core_1.Flags.boolean({ description: 'Pause the checks (the site and its findings stay)', exclusive: ['enable'] }),
|
|
22
|
+
};
|
|
23
|
+
async run() {
|
|
24
|
+
const { args, flags } = await this.parse(SeoSitesUpdate);
|
|
25
|
+
const body = {};
|
|
26
|
+
if (flags.url !== undefined)
|
|
27
|
+
body.base_url = flags.url;
|
|
28
|
+
if (flags.frequency !== undefined)
|
|
29
|
+
body.frequency = flags.frequency;
|
|
30
|
+
if (flags['max-pages'] !== undefined)
|
|
31
|
+
body.max_pages = flags['max-pages'];
|
|
32
|
+
if (flags.sitemap !== undefined)
|
|
33
|
+
body.follow_sitemap = flags.sitemap;
|
|
34
|
+
if (flags.enable)
|
|
35
|
+
body.enabled = true;
|
|
36
|
+
if (flags.disable)
|
|
37
|
+
body.enabled = false;
|
|
38
|
+
const res = await this.api.put(`/cli/v1/seo_sites/${args.id}`, body);
|
|
39
|
+
if (!this.jsonEnabled()) {
|
|
40
|
+
this.log((0, output_1.renderRecord)(res.data ?? {}));
|
|
41
|
+
}
|
|
42
|
+
return res;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.default = SeoSitesUpdate;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base';
|
|
2
|
+
export default class ServersIgnoredContainers 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
|
+
clear: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
set: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
run(): Promise<unknown>;
|
|
13
|
+
private render;
|
|
14
|
+
private split;
|
|
15
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../../base");
|
|
5
|
+
// CYRA-519 — i nomi che su una macchina NON sono servizi (container di compilazione e test, che
|
|
6
|
+
// nascono e muoiono a ogni lavorazione) si scrivevano solo dalla pagina web: chi lavora da terminale
|
|
7
|
+
// doveva aprire il browser per una lista di due righe.
|
|
8
|
+
//
|
|
9
|
+
// Senza flag il comando legge; --set sostituisce l'elenco, --clear lo svuota. Sostituisce e non
|
|
10
|
+
// accoda: un elenco di due nomi si riscrive per intero, e "aggiungi" andrebbe letto prima.
|
|
11
|
+
class ServersIgnoredContainers extends base_1.BaseCommand {
|
|
12
|
+
static args = {
|
|
13
|
+
id: core_1.Args.string({ description: 'Server id', required: true }),
|
|
14
|
+
};
|
|
15
|
+
static description = 'Read or set the container names this server does not treat as services';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> servers ignored-containers <server-id>',
|
|
18
|
+
'<%= config.bin %> servers ignored-containers <server-id> --set buildkit,runner',
|
|
19
|
+
'<%= config.bin %> servers ignored-containers <server-id> --clear',
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
clear: core_1.Flags.boolean({ description: 'Remove every ignored container name', exclusive: ['set'] }),
|
|
23
|
+
set: core_1.Flags.string({ description: 'Comma-separated names to ignore (replaces the list)' }),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { args, flags } = await this.parse(ServersIgnoredContainers);
|
|
27
|
+
const path = `/cli/v1/servers/${encodeURIComponent(args.id)}`;
|
|
28
|
+
if (flags.set === undefined && !flags.clear) {
|
|
29
|
+
const res = await this.api.get(path);
|
|
30
|
+
if (!this.jsonEnabled())
|
|
31
|
+
this.log(this.render(res.data));
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
const patterns = flags.clear ? [] : this.split(flags.set ?? '');
|
|
35
|
+
const res = await this.api.put(path, { ignored_container_patterns: patterns });
|
|
36
|
+
if (!this.jsonEnabled())
|
|
37
|
+
this.log(this.render(res.data));
|
|
38
|
+
return res;
|
|
39
|
+
}
|
|
40
|
+
render(data) {
|
|
41
|
+
const patterns = data?.ignored_container_patterns ?? [];
|
|
42
|
+
return patterns.length === 0 ? 'No ignored container names' : patterns.join('\n');
|
|
43
|
+
}
|
|
44
|
+
split(value) {
|
|
45
|
+
return value
|
|
46
|
+
.split(',')
|
|
47
|
+
.map((name) => name.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.default = ServersIgnoredContainers;
|
|
@@ -1,13 +1,24 @@
|
|
|
1
|
+
import { type Interfaces } from '@oclif/core';
|
|
1
2
|
import { BaseCommand } from '../../base';
|
|
2
3
|
export default class TicketsComment extends BaseCommand {
|
|
3
4
|
static args: {
|
|
4
|
-
id:
|
|
5
|
+
id: Interfaces.Arg<string, Record<string, unknown>>;
|
|
5
6
|
};
|
|
6
7
|
static description: string;
|
|
7
8
|
static examples: string[];
|
|
8
9
|
static flags: {
|
|
9
|
-
body:
|
|
10
|
-
project:
|
|
10
|
+
body: Interfaces.OptionFlag<string, Interfaces.CustomOptions>;
|
|
11
|
+
project: Interfaces.OptionFlag<string, Interfaces.CustomOptions>;
|
|
11
12
|
};
|
|
13
|
+
/** Ticket e progetto come li ha scritti l'utente: il suggerimento su R422 li ripete tali e quali. */
|
|
14
|
+
private target?;
|
|
12
15
|
run(): Promise<unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Il commento troppo lungo va scritto altrove, e il messaggio del server dice solo «use the work
|
|
18
|
+
* report»: prima di CYCL-31 quel campo dal terminale non esisteva e l'unica uscita era aprire il
|
|
19
|
+
* sito. Ora esiste, quindi al messaggio si accoda il comando pronto da eseguire, con il ticket e
|
|
20
|
+
* il progetto già dentro. Codice e testo del server restano intatti — anche in `--json`, dove il
|
|
21
|
+
* suggerimento viaggia in coda al `message` e il `code` resta `R422-COMMENT-002`.
|
|
22
|
+
*/
|
|
23
|
+
protected catch(error: Interfaces.CommandError): Promise<unknown>;
|
|
13
24
|
}
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const base_1 = require("../../base");
|
|
5
|
+
const api_1 = require("../../lib/api");
|
|
6
|
+
const limits_1 = require("../../lib/limits");
|
|
5
7
|
const output_1 = require("../../lib/output");
|
|
8
|
+
// Rifiuto del server per un commento oltre il limite: il messaggio rimanda «al work report». Il
|
|
9
|
+
// codice resta quello del backend, propagato tale e quale (vedi errors/error-codes.ts) — quello che
|
|
10
|
+
// si aggiunge è il comando che quel campo lo scrive davvero (CYCL-31).
|
|
11
|
+
const COMMENT_TOO_LONG = 'R422-COMMENT-002';
|
|
6
12
|
class TicketsComment extends base_1.BaseCommand {
|
|
7
13
|
static args = {
|
|
8
14
|
id: core_1.Args.string({ description: 'Ticket id', required: true }),
|
|
@@ -13,10 +19,18 @@ class TicketsComment extends base_1.BaseCommand {
|
|
|
13
19
|
];
|
|
14
20
|
static flags = {
|
|
15
21
|
...base_1.projectFlag,
|
|
16
|
-
|
|
22
|
+
// Il tetto è dichiarato qui e la misura resta al server: il suo rifiuto porta il comando pronto
|
|
23
|
+
// per scrivere altrove il testo che non ci sta (vedi `catch`), che un controllo locale toglierebbe.
|
|
24
|
+
body: core_1.Flags.string({
|
|
25
|
+
description: `Comment body (max ${limits_1.LENGTH_LIMITS.comment} characters — longer text goes in the work report)`,
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
17
28
|
};
|
|
29
|
+
/** Ticket e progetto come li ha scritti l'utente: il suggerimento su R422 li ripete tali e quali. */
|
|
30
|
+
target;
|
|
18
31
|
async run() {
|
|
19
32
|
const { args, flags } = await this.parse(TicketsComment);
|
|
33
|
+
this.target = { id: args.id, project: flags.project };
|
|
20
34
|
const projectId = await this.resolveProjectId(flags.project);
|
|
21
35
|
const res = await this.api.post(`/cli/v1/projects/${projectId}/tickets/${args.id}/comments`, { body: flags.body });
|
|
22
36
|
if (!this.jsonEnabled()) {
|
|
@@ -25,5 +39,19 @@ class TicketsComment extends base_1.BaseCommand {
|
|
|
25
39
|
}
|
|
26
40
|
return res;
|
|
27
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Il commento troppo lungo va scritto altrove, e il messaggio del server dice solo «use the work
|
|
44
|
+
* report»: prima di CYCL-31 quel campo dal terminale non esisteva e l'unica uscita era aprire il
|
|
45
|
+
* sito. Ora esiste, quindi al messaggio si accoda il comando pronto da eseguire, con il ticket e
|
|
46
|
+
* il progetto già dentro. Codice e testo del server restano intatti — anche in `--json`, dove il
|
|
47
|
+
* suggerimento viaggia in coda al `message` e il `code` resta `R422-COMMENT-002`.
|
|
48
|
+
*/
|
|
49
|
+
async catch(error) {
|
|
50
|
+
if (error instanceof api_1.ApiRequestError && error.code === COMMENT_TOO_LONG && this.target) {
|
|
51
|
+
const hint = `Write it with: ${this.config.bin} tickets report ${this.target.id} --project ${this.target.project} --body-file <file>`;
|
|
52
|
+
return super.catch(new api_1.ApiRequestError(error.status, error.code, `${error.message} ${hint}`, error.details));
|
|
53
|
+
}
|
|
54
|
+
return super.catch(error);
|
|
55
|
+
}
|
|
28
56
|
}
|
|
29
57
|
exports.default = TicketsComment;
|
|
@@ -3,6 +3,12 @@ export default class TicketsCreate extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
|
+
priority: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
'assignee-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
'milestone-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
'parent-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
'platform-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
6
12
|
description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
13
|
'technical-analysis': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
14
|
scenarios: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -13,12 +19,6 @@ export default class TicketsCreate extends BaseCommand {
|
|
|
13
19
|
condition: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
20
|
weight: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
21
|
'due-at': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
|
-
'status-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
-
'priority-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
|
-
'assignee-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
-
'milestone-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
-
'parent-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
-
'platform-id': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
22
|
title: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
23
|
kind: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
24
|
project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -2,26 +2,45 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const base_1 = require("../../base");
|
|
5
|
+
const limits_1 = require("../../lib/limits");
|
|
5
6
|
const output_1 = require("../../lib/output");
|
|
6
7
|
const ticket_body_1 = require("../../lib/ticket-body");
|
|
8
|
+
const ticket_lookup_1 = require("../../lib/ticket-lookup");
|
|
7
9
|
class TicketsCreate extends base_1.BaseCommand {
|
|
8
|
-
static description = 'Create a ticket';
|
|
10
|
+
static description = 'Create a ticket (status defaults to open, priority to medium; both take a code or a label)';
|
|
9
11
|
static examples = [
|
|
10
12
|
'<%= config.bin %> tickets create --project acme-api --title "Checkout fails"',
|
|
11
13
|
'<%= config.bin %> tickets create -p acme-api --title "Login bug" --kind bug --step-given "logged out" --step-when "I submit" --step-then "it 500s"',
|
|
12
14
|
'<%= config.bin %> tickets create -p acme-api --title "Checkout" --scenarios \'[{"title":"Happy","step_given":"logged in","step_when":"I pay","step_then":"order created"}]\' --condition "confirmation email is sent"',
|
|
15
|
+
'<%= config.bin %> tickets create -p acme-api --title "Payment retry" --status in_progress --priority High',
|
|
13
16
|
];
|
|
14
17
|
static flags = {
|
|
15
18
|
...base_1.projectFlag,
|
|
16
|
-
title: core_1.Flags.string({ description:
|
|
19
|
+
title: core_1.Flags.string({ description: `Ticket title (max ${limits_1.LENGTH_LIMITS.title} characters)`, required: true }),
|
|
17
20
|
kind: core_1.Flags.string({ description: 'Ticket kind (bug, story, task, epic)', default: 'bug', options: ['bug', 'story', 'task', 'epic'] }),
|
|
18
21
|
...ticket_body_1.ticketBodyFlags,
|
|
22
|
+
// Su un ticket che nasce, omettere stato e priorità non li lascia com'erano: applica i predefiniti.
|
|
23
|
+
...ticket_lookup_1.newTicketLookupFlags,
|
|
19
24
|
};
|
|
20
25
|
async run() {
|
|
21
26
|
const { flags } = await this.parse(TicketsCreate);
|
|
27
|
+
// Un ticket nuovo non ha niente da salvaguardare: il tetto vale pieno, e i campi troppo lunghi
|
|
28
|
+
// si leggono tutti in questo unico rifiuto invece che uno per tentativo (CYCL-32).
|
|
29
|
+
const tooLong = (0, limits_1.tooLongMessage)((0, ticket_body_1.ticketLengthChecks)(flags));
|
|
30
|
+
if (tooLong)
|
|
31
|
+
this.error(tooLong, { exit: 2 });
|
|
22
32
|
const projectId = await this.resolveProjectId(flags.project);
|
|
23
33
|
const body = { title: flags.title, kind: flags.kind };
|
|
24
34
|
(0, ticket_body_1.applyTicketBody)(body, flags);
|
|
35
|
+
// Stato e priorità il server li pretende ("Status must exist and Priority must exist") mentre
|
|
36
|
+
// l'aiuto li dava per opzionali: chi non li indica prende i predefiniti, chi li indica li scrive
|
|
37
|
+
// come li legge (`open`, `In Progress`) invece di andare a cercarne l'UUID (CYCL-34).
|
|
38
|
+
const [statusId, priorityId] = await Promise.all([
|
|
39
|
+
this.resolveTicketLookupId(ticket_lookup_1.TICKET_STATUS, flags.status),
|
|
40
|
+
this.resolveTicketLookupId(ticket_lookup_1.TICKET_PRIORITY, flags.priority),
|
|
41
|
+
]);
|
|
42
|
+
body.status_id = statusId;
|
|
43
|
+
body.priority_id = priorityId;
|
|
25
44
|
const res = await this.api.post(`/cli/v1/projects/${projectId}/tickets`, body);
|
|
26
45
|
if (!this.jsonEnabled()) {
|
|
27
46
|
this.log('Ticket created:');
|