Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/noldova.tools.pipelines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ It succeeds only when:
- the version is a version, the commit is its full id, and the scope is `@` and a lowercase name, such as `@noldova`;
- the checkout is at the commit;
- the folder holds one archive for each package at the version, named as `npm pack` names it, such as `noldova-tools.pipelines-0.0.4.tgz`, and nothing else;
- each archive's `package/package.json` has the package's name, inside the scope, the version, and the commit as its `gitHead`, isn't `"private": true`, has a `license`, and lists any other expected package under `dependencies` at exactly the version;
- each archive's `package/package.json` has the package's name, inside the scope, the version, and the commit as its `gitHead`, isn't `"private": true`, has a `license`, lists any other expected package under `dependencies` at exactly the version, and lists any under `peerDependencies` in the range from the version to the next minor one, such as `>=0.0.9 <0.1.0`, as a package that takes its siblings as peers names them;
- each archive holds `package/LICENSE`, and no file of a kind that holds credentials: `.npmrc`, `.netrc`, `.env` or `.env.<name>`, `id_rsa`, `id_dsa`, `id_ecdsa`, `id_ed25519`, or a name ending in `.pem`, `.key`, `.p12` or `.pfx`, such as `.key` itself, in any folder and whatever its case;
- each archive holds every file the build copies into its package, as a file with the same bytes as its source, and neither the copy nor its source is a symbolic link or under one.

Expand All @@ -102,7 +102,7 @@ A wrong value, a checkout at another commit or a missing folder fails with that
Otherwise the step fails with one line for each problem it finds with the archives, such as `archives has no noldova-tools.pipelines-0.0.4.tgz.` or `noldova-tools.pipelines-0.0.4.tgz: its version is 0.0.3, not 0.0.4.`
It reads each manifest and lists each archive's files with the `tar` program, which must be on the `PATH`; an archive `tar` can't read or list is one of those lines, with `tar`'s exit code and message.
When the archives pass, the step checks the copies, extracting each archive with `tar` into a new temporary folder that it removes after, and adds one line, such as `Each of the 2 files the build copies in matches its source.`; it fails with npm's exit code and message when the build can't list its copies, and when its output doesn't end with `end of copies`; otherwise with one line for each copy the build lists that isn't three fields, has a path that isn't inside its folder or a package without an archive, a source that is a symbolic link or under one or isn't a file, an archive `tar` can't extract, and a copy an archive lacks, holds as a symbolic link, under one or as a folder, or holds with other bytes.
A `package.json`, in an archive or under `src/`, makes the step throw `JsonException` if it isn't a JSON object, lacks a `name` or `version` text where the step needs one, has a `gitHead` or a `license` that isn't a text, or has a `private` that isn't `true` or `false` or `dependencies` that aren't an object of texts.
A `package.json`, in an archive or under `src/`, makes the step throw `JsonException` if it isn't a JSON object, lacks a `name` or `version` text where the step needs one, has a `gitHead` or a `license` that isn't a text, or has a `private` that isn't `true` or `false` or `dependencies` or `peerDependencies` that aren't an object of texts.

### The publish step

Expand Down
27 changes: 23 additions & 4 deletions src/noldova.tools.pipelines/src/api/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,22 @@ export declare class Version {
*/
public equals(other: Version): boolean;

/**
* Gives the first release of the next minor version, the upper bound of a
* sibling package's peer range.
*
* @returns The version with the minor part one higher, the patch part 0
* and no nightly date.
* @example
* ```ts
* import { Version } from "@noldova/tools.pipelines";
*
* Version.parse("0.0.9").nextMinor().toString(); // "0.1.0"
* Version.parse("1.4.1-nightly.20261101").nextMinor().toString(); // "1.5.0"
* ```
*/
public nextMinor(): Version;

/**
* Writes the version as its text.
*
Expand Down Expand Up @@ -1943,8 +1959,10 @@ export declare class SourceStep implements IStep {
* the commit, and the folder holds exactly one archive for each package at
* the version, named as `npm pack` names it, whose `package/package.json`
* has the package's name, inside the scope, the version, and the commit as
* its `gitHead`, is not `"private": true`, has a `license`, and lists any expected package under `dependencies` at
* exactly the version. Each archive must also hold `package/LICENSE`, and no
* its `gitHead`, is not `"private": true`, has a `license`, lists any
* expected package under `dependencies` at exactly the version, and lists any
* under `peerDependencies` in the range from the version to the next minor
* one, such as `>=0.0.9 <0.1.0`. Each archive must also hold `package/LICENSE`, and no
* file of a kind that holds credentials: `.npmrc`, `.netrc`, `.env` or
* `.env.<name>`, `id_rsa`, `id_dsa`, `id_ecdsa`, `id_ed25519`, or a name
* ending in `.pem`, `.key`, `.p12` or `.pfx`, such as `.key` itself, in any
Expand Down Expand Up @@ -2002,8 +2020,9 @@ export declare class ArchiveStep implements IStep {
* `tar` cannot read the manifest of, with `tar`'s exit code and message,
* and a manifest with another name, a name outside the scope, another
* version, no `gitHead` or another one, `"private": true`, no `license`
* or an empty one, or an expected package under `dependencies`
* at anything but exactly the version, an archive `tar` cannot list the
* or an empty one, an expected package under `dependencies`
* at anything but exactly the version, or one under `peerDependencies` in
* any range but `>=<version> <next minor>`, an archive `tar` cannot list the
* files of, with `tar`'s exit code and message, a file of a kind that holds
* credentials, and no `package/LICENSE`. When the archives pass, it asks
* the build for its copies and adds a line, such as
Expand Down
4 changes: 4 additions & 0 deletions src/noldova.tools.pipelines/src/models/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export class Version {
return this.compareTo(other) === 0;
}

public nextMinor(): Version {
return new Version(this.major, this.minor + 1, 0);
}

public toString(): string {
const release = [this.major, this.minor, this.patch].join(Resources.versionPartSeparator);

Expand Down
8 changes: 8 additions & 0 deletions src/noldova.tools.pipelines/src/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,14 @@ export class Resources {
return `${archiveName}: its dependency ${dependency} is ${found}, not exactly ${version}.`;
}

public static formatSiblingPeerRange(version: string, nextMinor: string): string {
return `>=${version} <${nextMinor}`;
}

public static formatPeerRangeDiffers(archiveName: string, peer: string, found: string, expected: string): string {
return `${archiveName}: its peer ${peer} is ${found}, not ${expected}.`;
}

public static formatArchiveChecked(archiveName: string, packageName: string, version: string, commit: string): string {
return `${archiveName}: ${packageName}@${version} from commit ${commit}`;
}
Expand Down
9 changes: 9 additions & 0 deletions src/noldova.tools.pipelines/src/services/archive-step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ export class ArchiveStep implements IStep {
findings.push(Resources.formatDependencyDiffers(archiveName, dependency, range, version));
}
}
if (manifest.hasField(Resources.peerDependenciesField)) {
const peers = manifest.readObject(Resources.peerDependenciesField);
const expected = Resources.formatSiblingPeerRange(version, Version.parse(version).nextMinor().toString());
for (const peer of packageNames.filter(t => peers.hasField(t))) {
const range = peers.readString(peer);
if (range !== expected)
findings.push(Resources.formatPeerRangeDiffers(archiveName, peer, range, expected));
}
}

findings.push(...await this.checkFilesAsync(archivePath));

Expand Down
8 changes: 8 additions & 0 deletions src/noldova.tools.pipelines/tests/models/version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export class VersionTests {
Assert.areEqual(text, version.toString());
}

@TestData("0.0.9", "0.1.0")
@TestData("1.4.1", "1.5.0")
@TestData("1.4.1-nightly.20261101", "1.5.0")
@TestMethod
public givesTheFirstReleaseOfTheNextMinorVersion(text: string, nextMinor: string): void {
Assert.areEqual(nextMinor, Version.parse(text).nextMinor().toString());
}

@TestData("1.4.1-nightly.20261101", 1, 4, 1, "20261101")
@TestData("0.0.0-nightly.10000101", 0, 0, 0, "10000101")
@TestData("0.1.1-nightly.20280229", 0, 1, 1, "20280229")
Expand Down
50 changes: 50 additions & 0 deletions src/noldova.tools.pipelines/tests/services/archive-step.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,45 @@ export class ArchiveStepTests {
result.message);
}

@TestMethod
public async succeedsForSiblingPeersInTheirRangeAsync(): Promise<void> {
using repository = GitRepositoryFixture.create();
const commit = repository.commit(ArchiveStepTests.PACKAGES);
const folder = ArchiveStepTests.packArchives(repository, ArchiveStepTests.MANIFEST_A, ArchiveStepTests.peerManifestB(">=0.0.4 <0.1.0"));

const result = await ArchiveStepTests.createStep(repository.directory).runAsync([folder, "0.0.4", commit, "@noldova"]);

Assert.isTrue(result.isSuccessful, result.message);
}

@TestData(">=0.0.3 <0.1.0")
@TestData(">=0.0.4 <0.0.5")
@TestData(">=0.0.4 <1.0.0")
@TestData("0.0.4")
@TestData("^0.0.4")
@TestMethod
public async failsForASiblingPeerInAnotherRangeAsync(range: string): Promise<void> {
using repository = GitRepositoryFixture.create();
const commit = repository.commit(ArchiveStepTests.PACKAGES);
const folder = ArchiveStepTests.packArchives(repository, ArchiveStepTests.MANIFEST_A, ArchiveStepTests.peerManifestB(range));

const result = await ArchiveStepTests.createStep(repository.directory).runAsync([folder, "0.0.4", commit, "@noldova"]);

Assert.isFalse(result.isSuccessful);
Assert.areEqual(`noldova-b-0.0.4.tgz: its peer @noldova/a is ${range}, not >=0.0.4 <0.1.0.`, result.message);
}

@TestMethod
public async rejectsPeerDependenciesThatAreNotAnObjectAsync(): Promise<void> {
using repository = GitRepositoryFixture.create();
const commit = repository.commit(ArchiveStepTests.PACKAGES);
const folder = ArchiveStepTests.packArchives(repository, ArchiveStepTests.MANIFEST_A, ArchiveStepTests.MANIFEST_B.replace("\"dependencies\"", "\"peerDependencies\": [], \"dependencies\""));

const exception = await Assert.throwsAsync(() => ArchiveStepTests.createStep(repository.directory).runAsync([folder, "0.0.4", commit, "@noldova"]), JsonException);

Assert.isTrue(exception.path.startsWith("noldova-b-0.0.4.tgz/package/package.json"), exception.path);
}

@TestMethod
public async failsForAnArchiveFromAnotherCommitAsync(): Promise<void> {
using repository = GitRepositoryFixture.create();
Expand Down Expand Up @@ -700,6 +739,17 @@ export class ArchiveStepTests {
return new ArchiveStep(new Git(processRunner, directory), new Tar(processRunner), new ListingNpm(directory, listed));
}

private static peerManifestB(range: string): string {
return JSON.stringify({
name: "@noldova/b",
version: "0.0.4",
gitHead: "__COMMIT__",
license: "MIT",
private: false,
peerDependencies: { "@noldova/a": range, "@example/d": "^1.0.0" }
});
}

private static packArchives(repository: GitRepositoryFixture, manifestA: string, manifestB: string, filesA: Readonly<Record<string, string>> = {}): string {
const commit = repository.readHead();
repository.pack("archives/noldova-a-0.0.4.tgz", { "package/package.json": ArchiveStepTests.withCommit(manifestA, commit), ...ArchiveStepTests.LICENSE, ...filesA });
Expand Down
Loading