UNPKG

@coder/backstage-plugin-coder

Version:

Create and manage Coder workspaces from Backstage

111 lines (108 loc) 3.93 kB
import { useMemo, useState } from 'react'; import { optional, union, literal, undefined_, object, record, string, parse } from 'valibot'; import { useApi } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { useEntity, getEntitySourceLocation } from '@backstage/plugin-catalog-react'; import '../components/CoderProvider/CoderAuthProvider.esm.js'; import { useCoderAppConfig } from '../components/CoderProvider/CoderAppConfigProvider.esm.js'; import '../components/CoderProvider/CoderProvider.esm.js'; const workspaceCreationModeSchema = optional( union( [literal("manual"), literal("auto")], "If defined, createMode must be 'manual' or 'auto'" ) ); const yamlConfigSchema = union([ undefined_(), object({ templateName: optional(string()), mode: workspaceCreationModeSchema, params: optional( record( string(), // Defining record value with undefined case as a safety net if user // hasn't or can't turn on the noUncheckedIndexedAccess compiler option union([string(), undefined_()]), "If defined, params must be JSON-serializable as Record<string, string>" ) ) }) ]); function compileCoderConfig(appConfig, rawYamlConfig, repoUrl) { const { workspaces, deployment } = appConfig; const yamlConfig = parse(yamlConfigSchema, rawYamlConfig); const mode = yamlConfig?.mode ?? workspaces.defaultMode ?? "manual"; const templateName = yamlConfig?.templateName ?? workspaces.defaultTemplateName; const urlParams = new URLSearchParams({ mode }); const compiledParams = {}; const paramsPrecedence = [workspaces.params, yamlConfig?.params ?? {}]; for (const params of paramsPrecedence) { for (const key in params) { if (!params.hasOwnProperty(key)) { continue; } const value = params[key]; if (typeof value === "string") { compiledParams[key] = value; urlParams.set(`param.${key}`, value); } } } let cleanedRepoUrl = repoUrl; if (repoUrl !== void 0) { cleanedRepoUrl = repoUrl.replace(/\/tree\/[\w._-]+\/?$/, ""); for (const key of workspaces.repoUrlParamKeys) { compiledParams[key] = cleanedRepoUrl; urlParams.set(`param.${key}`, cleanedRepoUrl); } } let creationUrl = void 0; if (templateName) { const safeTemplate = encodeURIComponent(templateName); creationUrl = `${deployment.accessUrl}/templates/${safeTemplate}/workspace?${urlParams.toString()}`; } return { mode, creationUrl, templateName, repoUrl: cleanedRepoUrl, isReadingEntityData: yamlConfig !== void 0, repoUrlParamKeys: workspaces.repoUrlParamKeys, params: compiledParams }; } function useCoderWorkspacesConfig({ readEntityData = false }) { const appConfig = useCoderAppConfig(); const { rawYaml, repoUrl } = useDynamicEntity(readEntityData); return useMemo( () => compileCoderConfig(appConfig, rawYaml, repoUrl), // Backstage seems to have stabilized the value of rawYamlConfig, so even // when it's an object, useMemo shouldn't re-run unnecessarily [appConfig, rawYaml, repoUrl] ); } function useDynamicEntity(readEntityData) { const [initialReadSetting] = useState(readEntityData); if (readEntityData !== initialReadSetting) { throw new Error( 'The value of "readEntityData" is not allowed to change across re-renders' ); } let rawYaml = void 0; let repoUrl = void 0; if (readEntityData) { const { entity } = useEntity(); const sourceControlRegistry = useApi(scmIntegrationsApiRef); const repoData = getEntitySourceLocation( entity, sourceControlRegistry ); rawYaml = entity.spec?.coder; repoUrl = repoData?.locationTargetUrl; } return { rawYaml, repoUrl }; } export { compileCoderConfig, useCoderWorkspacesConfig }; //# sourceMappingURL=useCoderWorkspacesConfig.esm.js.map