From 631e53c983eead59595b9c1b392de865d9100ea2 Mon Sep 17 00:00:00 2001 From: Lukas Holzer Date: Thu, 5 Oct 2023 15:14:41 +0200 Subject: [PATCH] feat: automatic create tsconfig for editor support on functions Fixes https://github.com/netlify/pod-dev-foundations/issues/595 --- package-lock.json | 12 ++-- src/commands/functions/functions-create.mjs | 5 ++ .../functions/check-tsconfig-for-v2-api.mjs | 62 +++++++++++++++++++ src/lib/functions/netlify-function.mjs | 9 +++ 4 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 src/lib/functions/check-tsconfig-for-v2-api.mjs diff --git a/package-lock.json b/package-lock.json index da66da23b1e..32e9ad3f6af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14920,9 +14920,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.0.tgz", - "integrity": "sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -35572,9 +35572,9 @@ } }, "get-tsconfig": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.0.tgz", - "integrity": "sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", "dev": true, "requires": { "resolve-pkg-maps": "^1.0.0" diff --git a/src/commands/functions/functions-create.mjs b/src/commands/functions/functions-create.mjs index f0fca7fdbf6..8b12b0015c5 100644 --- a/src/commands/functions/functions-create.mjs +++ b/src/commands/functions/functions-create.mjs @@ -16,6 +16,7 @@ import fetch from 'node-fetch' import ora from 'ora' import { fileExistsAsync } from '../../lib/fs.mjs' +import { checkTsconfigForV2Api } from '../../lib/functions/check-tsconfig-for-v2-api.mjs' import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.mjs' import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.mjs' import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs' @@ -715,6 +716,10 @@ const functionsCreate = async (name, options, command) => { const functionsDir = functionType === 'edge' ? await ensureEdgeFuncDirExists(command) : await ensureFunctionDirExists(command) + if (functionType === 'serverless') { + await checkTsconfigForV2Api({ functionsDir }) + } + /* either download from URL or scaffold from template */ const mainFunc = options.url ? downloadFromURL : scaffoldFromTemplate await mainFunc(command, options, name, functionsDir, functionType) diff --git a/src/lib/functions/check-tsconfig-for-v2-api.mjs b/src/lib/functions/check-tsconfig-for-v2-api.mjs new file mode 100644 index 00000000000..d39b2b7a8ba --- /dev/null +++ b/src/lib/functions/check-tsconfig-for-v2-api.mjs @@ -0,0 +1,62 @@ +// @ts-check +import { existsSync } from 'fs' +import { writeFile } from 'fs/promises' +import { createRequire } from 'module' +import { join } from 'path' + +import { NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, log } from '../../utils/command-helpers.mjs' + +/** + * The `tsconfig.json` we are going to write to the functions directory. + * We use a template string instead of JSON.stringify to be able to add comments to the JSON. + * Comments inside the JSON are accepted by TypeScript and tsconfig. + */ +const TSCONFIG_TMPL = `{ + // "extends": "../tsconfig.json", /** If you want to share configuration enable the extends property (like strict: true) */ + "compilerOptions": { + "noEmit": true /** This tsconfig.json is only used for type checking and editor support */, + "module": "ESNext", + "moduleResolution": "Bundler" /** This is needed to use .ts file extensions as we bundle it */, + "allowImportingTsExtensions": true /** This allows using .ts file extension instead of the standard .js extension. We allow this for better compatibility with Deno Edge Functions */, + "checkJs": true /** Enable type checking in JavaScript files as well */, + "allowJs": true /** Make JavaScript files part of the program as well */ + } +} +` + +/** + * Function that is responsible for validating the typescript configuration for serverless functions. + * It validates the `tsconfig.json` settings and if they don't comply it will throw an error. + * @param {object} config + * @param {string|undefined} config.functionsDir An absolute path to the functions directory + */ +export async function checkTsconfigForV2Api(config) { + // if no functionsDir is specified or the dir does not exist just return + if (!config.functionsDir || !existsSync(config.functionsDir)) { + return + } + + try { + const require = createRequire(config.functionsDir) + require.resolve('@netlify/functions') + } catch { + log( + `${NETLIFYDEVWARN} Please install the ${chalk.dim( + '@netlify/functions', + )} package to get a better typed experience!`, + ) + } + const tsconfig = join(config.functionsDir, 'tsconfig.json') + + if (existsSync(tsconfig)) { + return + } + + await writeFile(tsconfig, TSCONFIG_TMPL, 'utf-8') + + log( + `${NETLIFYDEVLOG} Successfully created a ${chalk.dim( + 'tsconfig.json', + )} file in your functions folder for better Editor support!`, + ) +} diff --git a/src/lib/functions/netlify-function.mjs b/src/lib/functions/netlify-function.mjs index 1002cde8696..2a6041f3f98 100644 --- a/src/lib/functions/netlify-function.mjs +++ b/src/lib/functions/netlify-function.mjs @@ -8,6 +8,8 @@ import semver from 'semver' import { error as errorExit } from '../../utils/command-helpers.mjs' import { BACKGROUND } from '../../utils/functions/get-functions.mjs' +import { checkTsconfigForV2Api } from './check-tsconfig-for-v2-api.mjs' + const TYPESCRIPT_EXTENSIONS = new Set(['.cts', '.mts', '.ts']) const V2_MIN_NODE_VERSION = '18.0.0' @@ -138,6 +140,13 @@ export default class NetlifyFunction { try { const { includedFiles = [], schedule, srcFiles, ...buildData } = await this.buildQueue + + if (buildData.runtimeAPIVersion === 2) { + // Check f the tsconfig.json file exists for the editor support. + // If not create it + await checkTsconfigForV2Api({ functionsDir: this.directory }) + } + const srcFilesSet = new Set(srcFiles) const srcFilesDiff = this.getSrcFilesDiff(srcFilesSet)