mirror of
https://github.com/Narcooo/inkos
synced 2026-04-21 14:37:16 +00:00
fix(release): stage canary publish before latest
This commit is contained in:
parent
816aacb746
commit
2f6048174c
8 changed files with 388 additions and 34 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -28,6 +28,7 @@ jobs:
|
|||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm test
|
||||
- run: pnpm verify:publish-manifests
|
||||
|
||||
verify-pack:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -46,6 +47,7 @@ jobs:
|
|||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm verify:publish-manifests
|
||||
|
||||
- name: Verify no workspace:* in tarballs
|
||||
run: |
|
||||
|
|
|
|||
179
.github/workflows/release.yml
vendored
179
.github/workflows/release.yml
vendored
|
|
@ -32,6 +32,7 @@ jobs:
|
|||
- run: pnpm --filter @actalk/inkos-core typecheck
|
||||
- run: pnpm --filter @actalk/inkos typecheck
|
||||
- run: pnpm test
|
||||
- run: pnpm verify:publish-manifests
|
||||
|
||||
smoke-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -50,6 +51,7 @@ jobs:
|
|||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm verify:publish-manifests
|
||||
|
||||
- name: Smoke test — CLI help and version
|
||||
run: |
|
||||
|
|
@ -79,9 +81,12 @@ jobs:
|
|||
cd - >/dev/null
|
||||
done
|
||||
|
||||
publish:
|
||||
publish-canary:
|
||||
runs-on: ubuntu-latest
|
||||
needs: smoke-test
|
||||
outputs:
|
||||
release_version: ${{ steps.versions.outputs.release_version }}
|
||||
canary_version: ${{ steps.versions.outputs.canary_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -97,41 +102,171 @@ jobs:
|
|||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm verify:publish-manifests
|
||||
|
||||
# Use npm publish (not pnpm) to ensure prepack hook replaces workspace:*
|
||||
- name: Publish core
|
||||
working-directory: packages/core
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish CLI
|
||||
working-directory: packages/cli
|
||||
run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
# Post-publish verification: install from npm and check it works
|
||||
- name: Verify published package
|
||||
- name: Derive release versions
|
||||
id: versions
|
||||
run: |
|
||||
RELEASE_VERSION="${GITHUB_REF_NAME#v}"
|
||||
CANARY_VERSION="${RELEASE_VERSION}-canary.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}"
|
||||
echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "canary_version=$CANARY_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Rewrite package versions for canary publish
|
||||
run: |
|
||||
node scripts/set-package-versions.mjs "${{ steps.versions.outputs.canary_version }}" --root .
|
||||
pnpm verify:publish-manifests
|
||||
|
||||
- name: Publish core canary
|
||||
working-directory: packages/core
|
||||
run: pnpm publish --tag canary --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish CLI canary
|
||||
working-directory: packages/cli
|
||||
run: pnpm publish --tag canary --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
verify-canary:
|
||||
runs-on: ubuntu-latest
|
||||
needs: publish-canary
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Verify canary dist-tag and installation
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXPECTED_CANARY="${{ needs.publish-canary.outputs.canary_version }}"
|
||||
|
||||
ACTUAL_CANARY=""
|
||||
for _ in 1 2 3 4 5 6; do
|
||||
ACTUAL_CANARY=$(npm view @actalk/inkos@canary version 2>/dev/null || true)
|
||||
if [ "$ACTUAL_CANARY" = "$EXPECTED_CANARY" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ "$ACTUAL_CANARY" != "$EXPECTED_CANARY" ]; then
|
||||
echo "FATAL: canary dist-tag mismatch: expected $EXPECTED_CANARY got ${ACTUAL_CANARY:-<empty>}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
cd "$TMPDIR"
|
||||
npm init -y
|
||||
npm install @actalk/inkos@latest
|
||||
# Must not contain workspace: in installed package.json
|
||||
npm install "@actalk/inkos@$EXPECTED_CANARY"
|
||||
|
||||
if grep -q '"workspace:' node_modules/@actalk/inkos/package.json; then
|
||||
echo "FATAL: published package still contains workspace: protocol"
|
||||
echo "FATAL: canary CLI package still contains workspace: protocol"
|
||||
cat node_modules/@actalk/inkos/package.json | grep workspace
|
||||
exit 1
|
||||
fi
|
||||
# Must be runnable
|
||||
|
||||
if grep -q '"workspace:' node_modules/@actalk/inkos-core/package.json; then
|
||||
echo "FATAL: canary core package still contains workspace: protocol"
|
||||
cat node_modules/@actalk/inkos-core/package.json | grep workspace
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npx inkos --version
|
||||
echo "Post-publish verification passed"
|
||||
echo "Canary verification passed"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
publish-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [publish-canary, verify-canary]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- run: pnpm verify:publish-manifests
|
||||
|
||||
- name: Rewrite package versions for final publish
|
||||
run: |
|
||||
node scripts/set-package-versions.mjs "${{ needs.publish-canary.outputs.release_version }}" --root .
|
||||
pnpm verify:publish-manifests
|
||||
|
||||
- name: Publish core latest
|
||||
working-directory: packages/core
|
||||
run: pnpm publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish CLI latest
|
||||
working-directory: packages/cli
|
||||
run: pnpm publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
verify-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [publish-canary, publish-release]
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Verify latest dist-tag and installation
|
||||
run: |
|
||||
set -euo pipefail
|
||||
EXPECTED_RELEASE="${{ needs.publish-canary.outputs.release_version }}"
|
||||
|
||||
ACTUAL_LATEST=""
|
||||
for _ in 1 2 3 4 5 6; do
|
||||
ACTUAL_LATEST=$(npm view @actalk/inkos@latest version 2>/dev/null || true)
|
||||
if [ "$ACTUAL_LATEST" = "$EXPECTED_RELEASE" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
|
||||
if [ "$ACTUAL_LATEST" != "$EXPECTED_RELEASE" ]; then
|
||||
echo "FATAL: latest dist-tag mismatch: expected $EXPECTED_RELEASE got ${ACTUAL_LATEST:-<empty>}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
cd "$TMPDIR"
|
||||
npm init -y
|
||||
npm install "@actalk/inkos@$EXPECTED_RELEASE"
|
||||
|
||||
if grep -q '"workspace:' node_modules/@actalk/inkos/package.json; then
|
||||
echo "FATAL: released CLI package still contains workspace: protocol"
|
||||
cat node_modules/@actalk/inkos/package.json | grep workspace
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -q '"workspace:' node_modules/@actalk/inkos-core/package.json; then
|
||||
echo "FATAL: released core package still contains workspace: protocol"
|
||||
cat node_modules/@actalk/inkos-core/package.json | grep workspace
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npx inkos --version
|
||||
echo "Release verification passed"
|
||||
rm -rf "$TMPDIR"
|
||||
|
||||
github-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: publish
|
||||
needs: verify-release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
"test": "pnpm -r test",
|
||||
"lint": "pnpm -r lint",
|
||||
"typecheck": "pnpm -r typecheck",
|
||||
"release": "pnpm build && pnpm test && pnpm -r --filter './packages/*' publish --no-git-checks"
|
||||
"verify:publish-manifests": "node scripts/verify-no-workspace-protocol.mjs packages/core packages/cli",
|
||||
"release": "pnpm build && pnpm test && pnpm verify:publish-manifests"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@
|
|||
"scripts": {
|
||||
"prepack": "node ../../scripts/prepare-package-for-publish.mjs",
|
||||
"postpack": "node ../../scripts/restore-package-json.mjs",
|
||||
"prepublishOnly": "node ../../scripts/verify-no-workspace-protocol.mjs .",
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actalk/inkos-core": "workspace:*",
|
||||
"@actalk/inkos-core": "0.4.6",
|
||||
"commander": "^13.0.0",
|
||||
"dotenv": "^16.4.0",
|
||||
"epub-gen-memory": "^1.0.10",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
|
@ -28,6 +28,87 @@ async function extractPackedPackageJson(packDir: string) {
|
|||
}
|
||||
|
||||
describe("publish packaging", () => {
|
||||
it("rewrites workspace package versions for canary publishing", async () => {
|
||||
const tempRoot = await mkdtemp(join(tmpdir(), "inkos-version-script-"));
|
||||
const tempPackagesDir = join(tempRoot, "packages");
|
||||
const tempCoreDir = join(tempPackagesDir, "core");
|
||||
const tempCliDir = join(tempPackagesDir, "cli");
|
||||
|
||||
try {
|
||||
await mkdir(tempCoreDir, { recursive: true });
|
||||
await mkdir(tempCliDir, { recursive: true });
|
||||
|
||||
await writeFile(
|
||||
join(tempRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "inkos", version: "0.4.6" }, null, 2)}\n`,
|
||||
);
|
||||
await writeFile(
|
||||
join(tempCoreDir, "package.json"),
|
||||
`${JSON.stringify({ name: "@actalk/inkos-core", version: "0.4.6" }, null, 2)}\n`,
|
||||
);
|
||||
await writeFile(
|
||||
join(tempCliDir, "package.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
name: "@actalk/inkos",
|
||||
version: "0.4.6",
|
||||
dependencies: {
|
||||
"@actalk/inkos-core": "0.4.6",
|
||||
commander: "^13.0.0",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
|
||||
execFileSync(
|
||||
"node",
|
||||
[resolve(workspaceRoot, "scripts/set-package-versions.mjs"), "0.4.8-canary.7", "--root", tempRoot],
|
||||
{
|
||||
cwd: workspaceRoot,
|
||||
env: process.env,
|
||||
encoding: "utf-8",
|
||||
},
|
||||
);
|
||||
|
||||
const rootPackageJson = JSON.parse(await readFile(join(tempRoot, "package.json"), "utf-8"));
|
||||
const corePackageJson = JSON.parse(await readFile(join(tempCoreDir, "package.json"), "utf-8"));
|
||||
const cliPackageJson = JSON.parse(await readFile(join(tempCliDir, "package.json"), "utf-8"));
|
||||
|
||||
expect(rootPackageJson.version).toBe("0.4.8-canary.7");
|
||||
expect(corePackageJson.version).toBe("0.4.8-canary.7");
|
||||
expect(cliPackageJson.version).toBe("0.4.8-canary.7");
|
||||
expect(cliPackageJson.dependencies["@actalk/inkos-core"]).toBe("0.4.8-canary.7");
|
||||
} finally {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps publishable CLI dependencies installable in source package.json", async () => {
|
||||
const cliPackageJson = JSON.parse(await readFile(resolve(cliDir, "package.json"), "utf-8"));
|
||||
const corePackageJson = JSON.parse(
|
||||
await readFile(resolve(workspaceRoot, "packages/core/package.json"), "utf-8"),
|
||||
);
|
||||
|
||||
expect(cliPackageJson.dependencies["@actalk/inkos-core"]).toBe(corePackageJson.version);
|
||||
expect(cliPackageJson.dependencies["@actalk/inkos-core"]).not.toMatch(/^workspace:/);
|
||||
});
|
||||
|
||||
it("verifies publishable manifests before npm publish runs", async () => {
|
||||
const cliPackageJson = JSON.parse(await readFile(resolve(cliDir, "package.json"), "utf-8"));
|
||||
const corePackageJson = JSON.parse(
|
||||
await readFile(resolve(workspaceRoot, "packages/core/package.json"), "utf-8"),
|
||||
);
|
||||
|
||||
expect(cliPackageJson.scripts.prepublishOnly).toBe(
|
||||
"node ../../scripts/verify-no-workspace-protocol.mjs .",
|
||||
);
|
||||
expect(corePackageJson.scripts.prepublishOnly).toBe(
|
||||
"node ../../scripts/verify-no-workspace-protocol.mjs .",
|
||||
);
|
||||
});
|
||||
|
||||
it("replaces workspace dependencies before npm pack", async () => {
|
||||
const packDir = await mkdtemp(join(tmpdir(), "inkos-cli-pack-"));
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"scripts": {
|
||||
"prepack": "node ../../scripts/prepare-package-for-publish.mjs",
|
||||
"postpack": "node ../../scripts/restore-package-json.mjs",
|
||||
"prepublishOnly": "node ../../scripts/verify-no-workspace-protocol.mjs .",
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "vitest run",
|
||||
|
|
|
|||
74
scripts/set-package-versions.mjs
Normal file
74
scripts/set-package-versions.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { readdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
function parseArgs(argv) {
|
||||
const [version, ...rest] = argv;
|
||||
if (!version) {
|
||||
throw new Error("Usage: node scripts/set-package-versions.mjs <version> [--root <path>]");
|
||||
}
|
||||
|
||||
let root = process.cwd();
|
||||
for (let i = 0; i < rest.length; i++) {
|
||||
if (rest[i] === "--root") {
|
||||
root = rest[i + 1];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return { version, root: resolve(root) };
|
||||
}
|
||||
|
||||
async function loadWorkspacePackages(root) {
|
||||
const packagesDir = join(root, "packages");
|
||||
const entries = await readdir(packagesDir);
|
||||
const packages = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
const dir = join(packagesDir, entry);
|
||||
const packageJsonPath = join(dir, "package.json");
|
||||
const pkg = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
||||
packages.push({ dir, packageJsonPath, pkg });
|
||||
} catch {
|
||||
// ignore non-package directories
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
function rewriteDependencyVersions(pkg, workspacePackageNames, version) {
|
||||
for (const field of ["dependencies", "optionalDependencies", "peerDependencies", "devDependencies"]) {
|
||||
const deps = pkg[field];
|
||||
if (!deps) continue;
|
||||
|
||||
for (const name of Object.keys(deps)) {
|
||||
if (workspacePackageNames.has(name)) {
|
||||
deps[name] = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { version, root } = parseArgs(process.argv.slice(2));
|
||||
const workspacePackages = await loadWorkspacePackages(root);
|
||||
const workspacePackageNames = new Set(workspacePackages.map(({ pkg }) => pkg.name));
|
||||
|
||||
const rootPackageJsonPath = join(root, "package.json");
|
||||
const rootPackageJson = JSON.parse(await readFile(rootPackageJsonPath, "utf-8"));
|
||||
rootPackageJson.version = version;
|
||||
await writeFile(rootPackageJsonPath, `${JSON.stringify(rootPackageJson, null, 2)}\n`, "utf-8");
|
||||
|
||||
for (const workspacePackage of workspacePackages) {
|
||||
workspacePackage.pkg.version = version;
|
||||
rewriteDependencyVersions(workspacePackage.pkg, workspacePackageNames, version);
|
||||
await writeFile(
|
||||
workspacePackage.packageJsonPath,
|
||||
`${JSON.stringify(workspacePackage.pkg, null, 2)}\n`,
|
||||
"utf-8",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
/**
|
||||
* Standalone verification: checks that no workspace: protocol remains in package.json.
|
||||
* Standalone verification for publishable package manifests.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/verify-no-workspace-protocol.mjs packages/cli packages/core
|
||||
* node ../../scripts/verify-no-workspace-protocol.mjs .
|
||||
*
|
||||
* Used by CI and as a pre-publish sanity check. The prepack script also runs
|
||||
* this check inline after replacement, but this script catches the case where
|
||||
* prepack is skipped entirely.
|
||||
* Checks two invariants before publish:
|
||||
* 1. publishable dependency fields must not contain workspace: specifiers
|
||||
* 2. internal workspace dependencies must match the current workspace package version exactly
|
||||
*/
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { access, readdir, readFile } from "node:fs/promises";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
const dirs = process.argv.slice(2);
|
||||
if (dirs.length === 0) {
|
||||
|
|
@ -18,12 +19,59 @@ if (dirs.length === 0) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
let failed = false;
|
||||
async function exists(path) {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const dir of dirs) {
|
||||
async function findWorkspaceRoot(startDir) {
|
||||
let dir = resolve(startDir);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if (await exists(join(dir, "packages"))) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
const parent = resolve(dir, "..");
|
||||
if (parent === dir) break;
|
||||
dir = parent;
|
||||
}
|
||||
|
||||
throw new Error(`Could not find workspace root from ${startDir}`);
|
||||
}
|
||||
|
||||
async function loadWorkspaceVersions(workspaceRoot) {
|
||||
const packagesDir = join(workspaceRoot, "packages");
|
||||
const entries = await readdir(packagesDir);
|
||||
const versions = new Map();
|
||||
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
const raw = await readFile(join(packagesDir, entry, "package.json"), "utf-8");
|
||||
const pkg = JSON.parse(raw);
|
||||
versions.set(pkg.name, pkg.version);
|
||||
} catch {
|
||||
// ignore directories that are not publishable packages
|
||||
}
|
||||
}
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
let failed = false;
|
||||
const workspaceRoot = await findWorkspaceRoot(process.cwd());
|
||||
const workspaceVersions = await loadWorkspaceVersions(workspaceRoot);
|
||||
|
||||
for (const dirArg of dirs) {
|
||||
const dir = resolve(process.cwd(), dirArg);
|
||||
const packageJsonPath = join(dir, "package.json");
|
||||
const raw = await readFile(packageJsonPath, "utf-8");
|
||||
const pkg = JSON.parse(raw);
|
||||
let dirFailed = false;
|
||||
|
||||
for (const field of ["dependencies", "optionalDependencies", "peerDependencies"]) {
|
||||
const deps = pkg[field];
|
||||
|
|
@ -31,12 +79,23 @@ for (const dir of dirs) {
|
|||
for (const [name, specifier] of Object.entries(deps)) {
|
||||
if (typeof specifier === "string" && specifier.startsWith("workspace:")) {
|
||||
process.stderr.write(`FAIL: ${dir} — ${field}.${name}: ${specifier}\n`);
|
||||
dirFailed = true;
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const workspaceVersion = workspaceVersions.get(name);
|
||||
if (workspaceVersion && specifier !== workspaceVersion) {
|
||||
process.stderr.write(
|
||||
`FAIL: ${dir} — ${field}.${name}: expected ${workspaceVersion}, got ${specifier}\n`,
|
||||
);
|
||||
dirFailed = true;
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
if (!dirFailed) {
|
||||
process.stderr.write(`OK: ${dir}\n`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue