From 5acf466a44c3c27ad768e36bb3c564f22f471c2a Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 24 Jan 2026 09:02:05 +0100 Subject: [PATCH] feat: support for JUNO_API_URL --- plugins/nextjs-plugin/src/index.ts | 6 +- plugins/nextjs-plugin/src/tests/index.spec.ts | 10 ++- plugins/plugin-tools/src/config.ts | 12 ++++ plugins/plugin-tools/src/init.ts | 10 ++- plugins/plugin-tools/src/tests/config.spec.ts | 70 +++++++++++++++++++ plugins/plugin-tools/src/tests/init.spec.ts | 4 +- plugins/vite-plugin/src/index.ts | 6 +- plugins/vite-plugin/src/tests/index.spec.ts | 4 +- 8 files changed, 111 insertions(+), 11 deletions(-) diff --git a/plugins/nextjs-plugin/src/index.ts b/plugins/nextjs-plugin/src/index.ts index 5da887f..a2fa679 100644 --- a/plugins/nextjs-plugin/src/index.ts +++ b/plugins/nextjs-plugin/src/index.ts @@ -43,7 +43,8 @@ export const withJuno = async (params?: { try { const args: ConfigArgs = {params: junoParams, mode}; - const {satelliteId, orbiterId, icpIds, container, authClientIds} = await initConfig(args); + const {satelliteId, orbiterId, icpIds, container, authClientIds, apiUrl} = + await initConfig(args); const prefix = prefixParam ?? 'NEXT_PUBLIC_'; @@ -88,6 +89,9 @@ export const withJuno = async (params?: { ...(container !== undefined && { [`${prefix}CONTAINER`]: container }), + ...(apiUrl !== undefined && { + [`${prefix}JUNO_API_URL`]: apiUrl + }), ...(authClientIds?.google !== undefined && { [`${prefix}GOOGLE_CLIENT_ID`]: authClientIds.google }), diff --git a/plugins/nextjs-plugin/src/tests/index.spec.ts b/plugins/nextjs-plugin/src/tests/index.spec.ts index 6b7af97..afed775 100644 --- a/plugins/nextjs-plugin/src/tests/index.spec.ts +++ b/plugins/nextjs-plugin/src/tests/index.spec.ts @@ -30,7 +30,8 @@ describe('withJuno', () => { snsWasmId: 'sns-wasm-id', nnsDappId: 'nns-dapp-id' }, - container: 'http://localhost:1234' + container: 'http://localhost:1234', + apiUrl: 'http://localhost:3000' }); const result = await withJuno(); @@ -56,6 +57,7 @@ describe('withJuno', () => { NEXT_PUBLIC_SNS_WASM_ID: 'sns-wasm-id', NEXT_PUBLIC_NNS_DAPP_ID: 'nns-dapp-id', NEXT_PUBLIC_CONTAINER: 'http://localhost:1234', + NEXT_PUBLIC_JUNO_API_URL: 'http://localhost:3000', NEXT_PUBLIC_GOOGLE_CLIENT_ID: 'google-client-id-123', NEXT_PUBLIC_GITHUB_CLIENT_ID: 'github-client-id-123' } @@ -68,7 +70,8 @@ describe('withJuno', () => { orbiterId: undefined, authClientIds: undefined, icpIds: undefined, - container: undefined + container: undefined, + apiUrl: undefined }); const result = await withJuno({prefix: 'TEST_'}); @@ -83,7 +86,8 @@ describe('withJuno', () => { orbiterId: undefined, authClientIds: undefined, icpIds: undefined, - container: undefined + container: undefined, + apiUrl: undefined }); const result = await withJuno({ diff --git a/plugins/plugin-tools/src/config.ts b/plugins/plugin-tools/src/config.ts index b50178f..815a181 100644 --- a/plugins/plugin-tools/src/config.ts +++ b/plugins/plugin-tools/src/config.ts @@ -187,3 +187,15 @@ export const authClientIds = async ({mode}: ConfigArgs): Promise => { + const exist = await junoConfigExist(); + + if (!exist) { + return undefined; + } + + const config = await readJunoConfig({mode}); + + return config?.api?.url; +}; diff --git a/plugins/plugin-tools/src/init.ts b/plugins/plugin-tools/src/init.ts index b1b1069..368c7f6 100644 --- a/plugins/plugin-tools/src/init.ts +++ b/plugins/plugin-tools/src/init.ts @@ -1,4 +1,5 @@ import { + apiUrl as apiUrlConfig, authClientIds as authClientIdsConfig, container as containerConfig, icpIds as icpIdsConfig, @@ -17,6 +18,7 @@ export const initConfig = async ( authClientIds: AuthClientIds | undefined; icpIds: IcpIds | undefined; container: string | undefined; + apiUrl: string | undefined; }> => { // We perform the checks in advance to throw the potential error only once. // We also assert the config file exists only if the Docker container should not be used given that the file won't be required otherwise. @@ -24,12 +26,13 @@ export const initConfig = async ( await assertJunoConfig(); } - const [satelliteId, orbiterId, authClientIds, icpIds, container] = await Promise.all([ + const [satelliteId, orbiterId, authClientIds, icpIds, container, apiUrl] = await Promise.all([ satelliteIdConfig(args), orbiterIdConfig(args), authClientIdsConfig(args), Promise.resolve(icpIdsConfig()), - Promise.resolve(containerConfig(args)) + Promise.resolve(containerConfig(args)), + apiUrlConfig(args) ]); return { @@ -37,6 +40,7 @@ export const initConfig = async ( orbiterId, authClientIds, icpIds, - container + container, + apiUrl }; }; diff --git a/plugins/plugin-tools/src/tests/config.spec.ts b/plugins/plugin-tools/src/tests/config.spec.ts index 7fef6f9..fe85c06 100644 --- a/plugins/plugin-tools/src/tests/config.spec.ts +++ b/plugins/plugin-tools/src/tests/config.spec.ts @@ -2,6 +2,7 @@ import type {JunoConfig} from '@junobuild/config'; import * as configLoader from '@junobuild/config-loader'; import { + apiUrl, authClientIds, container, icpIds, @@ -526,4 +527,73 @@ describe('config', () => { expect(ids).toEqual({google: 'google-client-id-123', github: 'github-client-id-123'}); }); }); + + describe('apiUrl', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns undefined if no config file exists', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(false); + + const url = await apiUrl({params: {}, mode: MODE_DEVELOPMENT}); + + expect(url).toBeUndefined(); + }); + + it('returns undefined if config is undefined', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue( + undefined as unknown as JunoConfig + ); + + const url = await apiUrl({params: {}, mode: MODE_DEVELOPMENT}); + + expect(url).toBeUndefined(); + }); + + it('returns undefined if config has no api key', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({} as unknown as JunoConfig); + + const url = await apiUrl({params: {}, mode: MODE_DEVELOPMENT}); + + expect(url).toBeUndefined(); + }); + + it('returns undefined if api empty object', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ + api: {} + } as unknown as JunoConfig); + + const url = await apiUrl({params: {}, mode: MODE_DEVELOPMENT}); + + expect(url).toBeUndefined(); + }); + + it('returns undefined if api.url is undefined', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ + api: {url: undefined} + } as unknown as JunoConfig); + + const url = await apiUrl({params: {}, mode: MODE_DEVELOPMENT}); + + expect(url).toBeUndefined(); + }); + + it('returns api url when set', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ + api: { + url: 'http://localhost:3000' + } + } as unknown as JunoConfig); + + const url = await apiUrl({params: {}, mode: 'production'}); + + expect(url).toEqual('http://localhost:3000'); + }); + }); }); diff --git a/plugins/plugin-tools/src/tests/init.spec.ts b/plugins/plugin-tools/src/tests/init.spec.ts index 580a518..e15c9cc 100644 --- a/plugins/plugin-tools/src/tests/init.spec.ts +++ b/plugins/plugin-tools/src/tests/init.spec.ts @@ -85,8 +85,8 @@ describe('init', () => { container: DOCKER_CONTAINER_URL }); - expect(configLoader.junoConfigExist).toHaveResolvedTimes(3); - expect(spyReadJunoConfig).toHaveBeenCalledTimes(3); + expect(configLoader.junoConfigExist).toHaveResolvedTimes(4); + expect(spyReadJunoConfig).toHaveBeenCalledTimes(4); }); it('returns config without container for production', async () => { diff --git a/plugins/vite-plugin/src/index.ts b/plugins/vite-plugin/src/index.ts index 23693c7..9d76bb1 100644 --- a/plugins/vite-plugin/src/index.ts +++ b/plugins/vite-plugin/src/index.ts @@ -20,7 +20,8 @@ export default function Juno(params?: JunoParams): Plugin { try { const args: ConfigArgs = {params, mode}; - const {satelliteId, orbiterId, icpIds, container, authClientIds} = await initConfig(args); + const {satelliteId, orbiterId, icpIds, container, authClientIds, apiUrl} = + await initConfig(args); const prefix = `import.meta.env.${envPrefix ?? 'VITE_'}`; @@ -63,6 +64,9 @@ export default function Juno(params?: JunoParams): Plugin { ...(container !== undefined && { [`${prefix}CONTAINER`]: JSON.stringify(container) }), + ...(apiUrl !== undefined && { + [`${prefix}JUNO_API_URL`]: JSON.stringify(apiUrl) + }), ...(authClientIds?.google !== undefined && { [`${prefix}GOOGLE_CLIENT_ID`]: JSON.stringify(authClientIds.google) }), diff --git a/plugins/vite-plugin/src/tests/index.spec.ts b/plugins/vite-plugin/src/tests/index.spec.ts index 2c547ae..5f29755 100644 --- a/plugins/vite-plugin/src/tests/index.spec.ts +++ b/plugins/vite-plugin/src/tests/index.spec.ts @@ -28,7 +28,8 @@ describe('vite-plugin-juno', () => { snsWasmId: 'sns-wasm-id', nnsDappId: 'nns-dapp-id' }, - container: 'http://localhost:1234' + container: 'http://localhost:1234', + apiUrl: 'http://localhost:3000' }); const plugin = Juno() as unknown as { @@ -59,6 +60,7 @@ describe('vite-plugin-juno', () => { 'import.meta.env.VITE_SNS_WASM_ID': JSON.stringify('sns-wasm-id'), 'import.meta.env.VITE_NNS_DAPP_ID': JSON.stringify('nns-dapp-id'), 'import.meta.env.VITE_CONTAINER': JSON.stringify('http://localhost:1234'), + 'import.meta.env.VITE_JUNO_API_URL': JSON.stringify('http://localhost:3000'), 'import.meta.env.VITE_GOOGLE_CLIENT_ID': JSON.stringify('google-client-id-123'), 'import.meta.env.VITE_GITHUB_CLIENT_ID': JSON.stringify('github-client-id-123') }