package-build-stats 8.2.3 → 8.2.5

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.
@@ -0,0 +1,112 @@
1
+ import childProcess from 'child_process';
2
+ import path from 'path';
3
+ import { builtinModules } from 'module';
4
+ import fs from 'fs';
5
+ import os from 'os';
6
+ const homeDirectory = os.homedir();
7
+ export const getBuiltInModules = () => builtinModules.flatMap(mod => [mod, 'node:' + mod]);
8
+ export function exec(command, options, timeout) {
9
+ let timerId;
10
+ return new Promise((resolve, reject) => {
11
+ const child = childProcess.exec(command, options, (error, stdout, stderr) => {
12
+ if (error) {
13
+ reject(stderr);
14
+ }
15
+ else {
16
+ resolve(stdout);
17
+ }
18
+ if (timerId) {
19
+ clearTimeout(timerId);
20
+ }
21
+ });
22
+ if (timeout) {
23
+ timerId = setTimeout(() => {
24
+ if (child.pid) {
25
+ process.kill(child.pid);
26
+ }
27
+ reject(`Execution of ${command.substring(0, 40)}... cancelled as it exceeded a timeout of ${timeout} ms`);
28
+ }, timeout * 10);
29
+ }
30
+ });
31
+ }
32
+ /**
33
+ * Gets external peerDeps that shouldn't be a
34
+ * part of the build in a regex format -
35
+ * /(^dep-a$|^dep-a\/|^dep-b$|^dep-b\/)\//
36
+ */
37
+ export function getExternals(packageName, installPath) {
38
+ const packageJSONPath = path.join(installPath, 'node_modules', packageName, 'package.json');
39
+ const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8'));
40
+ const dependencies = Object.keys(packageJSON.dependencies || {});
41
+ const peerDependencies = Object.keys(packageJSON.peerDependencies || {});
42
+ // All packages with name same as a built-in node module, but
43
+ // haven't explicitly been added as an npm dependency or aren't the package itself
44
+ // are externals
45
+ const builtInExternals = getBuiltInModules().filter(mod => !dependencies.includes(mod) && mod !== packageName);
46
+ return {
47
+ externalPackages: peerDependencies,
48
+ externalBuiltIns: builtInExternals,
49
+ };
50
+ }
51
+ function expandTilde(pathString) {
52
+ return homeDirectory
53
+ ? pathString.replace(/^~(?=$|\/|\\)/, homeDirectory)
54
+ : pathString;
55
+ }
56
+ function isLocalPackageString(packageString) {
57
+ const packageJsonPath = path.resolve(packageString, 'package.json');
58
+ try {
59
+ if (fs.existsSync(packageJsonPath)) {
60
+ return true;
61
+ }
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ function isScopedPackageString(packageString) {
68
+ return packageString.startsWith('@');
69
+ }
70
+ function parseLocalPackageString(packageString) {
71
+ const fullPath = path.resolve(packageString, 'package.json');
72
+ const packageJSON = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
73
+ return {
74
+ name: packageJSON.name,
75
+ version: packageJSON.version,
76
+ scoped: packageJSON.name.startsWith('@'),
77
+ normalPath: packageString,
78
+ isLocal: true,
79
+ };
80
+ }
81
+ function parseScopedPackageString(packageString) {
82
+ const lastAtIndex = packageString.lastIndexOf('@');
83
+ return {
84
+ name: lastAtIndex === 0
85
+ ? packageString
86
+ : packageString.substring(0, lastAtIndex),
87
+ version: lastAtIndex === 0 ? null : packageString.substring(lastAtIndex + 1),
88
+ scoped: true,
89
+ };
90
+ }
91
+ function parseUnscopedPackageString(packageString) {
92
+ const lastAtIndex = packageString.lastIndexOf('@');
93
+ return {
94
+ name: lastAtIndex === -1
95
+ ? packageString
96
+ : packageString.substring(0, lastAtIndex),
97
+ version: lastAtIndex === -1 ? null : packageString.substring(lastAtIndex + 1),
98
+ scoped: false,
99
+ };
100
+ }
101
+ export function parsePackageString(packageString) {
102
+ const normalPackageString = expandTilde(packageString);
103
+ if (isLocalPackageString(normalPackageString)) {
104
+ return parseLocalPackageString(normalPackageString);
105
+ }
106
+ else if (isScopedPackageString(normalPackageString)) {
107
+ return parseScopedPackageString(normalPackageString);
108
+ }
109
+ else {
110
+ return parseUnscopedPackageString(normalPackageString);
111
+ }
112
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Export scanning using oxc-parser and oxc-resolver
3
+ *
4
+ * This implementation follows the pattern from oxc-linter's export.rs
5
+ * using the public npm packages oxc-parser and oxc-resolver.
6
+ */
7
+ type ResolvedExports = {
8
+ [key: string]: string;
9
+ };
10
+ /**
11
+ * Get all exports from a package
12
+ *
13
+ * This is the main entry point that matches the API of getAllExports in exports.utils.ts
14
+ */
15
+ export declare function getAllExports(packageString: string, context: string, lookupPath: string, installPath?: string): Promise<ResolvedExports>;
16
+ /**
17
+ * Get exports details from code (compatibility function)
18
+ *
19
+ * This provides the same API as the existing getExportsDetails for backward compatibility
20
+ * Returns simple string arrays for exports (without moduleRequest info)
21
+ */
22
+ export declare function getExportsDetails(code: string, filename?: string): {
23
+ exports: string[];
24
+ exportAllLocations: string[];
25
+ };
26
+ export {};
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Export scanning using oxc-parser and oxc-resolver
3
+ *
4
+ * This implementation follows the pattern from oxc-linter's export.rs
5
+ * using the public npm packages oxc-parser and oxc-resolver.
6
+ */
7
+ import { parseSync } from 'oxc-parser';
8
+ import { ResolverFactory } from 'oxc-resolver';
9
+ import path from 'path';
10
+ import fs from 'fs/promises';
11
+ import Telemetry from './telemetry.utils.js';
12
+ import { performance } from 'perf_hooks';
13
+ // Initialize resolver with ESM-first configuration
14
+ // - main_fields: ["module", "main"] - prioritize ESM entry points
15
+ // - condition_names: ["import", "default", "require"] - ESM-first export conditions
16
+ // NOTE: We intentionally exclude "node" because Node.js conditional exports resolution
17
+ // uses the PACKAGE's exports field order (not our conditionNames order) to determine
18
+ // priority. Packages like Vue have "node" before "import" in their exports, so including
19
+ // "node" would resolve to CJS files instead of ESM. We keep "require" as a fallback for
20
+ // packages that only export via "require" condition.
21
+ // - extensions: all common JS/TS extensions
22
+ // - symlinks: false - keep paths as-is without resolving symlinks (matches enhanced-resolve behavior)
23
+ const resolver = new ResolverFactory({
24
+ extensions: [
25
+ '.mjs',
26
+ '.js',
27
+ '.mts',
28
+ '.ts',
29
+ '.jsx',
30
+ '.tsx',
31
+ '.cjs',
32
+ '.cts',
33
+ '.json',
34
+ ],
35
+ mainFields: ['module', 'main'], // ESM-first: prioritize "module" field over "main"
36
+ conditionNames: ['import', 'default', 'require'], // ESM-first: exclude "node" which resolves to CJS
37
+ symlinks: false, // Don't resolve symlinks to match enhanced-resolve behavior
38
+ });
39
+ /**
40
+ * Extract export information from parsed oxc module
41
+ */
42
+ function getExportsFromStaticExports(staticExports) {
43
+ const exports = [];
44
+ const exportAllLocations = [];
45
+ staticExports.forEach(staticExport => {
46
+ staticExport.entries.forEach((entry) => {
47
+ var _a, _b;
48
+ // Skip type-only exports (TypeScript type exports)
49
+ if (entry.isType) {
50
+ return;
51
+ }
52
+ // Handle different export types based on importName kind
53
+ switch (entry.importName.kind) {
54
+ case 'AllButDefault': // export * from "mod"
55
+ case 'All': // export * as ns from "mod"
56
+ if (entry.moduleRequest) {
57
+ exportAllLocations.push(entry.moduleRequest.value);
58
+ }
59
+ break;
60
+ case 'Name': // export { foo } or export { foo } from "mod"
61
+ case 'None': // export const foo = 1
62
+ // Get the export name
63
+ if (entry.exportName.kind === 'Name' && entry.exportName.name) {
64
+ exports.push({
65
+ name: entry.exportName.name,
66
+ moduleRequest: (_a = entry.moduleRequest) === null || _a === void 0 ? void 0 : _a.value, // Track the source module for re-exports
67
+ });
68
+ }
69
+ else if (entry.exportName.kind === 'Default') {
70
+ exports.push({
71
+ name: 'default',
72
+ moduleRequest: (_b = entry.moduleRequest) === null || _b === void 0 ? void 0 : _b.value,
73
+ });
74
+ }
75
+ break;
76
+ }
77
+ });
78
+ });
79
+ return { exports, exportAllLocations };
80
+ }
81
+ /**
82
+ * Resolve a module path from a given context
83
+ */
84
+ async function resolveModule(context, lookupPath) {
85
+ const result = resolver.sync(context, lookupPath);
86
+ if (!result.path) {
87
+ throw new Error(`Cannot resolve module '${lookupPath}' from '${context}'`);
88
+ }
89
+ return result.path;
90
+ }
91
+ /**
92
+ * Recursively walk exports following export * statements
93
+ *
94
+ * This mirrors the walk_exported_recursive function in export.rs
95
+ */
96
+ async function walkExportsRecursive(context, lookupPath, visited, rootContext, _isRootCall = false) {
97
+ // Use rootContext for calculating relative paths, context for resolution
98
+ const root = rootContext || context;
99
+ const resolvedPath = await resolveModule(context, lookupPath);
100
+ // Avoid circular dependencies
101
+ if (visited.has(resolvedPath)) {
102
+ return {};
103
+ }
104
+ visited.add(resolvedPath);
105
+ // Parse the file to get exports
106
+ const code = await fs.readFile(resolvedPath, 'utf8');
107
+ const parseResult = parseSync(resolvedPath, code, {
108
+ sourceType: 'module',
109
+ });
110
+ // Check if file has module syntax
111
+ if (!parseResult.module.hasModuleSyntax) {
112
+ return {};
113
+ }
114
+ const { exports, exportAllLocations } = getExportsFromStaticExports(parseResult.module.staticExports);
115
+ const resolvedExports = {};
116
+ // Add direct exports from this module, resolving re-exports to their source files
117
+ for (const exp of exports) {
118
+ let sourcePath = resolvedPath;
119
+ // If this is a re-export (export { foo } from './module.js'), resolve to the source file
120
+ if (exp.moduleRequest) {
121
+ try {
122
+ sourcePath = await resolveModule(path.dirname(resolvedPath), exp.moduleRequest);
123
+ }
124
+ catch {
125
+ // If resolution fails, fall back to current file
126
+ sourcePath = resolvedPath;
127
+ }
128
+ }
129
+ // Use path.relative() to calculate the relative path from root to sourcePath
130
+ // This works correctly since symlinks are not resolved (symlinks: false in resolver config)
131
+ const relativePath = root
132
+ ? path.relative(root, sourcePath)
133
+ : path.basename(sourcePath);
134
+ resolvedExports[exp.name] = relativePath;
135
+ }
136
+ // Recursively process export * statements
137
+ const promises = exportAllLocations.map(async (location) => {
138
+ const starExports = await walkExportsRecursive(path.dirname(resolvedPath), location, visited, root);
139
+ // Merge star exports into our exports
140
+ Object.keys(starExports).forEach(expKey => {
141
+ resolvedExports[expKey] = starExports[expKey];
142
+ });
143
+ });
144
+ await Promise.all(promises);
145
+ return resolvedExports;
146
+ }
147
+ /**
148
+ * Get all exports from a package
149
+ *
150
+ * This is the main entry point that matches the API of getAllExports in exports.utils.ts
151
+ */
152
+ export async function getAllExports(packageString, context, lookupPath, installPath) {
153
+ const startTime = performance.now();
154
+ const visited = new Set();
155
+ try {
156
+ // Read package.json to get the entry point
157
+ const packageJsonPath = path.join(context, 'package.json');
158
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
159
+ // Prefer module field for ESM, fallback to main, then default
160
+ let entryPoint = packageJson.module || packageJson.main || './index.js';
161
+ // Normalize entry point to start with ./
162
+ if (!entryPoint.startsWith('./') && !entryPoint.startsWith('../')) {
163
+ entryPoint = './' + entryPoint;
164
+ }
165
+ // Resolve the entry point relative to context
166
+ // Pass installPath as rootContext for calculating relative paths
167
+ const results = await walkExportsRecursive(context, entryPoint, visited, installPath, true);
168
+ Telemetry.walkPackageExportsTree(packageString, startTime, true);
169
+ return results;
170
+ }
171
+ catch (err) {
172
+ Telemetry.walkPackageExportsTree(packageString, startTime, false, err);
173
+ throw err;
174
+ }
175
+ }
176
+ /**
177
+ * Get exports details from code (compatibility function)
178
+ *
179
+ * This provides the same API as the existing getExportsDetails for backward compatibility
180
+ * Returns simple string arrays for exports (without moduleRequest info)
181
+ */
182
+ export function getExportsDetails(code, filename = 'module.js') {
183
+ const parseResult = parseSync(filename, code, {
184
+ sourceType: 'module',
185
+ });
186
+ const result = getExportsFromStaticExports(parseResult.module.staticExports);
187
+ // Return simple string array for backward compatibility
188
+ return {
189
+ exports: result.exports.map(exp => exp.name),
190
+ exportAllLocations: result.exportAllLocations,
191
+ };
192
+ }
@@ -0,0 +1,9 @@
1
+ import { InstallPackageOptions } from '../common.types.js';
2
+ declare const InstallationUtils: {
3
+ getInstallPath(packageName: string): string;
4
+ preparePath(packageName: string, clientOption?: "npm" | "yarn" | "pnpm" | "bun" | Array<"npm" | "yarn" | "pnpm" | "bun">): Promise<string>;
5
+ installPackage(packageString: string, installPath: string, installOptions: InstallPackageOptions): Promise<void>;
6
+ _installWithClient(packageString: string, installPath: string, installOptions: InstallPackageOptions, currentClient: "npm" | "yarn" | "pnpm" | "bun"): Promise<void>;
7
+ cleanupPath(installPath: string): Promise<void>;
8
+ };
9
+ export default InstallationUtils;
@@ -0,0 +1,203 @@
1
+ import { rimraf } from 'rimraf';
2
+ import path from 'path';
3
+ import fs from 'fs/promises';
4
+ import sanitize from 'sanitize-filename';
5
+ import { randomUUID } from 'crypto';
6
+ import createDebug from 'debug';
7
+ const debug = createDebug('bp:worker');
8
+ import { InstallError, PackageNotFoundError } from '../errors/CustomError.js';
9
+ import { exec } from './common.utils.js';
10
+ import config from '../config/config.js';
11
+ import Telemetry from './telemetry.utils.js';
12
+ import { performance } from 'perf_hooks';
13
+ // When operating on a local directory, force npm to copy directory structure
14
+ // and all dependencies instead of just symlinking files
15
+ const wrapPackCommand = (packagePath) => `$(npm pack --ignore-scripts ${packagePath} | tail -1)`;
16
+ const InstallationUtils = {
17
+ getInstallPath(packageName) {
18
+ const id = randomUUID().slice(0, 8);
19
+ return path.join(config.tmp, 'packages', sanitize(`build-${packageName}-${id}`));
20
+ },
21
+ async preparePath(packageName, clientOption) {
22
+ const startTime = performance.now();
23
+ const installPath = InstallationUtils.getInstallPath(packageName);
24
+ if (process.env.DEBUG_TIMING) {
25
+ console.log(`[TIMING] preparePath.getInstallPath: ${(performance.now() - startTime).toFixed(2)}ms`);
26
+ }
27
+ const step1 = performance.now();
28
+ await fs.mkdir(config.tmp, { recursive: true });
29
+ await fs.mkdir(installPath, { recursive: true });
30
+ if (process.env.DEBUG_TIMING) {
31
+ console.log(`[TIMING] preparePath.mkdir: ${(performance.now() - step1).toFixed(2)}ms`);
32
+ }
33
+ const step2 = performance.now();
34
+ // Check if yarn is being used (either as single value or in array)
35
+ const clients = clientOption
36
+ ? Array.isArray(clientOption)
37
+ ? clientOption
38
+ : [clientOption]
39
+ : [];
40
+ const isUsingYarn = clients.includes('yarn');
41
+ const packageJson = {
42
+ dependencies: {},
43
+ browserslist: [
44
+ 'last 5 Chrome versions',
45
+ 'last 5 Firefox versions',
46
+ 'Safari >= 9',
47
+ 'edge >= 12',
48
+ ],
49
+ };
50
+ // Add packageManager field if yarn is being used (required by corepack)
51
+ if (isUsingYarn) {
52
+ packageJson.packageManager = 'yarn@1.22.22';
53
+ }
54
+ await fs.writeFile(path.join(installPath, 'package.json'), JSON.stringify(packageJson));
55
+ if (process.env.DEBUG_TIMING) {
56
+ console.log(`[TIMING] preparePath.writeFile: ${(performance.now() - step2).toFixed(2)}ms`);
57
+ console.log(`[TIMING] preparePath.total: ${(performance.now() - startTime).toFixed(2)}ms`);
58
+ }
59
+ return installPath;
60
+ },
61
+ async installPackage(packageString, installPath, installOptions) {
62
+ const { client = ['bun', 'npm'], // Default: try bun first, fallback to npm
63
+ limitConcurrency: _limitConcurrency, networkConcurrency: _networkConcurrency, additionalPackages: _additionalPackages = [], isLocal: _isLocal, installTimeout: _installTimeout = 45000, } = installOptions;
64
+ // Normalize client to array
65
+ const clients = Array.isArray(client) ? client : [client];
66
+ // Try each client in order until one succeeds
67
+ let lastError = null;
68
+ for (let i = 0; i < clients.length; i++) {
69
+ const currentClient = clients[i];
70
+ const isLastClient = i === clients.length - 1;
71
+ try {
72
+ await InstallationUtils._installWithClient(packageString, installPath, {
73
+ ...installOptions,
74
+ client: currentClient,
75
+ }, currentClient);
76
+ // Success! Log which client was used
77
+ if (installOptions.debug || process.env.DEBUG_TIMING) {
78
+ console.log(`[INSTALL] Successfully installed with ${currentClient}`);
79
+ }
80
+ return;
81
+ }
82
+ catch (error) {
83
+ lastError = error;
84
+ if (!isLastClient) {
85
+ // Try next client
86
+ debug(`Installation with ${currentClient} failed, trying next client...`);
87
+ if (installOptions.debug) {
88
+ console.log(`[INSTALL] ${currentClient} failed, trying ${clients[i + 1]}...`);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ // All clients failed
94
+ throw lastError;
95
+ },
96
+ async _installWithClient(packageString, installPath, installOptions, currentClient) {
97
+ let flags, command;
98
+ let installStartTime = performance.now();
99
+ const { limitConcurrency, networkConcurrency, additionalPackages = [], isLocal, installTimeout = 45000, } = installOptions;
100
+ if (currentClient === 'yarn') {
101
+ flags = [
102
+ 'ignore-flags',
103
+ 'ignore-engines',
104
+ 'skip-integrity-check',
105
+ 'exact',
106
+ 'json',
107
+ 'no-progress',
108
+ 'silent',
109
+ 'no-lockfile',
110
+ 'no-bin-links',
111
+ 'ignore-optional',
112
+ ];
113
+ if (limitConcurrency) {
114
+ flags.push('mutex network');
115
+ }
116
+ if (networkConcurrency) {
117
+ flags.push(`network-concurrency ${networkConcurrency}`);
118
+ }
119
+ command = `yarn add ${packageString} ${additionalPackages.join(' ')} --${flags.join(' --')}`;
120
+ }
121
+ else if (currentClient === 'npm') {
122
+ flags = [
123
+ // Setting cache is required for concurrent `npm install`s to work
124
+ `cache=${path.join(config.tmp, 'cache')}`,
125
+ 'no-package-lock',
126
+ 'no-shrinkwrap',
127
+ 'legacy-peer-deps',
128
+ 'no-optional',
129
+ 'no-bin-links',
130
+ 'progress false',
131
+ 'loglevel error',
132
+ 'ignore-scripts',
133
+ 'save-exact',
134
+ 'production',
135
+ 'json',
136
+ ];
137
+ command = `npm install ${isLocal ? wrapPackCommand(packageString) : packageString} ${additionalPackages.join(' ')} --${flags.join(' --')}`;
138
+ }
139
+ else if (currentClient === 'pnpm') {
140
+ flags = ['no-optional', 'loglevel error', 'ignore-scripts', 'save-exact'];
141
+ command = `pnpm add ${packageString} ${additionalPackages.join(' ')} --${[].join(' --')}`;
142
+ }
143
+ else if (currentClient === 'bun') {
144
+ flags = [
145
+ 'no-save', // Don't update package.json or save lockfile
146
+ 'production', // Don't install devDependencies
147
+ 'ignore-scripts', // Skip lifecycle scripts
148
+ 'no-progress', // Disable progress bar
149
+ 'silent', // Don't log anything
150
+ ];
151
+ // Add network concurrency if specified
152
+ if (networkConcurrency) {
153
+ flags.push(`network-concurrency=${networkConcurrency}`);
154
+ }
155
+ command = `bun add ${packageString} ${additionalPackages.join(' ')} --cache-dir=${path.join(config.tmp, 'cache', 'bun')} --${flags.join(' --')}`;
156
+ }
157
+ else {
158
+ console.error('No valid client specified');
159
+ process.exit(1);
160
+ }
161
+ debug('install start %s', packageString);
162
+ try {
163
+ const execStartTime = performance.now();
164
+ await exec(command, {
165
+ cwd: installPath,
166
+ maxBuffer: 1024 * 500,
167
+ }, installTimeout);
168
+ const execDuration = performance.now() - execStartTime;
169
+ if (process.env.DEBUG_TIMING) {
170
+ console.log(`[TIMING] installPackage.exec (${currentClient}): ${execDuration.toFixed(2)}ms`);
171
+ }
172
+ debug('install finish %s', packageString);
173
+ Telemetry.installPackage(packageString, true, installStartTime, {
174
+ ...installOptions,
175
+ client: currentClient,
176
+ });
177
+ }
178
+ catch (err) {
179
+ if (installOptions.debug || process.env.DEBUG_TIMING) {
180
+ console.log(`[INSTALL ERROR] ${currentClient}:`, err);
181
+ }
182
+ Telemetry.installPackage(packageString, false, installStartTime, {
183
+ ...installOptions,
184
+ client: currentClient,
185
+ });
186
+ if (typeof err === 'string' && err.includes('code E404')) {
187
+ throw new PackageNotFoundError(err);
188
+ }
189
+ else {
190
+ throw new InstallError(err);
191
+ }
192
+ }
193
+ },
194
+ async cleanupPath(installPath) {
195
+ try {
196
+ await rimraf(installPath);
197
+ }
198
+ catch (err) {
199
+ console.error('cleaning up path ', installPath, ' failed due to ', err);
200
+ }
201
+ },
202
+ };
203
+ export default InstallationUtils;
@@ -0,0 +1,17 @@
1
+ import type { Emitter, EventType } from 'mitt';
2
+ type Events = Record<EventType, unknown>;
3
+ declare const emitter: Emitter<Events>;
4
+ export { emitter };
5
+ export default class Telemetry {
6
+ static installPackage(packageString: string, isSuccessful: boolean, startTime: number, options: any, error?: any): void;
7
+ static getPackageJSONDetails(packageName: string, isSuccessful: boolean, startTime: number, error?: any): void;
8
+ static buildPackage(packageName: string, isSuccessful: boolean, startTime: number, options: any, error?: any): void;
9
+ static compilePackage(packageName: string, isSuccessful: boolean, startTime: number, options: any, error?: any): void;
10
+ static packageStats(packageString: string, isSuccessful: boolean, startTime: number, options: any, error?: any): void;
11
+ static parseWebpackStats(packageName: string, isSuccessful: boolean, startTime: number, error?: any): void;
12
+ static dependencySizes(packageName: string, startTime: number, isSuccessful: boolean, options: any, error?: any): void;
13
+ static assetsGZIPParseTime(packageName: string, startTime: number): void;
14
+ static walkPackageExportsTree(packageString: string, startTime: number, isSuccessful: boolean, error?: any): void;
15
+ static packageExports(packageString: string, startTime: number, isSuccessful: boolean, error?: any): void;
16
+ static packageExportsSizes(packageString: string, startTime: number, isSuccessful: boolean, options: any, error?: any): void;
17
+ }
@@ -0,0 +1,121 @@
1
+ import mitt from 'mitt';
2
+ import { parsePackageString } from './common.utils.js';
3
+ import { performance } from 'perf_hooks';
4
+ import _ from 'lodash';
5
+ import createDebug from 'debug';
6
+ const debug = createDebug('bp-telemetry');
7
+ const emitter = mitt();
8
+ export { emitter };
9
+ emitter.on('*', (type, data) => {
10
+ debug('Telementry Event: %s %o', type, data);
11
+ });
12
+ function errorToObject(error) {
13
+ if (!error)
14
+ return;
15
+ if (error && typeof error === 'object') {
16
+ const errorObject = {};
17
+ Object.getOwnPropertyNames(error).forEach(key => {
18
+ // @ts-ignore
19
+ errorObject[key] =
20
+ typeof error[key] === 'object'
21
+ ? errorToObject(error[key])
22
+ : String(error[key]).substring(0, 40);
23
+ });
24
+ return errorObject;
25
+ }
26
+ return { error };
27
+ }
28
+ export default class Telemetry {
29
+ static installPackage(packageString, isSuccessful, startTime, options, error = null) {
30
+ emitter.emit('TASK_PACKAGE_INSTALL', {
31
+ package: parsePackageString(packageString),
32
+ isSuccessful,
33
+ duration: performance.now() - startTime,
34
+ options,
35
+ error: errorToObject(error),
36
+ });
37
+ }
38
+ static getPackageJSONDetails(packageName, isSuccessful, startTime, error = null) {
39
+ emitter.emit('TASK_PACKAGE_JSON_DETAILS', {
40
+ package: { name: packageName },
41
+ isSuccessful,
42
+ duration: performance.now() - startTime,
43
+ error: errorToObject(error),
44
+ });
45
+ }
46
+ static buildPackage(packageName, isSuccessful, startTime, options, error = null) {
47
+ emitter.emit('TASK_PACKAGE_BUILD', {
48
+ package: { name: packageName },
49
+ isSuccessful,
50
+ duration: performance.now() - startTime,
51
+ options: _.omit(options, 'customImports'),
52
+ error: errorToObject(error),
53
+ });
54
+ }
55
+ static compilePackage(packageName, isSuccessful, startTime, options, error = null) {
56
+ emitter.emit('TASK_PACKAGE_COMPILE', {
57
+ packageName,
58
+ isSuccessful,
59
+ duration: performance.now() - startTime,
60
+ options,
61
+ error: errorToObject(error),
62
+ });
63
+ }
64
+ static packageStats(packageString, isSuccessful, startTime, options, error = null) {
65
+ emitter.emit('TASK_PACKAGE_STATS', {
66
+ package: parsePackageString(packageString),
67
+ isSuccessful,
68
+ duration: performance.now() - startTime,
69
+ options,
70
+ error: errorToObject(error),
71
+ });
72
+ }
73
+ static parseWebpackStats(packageName, isSuccessful, startTime, error = null) {
74
+ emitter.emit('TASK_PACKAGE_PARSE_WEBPACK_STATS', {
75
+ package: { name: packageName },
76
+ isSuccessful,
77
+ duration: performance.now() - startTime,
78
+ error: errorToObject(error),
79
+ });
80
+ }
81
+ static dependencySizes(packageName, startTime, isSuccessful, options, error = null) {
82
+ emitter.emit('TASK_PACKAGE_DEPENDENCY_SIZES', {
83
+ package: { name: packageName },
84
+ duration: performance.now() - startTime,
85
+ isSuccessful,
86
+ options,
87
+ error: errorToObject(error),
88
+ });
89
+ }
90
+ static assetsGZIPParseTime(packageName, startTime) {
91
+ emitter.emit('TASK_PACKAGE_ASSETS_GZIP_PARSE_TIME', {
92
+ package: { name: packageName },
93
+ duration: performance.now() - startTime,
94
+ });
95
+ }
96
+ static walkPackageExportsTree(packageString, startTime, isSuccessful, error = null) {
97
+ emitter.emit('TASK_PACKAGE_EXPORTS_TREEWALK', {
98
+ package: parsePackageString(packageString),
99
+ isSuccessful,
100
+ duration: performance.now() - startTime,
101
+ error: errorToObject(error),
102
+ });
103
+ }
104
+ static packageExports(packageString, startTime, isSuccessful, error = null) {
105
+ emitter.emit('TASK_PACKAGE_EXPORTS', {
106
+ package: parsePackageString(packageString),
107
+ isSuccessful,
108
+ duration: performance.now() - startTime,
109
+ error: errorToObject(error),
110
+ });
111
+ }
112
+ static packageExportsSizes(packageString, startTime, isSuccessful, options, error = null) {
113
+ emitter.emit('TASK_PACKAGE_EXPORTS_SIZES', {
114
+ package: parsePackageString(packageString),
115
+ duration: performance.now() - startTime,
116
+ isSuccessful,
117
+ error: errorToObject(error),
118
+ options,
119
+ });
120
+ }
121
+ }