Skip to content

Commit

Permalink
feat(cli): Implement plugin system
Browse files Browse the repository at this point in the history
  • Loading branch information
tommy351 committed Feb 9, 2024
1 parent a375679 commit 4d09600
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-flies-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kosko/cli": minor
---

Implement the plugin system.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"@kosko/build-scripts": "workspace:^",
"@kosko/env": "workspace:^",
"@kosko/jest-preset": "workspace:^",
"@kosko/plugin": "workspace:^",
"@kosko/test-utils": "workspace:^",
"@types/bl": "^5.0.2",
"@types/exit": "^0.1.31",
Expand Down
5 changes: 1 addition & 4 deletions packages/cli/src/commands/generate/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ import { dirname, join } from "node:path";
import { readFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { env } from "node:process";
import { excludeFalsyInArray } from "../../utils";

const KOSKO_ENV = "@kosko/env";

function excludeFalsyInArray<T>(input: (T | undefined | null)[]): T[] {
return input.filter(Boolean) as T[];
}

function pickEnvArray(envs: string[]): string | string[] | undefined {
if (envs.length > 1) return envs;
return envs[0];
Expand Down
75 changes: 75 additions & 0 deletions packages/cli/src/commands/generate/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { PluginConfig } from "@kosko/config";
import type { Plugin, PluginContext } from "@kosko/plugin";
import { importPath } from "@kosko/require";
import resolveFrom from "resolve-from";
import { excludeFalsyInArray } from "../../utils";

async function loadPlugin({
name,
path,
...ctx
}: PluginContext & { name: string; path: string }): Promise<Plugin> {
const mod = await importPath(path);
const plugin = await mod(ctx);

if (typeof plugin !== "object" || plugin == null) {
throw new Error(`Plugin "${name}" must export an object`);
}

if (
plugin.transformManifest != null &&
typeof plugin.transformManifest !== "function"
) {
throw new Error(
`Expected "transformManifest" to be a function in plugin "${name}"`
);
}

return plugin;
}

function composeTransforms(
transformers: readonly Required<Plugin>["transformManifest"][]
): Plugin["transformManifest"] {
return async (manifest) => {
let data = manifest.data;

for (const transform of transformers) {
data = await transform({ ...manifest, data });

// Stop if the return value is undefined or null
if (data == null) return data;
}

return data;
};
}

export async function loadPlugins(
cwd: string,
configs: readonly PluginConfig[]
): Promise<Plugin> {
if (!configs.length) return {};

// eslint-disable-next-line no-restricted-globals
if (process.env.BUILD_TARGET !== "node") {
throw new Error("Plugins are only supported on Node.js");
}

const plugins: Plugin[] = [];

for (const conf of configs) {
const path = resolveFrom(cwd, conf.name);
const plugin = await loadPlugin({ ...conf, path, cwd });

plugins.push(plugin);
}

const transformers = excludeFalsyInArray(
plugins.map((p) => p.transformManifest)
);

return {
transformManifest: composeTransforms(transformers)
};
}
7 changes: 6 additions & 1 deletion packages/cli/src/commands/generate/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BaseGenerateArguments } from "./types";
import { fileURLToPath } from "node:url";
import { stdout, execPath, execArgv } from "node:process";
import { createRequire } from "node:module";
import { loadPlugins } from "./plugin";

async function doGenerate({
cwd,
Expand Down Expand Up @@ -58,14 +59,18 @@ export async function handler(options: WorkerOptions) {
}
}

// Load plugins
const plugin = await loadPlugins(args.cwd, config.plugins);

// Generate manifests
const result = await doGenerate({
cwd: args.cwd,
components: config.components,
extensions: config.extensions,
validate: args.validate,
bail: config.bail,
concurrency: config.concurrency
concurrency: config.concurrency,
transform: plugin.transformManifest
});

if (!result.manifests.length) {
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function excludeFalsyInArray<T>(input: (T | undefined | null)[]): T[] {
return input.filter(Boolean) as T[];
}
2 changes: 1 addition & 1 deletion packages/generate/src/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export interface ResolveOptions {
* manifest. If the return value is `undefined` or `null`, the manifest will
* be removed from the result.
*/
transform?(manifest: Manifest): unknown | Promise<unknown>;
transform?(manifest: Manifest): unknown;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4d09600

Please sign in to comment.