Skip to content

Commit cac9de3

Browse files
fix: simplify remote handling to be profile centric
1 parent 28b7ed7 commit cac9de3

23 files changed

Lines changed: 674 additions & 220 deletions

docs/github-import-flow.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ <h2>Remote Helper Details</h2>
124124
</tr>
125125
<tr>
126126
<td>Set PowerSync remote</td>
127-
<td><code>git remote add powersync powergit::https://...</code></td>
127+
<td><code>git remote add powersync powergit::/org/repo</code></td>
128128
<td>Helper swizzles Git transport, wrapping PowerSync APIs.</td>
129129
<td>N/A (configuration step)</td>
130130
</tr>

docs/supabase.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ When you need the daemon + browser to share a Supabase-issued token (useful for
3737
pnpm --filter @powersync-community/powergit login
3838
```
3939

40-
The CLI prints a device code and a verification URL (`POWERSYNC_DAEMON_DEVICE_URL`, default `http://localhost:5783/auth`). Open it, sign in, and the token is stored under `~/.powergit`.
40+
The CLI prints a device code and a verification URL (`POWERSYNC_DAEMON_DEVICE_URL`, default `http://localhost:5783/auth`). Open it, sign in, and the token is stored under `~/.powergit/daemon/<profile>/`.
4141

4242
### CLI live tests (optional)
4343

4444
When you have the local PowerSync + Supabase stack running (for example via `pnpm dev:stack` or your own Docker Compose deployment), you can run an additional Vitest suite that exercises `powergit sync` against the live services. Provide the connection details through environment variables so the test can discover the stack:
4545

4646
| Variable | Purpose |
4747
| --- | --- |
48-
| `POWERGIT_TEST_REMOTE_URL` | PowerSync remote URL (e.g. `powergit::https://localhost:8080/orgs/acme/repos/infra`). *Required to enable the test.* |
48+
| `POWERGIT_TEST_REMOTE_URL` | Powergit remote URL (e.g. `powergit::/acme/infra`, `powergit::staging/acme/infra`, or `powergit::local-dev/acme/infra`). *Required to enable the test.* |
4949
| `POWERGIT_TEST_REMOTE_NAME` | Git remote name to target (defaults to `powersync`). |
5050
| `POWERGIT_TEST_SUPABASE_URL` | Supabase REST URL (used for password login). |
5151
| `POWERGIT_TEST_SUPABASE_EMAIL` | Supabase user email used for HS256 login. |

packages/apps/explorer/tests/e2e/live-cli.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { spawnSync } from 'node:child_process'
66
import type { Page } from '@playwright/test'
77
import { test, expect } from './diagnostics'
88
import { BASE_URL } from '../../playwright.config'
9-
import { parsePowerSyncUrl } from '@powersync-community/powergit-core'
9+
import { resolvePowergitRemote } from '@powersync-community/powergit-core/node'
1010
import { loadProfileEnvironment } from '../../../../cli/src/profile-env.js'
1111

1212
const WAIT_TIMEOUT_MS = Number.parseInt(process.env.POWERSYNC_E2E_WAIT_MS ?? '300000', 10)
@@ -265,7 +265,7 @@ describeLive('CLI-seeded repo (live PowerSync)', () => {
265265
daemonBaseUrl = normalizeBaseUrl(requireEnv('POWERSYNC_DAEMON_URL'))
266266

267267
const remoteUrl = requireEnv('POWERGIT_TEST_REMOTE_URL')
268-
const parsed = parsePowerSyncUrl(remoteUrl)
268+
const parsed = resolvePowergitRemote(remoteUrl)
269269
orgId = parsed.org
270270
repoId = parsed.repo
271271

packages/apps/explorer/tests/e2e/setup/live-stack.setup.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ async function runDeviceLoginFlow(): Promise<void> {
205205
async function loginDaemonIfNeeded(): Promise<void> {
206206
const daemonUrl =
207207
process.env.POWERSYNC_DAEMON_URL ??
208-
process.env.POWERSYNC_DAEMON_ENDPOINT ??
209208
'http://127.0.0.1:5030'
210209
const daemonBase = daemonUrl.replace(/\/+$/, '')
211210
const status = await fetch(`${daemonBase}/auth/status`)

packages/cli/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ git remote -v
7171
> **Why `powergit::`?**
7272
> Git uses the prefix before `::` to pick a remote helper binary named `git-remote-<prefix>`. We ship `git-remote-powergit`, so the URL must start with `powergit::`.
7373
74-
### Shorthand remote URLs (profiles/aliases)
74+
### Shorthand remote URLs (profiles)
7575

76-
`powergit::/<org>/<repo>` uses the default profile (production out of the box). To target another stack, create a profile alias that bundles **both** the PowerSync and Supabase endpoints, then reference that alias in the remote URL:
76+
`powergit::/<org>/<repo>` uses the default profile (production out of the box). To target another stack, create a profile that bundles **both** the PowerSync and Supabase endpoints, then reference that profile in the remote URL:
7777

7878
```bash
7979
powergit profile set staging \
@@ -87,7 +87,7 @@ powergit profile set staging --set supabase.serviceRoleKey=<service-role-key>
8787
git remote add powersync powergit::staging/<org>/<repo>
8888
```
8989

90-
Supabase credentials are **not** part of the remote URL. They live in the profile/env and are used only for login/auth, so prefer aliases when you need a non-prod or custom stack.
90+
Supabase config (URL/anon key) and your login session are **not** part of the remote URL. Keep them in the profile/env (and never put a `supabase.serviceRoleKey` in a Git remote).
9191

9292
### Choose a different remote name
9393

@@ -105,7 +105,7 @@ Before running commands that talk to PowerSync (push/fetch or `powergit sync`),
105105
powergit login
106106
```
107107

108-
`powergit login` uses Supabase credentials from your profile or environment (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_EMAIL`, `SUPABASE_PASSWORD`). The resulting Supabase JWT is cached under `~/.powergit/session.json` and automatically reused by the CLI.
108+
`powergit login` uses Supabase credentials from your profile or environment (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_EMAIL`, `SUPABASE_PASSWORD`). The resulting Supabase JWT is cached per profile under `~/.powergit/daemon/<profile>/session.json` and automatically reused by the daemon.
109109

110110
Need to stash a token manually (for CI or when you already have one)?
111111

packages/cli/src/auth/session.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ export interface StoredCredentials {
1010
obtainedAt?: string
1111
}
1212

13+
function sanitizeProfileKey(value: string): string {
14+
const trimmed = value.trim()
15+
if (!trimmed) return 'default'
16+
return trimmed.replace(/[^a-zA-Z0-9._-]+/g, '-')
17+
}
18+
19+
function resolveProfileNameFromEnv(): string {
20+
const candidate =
21+
process.env.POWERGIT_PROFILE ??
22+
process.env.STACK_PROFILE ??
23+
process.env.POWERGIT_ACTIVE_PROFILE ??
24+
'prod'
25+
const trimmed = String(candidate ?? '').trim()
26+
return trimmed.length > 0 ? trimmed : 'prod'
27+
}
28+
1329
function resolvePowergitHome(): string {
1430
const override = process.env.POWERGIT_HOME
1531
if (override && override.trim().length > 0) {
@@ -20,7 +36,8 @@ function resolvePowergitHome(): string {
2036

2137
function getSessionPath(customPath?: string): string {
2238
if (customPath) return resolve(customPath)
23-
return resolve(resolvePowergitHome(), 'session.json')
39+
const profileKey = sanitizeProfileKey(resolveProfileNameFromEnv())
40+
return resolve(resolvePowergitHome(), 'daemon', profileKey, 'session.json')
2441
}
2542

2643
async function ensureDirectory(path: string) {

packages/cli/src/bin.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,7 @@ async function runLogoutCommand(args: LogoutCommandArgs) {
452452
}
453453

454454
async function runDaemonStopCommand(args: DaemonStopCommandArgs) {
455-
const defaultDaemonUrl =
456-
process.env.POWERSYNC_DAEMON_URL ?? process.env.POWERSYNC_DAEMON_ENDPOINT ?? 'http://127.0.0.1:5030'
455+
const defaultDaemonUrl = process.env.POWERSYNC_DAEMON_URL ?? 'http://127.0.0.1:5030'
457456
const baseUrl = normalizeDaemonBaseUrl(args.daemonUrl ?? defaultDaemonUrl)
458457
const responsive = await isDaemonResponsiveLocal(baseUrl)
459458
if (!responsive) {
@@ -774,7 +773,7 @@ function buildCli() {
774773
y
775774
.positional('url', {
776775
type: 'string',
777-
describe: 'Powergit remote URL (powergit::/org/repo or powergit::https://…)',
776+
describe: 'Powergit remote URL (powergit::/org/repo or powergit::<profile>/org/repo)',
778777
})
779778
.option('remote', {
780779
alias: 'r',

packages/cli/src/cli.e2e.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { createRequire } from 'node:module'
77
import { fileURLToPath, pathToFileURL } from 'node:url'
88
import { execFile, spawn, spawnSync } from 'node:child_process'
99
import { promisify } from 'node:util'
10-
import { parsePowerSyncUrl, PowerSyncRemoteClient, buildRepoStreamTargets, formatStreamKey } from '@powersync-community/powergit-core'
10+
import { PowerSyncRemoteClient, buildRepoStreamTargets, formatStreamKey } from '@powersync-community/powergit-core'
11+
import { resolvePowergitRemote } from '@powersync-community/powergit-core/node'
1112
import { startStack, stopStack } from '../../../scripts/test-stack-hooks.mjs'
1213
import { seedDemoRepository } from './index.js'
1314
import { clearStoredCredentials, loadStoredCredentials, saveStoredCredentials } from './auth/session.js'
@@ -125,7 +126,7 @@ async function runScript(scriptRelativePath: string, extraEnv: NodeJS.ProcessEnv
125126
}
126127

127128
async function seedLiveStackData(config: LiveStackConfig) {
128-
const { org, repo } = parsePowerSyncUrl(config.remoteUrl)
129+
const { org, repo } = resolvePowergitRemote(config.remoteUrl)
129130
await seedDemoRepository({
130131
remoteUrl: config.remoteUrl,
131132
remoteName: config.remoteName,
@@ -180,13 +181,13 @@ describe('powergit CLI e2e', () => {
180181
}
181182

182183
it('adds and updates the default powersync remote', async () => {
183-
const firstUrl = 'powergit::https://example.dev/orgs/acme/repos/infra'
184+
const firstUrl = 'powergit::/acme/infra'
184185
const { stdout: addStdout } = await runCli(['remote', 'add', 'powersync', firstUrl])
185186
expect(addStdout).toContain(`Added PowerSync remote (powersync): ${firstUrl}`)
186187

187188
expect(await getRemoteUrl()).toBe(firstUrl)
188189

189-
const secondUrl = 'powergit::https://example.dev/orgs/acme/repos/runtime'
190+
const secondUrl = 'powergit::/acme/runtime'
190191
const { stdout: updateStdout } = await runCli(['remote', 'add', 'powersync', secondUrl])
191192
expect(updateStdout).toContain(`Added PowerSync remote (powersync): ${secondUrl}`)
192193

@@ -195,7 +196,7 @@ describe('powergit CLI e2e', () => {
195196

196197
it('respects REMOTE_NAME overrides', async () => {
197198
const customRemote = 'powersync-upstream'
198-
const remoteUrl = 'powergit::https://example.dev/orgs/acme/repos/mobile'
199+
const remoteUrl = 'powergit::/acme/mobile'
199200

200201
const { stdout } = await runCli(
201202
['remote', 'add', 'powersync', remoteUrl],
@@ -505,7 +506,7 @@ describeLive('powergit sync against live PowerSync stack', () => {
505506
const remoteName = liveStackConfig.remoteName
506507
const daemonBaseUrl = process.env.POWERSYNC_DAEMON_URL ?? 'http://127.0.0.1:5030'
507508
const branchName = `cli-stream-${Date.now().toString(36)}`
508-
const { org, repo } = parsePowerSyncUrl(remoteUrl)
509+
const { org, repo } = resolvePowergitRemote(remoteUrl)
509510
const streamTargets = buildRepoStreamTargets(org, repo)
510511
const streamKeys = streamTargets.map(formatStreamKey)
511512

0 commit comments

Comments
 (0)