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
5 changes: 5 additions & 0 deletions .changeset/dedupe-pnpm-peer-variant-step-ids.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/builders': patch
---

Deduplicate identical pnpm peer-variant copies of a package instead of failing the build with a duplicate step/workflow ID error. Different implementations that map to the same ID still fail.
16 changes: 16 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@
"date-subclass-vm",
"decouple-vm-seed-from-startedat",
"dedupe-otel-api",
"dedupe-pnpm-peer-variant-step-ids",
"dense-stream-viewer",
"deployment-id-latest-noop-non-vercel",
"deprecate-storage-runs-observability",
Expand All @@ -161,6 +162,7 @@
"display-occurred-at",
"docs-event-sourcing-duplicate-events",
"docs-migration-guides-redirects",
"docs-next-16-3-3",
"docs-rendered-link-lint",
"docs-step-executed-multiple-times",
"docs-v5-whats-new-pages",
Expand Down Expand Up @@ -286,6 +288,7 @@
"great-mails-argue",
"great-news-beg",
"green-streams-decode",
"guard-astro-sveltekit-writes",
"guard-step-consumer-events",
"gzip-ref-compression-core",
"gzip-ref-compression-world-vercel",
Expand Down Expand Up @@ -329,9 +332,13 @@
"lazy-discovery-default",
"lazy-discovery-hmr",
"lazy-hook-replay-preload",
"lazy-hook-resume-durable-abort",
"lazy-hook-resume-vitest",
"lazy-hook-resumption",
"lazy-inline-step-start",
"lazy-next-step-imports",
"lazy-only-hook-resume-world",
"lazy-only-hook-resume",
"lazy-resume-deployment-affinity",
"lazy-stream-key-request-diagnostics",
"lazy-vercel-world-bundle",
Expand Down Expand Up @@ -418,13 +425,15 @@
"prewarm-next-swc-cache",
"private-member-dce",
"proud-friends-decide",
"prune-workflow-sandbox",
"python-workbench-app",
"queue-client-0-5-0",
"queue-namespace-primitive",
"queued-for-uses-first-step-started",
"quick-local-replay",
"quickjs-baseline-snapshot",
"quickjs-hook-capability",
"quickjs-hook-dispose-token",
"quickjs-hook-retention",
"quickjs-host-serde",
"quickjs-inline-steps",
Expand All @@ -446,6 +455,7 @@
"real-pens-cut",
"reconnect-object-streams",
"recover-invalid-tool-input",
"red-masks-rule",
"reject-empty-hook-token",
"relative-time-card",
"remove-client-mode",
Expand All @@ -464,6 +474,7 @@
"rename-set-attributes",
"repair-ui-message-stream-framing",
"replay-consumed-event-clock",
"replay-cost-telemetry",
"replay-divergence-redelivery",
"replay-lineage-execution-context",
"replay-timeout-excludes-step-bodies",
Expand All @@ -477,6 +488,8 @@
"resilient-step-dispatch-off",
"resilient-step-dispatch",
"resizable-trace-detail-panel",
"retain-vms-across-attributes",
"retain-vms-across-hooks",
"retain-workflow-vm",
"retained-vm-guest-code-gate",
"retire-step-build-output",
Expand Down Expand Up @@ -518,9 +531,11 @@
"silly-pears-jam",
"silver-coins-deny",
"silver-comics-care",
"sixty-lands-matter",
"sixty-plants-shout",
"skip-changeset-release-deploys",
"skip-community-worlds-main",
"skip-perm-tests-when-dac-bypassed",
"skip-unchanged-generated-writes",
"skip-world-local-nov-ghosts",
"slot-event-ids",
Expand Down Expand Up @@ -648,6 +663,7 @@
"wild-taxes-guess",
"windows-events-consumer-poll",
"windows-inline-sourcemaps",
"windows-step-registration-cleanup",
"wise-frogs-thank",
"wise-planes-tell",
"witty-pugs-applaud",
Expand Down
2 changes: 2 additions & 0 deletions .changeset/skip-perm-tests-when-dac-bypassed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
5 changes: 5 additions & 0 deletions .changeset/tidy-local-shutdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/world-local': patch
---

Abort active local queue deliveries when the World closes, including when transport timeouts are disabled.
20 changes: 20 additions & 0 deletions packages/astro/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# @workflow/astro

## 5.0.0-beta.46

### Patch Changes

- [#3688](https://github.com/vercel/workflow/pull/3688) [`0750fe8`](https://github.com/vercel/workflow/commit/0750fe8958f50ee354cd24fe3f02a37c801bc4dc) Thanks [@VaguelySerious](https://github.com/VaguelySerious)! - Stop rewriting generated files whose content is unchanged, so a no-op rebuild no longer invalidates them in the dev server.

- Updated dependencies [[`f806f8c`](https://github.com/vercel/workflow/commit/f806f8c258fb9ba45fec8b0e4919f55b0bf81a3f)]:
- @workflow/builders@5.0.0-beta.46
- @workflow/rollup@5.0.0-beta.46
- @workflow/vite@5.0.0-beta.46

## 5.0.0-beta.45

### Patch Changes

- Updated dependencies [[`d62b444`](https://github.com/vercel/workflow/commit/d62b44473b43e183e71386fe84b33f5e7bb5445c)]:
- @workflow/builders@5.0.0-beta.45
- @workflow/rollup@5.0.0-beta.45
- @workflow/vite@5.0.0-beta.45

## 5.0.0-beta.44

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@workflow/astro",
"version": "5.0.0-beta.44",
"version": "5.0.0-beta.46",
"description": "Astro integration for Workflow SDK",
"type": "module",
"main": "dist/index.js",
Expand Down
20 changes: 20 additions & 0 deletions packages/builders/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# @workflow/builders

## 5.0.0-beta.46

### Patch Changes

- [#3795](https://github.com/vercel/workflow/pull/3795) [`f806f8c`](https://github.com/vercel/workflow/commit/f806f8c258fb9ba45fec8b0e4919f55b0bf81a3f) Thanks [@TooTallNate](https://github.com/TooTallNate)! - Deduplicate identical pnpm peer-variant copies of a package instead of failing the build with a duplicate step/workflow ID error. Different implementations that map to the same ID still fail.

- Updated dependencies [[`d9e0777`](https://github.com/vercel/workflow/commit/d9e0777eb8b1ce5f3be3fe865bc5a17fdbdb9d5d), [`d9e0777`](https://github.com/vercel/workflow/commit/d9e0777eb8b1ce5f3be3fe865bc5a17fdbdb9d5d), [`82e2678`](https://github.com/vercel/workflow/commit/82e267893917e06f5d3ce316baefc27cd024a912), [`556f3f0`](https://github.com/vercel/workflow/commit/556f3f080a33f8673cf3c08805997dba4e4d865e)]:
- @workflow/core@5.0.0-beta.46
- @workflow/errors@5.0.0-beta.18

## 5.0.0-beta.45

### Patch Changes

- [#3550](https://github.com/vercel/workflow/pull/3550) [`d62b444`](https://github.com/vercel/workflow/commit/d62b44473b43e183e71386fe84b33f5e7bb5445c) Thanks [@NathanColosimo](https://github.com/NathanColosimo)! - Keep schema-only World modules out of workflow VM bundles, prevent unknown attribute deletions from offsetting new keys, and safely ignore prototype-like event type names.

- Updated dependencies [[`d62b444`](https://github.com/vercel/workflow/commit/d62b44473b43e183e71386fe84b33f5e7bb5445c), [`27cab14`](https://github.com/vercel/workflow/commit/27cab14adcc6f748500fca19cf78feeb60a125e7), [`f7fb012`](https://github.com/vercel/workflow/commit/f7fb0126528fac2168492c3143fe0379b6f5ef1a), [`5841558`](https://github.com/vercel/workflow/commit/584155897f75e712a1c2bc199d6d12027cd18dab)]:
- @workflow/core@5.0.0-beta.45
- @workflow/errors@5.0.0-beta.18

## 5.0.0-beta.44

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/builders/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@workflow/builders",
"version": "5.0.0-beta.44",
"version": "5.0.0-beta.46",
"description": "Shared builder infrastructure for Workflow SDK",
"type": "module",
"main": "./dist/index.js",
Expand Down
103 changes: 22 additions & 81 deletions packages/builders/src/base-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import {
type DiscoveredEntries,
fastDiscoverEntries,
} from './fast-discovery.js';
import {
hashManifestSource,
type ManifestEntryLocation,
mergeWorkflowManifest,
} from './manifest-ids.js';
import {
getImportPath,
resolveModuleSpecifier,
Expand Down Expand Up @@ -185,81 +190,18 @@ function moduleIdentityKey(file: string, moduleSpecifierRoot: string): string {
return file.replace(/\\/g, '/');
}

type ManifestEntryLocation = {
filePath: string;
name: string;
};

type CachedManifestTransform = {
size: number;
mtimeMs: number;
manifest: WorkflowManifest;
/**
* Fingerprint of the source contents that produced `manifest`, used to
* deduplicate equivalent copies of the same module across files (see
* `mergeWorkflowManifest` in `manifest-ids.ts`).
*/
contentHash: string;
};

function formatIdLocation(location: ManifestEntryLocation): string {
return `${location.filePath}#${location.name}`;
}

function assertUniqueManifestIds<TEntry>(
entriesByFile: Record<string, Record<string, TEntry>> | undefined,
ids: Map<string, ManifestEntryLocation>,
getId: (entry: TEntry) => string,
label: 'step' | 'workflow'
): void {
for (const [filePath, entries] of Object.entries(entriesByFile || {})) {
for (const [name, data] of Object.entries(entries)) {
const id = getId(data);
const existing = ids.get(id);
const current = { filePath, name };
if (
existing &&
(existing.filePath !== current.filePath ||
existing.name !== current.name)
) {
const idName = label === 'step' ? 'workflow step ID' : 'workflow ID';
const functionName = `${label} function`;
const capitalizedLabel = label === 'step' ? 'Step' : 'Workflow';
throw new WorkflowBuildError(
`Duplicate ${idName} "${id}" generated for ${formatIdLocation(existing)} and ${formatIdLocation(current)}.`,
{
hint:
`${capitalizedLabel} IDs must be unique across a build. ` +
`If you own one of the colliding files, rename the ${functionName} or export ` +
`the package file through a unique package subpath. If the collision is in a ` +
`transitive dependency you don't control, file an issue with the upstream ` +
`package or pin to a non-colliding version.`,
}
);
}
ids.set(id, current);
}
}
}

function mergeWorkflowManifest(
target: WorkflowManifest,
incoming: WorkflowManifest,
stepIds: Map<string, ManifestEntryLocation>,
workflowIds: Map<string, ManifestEntryLocation>
): void {
assertUniqueManifestIds(
incoming.steps,
stepIds,
(data) => data.stepId,
'step'
);
assertUniqueManifestIds(
incoming.workflows,
workflowIds,
(data) => data.workflowId,
'workflow'
);

target.workflows = Object.assign(target.workflows || {}, incoming.workflows);
target.steps = Object.assign(target.steps || {}, incoming.steps);
target.classes = Object.assign(target.classes || {}, incoming.classes);
}

/**
* Base class for workflow builders. Provides common build logic for transforming
* workflow source files into deployable bundles using esbuild and SWC.
Expand Down Expand Up @@ -784,7 +726,7 @@ export abstract class BaseBuilder {
private async getCachedManifestTransform(
file: string,
mode: 'workflow' | 'step'
): Promise<WorkflowManifest> {
): Promise<{ manifest: WorkflowManifest; contentHash: string }> {
const stats = await stat(file);
const cacheKey = `${mode}:${file}`;
const cached = this.manifestTransformCache.get(cacheKey);
Expand All @@ -793,7 +735,7 @@ export abstract class BaseBuilder {
cached.size === stats.size &&
cached.mtimeMs === stats.mtimeMs
) {
return cached.manifest;
return { manifest: cached.manifest, contentHash: cached.contentHash };
}

const source = await readFile(file, 'utf8');
Expand All @@ -806,12 +748,14 @@ export abstract class BaseBuilder {
this.transformProjectRoot,
this.moduleSpecifierRoot
);
const contentHash = hashManifestSource(source);
this.manifestTransformCache.set(cacheKey, {
size: stats.size,
mtimeMs: stats.mtimeMs,
manifest: workflowManifest,
contentHash,
});
return workflowManifest;
return { manifest: workflowManifest, contentHash };
}

protected createRouteImportSpecifier(file: string, routeDir: string): string {
Expand Down Expand Up @@ -912,15 +856,14 @@ export const __steps_registered = true;
const workflowIds = new Map<string, ManifestEntryLocation>();
await Promise.all(
manifestFiles.map(async (file) => {
const fileManifest = await this.getCachedManifestTransform(
file,
'step'
);
const { manifest: fileManifest, contentHash } =
await this.getCachedManifestTransform(file, 'step');
mergeWorkflowManifest(
workflowManifest,
fileManifest,
stepIds,
workflowIds
workflowIds,
contentHash
);
})
);
Expand Down Expand Up @@ -1239,10 +1182,8 @@ export const __steps_registered = true;
await Promise.all(
workflowOnlyFiles.map(async (workflowFile) => {
try {
const fileManifest = await this.getCachedManifestTransform(
workflowFile,
'workflow'
);
const { manifest: fileManifest } =
await this.getCachedManifestTransform(workflowFile, 'workflow');
if (fileManifest.workflows) {
workflowManifest.workflows = Object.assign(
workflowManifest.workflows || {},
Expand Down
Loading
Loading