@jungvonmatt/contentful-migrations 5.6.1 → 6.0.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/lib/config.js CHANGED
@@ -1,7 +1,5 @@
1
1
  const path = require('path');
2
- const inquirer = require('inquirer');
3
- const mergeOptions = require('merge-options').bind({ ignoreUndefined: true });
4
- const { cosmiconfig } = require('cosmiconfig');
2
+ const { Confirm } = require('enquirer');
5
3
 
6
4
  const { getSpaces, getEnvironments } = require('./contentful');
7
5
 
@@ -15,101 +13,50 @@ const STATE_FAILURE = 'failure';
15
13
  * Get configuration
16
14
  * @param {Object} args
17
15
  */
18
- const getConfig = async (args) => {
19
- const defaultOptions = {
20
- fieldId: 'migration',
21
- migrationContentTypeId: 'contentful-migrations',
22
- host: 'api.contentful.com',
23
- directory: path.resolve(process.cwd(), 'migrations'),
24
- };
25
-
26
- const environmentOptions = {
27
- spaceId: process.env.CONTENTFUL_SPACE_ID,
28
- environmentId: process.env.CONTENTFUL_ENVIRONMENT_ID,
29
- accessToken: process.env.CONTENTFUL_MANAGEMENT_TOKEN,
30
- };
31
-
32
- let contentfulCliOptions = {};
33
- try {
34
- // get configuration from contentful rc file (created by the contentful cli command)
35
- const explorer = cosmiconfig('contentful');
36
- const explorerResult = await explorer.search();
37
- if (explorerResult !== null) {
38
- const { config } = explorerResult || {};
39
- const { managementToken, activeSpaceId, activeEnvironmentId, host } = config || {};
40
- contentfulCliOptions = {
41
- spaceId: activeSpaceId,
42
- accessToken: managementToken,
43
- host,
44
- };
45
- }
46
- } catch (error) {
47
- console.log('Error:', error.message);
48
- }
49
-
50
- let configFileOptions = {};
51
- try {
52
- // get configuration from migrations rc file
53
- const explorer = cosmiconfig('migrations');
54
- const explorerResult = args.config ? await explorer.load(args.config) : await explorer.search();
55
- if (explorerResult !== null) {
56
- const { config, filepath } = explorerResult || {};
57
-
58
- configFileOptions = {
59
- directory: path.resolve(path.dirname(filepath || ''), args.directory || 'migrations'),
60
- ...(config || {}),
61
- };
16
+ const getConfig = async (args, required = []) => {
17
+ const { configFile, cwd, ...overrides } = args;
18
+ const { loadContentfulConfig } = await import('@jungvonmatt/contentful-config');
19
+ const result = await loadContentfulConfig('migrations', {
20
+ configFile,
21
+ cwd,
22
+ overrides,
23
+ defaultConfig: {
24
+ fieldId: 'migration',
25
+ migrationContentTypeId: 'contentful-migrations',
26
+ host: 'api.contentful.com',
27
+ directory: path.resolve(cwd || process.cwd(), 'migrations'),
28
+ },
29
+ envMap: {
30
+ CONTENTFUL_SPACE_ID: 'spaceId',
31
+ CONTENTFUL_ENVIRONMENT_ID: 'environmentId',
32
+ CONTENTFUL_MANAGEMENT_TOKEN: 'managementToken',
33
+ CONTENTFUL_HOST: 'host',
34
+ CONTENTFUL_PROXY: 'proxy',
35
+ CONTENTFUL_MIGRATIONS_STORAGE: 'storage',
36
+ CONTENTFUL_MIGRATIONS_FIELD_ID: 'fieldId',
37
+ CONTENTFUL_MIGRATIONS_CONTENT_TYPE_ID: 'migrationContentTypeId',
38
+ CONTENTFUL_MIGRATIONS_DIRECTORY: 'directory',
39
+ CONTENTFUL_MIGRATIONS_REQUEST_BATCH_SIZE: 'requestBatchSize',
40
+ },
41
+ prompts: getPromts,
42
+ required,
43
+ });
62
44
 
63
- if (configFileOptions.directory && !path.isAbsolute(configFileOptions.directory)) {
64
- configFileOptions.directory = path.resolve(path.dirname(filepath || ''), configFileOptions.directory);
65
- }
66
- }
67
- } catch (error) {
68
- console.log('Error:', error.message);
69
- }
45
+ const missingStorage = result.missing.includes('storage');
70
46
 
71
- return mergeOptions(defaultOptions, contentfulCliOptions, environmentOptions, configFileOptions, args || {});
47
+ return { ...result.config, missingStorageModel: missingStorage && result.config.storage === STORAGE_CONTENT };
72
48
  };
73
49
 
50
+ /**
51
+ * Add prompts for migration specific config fields.
52
+ * All default contentful prompts are already available via @jungvonmatt/contentful-config
53
+ * @param {*} data
54
+ * @returns
55
+ */
74
56
  const getPromts = (data) => {
75
57
  return [
76
58
  {
77
- type: 'input',
78
- name: 'accessToken',
79
- message: 'Management Token',
80
- default: function () {
81
- return data.accessToken;
82
- },
83
- },
84
- {
85
- type: 'list',
86
- name: 'spaceId',
87
- message: 'Space ID',
88
- choices: async (answers) => {
89
- const spaces = await getSpaces({ ...(data || {}), ...answers });
90
- return spaces.map((space) => ({
91
- name: `${space.name} (${space.sys.id})`,
92
- value: space.sys.id,
93
- }));
94
- },
95
- default: function () {
96
- return data.spaceId;
97
- },
98
- },
99
- {
100
- type: 'list',
101
- name: 'environmentId',
102
- message: 'Environment ID',
103
- choices: async (answers) => {
104
- const environments = await getEnvironments({ ...(data || {}), ...answers });
105
- return environments.map((environment) => environment.sys.id);
106
- },
107
- default: function () {
108
- return data.environmentId;
109
- },
110
- },
111
- {
112
- type: 'list',
59
+ type: 'select',
113
60
  name: 'storage',
114
61
  message: 'How should the migrations be managed',
115
62
  choices: [
@@ -122,87 +69,50 @@ const getPromts = (data) => {
122
69
  value: STORAGE_TAG,
123
70
  },
124
71
  ],
125
- default: function () {
126
- return data.fieldId;
127
- },
72
+ initial: data.fieldId,
128
73
  },
129
74
  {
130
75
  type: 'input',
131
76
  name: 'fieldId',
132
77
  message: 'Id of the tag where the the migration version is stored',
133
- when(answers) {
134
- return answers.storage === STORAGE_TAG;
135
- },
136
- default: function () {
137
- return data.fieldId;
78
+ skip(answers) {
79
+ return answers.storage === STORAGE_CONTENT;
138
80
  },
81
+ initial: data.fieldId,
139
82
  },
140
83
  {
141
84
  type: 'input',
142
85
  name: 'migrationContentTypeId',
143
86
  message: 'Id of the content-type where the the migrations are stored',
144
- when(answers) {
145
- return answers.storage === STORAGE_CONTENT;
146
- },
147
- default: function () {
148
- return data.migrationContentTypeId;
87
+ skip(answers) {
88
+ return answers.storage === STORAGE_TAG;
149
89
  },
90
+ initial: data.migrationContentTypeId,
150
91
  },
151
92
  {
152
93
  type: 'input',
153
94
  name: 'directory',
154
95
  message: 'Directory where the migrations are stored',
155
- default: function () {
156
- return data.directory;
157
- },
96
+ initial: data.directory,
158
97
  },
159
98
  ];
160
99
  };
161
100
 
162
- const askAll = async (data = {}) => {
163
- console.log('Please verify the following options');
164
-
165
- const answers = await inquirer.prompt(getPromts(data));
166
- answers.directory = path.resolve(process.cwd(), answers.directory || data.directory);
167
-
168
- return answers;
169
- };
170
-
171
- const askMissing = async (data = {}, requiredFields = undefined) => {
172
- const allQuestions = getPromts(data);
173
- if (!requiredFields) {
174
- requiredFields = allQuestions.map(({ name }) => name);
175
- }
176
-
177
- const missingPromts = getPromts(data).filter(({ name }) => !data[name] && requiredFields.includes(name));
178
-
179
- // Check if storage changed to content and run initialization
180
- const missingStorage = missingPromts.some((prompt) => prompt.name === 'storage');
181
- const answers = await inquirer.prompt(missingPromts);
182
- const { storage } = answers;
183
-
184
- return { ...data, ...answers, missingStorageModel: missingStorage && storage === STORAGE_CONTENT };
185
- };
186
-
187
101
  const confirm = async (config = {}) => {
188
102
  if (config.yes) {
189
103
  return true;
190
104
  }
191
- const { check } = await inquirer.prompt([
192
- {
193
- type: 'confirm',
194
- name: 'check',
195
- message: 'Do you wish to proceed?',
196
- default: true,
197
- },
198
- ]);
199
105
 
200
- return check;
106
+ const prompt = new Confirm({
107
+ name: 'check',
108
+ message: config?.message || 'Do you wish to proceed?',
109
+ initial: true,
110
+ });
111
+
112
+ return prompt.run();
201
113
  };
202
114
 
203
115
  module.exports.getConfig = getConfig;
204
- module.exports.askAll = askAll;
205
- module.exports.askMissing = askMissing;
206
116
  module.exports.confirm = confirm;
207
117
  module.exports.STORAGE_TAG = STORAGE_TAG;
208
118
  module.exports.STORAGE_CONTENT = STORAGE_CONTENT;
package/lib/content.js CHANGED
@@ -52,7 +52,7 @@ const transferContent = async (config) => {
52
52
  destEnvironmentId,
53
53
  spaceId,
54
54
  contentType,
55
- accessToken: managementToken,
55
+ managementToken,
56
56
  } = config || {};
57
57
  // Check migration version
58
58
  const sourceVersion = await getLatestVersion({ ...config, environmentId: sourceEnvironmentId });
@@ -133,7 +133,7 @@ const transferContent = async (config) => {
133
133
  });
134
134
 
135
135
  // just a small helper to add a line break after the inquiry
136
- const br = diffConflicts && Object.keys(entryOverwrites).length ? '\n' : '';
136
+ const br = diffConflicts ? '\n' : '';
137
137
 
138
138
  if (assets.length === 0 && entries.length === 0) {
139
139
  console.log(pc.green(`${br}All done`), '🚀');
package/lib/contentful.js CHANGED
@@ -20,7 +20,6 @@ const LINK_TYPE_ASSET = 'Asset';
20
20
  const LINK_TYPE_ENTRY = 'Entry';
21
21
 
22
22
  const MAX_ALLOWED_LIMIT = 1000;
23
- const DEFAULT_ENVIRONMENT_ID = 'master';
24
23
 
25
24
  const getContentTypeId = (node) => {
26
25
  const { sys } = node || {};
@@ -63,21 +62,21 @@ const getContentName = (node, displayField) => {
63
62
  };
64
63
 
65
64
  const getClient = async (options) => {
66
- const { accessToken, host } = options || {};
65
+ const { managementToken, host } = options || {};
67
66
 
68
67
  if (client) {
69
68
  return client;
70
69
  }
71
70
 
72
71
  const params = {
73
- accessToken,
72
+ accessToken: managementToken,
74
73
  };
75
74
 
76
75
  if (host) {
77
76
  params.host = host;
78
77
  }
79
78
 
80
- if (accessToken) {
79
+ if (params.accessToken) {
81
80
  client = await contentful.createClient(params);
82
81
  return client;
83
82
  }
@@ -1,7 +1,7 @@
1
1
  import type Migration from "contentful-migration";
2
2
  import type { MigrationContext } from "contentful-migration";
3
3
 
4
- export type ValueMappingFunction = (values: string[]) => string[];
4
+ export type ValueMappingFunction<T extends string = string> = (values: T[]) => T[];
5
5
 
6
6
  export type AddValuesOptionMode = 'sorted' | 'start' | 'end' | 'before' | 'after';
7
7
 
@@ -10,13 +10,36 @@ export interface AddValuesOptions {
10
10
  ref?: string;
11
11
  }
12
12
 
13
+ // define some known mark and node values here to support code completion, but allow any string as well
14
+ export type RichTextMarks = 'bold' | 'italic' | 'underline' | 'code' | 'superscript' | 'subscript' | 'strikethrough' | string & {};
15
+ export type RichTextNodeType = 'document' | 'paragraph' | 'heading-1' | 'heading-2' | 'heading-3' | 'heading-4' | 'heading-5' | 'heading-6' | 'ordered-list' | 'unordered-list' | 'list-item' | 'hr' | 'blockquote' | 'embedded-entry-block' | 'embedded-asset-block' | 'embedded-resource-block' | 'table' | 'table-row' | 'table-cell' | 'table-header-cell' | 'asset-hyperlink' | 'embedded-entry-inline' | 'embedded-resource-inline' | 'entry-hyperlink' | 'hyperlink' | 'resource-hyperlink' | string & {};
16
+
17
+ export type RichTextLinkedNodeType = 'entry-hyperlink' | 'embedded-entry-block' | 'embedded-entry-inline'
18
+
19
+ export interface RichTextValidationHelpers {
20
+ addEnabledMarksValues(contentTypeId: string, fieldId: string, values: RichTextMarks | RichTextMarks[]): Promise<void>;
21
+ removeEnabledMarksValues(contentTypeId: string, fieldId: string, values: RichTextMarks | RichTextMarks[]): Promise<void>;
22
+ modifyEnabledMarksValues(contentTypeId: string, fieldId: string, valueMappingFunction: ValueMappingFunction<RichTextMarks>): Promise<void>;
23
+
24
+ addEnabledNodeTypeValues(contentTypeId: string, fieldId: string, values: RichTextNodeType | RichTextNodeType[]): Promise<void>;
25
+ removeEnabledNodeTypeValues(contentTypeId: string, fieldId: string, values: RichTextNodeType | RichTextNodeType[]): Promise<void>;
26
+ modifyEnabledNodeTypeValues(contentTypeId: string, fieldId: string, valueMappingFunction: ValueMappingFunction<RichTextNodeType>): Promise<void>;
27
+
28
+ addNodeContentTypeValues(contentTypeId: string, fieldId: string, nodeType: RichTextLinkedNodeType, values: string | string[]): Promise<void>;
29
+ removeNodeContentTypeValues(contentTypeId: string, fieldId: string, nodeType: RichTextLinkedNodeType, values: string | string[]): Promise<void>;
30
+ modifyNodeContentTypeValues(contentTypeId: string, fieldId: string, nodeType: RichTextLinkedNodeType, valueMappingFunction: ValueMappingFunction): Promise<void>;
31
+ }
32
+
13
33
  export interface ValidationHelpers {
14
34
  addLinkContentTypeValues(contentTypeId: string, fieldId: string, values: string | string[]): Promise<void>;
15
- addInValues(contentTypeId: string, fieldId: string, values: string | string[], options?: AddValuesOptions): Promise<void>;
16
35
  removeLinkContentTypeValues(contentTypeId: string, fieldId: string, values: string | string[]): Promise<void>;
17
- removeInValues(contentTypeId: string, fieldId: string, values: string | string[]): Promise<void>;
18
36
  modifyLinkContentTypeValues(contentTypeId: string, fieldId: string, valueMappingFunction: ValueMappingFunction): Promise<void>;
37
+
38
+ addInValues(contentTypeId: string, fieldId: string, values: string | string[], options?: AddValuesOptions): Promise<void>;
39
+ removeInValues(contentTypeId: string, fieldId: string, values: string | string[]): Promise<void>;
19
40
  modifyInValues(contentTypeId: string, fieldId: string, valueMappingFunction: ValueMappingFunction): Promise<void>;
41
+
42
+ richText: RichTextValidationHelpers;
20
43
  }
21
44
 
22
45
  export function getValidationHelpers(migration: Migration, context: MigrationContext): ValidationHelpers;
@@ -27,26 +27,57 @@ const getValidationHelpers = (migration, context) => {
27
27
  const removeValidationValues = (existingValues, newValues = []) =>
28
28
  existingValues.filter((x) => !newValues.includes(x));
29
29
 
30
- const modifyValidations = (validations, method, key, values) =>
31
- validations.map((validation) => {
32
- if (validation?.[key]) {
33
- if (!Array.isArray(values)) {
34
- values = [values];
30
+ /**
31
+ * Ensure the complete key path is available.
32
+ * If more than two keys are given the first node is treated as an Object.
33
+ * Return the container node, which should be an array.
34
+ * @param root {Object} The root container (the validations array)
35
+ * @param keyPath {string[]} The names of the nodes that must exist
36
+ * @return {Array} A tuple with the container object and the last key.
37
+ */
38
+ const ensureObjectPath = (root, keyPath) => {
39
+ let container = undefined;
40
+ let node = root;
41
+ keyPath.forEach((key, index) => {
42
+ container = node;
43
+ const isObjectNode = keyPath.length > 2 && index === 0;
44
+ if (Array.isArray(node)) {
45
+ let entry = node.find((someEntry) => someEntry[key]);
46
+ if (!entry) {
47
+ entry = { [key]: isObjectNode ? {} : [] };
48
+ node.push(entry);
35
49
  }
36
- if (!Array.isArray(validation[key])) {
37
- throw new Error(
38
- `modifying validation properties is only supported on arrays. validation.${key} is typeof ${typeof validation[
39
- key
40
- ]}`
41
- );
50
+ container = entry;
51
+ node = entry[key];
52
+ } else {
53
+ if (!node[key]) {
54
+ node[key] = isObjectNode ? {} : [];
42
55
  }
43
- validation[key] = method(validation[key], values);
56
+ node = node[key];
44
57
  }
45
-
46
- return validation;
47
58
  });
59
+ return [container, keyPath[keyPath.length - 1]];
60
+ };
61
+
62
+ /**
63
+ * Modify a validation property.
64
+ * @param {string[]} validations The list of validation entries
65
+ * @param {function} method The modification function
66
+ * @param {string[]} keyPaths The key paths where the first is the id of the validation root.
67
+ * @param {string | string[]} values
68
+ * @returns {*}
69
+ */
70
+ const modifyValidations = (validations, method, keyPaths, values) => {
71
+ const [containerObject, validationKey] = ensureObjectPath(validations, keyPaths);
72
+
73
+ containerObject[validationKey] = method(containerObject[validationKey], values);
74
+
75
+ return validations;
76
+ };
77
+
78
+ const modifyValidationValuesForType = async (validationKeyPaths, method, contentTypeId, fieldId, valueOrValues) => {
79
+ const values = Array.isArray(valueOrValues) ? valueOrValues : [valueOrValues];
48
80
 
49
- const modifyValidationValuesForType = async (validationKey, method, contentTypeId, fieldId, values) => {
50
81
  // Fetch content type
51
82
  const { fields } = await makeRequest({
52
83
  method: 'GET',
@@ -62,11 +93,11 @@ const getValidationHelpers = (migration, context) => {
62
93
  const ct = migration.editContentType(contentTypeId);
63
94
  ct.editField(fieldId).items({
64
95
  ...items,
65
- validations: modifyValidations(items?.validations, method, validationKey, values),
96
+ validations: modifyValidations(items?.validations || [], method, validationKeyPaths, values),
66
97
  });
67
98
  } else {
68
99
  const ct = migration.editContentType(contentTypeId);
69
- ct.editField(fieldId).validations(modifyValidations(validations, method, validationKey, values));
100
+ ct.editField(fieldId).validations(modifyValidations(validations, method, validationKeyPaths, values));
70
101
  }
71
102
  };
72
103
 
@@ -75,7 +106,7 @@ const getValidationHelpers = (migration, context) => {
75
106
  * Add the specified values to the list of allowed content type values
76
107
  */
77
108
  async addLinkContentTypeValues(contentTypeId, fieldId, values) {
78
- await modifyValidationValuesForType('linkContentType', addValidationValues, contentTypeId, fieldId, values);
109
+ await modifyValidationValuesForType(['linkContentType'], addValidationValues, contentTypeId, fieldId, values);
79
110
  },
80
111
 
81
112
  /**
@@ -85,21 +116,21 @@ const getValidationHelpers = (migration, context) => {
85
116
  */
86
117
  async addInValues(contentTypeId, fieldId, values, options = {}) {
87
118
  const addValuesWithOptions = (existingValues, newValues = []) => addValues(existingValues, newValues, options);
88
- await modifyValidationValuesForType('in', addValuesWithOptions, contentTypeId, fieldId, values);
119
+ await modifyValidationValuesForType(['in'], addValuesWithOptions, contentTypeId, fieldId, values);
89
120
  },
90
121
 
91
122
  /**
92
123
  * Remove the specified values from the list of allowed content type values
93
124
  */
94
125
  async removeLinkContentTypeValues(contentTypeId, fieldId, values) {
95
- await modifyValidationValuesForType('linkContentType', removeValidationValues, contentTypeId, fieldId, values);
126
+ await modifyValidationValuesForType(['linkContentType'], removeValidationValues, contentTypeId, fieldId, values);
96
127
  },
97
128
 
98
129
  /**
99
130
  * Remove the specified values from the list of allowed values
100
131
  */
101
132
  async removeInValues(contentTypeId, fieldId, values) {
102
- await modifyValidationValuesForType('in', removeValidationValues, contentTypeId, fieldId, values);
133
+ await modifyValidationValuesForType(['in'], removeValidationValues, contentTypeId, fieldId, values);
103
134
  },
104
135
 
105
136
  /**
@@ -108,7 +139,7 @@ const getValidationHelpers = (migration, context) => {
108
139
  */
109
140
  async modifyLinkContentTypeValues(contentTypeId, fieldId, valueMappingFunction) {
110
141
  const uniqueMappingFunction = (values) => unique(valueMappingFunction(values));
111
- await modifyValidationValuesForType('linkContentType', uniqueMappingFunction, contentTypeId, fieldId, []);
142
+ await modifyValidationValuesForType(['linkContentType'], uniqueMappingFunction, contentTypeId, fieldId, []);
112
143
  },
113
144
 
114
145
  /**
@@ -117,7 +148,66 @@ const getValidationHelpers = (migration, context) => {
117
148
  */
118
149
  async modifyInValues(contentTypeId, fieldId, valueMappingFunction) {
119
150
  const uniqueMappingFunction = (values) => unique(valueMappingFunction(values));
120
- await modifyValidationValuesForType('in', uniqueMappingFunction, contentTypeId, fieldId, []);
151
+ await modifyValidationValuesForType(['in'], uniqueMappingFunction, contentTypeId, fieldId, []);
152
+ },
153
+
154
+ richText: {
155
+ async addEnabledMarksValues(contentTypeId, fieldId, values) {
156
+ await modifyValidationValuesForType(['enabledMarks'], addValidationValues, contentTypeId, fieldId, values);
157
+ },
158
+ async removeEnabledMarksValues(contentTypeId, fieldId, values) {
159
+ await modifyValidationValuesForType(['enabledMarks'], removeValidationValues, contentTypeId, fieldId, values);
160
+ },
161
+ async modifyEnabledMarksValues(contentTypeId, fieldId, valueMappingFunction) {
162
+ const uniqueMappingFunction = (values) => unique(valueMappingFunction(values));
163
+ await modifyValidationValuesForType(['enabledMarks'], uniqueMappingFunction, contentTypeId, fieldId, []);
164
+ },
165
+
166
+ async addEnabledNodeTypeValues(contentTypeId, fieldId, values) {
167
+ await modifyValidationValuesForType(['enabledNodeTypes'], addValidationValues, contentTypeId, fieldId, values);
168
+ },
169
+ async removeEnabledNodeTypeValues(contentTypeId, fieldId, values) {
170
+ await modifyValidationValuesForType(
171
+ ['enabledNodeTypes'],
172
+ removeValidationValues,
173
+ contentTypeId,
174
+ fieldId,
175
+ values
176
+ );
177
+ },
178
+ async modifyEnabledNodeTypeValues(contentTypeId, fieldId, valueMappingFunction) {
179
+ const uniqueMappingFunction = (values) => unique(valueMappingFunction(values));
180
+ await modifyValidationValuesForType(['enabledNodeTypes'], uniqueMappingFunction, contentTypeId, fieldId, []);
181
+ },
182
+
183
+ async addNodeContentTypeValues(contentTypeId, fieldId, nodeType, values) {
184
+ await modifyValidationValuesForType(
185
+ ['nodes', nodeType, 'linkContentType'],
186
+ addValidationValues,
187
+ contentTypeId,
188
+ fieldId,
189
+ values
190
+ );
191
+ },
192
+ async removeNodeContentTypeValues(contentTypeId, fieldId, nodeType, values) {
193
+ await modifyValidationValuesForType(
194
+ ['nodes', nodeType, 'linkContentType'],
195
+ removeValidationValues,
196
+ contentTypeId,
197
+ fieldId,
198
+ values
199
+ );
200
+ },
201
+ async modifyNodeContentTypeValues(contentTypeId, fieldId, nodeType, valueMappingFunction) {
202
+ const uniqueMappingFunction = (values) => unique(valueMappingFunction(values));
203
+ await modifyValidationValuesForType(
204
+ ['nodes', nodeType, 'linkContentType'],
205
+ uniqueMappingFunction,
206
+ contentTypeId,
207
+ fieldId,
208
+ []
209
+ );
210
+ },
121
211
  },
122
212
  };
123
213
  };