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
9 changes: 5 additions & 4 deletions tools/release/release-product-version-coverage.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function writeBase(repo, versions) {
write(repo, VECTOR_SOURCE, 'commit = "vector-v1"\n');
}

function writeRuntimeRelease(repo, versions, options = {}) {
function writeRuntimeRelease(repo, versions) {
write(repo, '.release-please-manifest.json', manifest(versions));
for (const product of [NATIVE, WASIX]) {
const packagePath = PRODUCT_PATHS[product];
Expand All @@ -92,7 +92,6 @@ function writeRuntimeRelease(repo, versions, options = {}) {
`# Changelog\n\n## ${versions[product]} (2026-07-15)\n`,
);
}
write(repo, VECTOR_RELEASE, vectorMetadata(versions[NATIVE], versions[WASIX], options));
}

function graph(versions) {
Expand Down Expand Up @@ -137,12 +136,14 @@ const v2 = { ...v1, [NATIVE]: '2.0.0', [WASIX]: '2.0.0' };
if (phase === 'base') {
writeBase(repo, v1);
} else if (phase === 'release') {
writeRuntimeRelease(repo, v2, scenario === 'config' ? { sqlName: 'not-vector' } : {});
writeRuntimeRelease(repo, v2);
if (scenario === 'config')
write(repo, VECTOR_RELEASE, vectorMetadata(v1[NATIVE], v1[WASIX], { sqlName: 'not-vector' }));
if (scenario === 'source') write(repo, VECTOR_SOURCE, 'commit = "vector-v2"\n');
} else if (phase === 'assert') {
const verify = () => verifyReleaseCommit({ repo, headRef, products: [NATIVE, WASIX] });
if (scenario === 'compatible') {
assert.deepEqual(verify().verifiedDerivedPaths, [VECTOR_RELEASE]);
assert.deepEqual(verify().verifiedDerivedPaths, []);
assert.deepEqual(
buildPlan(graph(v2), [VECTOR_SOURCE], 'release-coverage-test').releaseProducts,
[VECTOR],
Expand Down
67 changes: 57 additions & 10 deletions tools/release/verify-release-commit.mts
Original file line number Diff line number Diff line change
Expand Up @@ -247,16 +247,26 @@ function derivedVersionRules() {
const products = loadProducts(TOOL);
const structured = new Map();
const text = new Map();
const addStructured = (type, file, parts, sourceProduct, wrapped = false) => {
const addStructured = (
type,
file,
parts,
sourceProduct,
wrapped = false,
consumerProduct = undefined,
) => {
const key = structuredRuleKey(type, file, parts);
const prior = structured.get(key);
if (
prior !== undefined &&
(prior.sourceProduct !== sourceProduct || prior.wrapped !== wrapped)
(prior.sourceProduct !== sourceProduct ||
prior.wrapped !== wrapped ||
prior.consumerProduct !== consumerProduct)
) {
throw error(`conflicting derived version rules for ${file}:${parts.join('.')}`);
}
structured.set(key, { sourceProduct, wrapped });
const rule = { sourceProduct, wrapped, consumerProduct };
structured.set(key, rule);
if (
type === 'json' &&
file.endsWith('/package.json') &&
Expand All @@ -270,7 +280,7 @@ function derivedVersionRules() {
) {
structured.set(
structuredRuleKey('jsonc', 'bun.lock', ['workspaces', path.posix.dirname(file), ...parts]),
{ sourceProduct, wrapped },
rule,
);
}
};
Expand All @@ -290,14 +300,26 @@ function derivedVersionRules() {
const parser = separator === -1 ? entry.parser : entry.parser.slice(0, separator);
const expression = separator === -1 ? '' : entry.parser.slice(separator + 1);
if (parser === 'json' || parser === 'toml') {
addStructured(parser, entry.path, expression.split('.'), entry.sourceProduct);
addStructured(
parser,
entry.path,
expression.split('.'),
entry.sourceProduct,
false,
entry.product,
);
} else if (parser === 'raw') {
addText(entry.path, { type: 'raw', sourceProduct: entry.sourceProduct });
addText(entry.path, {
type: 'raw',
sourceProduct: entry.sourceProduct,
consumerProduct: entry.product,
});
} else if (parser === 'rust-const') {
addText(entry.path, {
type: 'rust-const',
name: expression,
sourceProduct: entry.sourceProduct,
consumerProduct: entry.product,
});
} else {
throw error(
Expand Down Expand Up @@ -350,7 +372,19 @@ function derivedVersionRules() {
return cachedDerivedRules;
}

function productTransition(rule, before, after, transitions) {
function productTransition(rule, before, after, transitions, productVersions) {
// Selected consumers may catch up from older pins without a producer release.
if (rule.consumerProduct !== undefined) {
return (
transitions.some(({ product }) => product === rule.consumerProduct) &&
typeof before === 'string' &&
SEMVER.test(before) &&
typeof after === 'string' &&
SEMVER.test(after) &&
after === productVersions.get(rule.sourceProduct) &&
Bun.semver.order(after, before) > 0
);
}
const transition = transitions.find(({ product }) => product === rule.sourceProduct);
if (transition === undefined) return false;
return rule.wrapped
Expand Down Expand Up @@ -530,7 +564,13 @@ function authorizedDerivedStructuredChange(context, rules) {
}
const rule = rules.structured.get(structuredRuleKey(context.type, context.file, context.parts));
if (rule !== undefined)
return productTransition(rule, context.before, context.after, context.transitions);
return productTransition(
rule,
context.before,
context.after,
context.transitions,
context.productVersions,
);
if (context.type !== 'toml') return false;
return cargoDependencyVersionChange(context) || cargoLockVersionChange(context);
}
Expand Down Expand Up @@ -577,6 +617,7 @@ function validateTextSemanticDiff({
derived,
transitions,
derivedRules,
productVersions,
}) {
const before = show(repo, parent, file);
const after = show(repo, commit, file);
Expand Down Expand Up @@ -612,7 +653,7 @@ function validateTextSemanticDiff({
if (
prior === null ||
next === null ||
!productTransition(derivedRule, prior[2], next[2], transitions) ||
!productTransition(derivedRule, prior[2], next[2], transitions, productVersions) ||
before.replace(pattern, '$1<release-version>$3') !==
after.replace(pattern, '$1<release-version>$3')
) {
Expand All @@ -622,7 +663,7 @@ function validateTextSemanticDiff({
}
if (
derivedRule?.type === 'raw' &&
productTransition(derivedRule, before.trim(), after.trim(), transitions) &&
productTransition(derivedRule, before.trim(), after.trim(), transitions, productVersions) &&
before.replace(before.trim(), '<release-version>') ===
after.replace(after.trim(), '<release-version>')
) {
Expand All @@ -642,6 +683,7 @@ function validateAllowedFileSemantics({
fieldsByFile,
derivedFiles,
transitions,
productVersions,
}) {
const derivedRules = derivedVersionRules();
const cargoVersions = new Map();
Expand All @@ -660,6 +702,7 @@ function validateAllowedFileSemantics({
derived,
transitions,
derivedRules,
productVersions,
});
continue;
}
Expand Down Expand Up @@ -696,6 +739,7 @@ function validateAllowedFileSemantics({
after: difference.after,
transitions,
cargoVersions,
productVersions,
},
derivedRules,
)
Expand Down Expand Up @@ -981,6 +1025,9 @@ export function verifyReleaseCommit({ repo = ROOT, headRef = 'HEAD', products })
fieldsByFile,
derivedFiles,
transitions,
productVersions: new Map(
[...byProduct].map(([product, { packagePath }]) => [product, after[packagePath]]),
),
});

return {
Expand Down
112 changes: 95 additions & 17 deletions tools/release/verify-release-commit.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,14 @@ function prepareCargo(base) {
{ type: 'json', path: 'packages/darwin-arm64/package.json', jsonpath: '$.version' },
],
},
'src/native/sdks/rust': { 'release-type': 'rust', component: 'oliphaunt-rust' },
},
});
const version = base ? '0.0.0' : '0.1.0';
json('.release-please-manifest.json', { 'src/native/broker': version });
json('.release-please-manifest.json', {
'src/native/broker': version,
'src/native/sdks/rust': version,
});
write('src/native/broker/Cargo.toml', cargo(broker, version));
const carrier = 'src/native/broker/packages/darwin-arm64';
json(`${carrier}/package.json`, { name: '@oliphaunt/broker-darwin-arm64', version });
Expand All @@ -125,13 +129,16 @@ function prepareCargo(base) {
if (base) {
write('src/native/broker/CHANGELOG.md', '# Changelog\n');
write('src/shared/unrelated/Cargo.toml', cargo('unrelated', '0.0.0'));
} else changelog('src/native/broker', version);
if (base || scenario !== 'unrelated-lock')
write(
'src/native/sdks/rust/Cargo.toml',
cargo('shadow-sdk', scenario === 'unrelated-package' ? '0.1.0' : '0.0.0') +
`\n[dependencies]\noliphaunt-broker = { path = "../../broker", version = "${version}" }\nunrelated = { path = "../../../../src/shared/unrelated", version = "${scenario === 'unrelated-pin' ? '0.1.0' : '0.0.0'}" }\n`,
);
write('src/native/sdks/rust/CHANGELOG.md', '# Changelog\n');
} else {
changelog('src/native/broker', version);
changelog('src/native/sdks/rust', version);
}
write(
'src/native/sdks/rust/Cargo.toml',
cargo('oliphaunt', scenario === 'unrelated-package' ? '0.2.0' : version) +
`\n[dependencies]\noliphaunt-broker = { path = "../../broker", version = "${version}" }\nunrelated = { path = "../../../../src/shared/unrelated", version = "${scenario === 'unrelated-pin' ? '0.1.0' : '0.0.0'}" }\n`,
);
if (base || ['exact', 'unrelated-lock'].includes(scenario))
write(
'Cargo.lock',
Expand Down Expand Up @@ -228,6 +235,55 @@ function prepareExample(base) {
`[package]\nname = "release-example"\nversion = "0.0.0"\n\n[package.metadata.oliphaunt]\nruntime = "liboliphaunt-native"\nruntime-version = "${runtimeVersion}"\n\n[target.'cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))'.dependencies]\nliboliphaunt-native-linux-x64-gnu = { version = "=${carrierVersion}" }\nunrelated = { version = "${unrelatedVersion}" }\n`,
);
}
function prepareCompatibility(base) {
const consumers = {
'src/extensions/external/pg_hashids': simple('oliphaunt-extension-pg-hashids'),
'src/native/sdks/ts': { 'release-type': 'node', component: 'oliphaunt-js' },
'src/native/sdks/rust': { 'release-type': 'rust', component: 'oliphaunt-rust' },
'src/native/sdks/swift': simple('oliphaunt-swift'),
};
const producers = { [nativePath]: simple(native), 'src/native/broker': simple(broker) };
json('release-please-config.json', { packages: { ...producers, ...consumers } });
const producerVersion = base || scenario === 'consumer-only' ? '0.3.1' : '0.3.2';
const consumerVersion = base || scenario === 'unselected-consumer' ? '0.1.0' : '0.2.0';
const pin = base ? '0.3.0' : producerVersion;
json('.release-please-manifest.json', {
...Object.fromEntries(Object.keys(producers).map((folder) => [folder, producerVersion])),
...Object.fromEntries(Object.keys(consumers).map((folder) => [folder, consumerVersion])),
});
for (const folder of Object.keys(producers)) {
write(`${folder}/VERSION`, `${producerVersion}\n`);
changelog(folder, producerVersion);
}
for (const folder of Object.keys(consumers)) changelog(folder, consumerVersion);
write('src/extensions/external/pg_hashids/VERSION', `${consumerVersion}\n`);
write(
'src/extensions/external/pg_hashids/release.toml',
`[extension.compatibility]\npostgres_major = "${scenario === 'non-version-edit' ? '19' : '18'}"\nnative_runtime_version = "${scenario === 'wrong-toml-pin' ? '0.4.0' : pin}"\n`,
);
json('src/native/sdks/ts/package.json', {
name: '@oliphaunt/ts',
version: consumerVersion,
oliphaunt: {
liboliphauntVersion: scenario === 'wrong-json-pin' ? '0.4.0' : pin,
brokerVersion: pin,
},
});
write(
'src/native/sdks/rust/Cargo.toml',
cargo('oliphaunt', consumerVersion) +
`\n[package.metadata.oliphaunt]\nnative-version = "${pin}"\nbroker-version = "${pin}"\n`,
);
write(
'src/native/sdks/rust/src/broker.rs',
`const BROKER_RELEASE_VERSION: &str = "${scenario === 'wrong-rust-const' ? '0.4.0' : pin}";\n`,
);
write('src/native/sdks/swift/VERSION', `${consumerVersion}\n`);
write(
'src/native/sdks/swift/LIBOLIPHAUNT_VERSION',
`${scenario === 'wrong-raw-pin' ? '0.4.0' : pin}\n`,
);
}
if (phase === 'write') {
if (scenario === 'later-fix') write('fix.txt', 'post-release fix\n');
else {
Expand All @@ -238,19 +294,34 @@ if (phase === 'write') {
wildcard: prepareWildcard,
wasix: prepareWasix,
example: prepareExample,
compatibility: prepareCompatibility,
}[family];
if (!prepare) throw new Error('unknown release fixture family');
prepare(scenario === 'base');
}
} else if (phase === 'assert') {
const products =
family === 'wasix'
? ['liboliphaunt-wasix', 'oliphaunt-wasix-ts']
: family === 'example' && scenario !== 'missing-native-transition'
? [native]
: scenario === 'hidden-version-config'
? ['beta']
: [broker];
family === 'compatibility'
? [
...(scenario === 'consumer-only' ? [] : [native, broker]),
...(scenario === 'unselected-consumer'
? []
: [
'oliphaunt-extension-pg-hashids',
'oliphaunt-js',
'oliphaunt-rust',
'oliphaunt-swift',
]),
].sort()
: family === 'cargo'
? [broker, 'oliphaunt-rust'].sort()
: family === 'wasix'
? ['liboliphaunt-wasix', 'oliphaunt-wasix-ts']
: family === 'example' && scenario !== 'missing-native-transition'
? [native]
: scenario === 'hidden-version-config'
? ['beta']
: [broker];
const verify = () => verifyReleaseCommit({ repo, headRef, products });
if (scenario === 'base') assert.equal(latestVerifiedReleaseCommit({ repo, headRef }), null);
else if (scenario === 'later-fix') {
Expand All @@ -268,10 +339,11 @@ if (phase === 'write') {
rename: /non-release-derived path.*src\/future-version[.]txt/u,
'hidden-version-config': /canonical version file.*non-version semantic change/u,
'hidden-derived-config': /derived file.*non-version semantic change/u,
'derived-version-only': /derived file.*oliphaunt[.]brokerVersion/u,
'unrelated-derived-dependency':
/derived file.*optionalDependencies[.]@oliphaunt\/unrelated/u,
'unrelated-pin': /derived file.*dependencies[.]unrelated[.]version/u,
'unrelated-package': /derived file.*package[.]version/u,
'unrelated-pin': /canonical version file.*dependencies[.]unrelated[.]version/u,
'unrelated-package': /canonical version file.*Cargo[.]toml contains/u,
'unrelated-lock': /derived file.*package[.]1[.]version/u,
'wrong-workspace-version': /workspace lock version.*non-version semantic change/u,
'unrelated-workspace-version': /derived file.*workspaces[.]unrelated[.]version/u,
Expand All @@ -281,6 +353,12 @@ if (phase === 'write') {
'unrelated-registry-version': /derived file.*unrelated[.]version/u,
'missing-native-transition':
/derived file src\/examples\/native\/tauri\/src-tauri\/Cargo[.]toml contains a non-version semantic change/u,
'unselected-consumer': /derived file.*extension[.]compatibility[.]native_runtime_version/u,
'wrong-toml-pin': /derived file.*extension[.]compatibility[.]native_runtime_version/u,
'wrong-json-pin': /canonical version file.*oliphaunt[.]liboliphauntVersion/u,
'wrong-raw-pin': /derived file.*LIBOLIPHAUNT_VERSION.*non-version semantic change/u,
'wrong-rust-const': /derived file.*broker[.]rs.*non-version semantic change/u,
'non-version-edit': /derived file.*extension[.]compatibility[.]postgres_major/u,
}[scenario] ??
(family === 'wildcard' && scenario !== 'workspace-wildcard'
? /canonical version file.*non-version semantic change/u
Expand Down
3 changes: 2 additions & 1 deletion tools/release/verify-release-commit.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ assert_history() {
bash "$root/tools/release/with-release-history.sh" "$repo" "$head" \
bash "$root/tools/dev/bun.sh" "$fixture" assert "$repo" "$family" "$1" "$head" "${release:-}"
}
for family in bootstrap basic cargo wildcard wasix example; do
for family in bootstrap basic cargo wildcard wasix example compatibility; do
repo="$scratch/$family"
git init -q "$repo"
git -C "$repo" config user.name 'Release Test'
Expand All @@ -31,6 +31,7 @@ for family in bootstrap basic cargo wildcard wasix example; do
wildcard) scenarios=(workspace-wildcard local-version wrong-version changed-path removed-path changed-features) ;;
wasix) scenarios=(workspace-links) ;;
example) scenarios=(exact wrong-registry-version wrong-runtime-version unrelated-registry-version missing-native-transition) ;;
compatibility) scenarios=(with-producer consumer-only unselected-consumer wrong-toml-pin wrong-json-pin wrong-raw-pin wrong-rust-const non-version-edit) ;;
esac
for scenario in "${scenarios[@]}"; do
start="$base"
Expand Down
Loading