@coder/backstage-plugin-coder
Version:
Create and manage Coder workspaces from Backstage
111 lines (108 loc) • 3.93 kB
JavaScript
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