wcc-contentful 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +51 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +240 -0
  6. data/.rubocop_todo.yml +13 -0
  7. data/CHANGELOG.md +7 -1
  8. data/Gemfile +4 -2
  9. data/Guardfile +36 -0
  10. data/README.md +1 -1
  11. data/Rakefile +5 -3
  12. data/bin/rspec +3 -0
  13. data/lib/generators/wcc/USAGE +24 -0
  14. data/lib/generators/wcc/menu_generator.rb +67 -0
  15. data/lib/generators/wcc/templates/.keep +0 -0
  16. data/lib/generators/wcc/templates/Procfile +3 -0
  17. data/lib/generators/wcc/templates/contentful_shell_wrapper +342 -0
  18. data/lib/generators/wcc/templates/menu/generated_add_menus.ts +85 -0
  19. data/lib/generators/wcc/templates/menu/menu.rb +25 -0
  20. data/lib/generators/wcc/templates/menu/menu_button.rb +25 -0
  21. data/lib/generators/wcc/templates/release +9 -0
  22. data/lib/generators/wcc/templates/wcc_contentful.rb +18 -0
  23. data/lib/wcc/contentful.rb +93 -26
  24. data/lib/wcc/contentful/client_ext.rb +15 -0
  25. data/lib/wcc/contentful/configuration.rb +93 -0
  26. data/lib/wcc/contentful/content_type_indexer.rb +153 -0
  27. data/lib/wcc/contentful/exceptions.rb +34 -0
  28. data/lib/wcc/contentful/graphql.rb +15 -0
  29. data/lib/wcc/contentful/graphql/builder.rb +172 -0
  30. data/lib/wcc/contentful/graphql/types.rb +54 -0
  31. data/lib/wcc/contentful/helpers.rb +28 -0
  32. data/lib/wcc/contentful/indexed_representation.rb +111 -0
  33. data/lib/wcc/contentful/model.rb +24 -0
  34. data/lib/wcc/contentful/model/menu.rb +7 -0
  35. data/lib/wcc/contentful/model/menu_button.rb +15 -0
  36. data/lib/wcc/contentful/model_builder.rb +151 -0
  37. data/lib/wcc/contentful/model_validators.rb +64 -0
  38. data/lib/wcc/contentful/model_validators/dsl.rb +165 -0
  39. data/lib/wcc/contentful/simple_client.rb +127 -0
  40. data/lib/wcc/contentful/simple_client/response.rb +160 -0
  41. data/lib/wcc/contentful/store.rb +8 -0
  42. data/lib/wcc/contentful/store/cdn_adapter.rb +79 -0
  43. data/lib/wcc/contentful/store/memory_store.rb +75 -0
  44. data/lib/wcc/contentful/store/postgres_store.rb +132 -0
  45. data/lib/wcc/contentful/version.rb +3 -1
  46. data/wcc-contentful.gemspec +49 -24
  47. metadata +261 -16
  48. data/lib/wcc/contentful/redirect.rb +0 -33
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wcc
4
+ class MenuGenerator < Rails::Generators::Base
5
+ source_root File.expand_path('templates', __dir__)
6
+
7
+ def create_menus_migration
8
+ now = Time.now.strftime('%Y%m%d%H%M')
9
+ copy_file 'menu/generated_add_menus.ts',
10
+ "db/migrate/#{now}01_generated_add_menus.ts"
11
+ end
12
+
13
+ def ensure_migration_tools_installed
14
+ in_root do
15
+ run 'npm init -y' unless File.exist?('package.json')
16
+ package = JSON.parse(File.read('package.json'))
17
+ deps = package['dependencies']
18
+
19
+ unless deps.try(:[], 'contentful-migration-cli').present?
20
+ run 'npm install --save watermarkchurch/migration-cli'
21
+ end
22
+ end
23
+ end
24
+
25
+ def ensure_wrapper_script_in_bin_dir
26
+ unless inside('bin') { File.exist?('contentful') }
27
+ copy_file 'contentful_shell_wrapper', 'bin/contentful'
28
+ end
29
+
30
+ if inside('bin') { File.exist?('release') }
31
+ release = inside('bin') { File.read('release') }
32
+ unless release.include?('contentful migrate')
33
+ insert_into_file('bin/release', after: 'bundle exec rake db:migrate') do
34
+ <<~HEREDOC
35
+ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
36
+ $DIR/contentful migrate -y
37
+ HEREDOC
38
+ end
39
+ end
40
+ else
41
+ copy_file 'release', 'bin/release'
42
+ end
43
+
44
+ if in_root { File.exist?('Procfile') }
45
+ procfile = in_root { File.read('Procfile') }
46
+ unless procfile.include?('release:')
47
+ insert_into_file('Procfile') do
48
+ 'release: bin/release'
49
+ end
50
+ end
51
+ else
52
+ copy_file 'Procfile'
53
+ end
54
+ end
55
+
56
+ def ensure_initializer_exists
57
+ return if inside('config/initializers') { File.exist?('wcc_contentful.rb') }
58
+
59
+ copy_file 'wcc_contentful.rb', 'config/initializers/wcc_contentful.rb'
60
+ end
61
+
62
+ def drop_model_overrides_in_lib_dir
63
+ copy_file 'menu/menu.rb', 'lib/wcc/contentful/model/menu.rb'
64
+ copy_file 'menu/menu_button.rb', 'lib/wcc/contentful/model/menu_button.rb'
65
+ end
66
+ end
67
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ web: bundle exec puma -C config/puma.rb
2
+ worker: bundle exec sidekiq
3
+ release: bin/release
@@ -0,0 +1,342 @@
1
+ #!/bin/bash
2
+
3
+ COLOR_NC='\033[0m' # No Color
4
+ COLOR_GRAY='\033[1;30m'
5
+ COLOR_RED='\033[0;31m'
6
+ COLOR_LCYAN='\033[1;36m'
7
+ COLOR_YELLOW='\033[1;33m'
8
+ COLOR_LGREEN='\033[1;32m'
9
+
10
+ logv() {
11
+ ([[ ! -z "$VERBOSE" ]] && >&2 echo -e "${COLOR_GRAY}$@${COLOR_NC}") || true
12
+ }
13
+
14
+ logerr() {
15
+ >&2 echo -e "${COLOR_RED}$@${COLOR_NC}"
16
+ }
17
+
18
+ ## *** Argument Parsing & validation ***
19
+
20
+ usage() {
21
+ echo "$0 <command> [opts]
22
+ Commands:
23
+ migrate [dir|file]
24
+ runs pending migration files in the given directory
25
+ * [dir|file] optional - Default: db/migrate
26
+
27
+ setup [file]
28
+ initializes a space with bare-minimum schema and seeds
29
+ * [file] optional - default: db/contentful-schema.json
30
+
31
+ backup [file]
32
+ downloads a backup of the current space to the given file
33
+ * [file] optional - default: timestamped file in current directory
34
+
35
+ clean [no-init]
36
+ Deletes all data in a given space and optionally sets it up again
37
+ using 'bin/contentful setup'.
38
+ * [no-init] optional - Skips the 'setup' step at the end.
39
+
40
+ restore [file]
41
+ restores a given backup file into the current space
42
+ * [file] optional - default: the most recent backup file in the current directory
43
+
44
+ restore_from -s [to space ID] <from space ID>
45
+ restores all data from the given space into the current space
46
+ * <space ID> required - the ID of the space to receive data from
47
+ * -s [to space ID] optional - the current working space. Default: \$CONTENTFUL_SPACE_ID
48
+
49
+ generate [name]
50
+ Creates a sample migration in the db/migrate directory
51
+ * [name] optional - default: 'contentful_migration'
52
+
53
+ Flags:" && \
54
+ grep " .)\ #" $0
55
+ echo "
56
+ Examples:" && \
57
+ grep -i "#\ example:" $0 | awk '{$1=""; $2=""; print " "$0}'
58
+ }
59
+
60
+ parse_args() {
61
+ OPTIND=1
62
+ local s=$(echo "$1" | tr '[:upper:]' '[:lower:]')
63
+ case "$s" in
64
+ migrate|setup|backup|export|restore|restore_from|import|generate|clean|help|h|\?)
65
+ export subcommand=$s
66
+ OPTIND=2
67
+ ;;
68
+ esac
69
+
70
+ # Parse flags
71
+ while getopts ":hyvs:a:" arg; do
72
+ case $arg in
73
+ y) # Yes - skip prompts
74
+ export YES="-y"
75
+ ;;
76
+ s) # Contentful Space ID - overrides env var CONTENTFUL_SPACE_ID
77
+ export CONTENTFUL_SPACE_ID=$OPTARG
78
+ ;;
79
+ a) # Contentful Mgmt Token - overrides env var CONTENTFUL_MANAGEMENT_TOKEN
80
+ export CONTENTFUL_MANAGEMENT_TOKEN=$OPTARG
81
+ ;;
82
+ v) # Verbose mode - extra output
83
+ export VERBOSE=true
84
+ ;;
85
+ h) # Display help.
86
+ usage
87
+ exit 0
88
+ ;;
89
+ *)
90
+ logerr "Unknown option: '$OPTARG'"
91
+ usage
92
+ exit -1
93
+ ;;
94
+ esac
95
+ done
96
+
97
+ export OPTIND
98
+ }
99
+
100
+ parse_args $@ && shift $(($OPTIND - 1))
101
+ # If they put args before the command like 'bin/contentful -s 1xab migrate -y', try parsing again
102
+ [[ -z "$subcommand" ]] && parse_args $@ && shift $(($OPTIND - 1))
103
+
104
+ require_environment() {
105
+ [[ -z "$CONTENTFUL_SPACE_ID" ]] && logerr "Please set CONTENTFUL_SPACE_ID environment variable or use '-s' flag." && exit -1;
106
+ [[ -z "$CONTENTFUL_MANAGEMENT_TOKEN" ]] && logerr "Please set CONTENTFUL_MANAGEMENT_TOKEN environment variable or use '-a' flag." && exit -1;
107
+ if [[ ! -f node_modules/.bin/contentful-migration ]]; then
108
+ command -v npm >/dev/null 2>&1 || (logerr "I require 'npm' but it's not installed. Please install nodejs."; exit -1)
109
+ logv "npm install"
110
+ npm install
111
+ [[ -f node_modules/.bin/contentful-migration ]] || (logerr "Failed installing node modules - please ensure contentful CLI is installed"; exit -1)
112
+ fi
113
+ }
114
+
115
+ ## *** Utility functions ***
116
+
117
+ confirm() {
118
+ [[ -z "$2" ]] && [[ ! -z "$YES" ]] && logv "$1 (y/n): confirmed by -y flag" && return 0;
119
+
120
+ while true; do
121
+ if [[ -z "$2" ]]; then
122
+ read -p $'\033[1;36m'"$1"' (y/n): '$'\033[0m' yn
123
+ else
124
+ # double confirm - extra dangerous.
125
+ read -p $'\033[0;31m'"$1"' (y/n): '$'\033[0m' yn
126
+ fi
127
+ case $yn in
128
+ [Yy]* ) return 0;;
129
+ [Nn]* ) return 1;;
130
+ * ) echo "Please answer yes or no.";;
131
+ esac
132
+ done
133
+ }
134
+
135
+ get_space_name() {
136
+ logv "curl -s https://api.contentful.com/spaces/$1?access_token=****"
137
+ curl -s https://api.contentful.com/spaces/$1?access_token=$CONTENTFUL_MANAGEMENT_TOKEN | jq -r .name | tr '[:upper:]' '[:lower:]'
138
+ }
139
+
140
+ set -e
141
+
142
+ # *** Commands ***
143
+
144
+ # Example: bin/contentful migrate -y -s 1xab -a $MY_TOKEN db/migrate/20180101120000_add_content_type_dog.ts
145
+ # equivalent to: bin/rake db:migrate
146
+ migrate() {
147
+ ARG="$1"
148
+ [[ -z "$ARG" ]] && ARG="db/migrate"
149
+ [[ -d "$ARG" ]] && ARG="batch $ARG"
150
+
151
+ require_environment
152
+
153
+ logv "contentful-migration -s $CONTENTFUL_SPACE_ID -a ***** $YES -p $ARG"
154
+ node_modules/.bin/ts-node node_modules/.bin/contentful-migration \
155
+ -s $CONTENTFUL_SPACE_ID -a $CONTENTFUL_MANAGEMENT_TOKEN \
156
+ $YES -p $ARG
157
+
158
+ mkdir -p db
159
+ logv "contentful-export -s --export-dir db --content-file contentful-schema.json --space-id $CONTENTFUL_SPACE_ID --management-token ***** --query-entries 'content_type=migrationHistory' --query-assets 'sys.id=false'"
160
+ node_modules/.bin/contentful-export --export-dir db --content-file contentful-schema.json \
161
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
162
+ --query-entries 'content_type=migrationHistory' \
163
+ --query-assets 'sys.id=false'
164
+
165
+ if [[ $(git diff-index --name-only HEAD | grep 'db/contentful-schema.json') == "" ]]; then
166
+ echo -e "${COLOR_LGREEN}✓ Schema in contentful space is equivalent to stored schema${COLOR_NC}"
167
+ else
168
+ echo -e "${COLOR_YELLOW}⚠️ Schema changed after running migrations${COLOR_NC}"
169
+ fi
170
+ }
171
+
172
+ # Example: bin/contentful backup -s 1xab -a $MY_TOKEN 2018_01_01.1xab.dump.json
173
+ # equivalent to: bin/rake db:dump[2018_01_01.dump]
174
+ backup() {
175
+ FILE="$1"
176
+ [[ ! -z "$FILE" ]] && FILE="--content-file $FILE" && shift
177
+
178
+ require_environment
179
+
180
+ logv "contentful-export $FILE --space-id $CONTENTFUL_SPACE_ID --management-token ***** $@"
181
+ node_modules/.bin/contentful-export $FILE \
182
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
183
+ $@
184
+ }
185
+
186
+ # Example: bin/contentful restore -y -s 1xab -a $MY_TOKEN 2018_01_01.1xab.dump.json
187
+ # equivalent to: bin/rake db:restore[2018_01_01.dump]
188
+ restore() {
189
+ FILE="$1"
190
+ if [[ -z "$FILE" ]]; then
191
+ FILE=$(ls contentful-export-$CONTENTFUL_SPACE_ID-* | sort -r | head -n 1)
192
+ [[ -z "$FILE" ]] && logerr "No file given on command line" && exit -1
193
+ fi
194
+
195
+ confirm "Import $FILE into $CONTENTFUL_SPACE_ID?" || exit -1
196
+
197
+ require_environment
198
+
199
+ logv "contentful-import --space-id $CONTENTFUL_SPACE_ID --management-token ***** --content-file $FILE"
200
+ node_modules/.bin/contentful-import \
201
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
202
+ --content-file $FILE
203
+ }
204
+
205
+ # Example: bin/contentful setup -y -s 1xab -a $MY_TOKEN db/my-schema.json
206
+ # equivalent to: bin/rake db:setup
207
+ setup() {
208
+ FILE="$1"
209
+ [[ -z "$FILE" ]] && FILE=db/contentful-schema.json
210
+
211
+ confirm "Initialize space $CONTENTFUL_SPACE_ID from seed file $FILE?" || exit -1
212
+
213
+ require_environment
214
+
215
+ logv "contentful-import --space-id $CONTENTFUL_SPACE_ID --management-token ***** --content-file $FILE"
216
+ node_modules/.bin/contentful-import \
217
+ --space-id $CONTENTFUL_SPACE_ID --management-token $CONTENTFUL_MANAGEMENT_TOKEN \
218
+ --content-file $FILE
219
+
220
+ migrate
221
+ }
222
+
223
+ # Example: bin/contentful clean -s 1xab -a $MY_TOKEN
224
+ clean() {
225
+ command -v jq >/dev/null 2>&1 || (logerr "I require 'jq' but it's not installed. Please run 'brew install jq'"; exit -1)
226
+
227
+ require_environment
228
+
229
+ name=$(get_space_name $CONTENTFUL_SPACE_ID)
230
+
231
+ confirm "This will delete all data and content types from $name. Are you sure?" || exit -1
232
+ [[ "$name" != *staging ]] && [[ "$name" != *test ]] && confirm "$name is not a staging or test environment! Are you really sure?" dangerous!
233
+
234
+ local bkup_file="contentful-export-$CONTENTFUL_SPACE_ID-`date +"%Y-%m-%dT%H-%M-%S"`.json"
235
+ backup $bkup_file
236
+ content_types=$(cat $bkup_file | jq -r '.contentTypes[].sys.id')
237
+ entries=$(cat $bkup_file | jq -r '.entries[].sys.id')
238
+ assets=$(cat $bkup_file | jq -r '.assets[].sys.id')
239
+
240
+ delete() {
241
+ logv "curl -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2/published\?access_token\=*****"
242
+ curl -s --fail -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2/published\?access_token\=$CONTENTFUL_MANAGEMENT_TOKEN > /dev/null
243
+ logv "curl -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2\?access_token\=*****"
244
+ curl -s --fail -XDELETE https://api.contentful.com/spaces/$CONTENTFUL_SPACE_ID/$1/$2\?access_token\=$CONTENTFUL_MANAGEMENT_TOKEN > /dev/null
245
+ }
246
+
247
+ [[ ! -z "$assets" ]] && while read -r id; do delete 'assets' $id; done <<< "${assets}"
248
+ [[ ! -z "$entries" ]] && while read -r id; do delete 'entries' $id; done <<< "${entries}"
249
+ [[ ! -z "$content_types" ]] && while read -r id; do delete 'content_types' $id; done <<< "${content_types}"
250
+
251
+ echo -e "${COLOR_LGREEN}All content deleted - prior content saved at${COLOR_NC} $bkup_file"
252
+ [[ "$1" == "no-init" ]] || (setup || logerr "Error setting up space $name!")
253
+ }
254
+
255
+ # Example: bin/contentful restore_from -s $staging_space_id -a $MY_TOKEN $production_space_id
256
+ restore_from() {
257
+ command -v jq >/dev/null 2>&1 || (logerr "I require 'jq' but it's not installed. Please run 'brew install jq'"; exit -1)
258
+
259
+ require_environment
260
+
261
+ from_space_id=$1
262
+ [[ -z "$from_space_id" ]] && logerr "Please provide the space ID from which to restore data" && exit -1
263
+ to_space_id=$CONTENTFUL_SPACE_ID
264
+
265
+ from_name=$(get_space_name $from_space_id)
266
+ to_name=$(get_space_name $CONTENTFUL_SPACE_ID)
267
+
268
+ echo -e "${COLOR_LCYAN}This will backup all data from $from_name, delete all data in $to_name, and then write the $from_name data over it.${COLOR_NC}"
269
+ confirm "Continue?" || exit -1
270
+ export YES='-y' # don't keep bugging the user
271
+
272
+ export CONTENTFUL_SPACE_ID=$from_space_id
273
+ local bkup_file="contentful-export-$from_space_id-`date +"%Y-%m-%dT%H-%M-%S"`.json"
274
+ logv "bin/contentful backup -s $CONTENTFUL_SPACE_ID $bkup_file"
275
+ backup $bkup_file --skip-roles --skip-webhooks
276
+
277
+ export CONTENTFUL_SPACE_ID=$to_space_id
278
+ logv "bin/contentful clean -s $CONTENTFUL_SPACE_ID"
279
+ clean no-init
280
+
281
+ logv "bin/contentful restore -s $CONTENTFUL_SPACE_ID $bkup_file"
282
+ restore $bkup_file
283
+ }
284
+
285
+ # Example: bin/contentful generate add content type dog
286
+ # equivalent to: bin/rails generate migration add_content_type_dog
287
+ generate() {
288
+ migration="
289
+ import Migration from 'contentful-migration-cli'
290
+
291
+ export = function (migration: Migration) {
292
+ const dog = migration.createContentType('dog', {
293
+ name: 'Dog'
294
+ })
295
+
296
+ const name = dog.createField('name')
297
+ name.name('Name')
298
+ .type('Symbol')
299
+ .required(true)
300
+ }
301
+ "
302
+
303
+ timestamp=$(date +%Y%m%d%H%M%S)
304
+ filename="$@"
305
+ [[ -z "$filename" ]] && filename="contentful_migration"
306
+ filename=${filename// /\_}
307
+ filename="db/migrate/${timestamp}_${filename}.ts"
308
+ echo "$migration" > $filename
309
+ echo "generated file $filename"
310
+ }
311
+
312
+ case $subcommand in
313
+ migrate)
314
+ migrate $@
315
+ ;;
316
+ backup|export)
317
+ backup $@
318
+ ;;
319
+ restore|import)
320
+ restore $@
321
+ ;;
322
+ setup)
323
+ setup $@
324
+ ;;
325
+ generate)
326
+ generate $@
327
+ ;;
328
+ clean)
329
+ clean $@
330
+ ;;
331
+ restore_from)
332
+ restore_from $@
333
+ ;;
334
+ help|h|\?)
335
+ usage
336
+ ;;
337
+ *)
338
+ logerr "Unknown command: '$1'"
339
+ usage
340
+ exit -1
341
+ ;;
342
+ esac
@@ -0,0 +1,85 @@
1
+
2
+ import Migration from 'contentful-migration-cli'
3
+
4
+ export = function (migration: Migration) {
5
+ const menu = migration.createContentType('menu')
6
+ .name('Menu')
7
+ .description('A Menu contains a number of Menu Buttons or other Menus, which ' +
8
+ 'will be rendered as drop-downs.')
9
+ .displayField('name')
10
+
11
+ menu.createField('name')
12
+ .name('Menu Name')
13
+ .type('Symbol')
14
+ .required(true)
15
+
16
+ menu.createField('topButton')
17
+ .name('Top Button')
18
+ .type('Link')
19
+ .linkType('Entry')
20
+ .validations([
21
+ {
22
+ linkContentType: [ 'menuButton' ],
23
+ message: 'The Top Button must be a button linking to a URL or page. ' +
24
+ 'If the menu is a dropdown, this button is visible when it is collapsed.'
25
+ }
26
+ ])
27
+
28
+ menu.createField('items')
29
+ .name('Items')
30
+ .type('Array')
31
+ .items({
32
+ type: 'Link',
33
+ linkType: 'Entry',
34
+ validations: [
35
+ {
36
+ linkContentType: [ 'menu', 'menuButton' ],
37
+ message: 'The items must be either buttons or drop-down menus'
38
+ }
39
+ ]
40
+ })
41
+
42
+ const menuButton = migration.createContentType('menuButton')
43
+ .name('Menu Button')
44
+ .description('A Menu Button is a clickable button that goes on a Menu. ' +
45
+ 'It has a link to a Page or a URL.')
46
+ .displayField('text')
47
+
48
+ menuButton.createField('text')
49
+ .name('Text')
50
+ .type('Symbol')
51
+ .required(true)
52
+ .validations([
53
+ {
54
+ size: { min: 1, max: 60 },
55
+ message: 'A Menu Button should have a very short text field - ideally a ' +
56
+ 'single word. Please limit the text to 60 characters.'
57
+ }
58
+ ])
59
+
60
+ menuButton.createField('icon')
61
+ .name('Icon')
62
+ .type('Link')
63
+ .linkType('Asset')
64
+
65
+ menuButton.createField('externalLink')
66
+ .name('External Link')
67
+ .type('Symbol')
68
+ .validations([
69
+ {
70
+ regexp: { pattern: "^(ftp|http|https):\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-\\/]))?$" },
71
+ message: "The external link must be a URL like 'https://www.watermark.org/'"
72
+ }
73
+ ])
74
+
75
+ menuButton.createField('link')
76
+ .name('Page Link')
77
+ .type('Link')
78
+ .linkType('Entry')
79
+ .validations([
80
+ {
81
+ linkContentType: [ 'page' ],
82
+ message: 'The Page Link must be a link to a Page which has a slug.'
83
+ }
84
+ ])
85
+ }