Skip to content

Commit 8602fb3

Browse files
committed
chore: release 0.0.1
1 parent eaa74cf commit 8602fb3

File tree

1 file changed

+166
-5
lines changed

1 file changed

+166
-5
lines changed

scripts/release.ts

Lines changed: 166 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ async function main(): Promise<void> {
3939
await Promise.all(manifests.cargoToml.map(file => updateCargoToml(file, version)));
4040

4141
const publishableTypeScriptPackages = await getPublishableTypeScriptPackages(manifests.packageJson);
42+
const publishableRustPackages = await getPublishableRustPackages(manifests.cargoToml);
4243

4344
await createAndPushCommit(version);
4445

4546
await publishTypeScriptPackages(publishableTypeScriptPackages, version);
46-
await runCommand('cargo', ['publish'], path.join(repoRoot, 'rust'));
47+
await publishRustPackages(publishableRustPackages, version);
4748
}
4849

4950
function isValidVersion(version: string): boolean {
@@ -158,14 +159,14 @@ async function updateCargoToml(filePath: string, version: string): Promise<void>
158159
console.log(`Updated ${relative(filePath)} to ${version}`);
159160
}
160161

161-
type PublishablePackage = {
162+
type PublishableTypeScriptPackage = {
162163
name: string;
163164
directory: string;
164165
};
165166

166-
async function getPublishableTypeScriptPackages(packageJsonPaths: string[]): Promise<PublishablePackage[]> {
167+
async function getPublishableTypeScriptPackages(packageJsonPaths: string[]): Promise<PublishableTypeScriptPackage[]> {
167168
const tsRoot = path.join(repoRoot, 'typescript');
168-
const packages: PublishablePackage[] = [];
169+
const packages: PublishableTypeScriptPackage[] = [];
169170

170171
for (const filePath of packageJsonPaths) {
171172
const isTypeScriptPackage =
@@ -209,7 +210,7 @@ async function getPublishableTypeScriptPackages(packageJsonPaths: string[]): Pro
209210
return packages;
210211
}
211212

212-
async function publishTypeScriptPackages(packages: PublishablePackage[], version: string): Promise<void> {
213+
async function publishTypeScriptPackages(packages: PublishableTypeScriptPackage[], version: string): Promise<void> {
213214
if (packages.length === 0) {
214215
console.warn('No publishable TypeScript packages found, skipping npm publish');
215216
return;
@@ -244,6 +245,166 @@ async function packageVersionExists(name: string, version: string): Promise<bool
244245
}
245246
}
246247

248+
type PublishableRustPackage = {
249+
name: string;
250+
directory: string;
251+
localDependencies: string[];
252+
};
253+
254+
async function getPublishableRustPackages(cargoTomlPaths: string[]): Promise<PublishableRustPackage[]> {
255+
const rustRoot = path.join(repoRoot, 'rust');
256+
const packages: PublishableRustPackage[] = [];
257+
258+
for (const filePath of cargoTomlPaths) {
259+
const normalized = path.normalize(filePath);
260+
const rustWorkspaceRoot = path.join(rustRoot, 'Cargo.toml');
261+
262+
if (normalized === rustWorkspaceRoot) {
263+
continue;
264+
}
265+
266+
const isRustCrate = normalized.startsWith(`${rustRoot}${path.sep}`);
267+
if (!isRustCrate) {
268+
continue;
269+
}
270+
271+
const crateDirectory = path.dirname(normalized);
272+
const relativeToRustRoot = path.relative(rustRoot, crateDirectory);
273+
if (relativeToRustRoot.split(path.sep).includes('examples')) {
274+
continue;
275+
}
276+
277+
const raw = await fs.readFile(normalized, 'utf8');
278+
const lines = raw.split(/\r?\n/);
279+
280+
let inPackageSection = false;
281+
let packageName: string | undefined;
282+
let publishFlag: string | undefined;
283+
284+
for (const line of lines) {
285+
const trimmed = line.trim();
286+
287+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
288+
inPackageSection = trimmed === '[package]';
289+
continue;
290+
}
291+
292+
if (!inPackageSection) {
293+
continue;
294+
}
295+
296+
if (!packageName) {
297+
const nameMatch = trimmed.match(/^name\s*=\s*"([^"]+)"/);
298+
if (nameMatch) {
299+
packageName = nameMatch[1];
300+
continue;
301+
}
302+
}
303+
304+
if (!publishFlag) {
305+
const publishMatch = trimmed.match(/^publish\s*=\s*(.+)$/);
306+
if (publishMatch) {
307+
publishFlag = publishMatch[1].trim();
308+
}
309+
}
310+
}
311+
312+
if (publishFlag && publishFlag.startsWith('false')) {
313+
continue;
314+
}
315+
316+
if (!packageName) {
317+
console.warn(`Skipping ${relative(normalized)} (missing package name)`);
318+
continue;
319+
}
320+
321+
const localDependencies: string[] = [];
322+
const dependencyRegex = /^([A-Za-z0-9_-]+)\s*=\s*{[^}]*path\s*=\s*"([^"]+)"[^}]*}$/gm;
323+
let match: RegExpExecArray | null;
324+
while ((match = dependencyRegex.exec(raw)) !== null) {
325+
localDependencies.push(match[1]);
326+
}
327+
328+
packages.push({
329+
name: packageName,
330+
directory: crateDirectory,
331+
localDependencies
332+
});
333+
}
334+
335+
return packages;
336+
}
337+
338+
async function publishRustPackages(packages: PublishableRustPackage[], version: string): Promise<void> {
339+
if (packages.length === 0) {
340+
console.warn('No publishable Rust crates found, skipping cargo publish');
341+
return;
342+
}
343+
344+
const ordered = orderRustPackages(packages);
345+
346+
for (const pkg of ordered) {
347+
console.log(`Preparing to publish ${pkg.name}@${version} from ${relative(pkg.directory)}`);
348+
349+
if (await rustPackageVersionExists(pkg.name, version)) {
350+
console.log(`Skipping ${pkg.name}@${version} (already published)`);
351+
continue;
352+
}
353+
354+
await runCommand('cargo', ['publish'], pkg.directory);
355+
}
356+
357+
console.log('Published Rust crates');
358+
}
359+
360+
function orderRustPackages(packages: PublishableRustPackage[]): PublishableRustPackage[] {
361+
const ordered: PublishableRustPackage[] = [];
362+
const packageMap = new Map(packages.map(pkg => [pkg.name, pkg]));
363+
const visited = new Set<string>();
364+
const visiting = new Set<string>();
365+
366+
const visit = (pkg: PublishableRustPackage): void => {
367+
if (visited.has(pkg.name)) {
368+
return;
369+
}
370+
371+
if (visiting.has(pkg.name)) {
372+
console.warn(`Detected cyclic dependency while ordering ${pkg.name}`);
373+
return;
374+
}
375+
376+
visiting.add(pkg.name);
377+
378+
for (const dep of pkg.localDependencies) {
379+
const depPkg = packageMap.get(dep);
380+
if (depPkg) {
381+
visit(depPkg);
382+
}
383+
}
384+
385+
visiting.delete(pkg.name);
386+
visited.add(pkg.name);
387+
ordered.push(pkg);
388+
};
389+
390+
for (const pkg of packages) {
391+
visit(pkg);
392+
}
393+
394+
return ordered;
395+
}
396+
397+
async function rustPackageVersionExists(name: string, version: string): Promise<boolean> {
398+
try {
399+
const output = await captureCommand('cargo', ['search', name, '--limit', '1'], repoRoot);
400+
return output
401+
.split(/\r?\n/)
402+
.some(line => line.startsWith(`${name} = "${version}"`));
403+
} catch {
404+
return false;
405+
}
406+
}
407+
247408
async function runCommand(command: string, args: string[], cwd: string): Promise<void> {
248409
console.log(`Running ${command} ${args.join(' ')} in ${relative(cwd)}`);
249410

0 commit comments

Comments
 (0)