dependency-owners 1.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/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025 Kirk Eaton
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # dependency-owners
2
+
3
+ Determine ownership of dependencies in a project.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ import { buildApplication, buildCommand, run, } from '@stricli/core';
3
+ import { createRequire } from 'node:module';
4
+ import path from 'node:path';
5
+ import { dependencyOwners } from './index.js';
6
+ import { getErrorMessage } from './utils.js';
7
+ const { name, version, description } = createRequire(import.meta.url)('../package.json');
8
+ function cwdParser(rawInput) {
9
+ return this.path.join(this.process.cwd(), rawInput);
10
+ }
11
+ const command = buildCommand({
12
+ async func(flags, dependencyFile) {
13
+ try {
14
+ const result = await dependencyOwners({
15
+ configFile: flags.config,
16
+ dependencies: flags.dependency,
17
+ dependencyFile,
18
+ loader: flags.loader,
19
+ });
20
+ this.process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
21
+ }
22
+ catch (error) {
23
+ const errorMessage = getErrorMessage(error);
24
+ this.process.stderr.write(`\x1B[1m\x1B[31m${errorMessage}\x1B[39m\x1B[22m\n`);
25
+ this.process.exit(1);
26
+ }
27
+ },
28
+ parameters: {
29
+ flags: {
30
+ config: {
31
+ kind: 'parsed',
32
+ brief: 'Path to the configuration file.',
33
+ default: 'dependency-owners.json',
34
+ parse: cwdParser,
35
+ },
36
+ dependency: {
37
+ kind: 'parsed',
38
+ parse: String,
39
+ brief: 'List of dependencies to check.',
40
+ optional: true,
41
+ variadic: true,
42
+ },
43
+ loader: {
44
+ kind: 'parsed',
45
+ parse: String,
46
+ brief: 'Loader to use for loading dependencies.',
47
+ optional: true,
48
+ },
49
+ },
50
+ positional: {
51
+ kind: 'tuple',
52
+ parameters: [
53
+ {
54
+ brief: 'Path to the dependency file.',
55
+ placeholder: 'dependency-file',
56
+ default: 'package.json',
57
+ parse: cwdParser,
58
+ },
59
+ ],
60
+ },
61
+ },
62
+ docs: {
63
+ brief: description,
64
+ },
65
+ });
66
+ const app = buildApplication(command, {
67
+ name,
68
+ versionInfo: {
69
+ currentVersion: version,
70
+ },
71
+ });
72
+ await run(app, process.argv.slice(2), { path, process });
@@ -0,0 +1,28 @@
1
+ import { DependencyLoader } from './loader.js';
2
+ /**
3
+ * Options for the dependency owners lookup.
4
+ */
5
+ export interface DependencyOwnersOptions {
6
+ /**
7
+ * Path to the dependency file.
8
+ */
9
+ dependencyFile?: string;
10
+ /**
11
+ * Path to the configuration file.
12
+ */
13
+ configFile?: string;
14
+ /**
15
+ * List of dependencies to check.
16
+ */
17
+ dependencies?: string[];
18
+ /**
19
+ * Loader to use for loading dependencies.
20
+ */
21
+ loader?: string | DependencyLoader;
22
+ }
23
+ /**
24
+ * Get the owners of the specified dependencies.
25
+ * @param {Options} options - The options for the dependency owners lookup.
26
+ * @returns {Promise<Record<string, string[]>>} A mapping of dependency owners for the specified dependencies.
27
+ */
28
+ export declare function dependencyOwners(options: DependencyOwnersOptions): Promise<Record<string, string[]>>;
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getOwners, getOwnersMapping, resolveDependencyLoader, } from './utils.js';
4
+ /**
5
+ * Loader for loading dependencies from package.json files.
6
+ */
7
+ const PackageJsonLoader = {
8
+ async canLoad(filePath) {
9
+ return path.basename(filePath) === 'package.json';
10
+ },
11
+ async load(filePath) {
12
+ const pkg = JSON.parse(await fs.readFile(filePath, 'utf-8'));
13
+ return [
14
+ ...Object.keys(pkg.dependencies || {}),
15
+ ...Object.keys(pkg.devDependencies || {}),
16
+ ];
17
+ },
18
+ };
19
+ /**
20
+ * Get the owners of the specified dependencies.
21
+ * @param {Options} options - The options for the dependency owners lookup.
22
+ * @returns {Promise<Record<string, string[]>>} A mapping of dependency owners for the specified dependencies.
23
+ */
24
+ export async function dependencyOwners(options) {
25
+ const { dependencies = [], loader = PackageJsonLoader, dependencyFile = path.join(process.cwd(), 'package.json'), configFile = path.join(process.cwd(), 'dependency-owners.json'), } = options;
26
+ const resolvedLoader = await resolveDependencyLoader(loader, dependencyFile);
27
+ if (!resolvedLoader) {
28
+ throw new Error(`No loader found for file: ${dependencyFile}`);
29
+ }
30
+ const ownersMapping = getOwnersMapping(configFile);
31
+ const resolvedDependencies = await resolvedLoader.load(dependencyFile);
32
+ const filteredDependencies = resolvedDependencies.filter((dep) => dependencies.length === 0 || dependencies.includes(dep));
33
+ return getOwners(filteredDependencies, ownersMapping);
34
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Interface for loading dependencies from various file types.
3
+ */
4
+ export interface DependencyLoader {
5
+ /**
6
+ * Check if the loader can handle the specified file.
7
+ * @param {string} filePath The path to the file to check.
8
+ * @returns {boolean} True if the loader can handle the file, false otherwise.
9
+ */
10
+ canLoad(filePath: string): Promise<boolean>;
11
+ /**
12
+ * Load the dependencies from the specified file.
13
+ * @param {string} filePath The path to the file to load dependencies from.
14
+ * @returns {string[]} The list of loaded dependencies.
15
+ */
16
+ load(filePath: string): Promise<string[]>;
17
+ }
package/dist/loader.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { DependencyLoader } from './loader.js';
2
+ /**
3
+ * Get the mapping of dependency owners from a JSON file.
4
+ * @param {string} filePath - The path to the dependency owners file.
5
+ * @returns {Record<string, string[]>} A mapping of dependency owners.
6
+ */
7
+ export declare function getOwnersMapping(filePath: string): Record<string, string[]>;
8
+ /**
9
+ * Get the owners of the specified dependencies.
10
+ * @param {string[]} dependencies - The list of dependencies to check.
11
+ * @param {Record<string, string[]>} ownersMapping - The mapping of dependency owners.
12
+ * @returns {Record<string, string[]>} A mapping of dependency owners for the specified dependencies.
13
+ */
14
+ export declare function getOwners(dependencies: string[], ownersMapping: Record<string, string[]>): Record<string, string[]>;
15
+ /**
16
+ * Resolve a dependency loader for a specific file.
17
+ * @param {DependencyLoader | string} loader The loader to use.
18
+ * @param {string} depFilePath The path to the dependency file.
19
+ * @returns {Promise<DependencyLoader | undefined>} The resolved dependency loader or undefined if not found.
20
+ */
21
+ export declare function resolveDependencyLoader(loader: DependencyLoader | string, depFilePath: string): Promise<DependencyLoader | undefined>;
22
+ /**
23
+ * Get a formatted error message.
24
+ * @param error The error to format.
25
+ * @returns The formatted error message.
26
+ */
27
+ export declare function getErrorMessage(error: unknown): string;
package/dist/utils.js ADDED
@@ -0,0 +1,82 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * Get the mapping of dependency owners from a JSON file.
5
+ * @param {string} filePath - The path to the dependency owners file.
6
+ * @returns {Record<string, string[]>} A mapping of dependency owners.
7
+ */
8
+ export function getOwnersMapping(filePath) {
9
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
10
+ }
11
+ /**
12
+ * Get the owners of the specified dependencies.
13
+ * @param {string[]} dependencies - The list of dependencies to check.
14
+ * @param {Record<string, string[]>} ownersMapping - The mapping of dependency owners.
15
+ * @returns {Record<string, string[]>} A mapping of dependency owners for the specified dependencies.
16
+ */
17
+ export function getOwners(dependencies, ownersMapping) {
18
+ return dependencies.reduce((result, dep) => {
19
+ const owners = Object.keys(ownersMapping).filter((owner) => Array.isArray(ownersMapping[owner]) &&
20
+ ownersMapping[owner].includes(dep));
21
+ result[dep] = owners;
22
+ return result;
23
+ }, {});
24
+ }
25
+ /**
26
+ * Import a dependency loader by name.
27
+ * @param {string} loaderName The name of the loader to import.
28
+ * @returns {Promise<DependencyLoader>} The imported dependency loader.
29
+ */
30
+ async function importDependencyLoader(loaderName) {
31
+ let loaderModule;
32
+ // Try importing from an installed dependency/absolute path
33
+ try {
34
+ loaderModule = await import(loaderName);
35
+ }
36
+ catch {
37
+ // no-op
38
+ }
39
+ // Try import from relative path
40
+ try {
41
+ loaderModule = await import(path.join(process.cwd(), loaderName));
42
+ }
43
+ catch {
44
+ // no-op
45
+ }
46
+ // If we couldn't find the module, throw an error
47
+ if (!loaderModule) {
48
+ throw new Error(`Failed to import loader: ${loaderName}`);
49
+ }
50
+ // Check if the loader has the required functions
51
+ const loader = loaderModule.default || loaderModule;
52
+ if (loader &&
53
+ typeof loader.canLoad === 'function' &&
54
+ typeof loader.load === 'function') {
55
+ return loader;
56
+ }
57
+ else {
58
+ throw new Error(`Invalid loader: ${loaderName}. The module does not export an object with 'canLoad' and 'load' functions.`);
59
+ }
60
+ }
61
+ /**
62
+ * Resolve a dependency loader for a specific file.
63
+ * @param {DependencyLoader | string} loader The loader to use.
64
+ * @param {string} depFilePath The path to the dependency file.
65
+ * @returns {Promise<DependencyLoader | undefined>} The resolved dependency loader or undefined if not found.
66
+ */
67
+ export async function resolveDependencyLoader(loader, depFilePath) {
68
+ const resolvedLoader = typeof loader === 'string' ? await importDependencyLoader(loader) : loader;
69
+ const canLoad = await resolvedLoader.canLoad(depFilePath);
70
+ return canLoad ? resolvedLoader : undefined;
71
+ }
72
+ /**
73
+ * Get a formatted error message.
74
+ * @param error The error to format.
75
+ * @returns The formatted error message.
76
+ */
77
+ export function getErrorMessage(error) {
78
+ if (error instanceof Error) {
79
+ return error.message;
80
+ }
81
+ return String(error);
82
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "dependency-owners",
3
+ "version": "1.0.0",
4
+ "description": "Determine ownership of dependencies in a project",
5
+ "author": "Kirk Eaton <contact@kirkeaton.ca>",
6
+ "bin": "./dist/cli.js",
7
+ "dependencies": {
8
+ "@stricli/core": "1.2.0"
9
+ },
10
+ "devDependencies": {
11
+ "@kirkeaton/prettier-config": "1.0.5",
12
+ "@kirkeaton/semantic-release-config": "1.0.4",
13
+ "@kirkeaton/tsconfig": "1.0.3",
14
+ "@types/node": "24.2.1",
15
+ "fs-fixture": "2.8.1",
16
+ "prettier": "3.6.2",
17
+ "semantic-release": "24.2.7",
18
+ "tsx": "4.20.4",
19
+ "typescript": "5.9.2"
20
+ },
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./loader": {
30
+ "types": "./dist/loader.d.ts",
31
+ "default": "./dist/loader.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "funding": "https://kirkeaton.ca/sponsor",
38
+ "homepage": "https://github.com/dependency-owners/dependency-owners#readme",
39
+ "keywords": [
40
+ "dependency",
41
+ "owners"
42
+ ],
43
+ "license": "BSD-3-Clause",
44
+ "packageManager": "pnpm@10.14.0",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/dependency-owners/dependency-owners.git"
48
+ },
49
+ "scripts": {
50
+ "build": "pnpm clean && tsc",
51
+ "clean": "rm -rf dist",
52
+ "format": "prettier \"**/*.{js,json,md,ts}\" --write",
53
+ "lint": "prettier \"**/*.{js,json,md,ts,md}\" --check",
54
+ "prepublishOnly": "pnpm build",
55
+ "test": "tsx --test",
56
+ "typecheck": "tsc --noEmit"
57
+ },
58
+ "type": "module"
59
+ }