@netlify/zip-it-and-ship-it 12.0.0 → 12.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ export declare const defaultFlags: {
7
7
  readonly zisi_output_cjs_extension: false;
8
8
  readonly zisi_unique_entry_file: false;
9
9
  readonly zisi_esbuild_fail_double_glob: false;
10
+ readonly zisi_add_instrumentation_loader: true;
10
11
  readonly zisi_dynamic_import_function_handler: false;
11
12
  };
12
13
  export type FeatureFlags = Partial<Record<keyof typeof defaultFlags, boolean>>;
@@ -19,5 +20,6 @@ export declare const getFlags: (input?: Record<string, boolean>, flags?: {
19
20
  readonly zisi_output_cjs_extension: false;
20
21
  readonly zisi_unique_entry_file: false;
21
22
  readonly zisi_esbuild_fail_double_glob: false;
23
+ readonly zisi_add_instrumentation_loader: true;
22
24
  readonly zisi_dynamic_import_function_handler: false;
23
25
  }) => FeatureFlags;
@@ -18,6 +18,8 @@ export const defaultFlags = {
18
18
  zisi_unique_entry_file: false,
19
19
  // If multiple glob stars are in includedFiles, fail the build instead of warning.
20
20
  zisi_esbuild_fail_double_glob: false,
21
+ // Adds the `___netlify-telemetry.mjs` file to the function bundle.
22
+ zisi_add_instrumentation_loader: true,
21
23
  // Dynamically import the function handler.
22
24
  zisi_dynamic_import_function_handler: false,
23
25
  };
@@ -1,5 +1,5 @@
1
1
  import { basename, dirname, extname, join } from 'path';
2
- import { copyFile } from 'cp-file';
2
+ import { copyFile } from 'copy-file';
3
3
  import { cachedLstat, cachedReaddir } from '../../utils/fs.js';
4
4
  import getInternalValue from '../../utils/get_internal_value.js';
5
5
  import { nonNullable } from '../../utils/non_nullable.js';
@@ -1,5 +1,5 @@
1
1
  import { extname, join } from 'path';
2
- import { copyFile } from 'cp-file';
2
+ import { copyFile } from 'copy-file';
3
3
  import { INVOCATION_MODE } from '../../function.js';
4
4
  import { Priority } from '../../priority.js';
5
5
  import { getTrafficRulesConfig } from '../../rate_limit.js';
@@ -4,10 +4,17 @@ export declare const ENTRY_FILE_NAME = "___netlify-entry-point";
4
4
  export declare const BOOTSTRAP_FILE_NAME = "___netlify-bootstrap.mjs";
5
5
  export declare const BOOTSTRAP_VERSION_FILE_NAME = "___netlify-bootstrap-version";
6
6
  export declare const METADATA_FILE_NAME = "___netlify-metadata.json";
7
+ export declare const TELEMETRY_FILE_NAME = "___netlify-telemetry.mjs";
7
8
  export interface EntryFile {
8
9
  contents: string;
9
10
  filename: string;
10
11
  }
12
+ /**
13
+ * A minimal implementation of kebab-case.
14
+ * It is used to transform the generator name into a service name for the telemetry file.
15
+ * As DataDog has a special handling for the service name, we need to make sure it is kebab-case.
16
+ */
17
+ export declare const kebabCase: (input: string) => string;
11
18
  export declare const isNamedLikeEntryFile: (file: string, { basePath, featureFlags, filename, runtimeAPIVersion, }: {
12
19
  basePath: string;
13
20
  featureFlags: FeatureFlags;
@@ -22,6 +29,7 @@ export declare const conflictsWithEntryFile: (srcFiles: string[], { basePath, ex
22
29
  mainFile: string;
23
30
  runtimeAPIVersion: number;
24
31
  }) => boolean;
32
+ export declare const getTelemetryFile: (generator?: string) => EntryFile;
25
33
  export declare const getEntryFile: ({ commonPrefix, featureFlags, filename, mainFile, moduleFormat, userNamespace, runtimeAPIVersion, }: {
26
34
  commonPrefix: string;
27
35
  featureFlags: FeatureFlags;
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'fs';
2
+ import { createRequire } from 'module';
1
3
  import { basename, extname, resolve } from 'path';
2
4
  import { FunctionBundlingUserError } from '../../../utils/error.js';
3
5
  import { RUNTIME } from '../../runtime.js';
@@ -7,6 +9,20 @@ export const ENTRY_FILE_NAME = '___netlify-entry-point';
7
9
  export const BOOTSTRAP_FILE_NAME = '___netlify-bootstrap.mjs';
8
10
  export const BOOTSTRAP_VERSION_FILE_NAME = '___netlify-bootstrap-version';
9
11
  export const METADATA_FILE_NAME = '___netlify-metadata.json';
12
+ export const TELEMETRY_FILE_NAME = '___netlify-telemetry.mjs';
13
+ const require = createRequire(import.meta.url);
14
+ /**
15
+ * A minimal implementation of kebab-case.
16
+ * It is used to transform the generator name into a service name for the telemetry file.
17
+ * As DataDog has a special handling for the service name, we need to make sure it is kebab-case.
18
+ */
19
+ export const kebabCase = (input) => input
20
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
21
+ .replace(/[@#//$\s_\\.-]+/g, ' ')
22
+ .trim()
23
+ .toLowerCase()
24
+ .split(' ')
25
+ .join('-');
10
26
  const getEntryFileContents = (mainPath, moduleFormat, featureFlags, runtimeAPIVersion) => {
11
27
  const importPath = `.${mainPath.startsWith('/') ? mainPath : `/${mainPath}`}`;
12
28
  if (runtimeAPIVersion === 2) {
@@ -77,6 +93,37 @@ const getEntryFileName = ({ extension, featureFlags, filename, runtimeAPIVersion
77
93
  }
78
94
  return `${basename(filename, extname(filename))}${extension}`;
79
95
  };
96
+ export const getTelemetryFile = (generator) => {
97
+ // TODO: switch with import.meta.resolve once we drop support for Node 16.x
98
+ const filePath = require.resolve('@netlify/serverless-functions-api/instrumentation.js');
99
+ let serviceName;
100
+ let serviceVersion;
101
+ if (generator) {
102
+ // the generator can be something like: `@netlify/plugin-nextjs@14.13.2`
103
+ // following the convention of name@version but it must not have a version.
104
+ // split the generator by the @ sign to separate name and version.
105
+ // pop the last part (the version) and join the rest with a @ again.
106
+ const versionSepPos = generator.lastIndexOf('@');
107
+ if (versionSepPos > 1) {
108
+ const name = generator.substring(0, versionSepPos);
109
+ const version = generator.substring(versionSepPos + 1);
110
+ serviceVersion = version;
111
+ serviceName = kebabCase(name);
112
+ }
113
+ else {
114
+ serviceName = kebabCase(generator);
115
+ }
116
+ }
117
+ const contents = `
118
+ var SERVICE_NAME = ${JSON.stringify(serviceName)};
119
+ var SERVICE_VERSION = ${JSON.stringify(serviceVersion)};
120
+ ${readFileSync(filePath, 'utf8')}
121
+ `;
122
+ return {
123
+ contents,
124
+ filename: TELEMETRY_FILE_NAME,
125
+ };
126
+ };
80
127
  export const getEntryFile = ({ commonPrefix, featureFlags, filename, mainFile, moduleFormat, userNamespace, runtimeAPIVersion, }) => {
81
128
  const mainPath = normalizeFilePath({ commonPrefix, path: mainFile, userNamespace });
82
129
  const extension = getFileExtensionForFormat(moduleFormat, featureFlags, runtimeAPIVersion);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { test, expect } from 'vitest';
2
+ import { getTelemetryFile, kebabCase } from './entry_file.js';
3
+ test('kebab-case', () => {
4
+ expect(kebabCase('hello-world')).toBe('hello-world');
5
+ expect(kebabCase('hello World')).toBe('hello-world');
6
+ expect(kebabCase('--Hello--World--')).toBe('hello-world');
7
+ expect(kebabCase('Next.js Runtime')).toBe('next-js-runtime');
8
+ expect(kebabCase('@netlify/plugin-nextjs@14')).toBe('netlify-plugin-nextjs-14');
9
+ expect(kebabCase('CamelCaseShould_Be_transformed')).toBe('camel-case-should-be-transformed');
10
+ expect(kebabCase('multiple spaces')).toBe('multiple-spaces');
11
+ });
12
+ test('getTelemetryFile should handle no defined generator', () => {
13
+ const telemetryFile = getTelemetryFile();
14
+ expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs');
15
+ expect(telemetryFile.contents).toContain('var SERVICE_NAME = undefined;');
16
+ expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;');
17
+ });
18
+ test('getTelemetryFile should handle internalFunc generator', () => {
19
+ const telemetryFile = getTelemetryFile('internalFunc');
20
+ expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs');
21
+ expect(telemetryFile.contents).toContain('var SERVICE_NAME = "internal-func";');
22
+ expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;');
23
+ });
24
+ test('getTelemetryFile should handle generator with version', () => {
25
+ const telemetryFile = getTelemetryFile('@netlify/plugin-nextjs@14.13.2');
26
+ expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs');
27
+ expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";');
28
+ expect(telemetryFile.contents).toContain('var SERVICE_VERSION = "14.13.2";');
29
+ });
30
+ test('getTelemetryFile should handle generator without version', () => {
31
+ const telemetryFile = getTelemetryFile('@netlify/plugin-nextjs');
32
+ expect(telemetryFile.filename).toBe('___netlify-telemetry.mjs');
33
+ expect(telemetryFile.contents).toContain('var SERVICE_NAME = "netlify-plugin-nextjs";');
34
+ expect(telemetryFile.contents).toContain('var SERVICE_VERSION = undefined;');
35
+ });
@@ -7,7 +7,7 @@ import { copyFile } from 'cp-file';
7
7
  import pMap from 'p-map';
8
8
  import { addZipContent, addZipFile, ARCHIVE_FORMAT, endZip, startZip, } from '../../../archive.js';
9
9
  import { cachedLstat, mkdirAndWriteFile } from '../../../utils/fs.js';
10
- import { BOOTSTRAP_FILE_NAME, METADATA_FILE_NAME, conflictsWithEntryFile, getEntryFile, isNamedLikeEntryFile, } from './entry_file.js';
10
+ import { BOOTSTRAP_FILE_NAME, METADATA_FILE_NAME, conflictsWithEntryFile, getEntryFile, getTelemetryFile, isNamedLikeEntryFile, } from './entry_file.js';
11
11
  import { getMetadataFile } from './metadata_file.js';
12
12
  import { normalizeFilePath } from './normalize_path.js';
13
13
  import { getPackageJsonIfAvailable } from './package_json.js';
@@ -49,12 +49,18 @@ const createDirectory = async function ({ aliases = new Map(), basePath, cache,
49
49
  userNamespace,
50
50
  runtimeAPIVersion,
51
51
  });
52
+ const { contents: telemetryContents, filename: telemetryFilename } = getTelemetryFile();
52
53
  const functionFolder = join(destFolder, basename(filename, extension));
53
54
  // Deleting the functions directory in case it exists before creating it.
54
55
  await rm(functionFolder, { recursive: true, force: true, maxRetries: 3 });
55
56
  await mkdir(functionFolder, { recursive: true });
56
57
  // Writing entry files.
57
- await writeFile(join(functionFolder, entryFilename), entryContents);
58
+ await Promise.all([
59
+ writeFile(join(functionFolder, entryFilename), entryContents),
60
+ featureFlags.zisi_add_instrumentation_loader
61
+ ? writeFile(join(functionFolder, telemetryFilename), telemetryContents)
62
+ : Promise.resolve(),
63
+ ]);
58
64
  if (runtimeAPIVersion === 2) {
59
65
  addBootstrapFile(srcFiles, aliases);
60
66
  }
@@ -94,7 +100,7 @@ const createDirectory = async function ({ aliases = new Map(), basePath, cache,
94
100
  });
95
101
  return { path: functionFolder, entryFilename };
96
102
  };
97
- const createZipArchive = async function ({ aliases = new Map(), basePath, branch, cache, destFolder, extension, featureFlags, filename, mainFile, moduleFormat, rewrites, runtimeAPIVersion, srcFiles, }) {
103
+ const createZipArchive = async function ({ aliases = new Map(), basePath, branch, cache, destFolder, extension, featureFlags, filename, mainFile, moduleFormat, rewrites, runtimeAPIVersion, srcFiles, generator, }) {
98
104
  const destPath = join(destFolder, `${basename(filename, extension)}.zip`);
99
105
  const { archive, output } = startZip(destPath);
100
106
  // There is a naming conflict with the entry file if one of the supporting
@@ -132,6 +138,10 @@ const createZipArchive = async function ({ aliases = new Map(), basePath, branch
132
138
  entryFilename = entryFile.filename;
133
139
  addEntryFileToZip(archive, entryFile);
134
140
  }
141
+ const telemetryFile = getTelemetryFile(generator);
142
+ if (featureFlags.zisi_add_instrumentation_loader === true) {
143
+ addEntryFileToZip(archive, telemetryFile);
144
+ }
135
145
  if (runtimeAPIVersion === 2) {
136
146
  const bootstrapPath = addBootstrapFile(srcFiles, aliases);
137
147
  const { version } = await getPackageJsonIfAvailable(bootstrapPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/zip-it-and-ship-it",
3
- "version": "12.0.0",
3
+ "version": "12.0.1",
4
4
  "description": "Zip it and ship it",
5
5
  "main": "./dist/main.js",
6
6
  "type": "module",
@@ -44,11 +44,11 @@
44
44
  "@babel/parser": "^7.22.5",
45
45
  "@babel/types": "7.27.1",
46
46
  "@netlify/binary-info": "^1.0.0",
47
- "@netlify/serverless-functions-api": "2.0.2",
47
+ "@netlify/serverless-functions-api": "^1.41.2",
48
48
  "@vercel/nft": "0.27.7",
49
49
  "archiver": "^7.0.0",
50
50
  "common-path-prefix": "^3.0.0",
51
- "cp-file": "^10.0.0",
51
+ "copy-file": "^11.0.0",
52
52
  "es-module-lexer": "^1.0.0",
53
53
  "esbuild": "0.25.4",
54
54
  "execa": "^7.0.0",
@@ -89,7 +89,7 @@
89
89
  "@vitest/coverage-v8": "0.34.6",
90
90
  "browserslist": "4.24.5",
91
91
  "cardinal": "2.1.1",
92
- "cpy": "9.0.1",
92
+ "cpy": "11.1.0",
93
93
  "decompress": "4.2.1",
94
94
  "deepmerge": "^4.3.1",
95
95
  "get-stream": "6.0.1",
@@ -102,5 +102,5 @@
102
102
  "engines": {
103
103
  "node": ">=18.14.0"
104
104
  },
105
- "gitHead": "11241b50b724a2f229614e74dd22c86c4ca8757d"
105
+ "gitHead": "20ebc60880b209b976d33578734b64007103cd5f"
106
106
  }