Skip to content
Open
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
11 changes: 11 additions & 0 deletions .changeset/remote-bundle-hooks-frameworks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@workflow/next': patch
'@workflow/nitro': patch
'@workflow/nuxt': patch
'@workflow/astro': patch
'@workflow/sveltekit': patch
'@workflow/nest': patch
---

Forward the completed-bundle hook through the framework integrations so
applications can generate derived workflow metadata consistently.
5 changes: 5 additions & 0 deletions .changeset/tidy-bundles-observe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/builders': minor
---

Add an optional hook for completed workflow bundles and watch rebuilds.
18 changes: 18 additions & 0 deletions packages/astro/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
# workflow/astro

The docs have moved! Refer to them [here](https://workflow-sdk.dev/)

Applications can derive deployment metadata after each successful workflow
bundle by passing `onAfterBundle` to `workflow()`:

```ts
import { workflow } from '@workflow/astro'

export default {
integrations: [
workflow({
onAfterBundle: ({ artifacts }) => {
// Derive deployment metadata from artifacts here.
void artifacts
}
})
]
}
```
2 changes: 2 additions & 0 deletions packages/astro/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class LocalBuilder extends BaseBuilder {
projectRoot: config.projectRoot,
dirs: config.dirs,
sourcemap: options.sourcemap,
onAfterBundle: options.onAfterBundle,
}),
...options,
dirs: config.dirs,
Expand Down Expand Up @@ -187,6 +188,7 @@ export class VercelBuilder extends VercelBuildOutputAPIBuilder {
dirs: config.dirs,
runtime: options.runtime,
sourcemap: options.sourcemap,
onAfterBundle: options.onAfterBundle,
}),
...options,
dirs: config.dirs,
Expand Down
13 changes: 12 additions & 1 deletion packages/astro/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { type AstroConfig, createBuildQueue } from '@workflow/builders';
import {
type AstroConfig,
createBuildQueue,
type WorkflowAfterBundleHook,
} from '@workflow/builders';
import { workflowTransformPlugin } from '@workflow/rollup';
import { workflowHotUpdatePlugin } from '@workflow/vite';
import type { AstroIntegration, HookParameters } from 'astro';
Expand All @@ -14,13 +18,19 @@ export interface WorkflowPluginOptions {
* also be set via the `WORKFLOW_SOURCEMAP` environment variable.
*/
sourcemap?: boolean | 'inline' | 'linked' | 'external' | 'both';

/**
* Runs after the workflow bundles and manifest have been written.
*/
onAfterBundle?: WorkflowAfterBundleHook;
}

export function workflowPlugin(
options: WorkflowPluginOptions = {}
): AstroIntegration {
let builderOptions: Partial<AstroConfig> = {
sourcemap: options.sourcemap,
onAfterBundle: options.onAfterBundle,
};
const enqueue = createBuildQueue();

Expand All @@ -36,6 +46,7 @@ export function workflowPlugin(
workingDir: fileURLToPath(config.root),
dirs: [join(srcDir, 'pages'), join(srcDir, 'workflows')],
sourcemap: options.sourcemap,
onAfterBundle: options.onAfterBundle,
};
const vitePlugins = [workflowTransformPlugin()];

Expand Down
57 changes: 57 additions & 0 deletions packages/builders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,63 @@ accepted. It cannot replace the generated code, and throwing aborts the build.
A source file may be observed multiple times across transform modes, bundles,
and watch rebuilds, so consumers should deduplicate results when necessary.

### Running a hook after bundle artifacts are complete

Low-level builder configurations can provide an `onAfterBundle` hook. It runs
once after a combined workflow bundle and its manifest have been written
successfully, and again after each successful watch rebuild:

```typescript
import type { WorkflowAfterBundleHook } from '@workflow/builders';

// Pass as `onAfterBundle` in the builder configuration.
const onAfterBundle: WorkflowAfterBundleHook = async ({
buildTarget,
workingDir,
artifacts,
}) => {
const manifestPath = artifacts.find(
(artifact) => artifact.kind === 'manifest'
)!.path;
// Read manifestPath or derive other data from the completed bundle.
};
```

Every invocation has exactly three artifact descriptors, ordered as `steps`,
`workflows`, and `manifest`. `workingDir` and every artifact path are absolute;
relative output paths are resolved against the builder's `workingDir` before
the files are written. The `manifest` artifact is authoritative: it points to
the serialized manifest with its `version`, converted entries, and workflow
graphs. The hook does not expose the internal SWC manifest shape.

This is a **bundle boundary**, not the end of the builder's complete `build()`
method. Framework-specific webhook, source-map, diagnostics, public-manifest,
function-configuration, and optional client outputs may not exist yet and do
not produce separate invocations or artifact descriptors. Code that needs one
of those later outputs must run at a framework-specific build-completion hook
instead.

`onAfterBundle` is currently a builder API. Direct `StandaloneBuilder` and
`VercelBuildOutputAPIBuilder` configurations can provide it. The Next, Nitro,
Nuxt, Astro, SvelteKit, and Nest integrations forward the option through their
framework configuration surfaces.
Builders that do not call `createManifest()`, including `SimBuilder`, do not
invoke it.

The hook is awaited serially, including during watch rebuilds, so it should stay
fast or hand expensive work to another system. A hook failure is thrown as
`onAfterBundle hook failed`, with the original thrown value available as its
`cause`. The three bundle files have already been written at that point and are
not rolled back. A direct build rejects; a framework watcher may catch and log
that rejection according to its normal error policy.

The hook is not called when bundle or manifest generation fails. Each
successful bundle write authorizes at most one hook call, and a later failed
rebuild invalidates the prior completion. Build systems can rebuild unchanged
inputs, so consumers should still make external side effects idempotent. The
hook runs in the build process with the builder's filesystem access and receives
absolute local paths; only install trusted hooks.

## Architecture

The builder system uses:
Expand Down
Loading
Loading