diff --git a/.changeset/five-rats-compare.md b/.changeset/five-rats-compare.md new file mode 100644 index 00000000..4c57eec3 --- /dev/null +++ b/.changeset/five-rats-compare.md @@ -0,0 +1,5 @@ +--- +'@srcbook/api': patch +--- + +Add typing support and errors for environment variables diff --git a/packages/api/server/http.mts b/packages/api/server/http.mts index 3c8c469d..d40e528d 100644 --- a/packages/api/server/http.mts +++ b/packages/api/server/http.mts @@ -29,6 +29,7 @@ import { importSrcbookFromSrcmdFile, importSrcbookFromSrcmdText, importSrcbookFromSrcmdUrl, + updateSessionEnvTypeDeclarations, } from '../srcbook/index.mjs'; import { readdir } from '../fs-utils.mjs'; import { EXAMPLE_SRCBOOKS } from '../srcbook/examples.mjs'; @@ -276,12 +277,14 @@ router.options('/sessions/:id/secrets/:name', cors()); router.put('/sessions/:id/secrets/:name', cors(), async (req, res) => { const { id, name } = req.params; await associateSecretWithSession(name, id); + await updateSessionEnvTypeDeclarations(id); return res.status(204).end(); }); router.delete('/sessions/:id/secrets/:name', cors(), async (req, res) => { const { id, name } = req.params; await disassociateSecretWithSession(name, id); + await updateSessionEnvTypeDeclarations(id); return res.status(204).end(); }); diff --git a/packages/api/srcbook/config.mts b/packages/api/srcbook/config.mts index 5ad9d778..532f4a60 100644 --- a/packages/api/srcbook/config.mts +++ b/packages/api/srcbook/config.mts @@ -32,14 +32,17 @@ export function buildTSPackageJson() { export function buildTsconfigJson() { return { compilerOptions: { + types: [], + strict: true, module: 'nodenext', moduleResolution: 'nodenext', target: 'es2022', resolveJsonModule: true, noEmit: true, allowImportingTsExtensions: true, + noPropertyAccessFromIndexSignature: true, }, - include: ['src/**/*'], + include: ['src/**/*', 'env.d.ts'], exclude: ['node_modules'], }; } diff --git a/packages/api/srcbook/index.mts b/packages/api/srcbook/index.mts index 3f9db7b9..0d53131f 100644 --- a/packages/api/srcbook/index.mts +++ b/packages/api/srcbook/index.mts @@ -12,9 +12,16 @@ import { toFormattedJSON } from '../utils.mjs'; import { readdir } from '../fs-utils.mjs'; import { SRCBOOKS_DIR } from '../constants.mjs'; import { EXAMPLE_SRCBOOKS } from '../srcbook/examples.mjs'; -import { pathToCodeFile, pathToPackageJson, pathToReadme, pathToTsconfigJson } from './path.mjs'; +import { + pathToCodeFile, + pathToPackageJson, + pathToReadme, + pathToSrcbook, + pathToTsconfigJson, +} from './path.mjs'; import { buildJSPackageJson, buildTSPackageJson, buildTsconfigJson } from './config.mjs'; import type { SessionType } from '../types.mjs'; +import { getSecretsAssociatedWithSession } from '../config.mjs'; function writeCellOnlyToDisk(srcbookDir: string, cell: PackageJsonCellType | CodeCellType) { const path = @@ -203,6 +210,10 @@ async function createSrcbookDir(basename: string = randomid()) { const srcPath = Path.join(srcbookDirectoryPath, 'src'); await fs.mkdir(srcPath); + const envTypeDeclarationPath = Path.join(srcbookDirectoryPath, 'env.d.ts'); + const envTypeDeclarationFileContent = generateEnvTypesFile({}); + await fs.writeFile(envTypeDeclarationPath, envTypeDeclarationFileContent); + return srcbookDirectoryPath; } @@ -217,3 +228,33 @@ export function removeSrcbook(srcbookDir: string) { export function removeCodeCellFromDisk(srcbookDir: string, filename: string) { return fs.rm(pathToCodeFile(srcbookDir, filename)); } + +export async function updateSessionEnvTypeDeclarations(sessionId: string) { + const sessionSecrets = await getSecretsAssociatedWithSession(sessionId); + if (!Object.entries(sessionSecrets).length) return; + const envTypeDeclarationFileContent = generateEnvTypesFile(sessionSecrets); + const srcbookDir = pathToSrcbook(sessionId); + const envDtsPath = Path.join(srcbookDir, 'env.d.ts'); + await fs.writeFile(envDtsPath, envTypeDeclarationFileContent); +} + +export function generateEnvTypesFile(secrets: Record) { + const envTypes = Object.entries(secrets).length + ? Object.keys(secrets) + .map((key) => `readonly ${key}: string;`) + .join('\n') + : ''; + + return ` + declare namespace NodeJS { + interface ProcessEnv { + ${envTypes} + } +} + +declare var process: { + env: NodeJS.ProcessEnv; +}; + + `; +} diff --git a/packages/web/src/routes/session.tsx b/packages/web/src/routes/session.tsx index c40a95a9..2d6aa39f 100644 --- a/packages/web/src/routes/session.tsx +++ b/packages/web/src/routes/session.tsx @@ -37,7 +37,6 @@ async function loader({ params }: LoaderFunctionArgs) { loadSessions(), loadSession({ id: params.id! }), ]); - return { config, srcbooks, session }; }