✨(tokens) add JS generator and re-organize the repo
Added JsGenerator which to implied to reorganize a bit the repo in order to extract generator-specific test into specific standalone files.
This commit is contained in:
@@ -16,8 +16,8 @@
|
||||
"lint": "eslint . 'src/**/*.{ts,tsx}'",
|
||||
"dev": "nodemon --watch 'src/bin' --ext '*' --exec 'yarn build'",
|
||||
"build": "tsc -p tsconfig.json && tsc-alias && cp src/bin/cunningham.dist.js dist/bin && chmod +x dist/bin/Main.js && yarn build-default-theme",
|
||||
"build-default-theme": "./dist/bin/Main.js -o dist -s html",
|
||||
"test": "FORCE_COLOR=1 jest --verbose src/bin/tests"
|
||||
"build-default-theme": "./dist/bin/Main.js -o dist -s html -g css,js",
|
||||
"test": "FORCE_COLOR=1 jest --verbose src/bin"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "4.1.2",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
export default {
|
||||
configurationFilenames: ["cunningham.js", "cunningham.cjs"],
|
||||
tokenFilenames: {
|
||||
css: "cunningham-tokens.css",
|
||||
js: "cunningham-tokens.js",
|
||||
},
|
||||
sass: {
|
||||
varSeparator: "--",
|
||||
varPrefix: "c--",
|
||||
tokenFilenameCss: "cunningham-tokens.css",
|
||||
},
|
||||
};
|
||||
|
||||
38
packages/tokens/src/bin/Generators/CssGenerator.spec.ts
Normal file
38
packages/tokens/src/bin/Generators/CssGenerator.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import Config from "Config";
|
||||
import { run } from "ThemeGenerator";
|
||||
import { cleanup } from "tests/Utils";
|
||||
|
||||
jest.mock("../Paths", () => ({
|
||||
workPath: () => __dirname,
|
||||
}));
|
||||
|
||||
describe("CssGenerator", () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
cleanup(__dirname);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(__dirname);
|
||||
});
|
||||
|
||||
const testSelector = async (opt: string) => {
|
||||
const cssTokensFile = path.join(__dirname, Config.tokenFilenames.css);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||
await run(["", "", "-g", "css", opt, "html"]);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`html {
|
||||
\t--c--colors--primary: #055FD2;
|
||||
\t--c--colors--secondary: #DA0000;
|
||||
}`);
|
||||
};
|
||||
|
||||
it("Runs with -s options.", async () => {
|
||||
await testSelector("-s");
|
||||
});
|
||||
it("Runs with --selector options.", async () => {
|
||||
await testSelector("--selector");
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,10 @@
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import chalk from "chalk";
|
||||
import { flatify } from "Utils/Flatify";
|
||||
import Config from "Config";
|
||||
import { Generator } from "Generators/index";
|
||||
import { put } from "Utils/Files";
|
||||
|
||||
export const cssGenerator = async (
|
||||
tokens: any,
|
||||
opts: { path: string; selector: string }
|
||||
) => {
|
||||
export const cssGenerator: Generator = async (tokens, opts) => {
|
||||
const flatTokens = flatify(tokens, Config.sass.varSeparator);
|
||||
const cssVars = Object.keys(flatTokens).reduce((acc, token) => {
|
||||
return (
|
||||
@@ -15,12 +12,7 @@ export const cssGenerator = async (
|
||||
);
|
||||
}, "");
|
||||
const cssContent = `${opts.selector} {\n${cssVars}}`;
|
||||
const dest = path.join(opts.path, Config.tokenFilenames.css);
|
||||
|
||||
const dest = path.join(opts.path, Config.sass.tokenFilenameCss);
|
||||
console.log("Generating tokens file to " + dest + " ...");
|
||||
if (!fs.existsSync(opts.path)) {
|
||||
fs.mkdirSync(opts.path);
|
||||
}
|
||||
fs.writeFileSync(dest, cssContent);
|
||||
console.log(chalk.bgGreen(chalk.white("File generated successfully.")));
|
||||
put(dest, cssContent);
|
||||
};
|
||||
|
||||
30
packages/tokens/src/bin/Generators/JsGenerator.spec.ts
Normal file
30
packages/tokens/src/bin/Generators/JsGenerator.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import Config from "Config";
|
||||
import { run } from "ThemeGenerator";
|
||||
import { cleanup } from "tests/Utils";
|
||||
|
||||
jest.mock("../Paths", () => ({
|
||||
workPath: () => __dirname,
|
||||
}));
|
||||
|
||||
describe("JsGenerator", () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
cleanup(__dirname);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(__dirname);
|
||||
});
|
||||
|
||||
it("generates valid JS file.", async () => {
|
||||
const tokensFile = path.join(__dirname, Config.tokenFilenames.js);
|
||||
expect(fs.existsSync(tokensFile)).toEqual(false);
|
||||
await run(["", "", "-g", "js"]);
|
||||
expect(fs.existsSync(tokensFile)).toEqual(true);
|
||||
expect(fs.readFileSync(tokensFile).toString()).toEqual(
|
||||
`export const tokens = {"colors":{"primary":"#055FD2","secondary":"#DA0000"}};`
|
||||
);
|
||||
});
|
||||
});
|
||||
15
packages/tokens/src/bin/Generators/JsGenerator.ts
Normal file
15
packages/tokens/src/bin/Generators/JsGenerator.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import path from "path";
|
||||
import { Generator } from "Generators/index";
|
||||
import Config from "Config";
|
||||
import { put } from "Utils/Files";
|
||||
import { Tokens } from "TokensGenerator";
|
||||
|
||||
export const jsGenerator: Generator = async (tokens, opts) => {
|
||||
const dest = path.join(opts.path, Config.tokenFilename + ".js");
|
||||
put(dest, await jsGeneratorContent(tokens));
|
||||
};
|
||||
|
||||
export const jsGeneratorContent = async (tokens: Tokens) => {
|
||||
const variable = JSON.stringify(tokens);
|
||||
return `export const tokens = ${variable};`;
|
||||
};
|
||||
13
packages/tokens/src/bin/Generators/index.ts
Normal file
13
packages/tokens/src/bin/Generators/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { cssGenerator } from "Generators/CssGenerator";
|
||||
import { jsGenerator } from "Generators/JsGenerator";
|
||||
import { Tokens } from "TokensGenerator";
|
||||
|
||||
export type Generator = (
|
||||
tokens: Tokens,
|
||||
opts: { path: string; selector: string }
|
||||
) => Promise<void>;
|
||||
|
||||
export const Generators: Record<string, Generator> = {
|
||||
css: cssGenerator,
|
||||
js: jsGenerator,
|
||||
};
|
||||
@@ -1,19 +1,27 @@
|
||||
import { program } from "commander";
|
||||
import chalk from "chalk";
|
||||
import figlet from "figlet";
|
||||
import { cssGenerator } from "Generators/CssGenerator";
|
||||
import { getConfig } from "ConfigLoader";
|
||||
import { tokensGenerator } from "TokensGenerator";
|
||||
import { workPath } from "Paths";
|
||||
import { Generators } from "Generators";
|
||||
|
||||
export const buildTheme = async () => {
|
||||
const options = program.opts();
|
||||
const config = await getConfig();
|
||||
const tokens = tokensGenerator(config);
|
||||
await cssGenerator(tokens, {
|
||||
path: options.output,
|
||||
selector: options.selector,
|
||||
});
|
||||
const { generators } = options;
|
||||
await Promise.allSettled(
|
||||
generators.map((generator: string) => {
|
||||
if (!Generators[generator]) {
|
||||
throw new Error('The generator "' + generator + '" does not exist.');
|
||||
}
|
||||
return Generators[generator](tokens, {
|
||||
path: options.output,
|
||||
selector: options.selector,
|
||||
});
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const run = async (args: string[]) => {
|
||||
@@ -21,6 +29,10 @@ export const run = async (args: string[]) => {
|
||||
chalk.red(figlet.textSync("Cunningham", { horizontalLayout: "full" }))
|
||||
);
|
||||
|
||||
const commaSeparatedList = (value: string) => {
|
||||
return value.split(",");
|
||||
};
|
||||
|
||||
program
|
||||
.description("Cunningham's CLI tool.")
|
||||
.option(
|
||||
@@ -33,6 +45,11 @@ export const run = async (args: string[]) => {
|
||||
"Specify the css root selector element.",
|
||||
":root"
|
||||
)
|
||||
.requiredOption(
|
||||
"-g, --generators <generators>",
|
||||
"Specify the generators to use.",
|
||||
commaSeparatedList
|
||||
)
|
||||
.parse(args);
|
||||
|
||||
await buildTheme();
|
||||
|
||||
@@ -8,7 +8,9 @@ export interface ConfigShape {
|
||||
theme: ThemeShape;
|
||||
}
|
||||
|
||||
export const tokensGenerator = (config: ConfigShape) => {
|
||||
export type Tokens = Record<PropertyKey, unknown>;
|
||||
|
||||
export const tokensGenerator = (config: ConfigShape): Tokens => {
|
||||
return {
|
||||
colors: { ...config.theme.colors },
|
||||
};
|
||||
|
||||
14
packages/tokens/src/bin/Utils/Files.ts
Normal file
14
packages/tokens/src/bin/Utils/Files.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import fs from "fs";
|
||||
import chalk from "chalk";
|
||||
|
||||
export const put = (path: string, content: string) => {
|
||||
console.log("Generating tokens file to " + path + " ...");
|
||||
const dir = path.substring(0, path.lastIndexOf("/"));
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
fs.writeFileSync(path, content);
|
||||
console.log(
|
||||
chalk.bgGreen(chalk.white("File " + path + " generated successfully."))
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { run } from "ThemeGenerator";
|
||||
import { cleanup } from "tests/Utils";
|
||||
import Config from "../Config";
|
||||
|
||||
jest.mock("../Paths", () => ({
|
||||
@@ -8,43 +9,25 @@ jest.mock("../Paths", () => ({
|
||||
}));
|
||||
|
||||
/**
|
||||
* Empty the current directory from generated tokens file and local
|
||||
* config to start with an predictable environment.
|
||||
* Test written here are supposed to be general ones and not specific to any generator.
|
||||
*
|
||||
* But as we need at least one generator to execute the bin we need to choose one to use by default,
|
||||
* that's why we use the css generator.
|
||||
*/
|
||||
const cleanup = () => {
|
||||
const filePath = path.join(__dirname, Config.sass.tokenFilenameCss);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
|
||||
const localConfigurationFile = path.join(
|
||||
__dirname,
|
||||
Config.configurationFilenames[0]
|
||||
);
|
||||
if (fs.existsSync(localConfigurationFile)) {
|
||||
fs.unlinkSync(localConfigurationFile);
|
||||
}
|
||||
|
||||
const outputPath = path.join(__dirname, "output");
|
||||
if (fs.existsSync(outputPath)) {
|
||||
fs.rmSync(outputPath, { recursive: true });
|
||||
}
|
||||
};
|
||||
|
||||
describe("Cunningham Bin", () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, "log").mockImplementation(() => {});
|
||||
cleanup();
|
||||
cleanup(__dirname);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
cleanup(__dirname);
|
||||
});
|
||||
|
||||
it("Runs without existing config file with default values.", async () => {
|
||||
const cssTokensFile = path.join(__dirname, Config.sass.tokenFilenameCss);
|
||||
const cssTokensFile = path.join(__dirname, Config.tokenFilenames.css);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||
await run([]);
|
||||
await run(["", "", "-g", "css"]);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
||||
\t--c--colors--primary: #055FD2;
|
||||
@@ -59,7 +42,7 @@ describe("Cunningham Bin", () => {
|
||||
);
|
||||
expect(fs.existsSync(localConfigurationFile)).toEqual(false);
|
||||
|
||||
const cssTokensFile = path.join(__dirname, Config.sass.tokenFilenameCss);
|
||||
const cssTokensFile = path.join(__dirname, Config.tokenFilenames.css);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||
|
||||
fs.copyFileSync(
|
||||
@@ -68,7 +51,7 @@ describe("Cunningham Bin", () => {
|
||||
);
|
||||
expect(fs.existsSync(localConfigurationFile)).toEqual(true);
|
||||
|
||||
await run([]);
|
||||
await run(["", "", "-g", "css"]);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
||||
\t--c--colors--primary: AntiqueWhite;
|
||||
@@ -78,9 +61,9 @@ describe("Cunningham Bin", () => {
|
||||
|
||||
const testOutput = async (opt: string) => {
|
||||
const outputDir = path.join(__dirname, "output");
|
||||
const cssTokensFile = path.join(outputDir, Config.sass.tokenFilenameCss);
|
||||
const cssTokensFile = path.join(outputDir, Config.tokenFilenames.css);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||
await run(["", "", opt, outputDir]);
|
||||
await run(["", "", "-g", "css", opt, outputDir]);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
||||
\t--c--colors--primary: #055FD2;
|
||||
@@ -95,22 +78,4 @@ describe("Cunningham Bin", () => {
|
||||
it("Runs with --output options.", async () => {
|
||||
await testOutput("--output");
|
||||
});
|
||||
|
||||
const testSelector = async (opt: string) => {
|
||||
const cssTokensFile = path.join(__dirname, Config.sass.tokenFilenameCss);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||
await run(["", "", opt, "html"]);
|
||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`html {
|
||||
\t--c--colors--primary: #055FD2;
|
||||
\t--c--colors--secondary: #DA0000;
|
||||
}`);
|
||||
};
|
||||
|
||||
it("Runs with -s options.", async () => {
|
||||
await testSelector("-s");
|
||||
});
|
||||
it("Runs with --selector options.", async () => {
|
||||
await testSelector("--selector");
|
||||
});
|
||||
});
|
||||
|
||||
29
packages/tokens/src/bin/tests/Utils.ts
Normal file
29
packages/tokens/src/bin/tests/Utils.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import Config from "Config";
|
||||
|
||||
/**
|
||||
* Empty the current directory from generated tokens file and local
|
||||
* config to start with an predictable environment.
|
||||
*/
|
||||
export const cleanup = (dir: string) => {
|
||||
Object.entries(Config.tokenFilenames).forEach(([key, filename]) => {
|
||||
const filePath = path.join(dir, filename);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
});
|
||||
|
||||
const localConfigurationFile = path.join(
|
||||
dir,
|
||||
Config.configurationFilenames[0]
|
||||
);
|
||||
if (fs.existsSync(localConfigurationFile)) {
|
||||
fs.unlinkSync(localConfigurationFile);
|
||||
}
|
||||
|
||||
const outputPath = path.join(dir, "output");
|
||||
if (fs.existsSync(outputPath)) {
|
||||
fs.rmSync(outputPath, { recursive: true });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user