Skip to content

Commit

Permalink
Add type synonym for plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
soenkehahn committed Nov 13, 2023
1 parent 23bef4b commit 52f70c8
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 13 deletions.
78 changes: 77 additions & 1 deletion ts/project.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Check } from "./check.ts";
import { Executable } from "./executable.ts";
import { Project } from "./project.ts";
import { Plugin, Project } from "./project.ts";
import { describe, it } from "https://deno.land/[email protected]/testing/bdd.ts";
import * as garn from "./mod.ts";
import * as nix from "./nix.ts";
import { assertStdout, assertSuccess, runExecutable } from "./testUtils.ts";
import { Package } from "./mod.ts";

const assertTypeIsCheck = (_c: Check) => {};
const assertTypeIsExecutable = (_e: Executable) => {};
const assertTypeIsPackage = (_p: Package) => {};

const _testTypeCheckingOfAddCheck = (project: Project) => {
const p = project
Expand Down Expand Up @@ -133,4 +135,78 @@ describe("Project.add", () => {
assertSuccess(output);
assertStdout(output, "bar\n");
});

it("provides a nice type synonym for plugins that add a field", () => {
const plugin: Plugin<object, { addedField: garn.Package }> = (p) => ({
...p,
addedField: garn.build``,
});
const project = garn
.mkProject(
{ description: "", defaultEnvironment: garn.emptyEnvironment },
{},
)
.addExecutable("foo", "")
.add(plugin);
assertTypeIsExecutable(project.foo);
assertTypeIsPackage(project.addedField);
});

it("provides a nice type synonym for plugins that add multiple fields", () => {
const plugin: Plugin<object, { one: garn.Package; two: garn.Check }> = (
p,
) => ({
...p,
one: garn.build``,
two: garn.check(""),
});
const project = garn
.mkProject(
{ description: "", defaultEnvironment: garn.emptyEnvironment },
{},
)
.addExecutable("foo", "")
.add(plugin);
assertTypeIsExecutable(project.foo);
assertTypeIsPackage(project.one);
assertTypeIsCheck(project.two);
});

it("provides a nice interface for plugins that depend on a non-standard field", () => {
const plugin: Plugin<{ dep: Package }, { addedField: Executable }> = (
p,
) => ({
...p,
addedField: garn.shell`${p.dep}/bin/whatever`,
});
const project = garn
.mkProject(
{ description: "", defaultEnvironment: garn.emptyEnvironment },
{ dep: garn.build`` },
)
.addExecutable("foo", "")
.add(plugin);
assertTypeIsExecutable(project.foo);
assertTypeIsExecutable(project.addedField);
// @ts-expect-error - `dep` is missing
() => garn.mkProject({ description: "" }, {}).add(plugin);
});

it("allows to overwrite fields", () => {
const plugin: Plugin<object, { field: Package }> = (p) => ({
...p,
field: garn.build``,
});
const project = garn
.mkProject(
{ description: "", defaultEnvironment: garn.emptyEnvironment },
{ field: garn.shell("") },
)
.addExecutable("foo", "")
.add(plugin);
assertTypeIsExecutable(project.foo);
// @ts-expect-error - should not be an `Executable` anymore
assertTypeIsExecutable(project.field);
assertTypeIsPackage(project.field);
});
});
26 changes: 14 additions & 12 deletions ts/project.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import "./internal/registerInternalLib.ts";

import { Check, mkCheck } from "./check.ts";
import { Environment, emptyEnvironment } from "./environment.ts";
import { Environment } from "./environment.ts";
import { Executable, mkShellExecutable } from "./executable.ts";
import { hasTag } from "./internal/utils.ts";
import { NixStrLitInterpolatable } from "./nix.ts";
import { Package, mkShellPackage } from "./package.ts";
import { mkShellPackage, Package } from "./package.ts";
import { markAsMayNotExport } from "./internal/may_not_export.ts";

/**
Expand All @@ -15,13 +15,15 @@ import { markAsMayNotExport } from "./internal/may_not_export.ts";
*/
export type Project = ProjectHelpers & ProjectData;

type ProjectData = {
export type ProjectData = {
tag: "project";
description: string;
defaultEnvironment?: Environment;
defaultExecutable?: Executable;
};

export type Plugin<Input, Output> = (project: Input) => Output;

type ProjectHelpers = {
/**
* Returns a new Project with the provided devtools added to the default
Expand Down Expand Up @@ -78,10 +80,10 @@ type ProjectHelpers = {
* .add(self => self.addExecutable("codegen")`${self.mainPackage}/bin/codegen`)
* ```
*/
add<T extends ProjectData, U extends ProjectData>(
this: T,
fn: (p: T) => U,
): U;
add<Input extends ProjectData, Output>(
this: Input,
fn: Plugin<Input, Output>,
): Omit<Input, keyof Output> & Output;

/**
* Adds an `Executable` with the given name to the Project
Expand Down Expand Up @@ -215,11 +217,11 @@ const proxyEnvironmentHelpers = (): ProjectHelpers => ({
return mkShellPackage(defaultEnvironment, s, ...args);
},

add<T extends ProjectData, U extends ProjectData>(
this: T,
fn: (p: T) => U,
): U {
return fn(this);
add<Input extends ProjectData, Output>(
this: Input,
fn: Plugin<Input, Output>,
): Omit<Input, keyof Output> & Output {
return { ...this, ...fn(this) };
},

addExecutable<T extends ProjectData, Name extends string>(
Expand Down

0 comments on commit 52f70c8

Please sign in to comment.