@advantacode/brander 0.1.5 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,9 @@ npx --package @advantacode/brander advantacode-brander setup --out src/brander -
21
21
 
22
22
  This creates `brand.config.ts`, adds a `brand:generate` script, patches your stylesheet imports, and prepares the token output folder.
23
23
 
24
+ During setup, Brander creates `brand.css` next to your main stylesheet, writes token/theme imports there, and adds a single `@import './brand.css';` to your main stylesheet.
25
+ The generated `brand:generate` script preserves the setup generation flags you used, including `--out` and `--style`, so repeat runs keep writing to the same target and refresh the stylesheet wiring when needed.
26
+
24
27
  AdvantaCode Brander generates design tokens and framework adapters from a single brand configuration file. It allows applications, design systems, and design tools to share a consistent source of truth for colors and semantic tokens.
25
28
 
26
29
  For architecture, development, testing, and publishing workflows, see [docs/TECH_OVERVIEW.md](docs/TECH_OVERVIEW.md).
@@ -84,12 +87,13 @@ Supported flags:
84
87
  * `--format <list>` limits output to specific formats: `all`, `css`, `json`, `typescript` or `ts`, `scss`, `tailwind`, `bootstrap`, `figma`
85
88
  * `--theme <value>` limits theme CSS output to `light`, `dark`, or `both`
86
89
  * `--prefix <value>` applies a CSS variable prefix like `ac`, producing variables such as `--ac-primary`
90
+ * `--style <path>` refreshes `brand.css` and the main stylesheet import during normal generation so package scripts can keep setup paths aligned
87
91
  * `--version`, `-v` prints the installed package version
88
92
  * `--help`, `-h` prints the CLI help text
89
93
 
90
94
  Setup commands:
91
95
 
92
- * `advantacode-brander setup` configures an existing app by creating `brand.config.ts` if needed, adding a `brand:generate` script, patching a stylesheet with token imports, and generating tokens
96
+ * `advantacode-brander setup` configures an existing app by creating `brand.config.ts` if needed, adding a `brand:generate` script, creating or updating `brand.css` with token imports, patching a stylesheet to import `brand.css`, and generating tokens
93
97
  * `advantacode-brander init` runs the same setup flow for a freshly created app and is intended to be called by a higher-level scaffolder such as `advantacode-init`
94
98
 
95
99
  ## Configuration
@@ -277,6 +281,18 @@ text-danger
277
281
  border-secondary
278
282
  ```
279
283
 
284
+ CommonJS Tailwind config:
285
+
286
+ ```js
287
+ const brandPreset = require("./src/assets/brander/adapters/tailwind.preset");
288
+
289
+ module.exports = {
290
+ presets: [brandPreset]
291
+ };
292
+ ```
293
+
294
+ This step should stay documented rather than auto-patched for now. Unlike stylesheet imports, Tailwind config shape varies across projects between CJS, ESM, TS, Vite plugins, and newer Tailwind entrypoints, so automatic mutation is riskier and easier to get wrong.
295
+
280
296
  ## Bootstrap / SCSS Frameworks
281
297
 
282
298
  Generated file:
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
+ import path from 'path';
2
3
  import { generateTokens, supportedFormats } from './generate-tokens.js';
3
- import { setupProject } from './setup.js';
4
+ import { setupProject, syncStyleImports } from './setup.js';
4
5
  export async function runCli(args) {
5
6
  try {
6
7
  if (args.includes('--version') || args.includes('-v')) {
@@ -8,13 +9,13 @@ export async function runCli(args) {
8
9
  return 0;
9
10
  }
10
11
  const command = resolveCommand(args);
11
- const commandArgs = command === 'generate' ? args : args.slice(1);
12
+ const commandArgs = command === "generate" && args[0] === "generate" ? args.slice(1) : command === "generate" ? args : args.slice(1);
12
13
  if (commandArgs.includes('--help') || commandArgs.includes('-h')) {
13
14
  console.log(getHelpText(command));
14
15
  return 0;
15
16
  }
16
17
  if (command === 'generate') {
17
- await generateTokens(parseGenerateArgs(commandArgs));
18
+ await runGenerateCommand(parseGenerateArgs(commandArgs));
18
19
  return 0;
19
20
  }
20
21
  await setupProject(parseSetupArgs(command, commandArgs));
@@ -36,11 +37,20 @@ function resolveCommand(args) {
36
37
  if (!firstArg || firstArg.startsWith("-")) {
37
38
  return "generate";
38
39
  }
40
+ if (firstArg === "generate") {
41
+ return "generate";
42
+ }
39
43
  if (firstArg === "setup" || firstArg === "init") {
40
44
  return firstArg;
41
45
  }
42
46
  throw new Error(`Unknown command "${firstArg}". Use --help to see supported commands.`);
43
47
  }
48
+ async function runGenerateCommand(options) {
49
+ await generateTokens(options);
50
+ if (options.stylePath) {
51
+ syncStyleImports(options.stylePath, options.outputDir ?? path.join('dist', 'brander'));
52
+ }
53
+ }
44
54
  function parseGenerateArgs(args) {
45
55
  const options = {};
46
56
  for (let index = 0; index < args.length; index += 1) {
@@ -71,6 +81,11 @@ function parseGenerateArgs(args) {
71
81
  index += 1;
72
82
  continue;
73
83
  }
84
+ if (arg === "--style") {
85
+ options.stylePath = getNextArgValue(arg, args, index);
86
+ index += 1;
87
+ continue;
88
+ }
74
89
  if (arg.startsWith("-")) {
75
90
  throw new Error(`Unknown option "${arg}". Use --help to see supported flags.`);
76
91
  }
@@ -139,55 +154,57 @@ function normalizeFormat(value) {
139
154
  }
140
155
  function getHelpText(command) {
141
156
  if (command === "setup" || command === "init") {
142
- return `AdvantaCode Brander
143
-
144
- Usage:
145
- advantacode-brander ${command} [options]
146
-
147
- Setup options:
148
- --out <dir> Output directory (default: src/brander)
149
- --style <path> Stylesheet file to patch with token imports
150
- --script-name <name> package.json script to create (default: brand:generate)
151
- --skip-imports Do not patch a stylesheet with token imports
152
- --skip-script Do not add a package.json script
153
- --skip-config Do not create brand.config.ts when missing
154
- --skip-generate Do not run token generation after setup
155
-
156
- Generation options:
157
- --format <list> Comma-separated formats: all, css, json, typescript|ts, scss, tailwind, bootstrap, figma
158
- --theme <value> Theme CSS output: light, dark, or both (default: both)
159
- --prefix <value> CSS variable prefix. Use "" or omit for no prefix
160
-
161
- Examples:
162
- advantacode-brander ${command}
163
- advantacode-brander ${command} --out src/brander
164
- advantacode-brander ${command} --style src/style.css
165
- advantacode-brander ${command} --skip-imports --skip-generate
157
+ return `AdvantaCode Brander
158
+
159
+ Usage:
160
+ advantacode-brander ${command} [options]
161
+
162
+ Setup options:
163
+ --out <dir> Output directory (default: src/brander)
164
+ --style <path> Main stylesheet file to patch with a brand.css import
165
+ --script-name <name> package.json script to create (default: brand:generate)
166
+ --skip-imports Do not create/update brand.css or patch stylesheet imports
167
+ --skip-script Do not add a package.json script
168
+ --skip-config Do not create brand.config.js when missing
169
+ --skip-generate Do not run token generation after setup
170
+
171
+ Generation options:
172
+ --format <list> Comma-separated formats: all, css, json, typescript|ts, scss, tailwind, bootstrap, figma
173
+ --theme <value> Theme CSS output: light, dark, or both (default: both)
174
+ --prefix <value> CSS variable prefix. Use "" or omit for no prefix
175
+
176
+ Examples:
177
+ advantacode-brander ${command}
178
+ advantacode-brander ${command} --out src/brander
179
+ advantacode-brander ${command} --style src/style.css
180
+ advantacode-brander ${command} --skip-imports --skip-generate
166
181
  `;
167
182
  }
168
- return `AdvantaCode Brander
169
-
170
- Usage:
171
- advantacode-brander [options]
172
- advantacode-brander setup [options]
173
- advantacode-brander init [options]
174
-
175
- Commands:
176
- setup Configure an existing app to use Brander
177
- init Initialize a new app for Brander-driven tokens
178
-
179
- Options:
180
- -h, --help Show this help output
181
- -v, --version Show the installed package version
182
- --out <dir> Output directory (default: dist/brander)
183
- --format <list> Comma-separated formats: all, css, json, typescript|ts, scss, tailwind, bootstrap, figma
184
- --theme <value> Theme CSS output: light, dark, or both (default: both)
185
- --prefix <value> CSS variable prefix. Use "" or omit for no prefix
186
-
187
- Examples:
188
- advantacode-brander
189
- advantacode-brander --out src/tokens
190
- advantacode-brander setup --out src/brander --style src/style.css
191
- advantacode-brander init --out resources/brander --skip-imports
183
+ return `AdvantaCode Brander
184
+
185
+ Usage:
186
+ advantacode-brander [options]
187
+ advantacode-brander setup [options]
188
+ advantacode-brander init [options]
189
+
190
+ Commands:
191
+ setup Configure an existing app to use Brander
192
+ init Initialize a new app for Brander-driven tokens
193
+
194
+ Options:
195
+ -h, --help Show this help output
196
+ -v, --version Show the installed package version
197
+ --out <dir> Output directory (default: dist/brander)
198
+ --format <list> Comma-separated formats: all, css, json, typescript|ts, scss, tailwind, bootstrap, figma
199
+ --theme <value> Theme CSS output: light, dark, or both (default: both)
200
+ --prefix <value> CSS variable prefix. Use "" or omit for no prefix
201
+ --style <path> Main stylesheet file to patch with a brand.css import
202
+
203
+ Examples:
204
+ advantacode-brander
205
+ advantacode-brander --out src/tokens
206
+ advantacode-brander --out src/brander --style src/style.css
207
+ advantacode-brander setup --out src/brander --style src/style.css
208
+ advantacode-brander init --out resources/brander --skip-imports
192
209
  `;
193
210
  }
package/dist/setup.js CHANGED
@@ -2,6 +2,7 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { generateTokens } from "./generate-tokens.js";
4
4
  const defaultSetupOutputDir = path.join("src", "brander");
5
+ const brandStylesheetFileName = "brand.css";
5
6
  const defaultStyleCandidates = [
6
7
  path.join("src", "style.css"),
7
8
  path.join("src", "main.css"),
@@ -42,6 +43,9 @@ export async function setupProject(options) {
42
43
  console.log(` - ${note}`);
43
44
  }
44
45
  }
46
+ export function syncStyleImports(stylePath, outputDir) {
47
+ return ensureStyleImports(stylePath, outputDir);
48
+ }
45
49
  function ensureBrandConfig() {
46
50
  const configPath = path.resolve(process.cwd(), "brand.config.js");
47
51
  if (fs.existsSync(configPath)) {
@@ -77,19 +81,49 @@ function ensureStyleImports(stylePath, outputDir) {
77
81
  message: "Skipped stylesheet imports because no stylesheet was found. Use --style <path> to target a file explicitly."
78
82
  };
79
83
  }
84
+ const brandStylesheetPath = path.join(path.dirname(resolvedStylePath), brandStylesheetFileName);
85
+ const brandStylesheetImports = [
86
+ buildImportLine(brandStylesheetPath, path.join(outputDir, "tokens.css")),
87
+ buildImportLine(brandStylesheetPath, path.join(outputDir, "themes", "light.css")),
88
+ buildImportLine(brandStylesheetPath, path.join(outputDir, "themes", "dark.css"))
89
+ ];
90
+ const brandStylesheetContents = `${brandStylesheetImports.join("\n")}\n`;
91
+ const hasBrandStylesheet = fs.existsSync(brandStylesheetPath);
92
+ const existingBrandStylesheet = hasBrandStylesheet ? fs.readFileSync(brandStylesheetPath, "utf8") : "";
93
+ if (existingBrandStylesheet !== brandStylesheetContents) {
94
+ fs.writeFileSync(brandStylesheetPath, brandStylesheetContents);
95
+ }
80
96
  const styleFileContents = fs.readFileSync(resolvedStylePath, "utf8");
81
- const importLines = [
97
+ const legacyTokenImports = [
82
98
  buildImportLine(resolvedStylePath, path.join(outputDir, "tokens.css")),
83
99
  buildImportLine(resolvedStylePath, path.join(outputDir, "themes", "light.css")),
84
100
  buildImportLine(resolvedStylePath, path.join(outputDir, "themes", "dark.css"))
85
101
  ];
86
- const missingImports = importLines.filter((importLine) => !styleFileContents.includes(importLine));
87
- if (missingImports.length === 0) {
88
- return { message: `Kept existing token imports in ${path.relative(process.cwd(), resolvedStylePath)}.` };
89
- }
90
- const nextContents = `${missingImports.join("\n")}\n${styleFileContents}`;
91
- fs.writeFileSync(resolvedStylePath, nextContents);
92
- return { message: `Added token imports to ${path.relative(process.cwd(), resolvedStylePath)}.` };
102
+ const brandImportLine = buildImportLine(resolvedStylePath, brandStylesheetPath);
103
+ const styleLineEnding = styleFileContents.includes("\r\n") ? "\r\n" : "\n";
104
+ const styleLines = styleFileContents.split(/\r?\n/);
105
+ const legacyImportCandidates = new Set();
106
+ for (const importLine of legacyTokenImports) {
107
+ legacyImportCandidates.add(importLine);
108
+ legacyImportCandidates.add(importLine.replace(/'/g, '"'));
109
+ }
110
+ let nextStyleLines = styleLines.filter((line) => !legacyImportCandidates.has(line.trim()));
111
+ const hasBrandImport = nextStyleLines.some((line) => line.trim() === brandImportLine || line.trim() === brandImportLine.replace(/'/g, '"'));
112
+ if (!hasBrandImport) {
113
+ nextStyleLines = [brandImportLine, ...nextStyleLines];
114
+ }
115
+ while (nextStyleLines[0] === "") {
116
+ nextStyleLines = nextStyleLines.slice(1);
117
+ }
118
+ const nextStyleContents = `${nextStyleLines.join(styleLineEnding)}${styleLineEnding}`;
119
+ if (nextStyleContents !== styleFileContents) {
120
+ fs.writeFileSync(resolvedStylePath, nextStyleContents);
121
+ }
122
+ const brandStylesheetStatus = hasBrandStylesheet ? "Updated" : "Created";
123
+ const mainStylesheetStatus = nextStyleContents === styleFileContents ? "Kept" : "Updated";
124
+ return {
125
+ message: `${brandStylesheetStatus} ${path.relative(process.cwd(), brandStylesheetPath)} and ${mainStylesheetStatus.toLowerCase()} ${path.relative(process.cwd(), resolvedStylePath)} to import it.`
126
+ };
93
127
  }
94
128
  function resolveStylePath(stylePath) {
95
129
  if (stylePath) {
@@ -130,24 +164,27 @@ function buildGenerateCommand(options) {
130
164
  if (options.prefix) {
131
165
  commandParts.push("--prefix", options.prefix);
132
166
  }
167
+ if (options.stylePath) {
168
+ commandParts.push("--style", options.stylePath);
169
+ }
133
170
  return commandParts.join(" ");
134
171
  }
135
172
  function getDefaultBrandConfigTemplate() {
136
- return `export default {
137
- name: process.env.COMPANY_NAME || "My Company",
138
- css: {
139
- prefix: process.env.CSS_PREFIX ?? ""
140
- },
141
- colors: {
142
- primary: process.env.PRIMARY_COLOR || "amber-500",
143
- secondary: process.env.SECONDARY_COLOR || "zinc-700",
144
- neutral: process.env.NEUTRAL_COLOR || process.env.SECONDARY_COLOR || "zinc-700",
145
- accent: process.env.ACCENT_COLOR || "amber-400",
146
- info: process.env.INFO_COLOR || "sky-500",
147
- success: process.env.SUCCESS_COLOR || "green-500",
148
- warning: process.env.WARNING_COLOR || "yellow-500",
149
- danger: process.env.DANGER_COLOR || "red-500"
150
- }
151
- };
173
+ return `export default {
174
+ name: process.env.COMPANY_NAME || "My Company",
175
+ css: {
176
+ prefix: process.env.CSS_PREFIX ?? ""
177
+ },
178
+ colors: {
179
+ primary: process.env.PRIMARY_COLOR || "amber-500",
180
+ secondary: process.env.SECONDARY_COLOR || "zinc-700",
181
+ neutral: process.env.NEUTRAL_COLOR || process.env.SECONDARY_COLOR || "zinc-700",
182
+ accent: process.env.ACCENT_COLOR || "amber-400",
183
+ info: process.env.INFO_COLOR || "sky-500",
184
+ success: process.env.SUCCESS_COLOR || "green-500",
185
+ warning: process.env.WARNING_COLOR || "yellow-500",
186
+ danger: process.env.DANGER_COLOR || "red-500"
187
+ }
188
+ };
152
189
  `;
153
190
  }
package/package.json CHANGED
@@ -1,60 +1,60 @@
1
- {
2
- "name": "@advantacode/brander",
3
- "version": "0.1.5",
4
- "description": "AdvantaCode Design System Brand Generator",
5
- "type": "module",
6
- "files": [
7
- "dist/**/*.js",
8
- "docs/*.md",
9
- "README.md",
10
- "LICENSE"
11
- ],
12
- "bin": {
13
- "advantacode-brander": "./dist/cli-wrapper.js"
14
- },
15
- "scripts": {
16
- "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
17
- "build": "npm run clean && tsc && chmod +x dist/cli-wrapper.js",
18
- "cli": "node --import tsx/esm dist/cli-wrapper.js",
19
- "lint": "eslint --max-warnings=0 src test",
20
- "pretest": "npm run build",
21
- "test": "node --import tsx/esm --test",
22
- "release:check": "npm run lint && npm test && npm pack --dry-run",
23
- "tokens": "tsx src/generate-tokens.ts",
24
- "prepack": "npm run build",
25
- "brand:generate": "advantacode-brander --out src/brander",
26
- "brand:test": "node ./dist/cli-wrapper.js --out src/brander"
27
- },
28
- "license": "MIT",
29
- "author": "Anthony Penn",
30
- "repository": {
31
- "type": "git",
32
- "url": "git+https://github.com/advantacode/advantacode-brander.git"
33
- },
34
- "homepage": "https://github.com/advantacode/advantacode-brander#readme",
35
- "bugs": {
36
- "url": "https://github.com/advantacode/advantacode-brander/issues"
37
- },
38
- "keywords": [
39
- "design-tokens",
40
- "branding",
41
- "oklch",
42
- "tailwind",
43
- "cli"
44
- ],
45
- "engines": {
46
- "node": ">=20.0.0"
47
- },
48
- "devDependencies": {
49
- "@types/node": "^25.3.3",
50
- "@typescript-eslint/eslint-plugin": "^8.56.1",
51
- "@typescript-eslint/parser": "^8.56.1",
52
- "eslint": "^8.0.0",
53
- "typescript": "^5.3.0"
54
- },
55
- "dependencies": {
56
- "culori": "^4.0.2",
57
- "dotenv": "^17.3.1",
58
- "tsx": "^4.21.0"
59
- }
60
- }
1
+ {
2
+ "name": "@advantacode/brander",
3
+ "version": "0.2.1",
4
+ "description": "AdvantaCode Design System Brand Generator",
5
+ "type": "module",
6
+ "files": [
7
+ "dist/**/*.js",
8
+ "docs/*.md",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "bin": {
13
+ "advantacode-brander": "./dist/cli-wrapper.js"
14
+ },
15
+ "scripts": {
16
+ "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
17
+ "build": "npm run clean && tsc && chmod +x dist/cli-wrapper.js",
18
+ "cli": "node --import tsx/esm dist/cli-wrapper.js",
19
+ "lint": "eslint --max-warnings=0 src test",
20
+ "pretest": "npm run build",
21
+ "test": "node --import tsx/esm --test",
22
+ "release:check": "npm run lint && npm test && npm pack --dry-run",
23
+ "tokens": "tsx src/generate-tokens.ts",
24
+ "prepack": "npm run build",
25
+ "brand:generate": "advantacode-brander --out src/brander",
26
+ "brand:test": "node ./dist/cli-wrapper.js --out src/brander"
27
+ },
28
+ "license": "MIT",
29
+ "author": "Anthony Penn",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/advantacode/advantacode-brander.git"
33
+ },
34
+ "homepage": "https://github.com/advantacode/advantacode-brander#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/advantacode/advantacode-brander/issues"
37
+ },
38
+ "keywords": [
39
+ "design-tokens",
40
+ "branding",
41
+ "oklch",
42
+ "tailwind",
43
+ "cli"
44
+ ],
45
+ "engines": {
46
+ "node": ">=20.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^25.3.3",
50
+ "@typescript-eslint/eslint-plugin": "^8.56.1",
51
+ "@typescript-eslint/parser": "^8.56.1",
52
+ "eslint": "^8.0.0",
53
+ "typescript": "^5.3.0"
54
+ },
55
+ "dependencies": {
56
+ "culori": "^4.0.2",
57
+ "dotenv": "^17.3.1",
58
+ "tsx": "^4.21.0"
59
+ }
60
+ }