Skip to content
Open
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
44 changes: 42 additions & 2 deletions src/registry/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,57 @@
* config + environment: the registry URL may be given inline or via an env-var *name*, and the
* access token is always read from an env-var name (never persisted as a value). This is the single
* source the publish (and later checkout) client reads its target + credentials from.
*
* The older `HANDOFF_CLOUD_*` names still work as deprecated fallbacks; see
* {@link DEPRECATED_ENV_ALIASES}.
*/

import type { Config } from '../types/config';
import { cliAuthMatchesRegistry, cliAuthTokenIsValid, readCliAuth } from '../cli/auth/store';
// Imported straight from the module, not the `auth` barrel, to keep the database schema off the CLI
// startup path; this file is resolved for every command.
import { resolveRegistrySyncSecret } from './auth/sync-secret';
import { Logger } from '../utils/logger';

/** Default env-var name holding the remote registry base URL. */
export const DEFAULT_REGISTRY_URL_ENV = 'HANDOFF_REGISTRY_URL';

/** Default env-var name holding the connected-workspace access token. */
export const DEFAULT_REGISTRY_ACCESS_TOKEN_ENV = 'HANDOFF_REGISTRY_ACCESS_TOKEN';

/**
* Old env-var names we still read, keyed by the name that replaced them. Keeps workspaces set up
* from the older docs working instead of silently reporting no registry.
*/
const DEPRECATED_ENV_ALIASES: Readonly<Record<string, string>> = {
[DEFAULT_REGISTRY_URL_ENV]: 'HANDOFF_CLOUD_URL',
[DEFAULT_REGISTRY_ACCESS_TOKEN_ENV]: 'HANDOFF_CLOUD_TOKEN',
};

/** Aliases we've already warned about, so each one warns once per run and not once per lookup. */
const warnedAliases = new Set<string>();

/**
* Value of the named env var, falling back to its deprecated alias. Only the default names have
* aliases: a custom `urlEnv`/`accessTokenEnv` is a deliberate choice, so a stray `HANDOFF_CLOUD_*`
* must not stand in for it. The warning goes through {@link Logger.warn}, so
* `HANDOFF_LOG_LEVEL=error` silences it in CI.
*/
const readConnectionEnv = (name: string): string => {
const configured = process.env[name]?.trim();
if (configured) return configured;

const alias = DEPRECATED_ENV_ALIASES[name];
const inherited = alias ? process.env[alias]?.trim() : '';
if (!inherited) return '';

if (!warnedAliases.has(alias)) {
warnedAliases.add(alias);
Logger.warn(`${alias} is deprecated and will be removed in a future release; set ${name} instead.`);
}
return inherited;
};

/** Fully resolved connected-workspace registry connection. */
export interface ResolvedRegistryConnection {
/** Resolved registry base URL (inline config value wins, else the named env var), or `''`. */
Expand All @@ -35,15 +72,18 @@ export interface ResolvedRegistryConnection {
/**
* Resolve the connected-workspace registry connection from config + environment. Never throws —
* Callers inspect the resolved URL and access token to surface specific configuration errors.
*
* `urlEnv` and `accessTokenEnv` always report the canonical name, even when an alias supplied the
* value, so errors built from them point at the variable to adopt.
*/
export const resolveRegistryConnection = (config: Config | null | undefined): ResolvedRegistryConnection => {
const connection = config?.runtime?.registryConnection;
const urlEnv = connection?.urlEnv?.trim() || DEFAULT_REGISTRY_URL_ENV;
const accessTokenEnv = connection?.accessTokenEnv?.trim() || DEFAULT_REGISTRY_ACCESS_TOKEN_ENV;

const inlineUrl = connection?.url?.trim();
const url = (inlineUrl || process.env[urlEnv]?.trim()) ?? '';
const accessToken = process.env[accessTokenEnv]?.trim() ?? '';
const url = inlineUrl || readConnectionEnv(urlEnv);
const accessToken = readConnectionEnv(accessTokenEnv);

return {
url,
Expand Down