From a6c39fc6c7d9eba8bd0e8489b8ddb80681b8d865 Mon Sep 17 00:00:00 2001 From: Lukas Holzer Date: Tue, 25 Jun 2024 15:29:00 +0200 Subject: [PATCH 1/2] fix: use new version of serverless functions api provide the service name and version as variables to it. --- .../src/runtimes/node/index.ts | 4 +- .../runtimes/node/utils/entry_file.test.ts | 34 ++++++++++++++++ .../src/runtimes/node/utils/entry_file.ts | 39 ++++++++++++++++++- .../src/runtimes/node/utils/zip.ts | 9 ++++- packages/zip-it-and-ship-it/vitest.config.ts | 2 +- 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/index.ts b/packages/zip-it-and-ship-it/src/runtimes/node/index.ts index bf94d1f343..3ed6e10eb8 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/index.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/index.ts @@ -108,6 +108,7 @@ const zipFunction: ZipFunction = async function ({ createPluginsModulesPathAliases(srcFiles, pluginsModulesPath, aliases, finalBasePath) + const generator = mergedConfig?.generator || getInternalValue(isInternal) const zipPath = await zipNodeJs({ aliases, archiveFormat, @@ -124,6 +125,7 @@ const zipFunction: ZipFunction = async function ({ rewrites, runtimeAPIVersion, srcFiles, + generator, }) await cleanupFunction?.() @@ -153,7 +155,7 @@ const zipFunction: ZipFunction = async function ({ config: mergedConfig, displayName: mergedConfig?.name, entryFilename: zipPath.entryFilename, - generator: mergedConfig?.generator || getInternalValue(isInternal), + generator, timeout: mergedConfig?.timeout, inputs, includedFiles, diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts new file mode 100644 index 0000000000..d23e5ff844 --- /dev/null +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts @@ -0,0 +1,34 @@ +import { test, expect } from 'vitest' + +import { getTelemetryFile, kebabCase } from './entry_file.js' + +test('kebab-case', () => { + expect(kebabCase('hello-world')).toBe('hello-world') + expect(kebabCase('hello World')).toBe('hello-world') + expect(kebabCase('--Hello--World--')).toBe('hello-world') + expect(kebabCase('Next.js Runtime')).toBe('next-js-runtime') + expect(kebabCase('@netlify/plugin-nextjs@14')).toBe('netlify-plugin-nextjs-14') + expect(kebabCase('CamelCaseShould_Be_transformed')).toBe('camel-case-should-be-transformed') + expect(kebabCase('multiple spaces')).toBe('multiple-spaces') +}) + +test('getTelemetryFile should handle no defined generator', () => { + const telemetryFile = getTelemetryFile() + expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs') + expect(telemetryFile.contents).toContain('var SERVICE_NAME = undefined;') + expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;') +}) + +test('getTelemetryFile should handle internalFunc generator', () => { + const telemetryFile = getTelemetryFile('internalFunc') + expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs') + expect(telemetryFile.contents).toContain('var SERVICE_NAME = "internal-func";') + expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;') +}) + +test('getTelemetryFile should handle generator with version', () => { + const telemetryFile = getTelemetryFile('@netlify/plugin-nextjs@14.13.2') + expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs') + expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";') + expect(telemetryFile.contents).toContain('var SERVICE_VERSION = "14.13.2";') +}) diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts index 2d1bed29ab..9775f83ff3 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts @@ -26,6 +26,20 @@ export interface EntryFile { filename: string } +/** + * A minimal implementation of kebab-case. + * It is used to transform the generator name into a service name for the telemetry file. + * As DataDog has a special handling for the service name, we need to make sure it is kebab-case. + */ +export const kebabCase = (input: string): string => + input + .replace(/([a-z])([A-Z])/g, '$1 $2') + .replace(/[@#//$\s_\\.-]+/g, ' ') + .trim() + .toLowerCase() + .split(' ') + .join('-') + const getEntryFileContents = ( mainPath: string, moduleFormat: string, @@ -158,10 +172,31 @@ const getEntryFileName = ({ return `${basename(filename, extname(filename))}${extension}` } -export const getTelemetryFile = (): EntryFile => { +export const getTelemetryFile = (generator?: string): EntryFile => { // TODO: switch with import.meta.resolve once we drop support for Node 16.x const filePath = require.resolve('@netlify/serverless-functions-api/instrumentation.js') - const contents = readFileSync(filePath, 'utf8') + let serviceName: string | undefined + let serviceVersion: string | undefined + + if (generator) { + // the generator can be something like: `@netlify/plugin-nextjs@14.13.2` + // following the convention of name@version but it must not have a version. + // split the generator by the @ sign to seperate name and version. + // pop the last part (the version) and join the rest with a @ again. + const parts = generator.split('@') + if (parts.length > 1) { + serviceVersion = parts.pop() + serviceName = kebabCase(parts.join('@')) + } else { + serviceName = kebabCase(parts[0]) + } + } + + const contents = ` +var SERVICE_NAME = ${JSON.stringify(serviceName)}; +var SERVICE_VERSION = ${JSON.stringify(serviceVersion)}; +${readFileSync(filePath, 'utf8')} +` return { contents, diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts index 6b16b982e0..bc50d90580 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts @@ -54,6 +54,7 @@ interface ZipNodeParameters { rewrites?: Map runtimeAPIVersion: number srcFiles: string[] + generator?: string } const addBootstrapFile = function (srcFiles: string[], aliases: Map) { @@ -188,6 +189,7 @@ const createZipArchive = async function ({ rewrites, runtimeAPIVersion, srcFiles, + generator, }: ZipNodeParameters) { const destPath = join(destFolder, `${basename(filename, extension)}.zip`) const { archive, output } = startZip(destPath) @@ -233,7 +235,7 @@ const createZipArchive = async function ({ addEntryFileToZip(archive, entryFile) } - const telemetryFile = getTelemetryFile() + const telemetryFile = getTelemetryFile(generator) if (featureFlags.zisi_add_instrumentation_loader === true) { addEntryFileToZip(archive, telemetryFile) @@ -268,7 +270,10 @@ const createZipArchive = async function ({ export const zipNodeJs = function ({ archiveFormat, ...options -}: ZipNodeParameters & { archiveFormat: ArchiveFormat }): Promise<{ path: string; entryFilename: string }> { +}: ZipNodeParameters & { archiveFormat: ArchiveFormat }): Promise<{ + path: string + entryFilename: string +}> { if (archiveFormat === ARCHIVE_FORMAT.ZIP) { return createZipArchive(options) } diff --git a/packages/zip-it-and-ship-it/vitest.config.ts b/packages/zip-it-and-ship-it/vitest.config.ts index e1dd5ab009..91b9bb63ec 100644 --- a/packages/zip-it-and-ship-it/vitest.config.ts +++ b/packages/zip-it-and-ship-it/vitest.config.ts @@ -5,7 +5,7 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { setupFiles: ['./tests/helpers/vitest_setup.ts'], - include: ['tests/**/*.test.ts'], + include: ['tests/**/*.test.ts', 'src/**/*.test.ts'], testTimeout: 90_000, deps: { // Disable vitest handling of imports to these paths, especially the tmpdir is important as we extract functions to there From ff0066564b046fcae231f0af5385d30965cd4d18 Mon Sep 17 00:00:00 2001 From: Lukas Holzer Date: Tue, 25 Jun 2024 16:22:32 +0200 Subject: [PATCH 2/2] chore: implement eduardos feedback --- .../src/runtimes/node/utils/entry_file.test.ts | 7 +++++++ .../src/runtimes/node/utils/entry_file.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts index d23e5ff844..47e9c2c2a7 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.test.ts @@ -32,3 +32,10 @@ test('getTelemetryFile should handle generator with version', () => { expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";') expect(telemetryFile.contents).toContain('var SERVICE_VERSION = "14.13.2";') }) + +test('getTelemetryFile should handle generator without version', () => { + const telemetryFile = getTelemetryFile('@netlify/plugin-nextjs') + expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs') + expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";') + expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;') +}) diff --git a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts index 9775f83ff3..a13c3eebb9 100644 --- a/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts +++ b/packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts @@ -181,14 +181,16 @@ export const getTelemetryFile = (generator?: string): EntryFile => { if (generator) { // the generator can be something like: `@netlify/plugin-nextjs@14.13.2` // following the convention of name@version but it must not have a version. - // split the generator by the @ sign to seperate name and version. + // split the generator by the @ sign to separate name and version. // pop the last part (the version) and join the rest with a @ again. - const parts = generator.split('@') - if (parts.length > 1) { - serviceVersion = parts.pop() - serviceName = kebabCase(parts.join('@')) + const versionSepPos = generator.lastIndexOf('@') + if (versionSepPos > 1) { + const name = generator.substring(0, versionSepPos) + const version = generator.substring(versionSepPos + 1) + serviceVersion = version + serviceName = kebabCase(name) } else { - serviceName = kebabCase(parts[0]) + serviceName = kebabCase(generator) } }