nuxt-content-assets 1.3.7 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -41
- package/dist/module.json +1 -1
- package/dist/module.mjs +44 -38
- package/dist/runtime/assets/source.d.ts +1 -0
- package/dist/runtime/assets/source.mjs +7 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
> Enable locally-located assets in Nuxt Content
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<img src="https://raw.githubusercontent.com/davestewart/nuxt-content-assets/main/
|
|
11
|
+
<img src="https://raw.githubusercontent.com/davestewart/nuxt-content-assets/main/playground/content/splash.png" alt="Nuxt Content Assets logo">
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
## Overview
|
|
@@ -63,26 +63,28 @@ Developer experience:
|
|
|
63
63
|
- image size injection
|
|
64
64
|
- zero config
|
|
65
65
|
|
|
66
|
-
##
|
|
66
|
+
## Playground
|
|
67
67
|
|
|
68
|
-
To
|
|
68
|
+
To test the module before installing, you can try out the Nuxt Content Assets playground.
|
|
69
|
+
|
|
70
|
+
To clone and run locally:
|
|
69
71
|
|
|
70
72
|
```bash
|
|
71
73
|
git clone https://github.com/davestewart/nuxt-content-assets.git
|
|
72
74
|
cd nuxt-content-assets
|
|
73
|
-
npm install && npm install --prefix ./
|
|
75
|
+
npm install && npm install --prefix ./playground
|
|
74
76
|
npm run dev
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
Then open the
|
|
79
|
+
Then open the playground in your browser at <a href="http://localhost:3000" target="_blank">localhost:3000</a>.
|
|
78
80
|
|
|
79
|
-
To run the
|
|
81
|
+
To run the playground online, visit:
|
|
80
82
|
|
|
81
|
-
- https://stackblitz.com/github/davestewart/nuxt-content-assets?file=
|
|
83
|
+
- https://stackblitz.com/github/davestewart/nuxt-content-assets?file=playground%2Fapp.vue
|
|
82
84
|
|
|
83
|
-
To browse the
|
|
85
|
+
To browse the playground folder:
|
|
84
86
|
|
|
85
|
-
- https://github.com/davestewart/nuxt-content-assets/tree/main/
|
|
87
|
+
- https://github.com/davestewart/nuxt-content-assets/tree/main/playground
|
|
86
88
|
|
|
87
89
|
## Setup
|
|
88
90
|
|
|
@@ -143,7 +145,7 @@ These values can then be passed to components:
|
|
|
143
145
|
:image-gallery{:data="images"}
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
See the
|
|
148
|
+
See the playground for [markup](playground/content/advanced/gallery.md) and [component](playground/components/content/ContentGallery.vue) examples.
|
|
147
149
|
|
|
148
150
|
### Live reload
|
|
149
151
|
|
|
@@ -171,7 +173,7 @@ Keeping this on prevents content jumps as your page loads.
|
|
|
171
173
|
|
|
172
174
|
#### Prose components
|
|
173
175
|
|
|
174
|
-
If you use [ProseImg](https://content.nuxtjs.org/api/components/prose) components, you can [hook into](
|
|
176
|
+
If you use [ProseImg](https://content.nuxtjs.org/api/components/prose) components, you can [hook into](playground/components/temp/ProseImg.vue) image size hints via the `$attrs` property:
|
|
175
177
|
|
|
176
178
|
```vue
|
|
177
179
|
<template>
|
|
@@ -189,7 +191,7 @@ export default {
|
|
|
189
191
|
|
|
190
192
|
#### Frontmatter
|
|
191
193
|
|
|
192
|
-
If you pass [frontmatter](
|
|
194
|
+
If you pass [frontmatter](playground/content/advanced/gallery.md) to [custom components](playground/components/content/ContentImage.vue) set `imageSize` to `'src'` to encode values in `src`:
|
|
193
195
|
|
|
194
196
|
```
|
|
195
197
|
:image-content{:src="image"}
|
|
@@ -201,24 +203,32 @@ The component will receive the size information as a query string which you can
|
|
|
201
203
|
<img class="image-content" src="/image.jpg?width=640&height=480">
|
|
202
204
|
```
|
|
203
205
|
|
|
204
|
-
See
|
|
205
|
-
|
|
206
|
-
### Nuxt Image compatibility
|
|
206
|
+
See playground component [here](playground/components/content/ContentImage.vue).
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
### Nuxt Image
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
Nuxt Content Assets works with [Nuxt Image](https://image.nuxtjs.org/) with just a little configuration:
|
|
211
211
|
|
|
212
212
|
```ts
|
|
213
213
|
// nuxt.config.ts
|
|
214
214
|
export default defineNuxtConfig({
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
modules: [
|
|
216
|
+
// Nuxt Image should be placed before Nuxt Content Assets
|
|
217
|
+
'@nuxt/image',
|
|
218
|
+
'nuxt-content-assets',
|
|
219
|
+
'@nuxt/content',
|
|
220
|
+
],
|
|
221
|
+
|
|
222
|
+
extends: [
|
|
223
|
+
// add Nuxt Content Assets build folder as a Nuxt Layer (since v1.4.0)
|
|
224
|
+
'.nuxt/content-assets',
|
|
225
|
+
],
|
|
218
226
|
}
|
|
219
227
|
```
|
|
220
228
|
|
|
221
|
-
|
|
229
|
+
> Note that the new Layers setup enables Nuxt Image to load images from both the project's `public` folder and from `content`.
|
|
230
|
+
|
|
231
|
+
To serve all images as Nuxt Image images, create a `ProseImg` component like so:
|
|
222
232
|
|
|
223
233
|
```vue
|
|
224
234
|
<!-- components/content/ProseImg.vue -->
|
|
@@ -227,9 +237,7 @@ Then, create a `ProseImg` component like so:
|
|
|
227
237
|
</template>
|
|
228
238
|
```
|
|
229
239
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
> For a per-image solution, check the [override](demo/components/content/NuxtImg.ts) in the demo folder.
|
|
240
|
+
See the playground folder for both the [global](playground/components/temp/ProseImg.vue) and a [per image](playground/components/content/NuxtImg.ts) solution.
|
|
233
241
|
|
|
234
242
|
|
|
235
243
|
## Configuration
|
|
@@ -264,12 +272,12 @@ You can add one or more image size hints to the generated images:
|
|
|
264
272
|
|
|
265
273
|
Pick from the following switches:
|
|
266
274
|
|
|
267
|
-
| Switch | What it does
|
|
268
|
-
|
|
269
|
-
| `'style'` | Adds `style="aspect-ratio:..."` to any `<img>` tag
|
|
270
|
-
| `'attrs'` | Adds `width` and `height` attributes to any `<img>` tag
|
|
275
|
+
| Switch | What it does |
|
|
276
|
+
|-----------|--------------------------------------------------------------------|
|
|
277
|
+
| `'style'` | Adds `style="aspect-ratio:..."` to any `<img>` tag |
|
|
278
|
+
| `'attrs'` | Adds `width` and `height` attributes to any `<img>` tag |
|
|
271
279
|
| `'src'` | Adds `?width=...&height=...` to `src` attribute (frontmatter only) |
|
|
272
|
-
| `false` | Disable image size hints
|
|
280
|
+
| `false` | Disable image size hints |
|
|
273
281
|
|
|
274
282
|
Note: if you add *only* `attrs`, include the following CSS in your app:
|
|
275
283
|
|
|
@@ -318,16 +326,16 @@ In development, file watching propagates asset changes to the public folder, upd
|
|
|
318
326
|
|
|
319
327
|
Should you wish to develop the project, the scripts are:
|
|
320
328
|
|
|
321
|
-
Develop the module (running
|
|
329
|
+
Develop the module (running the playground which uses the live module code):
|
|
322
330
|
|
|
323
331
|
```bash
|
|
324
332
|
# install dependencies
|
|
325
333
|
npm install
|
|
326
334
|
|
|
327
|
-
# generate
|
|
335
|
+
# generate playground type stubs (for the first time)
|
|
328
336
|
npm run dev:prepare
|
|
329
337
|
|
|
330
|
-
# develop (runs the
|
|
338
|
+
# develop (runs the playground app)
|
|
331
339
|
npm run dev
|
|
332
340
|
|
|
333
341
|
# run eslint
|
|
@@ -338,27 +346,27 @@ npm run test
|
|
|
338
346
|
npm run test:watch
|
|
339
347
|
```
|
|
340
348
|
|
|
341
|
-
Build and check the
|
|
349
|
+
Build and check the playground (simulating users' final build choices):
|
|
342
350
|
|
|
343
351
|
```bash
|
|
344
|
-
# generate the
|
|
352
|
+
# generate the playground
|
|
345
353
|
npm run dev:generate
|
|
346
354
|
|
|
347
|
-
# build the
|
|
355
|
+
# build the playground
|
|
348
356
|
npm run dev:build
|
|
349
357
|
|
|
350
|
-
# serve the built
|
|
351
|
-
npm run dev:
|
|
358
|
+
# serve the generated / built playground
|
|
359
|
+
npm run dev:preview
|
|
352
360
|
```
|
|
353
361
|
|
|
354
362
|
Make a new release (so users can install the module):
|
|
355
363
|
|
|
356
364
|
```bash
|
|
357
|
-
# release new version
|
|
358
|
-
npm run release
|
|
359
|
-
|
|
360
365
|
# dry run the release
|
|
361
366
|
npm run release:dry
|
|
367
|
+
|
|
368
|
+
# release new version
|
|
369
|
+
npm run release
|
|
362
370
|
```
|
|
363
371
|
|
|
364
372
|
Make sure to edit changelog and update `package.json` version before releasing!
|
|
@@ -375,7 +383,7 @@ This created the module code from the starter template found here:
|
|
|
375
383
|
|
|
376
384
|
- https://github.com/nuxt/starter/tree/module
|
|
377
385
|
|
|
378
|
-
Both [Nuxi](https://github.com/nuxt/cli) and the module's dependencies and scripts are updated fairly regularly, so from time to time this module
|
|
386
|
+
Both [Nuxi](https://github.com/nuxt/cli) and the module's dependencies and scripts are updated fairly regularly, so from time to time this module may need to be updated to keep in sync. So far, this has meant just updating the dependencies and scripts, which are found in the starter template code mentioned above.
|
|
379
387
|
|
|
380
388
|
<!-- Badges -->
|
|
381
389
|
[npm-version-src]: https://img.shields.io/npm/v/nuxt-content-assets/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import * as Path from 'crosspath';
|
|
4
|
-
import Path__default from 'crosspath';
|
|
1
|
+
import Fs from 'fs';
|
|
2
|
+
import Path from 'crosspath';
|
|
5
3
|
import { useNuxt, createResolver, defineNuxtModule, addPlugin } from '@nuxt/kit';
|
|
6
4
|
import { visit, SKIP, CONTINUE } from 'unist-util-visit';
|
|
7
5
|
import { listen } from 'listhen';
|
|
@@ -53,7 +51,7 @@ function isExcluded(path) {
|
|
|
53
51
|
return path.split("/").some((segment) => segment.startsWith(".") || segment.startsWith("_"));
|
|
54
52
|
}
|
|
55
53
|
function isImage(path) {
|
|
56
|
-
const ext =
|
|
54
|
+
const ext = Path.extname(path).substring(1);
|
|
57
55
|
return extensions.image.includes(ext);
|
|
58
56
|
}
|
|
59
57
|
function isArticle(path) {
|
|
@@ -155,33 +153,33 @@ function buildQuery(...expr) {
|
|
|
155
153
|
}
|
|
156
154
|
|
|
157
155
|
function readFile(path, asJson = false) {
|
|
158
|
-
const text =
|
|
156
|
+
const text = Fs.readFileSync(path, { encoding: "utf8" });
|
|
159
157
|
return asJson ? JSON.parse(text) : text;
|
|
160
158
|
}
|
|
161
159
|
function writeFile(path, data) {
|
|
162
160
|
const text = typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
|
|
163
|
-
createFolder(
|
|
164
|
-
|
|
161
|
+
createFolder(Path.dirname(path));
|
|
162
|
+
Fs.writeFileSync(path, text, { encoding: "utf8" });
|
|
165
163
|
}
|
|
166
164
|
async function writeBlob(path, data) {
|
|
167
165
|
const buffer = Buffer.from(await data.arrayBuffer());
|
|
168
|
-
createFolder(
|
|
169
|
-
|
|
166
|
+
createFolder(Path.dirname(path));
|
|
167
|
+
Fs.writeFileSync(path, buffer);
|
|
170
168
|
}
|
|
171
169
|
function copyFile(src, trg) {
|
|
172
|
-
createFolder(
|
|
173
|
-
|
|
170
|
+
createFolder(Path.dirname(trg));
|
|
171
|
+
Fs.copyFileSync(src, trg);
|
|
174
172
|
}
|
|
175
173
|
function removeFile(src) {
|
|
176
|
-
|
|
174
|
+
Fs.rmSync(src);
|
|
177
175
|
}
|
|
178
176
|
function createFolder(path) {
|
|
179
|
-
|
|
177
|
+
Fs.mkdirSync(path, { recursive: true });
|
|
180
178
|
}
|
|
181
179
|
function removeFolder(path) {
|
|
182
|
-
const isDownstream = path.startsWith(
|
|
180
|
+
const isDownstream = path.startsWith(Path.resolve());
|
|
183
181
|
if (isDownstream) {
|
|
184
|
-
|
|
182
|
+
Fs.rmSync(path, { recursive: true, force: true });
|
|
185
183
|
}
|
|
186
184
|
}
|
|
187
185
|
|
|
@@ -331,13 +329,13 @@ function makeSourceManager(key, source, publicPath, callback) {
|
|
|
331
329
|
return toPath(key2).replace(/\w+/, "").replace(source.prefix || "", "");
|
|
332
330
|
}
|
|
333
331
|
function getAbsSrc(key2) {
|
|
334
|
-
return
|
|
332
|
+
return Path.join(source.base, getRelSrc(key2));
|
|
335
333
|
}
|
|
336
334
|
function getRelTrg(key2) {
|
|
337
|
-
return
|
|
335
|
+
return Path.join(source.prefix || "", toPath(deKey(key2)));
|
|
338
336
|
}
|
|
339
337
|
function getAbsTrg(key2) {
|
|
340
|
-
return
|
|
338
|
+
return Path.join(publicPath, getRelTrg(key2));
|
|
341
339
|
}
|
|
342
340
|
function removeItem(key2) {
|
|
343
341
|
const absTrg = getAbsTrg(key2);
|
|
@@ -378,17 +376,22 @@ function makeSourceManager(key, source, publicPath, callback) {
|
|
|
378
376
|
return paths;
|
|
379
377
|
}
|
|
380
378
|
const storage = makeSourceStorage(source, key);
|
|
381
|
-
storage.watch(onWatch);
|
|
379
|
+
void storage.watch(onWatch);
|
|
380
|
+
async function dispose() {
|
|
381
|
+
await storage.unwatch();
|
|
382
|
+
await storage.dispose();
|
|
383
|
+
}
|
|
382
384
|
return {
|
|
383
385
|
storage,
|
|
384
386
|
init,
|
|
385
|
-
keys: getKeys
|
|
387
|
+
keys: getKeys,
|
|
388
|
+
dispose
|
|
386
389
|
};
|
|
387
390
|
}
|
|
388
391
|
|
|
389
392
|
function makeAssetsManager(publicPath, shouldWatch = true) {
|
|
390
393
|
const indexKey = "assets.json";
|
|
391
|
-
const storage = makeSourceStorage(
|
|
394
|
+
const storage = makeSourceStorage(Path.join(publicPath, ".."));
|
|
392
395
|
if (shouldWatch) {
|
|
393
396
|
void storage.watch(async (event, key) => {
|
|
394
397
|
if (event === "update" && key === indexKey) {
|
|
@@ -405,8 +408,8 @@ function makeAssetsManager(publicPath, shouldWatch = true) {
|
|
|
405
408
|
void storage.setItem(indexKey, assets);
|
|
406
409
|
}, 50);
|
|
407
410
|
function resolveAsset(content, relAsset, registerContent = false) {
|
|
408
|
-
const srcDir =
|
|
409
|
-
const srcAsset =
|
|
411
|
+
const srcDir = Path.dirname(content._file);
|
|
412
|
+
const srcAsset = Path.join(srcDir, relAsset);
|
|
410
413
|
const asset = assets[srcAsset];
|
|
411
414
|
if (asset && registerContent) {
|
|
412
415
|
const { _id } = content;
|
|
@@ -457,7 +460,7 @@ function makeAssetsManager(publicPath, shouldWatch = true) {
|
|
|
457
460
|
};
|
|
458
461
|
}
|
|
459
462
|
function getAssetPaths(srcDir, srcAbs) {
|
|
460
|
-
const srcRel =
|
|
463
|
+
const srcRel = Path.relative(srcDir, srcAbs);
|
|
461
464
|
const srcAttr = "/" + srcRel;
|
|
462
465
|
return {
|
|
463
466
|
srcRel,
|
|
@@ -527,11 +530,15 @@ const module = defineNuxtModule({
|
|
|
527
530
|
const assetsPath = Path.join(buildPath, "content-assets");
|
|
528
531
|
const publicPath = Path.join(assetsPath, "public");
|
|
529
532
|
const contentPath = Path.join(buildPath, "content-cache/parsed");
|
|
530
|
-
|
|
533
|
+
const isDev = !!nuxt.options.dev;
|
|
534
|
+
const isDebug = !!options.debug;
|
|
535
|
+
if (isDebug) {
|
|
531
536
|
log("Removing cache folders...");
|
|
532
537
|
}
|
|
533
538
|
removeFolder(Path.join(buildPath, "content-cache"));
|
|
534
539
|
removeFolder(assetsPath);
|
|
540
|
+
createFolder(`${assetsPath}/public`);
|
|
541
|
+
writeFile(`${assetsPath}/nuxt.config.ts`, "export default {}");
|
|
535
542
|
const { contentExtensions } = options;
|
|
536
543
|
if (contentExtensions) {
|
|
537
544
|
nuxt.options.content ||= {};
|
|
@@ -557,8 +564,7 @@ const module = defineNuxtModule({
|
|
|
557
564
|
};
|
|
558
565
|
}
|
|
559
566
|
}
|
|
560
|
-
const assets = makeAssetsManager(publicPath,
|
|
561
|
-
nuxt.hooks.hook("close", () => assets.dispose());
|
|
567
|
+
const assets = makeAssetsManager(publicPath, isDev);
|
|
562
568
|
function onAssetChange(event, absTrg) {
|
|
563
569
|
let src = "";
|
|
564
570
|
let width;
|
|
@@ -588,35 +594,35 @@ const module = defineNuxtModule({
|
|
|
588
594
|
}
|
|
589
595
|
}
|
|
590
596
|
addPlugin(resolve("./runtime/sockets/plugin"));
|
|
591
|
-
const socket =
|
|
597
|
+
const socket = isDev ? await setupSocketServer("content-assets") : null;
|
|
592
598
|
const managers = {};
|
|
593
599
|
for (const [key, source] of Object.entries(sources)) {
|
|
594
|
-
if (
|
|
600
|
+
if (isDebug) {
|
|
595
601
|
log(`Creating source "${key}"`);
|
|
596
602
|
}
|
|
597
603
|
managers[key] = makeSourceManager(key, source, publicPath, onAssetChange);
|
|
598
604
|
}
|
|
599
|
-
nuxt.hook("close", async () => {
|
|
600
|
-
for (const key in managers) {
|
|
601
|
-
await managers[key].storage.unwatch();
|
|
602
|
-
await managers[key].storage.dispose();
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
605
|
nuxt.hook("build:before", async function() {
|
|
606
606
|
for (const [key, manager] of Object.entries(managers)) {
|
|
607
607
|
const paths = await manager.init();
|
|
608
608
|
paths.forEach((path) => assets.setAsset(path));
|
|
609
|
-
if (
|
|
609
|
+
if (isDebug) {
|
|
610
610
|
list(`Copied "${key}" assets`, paths.map((path) => Path.relative(publicPath, path)));
|
|
611
611
|
}
|
|
612
612
|
}
|
|
613
613
|
});
|
|
614
|
+
nuxt.hook("close", async () => {
|
|
615
|
+
await assets.dispose();
|
|
616
|
+
for (const key in managers) {
|
|
617
|
+
await managers[key].dispose();
|
|
618
|
+
}
|
|
619
|
+
});
|
|
614
620
|
const pluginPath = resolve("./runtime/content/plugin");
|
|
615
621
|
const makeVar = (name, value) => `export const ${name} = ${JSON.stringify(value)};`;
|
|
616
622
|
const virtualConfig = [
|
|
617
623
|
makeVar("publicPath", publicPath),
|
|
618
624
|
makeVar("imageSizes", imageSizes),
|
|
619
|
-
makeVar("debug",
|
|
625
|
+
makeVar("debug", isDebug)
|
|
620
626
|
].join("\n");
|
|
621
627
|
nuxt.hook("nitro:config", async (config) => {
|
|
622
628
|
config.plugins ||= [];
|
|
@@ -90,10 +90,15 @@ export function makeSourceManager(key, source, publicPath, callback) {
|
|
|
90
90
|
return paths;
|
|
91
91
|
}
|
|
92
92
|
const storage = makeSourceStorage(source, key);
|
|
93
|
-
storage.watch(onWatch);
|
|
93
|
+
void storage.watch(onWatch);
|
|
94
|
+
async function dispose() {
|
|
95
|
+
await storage.unwatch();
|
|
96
|
+
await storage.dispose();
|
|
97
|
+
}
|
|
94
98
|
return {
|
|
95
99
|
storage,
|
|
96
100
|
init,
|
|
97
|
-
keys: getKeys
|
|
101
|
+
keys: getKeys,
|
|
102
|
+
dispose
|
|
98
103
|
};
|
|
99
104
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-content-assets",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Enable locally-located assets in Nuxt Content",
|
|
5
5
|
"repository": "davestewart/nuxt-content-assets",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"dev": "nuxi dev
|
|
22
|
-
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare
|
|
23
|
-
"dev:generate": "nuxi generate
|
|
24
|
-
"dev:build": "nuxi build
|
|
25
|
-
"dev:
|
|
26
|
-
"release": "npm run lint && npm run test && nuxt-module-build build && changelogen --release && npm publish && git push --follow-tags",
|
|
21
|
+
"dev": "nuxi dev playground",
|
|
22
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
23
|
+
"dev:generate": "nuxi generate playground",
|
|
24
|
+
"dev:build": "nuxi build playground",
|
|
25
|
+
"dev:preview": "nuxi preview playground",
|
|
27
26
|
"release:dry": "npm run lint && npm run test && nuxt-module-build build && npm publish --dry-run",
|
|
27
|
+
"release": "npm run lint && npm run test && nuxt-module-build build && changelogen --release && npm publish && git push --follow-tags",
|
|
28
28
|
"lint": "eslint .",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest watch"
|