Thanks to visit codestin.com
Credit goes to github.com

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
19 changes: 19 additions & 0 deletions docs/guide/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ export const myFileWriterExtension = () =>
});
```

## Official extension blueprints

The pipeline team incubates a catalog of “official” extensions inside `@wpkernel/pipeline`. Each entry describes its lifecycle, behaviours, reporter hooks, and rollout notes before the concrete factory ships. You can inspect these blueprints from any package (CLI, resource pipeline, custom tooling) via `@wpkernel/pipeline/extensions`.

```ts
import { OFFICIAL_EXTENSION_BLUEPRINTS } from '@wpkernel/pipeline/extensions';

const liveRunner = OFFICIAL_EXTENSION_BLUEPRINTS.find(
(entry) => entry.id === 'live-runner'
);

if (liveRunner?.factory) {
console.log(`Factory slug: ${liveRunner.factory.slug}`);
// Later, require(createLivePipelineRunExtension) once it graduates.
}
```

Blueprints help you plan ahead (e.g., annotate helpers with metadata an extension expects) while keeping the implementation optional. The CLI and resource pipelines both treat these extensions transactionally - the hook still follows the `commit`/`rollback` protocol you defined earlier.

## Creating Your First Extension

Let's create a simple "hello world" extension that logs a message to the console when the pipeline runs.
Expand Down
16 changes: 16 additions & 0 deletions docs/packages/pipeline/framework-contributors.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ Prefer `createPipelineExtension()` over manual registration so setup and hook ph

Expose new helper families through dedicated registration functions that wrap `registerHelper()` with shared defaults. When widening extension payloads, update `PipelineExtensionHookOptions` and the CLI runtime mirror so downstream packages inherit the same shape without hand-written adapters.

## Official extension catalog

The `@wpkernel/pipeline/extensions` entry point publishes `OFFICIAL_EXTENSION_BLUEPRINTS`, a typed manifest of incubating extensions (live runner, concurrency scheduler, etc.). Framework contributors can use the blueprint metadata to align helper annotations and reporter expectations before the factory lands.

```ts
import { OFFICIAL_EXTENSION_BLUEPRINTS } from '@wpkernel/pipeline/extensions';

for (const blueprint of OFFICIAL_EXTENSION_BLUEPRINTS) {
if (blueprint.id === 'live-runner') {
console.log(blueprint.pipelineTouchPoints);
}
}
```

When you add a new official extension, update the blueprint with helper annotations, lifecycle slots, and rollout notes so downstream packages can stage migrations against a stable contract.

## Testing

Cover helper and extension wiring inside `packages/pipeline/src/__tests__`. Pair happy-path tests with simulated rollback failures to confirm commits are skipped and diagnostics bubble up. Integration suites should snapshot the execution metadata so helper ordering regressions surface quickly.
Expand Down
30 changes: 18 additions & 12 deletions packages/cli/src/runtime/__tests__/adapterExtensions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function buildOptions(
generationState,
},
artifact,
lifecycle: 'after-fragments',
};

return {
Expand All @@ -113,15 +114,20 @@ function buildOptions(
...base.options,
...overrides.options,
},
lifecycle: overrides.lifecycle ?? base.lifecycle,
};
}

function buildHook(config: WPKernelConfigV1): {
async function buildHook(config: WPKernelConfigV1): Promise<{
hook: PipelineExtensionHook;
options: PipelineExtensionHookOptions;
} {
}> {
const extension = buildAdapterExtensionsExtension();
const hook = extension.register({} as never) as PipelineExtensionHook;
const registration = await extension.register({} as never);
const hook =
typeof registration === 'function'
? registration
: (registration?.hook ?? (() => Promise.resolve()));
const options = buildOptions({
options: {
config,
Expand Down Expand Up @@ -149,7 +155,7 @@ describe('buildAdapterExtensionsExtension', () => {
schemas: {},
adapters: { extensions: [factory] },
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

const result = await hook({
...options,
Expand All @@ -170,7 +176,7 @@ describe('buildAdapterExtensionsExtension', () => {
schemas: {},
adapters: { extensions: [] },
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

const result = await hook(options);

Expand All @@ -188,7 +194,7 @@ describe('buildAdapterExtensionsExtension', () => {
extensions: [() => [{ name: ' ', apply: jest.fn() }]],
},
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

await expect(hook(options)).rejects.toThrow(WPKernelError);
expect(runAdapterExtensionsMock).not.toHaveBeenCalled();
Expand All @@ -213,7 +219,7 @@ describe('buildAdapterExtensionsExtension', () => {
schemas: {},
adapters: { extensions: [factory] },
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

const commit = jest.fn().mockResolvedValue(undefined);
const rollback = jest.fn().mockResolvedValue(undefined);
Expand Down Expand Up @@ -293,7 +299,7 @@ describe('buildAdapterExtensionsExtension', () => {
],
},
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

await expect(hook(options)).rejects.toThrow(WPKernelError);
expect(runAdapterExtensionsMock).not.toHaveBeenCalled();
Expand Down Expand Up @@ -329,7 +335,7 @@ describe('buildAdapterExtensionsExtension', () => {
schemas: {},
adapters: { extensions: [factory as never] },
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

await expect(hook(options)).rejects.toThrow(WPKernelError);
expect(runAdapterExtensionsMock).not.toHaveBeenCalled();
Expand All @@ -350,7 +356,7 @@ describe('buildAdapterExtensionsExtension', () => {
schemas: {},
adapters: { extensions: [skip, empty] },
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

const result = await hook(options);

Expand Down Expand Up @@ -379,7 +385,7 @@ describe('buildAdapterExtensionsExtension', () => {
],
},
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

runAdapterExtensionsMock.mockResolvedValue({
ir: options.artifact,
Expand Down Expand Up @@ -415,7 +421,7 @@ describe('buildAdapterExtensionsExtension', () => {
extensions: [() => ({ name: 'single', apply })],
},
} as WPKernelConfigV1;
const { hook, options } = buildHook(config);
const { hook, options } = await buildHook(config);

runAdapterExtensionsMock.mockResolvedValue({
ir: options.artifact,
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/runtime/createPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,12 @@ export function createPipeline(): Pipeline {
steps,
} satisfies PipelineRunResult;
},
createExtensionHookOptions({ context, options, artifact }) {
createExtensionHookOptions({ context, options, artifact, lifecycle }) {
return {
context,
options,
artifact,
lifecycle,
} satisfies PipelineExtensionHookOptions;
},
onExtensionRollbackError({ error, extensionKeys, context }) {
Expand Down
15 changes: 15 additions & 0 deletions packages/pipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ console.log(result.artifact.result); // "item1, item2"
- **Typed contracts** – helper descriptors, execution metadata, and diagnostics surfaces are
fully typed for TypeScript consumers.

## Official extension incubator

The package owns an `src/extensions/` workspace where internal extensions are designed before
being promoted to standalone packages. The directory ships a README that documents authoring
guidelines and an [`official.ts`](./src/extensions/official.ts) catalogue describing the
blueprints for:

- a live runner extension that streams reporter events to interactive renderers;
- a deterministic concurrency scheduler;
- additional integration blueprints for telemetry and runtime adapters.

Consumers can import the catalogue through `@wpkernel/pipeline/extensions` to understand the
contracts and helper annotations each extension expects while we finalise their
implementations.

## Consumers

- `@wpkernel/cli` (code generation pipeline, codemod entry points)
Expand Down
5 changes: 5 additions & 0 deletions packages/pipeline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./extensions": {
"types": "./dist/extensions/index.d.ts",
"import": "./dist/extensions/index.js",
"default": "./dist/extensions/index.js"
},
"./package.json": "./package.json"
},
"files": [
Expand Down
41 changes: 41 additions & 0 deletions packages/pipeline/src/__tests__/createExtension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,45 @@ describe('createPipelineExtension', () => {
]);
expect(hook).toHaveBeenCalledTimes(1);
});

it('wraps inline hooks with lifecycle metadata', async () => {
const extension = createPipelineExtension<
TestPipeline,
TestContext,
TestRunOptions,
TestArtifact
>({
lifecycle: 'before-builders',
hook: ({ artifact }) => ({ artifact }),
});

const registration = await extension.register({} as TestPipeline);

expect(registration).toEqual({
lifecycle: 'before-builders',
hook: expect.any(Function),
});
});

it('prioritises hook-provided lifecycle metadata', async () => {
const extension = createPipelineExtension<
TestPipeline,
TestContext,
TestRunOptions,
TestArtifact
>({
lifecycle: 'prepare',
hook: {
lifecycle: 'after-builders',
hook: ({ artifact }) => ({ artifact }),
},
});

const registration = await extension.register({} as TestPipeline);

expect(registration).toEqual({
lifecycle: 'after-builders',
hook: expect.any(Function),
});
});
});
Loading