Skip to content

Commit

Permalink
feat(mod)!: per-dependency updating of a lockfile
Browse files Browse the repository at this point in the history
BREAKING CHANGE: tons of changes on exported and internal interfaces
  • Loading branch information
hasundue committed Mar 7, 2024
1 parent 48204a9 commit c9485c0
Show file tree
Hide file tree
Showing 34 changed files with 3,070 additions and 1,484 deletions.
90 changes: 44 additions & 46 deletions cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { colors, Command } from "./lib/x/cliffy.ts";
import { $ } from "./lib/x/dax.ts";
import { ensure, is } from "./lib/x/unknownutil.ts";
import { findFileUp } from "./lib/path.ts";
import { parse, resolveLatestVersion } from "./lib/dependency.ts";
import {
collect,
type CommitSequence,
CollectResult,
CommitSequence,
createCommitSequence,
type DependencyUpdate,
exec,
writeAll,
DependencyUpdate,
execute,
parse,
resolveLatestVersion,
write,
} from "./mod.ts";

const { gray, yellow, bold, cyan } = colors;
Expand Down Expand Up @@ -62,14 +64,14 @@ const main = new Command()
}
}
ensureFiles(files);
const updates = await collectUpdates(files, options);
printUpdates(files, updates);
const result = await collectUpdates(files, options);
printResult(files, result);
if (options.write) {
return writeUpdates(updates, options);
return writeResult(result, options);
}
if (options.commit) {
const tasks = await getTasks();
return commitUpdates(updates, {
return commitResult(result, {
...options,
preCommit: filterKeys(
tasks,
Expand Down Expand Up @@ -100,27 +102,23 @@ async function collectUpdates(
importMap?: string;
only?: string[];
},
): Promise<DependencyUpdate[]> {
return await $.progress("Checking for updates").with(async () => {
const updates = await Promise.all(
entrypoints.map(async (entrypoint) =>
await collect(entrypoint, {
ignore: options.ignore
? (dep) => options.ignore!.some((it) => dep.name.includes(it))
: undefined,
importMap: options.importMap,
only: options.only
? (dep) => options.only!.some((it) => dep.name.includes(it))
: undefined,
})
),
).then((results) => results.flat());
if (!updates.length) {
console.log("🍵 No updates found");
Deno.exit(0);
}
return updates;
});
): Promise<CollectResult> {
const result = await $.progress("Checking for updates").with(() =>
collect(entrypoints, {
importMap: options.importMap,
ignore: options.ignore
? (dep) => options.ignore!.some((it) => dep.name.includes(it))
: undefined,
only: options.only
? (dep) => options.only!.some((it) => dep.name.includes(it))
: undefined,
})
);
if (!result.updates.length) {
console.log("🍵 No updates found");
Deno.exit(0);
}
return result;
}

type TaskRecord = Record<string, string[]>;
Expand Down Expand Up @@ -151,28 +149,28 @@ async function getTasks() {

const toRelativePath = (path: string) => relative(Deno.cwd(), path);

function printUpdates(
function printResult(
files: string[],
updates: DependencyUpdate[],
result: CollectResult,
) {
const dependencies = new Map<string, DependencyUpdate[]>();
for (const u of updates) {
for (const u of result.updates) {
const list = dependencies.get(u.to.name) ?? [];
list.push(u);
dependencies.set(u.to.name, list);
}
let count = 0;
const nWrites = distinct(updates.map((u) => u.referrer)).length;
const nWrites = distinct(result.updates.map((u) => u.referrer)).length;
for (const [name, list] of dependencies.entries()) {
const froms = distinct(list.map((u) => u.from.version)).join(", ");
const froms = distinct(list.map((u) => u.from?.version)).join(", ");
console.log(
`📦 ${bold(name)} ${yellow(froms)} => ${yellow(list[0].to.version)}`,
);
if (files.length > 1 || nWrites > 1) {
distinct(
list.map((u) => {
const source = toRelativePath(u.map?.source ?? u.referrer);
return ` ${source} ` + gray(u.from.version ?? "");
return ` ${source} ` + gray(u.from?.version ?? "");
}),
).forEach((line) => console.log(line));
if (++count < dependencies.size) {
Expand All @@ -182,15 +180,15 @@ function printUpdates(
}
}

async function writeUpdates(
updates: DependencyUpdate[],
async function writeResult(
result: CollectResult,
options?: {
summary?: string;
report?: string;
},
) {
console.log();
await writeAll(updates, {
await write(result, {
onWrite: (file) => console.log(`💾 ${toRelativePath(file.path)}`),
});
if (options?.summary || options?.report) {
Expand All @@ -202,15 +200,17 @@ async function writeUpdates(
}
if (options?.report) {
const content = distinct(
updates.map((u) => `- ${u.to.name} ${u.from.version} => ${u.to.version}`),
result.updates.map((u) =>
`- ${u.to.name} ${u.from?.version} => ${u.to.version}`
),
).join("\n");
await Deno.writeTextFile(options.report, content);
console.log(`📄 ${options.report}`);
}
}

async function commitUpdates(
updates: DependencyUpdate[],
async function commitResult(
result: CollectResult,
options: {
preCommit?: TaskRecord;
postCommit?: TaskRecord;
Expand All @@ -226,8 +226,7 @@ async function commitUpdates(
const hasTask = preCommitTasks.length > 0 || postCommitTasks.length > 0;

let count = 0;

const commits = createCommitSequence(updates, {
const commits = createCommitSequence(result, {
groupBy: (dependency) => dependency.to.name,
composeCommitMessage: ({ group, version }) =>
formatPrefix(options.prefix) + `bump ${group}` +
Expand All @@ -251,8 +250,7 @@ async function commitUpdates(
}
},
});

await exec(commits);
await execute(commits);

if (options?.summary || options?.report) {
console.log();
Expand Down
4 changes: 2 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"cache": "deno cache ./lib/*/*.ts --lock",
"lock": "deno task -q cache --lock-write && git add deno.lock",
"check": "deno check ./*.ts ./lib/*.ts ./test/integration/*.ts",
"test": "NO_COLOR=1 deno test -A --no-check ./lib",
"test": "NO_COLOR=1 deno test -A --no-check",
"pre-commit": "deno fmt && deno lint && deno task -q check && deno task lock && deno task -q test",
"integration": "NO_COLOR=1 deno test --no-lock -A ./test/integration/*.ts",
"integration": "deno task -q test --no-lock ./test/integration/*.ts",
"run": "deno run --allow-env --allow-read --allow-net --allow-write=. --allow-run=git,deno cli.ts",
"update": "deno run --allow-env --allow-read --allow-write --allow-net=deno.land,registry.npmjs.org --allow-run=git,deno ./cli.ts ./lib/*/*.ts",
"update:commit": "deno task -q update --commit --pre-commit=fmt,lock --prefix 'build(deps):'"
Expand Down
30 changes: 18 additions & 12 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 14 additions & 5 deletions lib/dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,20 @@ function addSeparator(protocol: string): string {
* // -> "https://deno.land/std@1.0.0/fs/mod.ts"
* ```
*/
export function toUrl(dependency: Dependency): string {
export function stringify(dependency: Dependency, includePath = true): string {
const header = addSeparator(dependency.protocol);
const version = dependency.version ? "@" + dependency.version : "";
const path = dependency.path;
return `${header}${dependency.name}${version}${path}`;
return `${header}${dependency.name}${version}` + (includePath ? path : "");
}

export function hasVersionRange(
dependency: Dependency,
): boolean {
const constraint = dependency.version
? SemVer.tryParseRange(dependency.version)
: undefined;
return !!constraint && constraint.flat().length > 1;
}

/**
Expand All @@ -140,7 +149,7 @@ export function toUrl(dependency: Dependency): string {
*
* @example
* ```ts
* await Dependency.resolveLatestVersion(
* await resolveLatestVersion(
* Dependency.parse(new URL("https://deno.land/std@0.200.0/fs/mod.ts"))
* );
* // -> { name: "deno.land/std", version: "0.207.0", path: "/fs/mod.ts" }
Expand Down Expand Up @@ -218,7 +227,7 @@ async function _resolveLatestVersion(
{ message: `Invalid response from NPM registry: ${response.url}` },
);
const latest = pkg["dist-tags"].latest;
if (latest === dependency.version || isPreRelease(latest)) {
if (isPreRelease(latest)) {
break;
}
return { ...dependency, version: latest };
Expand All @@ -242,7 +251,7 @@ async function _resolveLatestVersion(
);
const semvers = Object.keys(candidates).map(SemVer.parse);
const latest = SemVer.format(semvers.sort(SemVer.compare).reverse()[0]);
if (latest === dependency.version || isPreRelease(latest)) {
if (isPreRelease(latest)) {
break;
}
return { ...dependency, version: latest };
Expand Down
10 changes: 5 additions & 5 deletions lib/dependency_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { afterAll, beforeAll, describe, it } from "./std/testing.ts";
import { assertEquals, assertExists, assertObjectMatch } from "./std/assert.ts";
import { isPreRelease, parse, resolveLatestVersion } from "./dependency.ts";
import { LatestSemVerStub } from "./testing.ts";
import { LatestVersionStub } from "./testing.ts";

describe("parse", () => {
it("deno.land/std", () =>
Expand Down Expand Up @@ -90,10 +90,10 @@ Deno.test("isPreRelease", () => {

describe("resolveLatestVersion", () => {
const LATEST = "123.456.789";
let stub: LatestSemVerStub;
let stub: LatestVersionStub;

beforeAll(() => {
stub = LatestSemVerStub.create(LATEST);
stub = LatestVersionStub.create(LATEST);
});

afterAll(() => {
Expand Down Expand Up @@ -200,10 +200,10 @@ describe("resolveLatestVersion", () => {
});

describe("resolveLatestVersion - pre-release", () => {
let stub: LatestSemVerStub;
let stub: LatestVersionStub;

beforeAll(() => {
stub = LatestSemVerStub.create("123.456.789-alpha.1");
stub = LatestVersionStub.create("123.456.789-alpha.1");
});

afterAll(() => {
Expand Down
Loading

0 comments on commit c9485c0

Please sign in to comment.