feat(main): build-time service URL defaults for packaged builds (#667)

Auth API, relay director, push gateway, connector and artifacts URLs can now be
set per build (ORCA_BUILD_CLOUD_API_URL, ORCA_BUILD_RELAY_URL,
ORCA_BUILD_PUSH_GATEWAY_URL, ORCA_BUILD_CONNECTOR_URL,
ORCA_BUILD_ARTIFACTS_API_URL, or the matching keys in
config/cloud-build-defaults.json). Unset keys keep today's built-in defaults,
and dev run-time env overrides still win. No values change.
This commit is contained in:
Neil
2026-11-13 05:45:21 -08:00
committed by GitHub
parent e70193040f
commit a4c9a8bbbc
9 changed files with 199 additions and 18 deletions
+65 -4
View File
@@ -1,13 +1,74 @@
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
type ServiceUrlKey =
| 'authApiUrl'
| 'relayDirectorUrl'
| 'pushGatewayUrl'
| 'connectorUrl'
| 'artifactsApiUrl'
type BuildDefaultsFile = Partial<Record<ServiceUrlKey, string>> & {
billingApiUrl?: string
cloudSandboxUrl?: string
allowDevSigning?: boolean
}
// Build-time service URL defaults; an unset key keeps the main-process literal default.
const SERVICE_URL_ENV: Record<ServiceUrlKey, string> = {
authApiUrl: 'ORCA_BUILD_CLOUD_API_URL',
relayDirectorUrl: 'ORCA_BUILD_RELAY_URL',
pushGatewayUrl: 'ORCA_BUILD_PUSH_GATEWAY_URL',
connectorUrl: 'ORCA_BUILD_CONNECTOR_URL',
artifactsApiUrl: 'ORCA_BUILD_ARTIFACTS_API_URL'
}
const SERVICE_URL_KEYS: readonly ServiceUrlKey[] = [
'authApiUrl',
'relayDirectorUrl',
'pushGatewayUrl',
'connectorUrl',
'artifactsApiUrl'
]
function serviceUrl(name: string, value: string | undefined): string | undefined {
const trimmed = value?.trim()
if (!trimmed) {
return undefined
}
// Fail the build rather than ship a default the app would reject at run time.
const url = URL.canParse(trimmed) ? new URL(trimmed) : null
if (
!url ||
url.protocol !== 'https:' ||
url.username ||
url.password ||
url.search ||
url.hash ||
url.pathname !== '/'
) {
throw new Error(`${name} must be an https origin`)
}
return url.origin
}
function readDefaultsFile(): BuildDefaultsFile {
return JSON.parse(readFileSync(resolve('config/cloud-build-defaults.json'), 'utf8'))
}
/** Integration-branch backends baked into main (packaged builds read no shell env); build-time env overrides the file. */
export function cloudBuildDefaultsDefine(env: NodeJS.ProcessEnv = process.env): string {
const file: { billingApiUrl?: string; cloudSandboxUrl?: string; allowDevSigning?: boolean } =
JSON.parse(readFileSync(resolve('config/cloud-build-defaults.json'), 'utf8'))
export function cloudBuildDefaultsDefine(
env: NodeJS.ProcessEnv = process.env,
file: BuildDefaultsFile = readDefaultsFile()
): string {
const serviceUrls: Partial<Record<ServiceUrlKey, string>> = {}
for (const key of SERVICE_URL_KEYS) {
const envName = SERVICE_URL_ENV[key]
serviceUrls[key] = serviceUrl(envName, env[envName] || file[key])
}
return JSON.stringify({
billingApiUrl: env.ORCA_BUILD_BILLING_API_URL || file.billingApiUrl,
cloudSandboxUrl: env.ORCA_BUILD_CLOUD_SANDBOX_URL || file.cloudSandboxUrl,
allowDevSigning: file.allowDevSigning === true
allowDevSigning: file.allowDevSigning === true,
...serviceUrls
})
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"_comment": "Integration-branch only (stablyai/orca-private cloud/integration). Public dev endpoints baked into packaged builds; no secrets. ORCA_BUILD_BILLING_API_URL / ORCA_BUILD_CLOUD_SANDBOX_URL at build time, or ORCA_BILLING_API_URL / ORCA_CLOUD_SANDBOX_URL at run time, override them. allowDevSigning lets this packaged build honor ORCA_CLOUD_SANDBOX_DEV_KEY_FILE (dev operator key) until login.onorca.dev ships the sandbox-token endpoint; the key itself is never in git.",
"_comment": "Integration-branch only (stablyai/orca-private cloud/integration). Public dev endpoints baked into packaged builds; no secrets. ORCA_BUILD_BILLING_API_URL / ORCA_BUILD_CLOUD_SANDBOX_URL at build time, or ORCA_BILLING_API_URL / ORCA_CLOUD_SANDBOX_URL at run time, override them. allowDevSigning lets this packaged build honor ORCA_CLOUD_SANDBOX_DEV_KEY_FILE (dev operator key) until login.onorca.dev ships the sandbox-token endpoint; the key itself is never in git. Optional build-time service URL defaults (authApiUrl, relayDirectorUrl, pushGatewayUrl, connectorUrl, artifactsApiUrl; or ORCA_BUILD_CLOUD_API_URL / ORCA_BUILD_RELAY_URL / ORCA_BUILD_PUSH_GATEWAY_URL / ORCA_BUILD_CONNECTOR_URL / ORCA_BUILD_ARTIFACTS_API_URL at build time) must be https origins; unset keys keep the built-in defaults.",
"billingApiUrl": "https://orca-billing-dev.lovecast.workers.dev",
"cloudSandboxUrl": "https://orca-sandbox-control-dev.lovecast.workers.dev",
"allowDevSigning": true
+4 -1
View File
@@ -1,4 +1,5 @@
import { app } from 'electron'
import { buildServiceUrlDefault } from '../cloud-build-defaults'
const PRODUCTION_ARTIFACTS_API_URL = 'https://share.onorca.dev'
@@ -16,7 +17,9 @@ export function resolveArtifactCloudApiUrl(
packaged = isPackaged()
): string {
const candidate = override?.trim() || env.ORCA_ARTIFACTS_API_URL?.trim()
const url = new URL(candidate || PRODUCTION_ARTIFACTS_API_URL)
const url = new URL(
candidate || buildServiceUrlDefault('artifactsApiUrl', PRODUCTION_ARTIFACTS_API_URL)
)
const loopback = ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)
const firstParty = ['onorca.dev', 'orca.dev'].some(
(domain) => url.hostname === domain || url.hostname.endsWith(`.${domain}`)
@@ -0,0 +1,78 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
vi.mock('electron', () => ({ app: { isPackaged: false } }))
import { resolveArtifactCloudApiUrl } from './artifacts/artifact-cloud-config'
import { resolveConnectorOrigin } from './connector-cloud/connector-origin'
import { getOrcaCloudAuthConfig } from './orca-profiles/cloud-auth/profile-cloud-auth-config'
import { resolvePushGatewayOrigin } from './runtime/push/push-gateway-origin'
const authApiUrl = (env: NodeJS.ProcessEnv, packaged: boolean): string | null => {
const result = getOrcaCloudAuthConfig({ ORCA_CLOUD_CLIENT_ID: 'client', ...env }, packaged)
return result.configured ? result.config.apiBaseUrl : null
}
const relayDirectorUrl = (env: NodeJS.ProcessEnv, packaged: boolean): string | null => {
const devAuth = packaged ? {} : { ORCA_CLOUD_API_URL: 'http://127.0.0.1:8788' }
const result = getOrcaCloudAuthConfig(
{ ORCA_CLOUD_CLIENT_ID: 'client', ...devAuth, ...env },
packaged
)
return result.configured ? result.config.relayDirectorUrl : null
}
// Each row: build-default key, dev run-time env name, resolver, and today's packaged literal.
const cases = [
{
key: 'authApiUrl',
envName: 'ORCA_CLOUD_API_URL',
resolve: authApiUrl,
literal: 'https://login.onorca.dev'
},
{
key: 'relayDirectorUrl',
envName: 'ORCA_RELAY_URL',
resolve: relayDirectorUrl,
literal: 'https://relay.onorca.dev'
},
{
key: 'pushGatewayUrl',
envName: 'ORCA_PUSH_GATEWAY_URL',
resolve: resolvePushGatewayOrigin,
literal: 'https://push.onorca.dev'
},
{
key: 'connectorUrl',
envName: 'ORCA_CONNECTOR_URL',
resolve: resolveConnectorOrigin,
literal: 'https://connector.onorca.dev'
},
{
key: 'artifactsApiUrl',
envName: 'ORCA_ARTIFACTS_API_URL',
resolve: (env: NodeJS.ProcessEnv, packaged: boolean) =>
resolveArtifactCloudApiUrl(undefined, env, packaged),
literal: 'https://share.onorca.dev'
}
]
describe('build-time service URL defaults', () => {
afterEach(() => vi.unstubAllGlobals())
describe.each(cases)('$key', ({ key, envName, resolve, literal }) => {
it('keeps the current default when the build sets none', () => {
expect(resolve({}, true)).toBe(literal)
vi.stubGlobal('ORCA_CLOUD_BUILD_DEFAULTS', { billingApiUrl: 'https://billing.example' })
expect(resolve({}, true)).toBe(literal)
})
it('uses the build default when set', () => {
vi.stubGlobal('ORCA_CLOUD_BUILD_DEFAULTS', { [key]: 'https://build.onorca.dev' })
expect(resolve({}, true)).toBe('https://build.onorca.dev')
})
it('lets the development run-time override win over the build default', () => {
vi.stubGlobal('ORCA_CLOUD_BUILD_DEFAULTS', { [key]: 'https://build.onorca.dev' })
expect(resolve({ [envName]: 'http://127.0.0.1:8787' }, false)).toBe('http://127.0.0.1:8787')
})
})
})
+19 -1
View File
@@ -1,5 +1,9 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cloudDevSigningAllowed, withCloudBuildDefaults } from './cloud-build-defaults'
import {
buildServiceUrlDefault,
cloudDevSigningAllowed,
withCloudBuildDefaults
} from './cloud-build-defaults'
describe('withCloudBuildDefaults', () => {
afterEach(() => vi.unstubAllGlobals())
@@ -24,6 +28,20 @@ describe('withCloudBuildDefaults', () => {
})
})
it('returns a build-time service URL default, else the fallback', () => {
expect(buildServiceUrlDefault('pushGatewayUrl', 'https://fallback.example')).toBe(
'https://fallback.example'
)
vi.stubGlobal('ORCA_CLOUD_BUILD_DEFAULTS', { pushGatewayUrl: ' ' })
expect(buildServiceUrlDefault('pushGatewayUrl', 'https://fallback.example')).toBe(
'https://fallback.example'
)
vi.stubGlobal('ORCA_CLOUD_BUILD_DEFAULTS', { pushGatewayUrl: 'https://push.example' })
expect(buildServiceUrlDefault('pushGatewayUrl', 'https://fallback.example')).toBe(
'https://push.example'
)
})
it('allows dev signing in packaged builds only when the build opts in', () => {
expect(cloudDevSigningAllowed(false)).toBe(true)
expect(cloudDevSigningAllowed(true)).toBe(false)
+20 -7
View File
@@ -1,20 +1,33 @@
// Packaged builds read no shell env, so the integration build bakes in its backend URLs
// (config/cloud-build-defaults.json via electron.vite.config.ts). Run-time env still wins.
declare const ORCA_CLOUD_BUILD_DEFAULTS: {
billingApiUrl?: string
cloudSandboxUrl?: string
allowDevSigning?: boolean
} | null
type BuildServiceUrlDefaults = {
authApiUrl?: string
relayDirectorUrl?: string
pushGatewayUrl?: string
connectorUrl?: string
artifactsApiUrl?: string
}
function buildDefaults(): {
type CloudBuildDefaults = BuildServiceUrlDefaults & {
billingApiUrl?: string
cloudSandboxUrl?: string
allowDevSigning?: boolean
} {
}
declare const ORCA_CLOUD_BUILD_DEFAULTS: CloudBuildDefaults | null
function buildDefaults(): CloudBuildDefaults {
// oxlint-disable-next-line unicorn/no-typeof-undefined -- undeclared outside electron-vite builds; a bare read throws.
return (typeof ORCA_CLOUD_BUILD_DEFAULTS === 'undefined' ? null : ORCA_CLOUD_BUILD_DEFAULTS) ?? {}
}
export type BuildServiceUrlKey = keyof BuildServiceUrlDefaults
/** Build-time service URL default, or `fallback` when the build sets none. */
export function buildServiceUrlDefault(key: BuildServiceUrlKey, fallback: string): string {
return buildDefaults()[key]?.trim() || fallback
}
export function withCloudBuildDefaults(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const defaults = buildDefaults()
return {
+3 -1
View File
@@ -1,7 +1,9 @@
import { cleanCloudServiceOrigin } from '../../shared/cloud-service-url'
import { buildServiceUrlDefault } from '../cloud-build-defaults'
export function resolveConnectorOrigin(env: NodeJS.ProcessEnv, packaged: boolean): string {
return (
cleanCloudServiceOrigin(env.ORCA_CONNECTOR_URL, !packaged) ?? 'https://connector.onorca.dev'
cleanCloudServiceOrigin(env.ORCA_CONNECTOR_URL, !packaged) ??
buildServiceUrlDefault('connectorUrl', 'https://connector.onorca.dev')
)
}
@@ -4,6 +4,7 @@ import {
cleanCloudServiceOrigin as cleanOrigin
} from '../../../shared/cloud-service-url'
import { resolvePushGatewayOrigin } from '../../runtime/push/push-gateway-origin'
import { buildServiceUrlDefault } from '../../cloud-build-defaults'
export type OrcaCloudAuthConfig = {
apiBaseUrl: string
@@ -52,7 +53,7 @@ export function getOrcaCloudAuthConfig(
const apiBaseUrl = configuredApiBaseUrl
? cleanEndpointUrl(configuredApiBaseUrl)
: packaged
? PRODUCTION_API_BASE_URL
? buildServiceUrlDefault('authApiUrl', PRODUCTION_API_BASE_URL)
: null
const clientId = env.ORCA_CLOUD_CLIENT_ID?.trim() || (packaged ? PRODUCTION_CLIENT_ID : undefined)
if (!apiBaseUrl || !clientId) {
@@ -94,7 +95,8 @@ export function getOrcaCloudAuthConfig(
cleanEndpointUrl(env.ORCA_CLOUD_CONNECTOR_TOKEN_URL) ??
endpoint(apiBaseUrl, '/v1/desktop/auth/connector-token'),
relayDirectorUrl:
cleanOrigin(env.ORCA_RELAY_URL, allowLoopbackHttp) ?? PRODUCTION_RELAY_DIRECTOR_URL,
cleanOrigin(env.ORCA_RELAY_URL, allowLoopbackHttp) ??
buildServiceUrlDefault('relayDirectorUrl', PRODUCTION_RELAY_DIRECTOR_URL),
clientId,
scope: env.ORCA_CLOUD_AUTH_SCOPE?.trim() || DEFAULT_SCOPE
}
+5 -1
View File
@@ -1,5 +1,9 @@
import { cleanCloudServiceOrigin } from '../../../shared/cloud-service-url'
import { buildServiceUrlDefault } from '../../cloud-build-defaults'
export function resolvePushGatewayOrigin(env: NodeJS.ProcessEnv, packaged: boolean): string {
return cleanCloudServiceOrigin(env.ORCA_PUSH_GATEWAY_URL, !packaged) ?? 'https://push.onorca.dev'
return (
cleanCloudServiceOrigin(env.ORCA_PUSH_GATEWAY_URL, !packaged) ??
buildServiceUrlDefault('pushGatewayUrl', 'https://push.onorca.dev')
)
}