Comparing version
#!/usr/bin/env node | ||
import ts from "typescript"; | ||
import { run } from "./copypack.js"; | ||
const [relativeProjectPath, ...sourcePackages] = ts.sys.args; | ||
if (!relativeProjectPath) { | ||
throw new Error("Missing relative project path"); | ||
import { run, watch } from "./copypack.js"; | ||
import { parseArgs } from "node:util"; | ||
import { resolve } from "node:path"; | ||
import { greenColor, redColor } from "./utils.js"; | ||
import { readFileSync } from "node:fs"; | ||
const cliArgsOptions = { | ||
strict: true, | ||
options: { | ||
source: { | ||
type: "string", | ||
short: "s", | ||
default: process.cwd(), | ||
}, | ||
target: { | ||
type: "string", | ||
short: "t", | ||
}, | ||
matchers: { | ||
type: "string", | ||
short: "m", | ||
multiple: true, | ||
default: ["packages/*/package.json"], | ||
}, | ||
watch: { | ||
type: "boolean", | ||
short: "w", | ||
}, | ||
version: { | ||
type: "boolean", | ||
short: "v", | ||
}, | ||
help: { | ||
type: "boolean", | ||
short: "h", | ||
}, | ||
}, | ||
}; | ||
const { values: { help, source, target, matchers, watch: watchMode, version: versionMode, }, } = parseArgs(cliArgsOptions); | ||
const version = loadPackageJson().version; | ||
if (versionMode) { | ||
console.log(version); | ||
process.exit(0); | ||
} | ||
if (!sourcePackages.length) { | ||
sourcePackages.push("packages/*/package.json"); | ||
if (help) { | ||
printHelp(); | ||
process.exit(0); | ||
} | ||
run(ts.sys.getCurrentDirectory(), relativeProjectPath, sourcePackages); | ||
console.log(greenColor(`Running to copypack version: ${version}`)); | ||
if (!matchers) { | ||
console.log(redColor("Missing matchers")); | ||
process.exit(1); | ||
} | ||
if (!source) { | ||
console.log(redColor("Missing source project path")); | ||
process.exit(1); | ||
} | ||
if (!target) { | ||
console.log(redColor("Missing target project path")); | ||
process.exit(1); | ||
} | ||
if (watchMode) { | ||
watch(resolve(source), resolve(target), matchers); | ||
} | ||
else { | ||
run(resolve(source), resolve(target), matchers); | ||
} | ||
function printHelp() { | ||
console.log(` | ||
Usage: copypack [options] | ||
Options: | ||
-s, --source <path> Source project path (default: current working directory) | ||
-t, --target <path> Target project path | ||
-m, --matchers <glob> Ts based glob patterns to match package.json files (default: packages/*/package.json) (multiple) | ||
-w, --watch Watch mode | ||
-v, --version Print copypack version | ||
-h, --help Print this message | ||
`); | ||
} | ||
function loadPackageJson() { | ||
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); | ||
} | ||
//# sourceMappingURL=cli.js.map |
@@ -1,2 +0,3 @@ | ||
export declare function run(context: string, targetProjectRelativePath: string, sourcePackagesFilter: string[]): void; | ||
export declare function watch(sourceProjectPath: string, targetProjectPath: string, sourcePackagesMatchers: string[]): void; | ||
export declare function run(sourceProjectPath: string, targetProjectPath: string, sourcePackagesMatchers: string[]): void; | ||
//# sourceMappingURL=copypack.d.ts.map |
import ts from "typescript"; | ||
import fs from "node:fs"; | ||
import path from "node:path"; | ||
export function run(context, targetProjectRelativePath, sourcePackagesFilter) { | ||
const targetProjectPath = path.join(context, targetProjectRelativePath); | ||
import watcher from "@parcel/watcher"; | ||
import { debounce, greenColor, darkGrayColor, redColor } from "./utils.js"; | ||
export function watch(sourceProjectPath, targetProjectPath, sourcePackagesMatchers) { | ||
const debounceTime = 1000; | ||
const tryRun = debounce(() => { | ||
try { | ||
console.log(greenColor("Running...")); | ||
run(sourceProjectPath, targetProjectPath, sourcePackagesMatchers); | ||
} | ||
catch (error) { | ||
console.error(error); | ||
} | ||
}, debounceTime); | ||
watcher.subscribe(sourceProjectPath, (err) => { | ||
if (err) { | ||
console.error(err); | ||
} | ||
else { | ||
log(redColor(`Changes detected, running in ${debounceTime / 1000} seconds.`)); | ||
tryRun(); | ||
} | ||
}); | ||
} | ||
export function run(sourceProjectPath, targetProjectPath, sourcePackagesMatchers) { | ||
const targetNodeModulesPath = getExistingNodeModulesPath(targetProjectPath); | ||
const packageJsonPaths = ts.sys.readDirectory(context, undefined, undefined, sourcePackagesFilter); | ||
const packageJsonPaths = ts.sys.readDirectory(sourceProjectPath, undefined, undefined, sourcePackagesMatchers); | ||
if (!targetNodeModulesPath) { | ||
@@ -12,5 +34,5 @@ throw new Error(nodeModulesNotFound(targetProjectPath)); | ||
if (packageJsonPaths.length === 0) { | ||
throw new Error(noPackageJsonFound(sourcePackagesFilter)); | ||
throw new Error(noPackageJsonFound(sourcePackagesMatchers)); | ||
} | ||
console.log(start(packageJsonPaths.length, targetNodeModulesPath)); | ||
log(start(packageJsonPaths.length, targetNodeModulesPath)); | ||
for (const filePath of packageJsonPaths) { | ||
@@ -21,25 +43,85 @@ try { | ||
const currentPackageDirPath = path.dirname(filePath); | ||
const targetPackageLocation = path.join(targetNodeModulesPath, pkg.name); | ||
if (fs.existsSync(targetPackageLocation) && | ||
fs.statSync(targetPackageLocation).isDirectory()) { | ||
fs.rmSync(targetPackageLocation, { recursive: true }); | ||
fs.mkdirSync(targetPackageLocation, { recursive: true }); | ||
fs.cpSync(currentPackageDirPath, targetPackageLocation, { | ||
const targetPackagePath = path.join(targetNodeModulesPath, pkg.name); | ||
const targetPackageJsonPath = path.join(targetPackagePath, "package.json"); | ||
if (fs.existsSync(targetPackagePath) && | ||
fs.statSync(targetPackagePath).isDirectory()) { | ||
reportPackageDependencyChanges(targetPackageJsonPath, pkg); | ||
fs.rmSync(targetPackagePath, { recursive: true }); | ||
fs.mkdirSync(targetPackagePath, { recursive: true }); | ||
fs.cpSync(currentPackageDirPath, targetPackagePath, { | ||
recursive: true, | ||
}); | ||
console.log(copied(pkg.name)); | ||
log(greenColor(copied(pkg.name))); | ||
} | ||
else { | ||
console.log(skipped(pkg.name, "does not exist in target node_modules")); | ||
log(darkGrayColor(skipped(pkg.name, "does not exist in target node_modules"))); | ||
} | ||
} | ||
else { | ||
console.log(skipped(filePath, "no package name")); | ||
log(redColor(skipped(filePath, "no package name"))); | ||
} | ||
} | ||
catch (error) { | ||
console.log(skipped(filePath, "error while reading or parsing package.json", error)); | ||
log(redColor(skipped(filePath, "error while reading or parsing package.json", error))); | ||
} | ||
} | ||
} | ||
function reportPackageDependencyChanges(targetPackageJsonPath, pkg) { | ||
const targetPkg = JSON.parse(ts.sys.readFile(targetPackageJsonPath) || "null"); | ||
if (!targetPkg) { | ||
throw new Error(`Could not read ${targetPackageJsonPath}`); | ||
} | ||
reportChangesInDependencies(pkg.dependencies || {}, targetPkg.dependencies || {}, "dependencies"); | ||
reportChangesInDependencies(pkg.devDependencies || {}, targetPkg.devDependencies || {}, "devDependencies"); | ||
reportChangesInDependencies(pkg.peerDependencies || {}, targetPkg.peerDependencies || {}, "peerDependencies"); | ||
reportChangesInDependencies(pkg.optionalDependencies || {}, targetPkg.optionalDependencies || {}, "optionalDependencies"); | ||
} | ||
function reportChangesInDependencies(currentDependencies, targetDependencies, key) { | ||
const currentDependencyNames = new Set(Object.keys(currentDependencies)); | ||
const targetDependencyNames = new Set(Object.keys(targetDependencies)); | ||
const addedDependencies = []; | ||
const removedDependencies = []; | ||
const updatedDependencies = []; | ||
if (currentDependencyNames.size || targetDependencyNames.size) { | ||
log(`Checking changes in ${key}...`); | ||
} | ||
for (const dependencyName of currentDependencyNames) { | ||
if (!targetDependencyNames.has(dependencyName)) { | ||
addedDependencies.push({ | ||
dependencyName, | ||
version: currentDependencies[dependencyName], | ||
}); | ||
} | ||
else if (currentDependencies[dependencyName] !== targetDependencies[dependencyName]) { | ||
updatedDependencies.push({ | ||
dependencyName, | ||
currentVersion: currentDependencies[dependencyName], | ||
targetVersion: targetDependencies[dependencyName], | ||
}); | ||
} | ||
} | ||
for (const dependencyName of targetDependencyNames) { | ||
if (!currentDependencyNames.has(dependencyName)) { | ||
removedDependencies.push(dependencyName); | ||
} | ||
} | ||
if (removedDependencies.length > 0) { | ||
log("Removed dependencies:"); | ||
for (const removedDependency of removedDependencies) { | ||
log(removedDependency); | ||
} | ||
} | ||
if (addedDependencies.length > 0) { | ||
log("Added dependencies:"); | ||
for (const addedDependency of addedDependencies) { | ||
log(`${addedDependency.dependencyName}: ${addedDependency.version}`); | ||
} | ||
} | ||
if (updatedDependencies.length > 0) { | ||
log("Updated dependencies:"); | ||
for (const updatedDependency of updatedDependencies) { | ||
log(`${updatedDependency.dependencyName}: ${updatedDependency.currentVersion} -> ${updatedDependency.targetVersion}`); | ||
} | ||
} | ||
} | ||
function getExistingNodeModulesPath(targetProjectPath) { | ||
@@ -55,7 +137,8 @@ const targetNodeModulesPath = path.join(targetProjectPath, "node_modules"); | ||
} | ||
const log = (message) => console.log(message); | ||
const copied = (pkg) => `COPIED: ${pkg}`; | ||
const skipped = (pkg, reason, error) => `SKIPPED: ${pkg} (${reason})${error ? `\n${error}` : ""}`; | ||
const start = (count, path) => `Start copying ${count === 1 ? "package" : "packages"} to ${path}`; | ||
const start = (count, path) => `Start copying ${count} ${count === 1 ? "package" : "packages"} to ${path}`; | ||
const nodeModulesNotFound = (path) => `node_modules does not exist or not a directory in: ${path}`; | ||
const noPackageJsonFound = (filters) => `No package.json files found for the given patterns [${filters}]`; | ||
//# sourceMappingURL=copypack.js.map |
@@ -8,5 +8,3 @@ import { run } from "./copypack.js"; | ||
try { | ||
run(path.join(testDirAbs, "source-package"), "../target-package", [ | ||
"packages/*/package.json", | ||
]); | ||
run(path.join(testDirAbs, "source-package"), path.join(testDirAbs, "target-package"), ["packages/*/package.json"]); | ||
const content = fs.readFileSync(path.join(testDirAbs, "target-package", "node_modules", "source-package", "file.js"), "utf8"); | ||
@@ -13,0 +11,0 @@ if (content !== "// this is the file from the source package!") { |
{ | ||
"name": "copypack", | ||
"version": "1.0.6", | ||
"version": "1.0.7", | ||
"description": "like links but copies", | ||
@@ -19,5 +19,7 @@ "type": "module", | ||
"dependencies": { | ||
"@parcel/watcher": "^2.1.0", | ||
"@types/node": "^20.1.5", | ||
"type-fest": "^3.10.0", | ||
"typescript": "^5.0.4" | ||
} | ||
} |
106
src/cli.ts
#!/usr/bin/env node | ||
import ts from "typescript"; | ||
import { run } from "./copypack.js"; | ||
import { run, watch } from "./copypack.js"; | ||
import { parseArgs, ParseArgsConfig } from "node:util"; | ||
import { resolve } from "node:path"; | ||
import { greenColor, redColor } from "./utils.js"; | ||
import { readFileSync } from "node:fs"; | ||
const [relativeProjectPath, ...sourcePackages] = ts.sys.args; | ||
const cliArgsOptions = { | ||
strict: true, | ||
options: { | ||
source: { | ||
type: "string" as const, | ||
short: "s", | ||
default: process.cwd(), | ||
}, | ||
target: { | ||
type: "string" as const, | ||
short: "t", | ||
}, | ||
matchers: { | ||
type: "string" as const, | ||
short: "m", | ||
multiple: true, | ||
default: ["packages/*/package.json"] as string[], | ||
}, | ||
watch: { | ||
type: "boolean" as const, | ||
short: "w", | ||
}, | ||
version: { | ||
type: "boolean" as const, | ||
short: "v", | ||
}, | ||
help: { | ||
type: "boolean" as const, | ||
short: "h", | ||
}, | ||
}, | ||
} as const satisfies ParseArgsConfig; | ||
if (!relativeProjectPath) { | ||
throw new Error("Missing relative project path"); | ||
const { | ||
values: { | ||
help, | ||
source, | ||
target, | ||
matchers, | ||
watch: watchMode, | ||
version: versionMode, | ||
}, | ||
} = parseArgs(cliArgsOptions); | ||
const version = loadPackageJson().version; | ||
if (versionMode) { | ||
console.log(version); | ||
process.exit(0); | ||
} | ||
if (!sourcePackages.length) { | ||
sourcePackages.push("packages/*/package.json"); | ||
if (help) { | ||
printHelp(); | ||
process.exit(0); | ||
} | ||
run(ts.sys.getCurrentDirectory(), relativeProjectPath, sourcePackages); | ||
console.log(greenColor(`Running to copypack version: ${version}`)); | ||
if (!matchers) { | ||
console.log(redColor("Missing matchers")); | ||
process.exit(1); | ||
} | ||
if (!source) { | ||
console.log(redColor("Missing source project path")); | ||
process.exit(1); | ||
} | ||
if (!target) { | ||
console.log(redColor("Missing target project path")); | ||
process.exit(1); | ||
} | ||
if (watchMode) { | ||
watch(resolve(source), resolve(target), matchers); | ||
} else { | ||
run(resolve(source), resolve(target), matchers); | ||
} | ||
function printHelp() { | ||
console.log(` | ||
Usage: copypack [options] | ||
Options: | ||
-s, --source <path> Source project path (default: current working directory) | ||
-t, --target <path> Target project path | ||
-m, --matchers <glob> Ts based glob patterns to match package.json files (default: packages/*/package.json) (multiple) | ||
-w, --watch Watch mode | ||
-v, --version Print copypack version | ||
-h, --help Print this message | ||
`); | ||
} | ||
function loadPackageJson(): { version: string } { | ||
return JSON.parse( | ||
readFileSync(new URL("../package.json", import.meta.url), "utf8") | ||
); | ||
} |
@@ -11,5 +11,7 @@ import { run } from "./copypack.js"; | ||
try { | ||
run(path.join(testDirAbs, "source-package"), "../target-package", [ | ||
"packages/*/package.json", | ||
]); | ||
run( | ||
path.join(testDirAbs, "source-package"), | ||
path.join(testDirAbs, "target-package"), | ||
["packages/*/package.json"] | ||
); | ||
const content = fs.readFileSync( | ||
@@ -16,0 +18,0 @@ path.join( |
import ts from "typescript"; | ||
import fs from "node:fs"; | ||
import path from "node:path"; | ||
import watcher from "@parcel/watcher"; | ||
import type { PackageJson } from "type-fest"; | ||
import { debounce, greenColor, darkGrayColor, redColor } from "./utils.js"; | ||
export function watch( | ||
sourceProjectPath: string, | ||
targetProjectPath: string, | ||
sourcePackagesMatchers: string[] | ||
) { | ||
const debounceTime = 1000; | ||
const tryRun = debounce(() => { | ||
try { | ||
console.log(greenColor("Running...")); | ||
run(sourceProjectPath, targetProjectPath, sourcePackagesMatchers); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}, debounceTime); | ||
watcher.subscribe(sourceProjectPath, (err) => { | ||
if (err) { | ||
console.error(err); | ||
} else { | ||
log( | ||
redColor(`Changes detected, running in ${debounceTime / 1000} seconds.`) | ||
); | ||
tryRun(); | ||
} | ||
}); | ||
} | ||
export function run( | ||
context: string, | ||
targetProjectRelativePath: string, | ||
sourcePackagesFilter: string[] | ||
sourceProjectPath: string, | ||
targetProjectPath: string, | ||
sourcePackagesMatchers: string[] | ||
) { | ||
const targetProjectPath = path.join(context, targetProjectRelativePath); | ||
const targetNodeModulesPath = getExistingNodeModulesPath(targetProjectPath); | ||
const packageJsonPaths = ts.sys.readDirectory( | ||
context, | ||
sourceProjectPath, | ||
undefined, | ||
undefined, | ||
sourcePackagesFilter | ||
sourcePackagesMatchers | ||
); | ||
@@ -24,6 +53,6 @@ | ||
if (packageJsonPaths.length === 0) { | ||
throw new Error(noPackageJsonFound(sourcePackagesFilter)); | ||
throw new Error(noPackageJsonFound(sourcePackagesMatchers)); | ||
} | ||
console.log(start(packageJsonPaths.length, targetNodeModulesPath)); | ||
log(start(packageJsonPaths.length, targetNodeModulesPath)); | ||
@@ -35,27 +64,37 @@ for (const filePath of packageJsonPaths) { | ||
const currentPackageDirPath = path.dirname(filePath); | ||
const targetPackageLocation = path.join( | ||
targetNodeModulesPath, | ||
pkg.name | ||
const targetPackagePath = path.join(targetNodeModulesPath, pkg.name); | ||
const targetPackageJsonPath = path.join( | ||
targetPackagePath, | ||
"package.json" | ||
); | ||
if ( | ||
fs.existsSync(targetPackageLocation) && | ||
fs.statSync(targetPackageLocation).isDirectory() | ||
fs.existsSync(targetPackagePath) && | ||
fs.statSync(targetPackagePath).isDirectory() | ||
) { | ||
fs.rmSync(targetPackageLocation, { recursive: true }); | ||
fs.mkdirSync(targetPackageLocation, { recursive: true }); | ||
fs.cpSync(currentPackageDirPath, targetPackageLocation, { | ||
reportPackageDependencyChanges(targetPackageJsonPath, pkg); | ||
fs.rmSync(targetPackagePath, { recursive: true }); | ||
fs.mkdirSync(targetPackagePath, { recursive: true }); | ||
fs.cpSync(currentPackageDirPath, targetPackagePath, { | ||
recursive: true, | ||
}); | ||
console.log(copied(pkg.name)); | ||
log(greenColor(copied(pkg.name))); | ||
} else { | ||
console.log( | ||
skipped(pkg.name, "does not exist in target node_modules") | ||
log( | ||
darkGrayColor( | ||
skipped(pkg.name, "does not exist in target node_modules") | ||
) | ||
); | ||
} | ||
} else { | ||
console.log(skipped(filePath, "no package name")); | ||
log(redColor(skipped(filePath, "no package name"))); | ||
} | ||
} catch (error) { | ||
console.log( | ||
skipped(filePath, "error while reading or parsing package.json", error) | ||
log( | ||
redColor( | ||
skipped( | ||
filePath, | ||
"error while reading or parsing package.json", | ||
error | ||
) | ||
) | ||
); | ||
@@ -66,2 +105,102 @@ } | ||
function reportPackageDependencyChanges( | ||
targetPackageJsonPath: string, | ||
pkg: PackageJson | ||
) { | ||
const targetPkg = JSON.parse( | ||
ts.sys.readFile(targetPackageJsonPath) || "null" | ||
) as PackageJson | null; | ||
if (!targetPkg) { | ||
throw new Error(`Could not read ${targetPackageJsonPath}`); | ||
} | ||
reportChangesInDependencies( | ||
pkg.dependencies || {}, | ||
targetPkg.dependencies || {}, | ||
"dependencies" | ||
); | ||
reportChangesInDependencies( | ||
pkg.devDependencies || {}, | ||
targetPkg.devDependencies || {}, | ||
"devDependencies" | ||
); | ||
reportChangesInDependencies( | ||
pkg.peerDependencies || {}, | ||
targetPkg.peerDependencies || {}, | ||
"peerDependencies" | ||
); | ||
reportChangesInDependencies( | ||
pkg.optionalDependencies || {}, | ||
targetPkg.optionalDependencies || {}, | ||
"optionalDependencies" | ||
); | ||
} | ||
function reportChangesInDependencies( | ||
currentDependencies: Partial<Record<string, string>>, | ||
targetDependencies: Partial<Record<string, string>>, | ||
key: | ||
| "dependencies" | ||
| "devDependencies" | ||
| "peerDependencies" | ||
| "optionalDependencies" | ||
) { | ||
const currentDependencyNames = new Set(Object.keys(currentDependencies)); | ||
const targetDependencyNames = new Set(Object.keys(targetDependencies)); | ||
const addedDependencies = []; | ||
const removedDependencies = []; | ||
const updatedDependencies = []; | ||
if (currentDependencyNames.size || targetDependencyNames.size) { | ||
log(`Checking changes in ${key}...`); | ||
} | ||
for (const dependencyName of currentDependencyNames) { | ||
if (!targetDependencyNames.has(dependencyName)) { | ||
addedDependencies.push({ | ||
dependencyName, | ||
version: currentDependencies[dependencyName], | ||
}); | ||
} else if ( | ||
currentDependencies[dependencyName] !== targetDependencies[dependencyName] | ||
) { | ||
updatedDependencies.push({ | ||
dependencyName, | ||
currentVersion: currentDependencies[dependencyName], | ||
targetVersion: targetDependencies[dependencyName], | ||
}); | ||
} | ||
} | ||
for (const dependencyName of targetDependencyNames) { | ||
if (!currentDependencyNames.has(dependencyName)) { | ||
removedDependencies.push(dependencyName); | ||
} | ||
} | ||
if (removedDependencies.length > 0) { | ||
log("Removed dependencies:"); | ||
for (const removedDependency of removedDependencies) { | ||
log(removedDependency); | ||
} | ||
} | ||
if (addedDependencies.length > 0) { | ||
log("Added dependencies:"); | ||
for (const addedDependency of addedDependencies) { | ||
log(`${addedDependency.dependencyName}: ${addedDependency.version}`); | ||
} | ||
} | ||
if (updatedDependencies.length > 0) { | ||
log("Updated dependencies:"); | ||
for (const updatedDependency of updatedDependencies) { | ||
log( | ||
`${updatedDependency.dependencyName}: ${updatedDependency.currentVersion} -> ${updatedDependency.targetVersion}` | ||
); | ||
} | ||
} | ||
} | ||
function getExistingNodeModulesPath(targetProjectPath: string) { | ||
@@ -79,2 +218,3 @@ const targetNodeModulesPath = path.join(targetProjectPath, "node_modules"); | ||
const log = (message: string) => console.log(message); | ||
const copied = (pkg: string) => `COPIED: ${pkg}`; | ||
@@ -84,3 +224,3 @@ const skipped = (pkg: string, reason: string, error?: unknown) => | ||
const start = (count: number, path: string) => | ||
`Start copying ${count === 1 ? "package" : "packages"} to ${path}`; | ||
`Start copying ${count} ${count === 1 ? "package" : "packages"} to ${path}`; | ||
const nodeModulesNotFound = (path: string) => | ||
@@ -87,0 +227,0 @@ `node_modules does not exist or not a directory in: ${path}`; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
45354
70.21%26
23.81%726
124.77%4
100%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added