@netlify/build-info 5.2.0-framework-version-detection → 6.0.0

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.
package/lib/bin.d.ts CHANGED
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
package/lib/bin.js CHANGED
@@ -1,26 +1,22 @@
1
- #!/usr/bin/env node
2
1
  import { exit, argv } from 'process';
2
+ import yargs from 'yargs';
3
3
  import { hideBin } from 'yargs/helpers';
4
- import yargs from 'yargs/yargs';
5
- import { getBuildInfo } from './main.js';
6
- const { projectDir, rootDir } = yargs(hideBin(argv))
7
- .command('* [projectDir]')
8
- .options({
4
+ import { getBuildInfo } from './get-build-info.js';
5
+ yargs(hideBin(argv))
6
+ .command('* [projectDir]', 'Print relevant build information from a project.', (builder) => builder.options({
9
7
  rootDir: {
10
8
  string: true,
11
9
  describe: `The root directory of the project if different from projectDir`,
12
10
  },
11
+ }), async ({ projectDir, rootDir }) => {
12
+ try {
13
+ const buildInfo = await getBuildInfo({ projectDir, rootDir });
14
+ console.log(JSON.stringify(buildInfo, null, 2));
15
+ }
16
+ catch (error) {
17
+ console.error(error);
18
+ exit(1);
19
+ }
13
20
  })
14
- .usage(`$0 [OPTIONS...] [PROJECT_DIRECTORY]
15
-
16
- Print relevant build information from a project.`)
17
21
  .strict()
18
22
  .parse();
19
- try {
20
- const buildInfo = await getBuildInfo({ projectDir, rootDir });
21
- console.log(JSON.stringify(buildInfo, null, 2));
22
- }
23
- catch (error) {
24
- console.error(error);
25
- exit(1);
26
- }
package/lib/context.d.ts CHANGED
@@ -5,7 +5,7 @@ export declare type ContextOptions = {
5
5
  };
6
6
  export declare type Context = {
7
7
  projectDir: string;
8
- rootDir: string;
8
+ rootDir?: string;
9
9
  rootPackageJson: PackageJson;
10
10
  };
11
11
  export declare const getContext: (config?: ContextOptions) => Promise<Context>;
@@ -0,0 +1,29 @@
1
+ export declare const enum PkgManager {
2
+ YARN = "yarn",
3
+ PNPM = "pnpm",
4
+ NPM = "npm"
5
+ }
6
+ export declare type PkgManagerFields = {
7
+ /** The package managers name that is used for logging */
8
+ name: PkgManager;
9
+ /** The package managers install command */
10
+ installCommand: string;
11
+ /** The lock file a package manager is using */
12
+ lockFile: string;
13
+ /** Environment variable that can be used to force the usage of a package manager even though there is no lock file or a different lock file */
14
+ forceEnvironment?: string;
15
+ /** Flags that should be used for running the install command */
16
+ installFlags?: string[];
17
+ /** A list of all cache locations for the package manager */
18
+ cacheLocations?: string[];
19
+ };
20
+ /**
21
+ * Detects the used package manager based on
22
+ * 1. packageManager field
23
+ * 2. environment variable that forces the usage
24
+ * 3. a lock file that is present in this directory or up in the tree for workspaces
25
+ * @param cwd The current process working directory of the build
26
+ * @param stopAt The repository root where it should stop looking up for package.json or lock files. (defaults to `path.parse(cwd).root`)
27
+ * @returns The package manager that was detected
28
+ */
29
+ export declare const detectPackageManager: (cwd?: string, stopAt?: string) => Promise<PkgManagerFields>;
@@ -0,0 +1,71 @@
1
+ import { readFileSync } from 'fs';
2
+ import { basename } from 'path';
3
+ import { findUp, findUpMultiple } from 'find-up';
4
+ /** The definition of all available package managers */
5
+ const AVAILABLE_PACKAGE_MANAGERS = {
6
+ ["yarn" /* PkgManager.YARN */]: {
7
+ name: "yarn" /* PkgManager.YARN */,
8
+ installCommand: 'yarn install',
9
+ lockFile: 'yarn.lock',
10
+ forceEnvironment: 'NETLIFY_USE_YARN',
11
+ },
12
+ ["pnpm" /* PkgManager.PNPM */]: {
13
+ name: "pnpm" /* PkgManager.PNPM */,
14
+ installCommand: 'pnpm install',
15
+ lockFile: 'pnpm-lock.yaml',
16
+ forceEnvironment: 'NETLIFY_USE_PNPM',
17
+ },
18
+ ["npm" /* PkgManager.NPM */]: {
19
+ name: "npm" /* PkgManager.NPM */,
20
+ installCommand: 'npm install',
21
+ lockFile: 'package-lock.json',
22
+ },
23
+ };
24
+ /**
25
+ * generate a map out of key is lock file and value the package manager
26
+ * this is to reduce the complexity in loops
27
+ */
28
+ const lockFileMap = Object.values(AVAILABLE_PACKAGE_MANAGERS).reduce((cur, pkgManager) => ({ ...cur, [pkgManager.lockFile]: pkgManager }), {});
29
+ /**
30
+ * Detects the used package manager based on
31
+ * 1. packageManager field
32
+ * 2. environment variable that forces the usage
33
+ * 3. a lock file that is present in this directory or up in the tree for workspaces
34
+ * @param cwd The current process working directory of the build
35
+ * @param stopAt The repository root where it should stop looking up for package.json or lock files. (defaults to `path.parse(cwd).root`)
36
+ * @returns The package manager that was detected
37
+ */
38
+ export const detectPackageManager = async (cwd, stopAt) => {
39
+ const pkgPaths = await findUpMultiple('package.json', { cwd, stopAt });
40
+ for (const pkgPath of pkgPaths) {
41
+ const { packageManager } = JSON.parse(readFileSync(pkgPath, 'utf-8'));
42
+ if (packageManager) {
43
+ //
44
+ const [parsed] = packageManager.split('@');
45
+ if (AVAILABLE_PACKAGE_MANAGERS[parsed]) {
46
+ return AVAILABLE_PACKAGE_MANAGERS[parsed];
47
+ }
48
+ }
49
+ }
50
+ // the package manager can be enforced via an environment variable as well
51
+ for (const pkgManager of Object.values(AVAILABLE_PACKAGE_MANAGERS)) {
52
+ if (pkgManager.forceEnvironment && process.env[pkgManager.forceEnvironment] === 'true') {
53
+ return pkgManager;
54
+ }
55
+ }
56
+ // find the correct lock file the tree up
57
+ const lockFilePath = await findUp(Object.keys(lockFileMap), { cwd, stopAt });
58
+ // if we found a lock file and the usage is not prohibited through an environment variable
59
+ // return the found package manager
60
+ if (lockFilePath) {
61
+ const lockFile = basename(lockFilePath);
62
+ const pkgManager = lockFileMap[lockFile];
63
+ // check if it not got disabled
64
+ if (!(pkgManager.forceEnvironment && process.env[pkgManager.forceEnvironment] === 'false')) {
65
+ return pkgManager;
66
+ }
67
+ }
68
+ // always default to npm
69
+ // TODO: add some reporting here to log that we fall backed
70
+ return AVAILABLE_PACKAGE_MANAGERS["npm" /* PkgManager.NPM */];
71
+ };
@@ -0,0 +1,9 @@
1
+ import { ContextOptions } from './context.js';
2
+ import { PkgManagerFields } from './detect-package-manager.js';
3
+ import { WorkspaceInfo } from './workspaces.js';
4
+ export declare type Info = {
5
+ jsWorkspaces?: WorkspaceInfo;
6
+ packageManager?: PkgManagerFields;
7
+ frameworks: unknown[];
8
+ };
9
+ export declare const getBuildInfo: (opts: ContextOptions) => Promise<Info>;
@@ -0,0 +1,19 @@
1
+ import { listFrameworks } from '@netlify/framework-info';
2
+ import { getContext } from './context.js';
3
+ import { detectPackageManager } from './detect-package-manager.js';
4
+ import { getWorkspaceInfo } from './workspaces.js';
5
+ export const getBuildInfo = async (opts) => {
6
+ const context = await getContext(opts);
7
+ const info = {
8
+ frameworks: await listFrameworks({ projectDir: context.projectDir }),
9
+ };
10
+ // only if we find a root package.json we know this is a javascript workspace
11
+ if (Object.keys(context.rootPackageJson).length > 0) {
12
+ info.packageManager = await detectPackageManager(context.projectDir, context.rootDir);
13
+ const workspaceInfo = await getWorkspaceInfo(info.packageManager, context);
14
+ if (workspaceInfo) {
15
+ info.jsWorkspaces = workspaceInfo;
16
+ }
17
+ }
18
+ return info;
19
+ };
package/lib/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { getBuildInfo } from './get-build-info.js';
package/lib/index.js ADDED
@@ -0,0 +1 @@
1
+ export { getBuildInfo } from './get-build-info.js';
@@ -1,5 +1,22 @@
1
+ import { PackageJson } from 'read-pkg';
1
2
  import type { Context } from './context.js';
2
- export declare const getWorkspaceInfo: (context: Context) => Promise<{
3
+ import { PkgManagerFields } from './detect-package-manager.js';
4
+ export declare type WorkspaceInfo = {
5
+ /** if we are in the current workspace root or not */
3
6
  isRoot: boolean;
4
- packages: any[];
5
- }>;
7
+ /** the workspace root directory */
8
+ rootDir: string;
9
+ /** list of relative package paths inside the workspace */
10
+ packages: string[];
11
+ };
12
+ /**
13
+ * Get a list of globs about all the packages inside a pnpm workspace
14
+ * https://pnpm.io/pnpm-workspace_yaml
15
+ */
16
+ export declare const detectPnpmWorkspaceGlobs: (rootDir: string) => string[];
17
+ export declare const detectNpmOrYarnWorkspaceGlobs: (pkgJson: PackageJson) => string[];
18
+ /**
19
+ * If it's a javascript workspace (npm, pnpm, yarn) it will retrieve a list of all
20
+ * relative package paths and will indicate if it's the root of the workspace
21
+ */
22
+ export declare const getWorkspaceInfo: (packageManager: PkgManagerFields, context: Context) => Promise<undefined | WorkspaceInfo>;
package/lib/workspaces.js CHANGED
@@ -1,18 +1,56 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join, relative } from 'path';
1
3
  import mapWorkspaces from '@npmcli/map-workspaces';
2
- export const getWorkspaceInfo = async (context) => {
3
- if (!context.rootPackageJson.workspaces) {
4
+ import { parse } from 'yaml';
5
+ /**
6
+ * Get a list of globs about all the packages inside a pnpm workspace
7
+ * https://pnpm.io/pnpm-workspace_yaml
8
+ */
9
+ export const detectPnpmWorkspaceGlobs = (rootDir) => {
10
+ const workspaceFile = join(rootDir, 'pnpm-workspace.yaml');
11
+ if (!existsSync(workspaceFile)) {
12
+ return [];
13
+ }
14
+ const { packages } = parse(readFileSync(workspaceFile, 'utf-8'));
15
+ return packages;
16
+ };
17
+ export const detectNpmOrYarnWorkspaceGlobs = (pkgJson) => {
18
+ if (Array.isArray(pkgJson.workspaces)) {
19
+ return pkgJson.workspaces;
20
+ }
21
+ if (typeof pkgJson.workspaces === 'object') {
22
+ return pkgJson.workspaces.packages;
23
+ }
24
+ return [];
25
+ };
26
+ /**
27
+ * If it's a javascript workspace (npm, pnpm, yarn) it will retrieve a list of all
28
+ * relative package paths and will indicate if it's the root of the workspace
29
+ */
30
+ export const getWorkspaceInfo = async (packageManager, context) => {
31
+ const rootDir = context.rootDir || context.projectDir;
32
+ const workspaceGlobs = packageManager.name === "pnpm" /* PkgManager.PNPM */
33
+ ? detectPnpmWorkspaceGlobs(rootDir)
34
+ : detectNpmOrYarnWorkspaceGlobs(context.rootPackageJson);
35
+ if (workspaceGlobs.length === 0) {
4
36
  return;
5
37
  }
6
38
  const workspacesMap = await mapWorkspaces({
7
- cwd: context.rootDir || context.projectDir,
8
- pkg: context.rootPackageJson,
39
+ cwd: rootDir,
40
+ pkg: { workspaces: workspaceGlobs },
9
41
  });
10
- const packages = [...workspacesMap.values()];
11
- // The provided project dir is a workspace package
12
- const isWorkspace = packages.find((path) => context.projectDir === path);
42
+ // make paths relative
43
+ const packages = [...workspacesMap.values()].map((p) => relative(rootDir, p));
44
+ // The provided project dir is a workspace package and not a different directory
45
+ // in a mono repository that is not part inside the npm workspaces
46
+ const isWorkspace = packages.find((path) => context.projectDir === join(rootDir, path));
13
47
  // The project dir is a collection of workspaces itself
14
48
  const isRoot = !context.rootDir;
15
49
  if (isWorkspace || isRoot) {
16
- return { isRoot, packages };
50
+ return {
51
+ isRoot,
52
+ packages,
53
+ rootDir,
54
+ };
17
55
  }
18
56
  };
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@netlify/build-info",
3
- "version": "5.2.0-framework-version-detection",
3
+ "version": "6.0.0",
4
4
  "description": "Build info utility",
5
5
  "type": "module",
6
- "exports": "./lib/main.js",
7
- "main": "./lib/main.js",
8
- "types": "./lib/main.d.ts",
6
+ "exports": {
7
+ ".": "./lib/index.js"
8
+ },
9
+ "main": "./lib/index.js",
10
+ "types": "./lib/index.d.ts",
9
11
  "bin": {
10
12
  "build-info": "./bin.js"
11
13
  },
@@ -17,7 +19,7 @@
17
19
  "prebuild": "rm -rf lib",
18
20
  "build": "tsc",
19
21
  "test": "vitest run",
20
- "test:dev": "vitest",
22
+ "test:dev": "vitest --ui",
21
23
  "test:ci": "vitest run --reporter=default"
22
24
  },
23
25
  "keywords": [],
@@ -28,19 +30,27 @@
28
30
  },
29
31
  "author": "Netlify Inc.",
30
32
  "dependencies": {
31
- "@netlify/framework-info": "9.3.1-framework-version-detection.0",
33
+ "@netlify/framework-info": "^9.3.0",
32
34
  "@npmcli/map-workspaces": "^2.0.0",
33
- "read-pkg": "^7.0.0",
34
- "yargs": "^17.0.0"
35
+ "find-up": "^6.3.0",
36
+ "read-pkg": "^7.1.0",
37
+ "yargs": "^17.6.0"
35
38
  },
36
39
  "devDependencies": {
37
40
  "@types/node": "^14.18.31",
41
+ "@types/semver": "^7.3.13",
38
42
  "@vitest/coverage-c8": "^0.24.1",
43
+ "@vitest/ui": "^0.24.3",
39
44
  "execa": "^6.0.0",
45
+ "memfs": "^3.4.7",
46
+ "semver": "^7.3.8",
40
47
  "typescript": "^4.8.4",
41
- "vitest": "^0.24.1"
48
+ "unionfs": "^4.4.0",
49
+ "vitest": "^0.24.1",
50
+ "yaml": "^2.1.3"
42
51
  },
43
52
  "engines": {
44
53
  "node": "^14.16.0 || >=16.0.0"
45
- }
54
+ },
55
+ "gitHead": "8e802afa869a43b3248e0fd3b25fb9a06a416072"
46
56
  }
package/lib/core.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import type { Context } from './context.js';
2
- export declare const buildInfo: (context: Context) => Promise<{
3
- frameworks: any;
4
- jsWorkspaces: {
5
- isRoot: boolean;
6
- packages: any[];
7
- };
8
- } | {
9
- frameworks: any;
10
- jsWorkspaces?: undefined;
11
- }>;
package/lib/core.js DELETED
@@ -1,8 +0,0 @@
1
- import { listFrameworks } from '@netlify/framework-info';
2
- import { getWorkspaceInfo } from './workspaces.js';
3
- export const buildInfo = async function (context) {
4
- const workspaceInfo = await getWorkspaceInfo(context);
5
- const jsWorkspaces = workspaceInfo ? { jsWorkspaces: workspaceInfo } : {};
6
- const frameworks = await listFrameworks({ projectDir: context.projectDir });
7
- return { ...jsWorkspaces, frameworks };
8
- };
package/lib/main.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { ContextOptions } from './context.js';
2
- export declare const getBuildInfo: (opts: ContextOptions) => Promise<{
3
- frameworks: any;
4
- jsWorkspaces: {
5
- isRoot: boolean;
6
- packages: any[];
7
- };
8
- } | {
9
- frameworks: any;
10
- jsWorkspaces?: undefined;
11
- }>;
package/lib/main.js DELETED
@@ -1,6 +0,0 @@
1
- import { getContext } from './context.js';
2
- import { buildInfo } from './core.js';
3
- export const getBuildInfo = async (opts) => {
4
- const context = await getContext(opts);
5
- return await buildInfo(context);
6
- };