@jungvonmatt/cssg-plugin-hugo 1.0.0 → 1.0.4

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/.eslintrc.cjs ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ root: true,
3
+ plugins: ['prettier'],
4
+ extends: ["xo-space", 'plugin:prettier/recommended'],
5
+ ignorePatterns: ['**/*.cjs', 'src/**/*.test.js', 'src/__test__/*'],
6
+ env: {
7
+ node: true,
8
+ jest: true,
9
+ }
10
+ };
package/CHANGELOG.md CHANGED
@@ -3,6 +3,38 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.0.4](https://github.com/jungvonmatt/contentful-ssg/compare/v1.0.3...v1.0.4) (2021-11-29)
7
+
8
+ **Note:** Version bump only for package @jungvonmatt/cssg-plugin-hugo
9
+
10
+
11
+
12
+
13
+
14
+ ## [1.0.3](https://github.com/jungvonmatt/contentful-ssg/compare/v1.0.2...v1.0.3) (2021-11-29)
15
+
16
+ **Note:** Version bump only for package @jungvonmatt/cssg-plugin-hugo
17
+
18
+
19
+
20
+
21
+
22
+ ## [1.0.2](https://github.com/jungvonmatt/contentful-ssg/compare/v1.0.1...v1.0.2) (2021-11-29)
23
+
24
+ **Note:** Version bump only for package @jungvonmatt/cssg-plugin-hugo
25
+
26
+
27
+
28
+
29
+
30
+ ## [1.0.1](https://github.com/jungvonmatt/contentful-ssg/compare/v1.0.0...v1.0.1) (2021-11-25)
31
+
32
+ **Note:** Version bump only for package @jungvonmatt/cssg-plugin-hugo
33
+
34
+
35
+
36
+
37
+
6
38
  # [1.0.0](https://github.com/jungvonmatt/contentful-ssg/compare/v1.0.0-alpha.0...v1.0.0) (2021-11-25)
7
39
 
8
40
  **Note:** Version bump only for package @jungvonmatt/cssg-plugin-hugo
package/README.md CHANGED
@@ -25,7 +25,6 @@ The configured translations can then be used using the [`i18n`](https://gohugo.i
25
25
  <p>
26
26
 
27
27
  ```js
28
-
29
28
  module.exports = function (migration) {
30
29
  const dI18n = migration
31
30
  .createContentType('d-i18n')
@@ -74,6 +73,7 @@ module.exports = function (migration) {
74
73
  });
75
74
  };
76
75
  ```
76
+
77
77
  </p>
78
78
  </details>
79
79
 
@@ -101,7 +101,6 @@ All reference fields are extended by the path to the associated markdown file so
101
101
  {{ end }}
102
102
  ```
103
103
 
104
-
105
104
  ## Install
106
105
 
107
106
  `npm install @jungvonmatt/cssg-plugin-hugo`
@@ -122,15 +121,16 @@ plugins: [
122
121
 
123
122
  ## Options
124
123
 
125
- | Name | Type | Default | Description |
126
- | -------------- | --------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
- | typeIdSettings | `string` | `'d-settings'` | The id of the settings content type. |
128
- | typeIdI18n | `string` | `'d-i18n'` | The id of the i18n content type for the translation of strings. |
129
- | fieldIdHome | `string` | `'home'` | The id of reference field to the home page in the settings content type. |
130
- | fieldIdSlug | `string` | `'slug'` | The id of the slug field in page content types. |
131
- | fieldIdParent | `string` | `'parent_page'` | The id of the parent page reference field in page content types. |
132
- | languageConfig | `boolean` | `true` | Auto-generate the hugo language config based on your locale configuration in contentful. |
133
- | typeConfig | `object` | `{ page: ['page'], data: ['d-*']}` | Pass a map with entry types (`data`, `map`) pointing to one or more glob patterns matching the content type ids.\ Data types will be stored inside the `/data/` directory. \ pages types will be stored inside `/content/<locale>/`.\ All content types that do not match are considered headless and will be stored inside `/content/headless` |
124
+ | Name | Type | Default | Description |
125
+ | ------------------- | ------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
126
+ | typeIdSettings | `string` | `'d-settings'` | The id of the settings content type. |
127
+ | typeIdI18n | `string` | `'d-i18n'` | The id of the i18n content type for the translation of strings. |
128
+ | fieldIdHome | `string` | `'home'` | The id of reference field to the home page in the settings content type. |
129
+ | fieldIdSlug | `string` | `'slug'` | The id of the slug field in page content types. |
130
+ | fieldIdParent | `string` | `'parent_page'` | The id of the parent page reference field in page content types. |
131
+ | languageConfig | `boolean` | `true` | Auto-generate the hugo language config based on your locale configuration in contentful. |
132
+ | translationStrategy | `'filename'` | `'directory'` \| `'filename'` | How to translate your content. See https://gohugo.io/content-management/multilingual/#translate-your-content |
133
+ | typeConfig | `object` | `{ page: ['page']}` | Pass a map with entry types (`page`) pointing to one or more glob patterns matching the content type ids.\ Data types will be stored inside the `/data/` directory. \ pages types will be stored inside `/content/${pagesFolder}/`.\ All content types that do not match are considered headless and will be stored inside `/content/${headlessFolder}/` |
134
134
 
135
135
  Example:
136
136
 
package/index.d.ts ADDED
@@ -0,0 +1,16 @@
1
+
2
+ export type TYPECONFIG_KEY = 'page' | 'data' | 'headless';
3
+ export type TRANSLATION_STRATEGY = 'directory' | 'filename';
4
+
5
+ export interface AssetPluginConfig {
6
+ typeIdSettings: string;
7
+ translationStrategy: TRANSLATION_STRATEGY;
8
+ typeIdI18n: string;
9
+ languageConfig: boolean;
10
+ fieldIdHome: string;
11
+ fieldIdSlug: string;
12
+ fieldIdParent: string;
13
+ typeConfig: {
14
+ [x: TYPECONFIG_KEY]: string | string[];
15
+ },
16
+ }
package/index.js CHANGED
@@ -1,66 +1,72 @@
1
-
2
1
  import mergeOptionsModule from 'merge-options';
3
2
 
4
- import {snakeCaseKeys} from '@jungvonmatt/contentful-ssg/lib/object';
3
+ import { snakeCaseKeys } from '@jungvonmatt/contentful-ssg/lib/object';
5
4
  import mm from 'micromatch';
6
- import {existsSync} from 'fs';
7
- import {writeFile, readFile} from 'fs/promises';
5
+ import { existsSync } from 'fs';
6
+ import { writeFile, readFile } from 'fs/promises';
8
7
  import path from 'path';
9
8
 
10
9
  const mergeOptions = mergeOptionsModule.bind({ ignoreUndefined: true });
11
10
 
12
- export const TYPE_PAGE = 'page';
11
+ export const TYPE_CONTENT = 'content';
13
12
  export const TYPE_DATA = 'data';
14
- export const TYPE_HEADLESS = 'headless';
13
+
14
+ export const STRATEGY_DIRECTORY = 'directory';
15
+ export const STRATEGY_FILENAME = 'filename';
15
16
 
16
17
  const defaultOptions = {
17
18
  typeIdSettings: 'd-settings',
19
+ translationStrategy: STRATEGY_DIRECTORY,
18
20
  typeIdI18n: 'd-i18n',
19
21
  languageConfig: true,
20
22
  fieldIdHome: 'home',
21
23
  fieldIdSlug: 'slug',
22
24
  fieldIdParent: 'parent_page',
23
- typeConfig: {
24
- [TYPE_PAGE]: ['page'],
25
- [TYPE_DATA]: ['d-*'],
25
+ typeConfig: {
26
+ [TYPE_CONTENT]: ['page'],
26
27
  },
27
- }
28
+ };
28
29
 
29
30
  export default (args) => {
30
- const options = {...defaultOptions, ...(args || {})}
31
-
32
-
31
+ const options = { ...defaultOptions, ...(args || {}) };
33
32
 
34
33
  const getSettingsHelper = (runtimeContext) => {
35
34
  let settings = {};
36
35
  if (options.typeIdSettings) {
37
- settings = Object.fromEntries(Array.from((runtimeContext?.localized ?? new Map()).entries()).map(([locale, contentfulData]) => {
38
- const entryMap = contentfulData?.entryMap ?? new Map();
39
- const settingsEntries = (Array.from(entryMap.values())).filter(entry => (entry?.sys?.contentType?.sys?.id ?? 'unknown') === options.typeIdSettings);
40
- const settingsFields = settingsEntries.map(entry => entry?.fields ?? {});
41
- return [locale, mergeOptions(...settingsFields)];
42
- }));
36
+ settings = Object.fromEntries(
37
+ Array.from((runtimeContext?.localized ?? new Map()).entries()).map(
38
+ ([locale, contentfulData]) => {
39
+ const entryMap = contentfulData?.entryMap ?? new Map();
40
+ const settingsEntries = Array.from(entryMap.values()).filter(
41
+ (entry) => (entry?.sys?.contentType?.sys?.id ?? 'unknown') === options.typeIdSettings
42
+ );
43
+ const settingsFields = settingsEntries.map((entry) => entry?.fields ?? {});
44
+ return [locale, mergeOptions(...settingsFields)];
45
+ }
46
+ )
47
+ );
43
48
  }
44
49
 
45
- return (key, locale, defaultValue) => settings?.[locale]?.[key] ?? defaultValue;
46
- }
50
+ return (key, locale, defaultValue) => settings?.[locale]?.[key] ?? defaultValue;
51
+ };
47
52
 
48
53
  const getEntryType = (transformContext) => {
49
- const {contentTypeId} = transformContext;
50
- const [type = TYPE_HEADLESS] = Object.entries(options?.typeConfig ?? {}).find(([,pattern]) => mm.isMatch(contentTypeId, pattern)) || [];
54
+ const { contentTypeId } = transformContext;
55
+ const [type = TYPE_DATA] =
56
+ Object.entries(options?.typeConfig ?? {}).find(([, pattern]) =>
57
+ mm.isMatch(contentTypeId, pattern)
58
+ ) || [];
51
59
 
52
60
  return type;
53
- }
54
-
55
-
61
+ };
56
62
 
57
63
  return {
58
64
  // Before hook
59
65
  async before(runtimeContext) {
60
- const {helper,converter, data, localized} = runtimeContext;
66
+ const { helper, converter, data, localized } = runtimeContext;
61
67
  const locales = data?.locales ?? [];
62
68
 
63
- // initialize getSettings
69
+ // Initialize getSettings
64
70
  const getSettings = getSettingsHelper(runtimeContext);
65
71
  helper.getSettings = getSettings;
66
72
  // Write config toml according to locale settings in contentful
@@ -76,39 +82,44 @@ export default (args) => {
76
82
  // https://github.com/gohugoio/hugo/issues/7344
77
83
  const languageCode = code.toLowerCase();
78
84
  const [languageNameShort] = languageCode.split('-');
85
+
86
+ const localeConfig = {
87
+ languageCode,
88
+ languageName,
89
+ languageNameShort,
90
+ weight: locale.default ? 1 : 2,
91
+ };
92
+
79
93
  return [
80
94
  code,
81
- {
82
- contentDir: `content/${languageCode}`,
83
- languageCode,
84
- languageName,
85
- languageNameShort,
86
- weight: locale.default ? 1 : 2,
87
- },
95
+ options.translationStrategy === 'directory'
96
+ ? { contentDir: `content/${languageCode}`, ...localeConfig }
97
+ : localeConfig,
88
98
  ];
89
99
  })
90
100
  );
91
- await writeFile(
92
- dst,
93
- converter.toml.stringify(languageConfig)
94
- );
101
+ await writeFile(dst, converter.toml.stringify(languageConfig));
95
102
  }
96
103
 
97
104
  // Find section pages and add them to the runtimeconfig
98
- const enhancedLocalized = new Map(Array.from(localized.entries()).map(([localeCode, contentfulData]) => {
99
- const {entries} = contentfulData;
100
- const sectionIds = entries.reduce((nodes, entry) => {
101
- const id = entry?.fields?.[options.fieldIdParent]?.sys?.id;
102
- if (id) {
103
- nodes.add(id);
104
- }
105
-
106
- return nodes;
107
- }, new Set());
108
- return [localeCode, {...contentfulData, sectionIds}];
109
- }));
110
-
111
- return {...runtimeContext, helper, localized: enhancedLocalized};
105
+ const enhancedLocalized = new Map(
106
+ Array.from(localized.entries()).map(([localeCode, contentfulData]) => {
107
+ const { entries } = contentfulData;
108
+ const sectionIds = entries.reduce((nodes, entry) => {
109
+ const id = entry?.fields?.[options.fieldIdParent]?.sys?.id;
110
+ if (id) {
111
+ nodes.add(id);
112
+ }
113
+
114
+ return nodes;
115
+ }, new Set());
116
+ return [localeCode, { ...contentfulData, sectionIds }];
117
+ })
118
+ );
119
+
120
+ const i18n = Object.fromEntries(locales.map((locale) => [locale.code, {}]));
121
+
122
+ return { ...runtimeContext, helper, localized: enhancedLocalized, i18n };
112
123
  },
113
124
 
114
125
  /**
@@ -117,15 +128,11 @@ export default (args) => {
117
128
  * @param runtimeContext
118
129
  * @returns
119
130
  */
120
- async mapEntryLink(
121
- transformContext,
122
- runtimeContext,
123
- prev
124
- ) {
131
+ async mapEntryLink(transformContext, runtimeContext, prev) {
125
132
  const directory = await runtimeContext.hooks.mapDirectory(transformContext);
126
133
  const filename = await runtimeContext.hooks.mapFilename(transformContext);
127
134
 
128
- return {...prev, path: path.join(directory,filename)};
135
+ return { ...prev, path: path.join(directory, filename) };
129
136
  },
130
137
 
131
138
  /**
@@ -134,73 +141,91 @@ export default (args) => {
134
141
  * @returns
135
142
  */
136
143
  async mapDirectory(transformContext) {
137
- const {contentTypeId, locale} = transformContext;
144
+ const { contentTypeId, locale } = transformContext;
138
145
  const type = getEntryType(transformContext);
139
146
 
140
- if (type === TYPE_DATA) {
141
- return '../data';
142
- }
143
-
144
- if (contentTypeId === TYPE_PAGE) {
145
- return locale.code;
147
+ if (type === TYPE_CONTENT) {
148
+ return options.translationStrategy === STRATEGY_FILENAME ? '' : locale.code;
146
149
  }
147
150
 
148
- return path.join('headless', contentTypeId);
151
+ return options.translationStrategy === STRATEGY_FILENAME
152
+ ? path.join('../data', contentTypeId)
153
+ : path.join('../data', locale.code, contentTypeId);
149
154
  },
150
155
 
151
156
  /**
152
157
  * Map filenames data files to data, headless bundles to headless folder and pages in a
153
158
  * directory structure which matches the sitemap
154
159
  * @param transformContext
155
- * @param runtimeContext
160
+ * @param {RuntimeContext} runtimeContext
156
161
  * @returns
157
162
  */
158
163
  async mapFilename(transformContext, runtimeContext) {
159
- const {id, locale, entry, contentTypeId, utils} = transformContext;
160
- const {helper, localized} = runtimeContext;
164
+ const { id, locale, entry, contentTypeId, utils } = transformContext;
165
+ const { helper, localized, defaultLocale } = runtimeContext;
161
166
  const sectionIds = localized?.get(locale.code)?.sectionIds ?? new Set();
162
167
 
168
+ const localeData =
169
+ options.translationStrategy === STRATEGY_FILENAME
170
+ ? localized.get(defaultLocale)
171
+ : localized.get(locale.code);
172
+ const collectEntryMap = localeData.entryMap;
173
+ const collectEntry = collectEntryMap.get(entry.sys.id);
174
+
163
175
  const type = getEntryType(transformContext);
164
176
 
165
177
  const home = helper.getSettings(options.fieldIdHome, locale.code);
166
178
  const homeId = home?.sys?.id;
167
179
 
168
- if (homeId && entry?.sys?.id === homeId) {
169
- return `/_index.md`;
170
- }
171
-
172
- if (contentTypeId === options.typeIdSettings) {
173
- return path.join(locale.code, `settings.json`);
180
+ if (homeId && entry?.sys?.id === homeId) {
181
+ return options.translationStrategy === STRATEGY_FILENAME
182
+ ? `/_index.${locale.code}.md`
183
+ : `/_index.md`;
174
184
  }
175
185
 
176
- if (type === TYPE_DATA) {
177
- return path.join(locale.code, contentTypeId, `${id}.json`);
186
+ if (contentTypeId === options.typeIdSettings) {
187
+ return options.translationStrategy === STRATEGY_FILENAME
188
+ ? `../settings.${locale.code}.json`
189
+ : '../settings.json';
178
190
  }
179
191
 
180
- if (type === TYPE_PAGE && sectionIds.has(id)) {
192
+ if (type === TYPE_CONTENT && sectionIds.has(id)) {
181
193
  const slugs = utils.collectValues(`fields.${options.fieldIdSlug}`, {
182
194
  linkField: `fields.${options.fieldIdParent}`,
183
- entry,
195
+ entry: collectEntry,
196
+ entryMap: collectEntryMap,
184
197
  });
185
- return path.join(...(slugs || []), `_index.md`)
198
+ return options.translationStrategy === STRATEGY_FILENAME
199
+ ? path.join(...(slugs || []).filter((v) => v), `_index.${locale.code}.md`)
200
+ : path.join(...(slugs || []).filter((v) => v), `_index.md`);
186
201
  }
187
202
 
188
- if (type === TYPE_PAGE) {
203
+ if (type === TYPE_CONTENT) {
189
204
  const slugs = utils.collectParentValues(`fields.${options.fieldIdSlug}`, {
190
205
  linkField: `fields.${options.fieldIdParent}`,
191
- entry
206
+ entry: collectEntry,
207
+ entryMap: collectEntryMap,
192
208
  });
193
209
 
194
- return path.join(...(slugs || []), `${entry?.fields?.[options.fieldIdSlug] ?? 'unknown'}.md`)
210
+ return options.translationStrategy === STRATEGY_FILENAME
211
+ ? path.join(
212
+ ...(slugs || []).filter((v) => v),
213
+ `${collectEntry?.fields?.[options.fieldIdSlug] ?? 'unknown'}.${locale.code}.md`
214
+ )
215
+ : path.join(
216
+ ...(slugs || []).filter((v) => v),
217
+ `${collectEntry?.fields?.[options.fieldIdSlug] ?? 'unknown'}.md`
218
+ );
195
219
  }
196
220
 
197
- return path.join(id, `index.${locale.code}.md`);
221
+ return options.translationStrategy === STRATEGY_FILENAME
222
+ ? `${id}.${locale.code}.json`
223
+ : `${id}.json`;
198
224
  },
199
225
 
200
226
  async transform(transformContext, runtimeContext) {
201
- const {content, id, contentTypeId, locale} = transformContext;
202
- const contentDir = runtimeContext.config.directory;
203
- const toml = runtimeContext.converter.toml;
227
+ const { content, id, contentTypeId, locale } = transformContext;
228
+
204
229
  const type = getEntryType(transformContext);
205
230
 
206
231
  // Automatically store dictionary entries in i18n/[locale].json
@@ -209,35 +234,39 @@ export default (args) => {
209
234
  const { key, other, one } = content;
210
235
  const translations = one ? { one, other } : { other };
211
236
 
212
- const dictionaryPath = path.join(contentDir, `../i18n/${locale.code}.toml`);
213
- const oldContent = existsSync(dictionaryPath) ? toml.parse(await readFile(dictionaryPath, 'utf8')) : {};
214
- await writeFile(
215
- dictionaryPath,
216
- toml.stringify({ ...oldContent, [key]: translations }, undefined, ' ')
217
- );
237
+ runtimeContext.i18n[locale.code][key] = translations;
218
238
 
219
- // dont't write i-18n objects to the content folder
239
+ // Dont't write i-18n objects to the content folder
220
240
  return undefined;
221
241
  }
222
242
 
223
- if (type === TYPE_PAGE) {
224
- return {...snakeCaseKeys({
225
- ...content,
226
-
227
- }), translationKey: id};
228
- }
229
-
230
- if (type === TYPE_HEADLESS) {
231
- return snakeCaseKeys({
232
- ...content,
233
- headless: true,
234
- });
243
+ if (type === TYPE_CONTENT) {
244
+ return {
245
+ ...snakeCaseKeys({
246
+ ...content,
247
+ }),
248
+ translationKey: id,
249
+ };
235
250
  }
236
251
 
237
252
  return snakeCaseKeys(content);
238
- }
253
+ },
254
+
255
+ async after(runtimeContext) {
256
+ const contentDir = runtimeContext.config.directory;
257
+ const { toml } = runtimeContext.converter;
258
+ const i18n = runtimeContext?.i18n ?? {};
259
+
260
+ await Promise.all(
261
+ Object.entries(i18n).map(async ([localeCode, translations]) => {
262
+ const dictionaryPath = path.join(contentDir, `../i18n/${localeCode}.toml`);
263
+ const oldContent = existsSync(dictionaryPath)
264
+ ? toml.parse(await readFile(dictionaryPath, 'utf8'))
265
+ : {};
266
+
267
+ return writeFile(dictionaryPath, toml.stringify({ ...oldContent, ...translations }));
268
+ })
269
+ );
270
+ },
239
271
  };
240
272
  };
241
-
242
-
243
-
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@jungvonmatt/cssg-plugin-hugo",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
+ "typings": "./index.d.ts",
6
7
  "exports": {
7
8
  ".": "./index.js"
8
9
  },
9
10
  "type": "module",
10
11
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1"
12
+ "lint": "eslint --color index.js --fix"
12
13
  },
13
14
  "repository": {
14
15
  "type": "git",
@@ -25,9 +26,9 @@
25
26
  },
26
27
  "homepage": "https://github.com/jungvonmatt/contentful-ssg#readme",
27
28
  "dependencies": {
28
- "@jungvonmatt/contentful-ssg": "^1.0.0",
29
+ "@jungvonmatt/contentful-ssg": "^1.0.4",
29
30
  "merge-options": "^3.0.4",
30
31
  "micromatch": "^4.0.4"
31
32
  },
32
- "gitHead": "a5b7e99aef936cac4d1166d7a347ffe1a480799a"
33
+ "gitHead": "9da40bcd4fa8124cf7ae3ff6a2dcfca91c82c4fb"
33
34
  }