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
22 changes: 5 additions & 17 deletions packages/action/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DEFAULT_TRIGGER,
type GitGlimpseConfig,
} from '@git-glimpse/core';
import { resolveBaseUrl } from './resolve-base-url.js';

function streamCommand(cmd: string, args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -154,13 +155,12 @@ async function run(): Promise<void> {
return;
}

const baseUrl = resolveBaseUrl(config, previewUrlInput);
if (!baseUrl) {
core.setFailed(
'No base URL available. Set app.previewUrl or app.startCommand + app.readyWhen in config.'
);
const baseUrlResult = resolveBaseUrl(config, previewUrlInput);
if (!baseUrlResult.url) {
core.setFailed(baseUrlResult.error!);
return;
}
const baseUrl = baseUrlResult.url;

if (config.setup) {
core.info(`Running setup: ${config.setup}`);
Expand Down Expand Up @@ -225,18 +225,6 @@ async function run(): Promise<void> {
}
}

function resolveBaseUrl(config: GitGlimpseConfig, previewUrlOverride?: string): string | null {
const previewUrl = previewUrlOverride ?? config.app.previewUrl;
if (previewUrl) {
const resolved = process.env[previewUrl] ?? previewUrl;
return resolved.startsWith('http') ? resolved : null;
}
if (config.app.readyWhen?.url) {
const u = new URL(config.app.readyWhen.url);
return u.origin;
}
return 'http://localhost:3000';
}

async function startApp(
startCommand: string,
Expand Down
31 changes: 31 additions & 0 deletions packages/action/src/resolve-base-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { GitGlimpseConfig } from '../../core/src/config/schema.js';

export function resolveBaseUrl(
config: GitGlimpseConfig,
previewUrlOverride?: string
): { url: string; error?: never } | { url?: never; error: string } {
const previewUrl = previewUrlOverride ?? config.app.previewUrl;
if (previewUrl) {
const resolved = process.env[previewUrl];
if (resolved === undefined) {
// previewUrl is a literal URL string, not an env var name
if (previewUrl.startsWith('http')) return { url: previewUrl };
return {
error:
`app.previewUrl is set to "${previewUrl}" but it doesn't look like a URL and no env var with that name was found. ` +
`Set it to a full URL (e.g. "https://my-preview.vercel.app") or an env var name that is available in this workflow job.`,
};
}
if (!resolved.startsWith('http')) {
return {
error: `Env var "${previewUrl}" was found but its value "${resolved}" is not a valid URL. Expected a value starting with "http".`,
};
}
return { url: resolved };
}
if (config.app.readyWhen?.url) {
const u = new URL(config.app.readyWhen.url);
return { url: u.origin };
}
return { url: 'http://localhost:3000' };
}
73 changes: 73 additions & 0 deletions tests/unit/resolve-base-url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { resolveBaseUrl } from '../../packages/action/src/resolve-base-url.js';
import type { GitGlimpseConfig } from '../../packages/core/src/config/schema.js';

function makeConfig(app: GitGlimpseConfig['app']): GitGlimpseConfig {
return {
app,
llm: { provider: 'anthropic' },
recording: { format: 'gif', maxDuration: 30, viewport: { width: 1280, height: 720 } },
} as unknown as GitGlimpseConfig;
}

describe('resolveBaseUrl', () => {
const originalEnv = process.env;

beforeEach(() => {
process.env = { ...originalEnv };
});

afterEach(() => {
process.env = originalEnv;
});

it('returns URL directly when previewUrl is a literal http URL', () => {
const result = resolveBaseUrl(makeConfig({ previewUrl: 'https://my-preview.vercel.app' }));
expect(result.url).toBe('https://my-preview.vercel.app');
expect(result.error).toBeUndefined();
});

it('resolves previewUrl as env var name when the env var is set', () => {
process.env['VERCEL_PREVIEW_URL'] = 'https://my-preview.vercel.app';
const result = resolveBaseUrl(makeConfig({ previewUrl: 'VERCEL_PREVIEW_URL' }));
expect(result.url).toBe('https://my-preview.vercel.app');
expect(result.error).toBeUndefined();
});

it('returns a descriptive error when env var name is set but env var is missing', () => {
delete process.env['VERCEL_PREVIEW_URL'];
const result = resolveBaseUrl(makeConfig({ previewUrl: 'VERCEL_PREVIEW_URL' }));
expect(result.url).toBeUndefined();
expect(result.error).toMatch(/VERCEL_PREVIEW_URL/);
expect(result.error).toMatch(/env var/);
});

it('returns a descriptive error when env var is set but value is not a URL', () => {
process.env['PREVIEW_URL'] = 'not-a-url';
const result = resolveBaseUrl(makeConfig({ previewUrl: 'PREVIEW_URL' }));
expect(result.url).toBeUndefined();
expect(result.error).toMatch(/PREVIEW_URL/);
expect(result.error).toMatch(/not a valid URL/);
});

it('falls back to localhost when no previewUrl and no readyWhen', () => {
const result = resolveBaseUrl(makeConfig({ startCommand: 'npm run dev' }));
expect(result.url).toBe('http://localhost:3000');
});

it('uses readyWhen.url origin as base when set', () => {
const result = resolveBaseUrl(
makeConfig({ startCommand: 'npm run dev', readyWhen: { url: 'http://localhost:4000/health' } })
);
expect(result.url).toBe('http://localhost:4000');
});

it('previewUrlOverride takes precedence over config', () => {
process.env['OVERRIDE_URL'] = 'https://override.example.com';
const result = resolveBaseUrl(
makeConfig({ previewUrl: 'SOME_OTHER_VAR' }),
'OVERRIDE_URL'
);
expect(result.url).toBe('https://override.example.com');
});
});
Loading