nuxt-content-assets 0.6.1 → 0.7.0

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 CHANGED
@@ -97,7 +97,7 @@ Relative paths are defined by anything not starting with a slash or `http`, for
97
97
 
98
98
  ```
99
99
  image.jpg
100
- images/featured.png
100
+ assets/featured.png
101
101
  ../assets/cv.pdf
102
102
  ```
103
103
 
@@ -119,16 +119,16 @@ However, you can use relative paths in frontmatter:
119
119
  ---
120
120
  title: Portfolio Item 1
121
121
  images:
122
- - images/image-1.jpg
123
- - images/image-2.jpg
124
- - images/image-3.jpg
122
+ - assets/image-1.jpg
123
+ - assets/image-2.jpg
124
+ - assets/image-3.jpg
125
125
  ---
126
126
  ```
127
127
 
128
128
  Then pass these to components like so:
129
129
 
130
130
  ```markdown
131
- ::gallery{:images="images"}
131
+ ::gallery{:data="images"}
132
132
  ::
133
133
  ```
134
134
 
@@ -196,7 +196,7 @@ export default defineNuxtConfig({
196
196
  // where to generate and serve the assets from
197
197
  output: 'assets/content/[path]/[file]',
198
198
 
199
- // add additional extensions
199
+ // include additional extensions
200
200
  additionalExtensions: 'html',
201
201
 
202
202
  // completely replace supported extensions
@@ -216,38 +216,43 @@ export default defineNuxtConfig({
216
216
  The output path can be customised using a template string:
217
217
 
218
218
  ```
219
- assets/content/[name]-[hash].[ext]
219
+ assets/[name]-[hash].[ext]
220
220
  ```
221
221
 
222
222
  The first part of the path is where you want content assets to be served from:
223
223
 
224
224
  ```
225
- assets/content/
225
+ assets/
226
226
  ```
227
227
 
228
- The optional second part of the path indicates the relative location of each image:
228
+ The optional second part of the path indicates the relative location of each asset.
229
229
 
230
- | Token | Description | Example |
231
- |-------------|--------------------------------------------|--------------------|
232
- | `[folder]` | The relative folder of the file | `posts/2023-01-01` |
233
- | `[file]` | The full filename of the file | `featured.jpg` |
234
- | `[name]` | The name of the file without the extension | `featured` |
235
- | `[hash]` | A hash of the absolute source path | `9M00N4l9A0` |
236
- | `[extname]` | The full extension with the dot | `.jpg` |
237
- | `[ext]` | The extension without the dot | `jpg` |
230
+ The table below shows replacements for the asset `content/posts/2023-01-01/featured.jpg`:
231
+
232
+ | Token | Description | Example |
233
+ |-------------|----------------------------------------------------------------------------------------------------|----------------------------|
234
+ | `[key]` | The config key of the source (see [sources](https://content.nuxtjs.org/api/configuration#sources)) | `content` |
235
+ | `[path]` | The relative path of the source | `content/posts/2023-01-01` |
236
+ | `[folder]` | The relative path of the file's folder | `posts/2023-01-01` |
237
+ | `[file]` | The full filename of the file | `featured.jpg` |
238
+ | `[name]` | The name of the file without the extension | `featured` |
239
+ | `[hash]` | A hash of the absolute source path | `9M00N4l9A0` |
240
+ | `[extname]` | The full extension with the dot | `.jpg` |
241
+ | `[ext]` | The extension without the dot | `jpg` |
238
242
 
239
243
  For example:
240
244
 
241
- | Template | Output |
242
- |--------------------------------------|----------------------------------------------------|
243
- | `assets/img/content/[folder]/[file]` | `assets/img/content/posts/2023-01-01/featured.jpg` |
244
- | `assets/img/[name]-[hash].[ext]` | `assets/img/featured-9M00N4l9A0.jpg` |
245
- | `content/[hash].[ext]` | `content/9M00N4l9A0.jpg` |
245
+ | Template | Output |
246
+ |------------------------------|------------------------------------------------|
247
+ | `assets/[path]/[file]` | `assets/content/posts/2023-01-01/featured.jpg` |
248
+ | `assets/[folder]/[file]` | `assets/posts/2023-01-01/featured.jpg` |
249
+ | `assets/[name]-[hash].[ext]` | `assets/featured-9M00N4l9A0.jpg` |
250
+ | `assets/[hash].[ext]` | `assets/9M00N4l9A0.jpg` |
246
251
 
247
252
  Note that the module defaults to:
248
253
 
249
254
  ```
250
- /assets/content/[folder]/[file]
255
+ /assets/[path]/[file]
251
256
  ```
252
257
 
253
258
  ### Extensions
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.6.1"
7
+ "version": "0.7.0"
8
8
  }
package/dist/module.mjs CHANGED
@@ -1,18 +1,20 @@
1
- import { createResolver, defineNuxtModule, addTemplate } from '@nuxt/kit';
2
- import getImageSize from 'image-size';
3
- import glob from 'glob';
4
1
  import * as Fs from 'fs';
5
2
  import * as Path from 'path';
6
3
  import Path__default from 'path';
4
+ import { createResolver, defineNuxtModule, addTemplate } from '@nuxt/kit';
5
+ import getImageSize from 'image-size';
7
6
  import { hash } from 'ohash';
7
+ import glob from 'glob';
8
+ import { createStorage } from 'unstorage';
9
+ import githubDriver from 'unstorage/drivers/github';
8
10
 
9
11
  function matchWords(value) {
10
12
  return typeof value === "string" ? value.match(/\w+/g) || [] : [];
11
13
  }
12
14
 
13
15
  const defaults = {
14
- assetsDir: "assets/content",
15
- assetsPattern: "[folder]/[file]"
16
+ assetsDir: "/assets/",
17
+ assetsPattern: "[path]/[file]"
16
18
  };
17
19
  const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
18
20
  const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
@@ -28,40 +30,57 @@ function isImage(path) {
28
30
  return imageExtensions.includes(ext);
29
31
  }
30
32
 
31
- function getSources(sources) {
32
- return Object.keys(sources).reduce((output, key) => {
33
- const source = sources[key];
34
- if (source) {
35
- const { driver, base } = source;
36
- if (driver === "fs") {
37
- output[key] = base;
38
- }
39
- }
40
- return output;
41
- }, {});
42
- }
43
-
44
33
  const moduleName = "nuxt-content-assets";
45
34
  const moduleKey = "content-assets";
46
35
 
47
36
  function log(...data) {
48
37
  console.info(`[${moduleKey}]`, ...data);
49
38
  }
39
+ function warn(...data) {
40
+ console.warn(`[${moduleKey}]`, ...data);
41
+ }
42
+ function list(message, items) {
43
+ log(`${message}:
44
+
45
+ ${items.map((item) => ` - ${item}`).join("\n")}
46
+ `);
47
+ }
48
+
49
+ function writeFile(path, data) {
50
+ const text = typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
51
+ createFolder(Path.dirname(path));
52
+ Fs.writeFileSync(path, text, { encoding: "utf8" });
53
+ }
54
+ function copyFile(src, trg) {
55
+ createFolder(Path.dirname(trg));
56
+ Fs.copyFileSync(src, trg);
57
+ }
58
+ function createFolder(path) {
59
+ Fs.mkdirSync(path, { recursive: true });
60
+ }
61
+ function removeFolder(path) {
62
+ const isDownstream = path.startsWith(Path.resolve());
63
+ if (isDownstream) {
64
+ Fs.rmSync(path, { recursive: true, force: true });
65
+ }
66
+ }
50
67
 
51
68
  const replacers = {
52
- folder: (src, dir) => Path__default.dirname(src.replace(dir, "")),
69
+ key: (src) => Path__default.dirname(src).split("/").filter((e) => e).shift() || "",
70
+ path: (src) => Path__default.dirname(src),
71
+ folder: (src) => Path__default.dirname(src).replace(/[^/]+\//, ""),
53
72
  file: (src) => Path__default.basename(src),
54
73
  name: (src) => Path__default.basename(src, Path__default.extname(src)),
55
74
  extname: (src) => Path__default.extname(src),
56
75
  ext: (src) => Path__default.extname(src).substring(1),
57
76
  hash: (src) => hash({ src })
58
77
  };
59
- function interpolatePattern(pattern, src, dir, warn = false) {
78
+ function interpolatePattern(pattern, src, warn = false) {
60
79
  return Path__default.join(pattern.replace(/\[\w+]/g, (match) => {
61
80
  const name = match.substring(1, match.length - 1);
62
81
  const fn = replacers[name];
63
82
  if (fn) {
64
- return fn(src, dir);
83
+ return fn(src);
65
84
  }
66
85
  if (warn) {
67
86
  log(`Unknown output token ${match}`, true);
@@ -70,6 +89,69 @@ function interpolatePattern(pattern, src, dir, warn = false) {
70
89
  }));
71
90
  }
72
91
 
92
+ function getAssetConfig(srcDir, srcAbs, pattern, hints) {
93
+ let width = void 0;
94
+ let height = void 0;
95
+ let ratio = void 0;
96
+ let query = void 0;
97
+ if (hints.length && isImage(srcAbs)) {
98
+ try {
99
+ const size = getImageSize(srcAbs);
100
+ if (hints.includes("style")) {
101
+ ratio = `${size.width}/${size.height}`;
102
+ }
103
+ if (hints.includes("attrs")) {
104
+ width = size.width;
105
+ height = size.height;
106
+ }
107
+ if (hints.includes("url")) {
108
+ query = `?width=${width}&height=${height}`;
109
+ }
110
+ } catch (err) {
111
+ warn(`could not read image "${srcAbs}`);
112
+ }
113
+ }
114
+ const srcRel = Path.basename(srcDir) + srcAbs.substring(srcDir.length);
115
+ const srcAttr = interpolatePattern(pattern, srcRel);
116
+ const id = srcRel.replaceAll("/", ":");
117
+ return { id, srcRel, srcAttr, width, height, ratio, query };
118
+ }
119
+
120
+ async function getGithubAssets(key, source, tempPath, extensions) {
121
+ const storage = createStorage();
122
+ storage.mount(key, githubDriver({
123
+ repo: source.repo,
124
+ branch: source.branch || "main",
125
+ dir: source.dir || "/",
126
+ ttl: source.ttl || 600
127
+ }));
128
+ const rx = new RegExp(`.${extensions.join("|")}$`);
129
+ const keys = await storage.getKeys();
130
+ const assetKeys = keys.filter((key2) => rx.test(key2));
131
+ const assetItems = await Promise.all(assetKeys.map(async (id) => {
132
+ const data = await storage.getItem(id);
133
+ return { id, data };
134
+ }));
135
+ const prefix = source.prefix || "";
136
+ const paths = [];
137
+ for (const { id, data } of assetItems) {
138
+ if (data) {
139
+ const path = id.replaceAll(":", "/");
140
+ const absPath = Path.join(tempPath, path.replace(key, `${key}/${prefix}`));
141
+ const absFolder = Path.dirname(absPath);
142
+ const buffer = data.constructor.name === "Blob" ? Buffer.from(await data.arrayBuffer()) : typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
143
+ Fs.mkdirSync(absFolder, { recursive: true });
144
+ Fs.writeFileSync(absPath, buffer);
145
+ paths.push(absPath);
146
+ }
147
+ }
148
+ return paths;
149
+ }
150
+ function getFsAssets(path, extensions) {
151
+ const pattern = `${path}/**/*.{${extensions.join(",")}}`;
152
+ return glob.globSync(pattern) || [];
153
+ }
154
+
73
155
  const resolve = createResolver(import.meta.url).resolve;
74
156
  const module = defineNuxtModule({
75
157
  meta: {
@@ -86,98 +168,134 @@ const module = defineNuxtModule({
86
168
  imageSize: "",
87
169
  debug: false
88
170
  },
89
- setup(options, nuxt) {
171
+ async setup(options, nuxt) {
90
172
  var _a, _b;
91
173
  const pluginPath = resolve("./runtime/plugin");
92
174
  const buildPath = nuxt.options.buildDir;
93
- const cachePath = Path.resolve(buildPath, "content-assets");
175
+ const cachePath = Path.join(buildPath, "content-assets");
176
+ const publicPath = Path.join(cachePath, "public");
177
+ const tempPath = Path.resolve("node_modules/.nuxt-content-assets");
178
+ const dump = (name, data) => {
179
+ const path = `${cachePath}/debug/${name}.json`;
180
+ log(`Dumping "${Path.relative("", path)}"`);
181
+ writeFile(path, data);
182
+ };
94
183
  if (options.debug) {
95
184
  log("Removing cache folders...");
96
185
  }
97
- Fs.rmSync(Path.join(buildPath, "content-cache"), { recursive: true, force: true });
98
- Fs.rmSync(cachePath, { recursive: true, force: true });
99
- const sources = nuxt.options._layers.map((layer) => layer.config?.content?.sources).reduce((output2, sources2) => {
100
- if (sources2) {
101
- Object.assign(output2, getSources(sources2));
102
- }
103
- return output2;
104
- }, {});
105
- if (Object.keys(sources).length === 0 || !sources.content) {
106
- const content = nuxt.options.srcDir + "/content";
107
- if (Fs.existsSync(content)) {
108
- sources.content = content;
109
- }
186
+ removeFolder(Path.join(buildPath, "content-cache"));
187
+ removeFolder(cachePath);
188
+ removeFolder(tempPath);
189
+ (_a = nuxt.options).content || (_a.content = {});
190
+ if (nuxt.options.content) {
191
+ (_b = nuxt.options.content).ignores || (_b.ignores = []);
110
192
  }
111
193
  const output = options.output || defaults.assetsDir;
112
194
  const matches = output.match(/([^[]+)(.*)?/);
113
- const assetsDir = matches ? matches[1] : defaults.assetsDir;
195
+ const assetsDir = matches ? matches[1].replace(/^\/*/, "/").replace(/\/*$/, "") : defaults.assetsDir;
114
196
  const assetsPattern = (matches ? matches[2] : "") || defaults.assetsPattern;
115
- interpolatePattern(assetsPattern, "", "", true);
197
+ interpolatePattern(assetsPattern, "", true);
116
198
  const imageFlags = matchWords(options.imageSize);
117
199
  if (options.extensions?.trim()) {
118
200
  extensions.splice(0, extensions.length, ...matchWords(options.extensions));
119
201
  } else if (options.additionalExtensions) {
120
202
  extensions.push(...matchWords(options.additionalExtensions));
121
203
  }
122
- function getAssetConfig(pattern, src, dir) {
123
- let width = void 0;
124
- let height = void 0;
125
- let ratio = void 0;
126
- let query = void 0;
127
- if (imageFlags.length && isImage(src)) {
128
- const size = getImageSize(src);
129
- if (imageFlags.includes("style")) {
130
- ratio = `${size.width}/${size.height}`;
131
- }
132
- if (imageFlags.includes("attrs")) {
133
- width = size.width;
134
- height = size.height;
135
- }
136
- if (imageFlags.includes("url")) {
137
- query = `?width=${width}&height=${height}`;
138
- }
139
- }
140
- const id = Path.join(Path.basename(dir), Path.relative(dir, src)).replaceAll("/", ":");
141
- const file = interpolatePattern(pattern, src, dir);
142
- const trg = Path.join(cachePath, assetsDir, file);
143
- const rel = Path.join("/", assetsDir, file);
144
- return { id, file, trg, rel, width, height, ratio, query };
204
+ if (options.debug) {
205
+ log("Preparing sources...");
145
206
  }
146
- const publicFolder = Path.join(cachePath, assetsDir);
147
- const sourceFolders = Object.values(sources);
148
207
  const assets = {};
149
- (_a = nuxt.options).content || (_a.content = {});
150
- if (nuxt.options.content) {
151
- (_b = nuxt.options.content).ignores || (_b.ignores = []);
208
+ const sources = nuxt.options._layers.map((layer) => layer.config?.content?.sources).reduce((output2, sources2) => {
209
+ if (sources2) {
210
+ Object.assign(output2, sources2);
211
+ }
212
+ return output2;
213
+ }, {});
214
+ if (Object.keys(sources).length === 0 || !sources.content) {
215
+ const content = nuxt.options.srcDir + "/content";
216
+ if (Fs.existsSync(content)) {
217
+ sources.content = {
218
+ driver: "fs",
219
+ base: content
220
+ };
221
+ }
222
+ }
223
+ for (const [key, source] of Object.entries(sources)) {
224
+ const { driver } = source;
225
+ let srcDir = "";
226
+ let paths = [];
227
+ switch (driver) {
228
+ case "fs":
229
+ paths = getFsAssets(source.base, extensions);
230
+ srcDir = source.base;
231
+ break;
232
+ case "github":
233
+ paths = await getGithubAssets(key, source, tempPath, extensions);
234
+ srcDir = Path.join(tempPath, key);
235
+ break;
236
+ }
237
+ if (options.debug) {
238
+ log(`Prepared ${paths.length} paths for source "${key}"`);
239
+ }
240
+ if (paths.length) {
241
+ paths.forEach((src) => {
242
+ const {
243
+ id,
244
+ srcRel,
245
+ srcAttr,
246
+ width,
247
+ height,
248
+ ratio,
249
+ query
250
+ } = getAssetConfig(srcDir, src, assetsPattern, imageFlags);
251
+ nuxt.options.content.ignores.push(id);
252
+ const trg = Path.join(publicPath, assetsDir, srcAttr);
253
+ assets[srcRel] = {
254
+ src,
255
+ trg,
256
+ config: {
257
+ srcAttr: Path.join(assetsDir, srcAttr),
258
+ width,
259
+ height,
260
+ ratio,
261
+ query
262
+ }
263
+ };
264
+ });
265
+ }
152
266
  }
153
- sourceFolders.forEach((folder) => {
154
- const pattern = `${folder}/**/*.{${extensions.join(",")}}`;
155
- const paths = glob.globSync(pattern);
156
- paths.forEach((src) => {
157
- const config = getAssetConfig(assetsPattern, src, folder);
158
- nuxt.options.content.ignores.push(config.id);
159
- assets[src] = config;
160
- });
161
- });
162
267
  nuxt.hook("build:before", function() {
163
- Fs.mkdirSync(publicFolder, { recursive: true });
164
268
  if (options.debug) {
165
- const paths = Object.keys(assets).map((key) => " - " + assets[key].id.replaceAll(":", "/"));
166
- log(`Copying ${Object.keys(assets).length} assets:
167
-
168
- ${paths.join("\n")}
169
- `);
269
+ dump("assets", assets);
270
+ }
271
+ if (options.debug) {
272
+ log(`Copying ${Object.keys(assets).length} assets...`);
273
+ }
274
+ const copied = [];
275
+ const failed = [];
276
+ for (const [key, { src, trg }] of Object.entries(assets)) {
277
+ if (Fs.existsSync(src)) {
278
+ copyFile(src, trg);
279
+ copied.push(key);
280
+ } else {
281
+ failed.push(key);
282
+ }
283
+ }
284
+ if (options.debug) {
285
+ if (copied.length) {
286
+ list("Copied", copied);
287
+ }
288
+ if (failed.length) {
289
+ list("Failed to copy", failed);
290
+ }
170
291
  }
171
- Object.keys(assets).forEach((src) => {
172
- const { trg } = assets[src];
173
- const trgFolder = Path.dirname(trg);
174
- Fs.mkdirSync(trgFolder, { recursive: true });
175
- Fs.copyFileSync(src, trg);
176
- });
177
292
  });
293
+ const nitroAssets = Object.entries(assets).reduce((output2, [key, value]) => {
294
+ output2[key] = value.config;
295
+ return output2;
296
+ }, {});
178
297
  const virtualConfig = [
179
- `export const assets = ${JSON.stringify(assets)}`,
180
- `export const sources = ${JSON.stringify(sources)}`
298
+ `export const assets = ${JSON.stringify(nitroAssets, null, " ")}`
181
299
  ].join("\n");
182
300
  nuxt.options.alias[`#${moduleName}`] = addTemplate({
183
301
  filename: `${moduleName}.mjs`,
@@ -190,7 +308,7 @@ ${paths.join("\n")}
190
308
  config.virtual[`#${moduleName}`] = virtualConfig;
191
309
  config.publicAssets || (config.publicAssets = []);
192
310
  config.publicAssets.push({
193
- dir: cachePath
311
+ dir: publicPath
194
312
  // maxAge: 60 * 60 * 24 * 365 // 1 year
195
313
  });
196
314
  });
@@ -1,7 +1,7 @@
1
- import { matchWords } from "./utils/config.mjs";
1
+ import { matchWords } from "./utils/string.mjs";
2
2
  export const defaults = {
3
- assetsDir: "assets/content",
4
- assetsPattern: "[folder]/[file]"
3
+ assetsDir: "/assets/",
4
+ assetsPattern: "[path]/[file]"
5
5
  };
6
6
  export const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
7
7
  export const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
@@ -2,49 +2,42 @@ import Path from "path";
2
2
  import { visit } from "unist-util-visit";
3
3
  import { isValidAsset, walk } from "./utils/index.mjs";
4
4
  import { tags } from "./options.mjs";
5
- import { assets, sources } from "#nuxt-content-assets";
6
- function getDocPath(id) {
7
- const parts = id.split(":");
8
- const key = parts.shift();
9
- const relPath = parts.join("/");
10
- const absBase = sources[key];
11
- return Path.join(absBase, relPath);
12
- }
13
- function getAsset(absDoc, relAsset) {
14
- const absAsset = Path.join(Path.dirname(absDoc), relAsset);
15
- return assets[absAsset] || {};
5
+ import { assets } from "#nuxt-content-assets";
6
+ function getAsset(srcDoc, relAsset) {
7
+ const srcAsset = Path.join(Path.dirname(srcDoc), relAsset);
8
+ return assets[srcAsset] || {};
16
9
  }
17
10
  const plugin = async (nitro) => {
18
11
  nitro.hooks.hook("content:file:afterParse", async (file) => {
19
12
  if (file._id.endsWith(".md")) {
20
- const absDoc = getDocPath(file._id);
13
+ const srcDoc = file._id.split(":").join("/");
21
14
  const filter = (value, key) => !(String(key).startsWith("_") || key === "body");
22
15
  walk(file, (value, parent, key) => {
23
16
  if (isValidAsset(value)) {
24
- const { rel, query } = getAsset(absDoc, value);
25
- if (rel) {
26
- parent[key] = rel + (query || "");
17
+ const { srcAttr, query } = getAsset(srcDoc, value);
18
+ if (srcAttr) {
19
+ parent[key] = srcAttr + (query || "");
27
20
  }
28
21
  }
29
22
  }, filter);
30
23
  visit(file.body, (n) => tags.includes(n.tag), (node) => {
31
24
  if (node.props.src) {
32
- const { rel, width, height, ratio } = getAsset(absDoc, node.props.src);
33
- if (rel) {
34
- node.props.src = rel;
35
- }
36
- if (width && height) {
37
- node.props.width = width;
38
- node.props.height = height;
39
- }
40
- if (ratio) {
41
- node.props.style = `aspect-ratio:${ratio}`;
25
+ const { srcAttr, width, height, ratio } = getAsset(srcDoc, node.props.src);
26
+ if (srcAttr) {
27
+ node.props.src = srcAttr;
28
+ if (width && height) {
29
+ node.props.width = width;
30
+ node.props.height = height;
31
+ }
32
+ if (ratio) {
33
+ node.props.style = `aspect-ratio:${ratio}`;
34
+ }
42
35
  }
43
36
  } else if (node.tag === "a") {
44
37
  if (node.props.href) {
45
- const { rel } = getAsset(absDoc, node.props.href);
46
- if (rel) {
47
- node.props.href = rel;
38
+ const { srcAttr } = getAsset(srcDoc, node.props.href);
39
+ if (srcAttr) {
40
+ node.props.href = srcAttr;
48
41
  node.props.target = "_blank";
49
42
  }
50
43
  }
@@ -0,0 +1,18 @@
1
+ export type AssetConfig = {
2
+ id: string;
3
+ srcRel: string;
4
+ srcAttr: string;
5
+ width?: number;
6
+ height?: number;
7
+ ratio?: string;
8
+ query?: string;
9
+ };
10
+ /**
11
+ * Get config for asset
12
+ *
13
+ * @param srcDir The absolute path to the asset's source folder
14
+ * @param srcAbs The absolute path to the asset itself
15
+ * @param pattern The user-defined pattern to create the public src attribute
16
+ * @param hints A list of named image size hints, i.e. 'style', 'attrs', etc
17
+ */
18
+ export declare function getAssetConfig(srcDir: string, srcAbs: string, pattern: string, hints: string[]): AssetConfig;
@@ -0,0 +1,31 @@
1
+ import * as Path from "path";
2
+ import getImageSize from "image-size";
3
+ import { isImage, warn } from "../utils/index.mjs";
4
+ import { interpolatePattern } from "./paths.mjs";
5
+ export function getAssetConfig(srcDir, srcAbs, pattern, hints) {
6
+ let width = void 0;
7
+ let height = void 0;
8
+ let ratio = void 0;
9
+ let query = void 0;
10
+ if (hints.length && isImage(srcAbs)) {
11
+ try {
12
+ const size = getImageSize(srcAbs);
13
+ if (hints.includes("style")) {
14
+ ratio = `${size.width}/${size.height}`;
15
+ }
16
+ if (hints.includes("attrs")) {
17
+ width = size.width;
18
+ height = size.height;
19
+ }
20
+ if (hints.includes("url")) {
21
+ query = `?width=${width}&height=${height}`;
22
+ }
23
+ } catch (err) {
24
+ warn(`could not read image "${srcAbs}`);
25
+ }
26
+ }
27
+ const srcRel = Path.basename(srcDir) + srcAbs.substring(srcDir.length);
28
+ const srcAttr = interpolatePattern(pattern, srcRel);
29
+ const id = srcRel.replaceAll("/", ":");
30
+ return { id, srcRel, srcAttr, width, height, ratio, query };
31
+ }
@@ -0,0 +1,3 @@
1
+ export * from './assets';
2
+ export * from './sources';
3
+ export * from './paths';
@@ -0,0 +1,3 @@
1
+ export * from "./assets.mjs";
2
+ export * from "./sources.mjs";
3
+ export * from "./paths.mjs";
@@ -2,8 +2,7 @@
2
2
  * Interpolate assets path pattern
3
3
  *
4
4
  * @param pattern A path pattern with tokens
5
- * @param src The absolute path to a src asset
6
- * @param dir The absolute path to its containing folder
5
+ * @param src The relative path to a src asset
7
6
  * @param warn An optional flag to warn for unknown tokens
8
7
  */
9
- export declare function interpolatePattern(pattern: string, src: string, dir: string, warn?: boolean): string;
8
+ export declare function interpolatePattern(pattern: string, src: string, warn?: boolean): string;
@@ -1,20 +1,22 @@
1
1
  import Path from "path";
2
2
  import { hash } from "ohash";
3
- import { log } from "./debug.mjs";
3
+ import { log } from "../utils/index.mjs";
4
4
  const replacers = {
5
- folder: (src, dir) => Path.dirname(src.replace(dir, "")),
5
+ key: (src) => Path.dirname(src).split("/").filter((e) => e).shift() || "",
6
+ path: (src) => Path.dirname(src),
7
+ folder: (src) => Path.dirname(src).replace(/[^/]+\//, ""),
6
8
  file: (src) => Path.basename(src),
7
9
  name: (src) => Path.basename(src, Path.extname(src)),
8
10
  extname: (src) => Path.extname(src),
9
11
  ext: (src) => Path.extname(src).substring(1),
10
12
  hash: (src) => hash({ src })
11
13
  };
12
- export function interpolatePattern(pattern, src, dir, warn = false) {
14
+ export function interpolatePattern(pattern, src, warn = false) {
13
15
  return Path.join(pattern.replace(/\[\w+]/g, (match) => {
14
16
  const name = match.substring(1, match.length - 1);
15
17
  const fn = replacers[name];
16
18
  if (fn) {
17
- return fn(src, dir);
19
+ return fn(src);
18
20
  }
19
21
  if (warn) {
20
22
  log(`Unknown output token ${match}`, true);
@@ -0,0 +1,10 @@
1
+ type GithubOptions = {
2
+ repo: string;
3
+ branch?: string;
4
+ dir?: string;
5
+ prefix?: string;
6
+ ttl?: number;
7
+ };
8
+ export declare function getGithubAssets(key: string, source: GithubOptions, tempPath: string, extensions: string[]): Promise<string[]>;
9
+ export declare function getFsAssets(path: string, extensions: string[]): string[];
10
+ export {};
@@ -0,0 +1,39 @@
1
+ import * as Fs from "fs";
2
+ import * as Path from "path";
3
+ import glob from "glob";
4
+ import { createStorage } from "unstorage";
5
+ import githubDriver from "unstorage/drivers/github";
6
+ export async function getGithubAssets(key, source, tempPath, extensions) {
7
+ const storage = createStorage();
8
+ storage.mount(key, githubDriver({
9
+ repo: source.repo,
10
+ branch: source.branch || "main",
11
+ dir: source.dir || "/",
12
+ ttl: source.ttl || 600
13
+ }));
14
+ const rx = new RegExp(`.${extensions.join("|")}$`);
15
+ const keys = await storage.getKeys();
16
+ const assetKeys = keys.filter((key2) => rx.test(key2));
17
+ const assetItems = await Promise.all(assetKeys.map(async (id) => {
18
+ const data = await storage.getItem(id);
19
+ return { id, data };
20
+ }));
21
+ const prefix = source.prefix || "";
22
+ const paths = [];
23
+ for (const { id, data } of assetItems) {
24
+ if (data) {
25
+ const path = id.replaceAll(":", "/");
26
+ const absPath = Path.join(tempPath, path.replace(key, `${key}/${prefix}`));
27
+ const absFolder = Path.dirname(absPath);
28
+ const buffer = data.constructor.name === "Blob" ? Buffer.from(await data.arrayBuffer()) : typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
29
+ Fs.mkdirSync(absFolder, { recursive: true });
30
+ Fs.writeFileSync(absPath, buffer);
31
+ paths.push(absPath);
32
+ }
33
+ }
34
+ return paths;
35
+ }
36
+ export function getFsAssets(path, extensions) {
37
+ const pattern = `${path}/**/*.{${extensions.join(",")}}`;
38
+ return glob.globSync(pattern) || [];
39
+ }
@@ -1,3 +1,3 @@
1
1
  export declare function log(...data: any[]): void;
2
2
  export declare function warn(...data: any[]): void;
3
- export declare function dump(name: string, data: any): void;
3
+ export declare function list(message: string, items: string[]): void;
@@ -1,5 +1,3 @@
1
- import Path from "path";
2
- import Fs from "fs";
3
1
  import { moduleKey } from "../config.mjs";
4
2
  export function log(...data) {
5
3
  console.info(`[${moduleKey}]`, ...data);
@@ -7,10 +5,9 @@ export function log(...data) {
7
5
  export function warn(...data) {
8
6
  console.warn(`[${moduleKey}]`, ...data);
9
7
  }
10
- export function dump(name, data) {
11
- const path = `debug/${name}.json`;
12
- const folder = Path.dirname(path);
13
- log(`Dumping "${path}"`);
14
- Fs.mkdirSync(folder, { recursive: true });
15
- Fs.writeFileSync(path, JSON.stringify(data, null, " "), { encoding: "utf8" });
8
+ export function list(message, items) {
9
+ log(`${message}:
10
+
11
+ ${items.map((item) => ` - ${item}`).join("\n")}
12
+ `);
16
13
  }
@@ -0,0 +1,4 @@
1
+ export declare function writeFile(path: string, data: null | string | number | boolean | object): void;
2
+ export declare function copyFile(src: string, trg: string): void;
3
+ export declare function createFolder(path: string): void;
4
+ export declare function removeFolder(path: string): void;
@@ -0,0 +1,20 @@
1
+ import * as Path from "path";
2
+ import * as Fs from "fs";
3
+ export function writeFile(path, data) {
4
+ const text = typeof data === "object" ? JSON.stringify(data, null, " ") : String(data);
5
+ createFolder(Path.dirname(path));
6
+ Fs.writeFileSync(path, text, { encoding: "utf8" });
7
+ }
8
+ export function copyFile(src, trg) {
9
+ createFolder(Path.dirname(trg));
10
+ Fs.copyFileSync(src, trg);
11
+ }
12
+ export function createFolder(path) {
13
+ Fs.mkdirSync(path, { recursive: true });
14
+ }
15
+ export function removeFolder(path) {
16
+ const isDownstream = path.startsWith(Path.resolve());
17
+ if (isDownstream) {
18
+ Fs.rmSync(path, { recursive: true, force: true });
19
+ }
20
+ }
@@ -1,6 +1,5 @@
1
- export * from './assets';
2
- export * from './config';
3
- export * from './content';
1
+ export * from './assert';
4
2
  export * from './debug';
3
+ export * from './fs';
4
+ export * from './string';
5
5
  export * from './object';
6
- export * from './paths';
@@ -1,6 +1,5 @@
1
- export * from "./assets.mjs";
2
- export * from "./config.mjs";
3
- export * from "./content.mjs";
1
+ export * from "./assert.mjs";
4
2
  export * from "./debug.mjs";
3
+ export * from "./fs.mjs";
4
+ export * from "./string.mjs";
5
5
  export * from "./object.mjs";
6
- export * from "./paths.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-content-assets",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Enable locally-located assets in Nuxt Content",
5
5
  "repository": "davestewart/nuxt-content-assets",
6
6
  "license": "MIT",
@@ -35,7 +35,8 @@
35
35
  "glob": "^9.3.2",
36
36
  "image-size": "^1.0.2",
37
37
  "ohash": "^1.0.0",
38
- "unist-util-visit": "^4.1.2"
38
+ "unist-util-visit": "^4.1.2",
39
+ "unstorage": "^1.4.1"
39
40
  },
40
41
  "peerDependencies": {
41
42
  "@nuxt/content": "latest"
@@ -50,5 +51,8 @@
50
51
  "eslint": "^8.36.0",
51
52
  "nuxt": "^3.3.2",
52
53
  "vitest": "^0.29.7"
54
+ },
55
+ "engines": {
56
+ "node": ">=14.0.0"
53
57
  }
54
58
  }
@@ -1,7 +0,0 @@
1
- import type { MountOptions } from '@nuxt/content';
2
- /**
3
- * Get content source folders
4
- *
5
- * @param sources
6
- */
7
- export declare function getSources(sources: Record<string, MountOptions>): Record<string, string>;
@@ -1,12 +0,0 @@
1
- export function getSources(sources) {
2
- return Object.keys(sources).reduce((output, key) => {
3
- const source = sources[key];
4
- if (source) {
5
- const { driver, base } = source;
6
- if (driver === "fs") {
7
- output[key] = base;
8
- }
9
- }
10
- return output;
11
- }, {});
12
- }
File without changes
File without changes
File without changes
File without changes