uifolio 0.5.1 → 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 -164
- package/cli/uifolio.mjs +219 -136
- package/package.json +2 -2
- package/src/components/ui/calendar.tsx +5 -5
- package/src/components/ui/color-picker.tsx +11 -11
- package/src/components/ui/command-menu.tsx +1 -1
- package/src/components/ui/file-upload.tsx +9 -9
- package/src/components/ui/icon-picker.tsx +8 -8
- package/src/components/ui/resizable.tsx +1 -1
- package/src/components/ui/rich-text-editor.tsx +5 -5
- package/src/components/ui/stories.tsx +2 -2
- package/src/components/ui/video-player.tsx +49 -49
- 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/README.md
CHANGED
|
@@ -4,142 +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
|
-
`--page <
|
|
76
|
-
`--force` (
|
|
77
|
-
|
|
78
|
-
`add`
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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:
|
|
94
101
|
|
|
95
102
|
```bash
|
|
96
103
|
npm install clsx tailwind-merge lucide-react hls.js
|
|
97
104
|
```
|
|
98
105
|
|
|
99
|
-
##
|
|
106
|
+
## Light/dark themes
|
|
100
107
|
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
- `useTheme()` (`src/lib/use-theme.ts`)
|
|
104
|
-
|
|
105
|
-
- `<ThemeToggle />` (`src/components/theme-toggle.tsx`)
|
|
106
|
-
-
|
|
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
|
|
107
114
|
`background`, `foreground`, `card`, `muted`, `border`, `primary`, `accent`, `ring`.
|
|
108
115
|
|
|
109
|
-
##
|
|
116
|
+
## Real upload
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
`uploader(file, onProgress) => Promise<url>`
|
|
113
|
-
|
|
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.
|
|
114
121
|
|
|
115
|
-
|
|
122
|
+
The demo uses `createUploader()` (`src/lib/uploader.ts`):
|
|
116
123
|
|
|
117
|
-
1. `POST /api/upload/sign` → `{ key, uploadUrl, publicUrl }` (URL
|
|
118
|
-
2. `PUT`
|
|
119
|
-
3. **
|
|
120
|
-
|
|
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).
|
|
121
128
|
|
|
122
|
-
|
|
129
|
+
On the server side, the `upload-plugin.ts` plugin (wired in `vite.config.ts`):
|
|
123
130
|
|
|
124
|
-
- **
|
|
125
|
-
`/uploads/<key
|
|
126
|
-
- **
|
|
127
|
-
`S3_BUCKET`, `S3_ACCESS_KEY_ID`
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
130
137
|
|
|
131
|
-
Configuration
|
|
138
|
+
Configuration in `.env` — see `.env.example`.
|
|
132
139
|
|
|
133
|
-
|
|
140
|
+
Plugging in your own uploader:
|
|
134
141
|
|
|
135
142
|
```tsx
|
|
136
143
|
const upload = createUploader({ signEndpoint: '/api/upload/sign' })
|
|
137
144
|
|
|
138
|
-
//
|
|
145
|
+
// or your own backend:
|
|
139
146
|
const upload = async (file: File, onProgress: (v: number) => void) => {
|
|
140
147
|
const form = new FormData()
|
|
141
148
|
form.append('file', file)
|
|
142
|
-
const res = await fetch('/api/
|
|
149
|
+
const res = await fetch('/api/your-endpoint', { method: 'POST', body: form })
|
|
143
150
|
const { url } = await res.json()
|
|
144
151
|
return url
|
|
145
152
|
}
|
|
@@ -150,29 +157,29 @@ const upload = async (file: File, onProgress: (v: number) => void) => {
|
|
|
150
157
|
## Stack
|
|
151
158
|
|
|
152
159
|
- Vite 8 · React 19 · TypeScript
|
|
153
|
-
- Tailwind CSS 4 (tokens
|
|
154
|
-
- `lucide-react`
|
|
155
|
-
- `hls.js`
|
|
156
|
-
-
|
|
157
|
-
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
|
|
158
165
|
|
|
159
|
-
##
|
|
166
|
+
## Components
|
|
160
167
|
|
|
161
|
-
|
|
|
168
|
+
| Component | File | What it handles |
|
|
162
169
|
| --- | --- | --- |
|
|
163
|
-
| VideoPlayer | `src/components/ui/video-player.tsx` | URL, File/Blob,
|
|
164
|
-
| Calendar | `src/components/ui/calendar.tsx` | date
|
|
165
|
-
| TimePicker | `src/components/ui/time-picker.tsx` | 12/24 h,
|
|
166
|
-
| AudioPlayer | `src/components/ui/audio-player.tsx` |
|
|
167
|
-
| Stories | `src/components/ui/stories.tsx` |
|
|
168
|
-
| RichTextEditor | `src/components/ui/rich-text-editor.tsx` |
|
|
169
|
-
| FileUpload | `src/components/ui/file-upload.tsx` | drag & drop, validation,
|
|
170
|
-
| CommandMenu | `src/components/ui/command-menu.tsx` |
|
|
171
|
-
| ResizablePanels | `src/components/ui/resizable.tsx` |
|
|
172
|
-
| OtpInput | `src/components/ui/otp-input.tsx` | 4
|
|
173
|
-
| ColorPicker | `src/components/ui/color-picker.tsx` |
|
|
174
|
-
|
|
175
|
-
|
|
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`,
|
|
176
183
|
`src/lib/video.ts`, `src/lib/vtt.ts`, `src/lib/date.ts`, `src/lib/uploader.ts`, `src/lib/color.ts`,
|
|
177
184
|
`src/lib/use-theme.ts`, `src/lib/use-hotkeys.ts`, `src/lib/use-reduced-motion.ts`.
|
|
178
185
|
|
|
@@ -190,27 +197,27 @@ Helpers copiables (la CLI les suit automatiquement) : `src/lib/utils.ts`,
|
|
|
190
197
|
loop
|
|
191
198
|
lazy
|
|
192
199
|
subtitles={[
|
|
193
|
-
{ src: '/fr.vtt', label: '
|
|
200
|
+
{ src: '/fr.vtt', label: 'French', lang: 'fr', default: true },
|
|
194
201
|
{ src: '/en.vtt', label: 'English', lang: 'en' },
|
|
195
202
|
]}
|
|
196
203
|
subtitleClassName="bottom-20"
|
|
197
|
-
overlay={(state) => <
|
|
204
|
+
overlay={(state) => <MyBadge playing={state.playing} />}
|
|
198
205
|
/>
|
|
199
206
|
```
|
|
200
207
|
|
|
201
|
-
- Sources
|
|
202
|
-
YouTube
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
(
|
|
206
|
-
- `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`,
|
|
207
214
|
`requestFullscreen`, `getVideo`.
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
-
|
|
211
|
-
object URLs
|
|
212
|
-
-
|
|
213
|
-
`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.
|
|
214
221
|
|
|
215
222
|
## Calendar / TimePicker
|
|
216
223
|
|
|
@@ -223,20 +230,20 @@ const [date, setDate] = useState<CalendarValue>(null)
|
|
|
223
230
|
<TimePicker value={time} onChange={setTime} withSeconds step={15} hour12 />
|
|
224
231
|
```
|
|
225
232
|
|
|
226
|
-
|
|
233
|
+
Manual input accepts `dd/mm/yyyy`, `dd-mm-yyyy` and `yyyy-mm-dd`.
|
|
227
234
|
|
|
228
235
|
## Audio / Stories
|
|
229
236
|
|
|
230
237
|
```tsx
|
|
231
|
-
<AudioPlayer src="/audio.wav" title="
|
|
232
|
-
<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" />
|
|
233
240
|
|
|
234
241
|
<Stories items={stories} open={open} onOpenChange={setOpen} duration={5000} />
|
|
235
242
|
```
|
|
236
243
|
|
|
237
|
-
|
|
244
|
+
A story can be an image, a video or a text slide with its own background and duration.
|
|
238
245
|
|
|
239
|
-
##
|
|
246
|
+
## Editor / Upload / Shortcuts / Panels / OTP
|
|
240
247
|
|
|
241
248
|
```tsx
|
|
242
249
|
<RichTextEditor value={html} onChange={setHtml} toolbar />
|
|
@@ -252,61 +259,61 @@ useHotkeys({ 'mod+k': () => setOpen(true) })
|
|
|
252
259
|
<ResizablePanel defaultSize={66} minSize={25}>…</ResizablePanel>
|
|
253
260
|
</ResizablePanelGroup>
|
|
254
261
|
|
|
255
|
-
<OtpInput length={4} value={code} onChange={setCode} onComplete={
|
|
262
|
+
<OtpInput length={4} value={code} onChange={setCode} onComplete={validate} />
|
|
256
263
|
```
|
|
257
264
|
|
|
258
|
-
|
|
259
|
-
|
|
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`.
|
|
260
267
|
|
|
261
268
|
## Animations
|
|
262
269
|
|
|
263
|
-
|
|
264
|
-
`#/animations/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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.
|
|
269
276
|
|
|
270
|
-
##
|
|
277
|
+
## Publishing the package
|
|
271
278
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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`.
|
|
275
282
|
|
|
276
283
|
```bash
|
|
277
|
-
npm login #
|
|
278
|
-
npm profile enable-2fa auth-and-writes # npm
|
|
279
|
-
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
|
|
280
287
|
```
|
|
281
288
|
|
|
282
|
-
|
|
283
|
-
|
|
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.
|
|
284
291
|
|
|
285
|
-
|
|
286
|
-
|
|
292
|
+
Then `npx uifolio@latest add <component>` and `pnpm dlx uifolio@latest add <component>`
|
|
293
|
+
work.
|
|
287
294
|
|
|
288
|
-
###
|
|
295
|
+
### Automatic publishing (GitHub Actions)
|
|
289
296
|
|
|
290
|
-
`.github/workflows/publish.yml`
|
|
291
|
-
|
|
292
|
-
`npm pack --dry-run`
|
|
293
|
-
token
|
|
294
|
-
|
|
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.
|
|
295
302
|
|
|
296
303
|
```bash
|
|
297
304
|
npm version patch # 0.3.0 → 0.3.1, commit + tag v0.3.1
|
|
298
|
-
git push --follow-tags #
|
|
305
|
+
git push --follow-tags # triggers the workflow
|
|
299
306
|
```
|
|
300
307
|
|
|
301
|
-
|
|
308
|
+
Prerequisites, once:
|
|
302
309
|
|
|
303
|
-
1.
|
|
304
|
-
2. npmjs.com → `uifolio` → Settings → **Trusted publishing** → GitHub Actions
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
3.
|
|
308
|
-
|
|
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.
|
|
309
316
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
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”.
|