✨(tokens) add TS generator and re-organize the repo
Add TS Generator which is a lot based on JS Generator.
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . 'src/**/*.{ts,tsx}'",
|
"lint": "eslint . 'src/**/*.{ts,tsx}'",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build-theme": "cunningham -o src",
|
"build-theme": "cunningham -o src -g css,ts",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
:root {
|
:root {
|
||||||
--c--colors--primary: purple;
|
--c--colors--primary: purple;
|
||||||
--c--colors--secondary: #DA0000;
|
--c--colors--secondary: #DA0000;
|
||||||
--c--text--primary: purple;
|
|
||||||
--c--text--secondary: #DA0000;
|
|
||||||
}
|
}
|
||||||
1
apps/demo/src/cunningham-tokens.ts
Normal file
1
apps/demo/src/cunningham-tokens.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const tokens = {"colors":{"primary":"purple","secondary":"#DA0000"}};
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
h1 {
|
h1, h3 {
|
||||||
color: white;
|
color: white;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { Button } from "@openfun/cunningham-react";
|
import { Button } from "@openfun/cunningham-react";
|
||||||
|
import { tokens } from "./cunningham-tokens";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<div className="center">
|
<div className="center">
|
||||||
<h1 className="test">Cunningham Demo.</h1>
|
<h1 className="test">Cunningham Demo.</h1>
|
||||||
<Button />
|
<Button />
|
||||||
|
<h3>Primary color is {tokens.colors.primary}</h3>
|
||||||
</div>
|
</div>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
7
packages/react/cunningham.cjs
Normal file
7
packages/react/cunningham.cjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
primary: 'grey'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
configurationFilenames: ["cunningham.js", "cunningham.cjs"],
|
configurationFilenames: ["cunningham.js", "cunningham.cjs", "cunningham.ts"],
|
||||||
tokenFilenames: {
|
tokenFilename: "cunningham-tokens",
|
||||||
css: "cunningham-tokens.css",
|
|
||||||
js: "cunningham-tokens.js",
|
|
||||||
},
|
|
||||||
sass: {
|
sass: {
|
||||||
varSeparator: "--",
|
varSeparator: "--",
|
||||||
varPrefix: "c--",
|
varPrefix: "c--",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as fs from "fs";
|
|||||||
import deepmerge from "deepmerge";
|
import deepmerge from "deepmerge";
|
||||||
import { ConfigShape } from "TokensGenerator";
|
import { ConfigShape } from "TokensGenerator";
|
||||||
import { workPath } from "Paths";
|
import { workPath } from "Paths";
|
||||||
|
import { register } from "ts-node";
|
||||||
import Config from "./Config";
|
import Config from "./Config";
|
||||||
|
|
||||||
const getLocalConfig = async () => {
|
const getLocalConfig = async () => {
|
||||||
@@ -14,11 +15,28 @@ const getLocalConfig = async () => {
|
|||||||
console.log("No local config found, using default config.");
|
console.log("No local config found, using default config.");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
console.log("Found local config file: " + filename);
|
||||||
|
|
||||||
|
const ext = path.extname(filename);
|
||||||
|
if (ext === ".ts") {
|
||||||
|
registerTypescriptLoader();
|
||||||
|
}
|
||||||
|
|
||||||
const config = await import(filename);
|
const config = await import(filename);
|
||||||
return config.default;
|
return config.default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const registerTypescriptLoader = () => {
|
||||||
|
register({
|
||||||
|
moduleTypes: {
|
||||||
|
"**/*.ts": "cjs",
|
||||||
|
},
|
||||||
|
compilerOptions: {
|
||||||
|
module: "commonjs",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getDistConfig = async () => {
|
const getDistConfig = async () => {
|
||||||
const config = await import("./cunningham.dist.js");
|
const config = await import("./cunningham.dist.js");
|
||||||
return config.default;
|
return config.default;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe("CssGenerator", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const testSelector = async (opt: string) => {
|
const testSelector = async (opt: string) => {
|
||||||
const cssTokensFile = path.join(__dirname, Config.tokenFilenames.css);
|
const cssTokensFile = path.join(__dirname, Config.tokenFilename + ".css");
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||||
await run(["", "", "-g", "css", opt, "html"]);
|
await run(["", "", "-g", "css", opt, "html"]);
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const cssGenerator: Generator = async (tokens, opts) => {
|
|||||||
);
|
);
|
||||||
}, "");
|
}, "");
|
||||||
const cssContent = `${opts.selector} {\n${cssVars}}`;
|
const cssContent = `${opts.selector} {\n${cssVars}}`;
|
||||||
const dest = path.join(opts.path, Config.tokenFilenames.css);
|
const dest = path.join(opts.path, Config.tokenFilename + ".css");
|
||||||
|
|
||||||
put(dest, cssContent);
|
put(dest, cssContent);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe("JsGenerator", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("generates valid JS file.", async () => {
|
it("generates valid JS file.", async () => {
|
||||||
const tokensFile = path.join(__dirname, Config.tokenFilenames.js);
|
const tokensFile = path.join(__dirname, Config.tokenFilename + ".js");
|
||||||
expect(fs.existsSync(tokensFile)).toEqual(false);
|
expect(fs.existsSync(tokensFile)).toEqual(false);
|
||||||
await run(["", "", "-g", "js"]);
|
await run(["", "", "-g", "js"]);
|
||||||
expect(fs.existsSync(tokensFile)).toEqual(true);
|
expect(fs.existsSync(tokensFile)).toEqual(true);
|
||||||
|
|||||||
30
packages/tokens/src/bin/Generators/TsGenerator.spec.ts
Normal file
30
packages/tokens/src/bin/Generators/TsGenerator.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("TsGenerator", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.spyOn(console, "log").mockImplementation(() => {});
|
||||||
|
cleanup(__dirname);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup(__dirname);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("generates valid TS file.", async () => {
|
||||||
|
const tokensFile = path.join(__dirname, Config.tokenFilename + ".ts");
|
||||||
|
expect(fs.existsSync(tokensFile)).toEqual(false);
|
||||||
|
await run(["", "", "-g", "ts"]);
|
||||||
|
expect(fs.existsSync(tokensFile)).toEqual(true);
|
||||||
|
expect(fs.readFileSync(tokensFile).toString()).toEqual(
|
||||||
|
`export const tokens = {"colors":{"primary":"#055FD2","secondary":"#DA0000"}};`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
10
packages/tokens/src/bin/Generators/TsGenerator.ts
Normal file
10
packages/tokens/src/bin/Generators/TsGenerator.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import path from "path";
|
||||||
|
import { Generator } from "Generators/index";
|
||||||
|
import Config from "Config";
|
||||||
|
import { put } from "Utils/Files";
|
||||||
|
import { jsGeneratorContent } from "Generators/JsGenerator";
|
||||||
|
|
||||||
|
export const tsGenerator: Generator = async (tokens, opts) => {
|
||||||
|
const dest = path.join(opts.path, Config.tokenFilename + ".ts");
|
||||||
|
put(dest, await jsGeneratorContent(tokens));
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { cssGenerator } from "Generators/CssGenerator";
|
import { cssGenerator } from "Generators/CssGenerator";
|
||||||
import { jsGenerator } from "Generators/JsGenerator";
|
import { jsGenerator } from "Generators/JsGenerator";
|
||||||
import { Tokens } from "TokensGenerator";
|
import { Tokens } from "TokensGenerator";
|
||||||
|
import { tsGenerator } from "Generators/TsGenerator";
|
||||||
|
|
||||||
export type Generator = (
|
export type Generator = (
|
||||||
tokens: Tokens,
|
tokens: Tokens,
|
||||||
@@ -10,4 +11,5 @@ export type Generator = (
|
|||||||
export const Generators: Record<string, Generator> = {
|
export const Generators: Record<string, Generator> = {
|
||||||
css: cssGenerator,
|
css: cssGenerator,
|
||||||
js: jsGenerator,
|
js: jsGenerator,
|
||||||
|
ts: tsGenerator,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,48 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
import * as child_process from "child_process";
|
||||||
import { run } from "ThemeGenerator";
|
import { run } from "ThemeGenerator";
|
||||||
import { cleanup } from "tests/Utils";
|
import { cleanup } from "tests/Utils";
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import * as util from "util";
|
||||||
import Config from "../Config";
|
import Config from "../Config";
|
||||||
|
|
||||||
jest.mock("../Paths", () => ({
|
jest.mock("../Paths", () => ({
|
||||||
workPath: () => __dirname,
|
workPath: () => __dirname,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock("../cunningham.dist.js", () => ({
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
primary: "#055FD2",
|
||||||
|
secondary: "#DA0000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const runBin = async (args: string) => {
|
||||||
|
const exec = util.promisify(child_process.exec);
|
||||||
|
const promise = exec(
|
||||||
|
path.join(__dirname, "..", "..", "..", "dist", "bin", "Main.js") +
|
||||||
|
" " +
|
||||||
|
args
|
||||||
|
);
|
||||||
|
|
||||||
|
promise.child.stdout?.on("data", (data) => {
|
||||||
|
console.log("stdout: " + data);
|
||||||
|
});
|
||||||
|
promise.child.stderr?.on("data", (data) => {
|
||||||
|
console.log("stderr: " + data);
|
||||||
|
});
|
||||||
|
promise.child.on("close", (code) => {
|
||||||
|
console.log("closing code: " + code);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdout, stderr } = await promise;
|
||||||
|
console.log("stdout", stdout);
|
||||||
|
console.log("stderr", stderr);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test written here are supposed to be general ones and not specific to any generator.
|
* Test written here are supposed to be general ones and not specific to any generator.
|
||||||
*
|
*
|
||||||
@@ -25,28 +60,25 @@ describe("Cunningham Bin", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Runs without existing config file with default values.", async () => {
|
it("Runs without existing config file with default values.", async () => {
|
||||||
const cssTokensFile = path.join(__dirname, Config.tokenFilenames.css);
|
const cssTokensFile = path.join(__dirname, Config.tokenFilename + ".css");
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||||
await run(["", "", "-g", "css"]);
|
await run(["", "", "-g", "css"]);
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
||||||
\t--c--colors--primary: #055FD2;
|
\t--c--theme--colors--primary: #055FD2;
|
||||||
\t--c--colors--secondary: #DA0000;
|
\t--c--theme--colors--secondary: #DA0000;
|
||||||
}`);
|
}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Runs with existing config file using local values.", async () => {
|
it("Runs with existing JS config file using local values.", async () => {
|
||||||
const localConfigurationFile = path.join(
|
const localConfigurationFile = path.join(__dirname, "cunningham.js");
|
||||||
__dirname,
|
|
||||||
Config.configurationFilenames[0]
|
|
||||||
);
|
|
||||||
expect(fs.existsSync(localConfigurationFile)).toEqual(false);
|
expect(fs.existsSync(localConfigurationFile)).toEqual(false);
|
||||||
|
|
||||||
const cssTokensFile = path.join(__dirname, Config.tokenFilenames.css);
|
const cssTokensFile = path.join(__dirname, Config.tokenFilename + ".css");
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||||
|
|
||||||
fs.copyFileSync(
|
fs.copyFileSync(
|
||||||
path.join(__dirname, "assets", Config.configurationFilenames[0]),
|
path.join(__dirname, "assets", "cunningham.js"),
|
||||||
localConfigurationFile
|
localConfigurationFile
|
||||||
);
|
);
|
||||||
expect(fs.existsSync(localConfigurationFile)).toEqual(true);
|
expect(fs.existsSync(localConfigurationFile)).toEqual(true);
|
||||||
@@ -54,20 +86,43 @@ describe("Cunningham Bin", () => {
|
|||||||
await run(["", "", "-g", "css"]);
|
await run(["", "", "-g", "css"]);
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
||||||
\t--c--colors--primary: AntiqueWhite;
|
\t--c--theme--colors--primary: AntiqueWhite;
|
||||||
\t--c--colors--secondary: #DA0000;
|
\t--c--theme--colors--secondary: #DA0000;
|
||||||
}`);
|
}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Runs with existing TS config file using local values.", async () => {
|
||||||
|
const localConfigurationFile = path.join(__dirname, "cunningham.ts");
|
||||||
|
expect(fs.existsSync(localConfigurationFile)).toEqual(false);
|
||||||
|
|
||||||
|
const cssTokensFile = path.join(__dirname, Config.tokenFilename + ".css");
|
||||||
|
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||||
|
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.join(__dirname, "assets", "cunningham.ts"),
|
||||||
|
localConfigurationFile
|
||||||
|
);
|
||||||
|
expect(fs.existsSync(localConfigurationFile)).toEqual(true);
|
||||||
|
|
||||||
|
// We must run the bin directly to be sure that it compiles the TS file. ( Importing TS from
|
||||||
|
// TS will always work )
|
||||||
|
await runBin(`-g css -cwd ${__dirname}`);
|
||||||
|
|
||||||
|
// await run(["", "", "-g", "css"]);
|
||||||
|
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||||
|
expect(fs.readFileSync(cssTokensFile).toString()).toContain(`
|
||||||
|
\t--c--theme--colors--primary: typescript;`);
|
||||||
|
});
|
||||||
|
|
||||||
const testOutput = async (opt: string) => {
|
const testOutput = async (opt: string) => {
|
||||||
const outputDir = path.join(__dirname, "output");
|
const outputDir = path.join(__dirname, "output");
|
||||||
const cssTokensFile = path.join(outputDir, Config.tokenFilenames.css);
|
const cssTokensFile = path.join(outputDir, Config.tokenFilename + ".css");
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
expect(fs.existsSync(cssTokensFile)).toEqual(false);
|
||||||
await run(["", "", "-g", "css", opt, outputDir]);
|
await run(["", "", "-g", "css", opt, outputDir]);
|
||||||
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
expect(fs.existsSync(cssTokensFile)).toEqual(true);
|
||||||
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
expect(fs.readFileSync(cssTokensFile).toString()).toEqual(`:root {
|
||||||
\t--c--colors--primary: #055FD2;
|
\t--c--theme--colors--primary: #055FD2;
|
||||||
\t--c--colors--secondary: #DA0000;
|
\t--c--theme--colors--secondary: #DA0000;
|
||||||
}`);
|
}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import Config from "Config";
|
import Config from "Config";
|
||||||
|
import { Generators } from "Generators";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty the current directory from generated tokens file and local
|
* Empty the current directory from generated tokens file and local
|
||||||
* config to start with an predictable environment.
|
* config to start with an predictable environment.
|
||||||
*/
|
*/
|
||||||
export const cleanup = (dir: string) => {
|
export const cleanup = (dir: string) => {
|
||||||
Object.entries(Config.tokenFilenames).forEach(([key, filename]) => {
|
const tokenFilenames = Object.keys(Generators).map(
|
||||||
|
(extension) => Config.tokenFilename + "." + extension
|
||||||
|
);
|
||||||
|
|
||||||
|
tokenFilenames.forEach((filename) => {
|
||||||
const filePath = path.join(dir, filename);
|
const filePath = path.join(dir, filename);
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const localConfigurationFile = path.join(
|
Config.configurationFilenames.forEach((filename) => {
|
||||||
dir,
|
const localConfigurationFile = path.join(dir, filename);
|
||||||
Config.configurationFilenames[0]
|
if (fs.existsSync(localConfigurationFile)) {
|
||||||
);
|
fs.unlinkSync(localConfigurationFile);
|
||||||
if (fs.existsSync(localConfigurationFile)) {
|
}
|
||||||
fs.unlinkSync(localConfigurationFile);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const outputPath = path.join(dir, "output");
|
const outputPath = path.join(dir, "output");
|
||||||
if (fs.existsSync(outputPath)) {
|
if (fs.existsSync(outputPath)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user