@slybridges/kiss 0.5.5 → 0.6.2

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
@@ -42,7 +42,7 @@ Concept is being tuned until reaching v1. Things might break. Use at your own ri
42
42
 
43
43
  ## Requirements
44
44
 
45
- Node 10 or above.
45
+ Node 12 or above.
46
46
 
47
47
  ## Quick start
48
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slybridges/kiss",
3
- "version": "0.5.5",
3
+ "version": "0.6.2",
4
4
  "description": "Keep It Simple and Static site generator",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -12,24 +12,24 @@
12
12
  "author": "Sylvestre Dupont",
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "browser-sync": "^2.27.5",
15
+ "browser-sync": "^2.27.7",
16
16
  "chalk": "^4.1.2",
17
17
  "cheerio": "^1.0.0-rc.10",
18
- "chokidar": "^3.5.2",
19
- "date-fns": "^2.23.0",
20
- "fast-glob": "^3.2.7",
18
+ "chokidar": "^3.5.3",
19
+ "date-fns": "^2.28.0",
20
+ "fast-glob": "^3.2.11",
21
21
  "front-matter": "^4.0.2",
22
22
  "fs-extra": "^10.0.0",
23
23
  "lodash": "^4.17.21",
24
- "marked": "^3.0.0",
24
+ "marked": "^4.0.12",
25
25
  "nunjucks": "^3.2.3",
26
- "sharp": "^0.28.3",
27
- "slugify": "^1.6.0",
26
+ "sharp": "^0.30.1",
27
+ "slugify": "^1.6.5",
28
28
  "xml": "^1.0.1",
29
- "yargs": "^17.1.1"
29
+ "yargs": "^17.3.1"
30
30
  },
31
31
  "devDependencies": {
32
- "eslint": "^7.32.0"
32
+ "eslint": "^8.9.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
package/src/build.js CHANGED
@@ -152,7 +152,14 @@ const computeDataViews = (context, config) => {
152
152
  const message =
153
153
  description || `Computing '${attribute}' data view using ${handler.name}`
154
154
  global.logger.info(message)
155
- _.set(context, attribute, handler(context, options, config))
155
+ try {
156
+ _.set(context, attribute, handler(context, options, config))
157
+ } catch (err) {
158
+ global.logger.error(
159
+ `[${handler.name}] Error during computing data view for '${attribute}'\n`,
160
+ err.stack
161
+ )
162
+ }
156
163
  })
157
164
  return context
158
165
  }
@@ -224,7 +231,14 @@ const computeAllPagesData = (context, config) => {
224
231
  while (round === 1 || pendingTotal > 0) {
225
232
  pendingTotal = 0
226
233
  _.forEach(context.pages, (page, key) => {
227
- computed = computePageData(page, config, context)
234
+ try {
235
+ computed = computePageData(page, config, context)
236
+ } catch (err) {
237
+ global.logger.error(
238
+ `[computePageData] Error during computing page data for page id '${page._meta.id}'\n`,
239
+ err.stack
240
+ )
241
+ }
228
242
  context.pages[key] = computed.data
229
243
  pendingTotal += computed.pendingCount
230
244
  })
@@ -326,9 +340,12 @@ const loadContent = async (config, context) => {
326
340
  config.loaders
327
341
  .filter((loader) => !loader.source || loader.source === "file")
328
342
  .map(async ({ handler, namespace, ...loaderOptions }) => {
329
- const { description, match, matchOptions = {}, ...options } = namespace
330
- ? _.get(config, namespace, {})
331
- : loaderOptions
343
+ const {
344
+ description,
345
+ match,
346
+ matchOptions = {},
347
+ ...options
348
+ } = namespace ? _.get(config, namespace, {}) : loaderOptions
332
349
  if ("active" in options && !options.active) {
333
350
  global.logger.log(`- [${handler.name}]: loader not active. Skipping.`)
334
351
  return
@@ -1,6 +1,5 @@
1
1
  const cheerio = require("cheerio")
2
- const path = require("path")
3
- const { isValidURL } = require("../helpers")
2
+ const { getAbsolutePath } = require("../helpers")
4
3
 
5
4
  const computeImage = (
6
5
  page,
@@ -8,47 +7,67 @@ const computeImage = (
8
7
  { pages, site },
9
8
  { setDefaultImage = true } = {}
10
9
  ) => {
10
+ if (typeof page.cover === "string") {
11
+ // there is a manually set cover image
12
+ return getImagePermalink(
13
+ page.cover,
14
+ page.permalink,
15
+ setDefaultImage,
16
+ site.image
17
+ )
18
+ }
19
+ // needed as computeImage is called recursively
11
20
  if (typeof page.image === "string") {
12
- // needed as computeImage is called recursively
13
- return page.image
21
+ return getImagePermalink(
22
+ page.image,
23
+ page.permalink,
24
+ setDefaultImage,
25
+ site.image
26
+ )
14
27
  }
15
28
  if (!page.content) {
16
- // check if there are descendants
29
+ // Search in descendants
17
30
  if (!page._meta.descendants || page._meta.descendants.length === 0) {
18
31
  return setDefaultImage ? site.image : null
19
32
  }
20
33
  // return the first image it finds
21
- let id = page._meta.descendants.find((id) =>
22
- computeImage(
23
- pages[id],
24
- config,
25
- { pages, site },
26
- { setDefaultImage: false }
27
- )
28
- )
29
- if (id) {
30
- return computeImage(
31
- pages[id],
34
+ for (const id of page._meta.descendants) {
35
+ const descendant = pages[id]
36
+ let image = computeImage(
37
+ descendant,
32
38
  config,
33
39
  { pages, site },
34
40
  { setDefaultImage: false }
35
41
  )
42
+ if (image) {
43
+ // found an image in descendants: break for loop
44
+ // no need to call getImagePermalink() here, it would have been called in recursive call
45
+ return image
46
+ }
36
47
  }
48
+ // no result anywhere
37
49
  return setDefaultImage ? site.image : null
38
50
  }
39
51
  // searches for an img tag in content
40
52
  const $ = cheerio.load(page.content)
41
53
  const src = $("img").first().attr("src")
42
- if (!src) {
43
- return setDefaultImage ? site.image : null
44
- }
45
- if (isValidURL(src)) {
46
- return src
47
- }
48
- // return the image, as an absolute path
49
- return path.isAbsolute(src) ? src : path.join(page.permalink, src)
54
+ return getImagePermalink(src, page.permalink, setDefaultImage, site.image)
50
55
  }
51
56
 
52
57
  computeImage.kissDependencies = ["content", "permalink", "_meta.descendants"]
53
58
 
54
59
  module.exports = computeImage
60
+
61
+ /** private */
62
+
63
+ const getImagePermalink = (
64
+ src,
65
+ pagePermalink,
66
+ setDefaultImage,
67
+ defaultImage
68
+ ) => {
69
+ if (!src) {
70
+ return setDefaultImage ? defaultImage : null
71
+ }
72
+ return getAbsolutePath(src, pagePermalink, { throwIfInvalid: true })
73
+ }
package/src/helpers.js CHANGED
@@ -29,10 +29,16 @@ const getAbsolutePath = (pathname, basePath, options = {}) => {
29
29
  `[getAbsolutePath]: expected path to be a string, got '${typeof pathname}'.`
30
30
  )
31
31
  }
32
- if (isValidURL(pathname) || path.isAbsolute(pathname)) {
33
- // don't change urls or absolute paths
32
+ if (isValidURL(pathname)) {
33
+ // don't change urls
34
34
  return pathname
35
35
  }
36
+ if (path.isAbsolute(pathname)) {
37
+ // prefix absolute paths with absoluteBase, if any
38
+ return options.absoluteBase
39
+ ? path.join(options.absoluteBase, pathname)
40
+ : pathname
41
+ }
36
42
  if (!isValidPath(pathname)) {
37
43
  if (options.throwIfInvalid) {
38
44
  throw new Error(`[getAbsolutePath]: Path '${pathname}' is invalid`)
@@ -87,6 +93,25 @@ const getDescendantPages = (
87
93
  return descendants
88
94
  }
89
95
 
96
+ /** Computes the input path based on the permalink by checking if the parent
97
+ * had a permalink different than their input path */
98
+ const getInputPath = (permalink, pages, baseContentPath) => {
99
+ const pathPbject = path.parse(permalink)
100
+ // search if a have a parent corresponding to this permalink
101
+ const parent = _.find(
102
+ pages,
103
+ (page) => page.permalink === pathPbject.dir + "/"
104
+ )
105
+ if (!parent) {
106
+ // no result: assume inputPath same as permalink
107
+ return path.join(baseContentPath, permalink)
108
+ }
109
+ return path.join(
110
+ path.dirname(parent._meta.inputPath), // inputPath of parent's dir
111
+ pathPbject.base // filename
112
+ )
113
+ }
114
+
90
115
  const getLocale = (context, sep = "-") => {
91
116
  const locale = _.get(context, "site.locale")
92
117
  if (!locale) {
@@ -197,6 +222,7 @@ module.exports = {
197
222
  getAbsoluteURL,
198
223
  getChildrenPages,
199
224
  getDescendantPages,
225
+ getInputPath,
200
226
  getLocale,
201
227
  getPageId,
202
228
  getParentId,
@@ -1,4 +1,4 @@
1
- const marked = require("marked")
1
+ const { marked } = require("marked")
2
2
 
3
3
  const loadMarked = (_, config) => {
4
4
  config.libs.marked = marked
@@ -1,6 +1,7 @@
1
1
  const parseISO = require("date-fns/parseISO")
2
2
  const _formatDate = require("date-fns/format")
3
3
  const _formatDateISO = require("date-fns/formatISO")
4
+ const locales = require("date-fns/locale")
4
5
 
5
6
  const allFilters = (_, config) => {
6
7
  if (!config.libs.nunjucks) {
@@ -23,7 +24,15 @@ const formatDate = (_, config) => {
23
24
  return ""
24
25
  }
25
26
  let date = typeof str === "string" ? parseISO(str) : str
26
- return _formatDate(date, format || config.defaults.dateFormat)
27
+ const fullLocale = config.context.site.locale.join("")
28
+ const shortLocale = config.context.site.locale[0]
29
+ let locale = locales["en"]
30
+ if (locales[fullLocale]) {
31
+ locale = locales[fullLocale]
32
+ } else if (locales[shortLocale]) {
33
+ locale = locales[shortLocale]
34
+ }
35
+ return _formatDate(date, format || config.defaults.dateFormat, { locale })
27
36
  })
28
37
  return config
29
38
  }
@@ -12,10 +12,7 @@ const computeCollectionLoader = (pages, options, config) => {
12
12
  options.pageData = options.pageData || pages["."]
13
13
  options.filter = options.filter || ((page) => !!page.content) //no access to isPost yet (we're pre-cascade)
14
14
  options.name = options.name || options.groupBy
15
- let baseLink = path.join(
16
- "/",
17
- options.baseLink || libs.slugify(options.groupBy)
18
- )
15
+ let baseLink = path.join("/", options.baseLink || libs.slugify(options.name))
19
16
  if (options.groupByType === "array") {
20
17
  if (typeof options.groupBy !== "string") {
21
18
  throw new Error(
@@ -3,7 +3,12 @@ const cheerio = require("cheerio")
3
3
  const path = require("path")
4
4
  const sharp = require("sharp")
5
5
 
6
- const { getAbsolutePath, isValidURL } = require("../helpers")
6
+ const {
7
+ getAbsolutePath,
8
+ getInputPath,
9
+ getPageId,
10
+ isValidURL,
11
+ } = require("../helpers")
7
12
 
8
13
  const META_SELECTORS = [
9
14
  "meta[property='og:image']",
@@ -25,17 +30,25 @@ const imageContextTransform = async (context, options, config) => {
25
30
  `Image '${src}' on page '${page._meta.outputPath}' has no 'alt' attribute.`
26
31
  )
27
32
  }
28
- let imgPath = getAbsolutePath(src, page.permalink, {
33
+ const imgPermalink = getAbsolutePath(src, page.permalink, {
29
34
  throwIfInvalid: true,
30
35
  })
36
+ const imgInputPath = getInputPath(
37
+ imgPermalink,
38
+ context.pages,
39
+ config.dirs.content
40
+ )
41
+ const imgId = getPageId(imgInputPath, config)
31
42
  const imageDetails = await getImageDetails(
32
- imgPath,
43
+ imgInputPath,
44
+ imgPermalink,
45
+ imgId,
33
46
  id,
34
47
  context,
35
48
  options,
36
49
  config
37
50
  )
38
- context.pages[imgPath] = imageDetails
51
+ context.pages[imgId] = imageDetails
39
52
  if (!imageDetails._meta.is404) {
40
53
  $(img).replaceWith(getImageTag($(img).clone(), imageDetails))
41
54
  context.pages[id]._html = $.html()
@@ -55,15 +68,23 @@ const imageContextTransform = async (context, options, config) => {
55
68
  return
56
69
  }
57
70
  const url = new URL(content)
58
- const imgPath = decodeURI(url.pathname)
71
+ const imgPermalink = decodeURI(url.pathname)
72
+ const imgInputPath = getInputPath(
73
+ imgPermalink,
74
+ context.pages,
75
+ config.dirs.content
76
+ )
77
+ const imgId = getPageId(imgInputPath, config)
59
78
  const imageDetails = await getImageDetails(
60
- imgPath,
79
+ imgInputPath,
80
+ imgPermalink,
81
+ imgId,
61
82
  id,
62
83
  context,
63
84
  options,
64
85
  config
65
86
  )
66
- context.pages[imgPath] = imageDetails
87
+ context.pages[imgId] = imageDetails
67
88
  if (!imageDetails._meta.is404) {
68
89
  const newPathname = getDefaultDerivative(imageDetails).permalink
69
90
  $(selector).attr("content", new URL(newPathname, url.origin))
@@ -147,27 +168,37 @@ const getDerivatives = (page, options, config) => {
147
168
  return derivatives
148
169
  }
149
170
 
150
- const getImageDetails = async (imgPath, sourceId, context, options, config) => {
171
+ const getImageDetails = async (
172
+ inputPath,
173
+ permalink,
174
+ id,
175
+ sourceId,
176
+ context,
177
+ options,
178
+ config
179
+ ) => {
151
180
  let details = {}
152
- if (!context.pages[imgPath]) {
153
- global.logger.log(`- [imageContextTransform] image found: '${imgPath}'`)
181
+ if (!context.pages[id]) {
182
+ global.logger.log(
183
+ `- [imageContextTransform] new image found: '${inputPath}'`
184
+ )
154
185
  details = {
155
186
  blur: options.blur,
156
187
  defaultWidth: options.defaultWidth || options.widths[0],
157
188
  defaultFormat: options.defaultFormat || options.formats[0],
158
189
  derivatives: [],
159
190
  formats: options.formats,
160
- permalink: imgPath, // original permalink
161
- permalinkDir: path.dirname(imgPath),
191
+ permalink,
192
+ permalinkDir: path.dirname(permalink),
162
193
  sizes: options.sizes || [],
163
194
  sources: [],
164
- _meta: await getImageMetadata(imgPath, options, config),
195
+ _meta: await getImageMetadata(inputPath, permalink, id, options, config),
165
196
  }
166
197
  if (!details._meta.is404) {
167
198
  details.derivatives = getDerivatives(details, options, config)
168
199
  }
169
200
  } else {
170
- details = context.pages[imgPath]
201
+ details = context.pages[id]
171
202
  }
172
203
  if (details.sources.indexOf(sourceId) === -1) {
173
204
  details.sources.push(sourceId)
@@ -202,22 +233,21 @@ const getImageTag = (imgNode, page) => {
202
233
  return picture
203
234
  }
204
235
 
205
- const getImageMetadata = async (imgPath, options, config) => {
206
- const { ext, name } = path.parse(imgPath)
207
- const srcPath = path.join(config.dirs.content, imgPath)
208
- const outputPath = path.join(config.dirs.public, imgPath)
236
+ const getImageMetadata = async (inputPath, permalink, id, options, config) => {
237
+ const { ext, name } = path.parse(inputPath)
238
+ const outputPath = path.join(config.dirs.public, permalink)
209
239
  let meta = {
210
240
  basename: name + ext,
211
241
  ext,
212
- id: imgPath,
213
- inputPath: srcPath,
214
- isURL: isValidURL(imgPath),
242
+ id,
243
+ inputPath,
244
+ isURL: isValidURL(permalink),
215
245
  name,
216
246
  outputPath, // output path of original file
217
247
  outputType: "IMAGE",
218
248
  }
219
249
  try {
220
- const image = sharp(srcPath)
250
+ const image = sharp(inputPath)
221
251
  const { format, width, height } = await image.metadata()
222
252
  meta = {
223
253
  ...meta,
@@ -230,7 +260,7 @@ const getImageMetadata = async (imgPath, options, config) => {
230
260
  }
231
261
  } catch (err) {
232
262
  global.logger.error(
233
- `[getImageMetadata] Error getting image metadata for ${srcPath}\n`,
263
+ `[getImageMetadata] Error getting image metadata for ${inputPath}\n`,
234
264
  err.stack
235
265
  )
236
266
  meta.is404 = true
@@ -0,0 +1,45 @@
1
+ const _ = require("lodash")
2
+
3
+ const computeIterableCollectionDataView = (context, options = {}, config) => {
4
+ if (!options.name) {
5
+ throw new Error(
6
+ "computeIterableCollectionDataView needs a collection 'name' option."
7
+ )
8
+ }
9
+ if (!context.collections) {
10
+ throw new Error(
11
+ "computeIterableCollectionDataView: collections not found. Were they computed?"
12
+ )
13
+ }
14
+ const collection = context.collections[options.name]
15
+ if (!collection) {
16
+ throw new Error(
17
+ `computeIterableCollectionDataView: no collection with name '${options.name}'`
18
+ )
19
+ }
20
+ return getCollectionObject(collection, "allPosts", context, config)
21
+ }
22
+
23
+ module.exports = computeIterableCollectionDataView
24
+
25
+ /** Private **/
26
+
27
+ const getCollectionObject = (collection, name, context, config) => {
28
+ const page = context.pages[collection._id]
29
+ if (!page) {
30
+ throw new Error(
31
+ `computeIterableCollectionDataView: couldn't find page with id'${collection._id}'`
32
+ )
33
+ }
34
+ const childrenCollection = _.pickBy(collection, (entry) =>
35
+ _.isPlainObject(entry)
36
+ )
37
+ return {
38
+ name: _.startCase(name),
39
+ entry: page,
40
+ allPosts: collection.allPosts,
41
+ children: _.map(childrenCollection, (child, childName) =>
42
+ getCollectionObject(child, childName, context, config)
43
+ ),
44
+ }
45
+ }
@@ -1,9 +1,11 @@
1
1
  const computeCategoriesDataView = require("./computeCategoriesDataView")
2
2
  const computeCollectionDataView = require("./computeCollectionDataView")
3
+ const computeIterableCollectionDataView = require("./computeIterableCollectionDataView")
3
4
  const computeSiteLastUpdatedDataView = require("./computeSiteLastUpdatedDataView")
4
5
 
5
6
  module.exports = {
6
7
  computeCategoriesDataView,
7
8
  computeCollectionDataView,
9
+ computeIterableCollectionDataView,
8
10
  computeSiteLastUpdatedDataView,
9
11
  }