@rsaglobaltech/specgate 0.21.0 → 0.22.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/CHANGELOG.md +93 -0
- package/dist/packages/core/src/domain/BriefCards.js +320 -0
- package/dist/packages/core/src/domain/BriefCards.js.map +1 -0
- package/dist/packages/core/src/domain/DraftChecklist.js +5 -1
- package/dist/packages/core/src/domain/DraftChecklist.js.map +1 -1
- package/dist/packages/core/src/domain/QuotedText.js +5 -0
- package/dist/packages/core/src/domain/QuotedText.js.map +1 -1
- package/dist/packages/core/src/domain/TicketText.js +337 -0
- package/dist/packages/core/src/domain/TicketText.js.map +1 -0
- package/dist/packages/core/src/infrastructure/ChangeWorkspace.js +7 -3
- package/dist/packages/core/src/infrastructure/ChangeWorkspace.js.map +1 -1
- package/dist/packages/core/src/infrastructure/IdsAcrossBranches.js +48 -0
- package/dist/packages/core/src/infrastructure/IdsAcrossBranches.js.map +1 -0
- package/dist/scripts/agents/commands.js +3 -1
- package/dist/scripts/agents/commands.js.map +1 -1
- package/dist/scripts/agents/contract-file.js +44 -0
- package/dist/scripts/agents/contract-file.js.map +1 -1
- package/dist/scripts/alm/cli.js +14 -3
- package/dist/scripts/alm/cli.js.map +1 -1
- package/dist/scripts/alm/port.js +1 -1
- package/dist/scripts/alm/port.js.map +1 -1
- package/dist/scripts/alm/providers/http-client.js +46 -3
- package/dist/scripts/alm/providers/http-client.js.map +1 -1
- package/dist/scripts/alm/providers/jira.js +108 -32
- package/dist/scripts/alm/providers/jira.js.map +1 -1
- package/dist/scripts/cli/commands/alm/PrCommand.js +198 -0
- package/dist/scripts/cli/commands/alm/PrCommand.js.map +1 -0
- package/dist/scripts/cli/commands/change/ChangeCommand.js +32 -6
- package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
- package/dist/scripts/cli/commands/change/TicketCommand.js +657 -0
- package/dist/scripts/cli/commands/change/TicketCommand.js.map +1 -0
- package/dist/scripts/cli/commands/harness/RunCommand.js +4 -2
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/AdoptCommand.js +58 -0
- package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/UpdateCommand.js +4 -0
- package/dist/scripts/cli/commands/project/UpdateCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/CheckCommand.js +12 -0
- package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/DraftCommand.js +240 -27
- package/dist/scripts/cli/commands/spec/DraftCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/ExploreCommand.js +13 -13
- package/dist/scripts/cli/commands/spec/ExploreCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StatusCommand.js +18 -6
- package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
- package/dist/scripts/docs/journey-storyboard.js +33 -27
- package/dist/scripts/docs/journey-storyboard.js.map +1 -1
- package/dist/scripts/docs/nav.js +5 -0
- package/dist/scripts/docs/nav.js.map +1 -1
- package/dist/scripts/docs/record-journey.js +93 -30
- package/dist/scripts/docs/record-journey.js.map +1 -1
- package/dist/scripts/draft/brief-check.js +34 -0
- package/dist/scripts/draft/brief-check.js.map +1 -0
- package/dist/scripts/draft/brief-setup.js +182 -17
- package/dist/scripts/draft/brief-setup.js.map +1 -1
- package/dist/scripts/draft/drafting-guide.js +43 -1
- package/dist/scripts/draft/drafting-guide.js.map +1 -1
- package/dist/scripts/draft/ticket-guide.js +129 -0
- package/dist/scripts/draft/ticket-guide.js.map +1 -0
- package/dist/scripts/lib/ticket-keys.js +140 -0
- package/dist/scripts/lib/ticket-keys.js.map +1 -0
- package/dist/scripts/propose.js +8 -2
- package/dist/scripts/propose.js.map +1 -1
- package/dist/scripts/vcs/github-pr.js +88 -0
- package/dist/scripts/vcs/github-pr.js.map +1 -0
- package/package.json +1 -1
- package/templates/base/AGENTS.md.tpl +6 -5
package/dist/scripts/alm/cli.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* specgate alm pull [--label <name>] [--project-dir <dir>] [--dry-run]
|
|
10
10
|
* specgate alm link <REQ-id> <issue-key> [--project-dir <dir>]
|
|
11
11
|
* specgate alm status [--project-dir <dir>]
|
|
12
|
+
* specgate alm pr <change> [--no-push] [--no-check] [--project-dir <dir>]
|
|
12
13
|
*/
|
|
13
14
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
15
|
if (k2 === undefined) k2 = k;
|
|
@@ -49,6 +50,7 @@ const core_1 = require("./core");
|
|
|
49
50
|
const clients_1 = require("./clients");
|
|
50
51
|
const diagnostics_1 = require("../lib/diagnostics");
|
|
51
52
|
const PullCommand_1 = require("../cli/commands/alm/PullCommand");
|
|
53
|
+
const PrCommand_1 = require("../cli/commands/alm/PrCommand");
|
|
52
54
|
const ui = __importStar(require("../lib/ui"));
|
|
53
55
|
function logInfo(msg) {
|
|
54
56
|
process.stdout.write(ui.logLine(msg));
|
|
@@ -64,13 +66,16 @@ function usage() {
|
|
|
64
66
|
" specgate alm sync [--project-dir <dir>] [--dry-run]\n" +
|
|
65
67
|
" specgate alm link <REQ-id> <issue-key> [--project-dir <dir>]\n" +
|
|
66
68
|
" specgate alm status [--project-dir <dir>]\n" +
|
|
67
|
-
" specgate alm pull [--label <name>] [--project-dir <dir>] [--dry-run]\n
|
|
69
|
+
" specgate alm pull [--label <name>] [--project-dir <dir>] [--dry-run]\n" +
|
|
70
|
+
" specgate alm pr <change> [--no-push] [--no-check] [--project-dir <dir>]\n\n" +
|
|
68
71
|
"sync Creates an ALM issue for every unlinked REQ, closes issues whose REQ\n" +
|
|
69
72
|
" is Implemented, and reports drift (issue done, REQ still open).\n" +
|
|
70
73
|
"link Manually map an existing issue to a requirement.\n" +
|
|
71
74
|
"status Print the current REQ ↔ issue mapping without network access.\n" +
|
|
72
75
|
"pull Open a change per labelled issue. The scenario is left unwritten:\n" +
|
|
73
|
-
" a ticket carries no acceptance criterion, so that part is yours.\n
|
|
76
|
+
" a ticket carries no acceptance criterion, so that part is yours.\n" +
|
|
77
|
+
"pr Push the ticket's branch and open, or update, its one pull request\n" +
|
|
78
|
+
" on GitHub (pr: in alm.config.yaml). Never merges.\n\n" +
|
|
74
79
|
"Configuration: alm.config.yaml at the project root (provider, base_url,\n" +
|
|
75
80
|
"project_key, token_env). Tokens come from the environment, never the file.\n");
|
|
76
81
|
}
|
|
@@ -174,6 +179,12 @@ async function main() {
|
|
|
174
179
|
process.exit(1);
|
|
175
180
|
}
|
|
176
181
|
}
|
|
182
|
+
if (sub === "pr") {
|
|
183
|
+
// Outside `scripts/alm/` too: a pull request belongs to the repository,
|
|
184
|
+
// not to the board (ADR-0039 §4).
|
|
185
|
+
await (0, PrCommand_1.main)(argv.slice(1));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
177
188
|
if (sub === "pull") {
|
|
178
189
|
// Implemented outside `scripts/alm/` on purpose: ADR-0021 forbids this
|
|
179
190
|
// subsystem from writing the spec tree, and a pull has to produce files.
|
|
@@ -181,7 +192,7 @@ async function main() {
|
|
|
181
192
|
await (0, PullCommand_1.main)(argv.slice(1));
|
|
182
193
|
return;
|
|
183
194
|
}
|
|
184
|
-
logError(`Unknown alm sub-command: ${sub || "(none)"}. Expected: sync, link, status, pull`);
|
|
195
|
+
logError(`Unknown alm sub-command: ${sub || "(none)"}. Expected: sync, link, status, pull, pr`);
|
|
185
196
|
usage();
|
|
186
197
|
process.exit(2);
|
|
187
198
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../scripts/alm/cli.ts"],"names":[],"mappings":";;AACA
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../scripts/alm/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/scripts/alm/port.js
CHANGED
|
@@ -50,7 +50,7 @@ exports.CAPABILITY_METHOD = {
|
|
|
50
50
|
listIssues: "listIssues",
|
|
51
51
|
};
|
|
52
52
|
/** Config keys the core itself reads, whatever the provider. */
|
|
53
|
-
exports.CORE_CONFIG_KEYS = ["alm_version", "provider"];
|
|
53
|
+
exports.CORE_CONFIG_KEYS = ["alm_version", "provider", "pr"];
|
|
54
54
|
/**
|
|
55
55
|
* Check a provider declaration is well formed.
|
|
56
56
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port.js","sourceRoot":"","sources":["../../../scripts/alm/port.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;;;
|
|
1
|
+
{"version":3,"file":"port.js","sourceRoot":"","sources":["../../../scripts/alm/port.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.HttpAlmClient = void 0;
|
|
4
|
+
exports.assertSecureUrl = assertSecureUrl;
|
|
4
5
|
/**
|
|
5
6
|
* What every HTTP-backed ALM connector shares: a credential read from the
|
|
6
7
|
* environment, a base URL that does not grow a doubled slash, and a refusal to
|
|
@@ -17,6 +18,7 @@ class HttpAlmClient {
|
|
|
17
18
|
// `https://host//rest/api` against a real server, so it is stripped once,
|
|
18
19
|
// here, rather than in each provider's URL building.
|
|
19
20
|
this.baseUrl = String(cfg.base_url ?? "").replace(/\/$/, "");
|
|
21
|
+
assertSecureUrl(this.baseUrl, "base_url");
|
|
20
22
|
this.fetchImpl = fetchImpl ?? globalThis.fetch;
|
|
21
23
|
}
|
|
22
24
|
/** Read a credential, naming the variable and how to set it when it is absent. */
|
|
@@ -30,13 +32,34 @@ class HttpAlmClient {
|
|
|
30
32
|
}
|
|
31
33
|
/** Perform a request and fail loudly, with the server's own words, when it is refused. */
|
|
32
34
|
async request(path, what, init) {
|
|
33
|
-
const res = await this.fetchImpl(`${this.
|
|
34
|
-
|
|
35
|
+
const res = await this.fetchImpl(`${await this.apiBase()}${path}`, init);
|
|
36
|
+
const status = this.refusedAs(res) ?? (res.ok ? undefined : res.status);
|
|
37
|
+
if (status !== undefined) {
|
|
35
38
|
const body = await res.text().catch(() => "");
|
|
36
|
-
|
|
39
|
+
const fix = this.fixFor(status);
|
|
40
|
+
throw new Error(`${what} failed: HTTP ${status} ${body.slice(0, 300)}` + (fix ? `\nFix: ${fix}` : ""));
|
|
37
41
|
}
|
|
38
42
|
return res;
|
|
39
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Where API requests go. The site itself, unless a provider's API lives
|
|
46
|
+
* elsewhere — Jira's scoped tokens only work through Atlassian's gateway.
|
|
47
|
+
*/
|
|
48
|
+
async apiBase() {
|
|
49
|
+
return this.baseUrl;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A response that says yes but means no. Some servers answer a refused
|
|
53
|
+
* credential as an anonymous request — 200 and an empty result — and say
|
|
54
|
+
* so only in a header; a provider maps that to the status it means.
|
|
55
|
+
*/
|
|
56
|
+
refusedAs(_res) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/** A provider's own advice for a refused request; none by default. */
|
|
60
|
+
fixFor(_status) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
40
63
|
/** The same, returning the parsed body, which is what every caller wants. */
|
|
41
64
|
async requestJson(path, what, init) {
|
|
42
65
|
const res = await this.request(path, what, init);
|
|
@@ -44,4 +67,24 @@ class HttpAlmClient {
|
|
|
44
67
|
}
|
|
45
68
|
}
|
|
46
69
|
exports.HttpAlmClient = HttpAlmClient;
|
|
70
|
+
/**
|
|
71
|
+
* A URL a credential is sent to. `alm.config.yaml` is committed, so a change
|
|
72
|
+
* to it is a change to where the token goes: never in clear text. Only a
|
|
73
|
+
* server on this machine may be plain http, for local tests and proxies.
|
|
74
|
+
*/
|
|
75
|
+
function assertSecureUrl(url, key) {
|
|
76
|
+
if (!url)
|
|
77
|
+
return;
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = new URL(url);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
throw new Error(`${key} '${url}' is not a URL.\nFix: write it as https://host.`);
|
|
84
|
+
}
|
|
85
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(parsed.hostname);
|
|
86
|
+
if (parsed.protocol !== "https:" && !(local && parsed.protocol === "http:")) {
|
|
87
|
+
throw new Error(`${key} '${url}' is not https: the token would travel in clear text.\nFix: use the https:// address.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
47
90
|
//# sourceMappingURL=http-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../../../scripts/alm/providers/http-client.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../../../scripts/alm/providers/http-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.jiraProvider = void 0;
|
|
3
|
+
exports.jiraProvider = exports.SEARCH_LIMIT = void 0;
|
|
4
4
|
const http_client_1 = require("./http-client");
|
|
5
|
+
const TicketText_1 = require("../../../packages/core/src/domain/TicketText");
|
|
6
|
+
/** Issues per page, the most `/search/jql` returns with these fields. */
|
|
7
|
+
const PAGE_SIZE = 100;
|
|
8
|
+
/** Where a search stops: a pull of more than this is a query to narrow, not a page to read. */
|
|
9
|
+
exports.SEARCH_LIMIT = 1000;
|
|
10
|
+
/** Atlassian's API gateway, the only door a token created with scopes opens. */
|
|
11
|
+
const GATEWAY = "https://api.atlassian.com/ex/jira";
|
|
5
12
|
/**
|
|
6
13
|
* Jira Cloud.
|
|
7
14
|
*
|
|
@@ -21,6 +28,9 @@ class JiraClient extends http_client_1.HttpAlmClient {
|
|
|
21
28
|
headers;
|
|
22
29
|
projectKey;
|
|
23
30
|
issueType;
|
|
31
|
+
cloudId;
|
|
32
|
+
acceptanceField;
|
|
33
|
+
gateway;
|
|
24
34
|
constructor(cfg, fetchImpl) {
|
|
25
35
|
super(cfg, fetchImpl);
|
|
26
36
|
const token = http_client_1.HttpAlmClient.requireEnv(cfg.token_env);
|
|
@@ -33,6 +43,49 @@ class JiraClient extends http_client_1.HttpAlmClient {
|
|
|
33
43
|
};
|
|
34
44
|
this.projectKey = String(cfg.project_key ?? "");
|
|
35
45
|
this.issueType = cfg.issue_type ?? "Task";
|
|
46
|
+
this.cloudId = cfg.cloud_id ? String(cfg.cloud_id).trim() : undefined;
|
|
47
|
+
this.acceptanceField = cfg.acceptance_field ? String(cfg.acceptance_field) : undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The site, or — with `cloud_id` — the gateway. `auto` asks the site for
|
|
51
|
+
* its id once, without credentials: `tenant_info` is public, and a token
|
|
52
|
+
* has no business travelling to an endpoint that does not need it.
|
|
53
|
+
*/
|
|
54
|
+
apiBase() {
|
|
55
|
+
if (!this.cloudId)
|
|
56
|
+
return Promise.resolve(this.baseUrl);
|
|
57
|
+
this.gateway ??= this.resolveCloudId().then((id) => `${GATEWAY}/${id}`);
|
|
58
|
+
return this.gateway;
|
|
59
|
+
}
|
|
60
|
+
async resolveCloudId() {
|
|
61
|
+
if (this.cloudId !== "auto")
|
|
62
|
+
return this.cloudId;
|
|
63
|
+
const res = await this.fetchImpl(`${this.baseUrl}/_edge/tenant_info`, {
|
|
64
|
+
headers: { Accept: "application/json" },
|
|
65
|
+
});
|
|
66
|
+
const body = res.ok ? (await res.json()) : {};
|
|
67
|
+
if (typeof body.cloudId !== "string" || !body.cloudId) {
|
|
68
|
+
throw new Error(`Jira: could not read the site id from ${this.baseUrl}/_edge/tenant_info (HTTP ${res.status}).\n` +
|
|
69
|
+
"Fix: check base_url, or set cloud_id to the site id itself.");
|
|
70
|
+
}
|
|
71
|
+
return body.cloudId;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Jira answers a credential it rejects as an anonymous request: a search
|
|
75
|
+
* returns 200 and no issues, and only `X-Seraph-LoginReason` tells. Read as
|
|
76
|
+
* a 401, so a scoped token sent to the site is not "nothing found".
|
|
77
|
+
*/
|
|
78
|
+
refusedAs(res) {
|
|
79
|
+
return res.headers?.get?.("x-seraph-loginreason") === "AUTHENTICATED_FAILED" ? 401 : undefined;
|
|
80
|
+
}
|
|
81
|
+
fixFor(status) {
|
|
82
|
+
if (status === 401 && !this.cloudId) {
|
|
83
|
+
return ("a token created with scopes only works through Atlassian's gateway — " +
|
|
84
|
+
"set `cloud_id: auto` in alm.config.yaml. Otherwise check the token and the account email.");
|
|
85
|
+
}
|
|
86
|
+
if (status === 401)
|
|
87
|
+
return "check the token, its scopes (read:jira-work) and the account email.";
|
|
88
|
+
return undefined;
|
|
36
89
|
}
|
|
37
90
|
async createIssue(reqId, title) {
|
|
38
91
|
const created = await this.requestJson("/rest/api/3/issue", `Jira create issue for ${reqId}`, {
|
|
@@ -50,11 +103,11 @@ class JiraClient extends http_client_1.HttpAlmClient {
|
|
|
50
103
|
return { key: created.key, url: `${this.baseUrl}/browse/${created.key}` };
|
|
51
104
|
}
|
|
52
105
|
async getIssueStatus(issueKey) {
|
|
53
|
-
const issue = await this.requestJson(`/rest/api/3/issue/${issueKey}?fields=status`, `Jira read ${issueKey}`, { headers: this.headers });
|
|
106
|
+
const issue = await this.requestJson(`/rest/api/3/issue/${issueKeyPath(issueKey)}?fields=status`, `Jira read ${issueKey}`, { headers: this.headers });
|
|
54
107
|
return issue.fields?.status?.statusCategory?.key === "done" ? "done" : "open";
|
|
55
108
|
}
|
|
56
109
|
/**
|
|
57
|
-
* Open issues carrying a label, via JQL.
|
|
110
|
+
* Open issues carrying a label, via JQL, page by page.
|
|
58
111
|
*
|
|
59
112
|
* The project is pinned as well as the label: a label is global in Jira, so
|
|
60
113
|
* searching on it alone reaches across every project the credential can see.
|
|
@@ -65,17 +118,48 @@ class JiraClient extends http_client_1.HttpAlmClient {
|
|
|
65
118
|
*/
|
|
66
119
|
async listIssues(label) {
|
|
67
120
|
const jql = `project = "${this.projectKey}" AND labels = "${label}" AND statusCategory != Done`;
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
const found = [];
|
|
122
|
+
let token;
|
|
123
|
+
// `/search/jql` pages with a token, not an offset: a page says whether it
|
|
124
|
+
// is the last, and the next one is asked for by the token the last gave.
|
|
125
|
+
do {
|
|
126
|
+
const result = await this.requestJson(`/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&fields=summary,description` +
|
|
127
|
+
`&maxResults=${PAGE_SIZE}` +
|
|
128
|
+
(token ? `&nextPageToken=${encodeURIComponent(token)}` : ""), `Jira search for issues labelled ${label}`, { headers: this.headers });
|
|
129
|
+
for (const issue of result.issues ?? []) {
|
|
130
|
+
found.push({
|
|
131
|
+
key: issue.key,
|
|
132
|
+
title: String(issue.fields?.summary ?? ""),
|
|
133
|
+
body: (0, TicketText_1.documentText)(issue.fields?.description),
|
|
134
|
+
url: `${this.baseUrl}/browse/${issue.key}`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
token = result.isLast ? undefined : result.nextPageToken;
|
|
138
|
+
if (token && found.length >= exports.SEARCH_LIMIT) {
|
|
139
|
+
process.stderr.write(`Jira: stopped after ${exports.SEARCH_LIMIT} issues labelled ${label}; narrow the label to pull the rest.\n`);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
} while (token);
|
|
143
|
+
return found;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* One issue, with the fields a ticket is read from. A key outside the
|
|
147
|
+
* configured project is refused before any request: the credential may see
|
|
148
|
+
* other projects, and this command has no business there.
|
|
149
|
+
*/
|
|
150
|
+
async readIssue(key) {
|
|
151
|
+
if (!new RegExp(`^${this.projectKey}-\\d+$`).test(key)) {
|
|
152
|
+
throw new Error(`Jira: ${key} is not an issue of project ${this.projectKey}.\n` +
|
|
153
|
+
"Fix: use a key of the project in alm.config.yaml, or export the issue and pass the file.");
|
|
154
|
+
}
|
|
155
|
+
const fields = ["summary", "issuetype", "parent", "labels", "description", "status"];
|
|
156
|
+
if (typeof this.acceptanceField === "string")
|
|
157
|
+
fields.push(this.acceptanceField);
|
|
158
|
+
return this.requestJson(`/rest/api/3/issue/${key}?fields=${fields.join(",")}`, `Jira read ${key}`, { headers: this.headers });
|
|
75
159
|
}
|
|
76
160
|
async closeIssue(issueKey) {
|
|
77
161
|
const transition = await this.findDoneTransition(issueKey);
|
|
78
|
-
await this.request(`/rest/api/3/issue/${issueKey}/transitions`, `Jira transition ${issueKey}`, {
|
|
162
|
+
await this.request(`/rest/api/3/issue/${issueKeyPath(issueKey)}/transitions`, `Jira transition ${issueKey}`, {
|
|
79
163
|
method: "POST",
|
|
80
164
|
headers: this.headers,
|
|
81
165
|
body: JSON.stringify({ transition: { id: transition.id } }),
|
|
@@ -83,7 +167,7 @@ class JiraClient extends http_client_1.HttpAlmClient {
|
|
|
83
167
|
}
|
|
84
168
|
/** The workflow decides which transition means done; there may be none. */
|
|
85
169
|
async findDoneTransition(issueKey) {
|
|
86
|
-
const { transitions } = await this.requestJson(`/rest/api/3/issue/${issueKey}/transitions`, `Jira list transitions for ${issueKey}`, { headers: this.headers });
|
|
170
|
+
const { transitions } = await this.requestJson(`/rest/api/3/issue/${issueKeyPath(issueKey)}/transitions`, `Jira list transitions for ${issueKey}`, { headers: this.headers });
|
|
87
171
|
const done = (transitions ?? []).find((t) => t.to?.statusCategory?.key === "done");
|
|
88
172
|
if (!done) {
|
|
89
173
|
throw new Error(`Jira: no transition to a done state available for ${issueKey}.`);
|
|
@@ -92,26 +176,15 @@ class JiraClient extends http_client_1.HttpAlmClient {
|
|
|
92
176
|
}
|
|
93
177
|
}
|
|
94
178
|
/**
|
|
95
|
-
* The
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* a shape this does not recognise yields "" rather than a stringified object.
|
|
179
|
+
* An issue key as a path segment. The map file and `alm link` hand keys in;
|
|
180
|
+
* one with a slash or a dot-dot would walk the authenticated request to
|
|
181
|
+
* another endpoint of the site.
|
|
99
182
|
*/
|
|
100
|
-
function
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const node = value;
|
|
106
|
-
if (typeof node.text === "string")
|
|
107
|
-
return node.text;
|
|
108
|
-
if (!Array.isArray(node.content))
|
|
109
|
-
return "";
|
|
110
|
-
return node.content
|
|
111
|
-
.map((child) => flattenAdf(child))
|
|
112
|
-
.filter(Boolean)
|
|
113
|
-
.join("\n")
|
|
114
|
-
.trim();
|
|
183
|
+
function issueKeyPath(key) {
|
|
184
|
+
if (!/^[A-Z][A-Z0-9_]+-\d+$/.test(key)) {
|
|
185
|
+
throw new Error(`Jira: '${key}' is not an issue key (PROJECT-123).`);
|
|
186
|
+
}
|
|
187
|
+
return encodeURIComponent(key);
|
|
115
188
|
}
|
|
116
189
|
exports.jiraProvider = {
|
|
117
190
|
id: "jira",
|
|
@@ -120,7 +193,10 @@ exports.jiraProvider = {
|
|
|
120
193
|
required: ["base_url", "project_key", "token_env"],
|
|
121
194
|
// Optional because $JIRA_USER is the conventional default. Naming the
|
|
122
195
|
// variable in the config is better, not compulsory.
|
|
123
|
-
|
|
196
|
+
// `cloud_id` routes a token created with scopes through Atlassian's
|
|
197
|
+
// gateway: a site id, or `auto` to read it from the site.
|
|
198
|
+
// `acceptance_field` and `epics` serve `propose --from-ticket` (ADR-0039).
|
|
199
|
+
optional: ["user_env", "issue_type", "cloud_id", "acceptance_field", "epics"],
|
|
124
200
|
},
|
|
125
201
|
capabilities: { create: true, readStatus: true, close: true, listIssues: true },
|
|
126
202
|
create: (cfg, fetchImpl) => new JiraClient(cfg, fetchImpl),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jira.js","sourceRoot":"","sources":["../../../../scripts/alm/providers/jira.ts"],"names":[],"mappings":";;;AAAA,+CAA8C;
|
|
1
|
+
{"version":3,"file":"jira.js","sourceRoot":"","sources":["../../../../scripts/alm/providers/jira.ts"],"names":[],"mappings":";;;AAAA,+CAA8C;AAC9C,6EAA4E;AAmC5E,yEAAyE;AACzE,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,+FAA+F;AAClF,QAAA,YAAY,GAAG,IAAI,CAAC;AACjC,gFAAgF;AAChF,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAW,SAAQ,2BAAa;IAC3B,YAAY,GAAoB;QACvC,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE,IAAI;QAChB,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,IAAI;KACjB,CAAC;IAEe,OAAO,CAAmC;IAC1C,UAAU,CAAS;IACnB,SAAS,CAAS;IAClB,OAAO,CAAqB;IAC5B,eAAe,CAAqB;IAC7C,OAAO,CAA8B;IAE7C,YAAY,GAAc,EAAE,SAAqB;QAC/C,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACtB,MAAM,KAAK,GAAG,2BAAa,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtD,qEAAqE;QACrE,8CAA8C;QAC9C,MAAM,IAAI,GAAG,2BAAa,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,IAAI,WAAW,CAAC,CAAC;QACnE,IAAI,CAAC,OAAO,GAAG;YACb,aAAa,EAAE,SAAS,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;YAC5E,cAAc,EAAE,kBAAkB;SACnC,CAAC;QACF,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,UAAU,IAAI,MAAM,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,CAAC;IAED;;;;OAIG;IACgB,OAAO;QACxB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,OAAQ,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,OAAO,oBAAoB,EAAE;YACpE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA2B,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACtD,MAAM,IAAI,KAAK,CACb,yCAAyC,IAAI,CAAC,OAAO,4BAA4B,GAAG,CAAC,MAAM,MAAM;gBAC/F,6DAA6D,CAChE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACgB,SAAS,CAAC,GAAiB;QAC5C,OAAO,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,KAAK,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACjG,CAAC;IAEkB,MAAM,CAAC,MAAc;QACtC,IAAI,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACpC,OAAO,CACL,uEAAuE;gBACvE,2FAA2F,CAC5F,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,KAAK,GAAG;YAChB,OAAO,qEAAqE,CAAC;QAC/E,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,KAAa;QAC5C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CACpC,mBAAmB,EACnB,yBAAyB,KAAK,EAAE,EAChC;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,MAAM,EAAE;oBACN,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE;oBACjC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE;oBACnC,OAAO,EAAE,GAAG,KAAK,MAAM,KAAK,EAAE;oBAC9B,MAAM,EAAE,CAAC,aAAa,EAAE,KAAK,CAAC;iBAC/B;aACF,CAAC;SACH,CACF,CAAC;QACF,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,WAAW,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,QAAgB;QACnC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAClC,qBAAqB,YAAY,CAAC,QAAQ,CAAC,gBAAgB,EAC3D,aAAa,QAAQ,EAAE,EACvB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAC1B,CAAC;QACF,OAAO,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAChF,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,UAAU,CAAC,KAAa;QAC5B,MAAM,GAAG,GAAG,cAAc,IAAI,CAAC,UAAU,mBAAmB,KAAK,8BAA8B,CAAC;QAChG,MAAM,KAAK,GAAmB,EAAE,CAAC;QACjC,IAAI,KAAyB,CAAC;QAC9B,0EAA0E;QAC1E,yEAAyE;QACzE,GAAG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CACnC,8BAA8B,kBAAkB,CAAC,GAAG,CAAC,6BAA6B;gBAChF,eAAe,SAAS,EAAE;gBAC1B,CAAC,KAAK,CAAC,CAAC,CAAC,kBAAkB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC9D,mCAAmC,KAAK,EAAE,EAC1C,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAC1B,CAAC;YACF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBACxC,KAAK,CAAC,IAAI,CAAC;oBACT,GAAG,EAAE,KAAK,CAAC,GAAG;oBACd,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,EAAE,CAAC;oBAC1C,IAAI,EAAE,IAAA,yBAAY,EAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC;oBAC7C,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,WAAW,KAAK,CAAC,GAAG,EAAE;iBAC3C,CAAC,CAAC;YACL,CAAC;YACD,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;YACzD,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,oBAAY,EAAE,CAAC;gBAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uBAAuB,oBAAY,oBAAoB,KAAK,wCAAwC,CACrG,CAAC;gBACF,MAAM;YACR,CAAC;QACH,CAAC,QAAQ,KAAK,EAAE;QAChB,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,UAAU,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,KAAK,CACb,SAAS,GAAG,+BAA+B,IAAI,CAAC,UAAU,KAAK;gBAC7D,0FAA0F,CAC7F,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;QACrF,IAAI,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ;YAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAChF,OAAO,IAAI,CAAC,WAAW,CACrB,qBAAqB,GAAG,WAAW,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EACrD,aAAa,GAAG,EAAE,EAClB,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAC1B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,QAAgB;QAC/B,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,OAAO,CAChB,qBAAqB,YAAY,CAAC,QAAQ,CAAC,cAAc,EACzD,mBAAmB,QAAQ,EAAE,EAC7B;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC;SAC5D,CACF,CAAC;IACJ,CAAC;IAED,2EAA2E;IACnE,KAAK,CAAC,kBAAkB,CAAC,QAAgB;QAC/C,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAC5C,qBAAqB,YAAY,CAAC,QAAQ,CAAC,cAAc,EACzD,6BAA6B,QAAQ,EAAE,EACvC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAC1B,CAAC;QACF,MAAM,IAAI,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,qDAAqD,QAAQ,GAAG,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,UAAU,GAAG,sCAAsC,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,kBAAkB,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAEY,QAAA,YAAY,GAAgB;IACvC,EAAE,EAAE,MAAM;IACV,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE;QACN,QAAQ,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,WAAW,CAAC;QAClD,sEAAsE;QACtE,oDAAoD;QACpD,oEAAoE;QACpE,0DAA0D;QAC1D,2EAA2E;QAC3E,QAAQ,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,CAAC;KAC9E;IACD,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;IAC/E,MAAM,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;CAC3D,CAAC;AAEF,kBAAe,oBAAY,CAAC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `specgate alm pr <change>` — push the ticket's branch and open, or update,
|
|
4
|
+
* its one pull request (ADR-0039, REQ-149).
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.main = main;
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const agent_1 = require("../../../lib/agent");
|
|
45
|
+
const diagnostics_1 = require("../../../lib/diagnostics");
|
|
46
|
+
const project_root_1 = require("../../../lib/project-root");
|
|
47
|
+
const ui = __importStar(require("../../../lib/ui"));
|
|
48
|
+
const core_1 = require("../../../alm/core");
|
|
49
|
+
const github_pr_1 = require("../../../vcs/github-pr");
|
|
50
|
+
const CHANGES = "docs/specs/changes";
|
|
51
|
+
const NULL_SHAPE = { pr: null };
|
|
52
|
+
function parse(argv) {
|
|
53
|
+
const o = {
|
|
54
|
+
change: "",
|
|
55
|
+
projectDir: ".",
|
|
56
|
+
push: true,
|
|
57
|
+
check: true,
|
|
58
|
+
json: (0, agent_1.wantsJson)(argv),
|
|
59
|
+
};
|
|
60
|
+
for (let i = 0; i < argv.length; i++) {
|
|
61
|
+
const a = argv[i];
|
|
62
|
+
if (a === "--project-dir")
|
|
63
|
+
o.projectDir = argv[++i] ?? ".";
|
|
64
|
+
else if (a === "--no-push")
|
|
65
|
+
o.push = false;
|
|
66
|
+
else if (a === "--no-check")
|
|
67
|
+
o.check = false;
|
|
68
|
+
else if (a === "--json")
|
|
69
|
+
continue;
|
|
70
|
+
else if (!a.startsWith("--") && !o.change)
|
|
71
|
+
o.change = a;
|
|
72
|
+
}
|
|
73
|
+
return o;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The change, open or archived. A ticket's change is archived on its branch,
|
|
77
|
+
* so the pull request carries the archive (REQ-159): the most recent
|
|
78
|
+
* `archive/<date>-<id>` when the change is no longer open.
|
|
79
|
+
*/
|
|
80
|
+
function changePath(projectDir, id) {
|
|
81
|
+
if (fs.existsSync(path.join(projectDir, CHANGES, id)))
|
|
82
|
+
return `${CHANGES}/${id}`;
|
|
83
|
+
const archive = path.join(projectDir, CHANGES, "archive");
|
|
84
|
+
if (!fs.existsSync(archive))
|
|
85
|
+
return null;
|
|
86
|
+
const dated = fs
|
|
87
|
+
.readdirSync(archive)
|
|
88
|
+
.filter((name) => name.endsWith(`-${id}`) && /^\d{4}-\d{2}-\d{2}-/.test(name))
|
|
89
|
+
.sort();
|
|
90
|
+
return dated.length ? `${CHANGES}/archive/${dated[dated.length - 1]}` : null;
|
|
91
|
+
}
|
|
92
|
+
function git(dir, args) {
|
|
93
|
+
return (0, node_child_process_1.spawnSync)("git", ["-C", dir, ...args], { encoding: "utf8" });
|
|
94
|
+
}
|
|
95
|
+
/** Every requirement of the change's deltas, with its status. */
|
|
96
|
+
function requirements(changeDir) {
|
|
97
|
+
const specs = path.join(changeDir, "specs");
|
|
98
|
+
if (!fs.existsSync(specs))
|
|
99
|
+
return [];
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const cap of fs.readdirSync(specs)) {
|
|
102
|
+
const file = path.join(specs, cap, "spec.md");
|
|
103
|
+
if (!fs.existsSync(file))
|
|
104
|
+
continue;
|
|
105
|
+
const blocks = fs
|
|
106
|
+
.readFileSync(file, "utf8")
|
|
107
|
+
.split(/^### Requirement: /m)
|
|
108
|
+
.slice(1);
|
|
109
|
+
for (const block of blocks) {
|
|
110
|
+
const head = /^(REQ-[\w.]+)\s*[—–-]\s*(.+)$/m.exec(block);
|
|
111
|
+
if (!head)
|
|
112
|
+
continue;
|
|
113
|
+
const status = /specgate:trace[^>]*\bstatus="?([^"\s>]+(?: Clarification)?)"?/.exec(block)?.[1];
|
|
114
|
+
out.push({ id: head[1], name: head[2].trim(), status: status ?? "Draft" });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
function gate(projectDir, run) {
|
|
120
|
+
if (!run)
|
|
121
|
+
return { line: "`specgate check` was not run for this update.", ok: null };
|
|
122
|
+
const r = (0, node_child_process_1.spawnSync)(process.execPath, [path.join((0, project_root_1.findCliRoot)(__dirname), "bin", "specgate.js"), "check", projectDir, "--json"], { encoding: "utf8" });
|
|
123
|
+
return r.status === 0
|
|
124
|
+
? { line: "✔ `specgate check` passes on this branch.", ok: true }
|
|
125
|
+
: { line: "✖ `specgate check` does not pass on this branch: not ready to merge.", ok: false };
|
|
126
|
+
}
|
|
127
|
+
async function main(argv) {
|
|
128
|
+
const o = parse(argv);
|
|
129
|
+
const io = (0, agent_1.agentIo)(o.json);
|
|
130
|
+
if (!o.change) {
|
|
131
|
+
io.usage(NULL_SHAPE, [
|
|
132
|
+
(0, diagnostics_1.error)("pr_no_change", "alm pr needs the change to open a pull request for.", {
|
|
133
|
+
fix: "specgate alm pr kan-13-register-vehicle-entry",
|
|
134
|
+
}),
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const projectDir = (0, project_root_1.resolveProjectDir)(o.projectDir);
|
|
139
|
+
const cfg = (0, github_pr_1.prConfig)((0, core_1.readAlmConfig)(projectDir).pr);
|
|
140
|
+
const changeRel = changePath(projectDir, o.change);
|
|
141
|
+
if (!changeRel) {
|
|
142
|
+
throw new Error(`No change '${o.change}' under ${CHANGES}/.\nFix: specgate change list.`);
|
|
143
|
+
}
|
|
144
|
+
const changeDir = path.join(projectDir, changeRel);
|
|
145
|
+
const yaml = fs.readFileSync(path.join(changeDir, "change.yaml"), "utf8");
|
|
146
|
+
const key = /^ticket:\s*(\S+)\s*$/m.exec(yaml)?.[1];
|
|
147
|
+
if (key && !/^[A-Z][A-Z0-9_]+-\d+$/.test(key)) {
|
|
148
|
+
throw new Error(`ticket: ${key} in change.yaml is not a Jira issue key.\nFix: write the ticket's key, e.g. ticket: KAN-13.`);
|
|
149
|
+
}
|
|
150
|
+
if (!key) {
|
|
151
|
+
throw new Error(`${o.change} was not opened from a ticket (no ticket: in change.yaml).\nFix: open it with specgate propose --from-ticket <KEY>.`);
|
|
152
|
+
}
|
|
153
|
+
const copy = fs.readFileSync(path.join(changeDir, "ticket.md"), "utf8");
|
|
154
|
+
const summary = /^#\s+\S+\s+—\s+(.+)$/m.exec(copy)?.[1]?.trim() ?? o.change;
|
|
155
|
+
const link = /\burl=(\S+)/.exec(copy)?.[1];
|
|
156
|
+
const branch = git(projectDir, ["rev-parse", "--abbrev-ref", "HEAD"]).stdout.trim();
|
|
157
|
+
if (!new RegExp(`(^|/)${key}(-|$)`).test(branch)) {
|
|
158
|
+
throw new Error(`The current branch '${branch}' is not ${key}'s.\nFix: check out its branch (feature/${key}-…) first.`);
|
|
159
|
+
}
|
|
160
|
+
// The pull request holds what is committed; the gate would judge the
|
|
161
|
+
// working tree. Uncommitted work makes the two disagree.
|
|
162
|
+
if (git(projectDir, ["status", "--porcelain"]).stdout.trim()) {
|
|
163
|
+
throw new Error("There are uncommitted changes: the pull request would not hold them.\nFix: commit them (starting with " +
|
|
164
|
+
key +
|
|
165
|
+
"), then run this again.");
|
|
166
|
+
}
|
|
167
|
+
if (o.push) {
|
|
168
|
+
const pushed = git(projectDir, ["push", "-q", "-u", "origin", branch]);
|
|
169
|
+
if (pushed.status !== 0)
|
|
170
|
+
throw new Error(`git push failed: ${pushed.stderr.trim()}`);
|
|
171
|
+
}
|
|
172
|
+
const reqs = requirements(changeDir);
|
|
173
|
+
const g = gate(projectDir, o.check);
|
|
174
|
+
const body = [
|
|
175
|
+
`<!-- specgate:pr change=${o.change} -->`,
|
|
176
|
+
`**Ticket:** ${link ? `[${key}](${link})` : key} — ${summary}`,
|
|
177
|
+
`**Change:** \`${changeRel}/\``,
|
|
178
|
+
"",
|
|
179
|
+
"| Requirement | Status |",
|
|
180
|
+
"|---|---|",
|
|
181
|
+
...reqs.map((r) => `| ${r.id} — ${r.name} | ${r.status} |`),
|
|
182
|
+
"",
|
|
183
|
+
`**Gate:** ${g.line}`,
|
|
184
|
+
"",
|
|
185
|
+
"_Opened by `specgate alm pr`. Specgate never merges._",
|
|
186
|
+
"<!-- /specgate:pr -->",
|
|
187
|
+
].join("\n");
|
|
188
|
+
const pr = await (0, github_pr_1.openOrUpdatePr)(cfg, { head: branch, key, title: `${key} ${summary}`, body });
|
|
189
|
+
io.emit({ pr: { ...pr, branch, ticket: key, requirements: reqs, gate: g.ok } }, () => process.stdout.write(ui.header("alm pr", o.change) +
|
|
190
|
+
ui.ok(`Pull request #${pr.number} ${pr.action} ${ui.muted(`→ ${cfg.base}`)} ${pr.url}`) +
|
|
191
|
+
(g.ok === false ? ui.warn(g.line) : "") +
|
|
192
|
+
"\n"));
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
io.fail(NULL_SHAPE, [(0, diagnostics_1.error)("pr_failed", e.message)]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=PrCommand.js.map
|