aeon-ui-engine 1.2.0 → 1.3.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/CHANGELOG.md +22 -0
- package/package.json +7 -2
- package/packages/core/dist/index.js +13 -3
- package/packages/core/src/brand-theme.ts +14 -3
- package/packages/panda/src/theme/consumer-shell.css +131 -10
- package/packages/panda/src/theme/electron.css +87 -3
- package/scripts/bump-version.mjs +93 -0
- package/scripts/require-version-bump.mjs +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.1] - 2026-07-30
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Describe this release.
|
|
8
|
+
|
|
3
9
|
All notable changes to **aeon-ui-engine** are documented here.
|
|
4
10
|
|
|
5
11
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
12
|
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
13
|
|
|
14
|
+
## [1.3.0] - 2026-07-30
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **Consumer shell defaults** aligned with dense Electron wallet chrome:
|
|
19
|
+
- Headless Dialog backdrop / positioner / content (so Prompt works without `@aeon-ui/ui` recipe classes)
|
|
20
|
+
- Prompt title + description typography (`--aeon-font-display`)
|
|
21
|
+
- StatusBanner sticky top placement
|
|
22
|
+
- AppNav dense 28px breadcrumb / back rail
|
|
23
|
+
- **`applyBrandPalette` fonts** also set `--fonts-ui`, `--fonts-body`, `--fonts-display` so recipes pick up brand typefaces
|
|
24
|
+
- **Electron CSS** — fill-height `.app-shell` / `.stage`, `.titlebar` alias, compact 3px scrollbars, hover-reveal scroll helper
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
|
|
28
|
+
- [PRODUCT_SHELL.md](./docs/PRODUCT_SHELL.md) — headless Prompt requires consumer-shell; do not re-copy shell CSS into products
|
|
29
|
+
|
|
8
30
|
## [1.2.0] - 2026-07-30
|
|
9
31
|
|
|
10
32
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aeon-ui-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,12 @@
|
|
|
35
35
|
"test:headless": "pnpm run build:headless && pnpm --filter @aeon-ui/core --filter @aeon-ui/primitives --filter @aeon-ui/react run test",
|
|
36
36
|
"changeset": "changeset",
|
|
37
37
|
"version-packages": "changeset version",
|
|
38
|
-
"release": "pnpm build && changeset publish"
|
|
38
|
+
"release": "pnpm build && changeset publish",
|
|
39
|
+
"version:patch": "node scripts/bump-version.mjs patch",
|
|
40
|
+
"version:minor": "node scripts/bump-version.mjs minor",
|
|
41
|
+
"version:major": "node scripts/bump-version.mjs major",
|
|
42
|
+
"version:push": "node scripts/bump-version.mjs patch --push",
|
|
43
|
+
"version:check": "node scripts/require-version-bump.mjs"
|
|
39
44
|
},
|
|
40
45
|
"devDependencies": {
|
|
41
46
|
"@changesets/cli": "^2.29.0",
|
|
@@ -623,9 +623,19 @@ function brandPaletteToCssVars(palette) {
|
|
|
623
623
|
"--shadows-focus-ring": palette.focusRing,
|
|
624
624
|
"--aeon-grid-line": palette.gridLine
|
|
625
625
|
};
|
|
626
|
-
if (palette.font)
|
|
627
|
-
|
|
628
|
-
|
|
626
|
+
if (palette.font) {
|
|
627
|
+
vars["--aeon-font"] = palette.font;
|
|
628
|
+
vars["--fonts-ui"] = palette.font;
|
|
629
|
+
vars["--fonts-body"] = palette.font;
|
|
630
|
+
}
|
|
631
|
+
if (palette.fontDisplay) {
|
|
632
|
+
vars["--aeon-font-display"] = palette.fontDisplay;
|
|
633
|
+
vars["--fonts-display"] = palette.fontDisplay;
|
|
634
|
+
}
|
|
635
|
+
if (palette.radius) {
|
|
636
|
+
vars["--aeon-radius"] = palette.radius;
|
|
637
|
+
vars["--radii-aeon"] = palette.radius;
|
|
638
|
+
}
|
|
629
639
|
return vars;
|
|
630
640
|
}
|
|
631
641
|
function applyBrandPalette(input, options = {}) {
|
|
@@ -97,9 +97,20 @@ export function brandPaletteToCssVars(palette: BrandPalette): Record<string, str
|
|
|
97
97
|
'--shadows-focus-ring': palette.focusRing,
|
|
98
98
|
'--aeon-grid-line': palette.gridLine,
|
|
99
99
|
}
|
|
100
|
-
if (palette.font)
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
if (palette.font) {
|
|
101
|
+
// Recipes read --fonts-ui / --fonts-body; also expose --aeon-font for shells.
|
|
102
|
+
vars['--aeon-font'] = palette.font
|
|
103
|
+
vars['--fonts-ui'] = palette.font
|
|
104
|
+
vars['--fonts-body'] = palette.font
|
|
105
|
+
}
|
|
106
|
+
if (palette.fontDisplay) {
|
|
107
|
+
vars['--aeon-font-display'] = palette.fontDisplay
|
|
108
|
+
vars['--fonts-display'] = palette.fontDisplay
|
|
109
|
+
}
|
|
110
|
+
if (palette.radius) {
|
|
111
|
+
vars['--aeon-radius'] = palette.radius
|
|
112
|
+
vars['--radii-aeon'] = palette.radius
|
|
113
|
+
}
|
|
103
114
|
return vars
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -1,21 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Consumer shell recipes — baseline look for Prompt, StatusBanner, AppNav
|
|
2
|
+
* Consumer shell recipes — baseline look for Prompt, StatusBanner, AppNav,
|
|
3
|
+
* and headless Dialog chrome.
|
|
3
4
|
* Opt into brand color via applyBrandPalette(); these rules only use Aeon CSS vars.
|
|
4
5
|
* Intentionally restrained — products still own marketing atmosphere.
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
/* ── Headless Dialog (Prompt composes Dialog without recipe class names) ── */
|
|
9
|
+
|
|
10
|
+
[data-aeon-scope='dialog'][data-aeon-part='backdrop'] {
|
|
11
|
+
position: fixed;
|
|
12
|
+
inset: 0;
|
|
13
|
+
z-index: 1000;
|
|
14
|
+
background: color-mix(in srgb, #000 72%, transparent);
|
|
15
|
+
animation: aeon-shell-rise 200ms ease both;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
[data-aeon-scope='dialog'][data-aeon-part='positioner'] {
|
|
19
|
+
position: fixed;
|
|
20
|
+
inset: 0;
|
|
21
|
+
z-index: 1001;
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
padding: 16px;
|
|
26
|
+
overflow-y: auto;
|
|
27
|
+
overscroll-behavior: contain;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
[data-aeon-scope='dialog'][data-aeon-part='content'] {
|
|
31
|
+
position: relative;
|
|
32
|
+
width: min(440px, 100%);
|
|
33
|
+
margin: auto;
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
max-height: calc(100vh - 32px);
|
|
36
|
+
overflow-y: auto;
|
|
37
|
+
border-radius: calc(var(--aeon-radius, 0.5rem) + 4px);
|
|
38
|
+
border: 1px solid var(--colors-aeon-border);
|
|
39
|
+
background: var(--colors-aeon-surface, var(--colors-aeon-bg));
|
|
40
|
+
color: var(--colors-aeon-text);
|
|
41
|
+
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55);
|
|
42
|
+
padding: 20px 22px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes aeon-shell-rise {
|
|
46
|
+
from {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transform: translateY(6px);
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
transform: none;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── Prompt ── */
|
|
57
|
+
|
|
7
58
|
[data-aeon-scope='prompt'][data-aeon-part='root'] {
|
|
8
59
|
display: contents;
|
|
9
60
|
}
|
|
10
61
|
|
|
62
|
+
[data-aeon-scope='dialog'][data-aeon-part='title'],
|
|
63
|
+
[data-aeon-scope='prompt'][data-aeon-part='title'] {
|
|
64
|
+
margin: 0;
|
|
65
|
+
font-family: var(--aeon-font-display, var(--fonts-display, inherit));
|
|
66
|
+
font-size: 1.05rem;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
letter-spacing: 0.06em;
|
|
69
|
+
text-transform: uppercase;
|
|
70
|
+
color: var(--colors-aeon-text);
|
|
71
|
+
line-height: 1.25;
|
|
72
|
+
}
|
|
73
|
+
|
|
11
74
|
[data-aeon-scope='prompt'][data-aeon-part='eyebrow'] {
|
|
12
75
|
margin: 0 0 8px;
|
|
13
76
|
color: var(--colors-aeon-accent);
|
|
77
|
+
font-family: var(--aeon-font-display, var(--fonts-display, inherit));
|
|
14
78
|
font-size: 0.72rem;
|
|
15
79
|
letter-spacing: 0.14em;
|
|
16
80
|
text-transform: uppercase;
|
|
17
81
|
}
|
|
18
82
|
|
|
83
|
+
[data-aeon-scope='prompt'][data-aeon-part='description'],
|
|
84
|
+
[data-aeon-scope='dialog'][data-aeon-part='description'] {
|
|
85
|
+
margin: 8px 0 0;
|
|
86
|
+
color: var(--colors-aeon-muted);
|
|
87
|
+
font-size: 0.9rem;
|
|
88
|
+
line-height: 1.45;
|
|
89
|
+
}
|
|
90
|
+
|
|
19
91
|
[data-aeon-scope='prompt'][data-aeon-part='meta'] {
|
|
20
92
|
display: grid;
|
|
21
93
|
gap: 10px;
|
|
@@ -45,6 +117,8 @@
|
|
|
45
117
|
border-radius: var(--aeon-radius, 10px);
|
|
46
118
|
padding: 10px 14px;
|
|
47
119
|
border: 1px solid transparent;
|
|
120
|
+
letter-spacing: 0.02em;
|
|
121
|
+
text-transform: none;
|
|
48
122
|
}
|
|
49
123
|
|
|
50
124
|
[data-aeon-scope='prompt'][data-aeon-part='primary'] {
|
|
@@ -59,7 +133,19 @@
|
|
|
59
133
|
border-color: var(--colors-aeon-border);
|
|
60
134
|
}
|
|
61
135
|
|
|
136
|
+
/* Product `.btn` inside Prompt / StatusBanner — sentence case, not all-caps CTAs */
|
|
137
|
+
[data-aeon-scope='statusBanner'] button.btn,
|
|
138
|
+
[data-aeon-scope='prompt'] button.btn {
|
|
139
|
+
text-transform: none;
|
|
140
|
+
letter-spacing: 0.02em;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ── StatusBanner (sticky top chrome) ── */
|
|
144
|
+
|
|
62
145
|
[data-aeon-scope='statusBanner'][data-aeon-part='root'] {
|
|
146
|
+
position: sticky;
|
|
147
|
+
top: 0;
|
|
148
|
+
z-index: 40;
|
|
63
149
|
display: flex;
|
|
64
150
|
align-items: center;
|
|
65
151
|
justify-content: space-between;
|
|
@@ -90,11 +176,24 @@
|
|
|
90
176
|
flex-shrink: 0;
|
|
91
177
|
}
|
|
92
178
|
|
|
179
|
+
/* ── AppNav breadcrumb / back (dense 28px rail) ── */
|
|
180
|
+
|
|
93
181
|
[data-aeon-scope='appNav'][data-aeon-part='breadcrumb'] {
|
|
94
182
|
display: flex;
|
|
95
|
-
flex-wrap:
|
|
96
|
-
gap: 6px;
|
|
183
|
+
flex-wrap: nowrap;
|
|
97
184
|
align-items: center;
|
|
185
|
+
gap: 0;
|
|
186
|
+
min-width: 0;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
margin: 0;
|
|
189
|
+
height: 28px;
|
|
190
|
+
font-family: var(--aeon-font-display, var(--fonts-display, inherit));
|
|
191
|
+
font-size: 0.78rem;
|
|
192
|
+
font-weight: 700;
|
|
193
|
+
letter-spacing: 0.1em;
|
|
194
|
+
text-transform: uppercase;
|
|
195
|
+
line-height: 28px;
|
|
196
|
+
color: var(--colors-aeon-text);
|
|
98
197
|
}
|
|
99
198
|
|
|
100
199
|
[data-aeon-scope='appNav'][data-aeon-part='crumb'] {
|
|
@@ -104,26 +203,48 @@
|
|
|
104
203
|
color: var(--colors-aeon-muted);
|
|
105
204
|
cursor: pointer;
|
|
106
205
|
padding: 0;
|
|
206
|
+
height: 28px;
|
|
207
|
+
line-height: 28px;
|
|
208
|
+
letter-spacing: inherit;
|
|
209
|
+
text-transform: inherit;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
[data-aeon-scope='appNav'][data-aeon-part='crumb']:hover {
|
|
213
|
+
color: var(--colors-aeon-accent);
|
|
107
214
|
}
|
|
108
215
|
|
|
109
216
|
[data-aeon-scope='appNav'][data-aeon-part='crumb'][data-aeon-state~='current'] {
|
|
110
217
|
color: var(--colors-aeon-text);
|
|
111
|
-
font-weight:
|
|
218
|
+
font-weight: 700;
|
|
112
219
|
cursor: default;
|
|
113
220
|
}
|
|
114
221
|
|
|
115
222
|
[data-aeon-scope='appNav'][data-aeon-part='crumb']:not(:last-child)::after {
|
|
116
223
|
content: '/';
|
|
117
|
-
margin
|
|
118
|
-
color: var(--colors-aeon-
|
|
224
|
+
margin: 0 0.45em;
|
|
225
|
+
color: var(--colors-aeon-muted);
|
|
226
|
+
font-weight: 500;
|
|
119
227
|
pointer-events: none;
|
|
120
228
|
}
|
|
121
229
|
|
|
122
230
|
[data-aeon-scope='appNav'][data-aeon-part='back'] {
|
|
123
|
-
|
|
124
|
-
|
|
231
|
+
appearance: none;
|
|
232
|
+
flex-shrink: 0;
|
|
233
|
+
width: 28px;
|
|
234
|
+
height: 28px;
|
|
235
|
+
margin: 0;
|
|
236
|
+
padding: 0;
|
|
125
237
|
border: 0;
|
|
126
|
-
|
|
238
|
+
border-radius: var(--aeon-radius, 0.5rem);
|
|
239
|
+
background: transparent;
|
|
240
|
+
color: var(--colors-aeon-muted);
|
|
241
|
+
display: inline-grid;
|
|
242
|
+
place-items: center;
|
|
127
243
|
cursor: pointer;
|
|
128
|
-
|
|
244
|
+
transition: color 140ms ease, background 140ms ease;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
[data-aeon-scope='appNav'][data-aeon-part='back']:hover {
|
|
248
|
+
color: var(--colors-aeon-accent);
|
|
249
|
+
background: color-mix(in srgb, var(--colors-aeon-accent) 10%, transparent);
|
|
129
250
|
}
|
|
@@ -1,9 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optional Electron chrome helpers.
|
|
3
3
|
* Import only in desktop shells: import '@aeon-ui/panda/electron.css'
|
|
4
|
+
*
|
|
5
|
+
* Defaults aligned with dense wallet / Electron fill-height layouts.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
/* Compact scrollbars on Electron platforms */
|
|
9
|
+
html[data-aeon-platform],
|
|
10
|
+
html.platform-darwin {
|
|
11
|
+
--aeon-scrollbar-size: 3px;
|
|
12
|
+
--aeon-scrollbar-track: transparent;
|
|
13
|
+
--aeon-scrollbar-thumb: color-mix(in srgb, var(--colors-aeon-muted, #a1a1aa) 35%, transparent);
|
|
14
|
+
--aeon-scrollbar-thumb-hover: color-mix(in srgb, var(--colors-aeon-muted, #a1a1aa) 55%, transparent);
|
|
15
|
+
--aeon-scrollbar-corner: transparent;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
html[data-aeon-platform],
|
|
19
|
+
html[data-aeon-platform] body,
|
|
20
|
+
html[data-aeon-platform] #root,
|
|
21
|
+
html.platform-darwin,
|
|
22
|
+
html.platform-darwin body,
|
|
23
|
+
html.platform-darwin #root {
|
|
24
|
+
height: 100%;
|
|
25
|
+
min-height: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
html[data-aeon-platform] body,
|
|
29
|
+
html.platform-darwin body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
font-family: var(--aeon-font, var(--fonts-ui, system-ui, sans-serif));
|
|
33
|
+
color: var(--colors-aeon-text);
|
|
34
|
+
background: var(--colors-aeon-bg);
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
letter-spacing: 0.01em;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.aeon-app-shell,
|
|
40
|
+
.app-shell {
|
|
41
|
+
height: 100%;
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-rows: auto 1fr;
|
|
44
|
+
min-height: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.aeon-stage,
|
|
48
|
+
.stage {
|
|
49
|
+
height: 100%;
|
|
50
|
+
min-height: 0;
|
|
51
|
+
overflow: auto;
|
|
52
|
+
padding: 20px clamp(14px, 3vw, 28px) 28px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@media (max-width: 640px) {
|
|
56
|
+
.aeon-stage,
|
|
57
|
+
.stage {
|
|
58
|
+
padding: 16px 12px 24px;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.aeon-titlebar,
|
|
63
|
+
.titlebar {
|
|
7
64
|
display: flex;
|
|
8
65
|
align-items: center;
|
|
9
66
|
justify-content: space-between;
|
|
@@ -18,13 +75,40 @@
|
|
|
18
75
|
.aeon-titlebar button,
|
|
19
76
|
.aeon-titlebar a,
|
|
20
77
|
.aeon-titlebar input,
|
|
21
|
-
.aeon-titlebar [data-aeon-no-drag]
|
|
78
|
+
.aeon-titlebar [data-aeon-no-drag],
|
|
79
|
+
.titlebar button,
|
|
80
|
+
.titlebar a,
|
|
81
|
+
.titlebar input,
|
|
82
|
+
.titlebar [data-aeon-no-drag] {
|
|
22
83
|
-webkit-app-region: no-drag;
|
|
23
84
|
app-region: no-drag;
|
|
24
85
|
}
|
|
25
86
|
|
|
26
87
|
html.platform-darwin .aeon-titlebar,
|
|
27
|
-
html
|
|
88
|
+
html.platform-darwin .titlebar,
|
|
89
|
+
html[data-aeon-platform='darwin'] .aeon-titlebar,
|
|
90
|
+
html[data-aeon-platform='darwin'] .titlebar {
|
|
28
91
|
/* hiddenInset traffic lights */
|
|
29
92
|
padding-top: 36px;
|
|
30
93
|
}
|
|
94
|
+
|
|
95
|
+
/* Hover-reveal scrollbars for dense nav stages */
|
|
96
|
+
.aeon-scroll-hover,
|
|
97
|
+
[data-aeon-scroll='hover'] {
|
|
98
|
+
scrollbar-color: transparent transparent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.aeon-scroll-hover::-webkit-scrollbar-thumb,
|
|
102
|
+
[data-aeon-scroll='hover']::-webkit-scrollbar-thumb {
|
|
103
|
+
background: transparent;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.aeon-scroll-hover:hover,
|
|
107
|
+
[data-aeon-scroll='hover']:hover {
|
|
108
|
+
scrollbar-color: var(--aeon-scrollbar-thumb) transparent;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.aeon-scroll-hover:hover::-webkit-scrollbar-thumb,
|
|
112
|
+
[data-aeon-scroll='hover']:hover::-webkit-scrollbar-thumb {
|
|
113
|
+
background: var(--aeon-scrollbar-thumb);
|
|
114
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bump aeon-ui-engine semver, changelog, tag, optionally push + npm publish.
|
|
4
|
+
*
|
|
5
|
+
* node scripts/bump-version.mjs patch|minor|major [--push] [--publish] [--no-commit]
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'node:child_process'
|
|
8
|
+
import fs from 'node:fs'
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
|
|
12
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
13
|
+
const pkgPath = path.join(root, 'package.json')
|
|
14
|
+
const changelogPath = path.join(root, 'CHANGELOG.md')
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2)
|
|
17
|
+
const bump = args.find((a) => a === 'patch' || a === 'minor' || a === 'major')
|
|
18
|
+
const noCommit = args.includes('--no-commit')
|
|
19
|
+
const doPush = args.includes('--push')
|
|
20
|
+
const doPublish = args.includes('--publish')
|
|
21
|
+
|
|
22
|
+
if (!bump) {
|
|
23
|
+
console.error('Usage: bump-version.mjs patch|minor|major [--push] [--publish] [--no-commit]')
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseSemver(v) {
|
|
28
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim())
|
|
29
|
+
if (!m) throw new Error(`Invalid semver: ${v}`)
|
|
30
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function format({ major, minor, patch }) {
|
|
34
|
+
return `${major}.${minor}.${patch}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function bumpSemver(v, kind) {
|
|
38
|
+
const s = parseSemver(v)
|
|
39
|
+
if (kind === 'major') return format({ major: s.major + 1, minor: 0, patch: 0 })
|
|
40
|
+
if (kind === 'minor') return format({ major: s.major, minor: s.minor + 1, patch: 0 })
|
|
41
|
+
return format({ major: s.major, minor: s.minor, patch: s.patch + 1 })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function today() {
|
|
45
|
+
return new Date().toISOString().slice(0, 10)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function prependChangelog(version) {
|
|
49
|
+
const entry = `## [${version}] - ${today()}\n\n### Changed\n\n- Describe this release.\n\n`
|
|
50
|
+
if (!fs.existsSync(changelogPath)) {
|
|
51
|
+
fs.writeFileSync(changelogPath, `# Changelog\n\n${entry}`)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
const existing = fs.readFileSync(changelogPath, 'utf8')
|
|
55
|
+
if (existing.includes(`## [${version}]`)) return
|
|
56
|
+
if (existing.startsWith('# Changelog')) {
|
|
57
|
+
fs.writeFileSync(changelogPath, existing.replace('# Changelog\n\n', `# Changelog\n\n${entry}`))
|
|
58
|
+
} else {
|
|
59
|
+
fs.writeFileSync(changelogPath, `# Changelog\n\n${entry}${existing}`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
64
|
+
const prev = pkg.version
|
|
65
|
+
const next = bumpSemver(prev, bump)
|
|
66
|
+
pkg.version = next
|
|
67
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
|
|
68
|
+
prependChangelog(next)
|
|
69
|
+
console.log(`aeon-ui-engine ${prev} → ${next}`)
|
|
70
|
+
|
|
71
|
+
if (!noCommit) {
|
|
72
|
+
execSync('git add package.json CHANGELOG.md', { cwd: root, stdio: 'inherit' })
|
|
73
|
+
execSync(`git commit -m "Release aeon-ui-engine v${next}"`, { cwd: root, stdio: 'inherit' })
|
|
74
|
+
try {
|
|
75
|
+
execSync(`git tag v${next}`, { cwd: root, stdio: 'inherit' })
|
|
76
|
+
} catch {
|
|
77
|
+
console.warn(`Tag v${next} already exists`)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (doPush) {
|
|
82
|
+
execSync('git push origin HEAD', { cwd: root, stdio: 'inherit' })
|
|
83
|
+
execSync(`git push origin v${next}`, { cwd: root, stdio: 'inherit' })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (doPublish) {
|
|
87
|
+
execSync('npm publish --access public', { cwd: root, stdio: 'inherit' })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!doPush) {
|
|
91
|
+
console.log(`Tagged v${next}. Push: git push && git push origin v${next}`)
|
|
92
|
+
console.log('Or: npm run version:push')
|
|
93
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fail if package.json version is not strictly newer than the latest remote tag (v*).
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'node:child_process'
|
|
6
|
+
import fs from 'node:fs'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
11
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
12
|
+
const local = pkg.version
|
|
13
|
+
|
|
14
|
+
function parse(v) {
|
|
15
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(v).replace(/^v/, ''))
|
|
16
|
+
if (!m) return null
|
|
17
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function gt(a, b) {
|
|
21
|
+
for (let i = 0; i < 3; i++) {
|
|
22
|
+
if (a[i] > b[i]) return true
|
|
23
|
+
if (a[i] < b[i]) return false
|
|
24
|
+
}
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
execSync('git fetch --tags --quiet', { cwd: root, stdio: 'ignore' })
|
|
30
|
+
} catch {
|
|
31
|
+
/* offline */
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let remoteTag = ''
|
|
35
|
+
try {
|
|
36
|
+
remoteTag = execSync(
|
|
37
|
+
'git describe --tags --abbrev=0 origin/main 2>/dev/null || git describe --tags --abbrev=0',
|
|
38
|
+
{ cwd: root, encoding: 'utf8' },
|
|
39
|
+
).trim()
|
|
40
|
+
} catch {
|
|
41
|
+
remoteTag = ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const localP = parse(local)
|
|
45
|
+
if (!localP) {
|
|
46
|
+
console.error(`Invalid package.json version: ${local}`)
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!remoteTag) {
|
|
51
|
+
console.log(`No remote tag yet — allowing push of v${local}`)
|
|
52
|
+
process.exit(0)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const remoteP = parse(remoteTag)
|
|
56
|
+
if (!remoteP) {
|
|
57
|
+
console.log(`Unparsed remote tag ${remoteTag} — allowing push`)
|
|
58
|
+
process.exit(0)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!gt(localP, remoteP)) {
|
|
62
|
+
console.error(`
|
|
63
|
+
Refusing push: aeon-ui-engine ${local} is not newer than remote tag ${remoteTag}.
|
|
64
|
+
|
|
65
|
+
npm run version:push
|
|
66
|
+
`)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`Version OK: ${local} > ${remoteTag.replace(/^v/, '')}`)
|