@jungvonmatt/contentful-ssg 0.17.2 → 1.0.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.
Files changed (134) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +158 -118
  3. package/dist/__test__/mock.d.ts +28 -0
  4. package/dist/__test__/mock.js +83 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +85 -0
  7. package/dist/converter/index.d.ts +7 -0
  8. package/dist/converter/index.js +42 -0
  9. package/dist/converter/json.d.ts +3 -0
  10. package/dist/converter/json.js +2 -0
  11. package/dist/converter/markdown.d.ts +3 -0
  12. package/dist/converter/markdown.js +17 -0
  13. package/dist/converter/toml.d.ts +3 -0
  14. package/dist/converter/toml.js +3 -0
  15. package/dist/converter/yaml.d.ts +3 -0
  16. package/dist/converter/yaml.js +27 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +125 -0
  19. package/dist/lib/array.d.ts +4 -0
  20. package/dist/lib/array.js +25 -0
  21. package/dist/lib/config.d.ts +3 -0
  22. package/dist/lib/config.js +144 -0
  23. package/dist/lib/contentful.d.ts +40 -0
  24. package/dist/lib/contentful.js +145 -0
  25. package/dist/lib/create-require.d.ts +2 -0
  26. package/dist/lib/create-require.js +4 -0
  27. package/dist/lib/error.d.ts +9 -0
  28. package/dist/lib/error.js +17 -0
  29. package/dist/lib/file-manager.d.ts +17 -0
  30. package/dist/lib/file-manager.js +64 -0
  31. package/dist/lib/hook-manager.d.ts +15 -0
  32. package/dist/lib/hook-manager.js +94 -0
  33. package/dist/lib/object.d.ts +11 -0
  34. package/dist/lib/object.js +40 -0
  35. package/dist/lib/stats.d.ts +18 -0
  36. package/dist/lib/stats.js +74 -0
  37. package/dist/lib/ui.d.ts +6 -0
  38. package/dist/lib/ui.js +88 -0
  39. package/dist/lib/utils.d.ts +4 -0
  40. package/dist/lib/utils.js +31 -0
  41. package/dist/mapper/map-date-field.d.ts +1 -0
  42. package/dist/mapper/map-date-field.js +6 -0
  43. package/dist/mapper/map-entry.d.ts +4 -0
  44. package/dist/mapper/map-entry.js +42 -0
  45. package/dist/mapper/map-field.d.ts +2 -0
  46. package/dist/mapper/map-field.js +24 -0
  47. package/dist/mapper/map-meta-fields.d.ts +9 -0
  48. package/dist/mapper/map-meta-fields.js +8 -0
  49. package/dist/mapper/map-reference-field.d.ts +11 -0
  50. package/dist/mapper/map-reference-field.js +71 -0
  51. package/dist/mapper/map-rich-text-field.d.ts +7 -0
  52. package/dist/mapper/map-rich-text-field.js +47 -0
  53. package/dist/tasks/fetch.d.ts +2 -0
  54. package/dist/tasks/fetch.js +12 -0
  55. package/dist/tasks/localize.d.ts +6 -0
  56. package/dist/tasks/localize.js +48 -0
  57. package/dist/tasks/setup.d.ts +2 -0
  58. package/dist/tasks/setup.js +26 -0
  59. package/dist/tasks/transform.d.ts +2 -0
  60. package/dist/tasks/transform.js +5 -0
  61. package/dist/tasks/write.d.ts +2 -0
  62. package/dist/tasks/write.js +33 -0
  63. package/dist/types.d.ts +194 -0
  64. package/dist/types.js +1 -0
  65. package/package.json +118 -91
  66. package/src/__test__/fixtures/assets.json +47 -0
  67. package/src/__test__/fixtures/content_types.json +181 -0
  68. package/src/__test__/fixtures/entries.json +783 -0
  69. package/src/__test__/fixtures/locales.json +35 -0
  70. package/src/__test__/fixtures/richtext.json +135 -0
  71. package/src/__test__/mock.ts +106 -0
  72. package/src/cli.ts +134 -0
  73. package/src/converter/index.test.ts +38 -0
  74. package/src/converter/index.ts +60 -0
  75. package/src/converter/json.test.ts +25 -0
  76. package/src/converter/json.ts +16 -0
  77. package/src/converter/markdown.test.ts +58 -0
  78. package/{lib/converter/markdown.js → src/converter/markdown.ts} +8 -10
  79. package/src/converter/toml.test.ts +74 -0
  80. package/src/converter/toml.ts +17 -0
  81. package/src/converter/yaml.test.ts +51 -0
  82. package/src/converter/yaml.ts +51 -0
  83. package/src/index.test.ts +89 -0
  84. package/src/index.ts +155 -0
  85. package/src/lib/array.test.ts +105 -0
  86. package/src/lib/array.ts +84 -0
  87. package/src/lib/config.ts +195 -0
  88. package/src/lib/contentful.test.ts +251 -0
  89. package/{lib/contentful.js → src/lib/contentful.ts} +99 -122
  90. package/src/lib/create-require.ts +6 -0
  91. package/src/lib/error.ts +19 -0
  92. package/src/lib/file-manager.test.ts +73 -0
  93. package/src/lib/file-manager.ts +106 -0
  94. package/src/lib/hook-manager.test.ts +169 -0
  95. package/src/lib/hook-manager.ts +161 -0
  96. package/src/lib/object.test.ts +56 -0
  97. package/src/lib/object.ts +102 -0
  98. package/src/lib/stats.test.ts +78 -0
  99. package/src/lib/stats.ts +112 -0
  100. package/src/lib/ui.test.ts +58 -0
  101. package/src/lib/ui.ts +128 -0
  102. package/src/lib/utils.test.ts +85 -0
  103. package/src/lib/utils.ts +57 -0
  104. package/src/mapper/map-date-field.ts +12 -0
  105. package/src/mapper/map-entry.test.ts +191 -0
  106. package/src/mapper/map-entry.ts +77 -0
  107. package/src/mapper/map-field.test.ts +577 -0
  108. package/src/mapper/map-field.ts +57 -0
  109. package/src/mapper/map-meta-fields.ts +17 -0
  110. package/src/mapper/map-reference-field.ts +122 -0
  111. package/src/mapper/map-rich-text-field.ts +107 -0
  112. package/src/tasks/fetch.test.ts +32 -0
  113. package/src/tasks/fetch.ts +17 -0
  114. package/src/tasks/localize.test.ts +83 -0
  115. package/src/tasks/localize.ts +95 -0
  116. package/src/tasks/setup.test.ts +33 -0
  117. package/src/tasks/setup.ts +31 -0
  118. package/src/tasks/transform.test.ts +26 -0
  119. package/src/tasks/transform.ts +11 -0
  120. package/src/tasks/write.test.ts +94 -0
  121. package/src/tasks/write.ts +49 -0
  122. package/src/types.ts +271 -0
  123. package/index.js +0 -90
  124. package/lib/array.js +0 -48
  125. package/lib/config.js +0 -142
  126. package/lib/converter/index.js +0 -63
  127. package/lib/converter/json.js +0 -20
  128. package/lib/converter/toml.js +0 -22
  129. package/lib/converter/yaml.js +0 -40
  130. package/lib/dump.js +0 -337
  131. package/lib/presets/grow.js +0 -88
  132. package/lib/transform/localize.js +0 -59
  133. package/lib/transform/mapper.js +0 -293
  134. package/lib/utils.js +0 -150
@@ -0,0 +1,3 @@
1
+ import { KeyValueMap } from '../types';
2
+ export declare const stringify: <T = KeyValueMap<any>>(obj: T) => string;
3
+ export declare const parse: <T = KeyValueMap<any>>(obj: string) => T;
@@ -0,0 +1,27 @@
1
+ import yaml from 'js-yaml';
2
+ const getPredicate = (type) => (data) => typeof data === 'string' && data.startsWith(`${type} `);
3
+ const getRepresent = (type) => (data) => typeof data === 'string' ? data.replace(`${type} `, '') : JSON.stringify(data);
4
+ const getConstruct = (type) => (data) => typeof data === 'string' ? `${type} ${data}` : `${type} ${typeof data}`;
5
+ const growYamlConstructors = [
6
+ '!g.csv',
7
+ '!g.doc',
8
+ '!g.json',
9
+ '!g.static',
10
+ '!g.string',
11
+ '!g.url',
12
+ '!g.yaml',
13
+ ];
14
+ const growYamlTypes = growYamlConstructors.map((type) => new yaml.Type(type, {
15
+ kind: 'scalar',
16
+ predicate: getPredicate(type),
17
+ represent: getRepresent(type),
18
+ construct: getConstruct(type),
19
+ }));
20
+ export const stringify = (obj) => {
21
+ const GROW_SCHEMA = yaml.DEFAULT_SCHEMA.extend(growYamlTypes);
22
+ return yaml.dump(obj, { schema: GROW_SCHEMA });
23
+ };
24
+ export const parse = (obj) => {
25
+ const GROW_SCHEMA = yaml.DEFAULT_SCHEMA.extend(growYamlTypes);
26
+ return yaml.load(obj, { schema: GROW_SCHEMA });
27
+ };
@@ -0,0 +1,2 @@
1
+ import type { Config } from './types.js';
2
+ export declare const run: (config: Config) => Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ import Listr from 'listr';
2
+ import chalk from 'chalk';
3
+ import { getContentTypeId, getContentId } from './lib/contentful.js';
4
+ import { setup } from './tasks/setup.js';
5
+ import { fetch } from './tasks/fetch.js';
6
+ import { localize } from './tasks/localize.js';
7
+ import { transform } from './tasks/transform.js';
8
+ import { write } from './tasks/write.js';
9
+ import { collectParentValues, collectValues } from './lib/utils.js';
10
+ import { ValidationError } from './lib/error.js';
11
+ class CustomListrRenderer {
12
+ _tasks;
13
+ constructor(tasks) {
14
+ this._tasks = tasks;
15
+ }
16
+ static get nonTTY() {
17
+ return false;
18
+ }
19
+ static subscribeTasks(tasks, indent = '') {
20
+ for (const task of tasks) {
21
+ task.subscribe((event) => {
22
+ if (event.type === 'STATE' && task.isPending()) {
23
+ console.log(`${indent} ${chalk.yellow('\u279E')} ${task.title}`);
24
+ }
25
+ if (event.type === 'SUBTASKS' && task.subtasks.length > 0) {
26
+ CustomListrRenderer.subscribeTasks(task.subtasks, indent + ' ');
27
+ }
28
+ });
29
+ }
30
+ }
31
+ render() {
32
+ CustomListrRenderer.subscribeTasks(this._tasks);
33
+ }
34
+ end(err) {
35
+ if (!err) {
36
+ console.log(` ${chalk.green('✔')} all tasks done!`);
37
+ }
38
+ }
39
+ }
40
+ export const run = async (config) => {
41
+ const tasks = new Listr([
42
+ {
43
+ title: 'Setup',
44
+ task: async (ctx) => setup(ctx, config),
45
+ },
46
+ {
47
+ title: 'Pulling data from contentful',
48
+ task: async (ctx) => fetch(ctx, config),
49
+ },
50
+ {
51
+ title: 'Localize data',
52
+ task: async (ctx) => localize(ctx),
53
+ },
54
+ {
55
+ title: 'Before Hook',
56
+ skip: (ctx) => !ctx.hooks.has('before'),
57
+ task: async (ctx) => {
58
+ const result = await ctx.hooks.before();
59
+ ctx = { ...ctx, ...(result || {}) };
60
+ },
61
+ },
62
+ {
63
+ title: 'Writing files',
64
+ task: async (ctx) => {
65
+ const { locales = [] } = ctx.data;
66
+ const tasks = locales.map((locale) => ({
67
+ title: `${locale.code}`,
68
+ task: async () => {
69
+ const data = ctx.localized.get(locale.code);
70
+ const { entries = [] } = data || {};
71
+ const promises = entries.map(async (entry) => {
72
+ const id = getContentId(entry);
73
+ const contentTypeId = getContentTypeId(entry);
74
+ const utils = {
75
+ collectValues: collectValues({ ...data, entry }),
76
+ collectParentValues: collectParentValues({ ...data, entry }),
77
+ };
78
+ const transformContext = {
79
+ ...data,
80
+ id,
81
+ contentTypeId,
82
+ entry,
83
+ locale,
84
+ utils,
85
+ };
86
+ try {
87
+ const content = await transform(transformContext, ctx, config);
88
+ if (typeof content === 'undefined') {
89
+ return;
90
+ }
91
+ await write({ ...transformContext, content }, ctx, config);
92
+ ctx.stats.addSuccess(transformContext);
93
+ }
94
+ catch (error) {
95
+ if (error instanceof ValidationError) {
96
+ ctx.stats.addSkipped(transformContext, error);
97
+ }
98
+ else if (typeof error === 'string' || error instanceof Error) {
99
+ ctx.stats.addError(transformContext, error);
100
+ }
101
+ }
102
+ });
103
+ return Promise.all(promises);
104
+ },
105
+ }));
106
+ return new Listr(tasks, { concurrent: true });
107
+ },
108
+ },
109
+ {
110
+ title: 'After Hook',
111
+ skip: (ctx) => !ctx.hooks.has('after'),
112
+ task: async (ctx) => {
113
+ const result = await ctx.hooks.after();
114
+ ctx = { ...ctx, ...(result || {}) };
115
+ },
116
+ },
117
+ {
118
+ title: 'Cleanup',
119
+ task: async (ctx) => ctx.fileManager.cleanup(),
120
+ },
121
+ ], { renderer: CustomListrRenderer });
122
+ const ctx = await tasks.run();
123
+ await ctx.stats.print();
124
+ console.log('\n---------------------------------------------');
125
+ };
@@ -0,0 +1,4 @@
1
+ export declare function mapAsync<T, U>(iterable: T[], callback: (value: T, index?: number, iterable?: T[]) => U | Promise<U>): Promise<U[]>;
2
+ export declare function forEachAsync<T>(iterable: T[], callback: (value: T, index?: number, iterable?: T[]) => void | Promise<void>): Promise<void>;
3
+ export declare function filterAsync<T>(iterable: T[], callback: (value: T, index?: number, array?: T[]) => boolean | Promise<boolean>): Promise<T[]>;
4
+ export declare function reduceAsync<T, U>(iterable: T[], callback: (previousValue: U, currentValue: T, currentIndex?: number, array?: T[]) => U | Promise<U>, initialValue?: U): Promise<U>;
@@ -0,0 +1,25 @@
1
+ function series(reducer, initialValue) {
2
+ return async (iterable) => iterable.reduce(async (chain, value, key) => chain.then(async (results) => reducer(results, value, key, iterable)), Promise.resolve(initialValue));
3
+ }
4
+ export async function mapAsync(iterable, callback) {
5
+ const result = [];
6
+ const promises = iterable.map(async (item, index, array) => callback(item, index, array));
7
+ for await (const mapped of promises) {
8
+ result.push(mapped);
9
+ }
10
+ return result;
11
+ }
12
+ export async function forEachAsync(iterable, callback) {
13
+ for (const index of iterable.keys()) {
14
+ await callback(iterable[index], index, iterable);
15
+ }
16
+ }
17
+ export async function filterAsync(iterable, callback) {
18
+ return Promise.resolve(iterable).then(async (array) => array.reduce(async (chain, value, key) => chain.then(async (results) => {
19
+ const active = await callback(value, key, iterable);
20
+ return active ? [...results, value] : results;
21
+ }), Promise.resolve([])));
22
+ }
23
+ export async function reduceAsync(iterable, callback, initialValue) {
24
+ return Promise.resolve(iterable).then(series(callback, initialValue));
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { Config, ContentfulConfig } from '../types.js';
2
+ export declare const getEnvironmentConfig: () => ContentfulConfig;
3
+ export declare const getConfig: (args?: Partial<Config>) => Promise<Config>;
@@ -0,0 +1,144 @@
1
+ import { register } from '@swc-node/register/register';
2
+ import chalk from 'chalk';
3
+ import { cosmiconfig } from 'cosmiconfig';
4
+ import mergeOptionsModule from 'merge-options';
5
+ import { dirname, isAbsolute, resolve } from 'path';
6
+ import slash from 'slash';
7
+ import { createRequire } from './create-require.js';
8
+ import { isObject, removeEmpty } from './object.js';
9
+ const typescriptLoader = async (filePath) => {
10
+ register({ format: 'esm', extensions: ['.ts', '.tsx', '.mts'] });
11
+ const require = createRequire();
12
+ const configModule = require(filePath);
13
+ return configModule.default || configModule;
14
+ };
15
+ const mergeOptions = mergeOptionsModule.bind({ ignoreUndefined: true });
16
+ const resolvePlugin = async (plugin, config) => {
17
+ let pluginName;
18
+ let pluginOptions;
19
+ if (typeof plugin === 'string') {
20
+ pluginName = plugin;
21
+ pluginOptions = {};
22
+ }
23
+ else if (Array.isArray(plugin)) {
24
+ pluginName = plugin[0];
25
+ pluginOptions = plugin[1] || {};
26
+ }
27
+ else {
28
+ pluginName = plugin.resolve;
29
+ pluginOptions = plugin.options || {};
30
+ }
31
+ const { rootDir, verbose } = config;
32
+ try {
33
+ const requireSource = rootDir === null ? createRequire() : createRequire(`${rootDir}/:internal:`);
34
+ const resolvedPath = slash(requireSource.resolve(pluginName));
35
+ const pluginModule = (await import(resolvedPath));
36
+ let pluginDefaultHooks = {};
37
+ if (typeof pluginModule.default === 'function') {
38
+ pluginDefaultHooks = await pluginModule.default(pluginOptions || {});
39
+ }
40
+ else if (isObject(pluginModule.default)) {
41
+ pluginDefaultHooks = pluginModule.default;
42
+ }
43
+ const pluginHooks = { ...(pluginDefaultHooks || {}), ...(pluginModule || {}) };
44
+ return pluginHooks;
45
+ }
46
+ catch (error) {
47
+ if (verbose) {
48
+ console.error(chalk.red(`Plugin "${pluginName} threw the following error:`));
49
+ console.error(error);
50
+ }
51
+ else {
52
+ console.error(chalk.red(`There was a problem loading plugin "${pluginName}". Perhaps you need to install its package?\nUse --verbose to see actual error.`));
53
+ }
54
+ process.exit(1);
55
+ }
56
+ };
57
+ const loadConfig = async (moduleName) => {
58
+ const explorer = cosmiconfig(moduleName, {
59
+ searchPlaces: [
60
+ 'package.json',
61
+ `.${moduleName}rc`,
62
+ `.${moduleName}rc.json`,
63
+ `.${moduleName}rc.yaml`,
64
+ `.${moduleName}rc.yml`,
65
+ `.${moduleName}rc.js`,
66
+ `${moduleName}.config.ts`,
67
+ `${moduleName}.config.js`,
68
+ ],
69
+ loaders: {
70
+ '.ts': typescriptLoader,
71
+ },
72
+ });
73
+ return explorer.search();
74
+ };
75
+ export const getEnvironmentConfig = () => removeEmpty({
76
+ spaceId: process.env.CONTENTFUL_SPACE_ID,
77
+ environmentId: process.env.CONTENTFUL_ENVIRONMENT_ID,
78
+ managementToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN,
79
+ previewAccessToken: process.env.CONTENTFUL_PREVIEW_TOKEN,
80
+ accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
81
+ });
82
+ export const getConfig = async (args) => {
83
+ const defaultOptions = {
84
+ environmentId: 'master',
85
+ host: 'api.contentful.com',
86
+ directory: resolve(process.cwd(), 'content'),
87
+ plugins: [],
88
+ resolvedPlugins: [],
89
+ };
90
+ const environmentOptions = getEnvironmentConfig();
91
+ let contentfulCliOptions = {};
92
+ try {
93
+ const contentfulConfig = await loadConfig('contentful');
94
+ if (!contentfulConfig.isEmpty) {
95
+ const { managementToken, activeSpaceId, activeEnvironmentId, host } = contentfulConfig.config;
96
+ contentfulCliOptions = removeEmpty({
97
+ spaceId: activeSpaceId,
98
+ managementToken,
99
+ environmentId: activeEnvironmentId,
100
+ host,
101
+ });
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (typeof error === 'string') {
106
+ console.log('Error (Contentful):', error);
107
+ }
108
+ else if (error instanceof Error) {
109
+ console.log('Error (Contentful):', error.message);
110
+ }
111
+ else {
112
+ console.log(error);
113
+ }
114
+ }
115
+ let configFileOptions = {};
116
+ args.rootDir = process.cwd();
117
+ try {
118
+ const configFile = await loadConfig('contentful-ssg');
119
+ if (!configFile.isEmpty) {
120
+ configFileOptions = configFile.config;
121
+ args.rootDir = dirname(configFile.filepath);
122
+ if (configFileOptions.directory && !isAbsolute(configFileOptions.directory)) {
123
+ configFileOptions.directory = resolve(args.rootDir, configFileOptions.directory);
124
+ }
125
+ }
126
+ }
127
+ catch (error) {
128
+ if (typeof error === 'string') {
129
+ console.log('Error:', error);
130
+ }
131
+ else if (error instanceof Error) {
132
+ console.log('Error:', error.message);
133
+ }
134
+ else {
135
+ console.log(error);
136
+ }
137
+ }
138
+ const result = mergeOptions(defaultOptions, contentfulCliOptions, environmentOptions, configFileOptions, args || {});
139
+ const resolvedPlugins = [
140
+ ...result.resolvedPlugins,
141
+ ...(await Promise.all((result.plugins || []).map(async (plugin) => resolvePlugin(plugin, result)))),
142
+ ];
143
+ return { ...result, resolvedPlugins };
144
+ };
@@ -0,0 +1,40 @@
1
+ import type { Space } from 'contentful-management/types';
2
+ import type { ContentfulConfig, FieldSettings, Node, Entry, ContentType } from '../types.js';
3
+ import contentful from 'contentful';
4
+ export declare const FIELD_TYPE_SYMBOL = "Symbol";
5
+ export declare const FIELD_TYPE_TEXT = "Text";
6
+ export declare const FIELD_TYPE_RICHTEXT = "RichText";
7
+ export declare const FIELD_TYPE_NUMBER = "Number";
8
+ export declare const FIELD_TYPE_INTEGER = "Integer";
9
+ export declare const FIELD_TYPE_DATE = "Date";
10
+ export declare const FIELD_TYPE_LOCATION = "Location";
11
+ export declare const FIELD_TYPE_ARRAY = "Array";
12
+ export declare const FIELD_TYPE_BOOLEAN = "Boolean";
13
+ export declare const FIELD_TYPE_LINK = "Link";
14
+ export declare const FIELD_TYPE_OBJECT = "Object";
15
+ export declare const LINK_TYPE_ASSET = "Asset";
16
+ export declare const LINK_TYPE_ENTRY = "Entry";
17
+ export declare const MAX_ALLOWED_LIMIT = 1000;
18
+ export declare const getContentTypeId: <T extends contentful.Asset | Entry | contentful.Entry<unknown>>(node: T) => string;
19
+ export declare const getEnvironmentId: <T extends Node>(node: T) => string;
20
+ export declare const getContentId: <T extends contentful.ContentType | contentful.Asset | Entry | contentful.Entry<unknown>>(node: T) => string;
21
+ export declare const getSpaces: (options: ContentfulConfig) => Promise<Space[]>;
22
+ export declare const getSpace: (options: ContentfulConfig) => Promise<Space>;
23
+ export declare const getEnvironments: (options: ContentfulConfig) => Promise<import("contentful-management/types").Environment[]>;
24
+ export declare const getEnvironment: (options: ContentfulConfig) => Promise<import("contentful-management/types").Environment>;
25
+ export declare const getApiKey: (options: ContentfulConfig) => Promise<string>;
26
+ export declare const getPreviewApiKey: (options: ContentfulConfig) => Promise<string>;
27
+ export declare const getContent: (options: ContentfulConfig) => Promise<{
28
+ entries: Entry[];
29
+ assets: contentful.Asset[];
30
+ contentTypes: contentful.ContentType[];
31
+ locales: contentful.Locale[];
32
+ }>;
33
+ export declare const isContentfulObject: (obj: any) => boolean;
34
+ export declare const isLink: (obj: any) => boolean;
35
+ export declare const isAssetLink: (obj: any) => boolean;
36
+ export declare const isEntryLink: (obj: any) => boolean;
37
+ export declare const isAsset: (obj: any) => boolean;
38
+ export declare const isEntry: (obj: any) => boolean;
39
+ export declare const getFieldSettings: (contentTypes: ContentType[]) => FieldSettings;
40
+ export declare const convertToMap: <T extends Node>(nodes?: T[]) => Map<string, T>;
@@ -0,0 +1,145 @@
1
+ import contentful from 'contentful';
2
+ import contentfulManagement from 'contentful-management';
3
+ let client;
4
+ let managementClient;
5
+ export const FIELD_TYPE_SYMBOL = 'Symbol';
6
+ export const FIELD_TYPE_TEXT = 'Text';
7
+ export const FIELD_TYPE_RICHTEXT = 'RichText';
8
+ export const FIELD_TYPE_NUMBER = 'Number';
9
+ export const FIELD_TYPE_INTEGER = 'Integer';
10
+ export const FIELD_TYPE_DATE = 'Date';
11
+ export const FIELD_TYPE_LOCATION = 'Location';
12
+ export const FIELD_TYPE_ARRAY = 'Array';
13
+ export const FIELD_TYPE_BOOLEAN = 'Boolean';
14
+ export const FIELD_TYPE_LINK = 'Link';
15
+ export const FIELD_TYPE_OBJECT = 'Object';
16
+ export const LINK_TYPE_ASSET = 'Asset';
17
+ export const LINK_TYPE_ENTRY = 'Entry';
18
+ export const MAX_ALLOWED_LIMIT = 1000;
19
+ export const getContentTypeId = (node) => node?.sys?.contentType?.sys?.id ?? 'unknown';
20
+ export const getEnvironmentId = (node) => node?.sys?.environment?.sys?.id ?? 'unknown';
21
+ export const getContentId = (node) => node?.sys?.id ?? 'unknown';
22
+ const getClient = (options) => {
23
+ const { accessToken, previewAccessToken, spaceId, environmentId, preview } = options || {};
24
+ if (client) {
25
+ return client;
26
+ }
27
+ if (accessToken) {
28
+ const params = {
29
+ space: spaceId,
30
+ host: preview ? 'preview.contentful.com' : 'cdn.contentful.com',
31
+ accessToken: preview ? previewAccessToken : accessToken,
32
+ environment: environmentId,
33
+ };
34
+ return contentful.createClient(params);
35
+ }
36
+ throw new Error('You need to login first. Run npx contentful login');
37
+ };
38
+ const getManagementClient = (options) => {
39
+ const { managementToken } = options || {};
40
+ if (managementClient) {
41
+ return managementClient;
42
+ }
43
+ if (managementToken) {
44
+ return contentfulManagement.createClient({
45
+ accessToken: managementToken,
46
+ });
47
+ }
48
+ throw new Error('You need to login first. Run npx contentful login');
49
+ };
50
+ export const getSpaces = async (options) => {
51
+ const client = getManagementClient(options);
52
+ const { items: spaces } = await client.getSpaces();
53
+ return spaces;
54
+ };
55
+ export const getSpace = async (options) => {
56
+ const { spaceId } = options || {};
57
+ const client = getManagementClient(options);
58
+ return client.getSpace(spaceId);
59
+ };
60
+ export const getEnvironments = async (options) => {
61
+ const space = await getSpace(options);
62
+ const { items: environments } = await space.getEnvironments();
63
+ return environments;
64
+ };
65
+ export const getEnvironment = async (options) => {
66
+ const { environmentId, spaceId } = options || {};
67
+ const space = await getSpace(options);
68
+ const { items: environments } = await space.getEnvironments();
69
+ const environmentIds = (environments || []).map((env) => env.sys.id);
70
+ if (environmentId && environmentIds.includes(environmentId)) {
71
+ return space.getEnvironment(environmentId);
72
+ }
73
+ if (environmentId && !environmentIds.includes(environmentId)) {
74
+ throw new Error(`Environment "${environmentId}" is not available in space ${spaceId}"`);
75
+ }
76
+ throw new Error('Missing required parameter: environmentId');
77
+ };
78
+ export const getApiKey = async (options) => {
79
+ const space = await getSpace(options);
80
+ const { items: apiKeys = [] } = (await space.getApiKeys()) || {};
81
+ const [apiKey] = apiKeys;
82
+ const { accessToken } = apiKey || {};
83
+ return accessToken;
84
+ };
85
+ export const getPreviewApiKey = async (options) => {
86
+ const space = await getSpace(options);
87
+ const { items: previewApiKeys = [] } = await space.getPreviewApiKeys();
88
+ const [previewApiKey] = previewApiKeys;
89
+ const { accessToken: previewAccessToken } = previewApiKey;
90
+ return previewAccessToken;
91
+ };
92
+ const pagedGet = async (apiClient, { method, skip = 0, aggregatedResponse = null, query = null }) => {
93
+ const fullQuery = {
94
+ skip,
95
+ limit: MAX_ALLOWED_LIMIT,
96
+ order: 'sys.createdAt,sys.id',
97
+ locale: '*',
98
+ include: 0,
99
+ ...(query || {}),
100
+ };
101
+ const response = (await apiClient[method](fullQuery));
102
+ if (aggregatedResponse) {
103
+ aggregatedResponse.items = aggregatedResponse.items.concat(response.items);
104
+ }
105
+ else {
106
+ aggregatedResponse = response;
107
+ }
108
+ if (skip + MAX_ALLOWED_LIMIT <= response.total) {
109
+ return pagedGet(apiClient, {
110
+ method,
111
+ skip: skip + MAX_ALLOWED_LIMIT,
112
+ aggregatedResponse,
113
+ query,
114
+ });
115
+ }
116
+ return aggregatedResponse;
117
+ };
118
+ export const getContent = async (options) => {
119
+ const apiClient = getClient(options);
120
+ const { items: locales } = await pagedGet(apiClient, {
121
+ method: 'getLocales',
122
+ });
123
+ const { items: contentTypes } = await pagedGet(apiClient, {
124
+ method: 'getContentTypes',
125
+ });
126
+ const { items: entries } = await pagedGet(apiClient, {
127
+ method: 'getEntries',
128
+ });
129
+ const { items: assets } = await pagedGet(apiClient, {
130
+ method: 'getAssets',
131
+ });
132
+ return { entries, assets, contentTypes, locales };
133
+ };
134
+ export const isContentfulObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]' && Object.keys(obj).includes('sys');
135
+ export const isLink = (obj) => isContentfulObject(obj) && obj.sys.type === FIELD_TYPE_LINK;
136
+ export const isAssetLink = (obj) => isLink(obj) && obj.sys.linkType === LINK_TYPE_ASSET;
137
+ export const isEntryLink = (obj) => isContentfulObject(obj) && obj.sys.linkType === LINK_TYPE_ENTRY;
138
+ export const isAsset = (obj) => isContentfulObject(obj) && obj.sys.type === LINK_TYPE_ASSET;
139
+ export const isEntry = (obj) => isContentfulObject(obj) && obj.sys.type === LINK_TYPE_ENTRY;
140
+ export const getFieldSettings = (contentTypes) => contentTypes.reduce((res, contentType) => {
141
+ const id = getContentId(contentType);
142
+ const fields = contentType.fields.reduce((fields, field) => ({ ...fields, [field.id]: field }), {});
143
+ return { ...res, [id]: fields };
144
+ }, {});
145
+ export const convertToMap = (nodes = []) => new Map(nodes.map((node) => [getContentId(node), node]));
@@ -0,0 +1,2 @@
1
+ /// <reference types="node" />
2
+ export declare const createRequire: (rootDir?: string) => NodeRequire;
@@ -0,0 +1,4 @@
1
+ import { createRequire as moduleCreateRequire } from 'module';
2
+ export const createRequire = (rootDir = null) => rootDir === null
3
+ ? moduleCreateRequire(import.meta.url)
4
+ : moduleCreateRequire(`${rootDir}/:internal:`);
@@ -0,0 +1,9 @@
1
+ import { ErrorEntry } from '../types';
2
+ export declare class ValidationError extends Error {
3
+ missingFields: string[];
4
+ link: string;
5
+ entryId: string;
6
+ contentTypeId: string;
7
+ locale: string;
8
+ constructor(entry: ErrorEntry);
9
+ }
@@ -0,0 +1,17 @@
1
+ export class ValidationError extends Error {
2
+ missingFields;
3
+ link;
4
+ entryId;
5
+ contentTypeId;
6
+ locale;
7
+ constructor(entry) {
8
+ const message = `ValidationError: Invalid entry ${entry.entryId} for locale ${entry.locale.name}`;
9
+ super(message);
10
+ this.link = `https://app.contentful.com/spaces/${entry.spaceId}/environments/${entry.environmentId}/entries/${entry.entryId}`;
11
+ this.entryId = entry.entryId;
12
+ this.contentTypeId = entry.contentTypeId;
13
+ this.locale = entry.locale.code;
14
+ this.missingFields = entry.missingFields;
15
+ this.name = 'ValidationError';
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ import type { WriteFileOptions } from 'fs-extra';
3
+ import type { Config, Ignore } from '../types';
4
+ export declare class FileManager {
5
+ ignoreBase: string;
6
+ ignore?: Ignore;
7
+ files: Set<string>;
8
+ config: Config;
9
+ constructor(config: Config);
10
+ get count(): number;
11
+ get ignoredFiles(): string[];
12
+ initialize(): Promise<void>;
13
+ writeFile(file: string, data: any, options?: WriteFileOptions | BufferEncoding | string): Promise<void>;
14
+ deleteFile(file: string): Promise<void>;
15
+ removeEmptyDirectories(directory: string): Promise<void>;
16
+ cleanup(): Promise<boolean>;
17
+ }
@@ -0,0 +1,64 @@
1
+ import { dirname, resolve, relative, join } from 'path';
2
+ import ignore from 'ignore';
3
+ import { readFile, readdir, lstat } from 'fs/promises';
4
+ import { remove, outputFile } from 'fs-extra';
5
+ export class FileManager {
6
+ ignoreBase = process.cwd();
7
+ ignore;
8
+ files = new Set();
9
+ config;
10
+ constructor(config) {
11
+ this.config = config;
12
+ }
13
+ get count() {
14
+ return [...this.ignoredFiles].length;
15
+ }
16
+ get ignoredFiles() {
17
+ return [...this.files].filter((file) => !this.ignore || this.ignore.ignores(relative(this.ignoreBase, file)));
18
+ }
19
+ async initialize() {
20
+ const { findUp } = await import('find-up');
21
+ const { globby } = await import('globby');
22
+ const gitignore = await findUp('.gitignore');
23
+ if (gitignore) {
24
+ this.ignoreBase = dirname(gitignore);
25
+ const ignorePatterns = await readFile(gitignore);
26
+ this.ignore = ignore().add(ignorePatterns.toString('utf8'));
27
+ }
28
+ const existing = await globby(`${this.config.directory}/**/*.*`);
29
+ this.files = new Set(existing.map((file) => resolve(file)));
30
+ }
31
+ async writeFile(file, data, options) {
32
+ await outputFile(file, data, options);
33
+ if (this.files.has(resolve(file))) {
34
+ this.files.delete(resolve(file));
35
+ }
36
+ }
37
+ async deleteFile(file) {
38
+ if (this.files.has(resolve(file))) {
39
+ this.files.delete(resolve(file));
40
+ }
41
+ return remove(file);
42
+ }
43
+ async removeEmptyDirectories(directory) {
44
+ const fileStats = await lstat(directory);
45
+ if (!fileStats.isDirectory()) {
46
+ return;
47
+ }
48
+ let fileNames = await readdir(directory);
49
+ if (fileNames.length > 0) {
50
+ const recursiveRemovalPromises = fileNames.map(async (fileName) => this.removeEmptyDirectories(join(directory, fileName)));
51
+ await Promise.all(recursiveRemovalPromises);
52
+ fileNames = await readdir(directory);
53
+ }
54
+ if (fileNames.length === 0 && directory !== this.config.directory) {
55
+ await remove(directory);
56
+ }
57
+ }
58
+ async cleanup() {
59
+ const promises = [...this.ignoredFiles].map(async (file) => this.deleteFile(file));
60
+ await Promise.allSettled(promises);
61
+ await this.removeEmptyDirectories(this.config.directory);
62
+ return true;
63
+ }
64
+ }
@@ -0,0 +1,15 @@
1
+ import type { KeyValueMap, Config, Hooks, RuntimeContext, TransformContext } from '../types.js';
2
+ export declare class HookManager {
3
+ runtimeContext: RuntimeContext;
4
+ config: Config;
5
+ constructor(runtimeContext: RuntimeContext, config: Config);
6
+ has(key: keyof Hooks): boolean;
7
+ before(defauleValue?: KeyValueMap): Promise<RuntimeContext>;
8
+ after(defauleValue?: KeyValueMap): Promise<RuntimeContext>;
9
+ transform(transformContext: TransformContext, defauleValue?: unknown): Promise<KeyValueMap<any>>;
10
+ mapMetaFields(transformContext: TransformContext, defauleValue?: unknown): Promise<KeyValueMap<any>>;
11
+ mapDirectory(transformContext: TransformContext, defauleValue?: unknown): Promise<string>;
12
+ mapFilename(transformContext: TransformContext, defauleValue?: unknown): Promise<string>;
13
+ mapAssetLink(transformContext: TransformContext, defauleValue?: unknown): Promise<KeyValueMap<any>>;
14
+ mapEntryLink(transformContext: TransformContext, defauleValue?: unknown): Promise<KeyValueMap<any>>;
15
+ }