From 05433ec151d00268043b9e141a3a52b9b1d4c5f1 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 6 Nov 2025 08:13:30 +0100 Subject: [PATCH] feat: inject env GOOGLE_CLIENT_ID --- plugins/nextjs-plugin/src/index.ts | 5 +- plugins/nextjs-plugin/src/tests/index.spec.ts | 9 ++- plugins/plugin-tools/src/config.ts | 37 ++++++++- plugins/plugin-tools/src/init.ts | 8 +- plugins/plugin-tools/src/tests/config.spec.ts | 80 ++++++++++++++++++- plugins/plugin-tools/src/tests/init.spec.ts | 16 +++- plugins/plugin-tools/src/types.ts | 12 +++ plugins/vite-plugin/src/index.ts | 5 +- plugins/vite-plugin/src/tests/index.spec.ts | 6 +- 9 files changed, 165 insertions(+), 13 deletions(-) diff --git a/plugins/nextjs-plugin/src/index.ts b/plugins/nextjs-plugin/src/index.ts index b29a74d..f880ed9 100644 --- a/plugins/nextjs-plugin/src/index.ts +++ b/plugins/nextjs-plugin/src/index.ts @@ -43,7 +43,7 @@ export const withJuno = async (params?: { try { const args: ConfigArgs = {params: junoParams, mode}; - const {satelliteId, orbiterId, icpIds, container} = await initConfig(args); + const {satelliteId, orbiterId, icpIds, container, authClientIds} = await initConfig(args); const prefix = prefixParam ?? 'NEXT_PUBLIC_'; @@ -87,6 +87,9 @@ export const withJuno = async (params?: { }), ...(container !== undefined && { [`${prefix}CONTAINER`]: container + }), + ...(authClientIds?.google !== undefined && { + [`${prefix}GOOGLE_CLIENT_ID`]: authClientIds.google }) }, ...REQUIRED_NEXT_CONFIG diff --git a/plugins/nextjs-plugin/src/tests/index.spec.ts b/plugins/nextjs-plugin/src/tests/index.spec.ts index 48b2f9d..587cd65 100644 --- a/plugins/nextjs-plugin/src/tests/index.spec.ts +++ b/plugins/nextjs-plugin/src/tests/index.spec.ts @@ -14,6 +14,9 @@ describe('withJuno', () => { const spy = vi.spyOn(pluginTools, 'initConfig').mockResolvedValue({ satelliteId: 'sat-id', orbiterId: 'orb-id', + authClientIds: { + google: 'google-client-id-123' + }, icpIds: { internetIdentityId: 'ii-id', icpLedgerId: 'ledger-id', @@ -51,7 +54,8 @@ describe('withJuno', () => { NEXT_PUBLIC_CYCLES_LEDGER_ID: 'cycles-ledger-id', NEXT_PUBLIC_SNS_WASM_ID: 'sns-wasm-id', NEXT_PUBLIC_NNS_DAPP_ID: 'nns-dapp-id', - NEXT_PUBLIC_CONTAINER: 'http://localhost:1234' + NEXT_PUBLIC_CONTAINER: 'http://localhost:1234', + NEXT_PUBLIC_GOOGLE_CLIENT_ID: 'google-client-id-123' } }); }); @@ -60,6 +64,7 @@ describe('withJuno', () => { vi.spyOn(pluginTools, 'initConfig').mockResolvedValue({ satelliteId: 'sat-id', orbiterId: undefined, + authClientIds: undefined, icpIds: undefined, container: undefined }); @@ -74,13 +79,13 @@ describe('withJuno', () => { vi.spyOn(pluginTools, 'initConfig').mockResolvedValue({ satelliteId: 'sat-id', orbiterId: undefined, + authClientIds: undefined, icpIds: undefined, container: undefined }); const result = await withJuno({ nextConfig: { - output: 'standalone', env: { FOO: 'bar' } diff --git a/plugins/plugin-tools/src/config.ts b/plugins/plugin-tools/src/config.ts index 20d7ff5..19d2e2f 100644 --- a/plugins/plugin-tools/src/config.ts +++ b/plugins/plugin-tools/src/config.ts @@ -1,3 +1,4 @@ +import type {JunoConfig} from '@junobuild/config'; import { CMC_ID, CYCLES_INDEX_ID, @@ -15,7 +16,7 @@ import { } from './constants'; import {JunoPluginError} from './error'; import {assertJunoConfig, junoConfigExist, readJunoConfig} from './fs'; -import type {ConfigArgs, IcpIds, JunoParams} from './types'; +import type {AuthClientIds, ConfigArgs, IcpIds, JunoParams} from './types'; export const useDockerContainer = ({params, mode}: ConfigArgs): boolean => params?.container !== false && @@ -153,3 +154,37 @@ export const container = ({ return undefined; }; + +export const authClientIds = async ({mode}: ConfigArgs): Promise => { + const exist = await junoConfigExist(); + + if (!exist) { + return undefined; + } + + const config: JunoConfig = await readJunoConfig({mode}); + + if (config === undefined || !('satellite' in config)) { + return undefined; + } + + const { + satellite: {authentication} + } = config; + + if (authentication === undefined) { + return undefined; + } + + const {google} = authentication; + + if (google === undefined) { + return undefined; + } + + const {clientId} = google; + + return { + google: clientId + }; +}; diff --git a/plugins/plugin-tools/src/init.ts b/plugins/plugin-tools/src/init.ts index e3cdf1d..b1b1069 100644 --- a/plugins/plugin-tools/src/init.ts +++ b/plugins/plugin-tools/src/init.ts @@ -1,4 +1,5 @@ import { + authClientIds as authClientIdsConfig, container as containerConfig, icpIds as icpIdsConfig, orbiterId as orbiterIdConfig, @@ -6,13 +7,14 @@ import { useDockerContainer } from './config'; import {assertJunoConfig} from './fs'; -import type {ConfigArgs, IcpIds} from './types'; +import type {AuthClientIds, ConfigArgs, IcpIds} from './types'; export const initConfig = async ( args: ConfigArgs ): Promise<{ satelliteId: string; orbiterId: string | undefined; + authClientIds: AuthClientIds | undefined; icpIds: IcpIds | undefined; container: string | undefined; }> => { @@ -22,9 +24,10 @@ export const initConfig = async ( await assertJunoConfig(); } - const [satelliteId, orbiterId, icpIds, container] = await Promise.all([ + const [satelliteId, orbiterId, authClientIds, icpIds, container] = await Promise.all([ satelliteIdConfig(args), orbiterIdConfig(args), + authClientIdsConfig(args), Promise.resolve(icpIdsConfig()), Promise.resolve(containerConfig(args)) ]); @@ -32,6 +35,7 @@ export const initConfig = async ( return { satelliteId, orbiterId, + authClientIds, icpIds, container }; diff --git a/plugins/plugin-tools/src/tests/config.spec.ts b/plugins/plugin-tools/src/tests/config.spec.ts index 74b162f..c32fc4f 100644 --- a/plugins/plugin-tools/src/tests/config.spec.ts +++ b/plugins/plugin-tools/src/tests/config.spec.ts @@ -1,7 +1,14 @@ import type {JunoConfig} from '@junobuild/config'; import * as configLoader from '@junobuild/config-loader'; -import {container, icpIds, orbiterId, satelliteId, useDockerContainer} from '../config'; +import { + authClientIds, + container, + icpIds, + orbiterId, + satelliteId, + useDockerContainer +} from '../config'; import { CMC_ID, CYCLES_INDEX_ID, @@ -406,4 +413,75 @@ describe('config', () => { expect(url).toBeUndefined(); }); }); + + describe('authClientIds', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns undefined if no config file exists', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(false); + + const ids = await authClientIds({params: {}, mode: MODE_DEVELOPMENT}); + + expect(ids).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 ids = await authClientIds({params: {}, mode: MODE_DEVELOPMENT}); + + expect(ids).toBeUndefined(); + }); + + it('returns undefined if config has no satellite key', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({} as unknown as JunoConfig); + + const ids = await authClientIds({params: {}, mode: MODE_DEVELOPMENT}); + + expect(ids).toBeUndefined(); + }); + + it('returns undefined if satellite.authentication is undefined', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ + satellite: {} + } as unknown as JunoConfig); + + const ids = await authClientIds({params: {}, mode: MODE_DEVELOPMENT}); + + expect(ids).toBeUndefined(); + }); + + it('returns undefined if authentication.google is undefined', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ + satellite: {authentication: {}} + } as unknown as JunoConfig); + + const ids = await authClientIds({params: {}, mode: MODE_DEVELOPMENT}); + + expect(ids).toBeUndefined(); + }); + + it('returns google clientId when set', async () => { + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); + vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ + satellite: { + authentication: { + google: {clientId: 'google-client-id-123'} + } + } + } as unknown as JunoConfig); + + const ids = await authClientIds({params: {}, mode: 'production'}); + + expect(ids).toEqual({google: 'google-client-id-123'}); + }); + }); }); diff --git a/plugins/plugin-tools/src/tests/init.spec.ts b/plugins/plugin-tools/src/tests/init.spec.ts index 27b312a..580a518 100644 --- a/plugins/plugin-tools/src/tests/init.spec.ts +++ b/plugins/plugin-tools/src/tests/init.spec.ts @@ -51,6 +51,8 @@ describe('init', () => { nnsDappId: NNS_DAPP_ID }; + const mockGoogleClientId = '1234567890-abcdef.apps.googleusercontent.com'; + let spyJunoConfigExist: MockInstance; let spyReadJunoConfig: MockInstance; @@ -59,13 +61,16 @@ describe('init', () => { spyJunoConfigExist = vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); spyReadJunoConfig = vi.spyOn(configLoader, 'readJunoConfig').mockResolvedValue({ - satellite: {ids: {production: 'mock-satellite-id'}}, + satellite: { + ids: {production: 'mock-satellite-id'}, + authentication: {google: {clientId: mockGoogleClientId}} + }, orbiter: {id: 'mock-orbiter-id'} }); }); it('returns config for development', async () => { - vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(false); + vi.spyOn(configLoader, 'junoConfigExist').mockResolvedValue(true); const result = await initConfig({ params: {}, @@ -75,12 +80,13 @@ describe('init', () => { expect(result).toEqual({ orbiterId: undefined, satelliteId: DOCKER_SATELLITE_ID, + authClientIds: {google: mockGoogleClientId}, icpIds: expectedIcpIds, container: DOCKER_CONTAINER_URL }); - expect(configLoader.junoConfigExist).toHaveBeenCalled(); - expect(spyReadJunoConfig).not.toHaveBeenCalled(); + expect(configLoader.junoConfigExist).toHaveResolvedTimes(3); + expect(spyReadJunoConfig).toHaveBeenCalledTimes(3); }); it('returns config without container for production', async () => { @@ -89,6 +95,7 @@ describe('init', () => { expect(result).toEqual({ satelliteId: 'mock-satellite-id', orbiterId: 'mock-orbiter-id', + authClientIds: {google: mockGoogleClientId}, icpIds: expectedIcpIds, container: undefined }); @@ -145,6 +152,7 @@ describe('init', () => { expect(result).toEqual({ satelliteId: 'mock-satellite-id', orbiterId: 'mock-orbiter-id', + authClientIds: {google: mockGoogleClientId}, icpIds: expectedIcpIds, container: undefined }); diff --git a/plugins/plugin-tools/src/types.ts b/plugins/plugin-tools/src/types.ts index 0f70585..c57553d 100644 --- a/plugins/plugin-tools/src/types.ts +++ b/plugins/plugin-tools/src/types.ts @@ -115,3 +115,15 @@ export interface IcpIds { */ nnsDappId: string; } + +/** + * Represent the provider client IDs for the OpenID authentication resolved + * from the juno.config file for the corresponding mode. + */ +export interface AuthClientIds { + /** + * Google client ID. + * @type {string} + */ + google: string; +} diff --git a/plugins/vite-plugin/src/index.ts b/plugins/vite-plugin/src/index.ts index 797a173..47c35a1 100644 --- a/plugins/vite-plugin/src/index.ts +++ b/plugins/vite-plugin/src/index.ts @@ -20,7 +20,7 @@ export default function Juno(params?: JunoParams): Plugin { try { const args: ConfigArgs = {params, mode}; - const {satelliteId, orbiterId, icpIds, container} = await initConfig(args); + const {satelliteId, orbiterId, icpIds, container, authClientIds} = await initConfig(args); const prefix = `import.meta.env.${envPrefix ?? 'VITE_'}`; @@ -62,6 +62,9 @@ export default function Juno(params?: JunoParams): Plugin { }), ...(container !== undefined && { [`${prefix}CONTAINER`]: JSON.stringify(container) + }), + ...(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 70971c6..0ca2dd8 100644 --- a/plugins/vite-plugin/src/tests/index.spec.ts +++ b/plugins/vite-plugin/src/tests/index.spec.ts @@ -12,6 +12,9 @@ describe('vite-plugin-juno', () => { const spyInitConfig = vi.spyOn(pluginTools, 'initConfig').mockResolvedValue({ satelliteId: 'sat-id', orbiterId: 'orb-id', + authClientIds: { + google: 'google-client-id-123' + }, icpIds: { internetIdentityId: 'ii-id', icpLedgerId: 'ledger-id', @@ -54,7 +57,8 @@ describe('vite-plugin-juno', () => { 'import.meta.env.VITE_CYCLES_LEDGER_ID': JSON.stringify('cycles-ledger-id'), '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_CONTAINER': JSON.stringify('http://localhost:1234'), + 'import.meta.env.VITE_GOOGLE_CLIENT_ID': JSON.stringify('google-client-id-123') } }); });