@inkdropapp/theme-dev-helpers 0.3.7 → 0.3.9

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.
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
12
  - name: Checkout 🛎️
13
- uses: actions/checkout@v4
13
+ uses: actions/checkout@v6
14
14
 
15
15
  - name: Use Bun 🥟
16
16
  uses: oven-sh/setup-bun@v2
package/.oxfmtrc.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "semi": false,
4
+ "singleQuote": true,
5
+ "trailingComma": "none",
6
+ "sortImports": false,
7
+ "sortPackageJson": true
8
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": ["typescript", "unicorn", "oxc", "react"],
4
+ "settings": {
5
+ "react": {
6
+ "version": "19.2.7"
7
+ }
8
+ },
9
+ "categories": {
10
+ "correctness": "error"
11
+ },
12
+ "rules": {
13
+ "typescript/no-explicit-any": "off",
14
+ "typescript/ban-ts-comment": "off",
15
+ "typescript/no-this-alias": "off",
16
+ "typescript/no-unused-vars": [
17
+ "warn",
18
+ {
19
+ "argsIgnorePattern": "^_",
20
+ "varsIgnorePattern": "^_",
21
+ "caughtErrorsIgnorePattern": "^_"
22
+ }
23
+ ],
24
+ "no-useless-escape": "off",
25
+ "prefer-const": "error",
26
+ "no-unused-vars": "off"
27
+ },
28
+ "ignorePatterns": ["node_modules/**"]
29
+ }
package/README.md CHANGED
@@ -45,3 +45,14 @@ It provides a simple UI to preview your theme with hot-reloading.
45
45
  dev-server
46
46
  ```
47
47
 
48
+ ## Development
49
+
50
+ Dependencies are managed with [pnpm](https://pnpm.io/), and [Bun](https://bun.sh/) is the runtime used to run the CLIs and tests.
51
+
52
+ ```sh
53
+ pnpm install # install dependencies
54
+ pnpm format # format with oxfmt
55
+ pnpm lint # lint with oxlint
56
+ pnpm typecheck # type-check with tsc
57
+ pnpm test # run unit tests (bun test)
58
+ ```
package/index.html CHANGED
@@ -1,16 +1,14 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Inkdrop theme dev tool</title>
8
+ </head>
3
9
 
4
- <head>
5
- <meta charset="UTF-8" />
6
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
- <title>Inkdrop theme dev tool</title>
9
- </head>
10
-
11
- <body>
12
- <div id="root"></div>
13
- <script type="module" src="/src/dev-server.tsx"></script>
14
- </body>
15
-
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="/src/dev-server.tsx"></script>
13
+ </body>
16
14
  </html>
package/package.json CHANGED
@@ -1,41 +1,45 @@
1
1
  {
2
2
  "name": "@inkdropapp/theme-dev-helpers",
3
- "version": "0.3.7",
4
- "type": "module",
3
+ "version": "0.3.9",
5
4
  "description": "A helper module for creating themes for Inkdrop",
6
5
  "keywords": [
7
6
  "inkdrop",
8
7
  "markdown"
9
8
  ],
10
- "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1"
12
- },
9
+ "license": "MIT",
10
+ "author": "Takuya Matsuyama<t@inkdrop.app>",
13
11
  "repository": {
14
12
  "type": "git",
15
13
  "url": "git+https://github.com/inkdropapp/theme-dev-helpers.git"
16
14
  },
17
- "author": "Takuya Matsuyama<t@inkdrop.app>",
18
- "license": "MIT",
19
15
  "bin": {
20
- "generate-palette": "./bin/generate-palette",
21
- "dev-server": "./bin/dev-server"
16
+ "dev-server": "./bin/dev-server",
17
+ "generate-palette": "./bin/generate-palette"
18
+ },
19
+ "type": "module",
20
+ "scripts": {
21
+ "format": "oxfmt",
22
+ "lint": "oxlint",
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "bun test"
22
25
  },
23
26
  "dependencies": {
24
- "@inkdropapp/base-ui-theme": "^0.3.2",
25
- "@inkdropapp/css": "^0.4.2",
26
- "@vitejs/plugin-react": "^4.3.2",
27
- "commander": "^12.1.0",
28
- "puppeteer": "^23.5.3",
29
- "react": "^18.3.1",
30
- "react-dom": "^18.3.1",
31
- "react-router-dom": "^6.27.0",
32
- "typescript": "^5.5.3",
33
- "vite": "^5.4.8"
27
+ "@inkdropapp/base-ui-theme": "^0.7.5",
28
+ "@inkdropapp/css": "^0.6.7",
29
+ "@vitejs/plugin-react": "^6.0.2",
30
+ "commander": "^15.0.0",
31
+ "puppeteer": "^25.1.0",
32
+ "react": "^19.2.7",
33
+ "react-dom": "^19.2.7",
34
+ "react-router-dom": "^7.17.0",
35
+ "typescript": "^6.0.3",
36
+ "vite": "^8.0.16"
34
37
  },
35
38
  "devDependencies": {
36
- "@types/react": "^18.3.10",
37
- "@types/react-dom": "^18.3.0",
38
- "typescript-eslint": "^8.7.0",
39
- "prettier": "^3.3.3"
39
+ "@types/bun": "1.3.14",
40
+ "@types/react": "^19.2.17",
41
+ "@types/react-dom": "^19.2.3",
42
+ "oxfmt": "0.55.0",
43
+ "oxlint": "1.70.0"
40
44
  }
41
45
  }
@@ -0,0 +1,6 @@
1
+ allowBuilds:
2
+ esbuild: set this to true or false
3
+ puppeteer: set this to true or false
4
+ minimumReleaseAgeExclude:
5
+ - '@inkdropapp/base-ui-theme'
6
+ - '@inkdropapp/css'
@@ -6,20 +6,23 @@ export const ColorTokensPage = () => {
6
6
  return (
7
7
  <>
8
8
  <h2>Color Tokens</h2>
9
- <div className='variable-list'>
9
+ <div className="variable-list">
10
10
  {Object.keys(cssVariables)
11
- .filter(name => name.startsWith('--color') || name.startsWith('--hsl'))
12
- .map(variableName => {
11
+ .filter((name) => name.startsWith('--color') || name.startsWith('--hsl'))
12
+ .map((variableName) => {
13
13
  return (
14
- <div key={variableName} className='variable-item'>
15
- <div className='color-box' style={{
16
- background: variableName.startsWith('--hsl') ? `hsl(var(${variableName}))` : `var(${variableName})`
17
- }}></div>
18
- <div className='data'>
19
- <div className='variable-name'>{variableName}</div>
20
- <div className='variable-value'>
21
- {cssVariables[variableName]}
22
- </div>
14
+ <div key={variableName} className="variable-item">
15
+ <div
16
+ className="color-box"
17
+ style={{
18
+ background: variableName.startsWith('--hsl')
19
+ ? `hsl(var(${variableName}))`
20
+ : `var(${variableName})`
21
+ }}
22
+ ></div>
23
+ <div className="data">
24
+ <div className="variable-name">{variableName}</div>
25
+ <div className="variable-value">{cssVariables[variableName]}</div>
23
26
  </div>
24
27
  </div>
25
28
  )
@@ -28,4 +31,3 @@ export const ColorTokensPage = () => {
28
31
  </>
29
32
  )
30
33
  }
31
-
@@ -5,7 +5,6 @@ export const ComponentsPage = () => {
5
5
  <>
6
6
  <h2>Components</h2>
7
7
  <div className="component-list">
8
-
9
8
  <div className="component-item">
10
9
  <h3>Link</h3>
11
10
  <div>
@@ -21,12 +20,21 @@ export const ComponentsPage = () => {
21
20
  <button className="ui basic button">Basic Button</button>
22
21
  <button className="ui primary button">Primary Button</button>
23
22
  <button className="ui negative button">Negative Button</button>
24
- <button className="ui tiny circular icon button plugin-uninstall-button "
25
- title="Uninstall">
26
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14"
27
- name="bin-1" className="svg-icon streamline bin-1 ">
23
+ <button
24
+ className="ui tiny circular icon button plugin-uninstall-button "
25
+ title="Uninstall"
26
+ >
27
+ <svg
28
+ xmlns="http://www.w3.org/2000/svg"
29
+ viewBox="0 0 24 24"
30
+ width="14"
31
+ height="14"
32
+ name="bin-1"
33
+ className="svg-icon streamline bin-1 "
34
+ >
28
35
  <defs>
29
- <style>{`
36
+ <style>
37
+ {`
30
38
  .bin-1_svg__a {
31
39
  fill: none;
32
40
  stroke: currentColor;
@@ -39,7 +47,8 @@ export const ComponentsPage = () => {
39
47
  </defs>
40
48
  <path
41
49
  d="M1.5 4.5h21M14.25 1.5h-4.5A1.5 1.5 0 0 0 8.25 3v1.5h7.5V3a1.5 1.5 0 0 0-1.5-1.5M9.75 17.25v-7.5M14.25 17.25v-7.5M18.865 21.124A1.5 1.5 0 0 1 17.37 22.5H6.631a1.5 1.5 0 0 1-1.495-1.376L3.75 4.5h16.5Z"
42
- className="bin-1_svg__a"></path>
50
+ className="bin-1_svg__a"
51
+ ></path>
43
52
  </svg>
44
53
  </button>
45
54
  </div>
@@ -47,27 +56,35 @@ export const ComponentsPage = () => {
47
56
  </div>
48
57
 
49
58
  <div className="component-item">
50
- <h3 className='ui header'>Message</h3>
59
+ <h3 className="ui header">Message</h3>
51
60
  <div>
52
61
  <div className="ui message">
53
- <div className="header">
54
- Changes in Service
55
- </div>
56
- <p>We just updated our privacy policy here to better service our customers. We recommend reviewing the changes.
62
+ <div className="header">Changes in Service</div>
63
+ <p>
64
+ We just updated our privacy policy here to better service our customers. We
65
+ recommend reviewing the changes.
57
66
  </p>
58
67
  </div>
59
68
 
60
69
  <div className="ui info message">
61
- <div className="header">
62
- Info
63
- </div>
64
-
65
- <p>Plugins add new functionality or provide new look to Inkdrop. You can activate or deactivate them at any time.
66
- Read&nbsp;<a href="https://docs.inkdrop.app/reference/extend-with-plugins">the manual&nbsp;<svg
67
- xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" name="expand-6"
68
- className="svg-icon streamline expand-6 inline">
69
- <defs>
70
- <style>{`
70
+ <div className="header">Info</div>
71
+
72
+ <p>
73
+ Plugins add new functionality or provide new look to Inkdrop. You can activate or
74
+ deactivate them at any time. Read&nbsp;
75
+ <a href="https://docs.inkdrop.app/reference/extend-with-plugins">
76
+ the manual&nbsp;
77
+ <svg
78
+ xmlns="http://www.w3.org/2000/svg"
79
+ viewBox="0 0 24 24"
80
+ width="1em"
81
+ height="1em"
82
+ name="expand-6"
83
+ className="svg-icon streamline expand-6 inline"
84
+ >
85
+ <defs>
86
+ <style>
87
+ {`
71
88
  .expand-6_svg__a {
72
89
  fill: none;
73
90
  stroke: currentColor;
@@ -76,37 +93,37 @@ export const ComponentsPage = () => {
76
93
  stroke-width: 1.5px
77
94
  }
78
95
  `}
79
- </style>
80
- </defs>
81
- <path
82
- d="M23.251 7.498V.748h-6.75M23.251.748l-15 15M11.251 5.248h-9a1.5 1.5 0 0 0-1.5 1.5v15a1.5 1.5 0 0 0 1.5 1.5h15a1.5 1.5 0 0 0 1.5-1.5v-9"
83
- className="expand-6_svg__a"></path>
84
- </svg></a> to learn more.</p>
96
+ </style>
97
+ </defs>
98
+ <path
99
+ d="M23.251 7.498V.748h-6.75M23.251.748l-15 15M11.251 5.248h-9a1.5 1.5 0 0 0-1.5 1.5v15a1.5 1.5 0 0 0 1.5 1.5h15a1.5 1.5 0 0 0 1.5-1.5v-9"
100
+ className="expand-6_svg__a"
101
+ ></path>
102
+ </svg>
103
+ </a>{' '}
104
+ to learn more.
105
+ </p>
85
106
  </div>
86
107
 
87
108
  <div className="ui positive message">
88
- <div className="header">
89
- You are eligible for a reward
90
- </div>
91
- <p>Go to your <b>special offers</b> page to see now.</p>
109
+ <div className="header">You are eligible for a reward</div>
110
+ <p>
111
+ Go to your <b>special offers</b> page to see now.
112
+ </p>
92
113
  </div>
93
114
 
94
115
  <div className="ui negative message">
95
- <div className="header">
96
- We're sorry we can't apply that discount
97
- </div>
98
- <p>That offer has expired
99
- </p>
116
+ <div className="header">We're sorry we can't apply that discount</div>
117
+ <p>That offer has expired</p>
100
118
  </div>
101
119
 
102
120
  <div className="ui warning message">
103
- <div className="header">
104
- Changes in Service
105
- </div>
106
- <p>We just updated our privacy policy here to better service our customers. We recommend reviewing the changes.
121
+ <div className="header">Changes in Service</div>
122
+ <p>
123
+ We just updated our privacy policy here to better service our customers. We
124
+ recommend reviewing the changes.
107
125
  </p>
108
126
  </div>
109
-
110
127
  </div>
111
128
  </div>
112
129
 
@@ -160,8 +177,8 @@ export const ComponentsPage = () => {
160
177
  <div className="component-item">
161
178
  <h3>Input</h3>
162
179
  <div>
163
- <div className='ui form'>
164
- <div className='field'>
180
+ <div className="ui form">
181
+ <div className="field">
165
182
  <input type="text" placeholder="Search keybindings" defaultValue="" />
166
183
  </div>
167
184
  </div>
@@ -190,13 +207,30 @@ export const ComponentsPage = () => {
190
207
  <h3>Dropdown</h3>
191
208
  <div>
192
209
  <div className="ui selection dropdown active focus-outline">
193
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1em" height="1em" name="dropdown-chevron" className="svg-icon streamline dropdown-chevron dropdown icon">
194
- <path fill="currentColor" d="M11.96 19.318a2.09 2.09 0 0 1-1.663-.782L.617 7.682C.031 6.9.128 5.726.911 5.14c.782-.587 1.857-.587 2.444.195l8.41 9.583c.097.098.195.098.39 0l8.41-9.583c.684-.782 1.76-.88 2.542-.195s.88 1.76.195 2.542l-.098.098-9.582 10.854c-.391.39-1.076.684-1.662.684"></path>
210
+ <svg
211
+ xmlns="http://www.w3.org/2000/svg"
212
+ viewBox="0 0 24 24"
213
+ width="1em"
214
+ height="1em"
215
+ name="dropdown-chevron"
216
+ className="svg-icon streamline dropdown-chevron dropdown icon"
217
+ >
218
+ <path
219
+ fill="currentColor"
220
+ d="M11.96 19.318a2.09 2.09 0 0 1-1.663-.782L.617 7.682C.031 6.9.128 5.726.911 5.14c.782-.587 1.857-.587 2.444.195l8.41 9.583c.097.098.195.098.39 0l8.41-9.583c.684-.782 1.76-.88 2.542-.195s.88 1.76.195 2.542l-.098.098-9.582 10.854c-.391.39-1.076.684-1.662.684"
221
+ ></path>
195
222
  </svg>
196
- <div className="text">
197
- Inbox
198
- </div>
199
- <div className="menu visible" style={{ display: 'block', overflow: 'auto', opacity: 1, height: 'auto', willChange: 'transform' }}>
223
+ <div className="text">Inbox</div>
224
+ <div
225
+ className="menu visible"
226
+ style={{
227
+ display: 'block',
228
+ overflow: 'auto',
229
+ opacity: 1,
230
+ height: 'auto',
231
+ willChange: 'transform'
232
+ }}
233
+ >
200
234
  <div className="item " data-value="book:Bk5Ivk0T">
201
235
  <span style={{ paddingLeft: 0 }}>App Development</span>
202
236
  </div>
@@ -207,9 +241,7 @@ export const ComponentsPage = () => {
207
241
  </div>
208
242
  </div>
209
243
  </div>
210
-
211
-
212
244
  </div>
213
245
  </>
214
- );
215
- };
246
+ )
247
+ }
@@ -1,12 +1,11 @@
1
1
  export function getCSSVariables() {
2
- const computedStyles = document.body.computedStyleMap();
3
- const variables: Record<string, string> = {};
2
+ const computedStyles = document.body.computedStyleMap()
3
+ const variables: Record<string, string> = {}
4
4
  for (const [prop, val] of computedStyles) {
5
5
  if (prop.startsWith('--')) {
6
- variables[prop] = val.toString();
6
+ variables[prop] = val.toString()
7
7
  }
8
8
  }
9
9
 
10
-
11
10
  return variables
12
11
  }
@@ -9,4 +9,3 @@ export const Index = () => {
9
9
  </div>
10
10
  )
11
11
  }
12
-
@@ -1,4 +1,4 @@
1
- import { Link } from "react-router-dom";
1
+ import { Link } from 'react-router-dom'
2
2
 
3
3
  export const Nav = () => {
4
4
  return (
@@ -7,5 +7,5 @@ export const Nav = () => {
7
7
  <Link to="/tokens">Color Tokens</Link>
8
8
  <Link to="/components">Components</Link>
9
9
  </div>
10
- );
11
- };
10
+ )
11
+ }
@@ -6,18 +6,19 @@ export const VariablesPage = () => {
6
6
  return (
7
7
  <>
8
8
  <h2>Variables</h2>
9
- <div className='variable-list'>
10
- {themeVariableNames.map(variableName => {
9
+ <div className="variable-list">
10
+ {themeVariableNames.map((variableName) => {
11
11
  return (
12
- <div key={variableName} className='variable-item'>
13
- <div className='color-box' style={{
14
- background: `var(${variableName})`
15
- }}></div>
16
- <div className='data'>
17
- <div className='variable-name'>{variableName}</div>
18
- <div className='variable-value'>
19
- {cssVariables[variableName]}
20
- </div>
12
+ <div key={variableName} className="variable-item">
13
+ <div
14
+ className="color-box"
15
+ style={{
16
+ background: `var(${variableName})`
17
+ }}
18
+ ></div>
19
+ <div className="data">
20
+ <div className="variable-name">{variableName}</div>
21
+ <div className="variable-value">{cssVariables[variableName]}</div>
21
22
  </div>
22
23
  </div>
23
24
  )
@@ -26,4 +27,3 @@ export const VariablesPage = () => {
26
27
  </>
27
28
  )
28
29
  }
29
-
@@ -17,7 +17,7 @@ h2 {
17
17
  justify-content: center;
18
18
  gap: 0.6rem;
19
19
 
20
- &>.variable-item {
20
+ & > .variable-item {
21
21
  display: flex;
22
22
  flex-direction: row;
23
23
  gap: 0.6em;
@@ -38,7 +38,6 @@ h2 {
38
38
  }
39
39
  }
40
40
  }
41
-
42
41
  }
43
42
 
44
43
  .nav {
@@ -1,45 +1,45 @@
1
- import { StrictMode } from "react";
2
- import { createRoot } from "react-dom/client";
3
- import { createBrowserRouter, RouterProvider } from "react-router-dom";
4
- import { Index } from "./dev-server/index";
5
- import "./dev-server.css";
6
- import "@inkdropapp/css/reset.css";
7
- import "@inkdropapp/css/tokens.css";
8
- import "@inkdropapp/base-ui-theme/styles/theme.css";
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom'
4
+ import { Index } from './dev-server/index'
5
+ import './dev-server.css'
6
+ import '@inkdropapp/css/reset.css'
7
+ import '@inkdropapp/css/tokens.css'
8
+ import '@inkdropapp/base-ui-theme/styles/theme.css'
9
9
 
10
- import { ColorTokensPage } from "./dev-server/color-tokens";
11
- import { VariablesPage } from "./dev-server/variables";
12
- import { ComponentsPage } from "./dev-server/components";
10
+ import { ColorTokensPage } from './dev-server/color-tokens'
11
+ import { VariablesPage } from './dev-server/variables'
12
+ import { ComponentsPage } from './dev-server/components'
13
13
  const themePackageName = import.meta.env.THEME_NAME || ''
14
14
  const themeAppearance = import.meta.env.THEME_APPEARANCE
15
15
  const baseProjectPath = import.meta.env.BASE_PROJECT_PATH || ''
16
16
  const styleSheets: string[] = import.meta.env.STYLE_SHEETS || []
17
17
 
18
- const cssFiles = styleSheets.map(ss => `${baseProjectPath}/styles/${ss}`)
18
+ const cssFiles = styleSheets.map((ss) => `${baseProjectPath}/styles/${ss}`)
19
19
 
20
20
  cssFiles.forEach((file) => {
21
- import(file);
22
- });
21
+ import(/* @vite-ignore */ file)
22
+ })
23
23
 
24
- document.body.classList.add(`theme-${themePackageName}`);
24
+ document.body.classList.add(`theme-${themePackageName}`)
25
25
  if (themeAppearance) {
26
- document.body.classList.add(`${themeAppearance}-mode`);
26
+ document.body.classList.add(`${themeAppearance}-mode`)
27
27
  }
28
28
 
29
29
  const router = createBrowserRouter([
30
30
  {
31
- path: "/",
31
+ path: '/',
32
32
  element: <Index />,
33
33
  children: [
34
- { path: "", element: <VariablesPage /> },
35
- { path: "tokens", element: <ColorTokensPage /> },
36
- { path: "components", element: <ComponentsPage /> },
37
- ],
38
- },
39
- ]);
34
+ { path: '', element: <VariablesPage /> },
35
+ { path: 'tokens', element: <ColorTokensPage /> },
36
+ { path: 'components', element: <ComponentsPage /> }
37
+ ]
38
+ }
39
+ ])
40
40
 
41
- createRoot(document.getElementById("root")!).render(
41
+ createRoot(document.getElementById('root')!).render(
42
42
  <StrictMode>
43
43
  <RouterProvider router={router} />
44
- </StrictMode>,
45
- );
44
+ </StrictMode>
45
+ )
@@ -1,11 +1,12 @@
1
- import puppeteer from 'puppeteer';
2
- import { writeFile } from 'fs/promises';
3
- import path from 'path';
1
+ import puppeteer from 'puppeteer'
2
+ import { writeFile } from 'fs/promises'
3
+ import path from 'path'
4
4
  import { pathToFileURL } from 'url'
5
- import { Command } from 'commander';
6
- import packageJson from '../package.json';
5
+ import { Command } from 'commander'
6
+ import packageJson from '../package.json'
7
+ import { buildPreviewHTML, mapThemeVariables } from './palette'
7
8
 
8
- const program = new Command();
9
+ const program = new Command()
9
10
 
10
11
  // Define CLI options and help using commander
11
12
  program
@@ -14,74 +15,56 @@ program
14
15
  .version(packageJson.version)
15
16
  .option('-a, --appearance <light/dark>', 'Force the UI appearance ("light" or "dark")')
16
17
  .option('-o, --output <path>', 'Output file path (default: ./palette.json)', './palette.json')
17
- .parse(process.argv);
18
+ .parse(process.argv)
18
19
 
19
20
  // Parse options
20
- const options = program.opts();
21
- const outputPath = options.output as string;
22
- const appearance = options.appearance as 'light' | 'dark' | undefined;
21
+ const options = program.opts()
22
+ const outputPath = options.output as string
23
+ const appearance = options.appearance as 'light' | 'dark' | undefined
23
24
 
24
25
  // Function to extract theme CSS variables
25
26
  async function extractPalette(outputPath: string) {
26
- const themePackageJson = (await import(path.join(process.cwd(), 'package.json')))
27
- const themeVariableNames: string[] = (await import(`@inkdropapp/base-ui-theme/lib/variable-names.json`)).default;
27
+ const themePackageJson = await import(path.join(process.cwd(), 'package.json'))
28
+ const themeVariableNames: string[] = (await import(`@inkdropapp/css/ui.json`)).default
28
29
 
29
- const browser = await puppeteer.launch();
30
- const page = await browser.newPage();
30
+ const browser = await puppeteer.launch()
31
+ const page = await browser.newPage()
31
32
 
32
33
  page
33
- .on('console', message => {
34
+ .on('console', (message) => {
34
35
  if (message.type() === 'error') {
35
36
  console.error(`${message.type().substr(0, 3).toUpperCase()} ${message.text()}`)
36
37
  }
37
38
  })
38
- .on('pageerror', ({ message }) => console.error(message));
39
+ .on('pageerror', (error) => console.error(error instanceof Error ? error.message : error))
39
40
 
40
- const themeCSSFiles = (themePackageJson.styleSheets?.map((filePath: string) => (`<link rel="stylesheet" href="styles/${filePath}" />`)) || []).join('\n')
41
- const baseUrl = pathToFileURL(process.cwd()).toString() + '/';
42
- const content = `<!DOCTYPE html>
43
- <html>
44
- <head>
45
- <base href="${baseUrl}" />
46
- <link rel="stylesheet" href="node_modules/@inkdropapp/css/reset.css" />
47
- <link rel="stylesheet" href="node_modules/@inkdropapp/css/tokens.css" />
48
- <link rel="stylesheet" href="node_modules/@inkdropapp/css/tags.css" />
49
- <link rel="stylesheet" href="node_modules/@inkdropapp/base-ui-theme/styles/theme.css" />
50
- ${themeCSSFiles}
51
- </head>
52
- <body class="${themePackageJson.name} ${typeof appearance !== 'undefined' ? appearance + '-mode' : ''}">
53
- <h1>Hello</h1>
54
- </body>
55
- </html>
56
- `;
41
+ const baseUrl = pathToFileURL(process.cwd()).toString() + '/'
42
+ const content = buildPreviewHTML(themePackageJson, baseUrl, appearance)
57
43
 
58
- await page.goto(baseUrl);
59
- await page.setContent(content);
44
+ await page.goto(baseUrl)
45
+ await page.setContent(content)
60
46
 
61
47
  // Extract CSS variables
62
48
  const computedCSSVariables = await page.$eval('body', (body) => {
63
- const computedStyles = body.computedStyleMap();
64
- const variables: Record<string, string> = {};
49
+ const computedStyles = body.computedStyleMap()
50
+ const variables: Record<string, string> = {}
65
51
  for (const [prop, val] of computedStyles) {
66
52
  if (prop.startsWith('--')) {
67
- variables[prop] = val.toString();
53
+ variables[prop] = val.toString()
68
54
  }
69
55
  }
70
- return variables;
71
- });
56
+ return variables
57
+ })
72
58
 
73
- const themeCSSVariables = themeVariableNames.reduce((variables, name) => {
74
- variables[name] = computedCSSVariables[name];
75
- return variables;
76
- }, {} as Record<string, string>);
59
+ const themeCSSVariables = mapThemeVariables(themeVariableNames, computedCSSVariables)
77
60
 
78
61
  // Write to the output file
79
- const outputFilePath = path.resolve(outputPath);
80
- await writeFile(outputFilePath, JSON.stringify(themeCSSVariables, null, 2));
62
+ const outputFilePath = path.resolve(outputPath)
63
+ await writeFile(outputFilePath, JSON.stringify(themeCSSVariables, null, 2))
81
64
 
82
- await browser.close();
65
+ await browser.close()
83
66
  }
84
67
 
85
68
  extractPalette(outputPath)
86
69
  .then(() => console.log('Palette extraction complete!'))
87
- .catch(err => console.error('Error extracting palette:', err));
70
+ .catch((err) => console.error('Error extracting palette:', err))
@@ -0,0 +1,73 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { buildPreviewHTML, mapThemeVariables } from './palette'
3
+
4
+ describe('buildPreviewHTML', () => {
5
+ const baseUrl = 'file:///themes/acme/'
6
+
7
+ test('emits the base href so relative links resolve', () => {
8
+ const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
9
+ expect(html).toContain(`<base href="${baseUrl}" />`)
10
+ })
11
+
12
+ test('always includes the Inkdrop base stylesheets', () => {
13
+ const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
14
+ expect(html).toContain('node_modules/@inkdropapp/css/reset.css')
15
+ expect(html).toContain('node_modules/@inkdropapp/css/tokens.css')
16
+ expect(html).toContain('node_modules/@inkdropapp/css/tags.css')
17
+ expect(html).toContain('node_modules/@inkdropapp/base-ui-theme/styles/theme.css')
18
+ })
19
+
20
+ test('links each declared theme stylesheet under styles/', () => {
21
+ const html = buildPreviewHTML(
22
+ { name: 'acme', styleSheets: ['base.css', 'syntax.css'] },
23
+ baseUrl
24
+ )
25
+ expect(html).toContain('<link rel="stylesheet" href="styles/base.css" />')
26
+ expect(html).toContain('<link rel="stylesheet" href="styles/syntax.css" />')
27
+ })
28
+
29
+ test('omits theme stylesheet links when none are declared', () => {
30
+ const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
31
+ expect(html).not.toContain('href="styles/')
32
+ })
33
+
34
+ test('sets the body class to the theme name', () => {
35
+ const html = buildPreviewHTML({ name: 'acme-dark' }, baseUrl)
36
+ expect(html).toContain('<body class="acme-dark">')
37
+ })
38
+
39
+ test('appends an appearance-mode class when an appearance is forced', () => {
40
+ const html = buildPreviewHTML({ name: 'acme' }, baseUrl, 'dark')
41
+ expect(html).toContain('<body class="acme dark-mode">')
42
+ })
43
+
44
+ test('adds no appearance class when appearance is omitted', () => {
45
+ const html = buildPreviewHTML({ name: 'acme' }, baseUrl)
46
+ expect(html).not.toContain('-mode')
47
+ })
48
+ })
49
+
50
+ describe('mapThemeVariables', () => {
51
+ test('maps each declared name to its computed value, ignoring extras', () => {
52
+ const result = mapThemeVariables(['--accent', '--bg'], {
53
+ '--accent': '#f00',
54
+ '--bg': '#fff',
55
+ '--extra': '#000'
56
+ })
57
+ expect(result).toEqual({ '--accent': '#f00', '--bg': '#fff' })
58
+ })
59
+
60
+ test('preserves the declaration order of the variable names', () => {
61
+ const result = mapThemeVariables(['--b', '--a'], { '--a': '1', '--b': '2' })
62
+ expect(Object.keys(result)).toEqual(['--b', '--a'])
63
+ })
64
+
65
+ test('leaves variables absent from the computed set undefined', () => {
66
+ const result = mapThemeVariables(['--missing'], { '--accent': '#f00' })
67
+ expect(result['--missing']).toBeUndefined()
68
+ })
69
+
70
+ test('returns an empty object when no names are declared', () => {
71
+ expect(mapThemeVariables([], { '--accent': '#f00' })).toEqual({})
72
+ })
73
+ })
package/src/palette.ts ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pure helpers for palette extraction, kept free of puppeteer / fs / process
3
+ * so they can be unit-tested directly. The orchestration that drives a headless
4
+ * browser lives in `generate-palette.ts`.
5
+ */
6
+
7
+ /** Theme metadata read from a theme package's `package.json`. */
8
+ export interface ThemePackage {
9
+ name?: string
10
+ styleSheets?: string[]
11
+ }
12
+
13
+ export type ThemeAppearance = 'light' | 'dark'
14
+
15
+ /**
16
+ * Builds the standalone HTML document that is loaded into a headless browser to
17
+ * compute a theme's CSS custom properties.
18
+ *
19
+ * It pulls in Inkdrop's base CSS plus the theme's own stylesheets so that the
20
+ * computed style of `<body>` resolves every themed variable. A `<base href>` is
21
+ * emitted so the relative `node_modules/` and `styles/` links resolve against
22
+ * the theme project root.
23
+ *
24
+ * @param theme - The theme package's metadata (`name`, `styleSheets`).
25
+ * @param baseUrl - File URL used as the document `<base href>`.
26
+ * @param appearance - Optional forced appearance; adds a `<appearance>-mode`
27
+ * body class when provided.
28
+ * @returns The full HTML document as a string.
29
+ */
30
+ export function buildPreviewHTML(
31
+ theme: ThemePackage,
32
+ baseUrl: string,
33
+ appearance?: ThemeAppearance
34
+ ): string {
35
+ const themeCSSLinks = (theme.styleSheets ?? [])
36
+ .map((filePath) => `<link rel="stylesheet" href="styles/${filePath}" />`)
37
+ .join('\n')
38
+
39
+ const bodyClass = [theme.name, appearance && `${appearance}-mode`].filter(Boolean).join(' ')
40
+
41
+ return `<!DOCTYPE html>
42
+ <html>
43
+ <head>
44
+ <base href="${baseUrl}" />
45
+ <link rel="stylesheet" href="node_modules/@inkdropapp/css/reset.css" />
46
+ <link rel="stylesheet" href="node_modules/@inkdropapp/css/tokens.css" />
47
+ <link rel="stylesheet" href="node_modules/@inkdropapp/css/tags.css" />
48
+ <link rel="stylesheet" href="node_modules/@inkdropapp/base-ui-theme/styles/theme.css" />
49
+ ${themeCSSLinks}
50
+ </head>
51
+ <body class="${bodyClass}">
52
+ <h1>Hello</h1>
53
+ </body>
54
+ </html>
55
+ `
56
+ }
57
+
58
+ /**
59
+ * Projects the raw computed CSS variables onto the theme's declared variable
60
+ * names, preserving their declaration order. Names absent from `computed` map
61
+ * to `undefined` (and are therefore dropped by `JSON.stringify`).
62
+ *
63
+ * @param variableNames - The theme's declared CSS variable names.
64
+ * @param computed - All `--*` custom properties read from the rendered body.
65
+ * @returns A record of `variableName -> computed value`.
66
+ */
67
+ export function mapThemeVariables(
68
+ variableNames: string[],
69
+ computed: Record<string, string>
70
+ ): Record<string, string> {
71
+ return variableNames.reduce(
72
+ (acc, name) => {
73
+ acc[name] = computed[name]
74
+ return acc
75
+ },
76
+ {} as Record<string, string>
77
+ )
78
+ }
package/tsconfig.json CHANGED
@@ -2,27 +2,24 @@
2
2
  "compilerOptions": {
3
3
  "target": "ESNext",
4
4
  "useDefineForClassFields": true,
5
- "lib": [
6
- "ES2020",
7
- "DOM",
8
- "DOM.Iterable"
9
- ],
5
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
10
6
  "module": "ESNext",
11
7
  "skipLibCheck": true,
12
8
  /* Bundler mode */
13
- "moduleResolution": "Node",
9
+ "moduleResolution": "bundler",
14
10
  "allowImportingTsExtensions": true,
15
11
  "isolatedModules": true,
16
12
  "moduleDetection": "force",
17
13
  "noEmit": true,
18
14
  "jsx": "react-jsx",
15
+ "types": ["bun"],
19
16
  /* Linting */
20
17
  "strict": true,
21
18
  "noUnusedLocals": true,
22
19
  "noUnusedParameters": true,
23
- "noFallthroughCasesInSwitch": true
20
+ "noFallthroughCasesInSwitch": true,
21
+ "allowSyntheticDefaultImports": true,
22
+ "resolveJsonModule": true
24
23
  },
25
- "include": [
26
- "src"
27
- ]
24
+ "include": ["src"]
28
25
  }
package/vite.config.ts CHANGED
@@ -5,11 +5,8 @@ import { readFile } from 'fs/promises'
5
5
  const baseProjectPath = process.env.BASE_PROJECT_PATH || process.cwd()
6
6
  console.log('Base project path:', baseProjectPath)
7
7
  const packageJson = JSON.parse(
8
- await readFile(
9
- `${baseProjectPath}/package.json`,
10
- { encoding: 'utf-8' }
11
- )
12
- );
8
+ await readFile(`${baseProjectPath}/package.json`, { encoding: 'utf-8' })
9
+ )
13
10
  const { styleSheets } = packageJson
14
11
 
15
12
  // https://vitejs.dev/config/
@@ -30,11 +27,20 @@ export default defineConfig({
30
27
  'import.meta.env.STYLE_SHEETS': JSON.stringify(styleSheets)
31
28
  },
32
29
  server: {
30
+ host: '127.0.0.1',
31
+ port: Number(process.env.PORT) || 5180,
32
+ strictPort: false,
33
33
  fs: {
34
34
  strict: false
35
35
  }
36
36
  },
37
37
  optimizeDeps: {
38
- include: ['react', 'react-dom', 'react-dom/client', 'react/jsx-dev-runtime', 'react/jsx-runtime']
38
+ include: [
39
+ 'react',
40
+ 'react-dom',
41
+ 'react-dom/client',
42
+ 'react/jsx-dev-runtime',
43
+ 'react/jsx-runtime'
44
+ ]
39
45
  }
40
46
  })
package/bun.lockb DELETED
Binary file