Skip to content

Instantly share code, notes, and snippets.

@AspireOne
Last active September 1, 2024 18:13
Show Gist options
  • Save AspireOne/5b5bf7f8c80ef66de17df454fb332801 to your computer and use it in GitHub Desktop.
Save AspireOne/5b5bf7f8c80ef66de17df454fb332801 to your computer and use it in GitHub Desktop.
Generate a comprehensive summary of packages used in your project, along wth links to them, their licenses (fetched from a public database), and warnings for non-permissive licences. Supports caching. You can use this in your pre-commit hook.
import * as fs from "fs/promises";
import * as path from "path";
import fetch from "node-fetch";
import PQueue from "p-queue";
interface PackageJson {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
}
interface NpmPackageInfo {
license?: string;
description?: string;
}
interface CachedDependencyInfo {
name: string;
npmLink: string;
licenseLink: string;
description: string;
isPermissive: boolean;
lastUpdated: string;
}
const queue = new PQueue({ concurrency: 10 });
const permissiveLicenses = [
"MIT",
"BSD",
"Apache",
"ISC",
"Unlicense",
"CC0",
"WTFPL",
"Zlib",
];
function isPermissiveLicense(license: string): boolean {
return permissiveLicenses.some((pl) =>
license.toUpperCase().includes(pl.toUpperCase()),
);
}
async function fetchPackageInfo(packageName: string): Promise<NpmPackageInfo> {
try {
const response = await fetch(
`https://registry.npmjs.org/${encodeURIComponent(packageName)}`,
{
headers: { "User-Agent": "Node.js" },
},
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data as NpmPackageInfo;
} catch (error) {
console.error(`Error fetching info for ${packageName}:`, error);
return {};
}
}
function formatDependencyInfo(
dep: string,
npmLink: string,
licenseLink: string,
description: string,
isPermissive: boolean,
lastUpdated: string,
): string {
const warningMark = isPermissive ? "" : "⚠️ ";
const warningNote = isPermissive
? ""
: "\n\n**Note:** This license may not be permissive. Please review before use.";
return `### ${warningMark}${dep}
${description || "No description available."}
- **NPM:** [${npmLink}](${npmLink})
- **License:** [${licenseLink}](${licenseLink})
- *Cache:* ${lastUpdated}${warningNote}
---`;
}
function updateProgress(current: number, total: number) {
const percentage = Math.round((current / total) * 100);
const progressBar = "=".repeat(percentage / 2) + "-".repeat(50 - percentage / 2);
process.stdout.write(`\r[${progressBar}] ${percentage}% | ${current}/${total}`);
}
function parseDate(dateString: string): Date {
// Try parsing ISO format first
let date = new Date(dateString);
if (!isNaN(date.getTime())) {
return date;
}
// Handle the specific format "31. 8. 2024 15"
const specificFormatMatch = dateString.match(/(\d+)\.\s*(\d+)\.\s*(\d+)\s*(\d+)/);
if (specificFormatMatch) {
const [, day, month, year, hour] = specificFormatMatch;
date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour));
if (!isNaN(date.getTime())) {
return date;
}
}
// Try parsing other common formats
const formats = [
"MM/DD/YYYY, HH:mm:ss",
"YYYY-MM-DD HH:mm:ss",
"MMMM D, YYYY HH:mm:ss",
"D. M. YYYY HH:mm:ss",
// Add more formats as needed
];
for (const format of formats) {
date = new Date(dateString);
if (!isNaN(date.getTime())) {
return date;
}
}
// If all parsing attempts fail, return the oldest possible date
console.warn(`Unable to parse date: ${dateString}. Using oldest possible date.`);
return new Date(0);
}
async function readCachedDependencies(
outputFile: string,
): Promise<Record<string, CachedDependencyInfo>> {
try {
const content = await fs.readFile(outputFile, "utf-8");
const dependencies = content.split("### ").slice(1);
const cachedDeps: Record<string, CachedDependencyInfo> = {};
for (const dep of dependencies) {
const lines = dep.split("\n").filter((line) => line.trim() !== "");
const name = lines[0].trim().replace("⚠️ ", "");
const description = lines[1].trim();
const npmLink = lines[2].split("(")[1].slice(0, -1);
const licenseLink = lines[3].split("(")[1].slice(0, -1);
const lastUpdatedLine = lines.find((line) => line.startsWith("- *Cache:*"));
const lastUpdated = lastUpdatedLine
? lastUpdatedLine.split(":")[1].trim().replace(/\*/g, "")
: new Date(0).toISOString();
const isPermissive = !lines[0].includes("⚠️");
cachedDeps[name] = {
name,
npmLink,
licenseLink,
description,
isPermissive,
lastUpdated,
};
}
return cachedDeps;
} catch (error) {
console.error("Error reading cached dependencies:", error);
return {};
}
}
async function generateDependenciesList(
inputFile: string = "package.json",
outputFile: string = "dependencies.md",
): Promise<void> {
try {
const packageJsonPath = path.resolve(process.cwd(), inputFile);
const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
const packageJson: PackageJson = JSON.parse(packageJsonContent);
const allDependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
const cachedDependencies = await readCachedDependencies(outputFile);
const dependenciesCount = Object.keys(allDependencies).length;
let processedCount = 0;
let nonPermissiveCount = 0;
let cachedCount = 0;
console.log("Generating dependency information:");
updateProgress(0, dependenciesCount);
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const dependenciesPromises = Object.keys(allDependencies)
.sort()
.map((dep) =>
queue.add(async () => {
const cachedInfo = cachedDependencies[dep];
const isCacheValid =
cachedInfo && parseDate(cachedInfo.lastUpdated) > oneWeekAgo;
let packageInfo: NpmPackageInfo;
let npmLink: string;
let licenseLink: string;
let isPermissive: boolean;
let lastUpdated: string;
if (isCacheValid) {
cachedCount++;
packageInfo = {
license: cachedInfo.licenseLink.split("/").pop(),
description: cachedInfo.description,
};
npmLink = cachedInfo.npmLink;
licenseLink = cachedInfo.licenseLink;
isPermissive = cachedInfo.isPermissive;
lastUpdated = cachedInfo.lastUpdated;
} else {
packageInfo = await fetchPackageInfo(dep);
npmLink = `https://www.npmjs.com/package/${encodeURIComponent(dep)}`;
licenseLink = packageInfo.license
? `https://opensource.org/licenses/${encodeURIComponent(
packageInfo.license,
)}`
: "No license information available";
isPermissive = isPermissiveLicense(packageInfo.license || "");
lastUpdated = new Date().toLocaleString();
}
if (!isPermissive) {
nonPermissiveCount++;
}
processedCount++;
updateProgress(processedCount, dependenciesCount);
return formatDependencyInfo(
dep,
npmLink,
licenseLink,
packageInfo.description || "",
isPermissive,
lastUpdated,
);
}),
);
const dependenciesList = (await Promise.all(dependenciesPromises)).join("\n\n");
process.stdout.write("\n");
const header = `# Project Dependencies List
> Generated on: ${new Date().toLocaleString()}
## Summary
- **Total Dependencies:** ${dependenciesCount}
- **Non-Permissive Licenses:** ${nonPermissiveCount}
## Description
This document contains a comprehensive list of all dependencies used in this codebase. For each dependency, you'll find:
- Its intended use
- Source link
- License information
- A link to the license definition
## Note
⚠️ **Warning:** Dependencies marked with ⚠️ have potentially non-permissive licenses. Please review these carefully before use.
---
`;
const footer = `
---
End of Dependencies List`;
const fullContent = header + dependenciesList + footer;
const outputPath = path.resolve(process.cwd(), outputFile);
await fs.writeFile(outputPath, fullContent, "utf-8");
console.log(`Dependencies list has been written to ${outputFile}`);
console.log(`Cached dependencies used: ${cachedCount}`);
if (nonPermissiveCount > 0) {
console.warn(
`\n⚠️ Warning: ${nonPermissiveCount} dependencies have potentially non-permissive licenses. Please review the output file.`,
);
}
} catch (error) {
console.error("An error occurred:", error);
}
}
generateDependenciesList();

Project Dependencies List

Generated on: 31. 8. 2024 15:56:56

Summary

  • Total Dependencies: 121
  • Non-Permissive Licenses: 0

Description

This document contains a comprehensive list of all dependencies used in this codebase. For each dependency, you'll find:

  • Its intended use
  • Source link
  • License information
  • A link to the license definition

Note

⚠️ Warning: Dependencies marked with ⚠️ have potentially non-permissive licenses. Please review these carefully before use.


@babel/core

Babel compiler core.


@commitlint/cli

Lint your commit messages


@commitlint/config-conventional

Shareable commitlint config enforcing conventional commits


@dev-plugins/react-navigation

Expo DevTools Plugin for React Navigation


@dev-plugins/react-query

Expo DevTools Plugin for TanStack Query


@expo/config

A library for interacting with the app.json


@expo/config-plugins

A library for Expo config plugins


@expo/metro-runtime

Tools for making advanced Metro bundler features work


@gorhom/bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀


@gorhom/portal

A simplified portal implementation for ⭕️ React Native ⭕️


@hookform/resolvers

React Hook Form validation resolvers: Yup, Joi, Superstruct, Zod, Vest, Class Validator, io-ts, Nope, computed-types, TypeBox, arktype, Typanion, Effect-TS and VineJS


@lingui/cli

CLI for working wit message catalogs


@lingui/macro

Macro for generating messages in ICU MessageFormat syntax


@lingui/react

React components for translations


@ptomasroos/react-native-multi-slider

Android and iOS supported pure JS slider component with multiple markers for React Native


@react-native-async-storage/async-storage

Asynchronous, persistent, key-value storage system for React Native.


@react-native-community/datetimepicker

DateTimePicker component for React Native


@react-native-community/eslint-config

ESLint config for React Native


@react-native-google-signin/google-signin

Google sign in for your react native applications


@react-native-picker/picker

React Native Picker for iOS, Android, macOS, and Windows


@react-navigation/bottom-tabs

Bottom tab navigator following iOS design guidelines


@react-navigation/native

React Native integration for React Navigation


@react-navigation/native-stack

Native stack navigator using react-native-screens


@sentry/react-native

Official Sentry SDK for react-native


@shopify/flash-list

FlashList is a more performant FlatList replacement


@tanstack/react-query

Hooks for managing, caching and syncing asynchronous and remote data in React


@testing-library/jest-dom

Custom jest matchers to test the state of the DOM


@testing-library/react-native

Simple and complete React Native testing utilities that encourage good testing practices.


@types/i18n-js

TypeScript definitions for i18n-js


@types/jest

TypeScript definitions for jest


@types/lodash.memoize

TypeScript definitions for lodash.memoize


@types/react

TypeScript definitions for react


@types/react-test-renderer

TypeScript definitions for react-test-renderer


@typescript-eslint/eslint-plugin

TypeScript plugin for ESLint


@typescript-eslint/parser

An ESLint custom parser which leverages TypeScript ESTree


app-icon-badge

App Icon Badge

App Icon Badge

✨ Easily generate icon


axios

Promise based HTTP client for the browser and node.js


babel-plugin-macros

Allows you to build compile-time libraries


babel-plugin-module-resolver

Module resolver plugin for Babel


burnt

Cross-platform toasts, powered by native elements.


class-variance-authority

Class Variance Authority 🧬


cross-env

Run scripts that set and use environment variables across platforms


dotenv

Loads environment variables from .env file


eslint

An AST-based pattern checker for JavaScript.


eslint-plugin-i18n-json

Fully extendable eslint plugin for JSON i18n translation files.


eslint-plugin-lingui

ESLint plugin for Lingui


eslint-plugin-simple-import-sort

Easy autofixable import sorting


eslint-plugin-tailwindcss

Rules enforcing best practices while using Tailwind CSS


eslint-plugin-testing-library

ESLint plugin to follow best practices and anticipate common mistakes when writing tests with Testing Library


eslint-plugin-unused-imports

Report and remove unused es6 modules


expo

The Expo SDK


ts-jest

A Jest transformer with source map support that lets you use Jest to test projects written in TypeScript


typescript

TypeScript is a language for application scale JavaScript development


zod

TypeScript-first schema declaration and validation library with static type inference


zustand

🐻 Bear necessities for state management in React



End of Dependencies List

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment