Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/zip-it-and-ship-it/src/runtimes/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -124,6 +125,7 @@ const zipFunction: ZipFunction = async function ({
rewrites,
runtimeAPIVersion,
srcFiles,
generator,
})

await cleanupFunction?.()
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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";')
})

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;')
})
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to use kebab case for the service name? Is that important enough that warrants shipping our own implementation of a kebab case transformer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, because Datadog has a special naming convention. Even @ signs, which are common in the generator, should not occur.

input
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/[@#//$\s_\\.-]+/g, ' ')
.trim()
.toLowerCase()
.split(' ')
.join('-')

const getEntryFileContents = (
mainPath: string,
moduleFormat: string,
Expand Down Expand Up @@ -158,10 +172,33 @@ 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 separate name and version.
// pop the last part (the version) and join the rest with a @ again.
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(generator)
}
}

const contents = `
var SERVICE_NAME = ${JSON.stringify(serviceName)};
var SERVICE_VERSION = ${JSON.stringify(serviceVersion)};
${readFileSync(filePath, 'utf8')}
`

return {
contents,
Expand Down
9 changes: 7 additions & 2 deletions packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface ZipNodeParameters {
rewrites?: Map<string, string>
runtimeAPIVersion: number
srcFiles: string[]
generator?: string
}

const addBootstrapFile = function (srcFiles: string[], aliases: Map<string, string>) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/zip-it-and-ship-it/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down