Skip to content

Commit

Permalink
Merge pull request #16 from vnphanquang/alter-build-strategy
Browse files Browse the repository at this point in the history
Proposal: Declarative Build Strategy with Mustache Template
  • Loading branch information
11bit authored Dec 20, 2024
2 parents 877ebea + 6091caf commit d912338
Show file tree
Hide file tree
Showing 43 changed files with 556 additions and 590 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true
max_line_length = 100

[*.yaml]
indent_style = space

1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
- main
paths:
- "scripts/**/*"
- "tests/**/*"
- ".github/workflows/tests.yaml"
pull_request:
branches:
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
## 1.3.0 (2024-11-25)
# Changelog

## 1.4.0

### Minor Changes

- [#14](https://github.com/evilmartians/harmony/pull/14) [`d3f6327`](https://github.com/evilmartians/harmony/commit/d3f6327d0113be26d1c3cc0468987700784726fe) Thanks [@vnphanquang](https://github.com/vnphanquang)! - set pacakge as ESM-first, prioritize ESM exports as `.js`, update CommonJS exports as `.cjs`, fix [publint](https://publint.dev/@evilmartians/[email protected]) recommendations

## 1.3.0 (2024-11-25)

- Add support for Tailwind v4

## 1.2.0 (2023-11-25)
Expand Down
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"tasks": {
"build": "deno run --allow-read --allow-write scripts/build.ts",
"publint": "deno run --allow-read --allow-env npm:publint",
"test": "deno test --allow-read --allow-write",
"publint": "deno lint dist && deno check dist && deno run --allow-read --allow-env npm:publint",
"test": "deno test --allow-read --allow-write --watch",
"update-snapshots": "deno test --allow-read --allow-write -- --update",
"changesets": "deno run -A npm:@changesets/cli",
"ci:test": "deno test -A --clean --coverage=coverage --junit-path=coverage/junit.xml",
Expand All @@ -20,6 +20,7 @@
},
"nodeModulesDir": "auto",
"imports": {
"@lambdalisue/sandbox": "jsr:@lambdalisue/sandbox@^2.0.1",
"@std/assert": "jsr:@std/assert@^1.0.8",
"@std/path": "jsr:@std/path@^1.0.8",
"@std/testing": "jsr:@std/testing@^1.0.5"
Expand Down
11 changes: 11 additions & 0 deletions deno.lock

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.10",
"mustache": "^4.2.0",
"publint": "^0.2.12",
"zod": "^3.23.8"
},
Expand Down
107 changes: 8 additions & 99 deletions scripts/build.ts
Original file line number Diff line number Diff line change
@@ -1,102 +1,11 @@
import * as z from "zod";
import * as path from "@std/path";
import { buildTailwindPalette } from "./targets/tailwind.ts";
import { buildBasicPalette } from "./targets/base.ts";
import { ExportTarget, PaletteWithFallback } from "./types.ts";
import { buildCssVars } from "./targets/cssVariables.ts";
import { buildTailwindv4Palette } from "./targets/tailwind-v4.ts";
import { build } from "./builder.ts";
import base from "./targets/base/base.config.ts";
import css from "./targets/css/css.config.ts";
import tailwind from "./targets/tailwind/tailwind.config.ts";
import source from "../source.json" with { type: "json" };

//
// Config
//
const SOURCE_FILE = "./source.json";
const DIST_DIR = path.join(Deno.cwd(), "dist");
const EXPORT_TARGETS = [
{
targetDir: "base",
target: buildBasicPalette,
},
{
targetDir: "tailwind",
target: buildTailwindPalette,
},
{
targetDir: "tailwind",
target: buildTailwindv4Palette,
},
{
targetDir: "css",
target: buildCssVars,
},
];
const configs = [base, css, tailwind];
const dir = path.join(Deno.cwd(), "dist");

//
// SOURCE_FILE schema
//
const SourceSchema = z.record(
z.string().regex(/\w+\/\d+/).toLowerCase().describe(
'Color name and shade. Example: "red/500"',
),
z.object({
$oklch: z.string().describe(
'Color in Oklch format. Example: "oklch(98.83% 0.005 20)"',
),
$srgbFallback: z.string().describe(
'Fallback color in sRGB format. Example: "#ffffff"',
),
}),
);

//
// Main
//
await createDistDir(DIST_DIR);
const palette = await loadPalette(SOURCE_FILE);
await Promise.all(
EXPORT_TARGETS.map(({ targetDir, target }) =>
runExportTarget(path.join(DIST_DIR, targetDir), target, palette)
),
);

//
// Helper functions
//
async function loadPalette(fileName: string): Promise<PaletteWithFallback> {
const content = await Deno.readTextFile(fileName);
const sourcePalette = SourceSchema.parse(JSON.parse(content.toString()));

const result: PaletteWithFallback = {};

for (const [key, value] of Object.entries(sourcePalette)) {
const [name, shade] = key.split("/") as [string, string];
if (!result[name]) {
result[name] = {};
}
result[name][shade] = {
oklch: value.$oklch,
rgbFallback: value.$srgbFallback,
};
}

return result;
}

async function runExportTarget(
targetDir: string,
target: ExportTarget,
palette: PaletteWithFallback,
) {
await Deno.mkdir(targetDir, { recursive: true });
await target({ targetDir, palette });
}

async function createDistDir(dir: string) {
try {
await Deno.remove(dir, { recursive: true });
} catch (err) {
if (!(err instanceof Deno.errors.NotFound)) {
throw err;
}
}
await Deno.mkdir(dir);
}
await build(source, dir, configs);
27 changes: 27 additions & 0 deletions scripts/builder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { stub } from "@std/testing/mock";
import { assert, assertRejects } from "@std/assert";
import { sandbox } from "@lambdalisue/sandbox";

import { testPaletteSource } from "../tests/utils.ts";
import base from "./targets/base/base.config.ts";
import { build } from "./builder.ts";

Deno.test("should abort and clean up if Deno.remove throws unexpect error", async () => {
const errorMessage = "some Deno.remove error";
const stubbed = stub(Deno, "remove", () => {
throw new Error(errorMessage);
});

const sbox = await sandbox();
try {
await build(testPaletteSource, sbox.path, [base]);
} catch (e) {
assert(e instanceof Error);
assert(e.message === errorMessage);
} finally {
stubbed.restore();
await sbox[Symbol.asyncDispose]();
}
assert(stubbed.calls.length === 1);
await assertRejects(() => Deno.lstat(sbox.path));
});
80 changes: 80 additions & 0 deletions scripts/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as z from "zod";
import { BuildConfig, PaletteWithFallback } from "./types.ts";
import mustache from "mustache";
import * as path from "@std/path";

//
// SOURCE_FILE schema
//
const SourceSchema = z.record(
z.string().regex(/\w+\/\d+/).toLowerCase().describe(
'Color name and shade. Example: "red/500"',
),
z.object({
$oklch: z.string().describe(
'Color in Oklch format. Example: "oklch(98.83% 0.005 20)"',
),
$srgbFallback: z.string().describe(
'Fallback color in sRGB format. Example: "#ffffff"',
),
}),
);

export type PaletteSource = z.infer<typeof SourceSchema>;

export function defineBuildConfig<C extends BuildConfig>(config: C): C {
return config;
}

export async function build(
source: PaletteSource,
dir: string,
configs: BuildConfig[],
): Promise<string[]> {
// STEP 1: parse palette from source
const parsed = SourceSchema.parse(source);

// STEP 2: prepare palette into disgestive array for easier processing during build
const map = new Map<string, PaletteWithFallback[number]>();
for (const [key, value] of Object.entries(parsed)) {
const [colorName, shadeName] = key.split("/");
let color = map.get(colorName);
if (!color) {
color = { colorName, shades: [] };
map.set(colorName, color);
}
color.shades.push({
shadeName,
oklch: value.$oklch,
srgbFallback: value.$srgbFallback,
});
}
const palette: PaletteWithFallback = Array.from(map.values());

// STEP 3: clean output dir to avoid stale files
try {
await Deno.remove(dir, { recursive: true });
} catch (err) {
if (!(err instanceof Deno.errors.NotFound)) {
throw err;
}
}

// STEP 4: build targets
const filepaths = await Promise.all(configs.flatMap((config) => {
const target = typeof config === "function" ? config(palette) : config;
return target.outputs.map(async (output) => {
const template = await Deno.readTextFile(output.templatePath);
const vars = output.templateVars ?? { palette };
const content = mustache.render(template, vars);
const targetDir = path.join(dir, target.dir);
const targetFile = path.join(targetDir, output.file);
await Deno.mkdir(targetDir, { recursive: true });
await Deno.writeTextFile(targetFile, content);

return targetFile;
});
}));

return filepaths;
}
44 changes: 0 additions & 44 deletions scripts/targets/__snapshots__/base_test.ts.snap

This file was deleted.

Loading

0 comments on commit d912338

Please sign in to comment.