From b6f930061a1e42a167e6aa872b6261c3d5b1fa74 Mon Sep 17 00:00:00 2001 From: "Agusti F." <6601142+agustif@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:44:07 +0100 Subject: [PATCH 1/2] add --source flag for npm or git download --- package-lock.json | 205 ++++++++++++++++++++++++++++++++ package.json | 4 +- src/commands/fetch.ts | 45 ++++--- src/commands/remove.ts | 3 +- src/index.ts | 43 ++++--- src/lib/common.ts | 91 ++++++++++++++ src/lib/git.ts | 33 +----- src/lib/npm.ts | 262 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 614 insertions(+), 72 deletions(-) create mode 100644 package-lock.json create mode 100644 src/lib/common.ts create mode 100644 src/lib/npm.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2f1c6cf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,205 @@ +{ + "name": "opensrc", + "version": "0.4.4", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opensrc", + "version": "0.4.4", + "license": "Apache-2.0", + "dependencies": { + "commander": "^12.1.0", + "simple-git": "^3.27.0", + "tar": "^7.5.2" + }, + "bin": { + "opensrc": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "prettier": "^3.4.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/simple-git": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.30.0.tgz", + "integrity": "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==", + "license": "MIT", + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "node_modules/tar": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json index 7ca776b..f97075c 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "license": "Apache-2.0", "dependencies": { "commander": "^12.1.0", - "simple-git": "^3.27.0" + "simple-git": "^3.27.0", + "tar": "^7.5.2" }, "devDependencies": { "@types/node": "^22.10.0", @@ -44,4 +45,3 @@ "node": ">=18.0.0" } } - diff --git a/src/commands/fetch.ts b/src/commands/fetch.ts index f867e7b..8bca092 100644 --- a/src/commands/fetch.ts +++ b/src/commands/fetch.ts @@ -1,11 +1,8 @@ import { parsePackageSpec, resolvePackage } from "../lib/registry.js"; import { detectInstalledVersion } from "../lib/version.js"; -import { - fetchSource, - packageExists, - listSources, - readMetadata, -} from "../lib/git.js"; +import { fetchSource as fetchSourceGit } from "../lib/git.js"; +import { fetchSource as fetchSourceNpm, listSources } from "../lib/npm.js"; +import { packageExists, readMetadata } from "../lib/common.js"; import { ensureGitignore } from "../lib/gitignore.js"; import { ensureTsconfigExclude } from "../lib/tsconfig.js"; import { updateAgentsMd } from "../lib/agents.js"; @@ -16,10 +13,14 @@ import { import { confirm } from "../lib/prompt.js"; import type { FetchResult } from "../types.js"; +export type SourceType = "git" | "npm"; + export interface FetchOptions { cwd?: string; /** Override file modification permission: true = allow, false = deny, undefined = prompt */ allowModifications?: boolean; + /** Source download method: git (clone from GitHub) or npm (download tarball) */ + source?: SourceType; } /** @@ -52,7 +53,9 @@ async function checkFileModificationPermission( } // Prompt user for permission - console.log("\nopensrc can update the following files for better integration:"); + console.log( + "\nopensrc can update the following files for better integration:", + ); console.log(" • .gitignore - add opensrc/ to ignore list"); console.log(" • tsconfig.json - exclude opensrc/ from compilation"); console.log(" • AGENTS.md - add source code reference section\n"); @@ -79,19 +82,20 @@ export async function fetchCommand( options: FetchOptions = {}, ): Promise { const cwd = options.cwd || process.cwd(); + const source = options.source || "git"; const results: FetchResult[] = []; - // Check if we're allowed to modify files - const canModifyFiles = await checkFileModificationPermission(cwd, options.allowModifications); + const canModifyFiles = await checkFileModificationPermission( + cwd, + options.allowModifications, + ); if (canModifyFiles) { - // Ensure .gitignore has opensrc/ entry const gitignoreUpdated = await ensureGitignore(cwd); if (gitignoreUpdated) { console.log("✓ Added opensrc/ to .gitignore"); } - // Ensure tsconfig.json excludes opensrc/ const tsconfigUpdated = await ensureTsconfigExclude(cwd); if (tsconfigUpdated) { console.log("✓ Added opensrc/ to tsconfig.json exclude"); @@ -104,11 +108,9 @@ export async function fetchCommand( console.log(`\nFetching ${name}...`); try { - // Determine target version let version = explicitVersion; if (!version) { - // Try to detect from installed packages const installedVersion = await detectInstalledVersion(name, cwd); if (installedVersion) { version = installedVersion; @@ -120,7 +122,6 @@ export async function fetchCommand( console.log(` → Using specified version: ${version}`); } - // Check if already exists with the same version if (packageExists(name, cwd)) { const existingMeta = await readMetadata(name, cwd); if (existingMeta && existingMeta.version === version) { @@ -141,8 +142,6 @@ export async function fetchCommand( } } - // Resolve package info from npm registry - console.log(` → Resolving repository...`); const resolved = await resolvePackage(name, version); console.log(` → Found: ${resolved.repoUrl}`); @@ -150,14 +149,17 @@ export async function fetchCommand( console.log(` → Monorepo path: ${resolved.repoDirectory}`); } - // Fetch the source - console.log(` → Cloning at ${resolved.gitTag}...`); - const result = await fetchSource(resolved, cwd); + const fetchFunc = source === "npm" ? fetchSourceNpm : fetchSourceGit; + const sourceMsg = + source === "npm" + ? "Downloading from npm..." + : `Cloning at ${resolved.gitTag}...`; + console.log(` → ${sourceMsg}`); + const result = await fetchFunc(resolved, cwd); if (result.success) { console.log(` ✓ Saved to ${result.path}`); if (result.error) { - // Warning message (e.g., tag not found) console.log(` ⚠ ${result.error}`); } } else { @@ -178,13 +180,11 @@ export async function fetchCommand( } } - // Summary const successful = results.filter((r) => r.success).length; const failed = results.filter((r) => !r.success).length; console.log(`\nDone: ${successful} succeeded, ${failed} failed`); - // Update AGENTS.md with all fetched sources (only if permission granted) if (successful > 0 && canModifyFiles) { const allSources = await listSources(cwd); const agentsUpdated = await updateAgentsMd(allSources, cwd); @@ -192,7 +192,6 @@ export async function fetchCommand( console.log("✓ Updated AGENTS.md"); } } else if (successful > 0 && !canModifyFiles) { - // Still update the sources.json index even without modifying AGENTS.md const allSources = await listSources(cwd); const { updatePackageIndex } = await import("../lib/agents.js"); await updatePackageIndex(allSources, cwd); diff --git a/src/commands/remove.ts b/src/commands/remove.ts index dc24542..56a018c 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -1,4 +1,5 @@ -import { removeSource, packageExists, listSources } from "../lib/git.js"; +import { removeSource, packageExists } from "../lib/common.js"; +import { listSources } from "../lib/git.js"; import { updateAgentsMd } from "../lib/agents.js"; export interface RemoveOptions { diff --git a/src/index.ts b/src/index.ts index 8d92839..72ee2ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,22 +18,33 @@ program program .argument("[packages...]", "packages to fetch (e.g., zod, react@18.2.0)") .option("--cwd ", "working directory (default: current directory)") - .option("--modify [value]", "allow/deny modifying .gitignore, tsconfig.json, AGENTS.md", (val) => { - if (val === undefined || val === "" || val === "true") return true; - if (val === "false") return false; - return true; - }) - .action(async (packages: string[], options: { cwd?: string; modify?: boolean }) => { - if (packages.length === 0) { - program.help(); - return; - } - - await fetchCommand(packages, { - cwd: options.cwd, - allowModifications: options.modify, - }); - }); + .option( + "--modify [value]", + "allow/deny modifying .gitignore, tsconfig.json, AGENTS.md", + (val) => { + if (val === undefined || val === "" || val === "true") return true; + if (val === "false") return false; + return true; + }, + ) + .option("--source ", "download source from npm or git", "git") + .action( + async ( + packages: string[], + options: { cwd?: string; modify?: boolean; source?: string }, + ) => { + if (packages.length === 0) { + program.help(); + return; + } + + await fetchCommand(packages, { + cwd: options.cwd, + allowModifications: options.modify, + source: options.source as "git" | "npm", + }); + }, + ); // List command program diff --git a/src/lib/common.ts b/src/lib/common.ts new file mode 100644 index 0000000..d03417e --- /dev/null +++ b/src/lib/common.ts @@ -0,0 +1,91 @@ +import { join } from "path"; +import { existsSync } from "fs"; +import { rm, readFile } from "fs/promises"; + +const OPENSRC_DIR = "opensrc"; + +/** + * Get the opensrc directory path + */ +export function getOpensrcDir(cwd: string = process.cwd()): string { + return join(cwd, OPENSRC_DIR); +} + +/** + * Get the path where a package's source will be stored + */ +export function getPackagePath( + packageName: string, + cwd: string = process.cwd(), +): string { + return join(getOpensrcDir(cwd), packageName); +} + +/** + * Check if a package source already exists + */ +export function packageExists( + packageName: string, + cwd: string = process.cwd(), +): boolean { + return existsSync(getPackagePath(packageName, cwd)); +} + +/** + * Read metadata for a fetched package + */ +export async function readMetadata( + packageName: string, + cwd: string = process.cwd(), +): Promise<{ + name: string; + version: string; + repoUrl: string; + repoDirectory?: string; + fetchedTag: string; + fetchedAt: string; + downloadMethod?: "git" | "npm"; +} | null> { + const packagePath = getPackagePath(packageName, cwd); + const metadataPath = join(packagePath, ".opensrc-meta.json"); + + if (!existsSync(metadataPath)) { + return null; + } + + try { + const content = await readFile(metadataPath, "utf-8"); + return JSON.parse(content); + } catch { + return null; + } +} + +/** + * Remove source code for a package + */ +export async function removeSource( + packageName: string, + cwd: string = process.cwd(), +): Promise { + const packagePath = getPackagePath(packageName, cwd); + + if (!existsSync(packagePath)) { + return false; + } + + await rm(packagePath, { recursive: true, force: true }); + + if (packageName.startsWith("@")) { + const scopeDir = join(getOpensrcDir(cwd), packageName.split("/")[0]); + try { + const { readdir } = await import("fs/promises"); + const contents = await readdir(scopeDir); + if (contents.length === 0) { + await rm(scopeDir, { recursive: true, force: true }); + } + } catch {} + } + + return true; +} diff --git a/src/lib/git.ts b/src/lib/git.ts index fef648d..ad1b446 100644 --- a/src/lib/git.ts +++ b/src/lib/git.ts @@ -2,38 +2,9 @@ import { simpleGit, SimpleGit } from "simple-git"; import { rm, mkdir, readFile, writeFile } from "fs/promises"; import { join } from "path"; import { existsSync } from "fs"; +import { getOpensrcDir, getPackagePath, packageExists } from "./common.js"; import type { ResolvedPackage, FetchResult } from "../types.js"; -const OPENSRC_DIR = "opensrc"; - -/** - * Get the opensrc directory path - */ -export function getOpensrcDir(cwd: string = process.cwd()): string { - return join(cwd, OPENSRC_DIR); -} - -/** - * Get the path where a package's source will be stored - */ -export function getPackagePath( - packageName: string, - cwd: string = process.cwd(), -): string { - // Handle scoped packages: @scope/name -> @scope/name (keep the structure) - return join(getOpensrcDir(cwd), packageName); -} - -/** - * Check if a package source already exists - */ -export function packageExists( - packageName: string, - cwd: string = process.cwd(), -): boolean { - return existsSync(getPackagePath(packageName, cwd)); -} - /** * Try to clone at a specific tag, with fallbacks */ @@ -93,6 +64,7 @@ async function writeMetadata( repoDirectory: resolved.repoDirectory, fetchedTag: actualTag, fetchedAt: new Date().toISOString(), + downloadMethod: "git" as const, }; const metadataPath = join(packagePath, ".opensrc-meta.json"); @@ -112,6 +84,7 @@ export async function readMetadata( repoDirectory?: string; fetchedTag: string; fetchedAt: string; + downloadMethod?: "git" | "npm"; } | null> { const packagePath = getPackagePath(packageName, cwd); const metadataPath = join(packagePath, ".opensrc-meta.json"); diff --git a/src/lib/npm.ts b/src/lib/npm.ts new file mode 100644 index 0000000..cda8608 --- /dev/null +++ b/src/lib/npm.ts @@ -0,0 +1,262 @@ +import { mkdir, readFile, writeFile, rm } from "fs/promises"; +import { join } from "path"; +import { pipeline } from "stream/promises"; +import { createWriteStream, existsSync } from "fs"; +import { extract } from "tar"; +import { getOpensrcDir, getPackagePath, packageExists } from "./common.js"; +import type { ResolvedPackage, FetchResult } from "../types.js"; + +/** + * Download tarball from npm registry + */ +async function downloadTarball( + tarballUrl: string, + targetPath: string, +): Promise { + const response = await fetch(tarballUrl); + + if (!response.ok) { + throw new Error( + `Failed to download tarball: ${response.status} ${response.statusText}`, + ); + } + + if (!response.body) { + throw new Error("No response body"); + } + + const fileStream = createWriteStream(targetPath); + await pipeline(response.body, fileStream); +} + +/** + * Extract tarball to target directory + */ +async function extractTarball( + tarballPath: string, + targetPath: string, +): Promise { + await extract({ + file: tarballPath, + cwd: targetPath, + strip: 1, + }); + + await rm(tarballPath, { force: true }); +} + +/** + * Write metadata file about the fetched source + */ +async function writeMetadata( + packagePath: string, + resolved: ResolvedPackage, +): Promise { + const metadata = { + name: resolved.name, + version: resolved.version, + repoUrl: resolved.repoUrl, + repoDirectory: resolved.repoDirectory, + fetchedTag: resolved.gitTag, + fetchedAt: new Date().toISOString(), + downloadMethod: "npm" as const, + }; + + const metadataPath = join(packagePath, ".opensrc-meta.json"); + await writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"); +} + +/** + * Read metadata for a fetched package + */ +export async function readMetadata( + packageName: string, + cwd: string = process.cwd(), +): Promise<{ + name: string; + version: string; + repoUrl: string; + repoDirectory?: string; + fetchedTag: string; + fetchedAt: string; + downloadMethod?: "git" | "npm"; +} | null> { + const packagePath = getPackagePath(packageName, cwd); + const metadataPath = join(packagePath, ".opensrc-meta.json"); + + if (!existsSync(metadataPath)) { + return null; + } + + try { + const content = await readFile(metadataPath, "utf-8"); + return JSON.parse(content); + } catch { + return null; + } +} + +/** + * Fetch source code from npm tarball + */ +export async function fetchSource( + resolved: ResolvedPackage, + cwd: string = process.cwd(), +): Promise { + const packagePath = getPackagePath(resolved.name, cwd); + const opensrcDir = getOpensrcDir(cwd); + + if (!existsSync(opensrcDir)) { + await mkdir(opensrcDir, { recursive: true }); + } + + if (existsSync(packagePath)) { + await rm(packagePath, { recursive: true, force: true }); + } + + const parentDir = join(packagePath, ".."); + if (!existsSync(parentDir)) { + await mkdir(parentDir, { recursive: true }); + } + + console.log(` → Getting npm tarball URL...`); + const tarballUrl = await getTarballUrl(resolved.name, resolved.version); + console.log(` → Downloading from npm...`); + + const tempTarballPath = join( + opensrcDir, + `${resolved.name.replace(/\//g, "-")}.tgz`, + ); + + try { + await downloadTarball(tarballUrl, tempTarballPath); + await mkdir(packagePath, { recursive: true }); + await extractTarball(tempTarballPath, packagePath); + await writeMetadata(packagePath, resolved); + + let sourcePath = packagePath; + if (resolved.repoDirectory) { + sourcePath = join(packagePath, resolved.repoDirectory); + } + + return { + package: resolved.name, + version: resolved.version, + path: sourcePath, + success: true, + }; + } catch (error) { + if (existsSync(packagePath)) { + await rm(packagePath, { recursive: true, force: true }); + } + if (existsSync(tempTarballPath)) { + await rm(tempTarballPath, { force: true }); + } + + throw error; + } +} + +/** + * Get tarball URL for a specific package version + */ +async function getTarballUrl( + packageName: string, + version: string, +): Promise { + const NPM_REGISTRY = "https://registry.npmjs.org"; + const url = `${NPM_REGISTRY}/${encodeURIComponent(packageName).replace("%40", "@")}`; + + const response = await fetch(url, { + headers: { + Accept: "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch package info: ${response.status}`); + } + + const info = (await response.json()) as { + versions: { + [version: string]: { + dist: { + tarball: string; + }; + }; + }; + }; + + const versionInfo = info.versions[version]; + if (!versionInfo) { + throw new Error(`Version ${version} not found for ${packageName}`); + } + + return versionInfo.dist.tarball; +} + +/** + * List all fetched packages + */ +export async function listSources(cwd: string = process.cwd()): Promise< + Array<{ + name: string; + version: string; + path: string; + fetchedAt: string; + }> +> { + const opensrcDir = getOpensrcDir(cwd); + + if (!existsSync(opensrcDir)) { + return []; + } + + const { readdir } = await import("fs/promises"); + const results: Array<{ + name: string; + version: string; + path: string; + fetchedAt: string; + }> = []; + + const entries = await readdir(opensrcDir, { withFileTypes: true }); + + for (const entry of entries) { + if (!entry.isDirectory()) continue; + + if (entry.name.startsWith("@")) { + const scopeDir = join(opensrcDir, entry.name); + const scopeEntries = await readdir(scopeDir, { withFileTypes: true }); + + for (const scopeEntry of scopeEntries) { + if (!scopeEntry.isDirectory()) continue; + + const packageName = `${entry.name}/${scopeEntry.name}`; + const metadata = await readMetadata(packageName, cwd); + + if (metadata) { + results.push({ + name: packageName, + version: metadata.version, + path: getPackagePath(packageName, cwd), + fetchedAt: metadata.fetchedAt, + }); + } + } + } else { + const metadata = await readMetadata(entry.name, cwd); + + if (metadata) { + results.push({ + name: entry.name, + version: metadata.version, + path: getPackagePath(entry.name, cwd), + fetchedAt: metadata.fetchedAt, + }); + } + } + } + + return results.sort((a, b) => a.name.localeCompare(b.name)); +} From 0ebac57bf13ef0656c85c00774450cb54838a10c Mon Sep 17 00:00:00 2001 From: "Agusti F." <6601142+agustif@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:46:55 +0100 Subject: [PATCH 2/2] fix: remove package-lock.json --- package-lock.json | 205 ---------------------------------------------- 1 file changed, 205 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 2f1c6cf..0000000 --- a/package-lock.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "name": "opensrc", - "version": "0.4.4", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "opensrc", - "version": "0.4.4", - "license": "Apache-2.0", - "dependencies": { - "commander": "^12.1.0", - "simple-git": "^3.27.0", - "tar": "^7.5.2" - }, - "bin": { - "opensrc": "dist/index.js" - }, - "devDependencies": { - "@types/node": "^22.10.0", - "prettier": "^3.4.0", - "typescript": "^5.7.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@kwsites/file-exists": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1" - } - }, - "node_modules/@kwsites/promise-deferred": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/simple-git": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.30.0.tgz", - "integrity": "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==", - "license": "MIT", - "dependencies": { - "@kwsites/file-exists": "^1.1.1", - "@kwsites/promise-deferred": "^1.1.1", - "debug": "^4.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/steveukx/git-js?sponsor=1" - } - }, - "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - } - } -}