Add label support to issue creation #29
3 changed files with 79 additions and 2 deletions
|
|
@ -432,13 +432,17 @@ Options:
|
|||
-b, --body <body> issue body (markdown)
|
||||
--body-file <path> read the issue body from a file
|
||||
--assignee <username...> assign the issue to one or more users
|
||||
--label <name...> apply one or more labels by name
|
||||
```
|
||||
|
||||
```bash
|
||||
stoke issue create -o heavy-duty -r stoke -t "Ship v2" --body-file body.md
|
||||
stoke issue create -o heavy-duty -r stoke -t "Ship v2" --body-file body.md \
|
||||
--label ready --label enhancement
|
||||
```
|
||||
|
||||
Calls `POST /api/v1/repos/{owner}/{repo}/issues`.
|
||||
Requested label names are resolved through the paginated repository label list,
|
||||
then their numeric IDs are included in `POST /api/v1/repos/{owner}/{repo}/issues`.
|
||||
An unknown label fails before the issue is created.
|
||||
|
||||
### `stoke issue show`
|
||||
|
||||
|
|
|
|||
|
|
@ -686,6 +686,7 @@ issue
|
|||
.option('-b, --body <body>', 'issue body (markdown)')
|
||||
.option('--body-file <path>', 'read the issue body from a file')
|
||||
.option('--assignee <username...>', 'assign the issue to one or more users')
|
||||
.option('--label <name...>', 'apply one or more labels by name')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
const config = loadConfig();
|
||||
|
|
@ -697,6 +698,9 @@ issue
|
|||
if (options.assignee && options.assignee.length) {
|
||||
payload.assignees = options.assignee;
|
||||
}
|
||||
if (options.label && options.label.length) {
|
||||
payload.labels = await resolveLabelIds(client, options.owner, options.repo, options.label);
|
||||
}
|
||||
const result = await client.createIssue(options.owner, options.repo, payload);
|
||||
console.log(`Issue created: #${result.number} ${result.title}`);
|
||||
console.log(`URL: ${result.html_url}`);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,75 @@ test('issue create --body-file reports unreadable files cleanly', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('issue create resolves repeated label names into the initial create payload', async () => {
|
||||
const cfg = path.join(os.tmpdir(), `stoke-cfg-${process.pid}-issue-labels.json`);
|
||||
const requests = [];
|
||||
const server = http.createServer((req, res) => {
|
||||
let data = '';
|
||||
req.on('data', (chunk) => { data += chunk; });
|
||||
req.on('end', () => {
|
||||
requests.push({ method: req.method, url: req.url, body: data });
|
||||
res.writeHead(req.method === 'POST' ? 201 : 200, { 'Content-Type': 'application/json' });
|
||||
if (req.method === 'GET') {
|
||||
res.end(JSON.stringify([
|
||||
{ id: 107, name: 'ready', color: '0e8a16' },
|
||||
{ id: 100, name: 'enhancement', color: '84b6eb' },
|
||||
]));
|
||||
} else {
|
||||
res.end(JSON.stringify({ number: 27, title: 'Probe', html_url: 'https://forge.test/o/r/issues/27' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const { port } = server.address();
|
||||
fs.writeFileSync(cfg, JSON.stringify({ url: `http://127.0.0.1:${port}`, token: 'tok' }));
|
||||
|
||||
try {
|
||||
const res = await spawnAsync(
|
||||
['issue', 'create', '-o', 'o', '-r', 'r', '-t', 'Probe', '--label', 'ready', '--label', 'enhancement'],
|
||||
{ STOKE_CONFIG_FILE: cfg },
|
||||
);
|
||||
assert.equal(res.status, 0, res.stderr);
|
||||
assert.deepEqual(requests.map(({ method }) => method), ['GET', 'POST']);
|
||||
assert.match(requests[0].url, /^\/api\/v1\/repos\/o\/r\/labels\?/);
|
||||
assert.equal(requests[1].url, '/api/v1/repos/o/r/issues');
|
||||
assert.deepEqual(JSON.parse(requests[1].body), {
|
||||
title: 'Probe',
|
||||
body: '',
|
||||
labels: [107, 100],
|
||||
});
|
||||
} finally {
|
||||
await new Promise((resolve) => server.close(resolve));
|
||||
fs.unlinkSync(cfg);
|
||||
}
|
||||
});
|
||||
|
||||
test('issue create rejects an unknown label before creating the issue', async () => {
|
||||
const cfg = path.join(os.tmpdir(), `stoke-cfg-${process.pid}-unknown-issue-label.json`);
|
||||
let createRequests = 0;
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === 'POST') createRequests += 1;
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify([{ id: 107, name: 'ready', color: '0e8a16' }]));
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const { port } = server.address();
|
||||
fs.writeFileSync(cfg, JSON.stringify({ url: `http://127.0.0.1:${port}`, token: 'tok' }));
|
||||
|
||||
try {
|
||||
const res = await spawnAsync(
|
||||
['issue', 'create', '-o', 'o', '-r', 'r', '-t', 'Probe', '--label', 'nonexistent'],
|
||||
{ STOKE_CONFIG_FILE: cfg },
|
||||
);
|
||||
assert.equal(res.status, 1);
|
||||
assert.match(res.stderr, /Label not found in o\/r: nonexistent/);
|
||||
assert.equal(createRequests, 0);
|
||||
} finally {
|
||||
await new Promise((resolve) => server.close(resolve));
|
||||
fs.unlinkSync(cfg);
|
||||
}
|
||||
});
|
||||
|
||||
test('pr show validates --number before any network call', () => {
|
||||
const cfg = path.join(os.tmpdir(), `stoke-cfg-${process.pid}.json`);
|
||||
fs.writeFileSync(cfg, JSON.stringify({ url: 'https://forge.test', token: 'tok' }));
|
||||
|
|
|
|||
Loading…
Reference in a new issue