opencode-pilot 0.5.2 → 0.6.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/AGENTS.md +10 -2
- package/examples/config.yaml +17 -1
- package/package.json +1 -1
- package/service/poll-service.js +34 -10
- package/service/presets/github.yaml +6 -1
- package/service/repo-config.js +19 -8
- package/test/unit/poll-service.test.js +140 -0
- package/test/unit/repo-config.test.js +51 -3
package/AGENTS.md
CHANGED
|
@@ -36,7 +36,15 @@ gh release list -R athal7/opencode-pilot -L 1
|
|
|
36
36
|
npm view opencode-pilot version
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
### 3.
|
|
39
|
+
### 3. Clear OpenCode Plugin Cache
|
|
40
|
+
|
|
41
|
+
OpenCode caches plugins at `~/.cache/opencode/node_modules/`. Clear the cache to pick up the new version:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
rm -rf ~/.cache/opencode/node_modules/opencode-pilot
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 4. Restart Service
|
|
40
48
|
|
|
41
49
|
If the service is running, restart it:
|
|
42
50
|
|
|
@@ -45,7 +53,7 @@ If the service is running, restart it:
|
|
|
45
53
|
npx opencode-pilot start
|
|
46
54
|
```
|
|
47
55
|
|
|
48
|
-
###
|
|
56
|
+
### 5. Verify Upgrade
|
|
49
57
|
|
|
50
58
|
```bash
|
|
51
59
|
npx opencode-pilot status
|
package/examples/config.yaml
CHANGED
|
@@ -29,9 +29,12 @@ sources:
|
|
|
29
29
|
- preset: github/review-requests
|
|
30
30
|
prompt: review
|
|
31
31
|
|
|
32
|
-
# My PRs that have change requests
|
|
32
|
+
# My PRs that have change requests (filter to specific repos)
|
|
33
33
|
- preset: github/my-prs-feedback
|
|
34
34
|
prompt: review-feedback
|
|
35
|
+
repos: # Optional: filter to specific repos
|
|
36
|
+
- myorg/backend
|
|
37
|
+
- myorg/frontend
|
|
35
38
|
|
|
36
39
|
# Linear issues (requires teamId and assigneeId)
|
|
37
40
|
# Find IDs via: opencode-pilot discover linear
|
|
@@ -82,6 +85,19 @@ sources:
|
|
|
82
85
|
# title: name
|
|
83
86
|
# body: notes
|
|
84
87
|
|
|
88
|
+
# =============================================================================
|
|
89
|
+
# REPOS - Map GitHub repos to local paths (OPTIONAL)
|
|
90
|
+
#
|
|
91
|
+
# GitHub presets automatically resolve the repo from each item's
|
|
92
|
+
# repository.full_name field. Add entries here to map repos to local paths.
|
|
93
|
+
# =============================================================================
|
|
94
|
+
# repos:
|
|
95
|
+
# myorg/backend:
|
|
96
|
+
# path: ~/code/backend
|
|
97
|
+
#
|
|
98
|
+
# myorg/frontend:
|
|
99
|
+
# path: ~/code/frontend
|
|
100
|
+
|
|
85
101
|
# =============================================================================
|
|
86
102
|
# Available presets (no tools config needed):
|
|
87
103
|
# github/my-issues - Issues assigned to me
|
package/package.json
CHANGED
package/service/poll-service.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 5. Track processed items to avoid duplicates
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { loadRepoConfig, getRepoConfig, getAllSources, getToolProviderConfig } from "./repo-config.js";
|
|
12
|
+
import { loadRepoConfig, getRepoConfig, getAllSources, getToolProviderConfig, resolveRepoForItem } from "./repo-config.js";
|
|
13
13
|
import { createPoller, pollGenericSource } from "./poller.js";
|
|
14
14
|
import { evaluateReadiness, sortByPriority } from "./readiness.js";
|
|
15
15
|
import { executeAction, buildCommand } from "./actions.js";
|
|
@@ -52,6 +52,25 @@ export function buildActionConfigFromSource(source, repoConfig) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Build action config for a specific item, resolving repo from item fields
|
|
57
|
+
* Uses source.repo template (e.g., "{repository.full_name}") to look up repo config
|
|
58
|
+
* @param {object} source - Source configuration
|
|
59
|
+
* @param {object} item - Item from the source (contains repo info)
|
|
60
|
+
* @returns {object} Merged action config
|
|
61
|
+
*/
|
|
62
|
+
export function buildActionConfigForItem(source, item) {
|
|
63
|
+
// Resolve repo key from item using source.repo template
|
|
64
|
+
const repoKeys = resolveRepoForItem(source, item);
|
|
65
|
+
const repoKey = repoKeys.length > 0 ? repoKeys[0] : null;
|
|
66
|
+
|
|
67
|
+
// Get repo config (returns empty object if repo not configured)
|
|
68
|
+
const repoConfig = repoKey ? getRepoConfig(repoKey) : {};
|
|
69
|
+
|
|
70
|
+
// Build config with repo config as base, source overrides on top
|
|
71
|
+
return buildActionConfigFromSource(source, repoConfig);
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
// Global state
|
|
56
75
|
let pollingInterval = null;
|
|
57
76
|
let pollerInstance = null;
|
|
@@ -87,8 +106,6 @@ export async function pollOnce(options = {}) {
|
|
|
87
106
|
// Process each source
|
|
88
107
|
for (const source of sources) {
|
|
89
108
|
const sourceName = source.name || 'unknown';
|
|
90
|
-
const repoKey = source.name || 'default';
|
|
91
|
-
const repoConfig = getRepoConfig(repoKey) || {};
|
|
92
109
|
|
|
93
110
|
if (!hasToolConfig(source)) {
|
|
94
111
|
console.error(`[poll] Source '${sourceName}' missing tool configuration (requires tool.mcp and tool.name)`);
|
|
@@ -112,21 +129,28 @@ export async function pollOnce(options = {}) {
|
|
|
112
129
|
// Evaluate readiness and filter
|
|
113
130
|
const readyItems = items
|
|
114
131
|
.map((item) => {
|
|
132
|
+
// Resolve repo from item for per-item config
|
|
133
|
+
const repoKeys = resolveRepoForItem(source, item);
|
|
134
|
+
const repoKey = repoKeys.length > 0 ? repoKeys[0] : null;
|
|
135
|
+
const repoConfig = repoKey ? getRepoConfig(repoKey) : {};
|
|
136
|
+
|
|
115
137
|
const readiness = evaluateReadiness(item, repoConfig);
|
|
116
138
|
debug(`Item ${item.id}: ready=${readiness.ready}, reason=${readiness.reason || 'none'}`);
|
|
117
139
|
return {
|
|
118
140
|
...item,
|
|
119
|
-
repo_key: repoKey,
|
|
120
|
-
repo_short: repoKey.split("/").pop(),
|
|
141
|
+
repo_key: repoKey || sourceName,
|
|
142
|
+
repo_short: repoKey ? repoKey.split("/").pop() : sourceName,
|
|
121
143
|
_readiness: readiness,
|
|
144
|
+
_repoConfig: repoConfig,
|
|
122
145
|
};
|
|
123
146
|
})
|
|
124
147
|
.filter((item) => item._readiness.ready);
|
|
125
148
|
|
|
126
149
|
debug(`${readyItems.length} items ready out of ${items.length}`);
|
|
127
150
|
|
|
128
|
-
// Sort by priority
|
|
129
|
-
const
|
|
151
|
+
// Sort by priority (use first item's repo config or empty)
|
|
152
|
+
const sortConfig = readyItems.length > 0 ? readyItems[0]._repoConfig : {};
|
|
153
|
+
const sortedItems = sortByPriority(readyItems, sortConfig);
|
|
130
154
|
|
|
131
155
|
// Process ready items
|
|
132
156
|
debug(`Processing ${sortedItems.length} sorted items`);
|
|
@@ -138,8 +162,8 @@ export async function pollOnce(options = {}) {
|
|
|
138
162
|
}
|
|
139
163
|
|
|
140
164
|
debug(`Executing action for ${item.id}`);
|
|
141
|
-
// Build action config from source (
|
|
142
|
-
const actionConfig =
|
|
165
|
+
// Build action config from source and item (resolves repo from item fields)
|
|
166
|
+
const actionConfig = buildActionConfigForItem(source, item);
|
|
143
167
|
|
|
144
168
|
// Execute or dry-run
|
|
145
169
|
if (dryRun) {
|
|
@@ -161,7 +185,7 @@ export async function pollOnce(options = {}) {
|
|
|
161
185
|
if (result.success) {
|
|
162
186
|
// Mark as processed to avoid re-triggering
|
|
163
187
|
if (pollerInstance) {
|
|
164
|
-
pollerInstance.markProcessed(item.id, { repoKey, command: result.command });
|
|
188
|
+
pollerInstance.markProcessed(item.id, { repoKey: item.repo_key, command: result.command });
|
|
165
189
|
}
|
|
166
190
|
console.log(`[poll] Started session for ${item.id}`);
|
|
167
191
|
} else {
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
# Provider-level config (applies to all GitHub presets)
|
|
4
4
|
_provider:
|
|
5
5
|
response_key: items
|
|
6
|
-
mappings:
|
|
6
|
+
mappings:
|
|
7
|
+
# Extract repo full name from repository_url (e.g., "https://api.github.com/repos/owner/repo")
|
|
8
|
+
repository_full_name: "repository_url:/repos\\/([^/]+\\/[^/]+)$/"
|
|
7
9
|
|
|
8
10
|
# Presets
|
|
9
11
|
my-issues:
|
|
@@ -15,6 +17,7 @@ my-issues:
|
|
|
15
17
|
q: "is:issue assignee:@me state:open"
|
|
16
18
|
item:
|
|
17
19
|
id: "{html_url}"
|
|
20
|
+
repo: "{repository_full_name}"
|
|
18
21
|
|
|
19
22
|
review-requests:
|
|
20
23
|
name: review-requests
|
|
@@ -25,6 +28,7 @@ review-requests:
|
|
|
25
28
|
q: "is:pr review-requested:@me state:open"
|
|
26
29
|
item:
|
|
27
30
|
id: "{html_url}"
|
|
31
|
+
repo: "{repository_full_name}"
|
|
28
32
|
|
|
29
33
|
my-prs-feedback:
|
|
30
34
|
name: my-prs-feedback
|
|
@@ -35,3 +39,4 @@ my-prs-feedback:
|
|
|
35
39
|
q: "is:pr author:@me state:open review:changes_requested"
|
|
36
40
|
item:
|
|
37
41
|
id: "{html_url}"
|
|
42
|
+
repo: "{repository_full_name}"
|
package/service/repo-config.js
CHANGED
|
@@ -234,20 +234,31 @@ export function getTemplate(templateName, templatesDir) {
|
|
|
234
234
|
* @returns {Array<string>} Array of repo keys
|
|
235
235
|
*/
|
|
236
236
|
export function resolveRepoForItem(source, item) {
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
return source.repos;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Single repo from field reference (e.g., "{repository.full_name}")
|
|
237
|
+
// Resolve repo from item using template (e.g., "{repository.full_name}")
|
|
238
|
+
let resolvedRepo = null;
|
|
243
239
|
if (typeof source.repo === "string") {
|
|
244
240
|
const resolved = expandTemplate(source.repo, item);
|
|
245
|
-
// Only
|
|
241
|
+
// Only use if actually resolved (not still a template)
|
|
246
242
|
if (resolved && !resolved.includes("{")) {
|
|
247
|
-
|
|
243
|
+
resolvedRepo = resolved;
|
|
248
244
|
}
|
|
249
245
|
}
|
|
250
246
|
|
|
247
|
+
// If source.repos is an array, use it as an allowlist filter
|
|
248
|
+
if (Array.isArray(source.repos)) {
|
|
249
|
+
// If we resolved a repo from the item, check if it's in the allowlist
|
|
250
|
+
if (resolvedRepo) {
|
|
251
|
+
return source.repos.includes(resolvedRepo) ? [resolvedRepo] : [];
|
|
252
|
+
}
|
|
253
|
+
// No repo template - return empty (can't match without item context)
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// No allowlist - return the resolved repo if we have one
|
|
258
|
+
if (resolvedRepo) {
|
|
259
|
+
return [resolvedRepo];
|
|
260
|
+
}
|
|
261
|
+
|
|
251
262
|
// No repo configuration - repo-agnostic source
|
|
252
263
|
return [];
|
|
253
264
|
}
|
|
@@ -139,4 +139,144 @@ sources:
|
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
});
|
|
142
|
+
|
|
143
|
+
describe('per-item repo resolution', () => {
|
|
144
|
+
test('resolves repo config from item using source.repo template', async () => {
|
|
145
|
+
const config = `
|
|
146
|
+
repos:
|
|
147
|
+
myorg/backend:
|
|
148
|
+
path: ~/code/backend
|
|
149
|
+
prompt: worktree
|
|
150
|
+
session:
|
|
151
|
+
name: "issue-{number}"
|
|
152
|
+
|
|
153
|
+
sources:
|
|
154
|
+
- preset: github/my-issues
|
|
155
|
+
`;
|
|
156
|
+
writeFileSync(configPath, config);
|
|
157
|
+
|
|
158
|
+
const { loadRepoConfig, getSources, getRepoConfig, resolveRepoForItem } = await import('../../service/repo-config.js');
|
|
159
|
+
loadRepoConfig(configPath);
|
|
160
|
+
|
|
161
|
+
const source = getSources()[0];
|
|
162
|
+
// Item with repository_full_name (mapped from repository_url by GitHub provider)
|
|
163
|
+
const item = {
|
|
164
|
+
repository_full_name: 'myorg/backend',
|
|
165
|
+
number: 123,
|
|
166
|
+
html_url: 'https://github.com/myorg/backend/issues/123'
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Source should have repo field from preset (uses mapped field)
|
|
170
|
+
assert.strictEqual(source.repo, '{repository_full_name}');
|
|
171
|
+
|
|
172
|
+
// resolveRepoForItem should extract repo key from item
|
|
173
|
+
const repoKeys = resolveRepoForItem(source, item);
|
|
174
|
+
assert.deepStrictEqual(repoKeys, ['myorg/backend']);
|
|
175
|
+
|
|
176
|
+
// getRepoConfig should return the repo settings
|
|
177
|
+
const repoConfig = getRepoConfig(repoKeys[0]);
|
|
178
|
+
assert.strictEqual(repoConfig.path, '~/code/backend');
|
|
179
|
+
assert.strictEqual(repoConfig.prompt, 'worktree');
|
|
180
|
+
assert.deepStrictEqual(repoConfig.session, { name: 'issue-{number}' });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('falls back gracefully when repo not in config', async () => {
|
|
184
|
+
const config = `
|
|
185
|
+
repos:
|
|
186
|
+
myorg/backend:
|
|
187
|
+
path: ~/code/backend
|
|
188
|
+
|
|
189
|
+
sources:
|
|
190
|
+
- preset: github/my-issues
|
|
191
|
+
`;
|
|
192
|
+
writeFileSync(configPath, config);
|
|
193
|
+
|
|
194
|
+
const { loadRepoConfig, getSources, getRepoConfig, resolveRepoForItem } = await import('../../service/repo-config.js');
|
|
195
|
+
loadRepoConfig(configPath);
|
|
196
|
+
|
|
197
|
+
const source = getSources()[0];
|
|
198
|
+
// Item with repository_full_name (mapped from repository_url by GitHub provider)
|
|
199
|
+
const item = {
|
|
200
|
+
repository_full_name: 'unknown/repo',
|
|
201
|
+
number: 456
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// resolveRepoForItem should extract repo key from item
|
|
205
|
+
const repoKeys = resolveRepoForItem(source, item);
|
|
206
|
+
assert.deepStrictEqual(repoKeys, ['unknown/repo']);
|
|
207
|
+
|
|
208
|
+
// getRepoConfig should return empty object for unknown repo
|
|
209
|
+
const repoConfig = getRepoConfig(repoKeys[0]);
|
|
210
|
+
assert.deepStrictEqual(repoConfig, {});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('buildActionConfigForItem', () => {
|
|
215
|
+
test('uses repo config resolved from item', async () => {
|
|
216
|
+
const config = `
|
|
217
|
+
repos:
|
|
218
|
+
myorg/backend:
|
|
219
|
+
path: ~/code/backend
|
|
220
|
+
prompt: repo-prompt
|
|
221
|
+
session:
|
|
222
|
+
name: "issue-{number}"
|
|
223
|
+
|
|
224
|
+
sources:
|
|
225
|
+
- preset: github/my-issues
|
|
226
|
+
prompt: source-prompt
|
|
227
|
+
`;
|
|
228
|
+
writeFileSync(configPath, config);
|
|
229
|
+
|
|
230
|
+
const { loadRepoConfig } = await import('../../service/repo-config.js');
|
|
231
|
+
const { buildActionConfigForItem } = await import('../../service/poll-service.js');
|
|
232
|
+
loadRepoConfig(configPath);
|
|
233
|
+
|
|
234
|
+
const source = {
|
|
235
|
+
name: 'my-issues',
|
|
236
|
+
repo: '{repository.full_name}',
|
|
237
|
+
prompt: 'source-prompt'
|
|
238
|
+
};
|
|
239
|
+
const item = {
|
|
240
|
+
repository: { full_name: 'myorg/backend' },
|
|
241
|
+
number: 123
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const actionConfig = buildActionConfigForItem(source, item);
|
|
245
|
+
|
|
246
|
+
// Should use repo path from repos config
|
|
247
|
+
assert.strictEqual(actionConfig.repo_path, '~/code/backend');
|
|
248
|
+
// Source prompt should override repo prompt
|
|
249
|
+
assert.strictEqual(actionConfig.prompt, 'source-prompt');
|
|
250
|
+
// Session should come from repo config
|
|
251
|
+
assert.deepStrictEqual(actionConfig.session, { name: 'issue-{number}' });
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('falls back to source working_dir when repo not configured', async () => {
|
|
255
|
+
const config = `
|
|
256
|
+
sources:
|
|
257
|
+
- preset: github/my-issues
|
|
258
|
+
working_dir: ~/default/path
|
|
259
|
+
`;
|
|
260
|
+
writeFileSync(configPath, config);
|
|
261
|
+
|
|
262
|
+
const { loadRepoConfig } = await import('../../service/repo-config.js');
|
|
263
|
+
const { buildActionConfigForItem } = await import('../../service/poll-service.js');
|
|
264
|
+
loadRepoConfig(configPath);
|
|
265
|
+
|
|
266
|
+
const source = {
|
|
267
|
+
name: 'my-issues',
|
|
268
|
+
repo: '{repository.full_name}',
|
|
269
|
+
working_dir: '~/default/path'
|
|
270
|
+
};
|
|
271
|
+
const item = {
|
|
272
|
+
repository: { full_name: 'unknown/repo' },
|
|
273
|
+
number: 456
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const actionConfig = buildActionConfigForItem(source, item);
|
|
277
|
+
|
|
278
|
+
// Should use source working_dir since repo not in config
|
|
279
|
+
assert.strictEqual(actionConfig.working_dir, '~/default/path');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
142
282
|
});
|
|
@@ -352,7 +352,9 @@ sources: []
|
|
|
352
352
|
|
|
353
353
|
// GitHub preset has response_key: items, user config doesn't override it
|
|
354
354
|
assert.strictEqual(toolConfig.response_key, 'items');
|
|
355
|
-
|
|
355
|
+
// GitHub provider has mapping for repository_full_name extraction from URL
|
|
356
|
+
assert.strictEqual(toolConfig.mappings.url, 'html_url');
|
|
357
|
+
assert.ok(toolConfig.mappings.repository_full_name, 'Should have repository_full_name mapping');
|
|
356
358
|
});
|
|
357
359
|
|
|
358
360
|
test('getToolProviderConfig falls back to preset provider config', async () => {
|
|
@@ -423,7 +425,7 @@ sources:
|
|
|
423
425
|
assert.deepStrictEqual(repos, ['myorg/backend']);
|
|
424
426
|
});
|
|
425
427
|
|
|
426
|
-
test('
|
|
428
|
+
test('repos array without repo template returns empty (needs item context)', async () => {
|
|
427
429
|
writeFileSync(configPath, `
|
|
428
430
|
sources:
|
|
429
431
|
- name: cross-repo
|
|
@@ -444,7 +446,8 @@ sources:
|
|
|
444
446
|
const item = { number: 123 };
|
|
445
447
|
const repos = resolveRepoForItem(source, item);
|
|
446
448
|
|
|
447
|
-
|
|
449
|
+
// Without a repo template, can't resolve from item - returns empty
|
|
450
|
+
assert.deepStrictEqual(repos, []);
|
|
448
451
|
});
|
|
449
452
|
|
|
450
453
|
test('returns empty array when no repo config', async () => {
|
|
@@ -614,6 +617,51 @@ sources:
|
|
|
614
617
|
|
|
615
618
|
assert.throws(() => getSources(), /Unknown preset: unknown\/preset/);
|
|
616
619
|
});
|
|
620
|
+
|
|
621
|
+
test('github presets include repo field for automatic resolution', async () => {
|
|
622
|
+
writeFileSync(configPath, `
|
|
623
|
+
sources:
|
|
624
|
+
- preset: github/my-issues
|
|
625
|
+
- preset: github/review-requests
|
|
626
|
+
- preset: github/my-prs-feedback
|
|
627
|
+
`);
|
|
628
|
+
|
|
629
|
+
const { loadRepoConfig, getSources, resolveRepoForItem } = await import('../../service/repo-config.js');
|
|
630
|
+
loadRepoConfig(configPath);
|
|
631
|
+
const sources = getSources();
|
|
632
|
+
|
|
633
|
+
// All GitHub presets should have repo field that references repository_full_name
|
|
634
|
+
// (which is mapped from repository_url by the GitHub provider)
|
|
635
|
+
const mockItem = { repository_full_name: 'myorg/backend' };
|
|
636
|
+
|
|
637
|
+
for (const source of sources) {
|
|
638
|
+
assert.strictEqual(source.repo, '{repository_full_name}', `Preset ${source.name} should have repo field`);
|
|
639
|
+
const repos = resolveRepoForItem(source, mockItem);
|
|
640
|
+
assert.deepStrictEqual(repos, ['myorg/backend'], `Preset ${source.name} should resolve repo from item`);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test('source.repos acts as allowlist filter', async () => {
|
|
645
|
+
writeFileSync(configPath, `
|
|
646
|
+
sources:
|
|
647
|
+
- preset: github/my-issues
|
|
648
|
+
repos:
|
|
649
|
+
- myorg/backend
|
|
650
|
+
- myorg/frontend
|
|
651
|
+
`);
|
|
652
|
+
|
|
653
|
+
const { loadRepoConfig, getSources, resolveRepoForItem } = await import('../../service/repo-config.js');
|
|
654
|
+
loadRepoConfig(configPath);
|
|
655
|
+
const source = getSources()[0];
|
|
656
|
+
|
|
657
|
+
// Item from allowed repo should resolve (repository_full_name is mapped from repository_url)
|
|
658
|
+
const allowedItem = { repository_full_name: 'myorg/backend' };
|
|
659
|
+
assert.deepStrictEqual(resolveRepoForItem(source, allowedItem), ['myorg/backend']);
|
|
660
|
+
|
|
661
|
+
// Item from non-allowed repo should return empty (filtered out)
|
|
662
|
+
const filteredItem = { repository_full_name: 'other/repo' };
|
|
663
|
+
assert.deepStrictEqual(resolveRepoForItem(source, filteredItem), []);
|
|
664
|
+
});
|
|
617
665
|
});
|
|
618
666
|
|
|
619
667
|
describe('shorthand syntax', () => {
|