From e475f37e02a9077a997a19c539d9ddcfba299f7b Mon Sep 17 00:00:00 2001 From: Alex David Date: Fri, 10 Nov 2023 14:12:16 -0800 Subject: [PATCH 1/9] Add `Project.add` helper --- examples/frontend-create-react-app/flake.nix | 5 +- examples/frontend-yarn-webpack/flake.nix | 5 +- examples/go-http-backend/flake.nix | 5 +- examples/haskell/flake.nix | 5 +- examples/monorepo/flake.nix | 5 +- examples/npm-project/flake.nix | 5 +- justfile | 4 +- ts/internal/runner.ts | 5 +- ts/project.test.ts | 162 +++++++++++++++++++ ts/project.ts | 23 +++ website/flake.nix | 5 +- 11 files changed, 219 insertions(+), 10 deletions(-) diff --git a/examples/frontend-create-react-app/flake.nix b/examples/frontend-create-react-app/flake.nix index 0e504cd1..34a5186b 100644 --- a/examples/frontend-create-react-app/flake.nix +++ b/examples/frontend-create-react-app/flake.nix @@ -152,7 +152,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "main/start" = { diff --git a/examples/frontend-yarn-webpack/flake.nix b/examples/frontend-yarn-webpack/flake.nix index 42cc7eea..a35e38c5 100644 --- a/examples/frontend-yarn-webpack/flake.nix +++ b/examples/frontend-yarn-webpack/flake.nix @@ -125,7 +125,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "frontend" = { diff --git a/examples/go-http-backend/flake.nix b/examples/go-http-backend/flake.nix index 5a488f1f..2b289b1e 100644 --- a/examples/go-http-backend/flake.nix +++ b/examples/go-http-backend/flake.nix @@ -126,7 +126,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "server/migrate" = { diff --git a/examples/haskell/flake.nix b/examples/haskell/flake.nix index fa42c5a3..fe9c8958 100644 --- a/examples/haskell/flake.nix +++ b/examples/haskell/flake.nix @@ -186,7 +186,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "helloFromHaskell" = { diff --git a/examples/monorepo/flake.nix b/examples/monorepo/flake.nix index be0d43ad..4777eae3 100644 --- a/examples/monorepo/flake.nix +++ b/examples/monorepo/flake.nix @@ -232,7 +232,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "backend/run" = { diff --git a/examples/npm-project/flake.nix b/examples/npm-project/flake.nix index 2b812418..802cd0a5 100644 --- a/examples/npm-project/flake.nix +++ b/examples/npm-project/flake.nix @@ -224,7 +224,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "run" = { diff --git a/justfile b/justfile index 4037fa31..52ef5ee2 100644 --- a/justfile +++ b/justfile @@ -112,8 +112,8 @@ hpack-check: when (oldCabal /= newCabal) $ error "package.yaml has changed, please run hpack" -test-ts: - deno test --allow-write --allow-read --allow-run ts/*.test.ts ts/**/*.test.ts +test-ts *args="": + deno test --check --allow-write --allow-read --allow-run ts/*.test.ts ts/**/*.test.ts {{ args }} test *args="": hpack cabal run --test-show-details=streaming garn:spec -- {{ args }} diff --git a/ts/internal/runner.ts b/ts/internal/runner.ts index 3e01d5b7..211fba4f 100644 --- a/ts/internal/runner.ts +++ b/ts/internal/runner.ts @@ -173,7 +173,10 @@ const formatFlake = ( ${nixAttrSet(shells)} ); apps = forAllSystems (system: let - pkgs = import "\${nixpkgs}" { inherit system; }; + pkgs = import "\${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in ${nixAttrSet(apps)} ); diff --git a/ts/project.test.ts b/ts/project.test.ts index 8119f5b0..1d440c66 100644 --- a/ts/project.test.ts +++ b/ts/project.test.ts @@ -1,6 +1,11 @@ import { Check } from "./check.ts"; import { Executable } from "./executable.ts"; import { Project } from "./project.ts"; +import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; +import { describe, it } from "https://deno.land/std@0.206.0/testing/bdd.ts"; +import * as garn from "./mod.ts"; +import * as nix from "./nix.ts"; +import { nixAttrSet } from "./nix.ts"; const assertTypeIsCheck = (_c: Check) => {}; const assertTypeIsExecutable = (_e: Executable) => {}; @@ -50,3 +55,160 @@ const _testTypeCheckingOfAddExecutableTemplate = (project: Project) => { // @ts-expect-error - shell should be the actual executable now, not the helper p.shell``; }; + +describe("Project.add", () => { + it("allows to add fields with .add", () => { + const project = garn + .mkProject({ description: "" }, {}) + .add((self) => ({ ...self, foo: garn.shell("echo foo") })); + const output = runExecutable(project.foo); + assertSuccess(output); + assertStdout(output, "foo\n"); + }); + + it("allows to add fields while referencing the Project", () => { + const project = garn + .mkProject( + { description: "", defaultEnvironment: garn.emptyEnvironment }, + {}, + ) + .withDevTools([garn.mkPackage(nix.nixRaw`pkgs.hello`, "")]) + .add((self) => ({ ...self, foo: self.shell("hello") })); + const output = runExecutable(project.foo); + assertSuccess(output); + assertStdout(output, "Hello, world!\n"); + }); + + it("allows to splice in existing fields", () => { + const project = garn + .mkProject( + { description: "", defaultEnvironment: garn.emptyEnvironment }, + { + package: garn.build` + mkdir -p $out/bin + echo 'echo main executable' > $out/bin/main + chmod +x $out/bin/main + `, + }, + ) + .add((self) => ({ ...self, foo: self.shell`${self.package}/bin/main` })); + const output = runExecutable(project.foo); + assertSuccess(output); + assertStdout(output, "main executable\n"); + }); + + it("allows to bundle multiple changes into plugins", () => { + const plugin =

(p: P) => + p.addExecutable("foo", "echo foo").addExecutable("bar", "echo bar"); + const project = garn + .mkProject( + { description: "", defaultEnvironment: garn.emptyEnvironment }, + {}, + ) + .add(plugin); + let output = runExecutable(project.foo); + assertSuccess(output); + assertStdout(output, "foo\n"); + output = runExecutable(project.bar); + assertSuccess(output); + assertStdout(output, "bar\n"); + }); + + it("allows to write plugins with parameters", () => { + const plugin = +

(config: { a: string; b: string }) => + (p: P) => + p + .addExecutable("a", `echo ${config.a}`) + .addExecutable("b", `echo ${config.b}`); + const project = garn + .mkProject( + { description: "", defaultEnvironment: garn.emptyEnvironment }, + {}, + ) + .add(plugin({ a: "foo", b: "bar" })); + let output = runExecutable(project.a); + assertSuccess(output); + assertStdout(output, "foo\n"); + output = runExecutable(project.b); + assertSuccess(output); + assertStdout(output, "bar\n"); + }); +}); + +interface Output { + exitCode: number; + stdout: string; + stderr: string; +} + +const printOutput = (output: Output) => { + console.error(` + exitcode: ${output.exitCode} + stdout: + ${output.stdout} + stderr: + ${output.stderr} + `); +}; + +const assertSuccess = (output: Output) => { + try { + assertEquals(output.exitCode, 0); + } catch (e) { + printOutput(output); + throw e; + } +}; + +const assertStdout = (output: Output, expected: string) => { + try { + assertEquals(output.stdout, expected); + } catch (e) { + printOutput(output); + throw e; + } +}; + +const assertStderr = (output: Output, expected: string) => { + try { + assertEquals(output.stderr, expected); + } catch (e) { + printOutput(output); + throw e; + } +}; + +const runExecutable = (executable: garn.Executable): Output => { + const tempDir = Deno.makeTempDirSync({ prefix: "garn-test" }); + const nixpkgsInput = nix.nixFlakeDep("nixpkgs-repo", { + url: "github:NixOS/nixpkgs/6fc7203e423bbf1c8f84cccf1c4818d097612566", + }); + const flakeFile = nix.renderFlakeFile( + nixAttrSet({ + apps: nixAttrSet({ + "x86_64-linux": nixAttrSet({ + default: nixAttrSet({ + type: nix.nixStrLit`app`, + program: nix.nixRaw` + let pkgs = import ${nixpkgsInput} { + config.allowUnfree = true; + system = "x86_64-linux"; + }; + in ${executable.nixExpression} + `, + }), + }), + }), + }), + ); + Deno.writeTextFileSync(`${tempDir}/flake.nix`, flakeFile); + const output = new Deno.Command("nix", { + args: ["run", tempDir], + }).outputSync(); + return { + exitCode: output.code, + stdout: new TextDecoder().decode(output.stdout), + stderr: new TextDecoder().decode(output.stderr), + }; +}; diff --git a/ts/project.ts b/ts/project.ts index 6e15adb4..d24b20ca 100644 --- a/ts/project.ts +++ b/ts/project.ts @@ -67,6 +67,22 @@ type ProjectHelpers = { ..._args: Array ): Package; + /** + * Modify the given project. + * + * This can be useful for modifying a project in a method chaining style while + * being able to reference that project. For example: + * + * ```typescript + * export const myProject = garn.mkHaskellProject(...) + * .add(self => self.addExecutable("codegen")`${self.mainPackage}/bin/codegen`) + * ``` + */ + add( + this: T, + fn: (p: T) => U, + ): U; + /** * Adds an `Executable` with the given name to the Project * @@ -199,6 +215,13 @@ const proxyEnvironmentHelpers = (): ProjectHelpers => ({ return mkShellPackage(defaultEnvironment, s, ...args); }, + add( + this: T, + fn: (p: T) => U, + ): U { + return fn(this); + }, + addExecutable( this: T, name: Name, diff --git a/website/flake.nix b/website/flake.nix index b6d66447..c43fdb3f 100644 --- a/website/flake.nix +++ b/website/flake.nix @@ -239,7 +239,10 @@ ); apps = forAllSystems (system: let - pkgs = import "${nixpkgs}" { inherit system; }; + pkgs = import "${nixpkgs}" { + config.allowUnfree = true; + inherit system; + }; in { "build" = { From 23bef4beb9b022fff9dd0e1209b894c4ae5fe08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Fri, 10 Nov 2023 18:46:42 -0500 Subject: [PATCH 2/9] Move test helpers into their own module --- ts/project.test.ts | 80 +-------------------------------------------- ts/testUtils.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 79 deletions(-) create mode 100644 ts/testUtils.ts diff --git a/ts/project.test.ts b/ts/project.test.ts index 1d440c66..d9cd67d9 100644 --- a/ts/project.test.ts +++ b/ts/project.test.ts @@ -1,11 +1,10 @@ import { Check } from "./check.ts"; import { Executable } from "./executable.ts"; import { Project } from "./project.ts"; -import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; import { describe, it } from "https://deno.land/std@0.206.0/testing/bdd.ts"; import * as garn from "./mod.ts"; import * as nix from "./nix.ts"; -import { nixAttrSet } from "./nix.ts"; +import { assertStdout, assertSuccess, runExecutable } from "./testUtils.ts"; const assertTypeIsCheck = (_c: Check) => {}; const assertTypeIsExecutable = (_e: Executable) => {}; @@ -135,80 +134,3 @@ describe("Project.add", () => { assertStdout(output, "bar\n"); }); }); - -interface Output { - exitCode: number; - stdout: string; - stderr: string; -} - -const printOutput = (output: Output) => { - console.error(` - exitcode: ${output.exitCode} - stdout: - ${output.stdout} - stderr: - ${output.stderr} - `); -}; - -const assertSuccess = (output: Output) => { - try { - assertEquals(output.exitCode, 0); - } catch (e) { - printOutput(output); - throw e; - } -}; - -const assertStdout = (output: Output, expected: string) => { - try { - assertEquals(output.stdout, expected); - } catch (e) { - printOutput(output); - throw e; - } -}; - -const assertStderr = (output: Output, expected: string) => { - try { - assertEquals(output.stderr, expected); - } catch (e) { - printOutput(output); - throw e; - } -}; - -const runExecutable = (executable: garn.Executable): Output => { - const tempDir = Deno.makeTempDirSync({ prefix: "garn-test" }); - const nixpkgsInput = nix.nixFlakeDep("nixpkgs-repo", { - url: "github:NixOS/nixpkgs/6fc7203e423bbf1c8f84cccf1c4818d097612566", - }); - const flakeFile = nix.renderFlakeFile( - nixAttrSet({ - apps: nixAttrSet({ - "x86_64-linux": nixAttrSet({ - default: nixAttrSet({ - type: nix.nixStrLit`app`, - program: nix.nixRaw` - let pkgs = import ${nixpkgsInput} { - config.allowUnfree = true; - system = "x86_64-linux"; - }; - in ${executable.nixExpression} - `, - }), - }), - }), - }), - ); - Deno.writeTextFileSync(`${tempDir}/flake.nix`, flakeFile); - const output = new Deno.Command("nix", { - args: ["run", tempDir], - }).outputSync(); - return { - exitCode: output.code, - stdout: new TextDecoder().decode(output.stdout), - stderr: new TextDecoder().decode(output.stderr), - }; -}; diff --git a/ts/testUtils.ts b/ts/testUtils.ts new file mode 100644 index 00000000..6a02faa1 --- /dev/null +++ b/ts/testUtils.ts @@ -0,0 +1,81 @@ +import { assertEquals } from "https://deno.land/std@0.206.0/assert/mod.ts"; +import * as garn from "./mod.ts"; +import * as nix from "./nix.ts"; +import { nixAttrSet } from "./nix.ts"; + +interface Output { + exitCode: number; + stdout: string; + stderr: string; +} + +const printOutput = (output: Output) => { + console.error(` + exitcode: ${output.exitCode} + stdout: + ${output.stdout} + stderr: + ${output.stderr} + `); +}; + +export const assertSuccess = (output: Output) => { + try { + assertEquals(output.exitCode, 0); + } catch (e) { + printOutput(output); + throw e; + } +}; + +export const assertStdout = (output: Output, expected: string) => { + try { + assertEquals(output.stdout, expected); + } catch (e) { + printOutput(output); + throw e; + } +}; + +export const assertStderr = (output: Output, expected: string) => { + try { + assertEquals(output.stderr, expected); + } catch (e) { + printOutput(output); + throw e; + } +}; + +export const runExecutable = (executable: garn.Executable): Output => { + const tempDir = Deno.makeTempDirSync({ prefix: "garn-test" }); + const nixpkgsInput = nix.nixFlakeDep("nixpkgs-repo", { + url: "github:NixOS/nixpkgs/6fc7203e423bbf1c8f84cccf1c4818d097612566", + }); + const flakeFile = nix.renderFlakeFile( + nixAttrSet({ + apps: nixAttrSet({ + "x86_64-linux": nixAttrSet({ + default: nixAttrSet({ + type: nix.nixStrLit`app`, + program: nix.nixRaw` + let pkgs = import ${nixpkgsInput} { + config.allowUnfree = true; + system = "x86_64-linux"; + }; + in ${executable.nixExpression} + `, + }), + }), + }), + }), + ); + Deno.writeTextFileSync(`${tempDir}/flake.nix`, flakeFile); + const output = new Deno.Command("nix", { + args: ["run", tempDir], + }).outputSync(); + return { + exitCode: output.code, + stdout: new TextDecoder().decode(output.stdout), + stderr: new TextDecoder().decode(output.stderr), + }; +}; From 52f70c81dc778ac17975c4be3dc8abbde4c3cf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Sun, 12 Nov 2023 19:08:02 -0500 Subject: [PATCH 3/9] Add type synonym for plugins --- ts/project.test.ts | 78 +++++++++++++++++++++++++++++++++++++++++++++- ts/project.ts | 26 +++++++++------- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/ts/project.test.ts b/ts/project.test.ts index d9cd67d9..48712a65 100644 --- a/ts/project.test.ts +++ b/ts/project.test.ts @@ -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/std@0.206.0/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 @@ -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 = (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 = ( + 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 = (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); + }); }); diff --git a/ts/project.ts b/ts/project.ts index d24b20ca..301053d0 100644 --- a/ts/project.ts +++ b/ts/project.ts @@ -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"; /** @@ -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 = (project: Input) => Output; + type ProjectHelpers = { /** * Returns a new Project with the provided devtools added to the default @@ -78,10 +80,10 @@ type ProjectHelpers = { * .add(self => self.addExecutable("codegen")`${self.mainPackage}/bin/codegen`) * ``` */ - add( - this: T, - fn: (p: T) => U, - ): U; + add( + this: Input, + fn: Plugin, + ): Omit & Output; /** * Adds an `Executable` with the given name to the Project @@ -215,11 +217,11 @@ const proxyEnvironmentHelpers = (): ProjectHelpers => ({ return mkShellPackage(defaultEnvironment, s, ...args); }, - add( - this: T, - fn: (p: T) => U, - ): U { - return fn(this); + add( + this: Input, + fn: Plugin, + ): Omit & Output { + return { ...this, ...fn(this) }; }, addExecutable( From c2b67184ced47fa47b5aa193723b04854af9fc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Mon, 13 Nov 2023 14:26:57 -0500 Subject: [PATCH 4/9] Provide ProjectData to plugins --- ts/project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/project.ts b/ts/project.ts index 301053d0..1a463cda 100644 --- a/ts/project.ts +++ b/ts/project.ts @@ -22,7 +22,7 @@ export type ProjectData = { defaultExecutable?: Executable; }; -export type Plugin = (project: Input) => Output; +export type Plugin = (project: Input & ProjectData) => Output; type ProjectHelpers = { /** From 27b57484189e9804d632be4603cc0e8928261e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Mon, 13 Nov 2023 14:36:02 -0500 Subject: [PATCH 5/9] Switch order of type parameters and provide default --- ts/project.test.ts | 10 ++++------ ts/project.ts | 25 +++++++++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ts/project.test.ts b/ts/project.test.ts index 48712a65..0cda3166 100644 --- a/ts/project.test.ts +++ b/ts/project.test.ts @@ -137,7 +137,7 @@ describe("Project.add", () => { }); it("provides a nice type synonym for plugins that add a field", () => { - const plugin: Plugin = (p) => ({ + const plugin: Plugin<{ addedField: garn.Package }> = (p) => ({ ...p, addedField: garn.build``, }); @@ -153,9 +153,7 @@ describe("Project.add", () => { }); it("provides a nice type synonym for plugins that add multiple fields", () => { - const plugin: Plugin = ( - p, - ) => ({ + const plugin: Plugin<{ one: garn.Package; two: garn.Check }> = (p) => ({ ...p, one: garn.build``, two: garn.check(""), @@ -173,7 +171,7 @@ describe("Project.add", () => { }); it("provides a nice interface for plugins that depend on a non-standard field", () => { - const plugin: Plugin<{ dep: Package }, { addedField: Executable }> = ( + const plugin: Plugin<{ addedField: Executable }, { dep: Package }> = ( p, ) => ({ ...p, @@ -193,7 +191,7 @@ describe("Project.add", () => { }); it("allows to overwrite fields", () => { - const plugin: Plugin = (p) => ({ + const plugin: Plugin<{ field: Package }> = (p) => ({ ...p, field: garn.build``, }); diff --git a/ts/project.ts b/ts/project.ts index 1a463cda..fd625f5a 100644 --- a/ts/project.ts +++ b/ts/project.ts @@ -22,7 +22,9 @@ export type ProjectData = { defaultExecutable?: Executable; }; -export type Plugin = (project: Input & ProjectData) => Output; +export type Plugin = ( + project: Dependencies & ProjectData, +) => Additions; type ProjectHelpers = { /** @@ -80,10 +82,10 @@ type ProjectHelpers = { * .add(self => self.addExecutable("codegen")`${self.mainPackage}/bin/codegen`) * ``` */ - add( - this: Input, - fn: Plugin, - ): Omit & Output; + add( + this: T, + fn: Plugin, + ): Omit & Additions; /** * Adds an `Executable` with the given name to the Project @@ -217,11 +219,14 @@ const proxyEnvironmentHelpers = (): ProjectHelpers => ({ return mkShellPackage(defaultEnvironment, s, ...args); }, - add( - this: Input, - fn: Plugin, - ): Omit & Output { - return { ...this, ...fn(this) }; + add( + this: T, + fn: Plugin, + ): Omit & Additions { + return { + ...this, + ...fn(this), + }; }, addExecutable( From cb16d3d53adbbdd194a60c99cfa795ec4f1dd08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Mon, 13 Nov 2023 19:02:08 -0500 Subject: [PATCH 6/9] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df62958..6dbc64fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ - Allow to build packages that are nested within projects with `garn build projectName.packageName`. - Allow to build top-level packages with `garn build packageName. - Allow adding packages to projects with `.addPackage("packageName", "{build script writing to $out}")`. +- Add `Project.add`, a function to apply so-called `Plugin`s to a project. This + provides a nice way to bundle up more complex project modifications into a + single declaration. It also allows to use `Plugin`s from other sources, + including third-party libraries. ## v0.0.15 From a515a1ea98f04244068b66aebda150067c40f17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Hahn?= Date: Mon, 13 Nov 2023 19:04:12 -0500 Subject: [PATCH 7/9] Tweak test names --- ts/project.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ts/project.test.ts b/ts/project.test.ts index 0cda3166..a91c7c65 100644 --- a/ts/project.test.ts +++ b/ts/project.test.ts @@ -58,7 +58,7 @@ const _testTypeCheckingOfAddExecutableTemplate = (project: Project) => { }; describe("Project.add", () => { - it("allows to add fields with .add", () => { + it("allows adding fields with .add", () => { const project = garn .mkProject({ description: "" }, {}) .add((self) => ({ ...self, foo: garn.shell("echo foo") })); @@ -67,7 +67,7 @@ describe("Project.add", () => { assertStdout(output, "foo\n"); }); - it("allows to add fields while referencing the Project", () => { + it("allows adding fields while referencing the Project", () => { const project = garn .mkProject( { description: "", defaultEnvironment: garn.emptyEnvironment }, @@ -80,7 +80,7 @@ describe("Project.add", () => { assertStdout(output, "Hello, world!\n"); }); - it("allows to splice in existing fields", () => { + it("allows splicing in existing fields", () => { const project = garn .mkProject( { description: "", defaultEnvironment: garn.emptyEnvironment }, @@ -98,7 +98,7 @@ describe("Project.add", () => { assertStdout(output, "main executable\n"); }); - it("allows to bundle multiple changes into plugins", () => { + it("allows bundling multiple changes into plugins", () => { const plugin =

(p: P) => p.addExecutable("foo", "echo foo").addExecutable("bar", "echo bar"); const project = garn @@ -115,7 +115,7 @@ describe("Project.add", () => { assertStdout(output, "bar\n"); }); - it("allows to write plugins with parameters", () => { + it("allows writing plugins with parameters", () => { const plugin =

(config: { a: string; b: string }) => (p: P) => @@ -190,7 +190,7 @@ describe("Project.add", () => { () => garn.mkProject({ description: "" }, {}).add(plugin); }); - it("allows to overwrite fields", () => { + it("allows overwriting fields", () => { const plugin: Plugin<{ field: Package }> = (p) => ({ ...p, field: garn.build``, From d0ca07c5b92323d01c3f5f019ba7df5b244343a1 Mon Sep 17 00:00:00 2001 From: Alex David Date: Mon, 13 Nov 2023 17:32:47 -0800 Subject: [PATCH 8/9] Use type over interface for testUtils::Output --- ts/testUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/testUtils.ts b/ts/testUtils.ts index 6a02faa1..14a97f20 100644 --- a/ts/testUtils.ts +++ b/ts/testUtils.ts @@ -3,11 +3,11 @@ import * as garn from "./mod.ts"; import * as nix from "./nix.ts"; import { nixAttrSet } from "./nix.ts"; -interface Output { +type Output = { exitCode: number; stdout: string; stderr: string; -} +}; const printOutput = (output: Output) => { console.error(` From 552851a4d362fb470a784168179cc9275b63d7ae Mon Sep 17 00:00:00 2001 From: Alex David Date: Mon, 13 Nov 2023 20:13:00 -0800 Subject: [PATCH 9/9] Remove unneeded splats --- ts/project.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ts/project.test.ts b/ts/project.test.ts index a91c7c65..4b024ab4 100644 --- a/ts/project.test.ts +++ b/ts/project.test.ts @@ -138,7 +138,6 @@ describe("Project.add", () => { it("provides a nice type synonym for plugins that add a field", () => { const plugin: Plugin<{ addedField: garn.Package }> = (p) => ({ - ...p, addedField: garn.build``, }); const project = garn @@ -154,7 +153,6 @@ describe("Project.add", () => { it("provides a nice type synonym for plugins that add multiple fields", () => { const plugin: Plugin<{ one: garn.Package; two: garn.Check }> = (p) => ({ - ...p, one: garn.build``, two: garn.check(""), }); @@ -174,7 +172,6 @@ describe("Project.add", () => { const plugin: Plugin<{ addedField: Executable }, { dep: Package }> = ( p, ) => ({ - ...p, addedField: garn.shell`${p.dep}/bin/whatever`, }); const project = garn @@ -192,7 +189,6 @@ describe("Project.add", () => { it("allows overwriting fields", () => { const plugin: Plugin<{ field: Package }> = (p) => ({ - ...p, field: garn.build``, }); const project = garn