Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions layers/identity/server/utils/scan-owned-repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ async function requestOrgList(
url: string,
userToken: string,
): Promise<OrgListRequestOutcome> {
const outcome = await fetchImpl(url, {
const outcome = await fetchImpl.call(globalThis, url, {
headers: {
'Authorization': `Bearer ${userToken}`,
'Accept': 'application/vnd.github+json',
Expand Down Expand Up @@ -324,7 +324,9 @@ export function makeOwnedRepoScanner(deps: ScanOwnedReposDependencies) {
for (const owner of owners) {
for (let page = 1; page <= MAX_PAGES; page++) {
const q = encodeURIComponent(`filename:SKILL.md ${owner.qualifier} is:public`)
const responseOutcome = await deps.fetch(
// Worker fetch requires the global receiver, not the dependency object.
const responseOutcome = await deps.fetch.call(
globalThis,
`https://api.github.com/search/code?q=${q}&per_page=${PER_PAGE}&page=${page}`,
{
headers: {
Expand Down
2 changes: 2 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const hasSentryAuthToken = Boolean(process.env.SENTRY_AUTH_TOKEN)

export default defineNuxtConfig({
checkin: { external: externalCheckin },
// Private components need their parent's props and must never become pages.
ignore: ['**/pages/**/_*.vue'],
extends: ['./layers/admin', './layers/artifact-delivery', './layers/identity', './layers/registry', './layers/marketing', './layers/mcp'],

hooks: {
Expand Down
10 changes: 10 additions & 0 deletions test/unit/private-page-components.nuxt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useRouter } from '#app'

describe('private page components', () => {
it.each(['/_HomeDemoCarousel', '/brand-kit/_GithubBadgePreview'])(
'does not resolve %s as a page',
(path) => {
expect(useRouter().resolve(path).name).toBeUndefined()
},
)
})
33 changes: 33 additions & 0 deletions test/unit/scan-owned-repos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Database from 'better-sqlite3'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
buildScanOwners,
makeGithubOrgLister,
makeOwnedRepoScanner,
ownedRepoScanResponse,
ownedRepoScanWarning,
Expand Down Expand Up @@ -62,6 +63,38 @@ describe('owned GitHub repository discovery', () => {

afterEach(() => sqlite.close())

it('calls the Worker fetch with its global receiver', async () => {
const fetchImpl = function (this: unknown): Promise<Response> {
if (this !== globalThis)
throw new TypeError('Illegal invocation')
return Promise.resolve(Response.json(searchBody({ total_count: 0, items: [] })))
}
const scan = makeOwnedRepoScanner({
fetch: fetchImpl,
listOrgs: noOrgs,
syncRepo: vi.fn(),
resolveGithubBindings: () => ({}),
now: () => 100,
claimToken: () => 'claim-one',
})

await expect(scan({ login: 'acme', userToken: 'token', db, env: {} as Cloudflare.Env }))
.resolves
.toMatchObject({ _tag: 'complete', reposFound: 0 })
})

it('preserves the Worker fetch receiver when listing organisations', async () => {
const listOrgs = makeGithubOrgLister(function (this: unknown): Promise<Response> {
if (this !== globalThis)
throw new TypeError('Illegal invocation')
return Promise.resolve(Response.json([{ login: 'acme-org' }]))
})

await expect(listOrgs({ login: 'acme', userToken: 'token' }))
.resolves
.toEqual({ _tag: 'orgs', logins: ['acme-org'], visibility: 'member' })
})

it('persists an unknown candidate before a failed sync without creating a skill row', async () => {
const syncRepo = vi.fn(async (owner: string, repo: string) => ({
...indexed(owner, repo),
Expand Down
Loading