Skip to content

Commit 8a5149b

Browse files
authored
fix: revert b0e66dd (#6334)
1 parent ad5b8b2 commit 8a5149b

File tree

8 files changed

+381
-21777
lines changed

8 files changed

+381
-21777
lines changed

package-lock.json

Lines changed: 159 additions & 21774 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/zip-it-and-ship-it/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@babel/parser": "^7.22.5",
4545
"@babel/types": "7.27.1",
4646
"@netlify/binary-info": "^1.0.0",
47-
"@netlify/serverless-functions-api": "2.0.2",
47+
"@netlify/serverless-functions-api": "^1.41.2",
4848
"@vercel/nft": "0.27.7",
4949
"archiver": "^7.0.0",
5050
"common-path-prefix": "^3.0.0",

packages/zip-it-and-ship-it/src/feature_flags.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export const defaultFlags = {
2727
// If multiple glob stars are in includedFiles, fail the build instead of warning.
2828
zisi_esbuild_fail_double_glob: false,
2929

30+
// Adds the `___netlify-telemetry.mjs` file to the function bundle.
31+
zisi_add_instrumentation_loader: true,
32+
3033
// Dynamically import the function handler.
3134
zisi_dynamic_import_function_handler: false,
3235
} as const
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { test, expect } from 'vitest'
2+
3+
import { getTelemetryFile, kebabCase } from './entry_file.js'
4+
5+
test('kebab-case', () => {
6+
expect(kebabCase('hello-world')).toBe('hello-world')
7+
expect(kebabCase('hello World')).toBe('hello-world')
8+
expect(kebabCase('--Hello--World--')).toBe('hello-world')
9+
expect(kebabCase('Next.js Runtime')).toBe('next-js-runtime')
10+
expect(kebabCase('@netlify/plugin-nextjs@14')).toBe('netlify-plugin-nextjs-14')
11+
expect(kebabCase('CamelCaseShould_Be_transformed')).toBe('camel-case-should-be-transformed')
12+
expect(kebabCase('multiple spaces')).toBe('multiple-spaces')
13+
})
14+
15+
test('getTelemetryFile should handle no defined generator', () => {
16+
const telemetryFile = getTelemetryFile()
17+
expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs')
18+
expect(telemetryFile.contents).toContain('var SERVICE_NAME = undefined;')
19+
expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;')
20+
})
21+
22+
test('getTelemetryFile should handle internalFunc generator', () => {
23+
const telemetryFile = getTelemetryFile('internalFunc')
24+
expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs')
25+
expect(telemetryFile.contents).toContain('var SERVICE_NAME = "internal-func";')
26+
expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;')
27+
})
28+
29+
test('getTelemetryFile should handle generator with version', () => {
30+
const telemetryFile = getTelemetryFile('@netlify/[email protected]')
31+
expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs')
32+
expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";')
33+
expect(telemetryFile.contents).toContain('var SERVICE_VERSION = "14.13.2";')
34+
})
35+
36+
test('getTelemetryFile should handle generator without version', () => {
37+
const telemetryFile = getTelemetryFile('@netlify/plugin-nextjs')
38+
expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs')
39+
expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";')
40+
expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;')
41+
})

packages/zip-it-and-ship-it/src/runtimes/node/utils/entry_file.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { readFileSync } from 'fs'
2+
import { createRequire } from 'module'
13
import { basename, extname, resolve } from 'path'
24

35
import type { FeatureFlags } from '../../../feature_flags.js'
@@ -17,12 +19,29 @@ export const ENTRY_FILE_NAME = '___netlify-entry-point'
1719
export const BOOTSTRAP_FILE_NAME = '___netlify-bootstrap.mjs'
1820
export const BOOTSTRAP_VERSION_FILE_NAME = '___netlify-bootstrap-version'
1921
export const METADATA_FILE_NAME = '___netlify-metadata.json'
22+
export const TELEMETRY_FILE_NAME = '___netlify-telemetry.mjs'
23+
24+
const require = createRequire(import.meta.url)
2025

2126
export interface EntryFile {
2227
contents: string
2328
filename: string
2429
}
2530

31+
/**
32+
* A minimal implementation of kebab-case.
33+
* It is used to transform the generator name into a service name for the telemetry file.
34+
* As DataDog has a special handling for the service name, we need to make sure it is kebab-case.
35+
*/
36+
export const kebabCase = (input: string): string =>
37+
input
38+
.replace(/([a-z])([A-Z])/g, '$1 $2')
39+
.replace(/[@#//$\s_\\.-]+/g, ' ')
40+
.trim()
41+
.toLowerCase()
42+
.split(' ')
43+
.join('-')
44+
2645
const getEntryFileContents = (
2746
mainPath: string,
2847
moduleFormat: string,
@@ -161,6 +180,40 @@ const getEntryFileName = ({
161180
return `${basename(filename, extname(filename))}${extension}`
162181
}
163182

183+
export const getTelemetryFile = (generator?: string): EntryFile => {
184+
// TODO: switch with import.meta.resolve once we drop support for Node 16.x
185+
const filePath = require.resolve('@netlify/serverless-functions-api/instrumentation.js')
186+
let serviceName: string | undefined
187+
let serviceVersion: string | undefined
188+
189+
if (generator) {
190+
// the generator can be something like: `@netlify/[email protected]`
191+
// following the convention of name@version but it must not have a version.
192+
// split the generator by the @ sign to separate name and version.
193+
// pop the last part (the version) and join the rest with a @ again.
194+
const versionSepPos = generator.lastIndexOf('@')
195+
if (versionSepPos > 1) {
196+
const name = generator.substring(0, versionSepPos)
197+
const version = generator.substring(versionSepPos + 1)
198+
serviceVersion = version
199+
serviceName = kebabCase(name)
200+
} else {
201+
serviceName = kebabCase(generator)
202+
}
203+
}
204+
205+
const contents = `
206+
var SERVICE_NAME = ${JSON.stringify(serviceName)};
207+
var SERVICE_VERSION = ${JSON.stringify(serviceVersion)};
208+
${readFileSync(filePath, 'utf8')}
209+
`
210+
211+
return {
212+
contents,
213+
filename: TELEMETRY_FILE_NAME,
214+
}
215+
}
216+
164217
export const getEntryFile = ({
165218
commonPrefix,
166219
featureFlags,

packages/zip-it-and-ship-it/src/runtimes/node/utils/zip.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import os from 'os'
55
import { basename, dirname, extname, join } from 'path'
66

77
import { getPath as getV2APIPath } from '@netlify/serverless-functions-api'
8-
import { copyFile } from 'copy-file'
8+
import { copyFile } from 'cp-file'
99
import pMap from 'p-map'
1010

1111
import {
@@ -27,6 +27,7 @@ import {
2727
conflictsWithEntryFile,
2828
EntryFile,
2929
getEntryFile,
30+
getTelemetryFile,
3031
isNamedLikeEntryFile,
3132
} from './entry_file.js'
3233
import { getMetadataFile } from './metadata_file.js'
@@ -111,14 +112,20 @@ const createDirectory = async function ({
111112
userNamespace,
112113
runtimeAPIVersion,
113114
})
115+
const { contents: telemetryContents, filename: telemetryFilename } = getTelemetryFile()
114116
const functionFolder = join(destFolder, basename(filename, extension))
115117

116118
// Deleting the functions directory in case it exists before creating it.
117119
await rm(functionFolder, { recursive: true, force: true, maxRetries: 3 })
118120
await mkdir(functionFolder, { recursive: true })
119121

120122
// Writing entry files.
121-
await writeFile(join(functionFolder, entryFilename), entryContents)
123+
await Promise.all([
124+
writeFile(join(functionFolder, entryFilename), entryContents),
125+
featureFlags.zisi_add_instrumentation_loader
126+
? writeFile(join(functionFolder, telemetryFilename), telemetryContents)
127+
: Promise.resolve(),
128+
])
122129

123130
if (runtimeAPIVersion === 2) {
124131
addBootstrapFile(srcFiles, aliases)
@@ -192,6 +199,7 @@ const createZipArchive = async function ({
192199
rewrites,
193200
runtimeAPIVersion,
194201
srcFiles,
202+
generator,
195203
}: ZipNodeParameters) {
196204
const destPath = join(destFolder, `${basename(filename, extension)}.zip`)
197205
const { archive, output } = startZip(destPath)
@@ -238,6 +246,11 @@ const createZipArchive = async function ({
238246

239247
addEntryFileToZip(archive, entryFile)
240248
}
249+
const telemetryFile = getTelemetryFile(generator)
250+
251+
if (featureFlags.zisi_add_instrumentation_loader === true) {
252+
addEntryFileToZip(archive, telemetryFile)
253+
}
241254

242255
if (runtimeAPIVersion === 2) {
243256
const bootstrapPath = addBootstrapFile(srcFiles, aliases)

packages/zip-it-and-ship-it/tests/symlinked_included_files.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ test.skipIf(platform() === 'win32')('Symlinked directories from `includedFiles`
5959
expect(await readDirWithType(unzippedPath)).toEqual({
6060
'___netlify-bootstrap.mjs': false,
6161
'___netlify-entry-point.mjs': false,
62+
'___netlify-telemetry.mjs': false,
6263
'___netlify-metadata.json': false,
6364
'function.mjs': false,
6465
[join('node_modules/.pnpm/crazy-dep/package.json')]: false,
@@ -108,6 +109,7 @@ test('preserves multiple symlinks that link to the same target', async () => {
108109
expect(await readDirWithType(join(tmpDir, 'function'))).toEqual({
109110
'___netlify-bootstrap.mjs': false,
110111
'___netlify-entry-point.mjs': false,
112+
'___netlify-telemetry.mjs': false,
111113
'function.mjs': false,
112114
['node_modules/.pnpm/[email protected]/node_modules/is-even'.replace(/\//g, sep)]: true,
113115
['node_modules/.pnpm/[email protected]/node_modules/is-even-or-odd/index.js'.replace(/\//g, sep)]: false,
@@ -151,6 +153,7 @@ test('symlinks in subdir of `includedFiles` are copied over successfully', async
151153
expect(await readDirWithType(join(tmpDir, 'function'))).toEqual({
152154
'___netlify-bootstrap.mjs': false,
153155
'___netlify-entry-point.mjs': false,
156+
'___netlify-telemetry.mjs': false,
154157
'function.cjs': false,
155158
[join('subproject/node_modules/.bin/cli.js')]: true,
156159
[join('subproject/node_modules/tool/cli.js')]: false,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { join } from 'path'
2+
3+
import decompress from 'decompress'
4+
import glob from 'fast-glob'
5+
import { dir as getTmpDir } from 'tmp-promise'
6+
import { expect, test } from 'vitest'
7+
8+
import { ARCHIVE_FORMAT, zipFunction } from '../src/main.js'
9+
10+
import { FIXTURES_ESM_DIR } from './helpers/main.js'
11+
12+
test('The telemetry file should be added by default to the function bundle', async () => {
13+
const { path: tmpDir } = await getTmpDir({ prefix: 'zip-it-test' })
14+
const basePath = join(FIXTURES_ESM_DIR, 'v2-api')
15+
const mainFile = join(basePath, 'function.js')
16+
17+
const result = await zipFunction(mainFile, tmpDir, {
18+
archiveFormat: ARCHIVE_FORMAT.ZIP,
19+
basePath,
20+
config: {
21+
'*': {
22+
includedFiles: ['**'],
23+
},
24+
},
25+
repositoryRoot: basePath,
26+
systemLog: console.log,
27+
debug: true,
28+
internalSrcFolder: undefined,
29+
})
30+
31+
const unzippedPath = join(tmpDir, 'extracted')
32+
33+
await decompress(result!.path, unzippedPath)
34+
35+
const files = await glob('**/*', { cwd: unzippedPath })
36+
expect(files.sort()).toEqual([
37+
'___netlify-bootstrap.mjs',
38+
'___netlify-entry-point.mjs',
39+
'___netlify-metadata.json',
40+
'___netlify-telemetry.mjs',
41+
'function.mjs',
42+
'package.json',
43+
])
44+
})
45+
46+
test('The telemetry file should be added if bundler is none', async () => {
47+
const { path: tmpDir } = await getTmpDir({ prefix: 'zip-it-test' })
48+
const basePath = join(FIXTURES_ESM_DIR, 'v2-api')
49+
const mainFile = join(basePath, 'function.js')
50+
51+
const result = await zipFunction(mainFile, tmpDir, {
52+
archiveFormat: ARCHIVE_FORMAT.NONE,
53+
basePath,
54+
config: {
55+
'*': {
56+
includedFiles: ['**'],
57+
},
58+
},
59+
repositoryRoot: basePath,
60+
systemLog: console.log,
61+
debug: true,
62+
internalSrcFolder: undefined,
63+
})
64+
65+
const files = await glob('**/*', { cwd: result!.path })
66+
expect(files.sort()).toEqual([
67+
'___netlify-bootstrap.mjs',
68+
'___netlify-entry-point.mjs',
69+
'___netlify-telemetry.mjs',
70+
'function.mjs',
71+
'package.json',
72+
])
73+
})
74+
75+
test('The telemetry file should not be added to the bundle if the feature flag is explicitly turned off', async () => {
76+
const { path: tmpDir } = await getTmpDir({ prefix: 'zip-it-test' })
77+
const basePath = join(FIXTURES_ESM_DIR, 'v2-api')
78+
const mainFile = join(basePath, 'function.js')
79+
80+
const result = await zipFunction(mainFile, tmpDir, {
81+
archiveFormat: ARCHIVE_FORMAT.ZIP,
82+
basePath,
83+
config: {
84+
'*': {
85+
includedFiles: ['**'],
86+
},
87+
},
88+
featureFlags: { zisi_add_instrumentation_loader: false },
89+
repositoryRoot: basePath,
90+
systemLog: console.log,
91+
debug: true,
92+
internalSrcFolder: undefined,
93+
})
94+
95+
const unzippedPath = join(tmpDir, 'extracted')
96+
await decompress(result!.path, unzippedPath)
97+
98+
const files = await glob('**/*', { cwd: unzippedPath })
99+
expect(files.sort()).toEqual([
100+
'___netlify-bootstrap.mjs',
101+
'___netlify-entry-point.mjs',
102+
'___netlify-metadata.json',
103+
'function.mjs',
104+
'package.json',
105+
])
106+
})

0 commit comments

Comments
 (0)