Skip to content

Commit b8bfbd2

Browse files
test: fix failing gitimport test
1 parent fffbe82 commit b8bfbd2

3 files changed

Lines changed: 167 additions & 4 deletions

File tree

packages/apps/explorer/src/ps/daemon-client.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,15 @@ export interface DaemonGithubImportRequest {
371371
}
372372

373373
export async function requestGithubImport(payload: DaemonGithubImportRequest): Promise<PowerSyncImportJob> {
374+
const importMode = getImportMode()
375+
376+
if (importMode === 'actions') {
377+
if (!actionsImportEnabled) {
378+
throw new Error('GitHub Actions import is disabled in this environment.')
379+
}
380+
return dispatchGithubImport(payload as GithubActionsImportRequest)
381+
}
382+
374383
if (daemonEnabled) {
375384
const { status, data } = await fetchDaemonJson<{ job?: PowerSyncImportJob; error?: string }>('/repos/import', {
376385
method: 'POST',

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"postinstall": "node scripts/postinstall.cjs",
2727
"typecheck": "tsc -p tsconfig.json --noEmit",
2828
"lint": "echo 'stub'",
29-
"test": "pnpm --filter @powersync-community/powergit-core build && vitest run"
29+
"test": "pnpm run build && vitest run"
3030
},
3131
"dependencies": {
3232
"@powersync/common": "1.44.0",

packages/cli/src/bin.ts

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { hideBin } from 'yargs/helpers'
44
import { createClient, type SupabaseClient } from '@supabase/supabase-js'
55
import { addPowerSyncRemote, syncPowerSyncRepository, seedDemoRepository } from './index.js'
66
import { loginWithDaemonDevice, logout as logoutSession } from './auth/login.js'
7+
import {
8+
completeDaemonDeviceLogin,
9+
extractDeviceChallenge,
10+
fetchDaemonAuthStatus,
11+
postDaemonAuthDevice,
12+
resolveDaemonBaseUrl,
13+
} from './auth/daemon-client.js'
714
import {
815
resolveProfile,
916
listProfiles,
@@ -25,6 +32,10 @@ interface LoginCommandArgs {
2532
endpoint?: string
2633
session?: string
2734
daemonUrl?: string
35+
supabaseEmail?: string
36+
supabasePassword?: string
37+
supabaseUrl?: string
38+
supabaseAnonKey?: string
2839
}
2940

3041
interface DemoSeedCommandArgs {
@@ -134,6 +145,10 @@ function firstNonEmpty(...candidates: Array<string | null | undefined>): string
134145
return undefined
135146
}
136147

148+
function hasText(value: unknown): value is string {
149+
return typeof value === 'string' && value.trim().length > 0
150+
}
151+
137152
function parseKeyPath(input: string): string[] {
138153
const segments = input
139154
.split('.')
@@ -343,10 +358,129 @@ async function runDemoSeedCommand(args: DemoSeedCommandArgs) {
343358
}
344359

345360
async function runLoginCommand(args: LoginCommandArgs) {
346-
if (args.session) {
361+
const wantsSupabasePasswordLogin =
362+
hasText(args.supabaseEmail) ||
363+
hasText(args.supabasePassword) ||
364+
hasText(args.supabaseUrl) ||
365+
hasText(args.supabaseAnonKey)
366+
367+
if (args.session && !wantsSupabasePasswordLogin) {
347368
console.warn(`${LOG_PREFIX} Ignoring legacy --session option; Supabase session persistence is automatic.`)
348369
}
349370

371+
if (wantsSupabasePasswordLogin) {
372+
const daemonBaseUrl = await resolveDaemonBaseUrl({ daemonUrl: args.daemonUrl })
373+
const status = await fetchDaemonAuthStatus(daemonBaseUrl)
374+
375+
if (status?.status === 'ready') {
376+
persistDaemonCredentials('', status.expiresAt ?? null, status.context ?? null)
377+
console.log('✅ PowerSync daemon already authenticated via Supabase.')
378+
if (status.expiresAt) {
379+
console.log(` Expires: ${status.expiresAt}`)
380+
}
381+
return
382+
}
383+
384+
const supabaseUrl = firstNonEmpty(
385+
args.supabaseUrl,
386+
process.env.SUPABASE_URL,
387+
process.env.POWERGIT_TEST_SUPABASE_URL,
388+
)
389+
const supabaseAnonKey = firstNonEmpty(
390+
args.supabaseAnonKey,
391+
process.env.SUPABASE_ANON_KEY,
392+
process.env.POWERGIT_TEST_SUPABASE_ANON_KEY,
393+
)
394+
if (!supabaseUrl || !supabaseAnonKey) {
395+
throw new Error('Supabase URL and anon key are required. Configure SUPABASE_URL and SUPABASE_ANON_KEY.')
396+
}
397+
398+
const email = firstNonEmpty(args.supabaseEmail, process.env.SUPABASE_EMAIL, process.env.POWERGIT_TEST_SUPABASE_EMAIL)
399+
const password = firstNonEmpty(
400+
args.supabasePassword,
401+
process.env.SUPABASE_PASSWORD,
402+
process.env.POWERGIT_TEST_SUPABASE_PASSWORD,
403+
)
404+
if (!email || !password) {
405+
throw new Error('Supabase email and password are required. Use --supabase-email/--supabase-password.')
406+
}
407+
408+
const authStoragePath = resolveSupabaseSessionPath(args.session)
409+
const supabaseStorage = createSupabaseFileStorage(authStoragePath)
410+
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
411+
auth: {
412+
persistSession: true,
413+
autoRefreshToken: true,
414+
storage: supabaseStorage,
415+
storageKey: 'powergit',
416+
},
417+
})
418+
419+
const { data, error } = await supabase.auth.signInWithPassword({ email, password })
420+
if (error) {
421+
throw new Error(`Supabase login failed (${error.name ?? 'AuthError'}): ${error.message}`)
422+
}
423+
const session = data?.session ?? (await supabase.auth.getSession()).data.session
424+
const accessToken = session?.access_token?.trim?.() ?? ''
425+
const refreshToken = session?.refresh_token?.trim?.() ?? ''
426+
if (!accessToken || !refreshToken) {
427+
throw new Error('Supabase login response did not include an access_token and refresh_token.')
428+
}
429+
430+
const existingChallenge = extractDeviceChallenge(status)?.challengeId ?? null
431+
const pendingStatus = existingChallenge
432+
? status
433+
: await postDaemonAuthDevice(daemonBaseUrl, {
434+
endpoint: args.endpoint,
435+
})
436+
if (pendingStatus?.status === 'ready') {
437+
persistDaemonCredentials('', pendingStatus.expiresAt ?? null, pendingStatus.context ?? null)
438+
console.log('✅ PowerSync daemon authenticated via Supabase.')
439+
if (pendingStatus.expiresAt) {
440+
console.log(` Expires: ${pendingStatus.expiresAt}`)
441+
}
442+
return
443+
}
444+
const challengeId = existingChallenge ?? extractDeviceChallenge(pendingStatus)?.challengeId ?? null
445+
if (!challengeId) {
446+
throw new Error('Daemon did not provide a device code for login.')
447+
}
448+
449+
const final = await completeDaemonDeviceLogin(daemonBaseUrl, {
450+
challengeId,
451+
endpoint: args.endpoint ?? null,
452+
session: {
453+
access_token: accessToken,
454+
refresh_token: refreshToken,
455+
expires_in: session?.expires_in ?? null,
456+
expires_at: session?.expires_at ?? null,
457+
},
458+
metadata: null,
459+
})
460+
461+
if (!final || final.status !== 'ready') {
462+
const refreshed = await fetchDaemonAuthStatus(daemonBaseUrl)
463+
const resolved = refreshed ?? final
464+
if (resolved?.status === 'ready') {
465+
persistDaemonCredentials('', resolved.expiresAt ?? null, resolved.context ?? null)
466+
console.log('✅ PowerSync daemon authenticated via Supabase.')
467+
if (resolved.expiresAt) {
468+
console.log(` Expires: ${resolved.expiresAt}`)
469+
}
470+
return
471+
}
472+
const reason = resolved?.reason ? ` (${resolved.reason})` : ''
473+
throw new Error(`Daemon login did not complete successfully${reason}.`)
474+
}
475+
476+
persistDaemonCredentials('', final.expiresAt ?? null, final.context ?? null)
477+
console.log('✅ PowerSync daemon authenticated via Supabase.')
478+
if (final.expiresAt) {
479+
console.log(` Expires: ${final.expiresAt}`)
480+
}
481+
return
482+
}
483+
350484
let observedChallenge: {
351485
challengeId?: string | null
352486
verificationUrl?: string | null
@@ -537,7 +671,7 @@ function printUsage() {
537671
console.log(
538672
` ${CLI_NAME} demo-seed [--remote-url <url>] [--remote <name>] [--branch <branch>] [--skip-sync] [--keep-repo] [--template-url <url>] [--no-template]`,
539673
)
540-
console.log(` ${CLI_NAME} login [--endpoint <url>] [--daemon-url <url>]`)
674+
console.log(` ${CLI_NAME} login [--endpoint <url>] [--daemon-url <url>] [--supabase-email <email>] [--supabase-password <password>]`)
541675
console.log(` ${CLI_NAME} daemon stop [--daemon-url <url>] [--wait <ms>]`)
542676
console.log(` ${CLI_NAME} logout [--daemon-url <url>]`)
543677
console.log(` ${CLI_NAME} profile list|show|set|use …`)
@@ -554,7 +688,7 @@ function buildCli() {
554688
` ${CLI_NAME} sync [--remote <name>]\n` +
555689
` ${CLI_NAME} org list|create|members …\n` +
556690
` ${CLI_NAME} demo-seed [--remote-url <url>] [--remote <name>] [--branch <branch>] [--skip-sync] [--keep-repo] [--template-url <url>] [--no-template]\n` +
557-
` ${CLI_NAME} login [--endpoint <url>] [--daemon-url <url>]\n` +
691+
` ${CLI_NAME} login [--endpoint <url>] [--daemon-url <url>] [--supabase-email <email>] [--supabase-password <password>]\n` +
558692
` ${CLI_NAME} daemon stop [--daemon-url <url>] [--wait <ms>]\n` +
559693
` ${CLI_NAME} logout [--daemon-url <url>]\n`,
560694
)
@@ -1067,6 +1201,22 @@ function buildCli() {
10671201
type: 'string',
10681202
describe: 'Legacy credential cache path',
10691203
})
1204+
.option('supabase-email', {
1205+
type: 'string',
1206+
describe: 'Supabase login email (non-interactive).',
1207+
})
1208+
.option('supabase-password', {
1209+
type: 'string',
1210+
describe: 'Supabase login password (non-interactive).',
1211+
})
1212+
.option('supabase-url', {
1213+
type: 'string',
1214+
describe: 'Supabase URL override for password login (optional).',
1215+
})
1216+
.option('supabase-anon-key', {
1217+
type: 'string',
1218+
describe: 'Supabase anon key override for password login (optional).',
1219+
})
10701220
.option('daemon-url', {
10711221
type: 'string',
10721222
describe: 'PowerSync daemon base URL',
@@ -1076,6 +1226,10 @@ function buildCli() {
10761226
endpoint: argv.endpoint as string | undefined,
10771227
session: argv.session as string | undefined,
10781228
daemonUrl: argv['daemon-url'] as string | undefined,
1229+
supabaseEmail: argv['supabase-email'] as string | undefined,
1230+
supabasePassword: argv['supabase-password'] as string | undefined,
1231+
supabaseUrl: argv['supabase-url'] as string | undefined,
1232+
supabaseAnonKey: argv['supabase-anon-key'] as string | undefined,
10791233
})
10801234
},
10811235
)

0 commit comments

Comments
 (0)