Skip to content

Commit 1c6557a

Browse files
authored
Merge pull request #21 from syi0808/fix/crates-dry-run-sibling-deps
fix: dry-run validation for workspace crates with internal dependencies
2 parents 0e3d9ac + 2609f78 commit 1c6557a

File tree

26 files changed

+276
-28
lines changed

26 files changed

+276
-28
lines changed

packages/core/src/config/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export interface PubmConfig {
7474
rollbackStrategy?: "individual" | "all";
7575
rollback?: RollbackConfig;
7676
lockfileSync?: "required" | "optional" | "skip";
77+
/** Skip dry-run validation during prepare phase. @default false */
78+
skipDryRun?: boolean;
7779
ecosystems?: Record<string, EcosystemConfig>;
7880
plugins?: PubmPlugin[];
7981
compress?: CompressOption;
@@ -107,6 +109,7 @@ export interface ResolvedPubmConfig
107109
| "ecosystems"
108110
| "testScript"
109111
| "buildScript"
112+
| "skipDryRun"
110113
>
111114
> {
112115
compress?: CompressOption;
@@ -123,6 +126,7 @@ export interface ResolvedPubmConfig
123126
ecosystems: Record<string, EcosystemConfig>;
124127
testScript?: string;
125128
buildScript?: string;
129+
skipDryRun?: boolean;
126130
discoveryEmpty?: boolean;
127131
}
128132

packages/core/src/i18n/locales/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"cli.option.noTests": "Tests vor der Veröffentlichung überspringen",
270270
"cli.option.noBuild": "Build vor der Veröffentlichung überspringen",
271271
"cli.option.noPublish": "Veröffentlichungsaufgabe überspringen",
272+
"cli.option.noDryRunValidation": "Dry-Run-Validierung in der Vorbereitungsphase überspringen",
272273
"cli.option.skipRelease": "GitHub Release-Erstellung überspringen",
273274
"cli.option.tag": "Unter einem bestimmten dist-tag veröffentlichen",
274275
"cli.option.contents": "Unterverzeichnis zum Veröffentlichen",

packages/core/src/i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"cli.option.noTests": "Skip running tests before publishing",
270270
"cli.option.noBuild": "Skip build before publishing",
271271
"cli.option.noPublish": "Skip publishing task",
272+
"cli.option.noDryRunValidation": "Skip dry-run validation during prepare phase",
272273
"cli.option.skipRelease": "Skip GitHub Release creation",
273274
"cli.option.tag": "Publish under a specific dist-tag",
274275
"cli.option.contents": "Subdirectory to publish",

packages/core/src/i18n/locales/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"cli.option.noTests": "Omitir la ejecución de pruebas antes de publicar",
270270
"cli.option.noBuild": "Omitir la construcción antes de publicar",
271271
"cli.option.noPublish": "Omitir la tarea de publicación",
272+
"cli.option.noDryRunValidation": "Omitir la validación dry-run durante la fase de preparación",
272273
"cli.option.skipRelease": "Omitir la creación de GitHub Release",
273274
"cli.option.tag": "Publicar bajo un dist-tag específico",
274275
"cli.option.contents": "Subdirectorio a publicar",

packages/core/src/i18n/locales/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"cli.option.noTests": "Ignorer l'exécution des tests avant la publication",
270270
"cli.option.noBuild": "Ignorer la construction avant la publication",
271271
"cli.option.noPublish": "Ignorer la tâche de publication",
272+
"cli.option.noDryRunValidation": "Ignorer la validation dry-run pendant la phase de préparation",
272273
"cli.option.skipRelease": "Ignorer la création de la GitHub Release",
273274
"cli.option.tag": "Publier sous un dist-tag spécifique",
274275
"cli.option.contents": "Sous-répertoire à publier",

packages/core/src/i18n/locales/ko.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"cli.option.noTests": "배포 전 테스트 실행 건너뜀",
270270
"cli.option.noBuild": "배포 전 빌드 건너뜀",
271271
"cli.option.noPublish": "배포 작업 건너뜀",
272+
"cli.option.noDryRunValidation": "준비 단계에서 드라이런 검증을 건너뜁니다",
272273
"cli.option.skipRelease": "GitHub Release 생성 건너뜀",
273274
"cli.option.tag": "특정 dist-tag로 배포",
274275
"cli.option.contents": "배포할 하위 디렉토리",

packages/core/src/i18n/locales/zh-cn.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
"cli.option.noTests": "跳过发布前运行测试",
270270
"cli.option.noBuild": "跳过发布前构建",
271271
"cli.option.noPublish": "跳过发布任务",
272+
"cli.option.noDryRunValidation": "跳过准备阶段的试运行验证",
272273
"cli.option.skipRelease": "跳过 GitHub Release 创建",
273274
"cli.option.tag": "以特定 dist-tag 发布",
274275
"cli.option.contents": "要发布的子目录",

packages/core/src/tasks/dry-run-publish.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -126,31 +126,41 @@ async function getCrateName(packagePath: string): Promise<string> {
126126
}
127127

128128
const MISSING_CRATE_PATTERN = /no matching package named `([^`]+)` found/;
129+
const VERSION_MISMATCH_PATTERN =
130+
/failed to select a version for the requirement `([^=`\s]+)/;
129131

130132
async function findUnpublishedSiblingDeps(
131133
packagePath: string,
132-
siblingPaths: string[],
134+
siblingKeys: string[],
135+
ctx: PubmContext,
133136
): Promise<string[]> {
134137
const eco = new RustEcosystem(packagePath);
135138
const deps = await eco.dependencies();
136139

137-
const siblingNameToPath = new Map<string, string>();
140+
const siblingNameToKey = new Map<string, string>();
138141
await Promise.all(
139-
siblingPaths.map(async (p) => {
140-
const name = await getCrateName(p);
141-
siblingNameToPath.set(name, p);
142+
siblingKeys.map(async (k) => {
143+
const name = await getCrateName(pathFromKey(k));
144+
siblingNameToKey.set(name, k);
142145
}),
143146
);
144147

145-
const siblingDeps = deps.filter((d) => siblingNameToPath.has(d));
148+
const siblingDeps = deps.filter((d) => siblingNameToKey.has(d));
146149

147150
const results = await Promise.all(
148151
siblingDeps.map(async (name) => {
149-
const siblingPath = siblingNameToPath.get(name);
150-
if (!siblingPath) {
151-
throw new Error(`Missing sibling crate path for dependency: ${name}`);
152+
const siblingKey = siblingNameToKey.get(name);
153+
if (!siblingKey) {
154+
throw new Error(`Missing sibling crate key for dependency: ${name}`);
152155
}
156+
const siblingPath = pathFromKey(siblingKey);
153157
const registry = await cratesPackageRegistry(siblingPath);
158+
const version = getPackageVersion(ctx, siblingKey);
159+
if (version) {
160+
const versionPublished = await registry.isVersionPublished(version);
161+
return versionPublished ? null : name;
162+
}
163+
// Fallback: check if the crate exists at all
154164
const published = await registry.isPublished();
155165
return published ? null : name;
156166
}),
@@ -164,7 +174,6 @@ export function createCratesDryRunPublishTask(
164174
siblingKeys?: string[],
165175
): ListrTask<PubmContext> {
166176
const packagePath = pathFromKey(key);
167-
const siblingPaths = siblingKeys?.map(pathFromKey);
168177
return {
169178
title: t("task.dryRun.crates.title", { path: packagePath }),
170179
task: async (ctx, task): Promise<void> => {
@@ -185,11 +194,12 @@ export function createCratesDryRunPublishTask(
185194
return task.skip();
186195
}
187196

188-
// Proactive: skip if any sibling dependency is not yet on crates.io
189-
if (siblingPaths?.length) {
197+
// Proactive: skip if any sibling dependency's new version is not yet on crates.io
198+
if (siblingKeys?.length) {
190199
const unpublished = await findUnpublishedSiblingDeps(
191200
packagePath,
192-
siblingPaths,
201+
siblingKeys,
202+
ctx,
193203
);
194204
if (unpublished.length > 0) {
195205
task.title = t("task.dryRun.crates.skippedSibling", {
@@ -209,15 +219,18 @@ export function createCratesDryRunPublishTask(
209219
} catch (error) {
210220
// Reactive fallback: catch sibling-related errors
211221
const message = error instanceof Error ? error.message : String(error);
212-
const match = message.match(MISSING_CRATE_PATTERN);
213-
if (match && siblingPaths) {
222+
const missingMatch = message.match(MISSING_CRATE_PATTERN);
223+
const versionMatch = message.match(VERSION_MISMATCH_PATTERN);
224+
const crateName = missingMatch?.[1] ?? versionMatch?.[1]?.trim();
225+
226+
if (crateName && siblingKeys) {
214227
const siblingNames = await Promise.all(
215-
siblingPaths.map((p) => getCrateName(p)),
228+
siblingKeys.map((k) => getCrateName(pathFromKey(k))),
216229
);
217-
if (siblingNames.includes(match[1])) {
230+
if (siblingNames.includes(crateName)) {
218231
task.title = t("task.dryRun.crates.skippedSibling", {
219232
path: packagePath,
220-
crate: match[1],
233+
crate: crateName,
221234
});
222235
return;
223236
}

packages/core/src/tasks/phases/dry-run.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ export function createDryRunTasks(
2121
dryRun: boolean,
2222
mode: string,
2323
hasPrepare: boolean,
24+
skipDryRun: boolean,
2425
): ListrTask<PubmContext>[] {
2526
return [
2627
{
27-
enabled: dryRun || (mode === "ci" && hasPrepare),
28+
enabled: !skipDryRun && (dryRun || (mode === "ci" && hasPrepare)),
2829
title: t("task.dryRunValidation.title"),
2930
task: async (ctx, parentTask): Promise<Listr<PubmContext>> => {
3031
await resolveWorkspaceProtocols(ctx);
@@ -47,7 +48,7 @@ export function createDryRunTasks(
4748
},
4849
},
4950
{
50-
enabled: dryRun || (mode === "ci" && hasPrepare),
51+
enabled: !skipDryRun && (dryRun || (mode === "ci" && hasPrepare)),
5152
skip: (ctx) => !ctx.runtime.workspaceBackups?.size,
5253
title: t("task.dryRunValidation.restoreProtocols"),
5354
task: async (ctx) => {

packages/core/src/tasks/runner.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ export async function run(ctx: PubmContext): Promise<void> {
7777
createBuildTask(hasPrepare, !!ctx.options.skipBuild),
7878
createVersionTask(hasPrepare, dryRun),
7979
...createPublishTasks(hasPublish, dryRun, !!ctx.options.skipPublish),
80-
...createDryRunTasks(dryRun, mode, hasPrepare),
80+
...createDryRunTasks(
81+
dryRun,
82+
mode,
83+
hasPrepare,
84+
!!ctx.options.skipDryRun,
85+
),
8186
createPushTask(hasPrepare, dryRun),
8287
createReleaseTask(
8388
hasPublish,

0 commit comments

Comments
 (0)