uifolio 0.4.0 → 0.5.3
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 +171 -157
- package/cli/uifolio.mjs +644 -354
- package/package.json +3 -2
- package/src/components/ui/audio-player.tsx +1 -1
- package/src/components/ui/calendar.tsx +6 -6
- package/src/components/ui/choice.tsx +1 -1
- package/src/components/ui/color-picker.tsx +14 -14
- package/src/components/ui/command-menu.tsx +2 -2
- package/src/components/ui/file-upload.tsx +10 -10
- package/src/components/ui/icon-picker.tsx +13 -13
- package/src/components/ui/motion.tsx +10 -8
- package/src/components/ui/otp-input.tsx +1 -1
- package/src/components/ui/resizable.tsx +6 -6
- package/src/components/ui/rich-text-editor.tsx +8 -8
- package/src/components/ui/stories.tsx +3 -3
- package/src/components/ui/time-picker.tsx +1 -1
- package/src/components/ui/video-player.tsx +50 -50
- package/src/lib/date.ts +30 -30
- package/src/lib/uploader.ts +13 -13
- package/src/lib/use-controllable.ts +9 -9
- package/src/lib/use-hotkeys.ts +1 -1
- package/src/lib/use-theme.ts +2 -2
- package/src/lib/video.ts +12 -12
- package/src/lib/vtt.ts +1 -1
- package/src/components/ui/stories-tokens.ts +0 -18
package/README.md
CHANGED
|
@@ -4,135 +4,149 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/uifolio)
|
|
5
5
|
[](https://www.npmjs.com/package/uifolio)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
My component and animation library: the code lives in your project, gets copied and edited.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
The `uifolio` npm package contains the CLI, the 14 components and their 9 helpers — nothing else.
|
|
10
|
+
The demo site (Vite, animations, playgrounds) lives in the project repository and is not part
|
|
11
|
+
of the package.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Getting started
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install
|
|
17
|
-
npm run dev #
|
|
18
|
-
npm run build # typecheck + build
|
|
19
|
-
npm run preview #
|
|
17
|
+
npm run dev # demo server (http://localhost:5173)
|
|
18
|
+
npm run build # typecheck + production build
|
|
19
|
+
npm run preview # serve the build
|
|
20
20
|
npm run lint # oxlint
|
|
21
|
-
npm test # tests
|
|
21
|
+
npm test # CLI tests (copy, color, npm manifest)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Routes (hash)
|
|
24
|
+
Routes (hash):
|
|
25
25
|
|
|
26
|
-
- `#/` — page
|
|
27
|
-
- `#/docs` — introduction, installation,
|
|
28
|
-
- `#/components` —
|
|
29
|
-
- `#/components/video` — playground
|
|
26
|
+
- `#/` — home page (UIFolio identity)
|
|
27
|
+
- `#/docs` — introduction, installation, theme
|
|
28
|
+
- `#/components` — documented catalog
|
|
29
|
+
- `#/components/video` — VideoPlayer playground
|
|
30
30
|
- `#/components/calendar`, `/time`, `/audio`, `/stories`, `/editor`, `/upload`,
|
|
31
31
|
`/shortcuts`, `/panels`, `/choice`, `/color-picker`, `/icon-picker`
|
|
32
|
-
- `#/animations/otp` —
|
|
33
|
-
- `#/animations/
|
|
34
|
-
`/
|
|
32
|
+
- `#/animations/otp` — animated OTP demo
|
|
33
|
+
- `#/animations/horizontal-accordion`, `/storytelling`, `/stacking`,
|
|
34
|
+
`/scroll-text`, `/image-crossfade` — scroll animations and crossfades
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
`localStorage (uifolio-theme)`,
|
|
38
|
-
|
|
36
|
+
**Light/dark** theme: button in the header, choice persisted in
|
|
37
|
+
`localStorage (uifolio-theme)`, initialized before the first paint (no flash),
|
|
38
|
+
default = system preference.
|
|
39
39
|
|
|
40
|
-
**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
(`--color`, `--radius`).
|
|
40
|
+
**Global customization**: the right panel (or the accordion on mobile) sets the
|
|
41
|
+
`--primary` color and the `--radius` for every component. The advanced picker offers
|
|
42
|
+
saturation/lightness, hue, alpha, eyedropper, RGB channels, presets and HEX/RGB/HSL/RGBA
|
|
43
|
+
formats. The choice is persisted in `localStorage (uifolio:design)` and reused in the
|
|
44
|
+
copied CLI commands (`--color`, `--radius`).
|
|
46
45
|
|
|
47
|
-
##
|
|
46
|
+
## Using the components in another project (CLI)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
Like shadcn: you install the files into **your** project, with their helpers,
|
|
49
|
+
and the alias is rewritten automatically.
|
|
51
50
|
|
|
52
51
|
```bash
|
|
53
|
-
#
|
|
52
|
+
# from any project, through the npm package
|
|
54
53
|
npx uifolio@latest add calendar --color "#10B981" --radius 6
|
|
55
54
|
pnpm dlx uifolio@latest add video-player otp
|
|
56
55
|
|
|
57
|
-
#
|
|
56
|
+
# from the root of the project that contains UIfolio
|
|
58
57
|
npm run uifolio -- list
|
|
59
|
-
npm run uifolio -- init --cwd ../
|
|
60
|
-
npm run uifolio -- add video-player calendar otp --cwd ../
|
|
58
|
+
npm run uifolio -- init --cwd ../my-project
|
|
59
|
+
npm run uifolio -- add video-player calendar otp --cwd ../my-project
|
|
61
60
|
|
|
62
|
-
#
|
|
63
|
-
node cli/uifolio.mjs add otp --cwd ../
|
|
61
|
+
# or directly, through the « bin »
|
|
62
|
+
node cli/uifolio.mjs add otp --cwd ../my-project
|
|
64
63
|
node cli/uifolio.mjs add file-upload --alias "#lib" --dry-run # simulate
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
|
|
|
66
|
+
| Command | Purpose |
|
|
68
67
|
| --- | --- |
|
|
69
|
-
| `uifolio list` |
|
|
70
|
-
| `uifolio init` |
|
|
71
|
-
| `uifolio add <
|
|
72
|
-
|
|
73
|
-
Options
|
|
74
|
-
`--lib <
|
|
75
|
-
`--
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
68
|
+
| `uifolio list` | the available components |
|
|
69
|
+
| `uifolio init` | writes `uifolio.json` (dir, lib, alias) in the target project |
|
|
70
|
+
| `uifolio add <names...>` | copies the component **+ transitive dependencies** (helpers `@/lib/*`) |
|
|
71
|
+
|
|
72
|
+
Options: `--cwd <dir>`, `--dir <path>` (default `src/components/ui`),
|
|
73
|
+
`--lib <path>` (default `src/lib`), `--alias <alias>` (default `@`),
|
|
74
|
+
`--page <path>` (created if missing), `--interactive` (force the questions),
|
|
75
|
+
`--force` (overwrite), `--dry-run` (simulate), `--color <hex>` and `--radius <px>`.
|
|
76
|
+
|
|
77
|
+
`add` does not stop at the copy. Without `--dir` or `--page`, the CLI asks for the
|
|
78
|
+
components folder, then:
|
|
79
|
+
|
|
80
|
+
1. *Do you already have a file ready to receive the component (Calendar)?*
|
|
81
|
+
`y` → *In which file should the component (Calendar) be copied?* (format
|
|
82
|
+
`src/pages/calendar.tsx`): the import and `<Component />` are added without
|
|
83
|
+
duplication, and the file is created when missing;
|
|
84
|
+
2. `n` → *Would you like to create a new folder and an import file?*
|
|
85
|
+
`y` → *Please specify the new path*: the folder and the file are created;
|
|
86
|
+
`n` → install only, no file is touched.
|
|
87
|
+
|
|
88
|
+
Answering `-` to a path question installs the component without creating or
|
|
89
|
+
modifying any page.
|
|
90
|
+
|
|
91
|
+
Colors go through **Tailwind CSS 4 tokens**: the CLI writes
|
|
92
|
+
`uifolio-theme.css` (`:root`, `.dark`, then `@theme inline`, which exposes
|
|
93
|
+
`--color-primary`, `--color-muted-foreground`, …) and imports it automatically
|
|
94
|
+
into the project's global CSS (the one with `@import 'tailwindcss'`). The
|
|
95
|
+
components therefore keep `bg-primary`, `border-primary/50`, `ring-primary/40`:
|
|
96
|
+
no hard-coded colors. `--color` changes `--primary` in the theme and `--radius`
|
|
97
|
+
adds `--radius` to it. When no global Tailwind CSS is found, the file is written
|
|
98
|
+
at the root and the import to add is printed.
|
|
99
|
+
|
|
100
|
+
At the end, the CLI prints the detected npm dependencies, for example:
|
|
84
101
|
|
|
85
102
|
```bash
|
|
86
103
|
npm install clsx tailwind-merge lucide-react hls.js
|
|
87
104
|
```
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
copie les variables CSS de `src/index.css` (`:root`, `.dark`, `@theme`) si ton projet ne les a pas.
|
|
91
|
-
|
|
92
|
-
## Thèmes clair/sombre
|
|
106
|
+
## Light/dark themes
|
|
93
107
|
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
- `useTheme()` (`src/lib/use-theme.ts`)
|
|
97
|
-
|
|
98
|
-
- `<ThemeToggle />` (`src/components/theme-toggle.tsx`)
|
|
99
|
-
-
|
|
108
|
+
- CSS tokens: `:root` (light) and `.dark` (dark) in `src/index.css`,
|
|
109
|
+
wired to Tailwind through `@theme inline` + `@custom-variant dark`.
|
|
110
|
+
- `useTheme()` (`src/lib/use-theme.ts`): `{ theme, setTheme, toggle }`,
|
|
111
|
+
persistence in `localStorage('uifolio-theme')`.
|
|
112
|
+
- `<ThemeToggle />` (`src/components/theme-toggle.tsx`): Sun/Moon button.
|
|
113
|
+
- No fixed color anywhere in the components: everything goes through
|
|
100
114
|
`background`, `foreground`, `card`, `muted`, `border`, `primary`, `accent`, `ring`.
|
|
101
115
|
|
|
102
|
-
##
|
|
116
|
+
## Real upload
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
`uploader(file, onProgress) => Promise<url>`
|
|
106
|
-
|
|
118
|
+
`FileUpload` knows nothing about your backend: it calls
|
|
119
|
+
`uploader(file, onProgress) => Promise<url>` and shows progress, success and a link
|
|
120
|
+
to the returned public URL.
|
|
107
121
|
|
|
108
|
-
|
|
122
|
+
The demo uses `createUploader()` (`src/lib/uploader.ts`):
|
|
109
123
|
|
|
110
|
-
1. `POST /api/upload/sign` → `{ key, uploadUrl, publicUrl }` (URL
|
|
111
|
-
2. `PUT`
|
|
112
|
-
3. **
|
|
113
|
-
|
|
124
|
+
1. `POST /api/upload/sign` → `{ key, uploadUrl, publicUrl }` (signed URL);
|
|
125
|
+
2. `PUT` of the file through **XHR with progress**;
|
|
126
|
+
3. **automatic retry** (2 retries, backoff) reusing the same signed URL;
|
|
127
|
+
a 4xx does not retry (final refusal).
|
|
114
128
|
|
|
115
|
-
|
|
129
|
+
On the server side, the `upload-plugin.ts` plugin (wired in `vite.config.ts`):
|
|
116
130
|
|
|
117
|
-
- **
|
|
118
|
-
`/uploads/<key
|
|
119
|
-
- **
|
|
120
|
-
`S3_BUCKET`, `S3_ACCESS_KEY_ID`
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
- **Local mode (default, zero config)**: writes into `public/uploads/` and serves
|
|
132
|
+
`/uploads/<key>`;
|
|
133
|
+
- **S3-compatible mode** (AWS S3, Cloudflare R2, MinIO): when `S3_ENDPOINT`,
|
|
134
|
+
`S3_BUCKET`, `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY` are set, the server
|
|
135
|
+
returns a **SigV4** signed PUT URL (implemented with `node:crypto`, no SDK)
|
|
136
|
+
and the browser uploads straight to the bucket.
|
|
123
137
|
|
|
124
|
-
Configuration
|
|
138
|
+
Configuration in `.env` — see `.env.example`.
|
|
125
139
|
|
|
126
|
-
|
|
140
|
+
Plugging in your own uploader:
|
|
127
141
|
|
|
128
142
|
```tsx
|
|
129
143
|
const upload = createUploader({ signEndpoint: '/api/upload/sign' })
|
|
130
144
|
|
|
131
|
-
//
|
|
145
|
+
// or your own backend:
|
|
132
146
|
const upload = async (file: File, onProgress: (v: number) => void) => {
|
|
133
147
|
const form = new FormData()
|
|
134
148
|
form.append('file', file)
|
|
135
|
-
const res = await fetch('/api/
|
|
149
|
+
const res = await fetch('/api/your-endpoint', { method: 'POST', body: form })
|
|
136
150
|
const { url } = await res.json()
|
|
137
151
|
return url
|
|
138
152
|
}
|
|
@@ -143,29 +157,29 @@ const upload = async (file: File, onProgress: (v: number) => void) => {
|
|
|
143
157
|
## Stack
|
|
144
158
|
|
|
145
159
|
- Vite 8 · React 19 · TypeScript
|
|
146
|
-
- Tailwind CSS 4 (tokens
|
|
147
|
-
- `lucide-react`
|
|
148
|
-
- `hls.js`
|
|
149
|
-
-
|
|
150
|
-
OTP
|
|
160
|
+
- Tailwind CSS 4 (CSS tokens, no JS config)
|
|
161
|
+
- `lucide-react` for icons, `clsx` + `tailwind-merge` for `cn()`
|
|
162
|
+
- `hls.js` imported **dynamically**, only when a `.m3u8` stream is played
|
|
163
|
+
- No other dependency: editor, calendar, time picker, panels, palette,
|
|
164
|
+
OTP and SigV4 signing are home-made
|
|
151
165
|
|
|
152
|
-
##
|
|
166
|
+
## Components
|
|
153
167
|
|
|
154
|
-
|
|
|
168
|
+
| Component | File | What it handles |
|
|
155
169
|
| --- | --- | --- |
|
|
156
|
-
| VideoPlayer | `src/components/ui/video-player.tsx` | URL, File/Blob,
|
|
157
|
-
| Calendar | `src/components/ui/calendar.tsx` | date
|
|
158
|
-
| TimePicker | `src/components/ui/time-picker.tsx` | 12/24 h,
|
|
159
|
-
| AudioPlayer | `src/components/ui/audio-player.tsx` |
|
|
160
|
-
| Stories | `src/components/ui/stories.tsx` |
|
|
161
|
-
| RichTextEditor | `src/components/ui/rich-text-editor.tsx` |
|
|
162
|
-
| FileUpload | `src/components/ui/file-upload.tsx` | drag & drop, validation,
|
|
163
|
-
| CommandMenu | `src/components/ui/command-menu.tsx` |
|
|
164
|
-
| ResizablePanels | `src/components/ui/resizable.tsx` |
|
|
165
|
-
| OtpInput | `src/components/ui/otp-input.tsx` | 4
|
|
166
|
-
| ColorPicker | `src/components/ui/color-picker.tsx` |
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
| VideoPlayer | `src/components/ui/video-player.tsx` | URL, File/Blob, qualities, HLS, YouTube/Vimeo, VTT subtitles, controls and playback |
|
|
171
|
+
| Calendar | `src/components/ui/calendar.tsx` | single date, start–end range, manual input, min/max, disabled days |
|
|
172
|
+
| TimePicker | `src/components/ui/time-picker.tsx` | 12/24 h, seconds, step, typing and arrow keys |
|
|
173
|
+
| AudioPlayer | `src/components/ui/audio-player.tsx` | card/compact variants, seek, volume, speed, equalizer |
|
|
174
|
+
| Stories | `src/components/ui/stories.tsx` | Instagram viewer: images, videos, text slides, pause, progress |
|
|
175
|
+
| RichTextEditor | `src/components/ui/rich-text-editor.tsx` | `contentEditable` WYSIWYG, toolbar, HTML output |
|
|
176
|
+
| FileUpload | `src/components/ui/file-upload.tsx` | drag & drop, validation, previews, progress, public URL, removal |
|
|
177
|
+
| CommandMenu | `src/components/ui/command-menu.tsx` | Ctrl/⌘ + K palette, search, keyboard |
|
|
178
|
+
| ResizablePanels | `src/components/ui/resizable.tsx` | horizontal/vertical groups, handles, min-size, keyboard |
|
|
179
|
+
| OtpInput | `src/components/ui/otp-input.tsx` | 4 fields, paste, rotate + success check |
|
|
180
|
+
| ColorPicker | `src/components/ui/color-picker.tsx` | advanced panel, saturation/lightness, hue, alpha, eyedropper, RGB channels, presets, HEX/RGB/HSL/RGBA |
|
|
181
|
+
|
|
182
|
+
Copyable helpers (the CLI follows them automatically): `src/lib/utils.ts`,
|
|
169
183
|
`src/lib/video.ts`, `src/lib/vtt.ts`, `src/lib/date.ts`, `src/lib/uploader.ts`, `src/lib/color.ts`,
|
|
170
184
|
`src/lib/use-theme.ts`, `src/lib/use-hotkeys.ts`, `src/lib/use-reduced-motion.ts`.
|
|
171
185
|
|
|
@@ -183,27 +197,27 @@ Helpers copiables (la CLI les suit automatiquement) : `src/lib/utils.ts`,
|
|
|
183
197
|
loop
|
|
184
198
|
lazy
|
|
185
199
|
subtitles={[
|
|
186
|
-
{ src: '/fr.vtt', label: '
|
|
200
|
+
{ src: '/fr.vtt', label: 'French', lang: 'fr', default: true },
|
|
187
201
|
{ src: '/en.vtt', label: 'English', lang: 'en' },
|
|
188
202
|
]}
|
|
189
203
|
subtitleClassName="bottom-20"
|
|
190
|
-
overlay={(state) => <
|
|
204
|
+
overlay={(state) => <MyBadge playing={state.playing} />}
|
|
191
205
|
/>
|
|
192
206
|
```
|
|
193
207
|
|
|
194
|
-
- Sources
|
|
195
|
-
YouTube
|
|
196
|
-
-
|
|
197
|
-
|
|
198
|
-
(
|
|
199
|
-
- `ref`
|
|
208
|
+
- Sources: direct URL, `File`/`Blob`, quality list (`{ src, quality }`), `.m3u8`,
|
|
209
|
+
YouTube and Vimeo.
|
|
210
|
+
- For embeds, the iframe is only injected on click (facade + thumbnail) and native
|
|
211
|
+
controls are hidden: the component bar drives the platform player
|
|
212
|
+
(progress, time, volume, speed, play/pause) through `postMessage`.
|
|
213
|
+
- `ref` exposes `play`, `pause`, `seek`, `mute`, `setVolume`, `setPlaybackRate`,
|
|
200
214
|
`requestFullscreen`, `getVideo`.
|
|
201
|
-
-
|
|
202
|
-
|
|
203
|
-
-
|
|
204
|
-
object URLs
|
|
205
|
-
-
|
|
206
|
-
`subtitleClassName`, menu
|
|
215
|
+
- The progress bar and the volume follow the global `--radius` (handles
|
|
216
|
+
included), like the rest of the catalog.
|
|
217
|
+
- Optimizations: lazy loading, pause off-screen, controlled `preload`, `hls.js` in a separate
|
|
218
|
+
chunk, revoked object URLs, quality switch without losing the position.
|
|
219
|
+
- Subtitles: tolerant VTT parsing, file or URL tracks, free styling through
|
|
220
|
+
`subtitleClassName`, CC menu.
|
|
207
221
|
|
|
208
222
|
## Calendar / TimePicker
|
|
209
223
|
|
|
@@ -216,20 +230,20 @@ const [date, setDate] = useState<CalendarValue>(null)
|
|
|
216
230
|
<TimePicker value={time} onChange={setTime} withSeconds step={15} hour12 />
|
|
217
231
|
```
|
|
218
232
|
|
|
219
|
-
|
|
233
|
+
Manual input accepts `dd/mm/yyyy`, `dd-mm-yyyy` and `yyyy-mm-dd`.
|
|
220
234
|
|
|
221
235
|
## Audio / Stories
|
|
222
236
|
|
|
223
237
|
```tsx
|
|
224
|
-
<AudioPlayer src="/audio.wav" title="
|
|
225
|
-
<AudioPlayer variant="compact" src="/audio.wav" title="
|
|
238
|
+
<AudioPlayer src="/audio.wav" title="Title" cover="/cover.svg" accent="#facc15" />
|
|
239
|
+
<AudioPlayer variant="compact" src="/audio.wav" title="Title" />
|
|
226
240
|
|
|
227
241
|
<Stories items={stories} open={open} onOpenChange={setOpen} duration={5000} />
|
|
228
242
|
```
|
|
229
243
|
|
|
230
|
-
|
|
244
|
+
A story can be an image, a video or a text slide with its own background and duration.
|
|
231
245
|
|
|
232
|
-
##
|
|
246
|
+
## Editor / Upload / Shortcuts / Panels / OTP
|
|
233
247
|
|
|
234
248
|
```tsx
|
|
235
249
|
<RichTextEditor value={html} onChange={setHtml} toolbar />
|
|
@@ -245,61 +259,61 @@ useHotkeys({ 'mod+k': () => setOpen(true) })
|
|
|
245
259
|
<ResizablePanel defaultSize={66} minSize={25}>…</ResizablePanel>
|
|
246
260
|
</ResizablePanelGroup>
|
|
247
261
|
|
|
248
|
-
<OtpInput length={4} value={code} onChange={setCode} onComplete={
|
|
262
|
+
<OtpInput length={4} value={code} onChange={setCode} onComplete={validate} />
|
|
249
263
|
```
|
|
250
264
|
|
|
251
|
-
|
|
252
|
-
|
|
265
|
+
The OTP aligns 4 to 8 fields, forms a circle on validation and then shows a check.
|
|
266
|
+
The animation respects `prefers-reduced-motion`.
|
|
253
267
|
|
|
254
268
|
## Animations
|
|
255
269
|
|
|
256
|
-
|
|
257
|
-
`#/animations/
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
Every animation has its own documented page: `#/animations/otp` for the OTP, then
|
|
271
|
+
`#/animations/horizontal-accordion` and `#/animations/storytelling` for the
|
|
272
|
+
horizontal cards, and `#/animations/stacking` for the cards that stack on scroll with
|
|
273
|
+
a text color driven by progress. Colors follow `--primary`. The number of cards is set
|
|
274
|
+
from 4 to 8 right in each preview. These stand-alone demos are not installable
|
|
275
|
+
through the CLI.
|
|
262
276
|
|
|
263
|
-
##
|
|
277
|
+
## Publishing the package
|
|
264
278
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
279
|
+
The published package only contains the CLI, the components and the helpers (`files`): no
|
|
280
|
+
runtime dependency, React and Tailwind stay in `devDependencies` so they are not installed
|
|
281
|
+
by people running `npx uifolio`.
|
|
268
282
|
|
|
269
283
|
```bash
|
|
270
|
-
npm login #
|
|
271
|
-
npm profile enable-2fa auth-and-writes # npm
|
|
272
|
-
npm publish --otp 123456 # prepublishOnly
|
|
284
|
+
npm login # once per machine
|
|
285
|
+
npm profile enable-2fa auth-and-writes # npm refuses to publish without 2FA
|
|
286
|
+
npm publish --otp 123456 # prepublishOnly checks cli/uifolio.mjs
|
|
273
287
|
```
|
|
274
288
|
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
A *granular* token with “Bypass 2FA” also works, but npm is progressively restricting
|
|
290
|
+
these tokens for publishing: 2FA + `--otp` is the safe path.
|
|
277
291
|
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
Then `npx uifolio@latest add <component>` and `pnpm dlx uifolio@latest add <component>`
|
|
293
|
+
work.
|
|
280
294
|
|
|
281
|
-
###
|
|
295
|
+
### Automatic publishing (GitHub Actions)
|
|
282
296
|
|
|
283
|
-
`.github/workflows/publish.yml`
|
|
284
|
-
|
|
285
|
-
`npm pack --dry-run`
|
|
286
|
-
token
|
|
287
|
-
|
|
297
|
+
`.github/workflows/publish.yml` publishes to npm on every `v*` tag: it checks that the tag
|
|
298
|
+
matches the `package.json` version, installs with `npm ci`, runs `oxlint`,
|
|
299
|
+
`npm pack --dry-run` and `npm test`, then publishes through **trusted publishing** (OIDC, no
|
|
300
|
+
stored token). A second job then creates the tag's **GitHub Release** with notes generated
|
|
301
|
+
from the commits.
|
|
288
302
|
|
|
289
303
|
```bash
|
|
290
304
|
npm version patch # 0.3.0 → 0.3.1, commit + tag v0.3.1
|
|
291
|
-
git push --follow-tags #
|
|
305
|
+
git push --follow-tags # triggers the workflow
|
|
292
306
|
```
|
|
293
307
|
|
|
294
|
-
|
|
308
|
+
Prerequisites, once:
|
|
295
309
|
|
|
296
|
-
1.
|
|
297
|
-
2. npmjs.com → `uifolio` → Settings → **Trusted publishing** → GitHub Actions
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
3.
|
|
301
|
-
|
|
310
|
+
1. first manual publication (above) — the package must exist on npm;
|
|
311
|
+
2. npmjs.com → `uifolio` → Settings → **Trusted publishing** → GitHub Actions:
|
|
312
|
+
organization `Barronakn`, repository `UIfolio`, workflow `publish.yml`,
|
|
313
|
+
“npm publish” action allowed;
|
|
314
|
+
3. a new configuration must complete its first publication **within 2 days**,
|
|
315
|
+
otherwise it expires and has to be recreated.
|
|
302
316
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
disallow tokens
|
|
317
|
+
The workflow must be present on the tagged commit. Once enabled, you can harden the
|
|
318
|
+
package with Settings → Publishing access → “Require two-factor authentication and
|
|
319
|
+
disallow tokens”.
|