├── spec ├── fixtures │ ├── ini_fixtures │ │ ├── no_token.ini │ │ ├── no_global.ini │ │ ├── no_org.ini │ │ ├── contentfulrc.ini │ │ ├── orgid.ini │ │ └── sections.ini │ ├── json_fixtures │ │ ├── invalid.json │ │ ├── high.json │ │ ├── low.json │ │ ├── ok_version.json │ │ ├── links.json │ │ ├── asset_no_transform.json │ │ ├── no_ids.json │ │ ├── update_space_localized.json │ │ ├── object.json │ │ ├── simple.json │ │ ├── display_field.json │ │ ├── f3abi4dqvrhg_preview.json │ │ ├── assets_draft.json │ │ ├── environment_template.json │ │ ├── processed.json │ │ ├── issue_22.json │ │ ├── wl1z0pal05vy_content_types_only.json │ │ ├── cfexampleapi_cat.json │ │ └── cfexampleapi_cat_human.json │ └── vcr_fixtures │ │ ├── check_staging_environment_status.yml │ │ ├── generate_json_content_types_only.yml │ │ ├── check_created_space.yml │ │ ├── create_space.yml │ │ ├── check_update_space_localized.yml │ │ ├── check_original_staging_environment_status.yml │ │ └── multiple_organizations.yml ├── contentful │ └── bootstrap │ │ ├── support_spec.rb │ │ ├── templates │ │ ├── links │ │ │ ├── asset_spec.rb │ │ │ ├── entry_spec.rb │ │ │ └── base_spec.rb │ │ ├── blog_spec.rb │ │ ├── gallery_spec.rb │ │ ├── catalogue_spec.rb │ │ └── base_spec.rb │ │ ├── generator_spec.rb │ │ ├── commands │ │ ├── generate_token_spec.rb │ │ ├── generate_json_spec.rb │ │ ├── base_spec.rb │ │ ├── update_space_spec.rb │ │ └── create_space_spec.rb │ │ ├── server_spec.rb │ │ ├── command_runner_spec.rb │ │ └── token_spec.rb └── spec_helper.rb ├── Gemfile ├── lib └── contentful │ ├── bootstrap │ ├── templates │ │ ├── links.rb │ │ ├── links │ │ │ ├── asset.rb │ │ │ ├── entry.rb │ │ │ └── base.rb │ │ ├── blog.rb │ │ ├── gallery.rb │ │ ├── json_template.rb │ │ ├── base.rb │ │ └── catalogue.rb │ ├── version.rb │ ├── templates.rb │ ├── commands.rb │ ├── constants.rb │ ├── support.rb │ ├── commands │ │ ├── generate_token.rb │ │ ├── generate_json.rb │ │ ├── update_space.rb │ │ ├── base.rb │ │ └── create_space.rb │ ├── command_runner.rb │ ├── token.rb │ ├── server.rb │ └── generator.rb │ └── bootstrap.rb ├── Rakefile ├── Guardfile ├── .gitignore ├── .travis.yml ├── .rubocop.yml ├── LICENSE ├── contentful_bootstrap.gemspec ├── CONTRIBUTING.md ├── .rubocop_todo.yml ├── bin └── contentful_bootstrap ├── examples └── templates │ └── catalogue.json └── CHANGELOG.md /spec/fixtures/ini_fixtures/no_token.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | -------------------------------------------------------------------------------- /spec/fixtures/ini_fixtures/no_global.ini: -------------------------------------------------------------------------------- 1 | [other_section] 2 | SPACE_ID = blah 3 | -------------------------------------------------------------------------------- /spec/fixtures/ini_fixtures/no_org.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | CONTENTFUL_MANAGEMENT_ACCESS_TOKEN = foo 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "https://rubygems.org" 3 | 4 | gemspec 5 | 6 | gem 'pry' 7 | -------------------------------------------------------------------------------- /spec/fixtures/ini_fixtures/contentfulrc.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | CONTENTFUL_MANAGEMENT_ACCESS_TOKEN = foobar 3 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "content_types": [], 3 | "assets": [], 4 | "entries": {} 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/ini_fixtures/orgid.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | CONTENTFUL_MANAGEMENT_ACCESS_TOKEN = foobar 3 | CONTENTFUL_ORGANIZATION_ID = my_org 4 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/high.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "content_types": [], 4 | "assets": [], 5 | "entries": {} 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/low.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "content_types": [], 4 | "assets": [], 5 | "entries": {} 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/ini_fixtures/sections.ini: -------------------------------------------------------------------------------- 1 | [global] 2 | CONTENTFUL_MANAGEMENT_ACCESS_TOKEN = foo 3 | 4 | [other_section] 5 | SPACE_ID = blah 6 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/ok_version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "content_types": [], 4 | "assets": [], 5 | "entries": {} 6 | } 7 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/links.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/templates/links/entry' 2 | require 'contentful/bootstrap/templates/links/asset' 3 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/version.rb: -------------------------------------------------------------------------------- 1 | module Contentful 2 | module Bootstrap 3 | VERSION = '3.12.0'.freeze 4 | 5 | def self.major_version 6 | VERSION.split('.').first.to_i 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new do |t| 5 | t.rspec_opts = '--format documentation --color' 6 | end 7 | 8 | task test: :spec 9 | task default: :spec 10 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/templates/blog' 2 | require 'contentful/bootstrap/templates/catalogue' 3 | require 'contentful/bootstrap/templates/gallery' 4 | require 'contentful/bootstrap/templates/json_template' 5 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/commands.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/commands/create_space' 2 | require 'contentful/bootstrap/commands/update_space' 3 | require 'contentful/bootstrap/commands/generate_token' 4 | require 'contentful/bootstrap/commands/generate_json' 5 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/constants.rb: -------------------------------------------------------------------------------- 1 | module Contentful 2 | module Bootstrap 3 | module Constants 4 | OAUTH_APP_ID = 'a19770bea126e6d596d8599ff42e14173409e3e252895b78d0cb289c10586276'.freeze 5 | OAUTH_CALLBACK_URL = 'http://localhost:5123/oauth_callback'.freeze 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/token' 2 | require 'contentful/bootstrap/server' 3 | require 'contentful/bootstrap/version' 4 | require 'contentful/bootstrap/commands' 5 | require 'contentful/bootstrap/templates' 6 | require 'contentful/bootstrap/command_runner' 7 | require 'contentful/bootstrap/templates/links' 8 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/links.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "content_types": [], 4 | "assets": [], 5 | "entries": { 6 | "cat": [ 7 | { 8 | "sys": { "id": "foo" }, 9 | "fields": { 10 | "link": { 11 | "id": "foobar", 12 | "linkType": "Entry" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/links/asset.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/management' 2 | require 'contentful/bootstrap/templates/links/base' 3 | 4 | module Contentful 5 | module Bootstrap 6 | module Templates 7 | module Links 8 | class Asset < Base 9 | def management_class 10 | Contentful::Management::Asset 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/links/entry.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/management' 2 | require 'contentful/bootstrap/templates/links/base' 3 | 4 | module Contentful 5 | module Bootstrap 6 | module Templates 7 | module Links 8 | class Entry < Base 9 | def management_class 10 | Contentful::Management::Entry 11 | end 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/asset_no_transform.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [], 4 | "entries": {}, 5 | "assets": [ 6 | { 7 | "id": "my_asset", 8 | "title": "My Asset", 9 | "file": { 10 | "filename": "some_filename.svg", 11 | "contentType": "image/svg+xml", 12 | "url": "https://upload.wikimedia.org/wikipedia/commons/3/30/Vector-based_example.svg" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/no_ids.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "cat", 6 | "name": "Cat", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Symbol" 13 | } 14 | ] 15 | } 16 | ], 17 | "entries": { 18 | "cat": [ 19 | { 20 | "name": "Nyan Cat" 21 | } 22 | ] 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/support_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class Double 4 | def write 5 | $stderr.write('foo\n') 6 | end 7 | 8 | def muted_write 9 | Contentful::Bootstrap::Support.silence_stderr do 10 | write 11 | end 12 | end 13 | end 14 | 15 | describe Contentful::Bootstrap::Support do 16 | subject { Double.new } 17 | 18 | describe 'module methods' do 19 | it '#silence_stderr' do 20 | expect { subject.write }.to output('foo\n').to_stderr 21 | 22 | expect { subject.muted_write }.to_not output.to_stderr 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | group :green_red_refactor, halt_on_fail: true do 2 | guard :rspec, cmd: "bundle exec rspec" do 3 | require "guard/rspec/dsl" 4 | dsl = Guard::RSpec::Dsl.new(self) 5 | 6 | # Feel free to open issues for suggestions and improvements 7 | 8 | # RSpec files 9 | rspec = dsl.rspec 10 | watch(rspec.spec_helper) { rspec.spec_dir } 11 | watch(rspec.spec_support) { rspec.spec_dir } 12 | watch(rspec.spec_files) 13 | 14 | # Ruby files 15 | ruby = dsl.ruby 16 | dsl.watch_spec_files_for(ruby.lib_files) 17 | end 18 | 19 | guard :rubocop, cmd: "rubocop" do 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | 12 | ## Specific to RubyMotion: 13 | .dat* 14 | .repl_history 15 | build/ 16 | 17 | ## Documentation cache and generated files: 18 | /.yardoc/ 19 | /_yardoc/ 20 | /doc/ 21 | /rdoc/ 22 | 23 | ## Environment normalisation: 24 | /.bundle/ 25 | /vendor/bundle 26 | /lib/bundler/man/ 27 | 28 | # for a library or gem, you might want to ignore these files since the code is 29 | # intended to run in multiple environments; otherwise, check them in: 30 | Gemfile.lock 31 | # .ruby-version 32 | # .ruby-gemset 33 | 34 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 35 | .rvmrc 36 | 37 | .contentful_token 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.5.1 4 | - 2.6.1 5 | - 2.7.0 6 | notifications: 7 | slack: 8 | secure: Pw7UFAHD2fSAUtjf0P/W0Wg91TVTMAV8Gl04B/XhFrYpjVtifsGxWipOBDvJr9S7ep2UFbR3YbBABRWAJAigoDHvwEaf6eytEFT9LGsMwmPQOtb5NlJt0+Qhvys3sZUM0KskBORQ+Etz1M0O4e9baC5Tz1V8GzI/lNxIbpIM7dwQU18st+9sUTjBh6UFJDRXd0Qm28VAHk7fjlhpdKVH+5F4iEnFfRw414wVja8kPVmVDHCEqHPWS1JyHIj4eU8E50yBi3WjC60eE2TjAsCA9wc9gzvxb4tN5vXyuIVtqopPW4JPIBCaIv+j07biNAiLrTccQ5vv9RD9NTPmXANX9LyF/qD9I+itFl8xLatnwy1JXtPpi/S+OlQj6lluF17F+3c8g64vVgTYXyCDgCqpzSTj/KsTfwC9PkVGh8bmZMLtfUyd4IiMw8qiXt2opEmv90K1aodcixrpL2Db/OS2aSo4djRfMrjMlUN7tGM7hg9yQfAaZQKCPjof4C/XGG9me9FX+BvnNf9Xl6/t6uHFnkBfF969UX7gk7tzkwyyqpOp/FQ+6c+EiZF7wnVotbW49FBgEmZN62JsXSquDBlDfWrALeX4y9dZ+uYrWBd4q47N+kZUtogwf3f3gj0pE+K1XmKV549HkiHZ9MHcFtOwNu7KrCFMmUSPGIUKSotbg/c= 9 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/update_space_localized.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "test", 6 | "name": "Test", 7 | "displayField": "text", 8 | "fields": [ 9 | { 10 | "id": "text", 11 | "name": "Text", 12 | "type": "Symbol" 13 | } 14 | ] 15 | } 16 | ], 17 | "assets": [ 18 | 19 | ], 20 | "entries": { 21 | "test": [ 22 | { 23 | "sys": { 24 | "id": "test_1" 25 | }, 26 | "fields": { 27 | "text": "Foo" 28 | } 29 | }, 30 | { 31 | "sys": { 32 | "id": "test_2" 33 | }, 34 | "fields": { 35 | "text": "Bar" 36 | } 37 | } 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/object.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "test", 6 | "name": "Test", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Symbol" 13 | }, 14 | { 15 | "id": "object", 16 | "name": "Object", 17 | "type": "Object" 18 | } 19 | ] 20 | } 21 | ], 22 | "assets": [], 23 | "entries": { 24 | "test": [ 25 | { 26 | "sys": { "id": "foo" }, 27 | "fields": { 28 | "name": "test", 29 | "object": { 30 | "foo": "bar", 31 | "baz": "qux" 32 | } 33 | } 34 | } 35 | ] 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "cat", 6 | "name": "Cat", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Symbol" 13 | } 14 | ] 15 | } 16 | ], 17 | "assets": [ 18 | { 19 | "id": "cat_asset", 20 | "title": "Cat", 21 | "file": { 22 | "filename": "cat", 23 | "url": "https://somecat.com/my_cat.jpeg" 24 | } 25 | } 26 | ], 27 | "entries": { 28 | "cat": [ 29 | { 30 | "sys": { 31 | "id": "nyancat" 32 | }, 33 | "fields": { 34 | "name": "Nyan Cat" 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/display_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "cat", 6 | "name": "Cat", 7 | "display_field": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Symbol" 13 | } 14 | ] 15 | } 16 | ], 17 | "assets": [ 18 | { 19 | "id": "cat_asset", 20 | "title": "Cat", 21 | "file": { 22 | "filename": "cat", 23 | "url": "https://somecat.com/my_cat.jpeg" 24 | } 25 | } 26 | ], 27 | "entries": { 28 | "cat": [ 29 | { 30 | "sys": { 31 | "id": "nyancat" 32 | }, 33 | "fields": { 34 | "name": "Nyan Cat" 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/f3abi4dqvrhg_preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "testContentType", 6 | "name": "Test Content Type", 7 | "displayField": "title", 8 | "fields": [ 9 | { 10 | "id": "title", 11 | "name": "Title", 12 | "type": "Symbol" 13 | } 14 | ] 15 | } 16 | ], 17 | "assets": [ 18 | 19 | ], 20 | "entries": { 21 | "testContentType": [ 22 | { 23 | "sys": { 24 | "id": "1uT4AXWJ1me40QsAgAeSIi" 25 | }, 26 | "fields": { 27 | "title": "Published Entry" 28 | } 29 | }, 30 | { 31 | "sys": { 32 | "id": "49iT3aM9aUkK44KwuuyQow" 33 | }, 34 | "fields": { 35 | "title": "Draft Entry" 36 | } 37 | } 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/support.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | module Contentful 4 | module Bootstrap 5 | module Support 6 | def self.camel_case(a_string) 7 | a_string.split('_').each_with_object([]) { |e, a| a.push(a.empty? ? e : e.capitalize) }.join 8 | end 9 | 10 | def self.silence_stderr 11 | old_stderr = $stderr 12 | $stderr = StringIO.new 13 | yield 14 | ensure 15 | $stderr = old_stderr 16 | end 17 | 18 | def self.output(text = nil, quiet = false) 19 | if text.nil? 20 | puts unless quiet 21 | return 22 | end 23 | 24 | puts text unless quiet 25 | end 26 | 27 | def self.input(prompt_text, no_input = false) 28 | return if no_input 29 | 30 | print prompt_text 31 | answer = gets.chomp 32 | yield answer if block_given? 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/assets_draft.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "testContentType", 6 | "name": "Test Content Type", 7 | "displayField": "title", 8 | "fields": [ 9 | { 10 | "id": "title", 11 | "name": "Title", 12 | "type": "Symbol" 13 | } 14 | ] 15 | } 16 | ], 17 | "assets": [ 18 | { 19 | "id": "3JX5vcHRhmAmaYwWq0S0wa", 20 | "title": "Cat", 21 | "file": { 22 | "filename": "cat", 23 | "url": "https://images.contentful.com/f3abi4dqvrhg/3JX5vcHRhmAmaYwWq0S0wa/9ba45c1084d8530702bcd0de5617c59b/cat.jpg" 24 | } 25 | } 26 | ], 27 | "entries": { 28 | "testContentType": [ 29 | { 30 | "sys": { 31 | "id": "1uT4AXWJ1me40QsAgAeSIi" 32 | }, 33 | "fields": { 34 | "title": "Published Entry" 35 | } 36 | } 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/links/base.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/management' 2 | 3 | module Contentful 4 | module Bootstrap 5 | module Templates 6 | module Links 7 | class Base 8 | attr_reader :id 9 | def initialize(id) 10 | @id = id 11 | end 12 | 13 | def link_type 14 | self.class.name.split('::').last 15 | end 16 | 17 | def type 18 | Contentful::Management::ContentType::LINK 19 | end 20 | 21 | def to_management_object 22 | object = management_class.new 23 | object.id = id 24 | object 25 | end 26 | 27 | def management_class 28 | raise 'must implement' 29 | end 30 | 31 | def ==(other) 32 | return false unless other.is_a? self.class 33 | other.id == id 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/environment_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "foo", 6 | "name": "Foo", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Symbol" 13 | }, 14 | { 15 | "id": "content", 16 | "name": "Content", 17 | "type": "Text" 18 | } 19 | ] 20 | } 21 | ], 22 | "assets": [ 23 | 24 | ], 25 | "entries": { 26 | "foo": [ 27 | { 28 | "sys": { 29 | "id": "6yVdruR4GsKO2iKOqQS2CS" 30 | }, 31 | "fields": { 32 | "name": "Test", 33 | "content": "Some content" 34 | } 35 | }, 36 | { 37 | "sys": { 38 | "id": "foo_update" 39 | }, 40 | "fields": { 41 | "name": "Test updated", 42 | "content": "Some content" 43 | } 44 | } 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | Exclude: 5 | - contentful_bootstrap.gemspec 6 | - spec/**/* 7 | - examples/**/* 8 | - Guardfile 9 | - Gemfile 10 | - Rakefile 11 | 12 | Style/Documentation: 13 | Exclude: 14 | - 'spec/**/*' 15 | - 'test/**/*' 16 | - 'lib/contentful/bootstrap/commands.rb' 17 | - 'lib/contentful/bootstrap/constants.rb' 18 | - 'lib/contentful/bootstrap/generator.rb' 19 | - 'lib/contentful/bootstrap/server.rb' 20 | - 'lib/contentful/bootstrap/support.rb' 21 | - 'lib/contentful/bootstrap/templates/base.rb' 22 | - 'lib/contentful/bootstrap/templates/blog.rb' 23 | - 'lib/contentful/bootstrap/templates/catalogue.rb' 24 | - 'lib/contentful/bootstrap/templates/gallery.rb' 25 | - 'lib/contentful/bootstrap/templates/json_template.rb' 26 | - 'lib/contentful/bootstrap/templates/links/asset.rb' 27 | - 'lib/contentful/bootstrap/templates/links/base.rb' 28 | - 'lib/contentful/bootstrap/templates/links/entry.rb' 29 | - 'lib/contentful/bootstrap/token.rb' 30 | - 'lib/contentful/bootstrap/version.rb' 31 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/links/asset_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Links::Asset do 4 | subject { Contentful::Bootstrap::Templates::Links::Asset.new 'foo' } 5 | 6 | describe 'instance methods' do 7 | it '#link_type' do 8 | expect(subject.link_type).to eq 'Asset' 9 | end 10 | 11 | it '#type' do 12 | expect(subject.type).to eq 'Link' 13 | end 14 | 15 | it '#to_management_object' do 16 | expect(subject.to_management_object).to be_a Contentful::Management::Asset 17 | end 18 | 19 | it '#management_class' do 20 | expect(subject.management_class).to eq Contentful::Management::Asset 21 | end 22 | 23 | describe '#==' do 24 | it 'false when different type' do 25 | expect(subject == 2).to be_falsey 26 | end 27 | 28 | it 'false when different id' do 29 | expect(subject == described_class.new('bar')).to be_falsey 30 | end 31 | 32 | it 'true when same id' do 33 | expect(subject == described_class.new('foo')).to be_truthy 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/links/entry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Links::Entry do 4 | subject { Contentful::Bootstrap::Templates::Links::Entry.new 'foo' } 5 | 6 | describe 'instance methods' do 7 | it '#link_type' do 8 | expect(subject.link_type).to eq 'Entry' 9 | end 10 | 11 | it '#type' do 12 | expect(subject.type).to eq 'Link' 13 | end 14 | 15 | it '#to_management_object' do 16 | expect(subject.to_management_object).to be_a Contentful::Management::Entry 17 | end 18 | 19 | it '#management_class' do 20 | expect(subject.management_class).to eq Contentful::Management::Entry 21 | end 22 | 23 | describe '#==' do 24 | it 'false when different type' do 25 | expect(subject == 2).to be_falsey 26 | end 27 | 28 | it 'false when different id' do 29 | expect(subject == described_class.new('bar')).to be_falsey 30 | end 31 | 32 | it 'true when same id' do 33 | expect(subject == described_class.new('foo')).to be_truthy 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/blog_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Blog do 4 | let(:space) { Contentful::Management::Space.new } 5 | subject { described_class.new(space, 'master') } 6 | 7 | before :each do 8 | environment_proxy = Object.new 9 | allow(space).to receive(:environments) { environment_proxy } 10 | 11 | environment = Object.new 12 | allow(environment_proxy).to receive(:find) { environment } 13 | end 14 | 15 | describe 'content types' do 16 | it 'has Post content type' do 17 | expect(subject.content_types.detect { |ct| ct['id'] == 'post' }).to be_truthy 18 | end 19 | 20 | it 'has Author content type' do 21 | expect(subject.content_types.detect { |ct| ct['id'] == 'author' }).to be_truthy 22 | end 23 | end 24 | 25 | describe 'entries' do 26 | it 'has 2 posts' do 27 | expect(subject.entries['post'].size).to eq 2 28 | end 29 | 30 | it 'has 2 authors' do 31 | expect(subject.entries['author'].size).to eq 2 32 | end 33 | end 34 | 35 | describe 'assets' do 36 | it 'has no assets' do 37 | expect(subject.assets.empty?).to be_truthy 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/links/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Links::Base do 4 | subject { described_class.new 'foo' } 5 | 6 | describe 'instance methods' do 7 | it '#link_type' do 8 | expect(subject.link_type).to eq 'Base' 9 | end 10 | 11 | it '#type' do 12 | expect(subject.type).to eq 'Link' 13 | end 14 | 15 | describe 'abstract methods' do 16 | it '#to_management_object' do 17 | expect { subject.to_management_object }.to raise_error "must implement" 18 | end 19 | 20 | it '#management_class' do 21 | expect { subject.management_class }.to raise_error "must implement" 22 | end 23 | 24 | describe '#==' do 25 | it 'false when different type' do 26 | expect(subject == 2).to be_falsey 27 | end 28 | 29 | it 'false when different id' do 30 | expect(subject == described_class.new('bar')).to be_falsey 31 | end 32 | 33 | it 'true when same id' do 34 | expect(subject == described_class.new('foo')).to be_truthy 35 | end 36 | end 37 | end 38 | end 39 | 40 | describe 'properties' do 41 | it ':id' do 42 | expect(subject.id).to eq 'foo' 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/gallery_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Gallery do 4 | let(:space) { Contentful::Management::Space.new } 5 | subject { described_class.new(space) } 6 | 7 | before :each do 8 | environment_proxy = Object.new 9 | allow(space).to receive(:environments) { environment_proxy } 10 | 11 | environment = Object.new 12 | allow(environment_proxy).to receive(:find) { environment } 13 | end 14 | 15 | describe 'content types' do 16 | it 'has Author content type' do 17 | expect(subject.content_types.detect { |ct| ct['id'] == 'author' }).to be_truthy 18 | end 19 | 20 | it 'has Image content type' do 21 | expect(subject.content_types.detect { |ct| ct['id'] == 'image' }).to be_truthy 22 | end 23 | 24 | it 'has Gallery content type' do 25 | expect(subject.content_types.detect { |ct| ct['id'] == 'gallery' }).to be_truthy 26 | end 27 | end 28 | 29 | describe 'entries' do 30 | it 'has 1 author' do 31 | expect(subject.entries['author'].size).to eq 1 32 | end 33 | 34 | it 'has 2 images' do 35 | expect(subject.entries['image'].size).to eq 2 36 | end 37 | 38 | it 'has 1 gallery' do 39 | expect(subject.entries['gallery'].size).to eq 1 40 | end 41 | end 42 | 43 | describe 'assets' do 44 | it 'has 2 assets' do 45 | expect(subject.assets.size).to eq 2 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/catalogue_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Catalogue do 4 | let(:space) { Contentful::Management::Space.new } 5 | subject { described_class.new(space, 'master') } 6 | 7 | before :each do 8 | environment_proxy = Object.new 9 | allow(space).to receive(:environments) { environment_proxy } 10 | 11 | environment = Object.new 12 | allow(environment_proxy).to receive(:find) { environment } 13 | end 14 | 15 | describe 'content types' do 16 | it 'has Brand content type' do 17 | expect(subject.content_types.detect { |ct| ct['id'] == 'brand' }).to be_truthy 18 | end 19 | 20 | it 'has Category content type' do 21 | expect(subject.content_types.detect { |ct| ct['id'] == 'category' }).to be_truthy 22 | end 23 | 24 | it 'has Product content type' do 25 | expect(subject.content_types.detect { |ct| ct['id'] == 'product' }).to be_truthy 26 | end 27 | end 28 | 29 | describe 'entries' do 30 | it 'has 2 brands' do 31 | expect(subject.entries['brand'].size).to eq 2 32 | end 33 | 34 | it 'has 2 categories' do 35 | expect(subject.entries['category'].size).to eq 2 36 | end 37 | 38 | it 'has 2 products' do 39 | expect(subject.entries['product'].size).to eq 2 40 | end 41 | end 42 | 43 | describe 'assets' do 44 | it 'has 6 assets' do 45 | expect(subject.assets.size).to eq 6 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/processed.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "cat", 6 | "name": "Cat", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Symbol" 13 | } 14 | ] 15 | }, 16 | { 17 | "id": "dog", 18 | "bootstrapProcessed": true, 19 | "name": "Dog", 20 | "displayField": "name", 21 | "fields": [ 22 | { 23 | "id": "name", 24 | "name": "Name", 25 | "type": "Symbol" 26 | } 27 | ] 28 | } 29 | ], 30 | "assets": [ 31 | { 32 | "id": "cat_asset", 33 | "title": "Cat", 34 | "file": { 35 | "filename": "cat", 36 | "url": "https://somecat.com/my_cat.jpeg" 37 | } 38 | }, 39 | { 40 | "id": "dog_asset", 41 | "bootstrapProcessed": true, 42 | "title": "Dog", 43 | "file": { 44 | "filename": "Dog", 45 | "url": "https://somedog.com/my_dog.jpeg" 46 | } 47 | } 48 | ], 49 | "entries": { 50 | "cat": [ 51 | { 52 | "sys": { 53 | "id": "nyancat" 54 | }, 55 | "fields": { 56 | "name": "Nyan Cat" 57 | } 58 | } 59 | ], 60 | "dog": [ 61 | { 62 | "sys": { 63 | "id": "doge", 64 | "bootstrapProcessed": true 65 | }, 66 | "fields": { 67 | "name": "Doge" 68 | } 69 | } 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/templates/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Templates::Base do 4 | let(:space) { Contentful::Management::Space.new } 5 | subject { described_class.new(space, 'master', true) } 6 | 7 | before :each do 8 | environment_proxy = Object.new 9 | allow(space).to receive(:environments) { environment_proxy } 10 | 11 | environment = Object.new 12 | allow(environment_proxy).to receive(:find) { environment } 13 | end 14 | 15 | describe 'instance methods' do 16 | it '#content_types' do 17 | expect(subject.content_types).to eq [] 18 | end 19 | 20 | it '#entries' do 21 | expect(subject.entries).to eq({}) 22 | end 23 | 24 | it '#assets' do 25 | expect(subject.assets).to eq [] 26 | end 27 | 28 | describe '#run' do 29 | it 'calls create for each kind of object' do 30 | ['content_types', 'assets', 'entries'].each do |name| 31 | expect(subject).to receive("create_#{name}".to_sym) 32 | end 33 | 34 | subject.run 35 | end 36 | 37 | it 'calls after_run when done' do 38 | expect(subject).to receive(:after_run) 39 | 40 | subject.run 41 | end 42 | 43 | context 'with skip_content_types set to true' do 44 | subject { described_class.new(space, 'master', true, true) } 45 | 46 | it 'doesnt call create_content_type if skip_content_types is sent' do 47 | expect(subject).to receive(:create_entries) 48 | expect(subject).to receive(:create_assets) 49 | expect(subject).not_to receive(:create_content_types) 50 | 51 | subject.run 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /contentful_bootstrap.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'contentful/bootstrap/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "contentful_bootstrap" 8 | spec.version = Contentful::Bootstrap::VERSION 9 | spec.authors = ["David Litvak Bruno"] 10 | spec.email = ["david.litvakb@gmail.com"] 11 | spec.summary = %q{Contentful CLI tool for getting started with Contentful} 12 | spec.homepage = "https://www.contentful.com" 13 | spec.license = "MIT" 14 | 15 | spec.files = `git ls-files -z`.split("\x0") 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ["lib"] 19 | 20 | spec.add_development_dependency 'bundler' 21 | spec.add_development_dependency 'rake', '>= 12.3.3' 22 | spec.add_development_dependency 'public_suffix', '< 1.5' 23 | spec.add_development_dependency 'rspec', '~> 3' 24 | spec.add_development_dependency 'vcr', '~> 2.9' 25 | spec.add_development_dependency 'webmock', '~> 1.24' 26 | spec.add_development_dependency 'rr' 27 | spec.add_development_dependency 'simplecov' 28 | spec.add_development_dependency 'guard' 29 | spec.add_development_dependency 'guard-rspec' 30 | spec.add_development_dependency 'listen', '~> 3.0' 31 | spec.add_development_dependency 'guard-rubocop' 32 | spec.add_development_dependency 'rubocop', '~> 0.49' 33 | 34 | spec.add_runtime_dependency 'launchy' 35 | spec.add_runtime_dependency 'contentful-management', '~> 2.0', '>= 2.0.2' 36 | spec.add_runtime_dependency 'contentful', '~> 2.6.0' 37 | spec.add_runtime_dependency 'inifile' 38 | end 39 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/commands/generate_token.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/management' 2 | require 'contentful/management/error' 3 | require 'contentful/management/request' 4 | require 'contentful/bootstrap/version' 5 | require 'contentful/bootstrap/commands/base' 6 | 7 | module Contentful 8 | module Bootstrap 9 | module Commands 10 | class GenerateToken < Base 11 | attr_reader :token_name 12 | def initialize(token, space, options = {}) 13 | @token_name = options.fetch(:token_name, 'Bootstrap Token') 14 | 15 | super(token, space, options) 16 | end 17 | 18 | def run 19 | output 'Creating Delivery API Token' 20 | 21 | fetch_space 22 | 23 | access_token = fetch_access_token 24 | 25 | output "Token '#{token_name}' created! - '#{access_token}'" 26 | Support.input('Do you want to write the Delivery Token to your configuration file? (Y/n): ', no_input) do |answer| 27 | unless answer.casecmp('n').zero? 28 | @token.write_access_token(@actual_space.name, access_token) 29 | @token.write_space_id(@actual_space.name, @actual_space.id) 30 | end 31 | end 32 | 33 | access_token 34 | end 35 | 36 | def fetch_space 37 | @actual_space = if @space.is_a?(String) 38 | client.spaces.find(@space) 39 | else 40 | @space 41 | end 42 | end 43 | 44 | def fetch_access_token 45 | @actual_space.api_keys.create( 46 | name: token_name, 47 | description: "Created with 'contentful_bootstrap.rb v#{Contentful::Bootstrap::VERSION}'" 48 | ).access_token 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | In the spirit of [free software][free-sw], **everyone** is encouraged to help 3 | improve this project. 4 | 5 | [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html 6 | 7 | Here are some ways *you* can contribute: 8 | 9 | * by using alpha, beta, and prerelease versions 10 | * by reporting bugs 11 | * by suggesting new features 12 | * by writing or editing documentation 13 | * by writing specifications 14 | * by writing code ( **no patch is too small** : fix typos, add comments, clean up inconsistent whitespace ) 15 | * by refactoring code 16 | * by closing [issues][] 17 | * by reviewing patches 18 | 19 | [issues]: https://github.com/contentful-labs/contentful_bootstrap.rb/issues 20 | 21 | ## Submitting an Issue 22 | We use the [GitHub issue tracker][issues] to track bugs and features. Before 23 | submitting a bug report or feature request, check to make sure it hasn't 24 | already been submitted. When submitting a bug report, please include a [Gist][] 25 | that includes a stack trace and any details that may be necessary to reproduce 26 | the bug, including your gem version, Ruby version, and operating system. 27 | Ideally, a bug report should include a pull request with failing specs. 28 | 29 | [gist]: https://gist.github.com/ 30 | 31 | ## Submitting a Pull Request 32 | 1. [Fork the repository.][fork] 33 | 2. [Create a topic branch.][branch] 34 | 3. Add specs for your unimplemented feature or bug fix. 35 | 4. Run `bundle exec rake test`. If your specs pass, return to step 3. 36 | 5. Implement your feature or bug fix. 37 | 6. Run `bundle exec rake test`. If your specs fail, return to step 5. 38 | 7. Add, commit, and push your changes. 39 | 8. [Submit a pull request.][pr] 40 | 41 | [fork]: http://help.github.com/fork-a-repo/ 42 | [branch]: http://learn.github.com/p/branching.html 43 | [pr]: http://help.github.com/send-pull-requests/ 44 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/command_runner.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/token' 2 | require 'contentful/bootstrap/commands' 3 | 4 | module Contentful 5 | module Bootstrap 6 | class CommandRunner 7 | attr_reader :config_path, :token 8 | 9 | def initialize(config_path = '') 10 | @config_path = config_path 11 | @token = Token.new(config_path) 12 | end 13 | 14 | def create_space(space_name, options = {}) 15 | Contentful::Bootstrap::Commands::CreateSpace.new( 16 | @token, space_name, options 17 | ).run 18 | end 19 | 20 | def update_space(space_id, options = {}) 21 | Contentful::Bootstrap::Commands::UpdateSpace.new( 22 | @token, space_id, options 23 | ).run 24 | end 25 | 26 | def generate_token(space, options = {}) 27 | Contentful::Bootstrap::Commands::GenerateToken.new( 28 | @token, space, options 29 | ).run 30 | end 31 | 32 | def generate_json(space_id, options = {}) 33 | filename = options.fetch(:filename, nil) 34 | access_token = options.fetch(:access_token, nil) 35 | environment = options.fetch(:environment, 'master') 36 | content_types_only = options.fetch(:content_types_only, false) 37 | quiet = options.fetch(:quiet, false) 38 | use_preview = options.fetch(:use_preview, false) 39 | content_type_ids = options.fetch(:content_type_ids, []) 40 | 41 | raise 'Access Token required' if access_token.nil? 42 | 43 | Contentful::Bootstrap::Commands::GenerateJson.new( 44 | space_id, 45 | access_token, 46 | environment, 47 | filename, 48 | content_types_only, 49 | quiet, 50 | use_preview, 51 | content_type_ids 52 | ).run 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Generator do 4 | subject { described_class.new('wl1z0pal05vy', '48d7db7d4cd9d09df573c251d456f4acc72141b92f36e57f8684b36cf5cfff6e', 'master', false, false, []) } 5 | 6 | describe 'user agent headers' do 7 | it 'client has proper integration data' do 8 | expect(subject.client.app_info).to eq(name: 'bootstrap', version: Contentful::Bootstrap::VERSION) 9 | end 10 | end 11 | 12 | describe 'JSON template generator' do 13 | it 'can generate a JSON template for a given space' do 14 | vcr('generate_json') { 15 | json_fixture('wl1z0pal05vy') { |json| 16 | expect(subject.generate_json).to eq JSON.pretty_generate(json) 17 | } 18 | } 19 | end 20 | 21 | context 'with content_types_only set to true' do 22 | subject { described_class.new('wl1z0pal05vy', '48d7db7d4cd9d09df573c251d456f4acc72141b92f36e57f8684b36cf5cfff6e', 'master', true, false, []) } 23 | 24 | it 'can generate a JSON template for a given space with only Content Types' do 25 | vcr('generate_json_content_types_only') { 26 | json_fixture('wl1z0pal05vy_content_types_only') { |json| 27 | expect(subject.generate_json).to eq JSON.pretty_generate(json) 28 | } 29 | } 30 | end 31 | end 32 | end 33 | 34 | describe 'integration' do 35 | subject { described_class.new('9utsm1g0t7f5', 'a67d4d9011f6d6c1dfe4169d838114d3d3849ab6df6fb1d322cf3ee91690fae4', 'staging', false, false, []) } 36 | it 'can generate templates from an environment' do 37 | vcr('generate_json_environments') { 38 | unparsed_json = subject.generate_json 39 | json = JSON.load(unparsed_json) 40 | 41 | json_fixture('environment_template') { |fixture| 42 | expect(json).to eq fixture 43 | } 44 | expect(json['entries']['foo'].size).to eq 2 45 | } 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | SimpleCov.start 3 | 4 | $LOAD_PATH.unshift File.expand_path('lib', __FILE__) 5 | 6 | require 'contentful/bootstrap' 7 | require 'vcr' 8 | require 'json' 9 | 10 | RSpec.configure do |config| 11 | config.filter_run :focus => true 12 | config.run_all_when_everything_filtered = true 13 | end 14 | 15 | VCR.configure do |config| 16 | config.cassette_library_dir = File.join('spec', 'fixtures', 'vcr_fixtures') 17 | config.hook_into :webmock 18 | end 19 | 20 | def json_path(name) 21 | File.join('spec', 'fixtures', 'json_fixtures', "#{name}.json") 22 | end 23 | 24 | def json_fixture(name) 25 | json = JSON.load(File.read(File.expand_path(json_path(name)))) 26 | yield json if block_given? 27 | json 28 | end 29 | 30 | def vcr(cassette) 31 | VCR.use_cassette(cassette) do 32 | yield if block_given? 33 | end 34 | end 35 | 36 | def ini(ini_file) 37 | file = IniFile.load(ini_file) 38 | yield file if block_given? 39 | file 40 | end 41 | 42 | class ApiKeyDouble 43 | attr_reader :name, :description, :access_token 44 | 45 | def initialize(name, description) 46 | @name = name 47 | @description = description 48 | @access_token = 'random_api_key' 49 | end 50 | end 51 | 52 | class ApiKeysHandlerDouble 53 | def create(options) 54 | ApiKeyDouble.new(options[:name], options[:description]) 55 | end 56 | end 57 | 58 | class SpaceDouble 59 | def id 60 | 'foobar' 61 | end 62 | 63 | def name 64 | 'foobar' 65 | end 66 | 67 | def api_keys 68 | ApiKeysHandlerDouble.new 69 | end 70 | end 71 | 72 | class ServerDouble 73 | def [](key) 74 | end 75 | end 76 | 77 | class ErrorRequestDouble 78 | def request; self; end 79 | def endpoint; self; end 80 | def error_message; self; end 81 | def raw; self; end 82 | def body; self; end 83 | def status; self; end 84 | end 85 | 86 | class RequestDouble 87 | attr_accessor :query 88 | end 89 | 90 | class ResponseDouble 91 | attr_accessor :body, :status, :content_type 92 | end 93 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/commands/generate_json.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/generator' 2 | require 'contentful/bootstrap/commands/base' 3 | 4 | module Contentful 5 | module Bootstrap 6 | module Commands 7 | class GenerateJson 8 | attr_reader :space_id, :filename, :access_token, :content_types_only, :use_preview, :content_type_ids, :environment 9 | def initialize( 10 | space_id, 11 | access_token, 12 | environment = 'master', 13 | filename = nil, 14 | content_types_only = false, 15 | quiet = false, 16 | use_preview = false, 17 | content_type_ids = [] 18 | ) 19 | @space_id = space_id 20 | @access_token = access_token 21 | @environment = environment 22 | @filename = filename 23 | @content_types_only = content_types_only 24 | @quiet = quiet 25 | @use_preview = use_preview 26 | @content_type_ids = content_type_ids 27 | end 28 | 29 | def run 30 | if access_token.nil? 31 | output 'Access Token not specified' 32 | output 'Exiting!' 33 | exit(1) 34 | end 35 | 36 | output "Generating JSON Template for Space: '#{space_id}'" 37 | output 38 | 39 | json = Contentful::Bootstrap::Generator.new( 40 | space_id, 41 | access_token, 42 | environment, 43 | content_types_only, 44 | use_preview, 45 | content_type_ids 46 | ).generate_json 47 | 48 | write(json) 49 | end 50 | 51 | def write(json) 52 | if filename.nil? 53 | output "#{json}\n" 54 | else 55 | output "Saving JSON template to '#{filename}'" 56 | ::File.write(filename, json) 57 | end 58 | end 59 | 60 | protected 61 | 62 | def output(text = nil) 63 | Support.output(text, @quiet) 64 | end 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/commands/update_space.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/management' 2 | require 'contentful/management/error' 3 | require 'contentful/bootstrap/templates' 4 | require 'contentful/bootstrap/commands/base' 5 | 6 | module Contentful 7 | module Bootstrap 8 | module Commands 9 | class UpdateSpace < Base 10 | attr_reader :json_template 11 | def initialize(token, space_id, options = {}) 12 | @json_template = options.fetch(:json_template, nil) 13 | @mark_processed = options.fetch(:mark_processed, false) 14 | @skip_content_types = options.fetch(:skip_content_types, false) 15 | @no_publish = options.fetch(:no_publish, false) 16 | @quiet = options.fetch(:quiet, false) 17 | @environment = options.fetch(:environment, 'master') 18 | 19 | super(token, space_id, options) 20 | end 21 | 22 | def run 23 | if @json_template.nil? 24 | output 'JSON Template not found. Exiting!' 25 | exit(1) 26 | end 27 | 28 | output "Updating Space '#{@space}'" 29 | 30 | update_space = fetch_space 31 | 32 | update_json_template(update_space) 33 | 34 | output 35 | output "Successfully updated Space #{@space}" 36 | 37 | update_space 38 | end 39 | 40 | protected 41 | 42 | def fetch_space 43 | client.spaces.find(@space) 44 | rescue Contentful::Management::NotFound 45 | output 'Space Not Found. Exiting!' 46 | exit(1) 47 | end 48 | 49 | private 50 | 51 | def update_json_template(space) 52 | if ::File.exist?(@json_template) 53 | output "Updating from JSON Template '#{@json_template}'" 54 | Templates::JsonTemplate.new(space, @json_template, @environment, @mark_processed, true, @quiet, @skip_content_types, @no_publish).run 55 | output "JSON Template '#{@json_template}' updated!" 56 | else 57 | output "JSON Template '#{@json_template}' does not exist. Please check that you specified the correct file name." 58 | exit(1) 59 | end 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/token.rb: -------------------------------------------------------------------------------- 1 | require 'inifile' 2 | 3 | module Contentful 4 | module Bootstrap 5 | class Token 6 | CONFIG_ENV = 'CONTENTFUL_ENV'.freeze 7 | DEFAULT_SECTION = 'global'.freeze 8 | DEFAULT_PATH = '.contentfulrc'.freeze 9 | MANAGEMENT_TOKEN = 'CONTENTFUL_MANAGEMENT_ACCESS_TOKEN'.freeze 10 | DELIVERY_TOKEN = 'CONTENTFUL_DELIVERY_ACCESS_TOKEN'.freeze 11 | ORGANIZATION_ID = 'CONTENTFUL_ORGANIZATION_ID'.freeze 12 | SPACE_ID = 'SPACE_ID'.freeze 13 | 14 | attr_reader :config_path 15 | 16 | def initialize(config_path = '') 17 | @config_path = config_path 18 | end 19 | 20 | def ==(other) 21 | return false unless other.is_a?(Contentful::Bootstrap::Token) 22 | other.config_path == @config_path 23 | end 24 | 25 | def present? 26 | return false unless ::File.exist? filename 27 | config_file[config_section].key? MANAGEMENT_TOKEN 28 | end 29 | 30 | def read 31 | config_file[config_section].fetch(MANAGEMENT_TOKEN) 32 | rescue KeyError 33 | raise 'Token not found' 34 | end 35 | 36 | def read_organization_id 37 | config_file[config_section].fetch(ORGANIZATION_ID, nil) 38 | end 39 | 40 | def write(value, section = nil, key = MANAGEMENT_TOKEN) 41 | file = config_file 42 | file[section || config_section][key] = value 43 | file.save 44 | end 45 | 46 | def write_organization_id(organization_id) 47 | write(organization_id, nil, ORGANIZATION_ID) 48 | end 49 | 50 | def write_access_token(space_name, token) 51 | write(token, space_name, DELIVERY_TOKEN) 52 | end 53 | 54 | def write_space_id(space_name, space_id) 55 | write(space_id, space_name, SPACE_ID) 56 | end 57 | 58 | def filename 59 | return config_path unless config_path.empty? 60 | ::File.join(ENV['HOME'], DEFAULT_PATH) 61 | end 62 | 63 | def config_section 64 | return ENV[CONFIG_ENV] if config_file.has_section? ENV[CONFIG_ENV] 65 | DEFAULT_SECTION 66 | end 67 | 68 | def config_file 69 | @config_file ||= ::File.exist?(filename) ? IniFile.load(filename) : IniFile.new(filename: filename) 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/blog.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/templates/base' 2 | require 'contentful/bootstrap/templates/links' 3 | 4 | module Contentful 5 | module Bootstrap 6 | module Templates 7 | class Blog < Base 8 | def content_types 9 | [ 10 | { 11 | 'id' => 'author', 12 | 'name' => 'Author', 13 | 'displayField' => 'name', 14 | 'fields' => [ 15 | { 16 | 'id' => 'name', 17 | 'name' => 'Author Name', 18 | 'type' => 'Symbol' 19 | } 20 | ] 21 | }, 22 | { 23 | 'id' => 'post', 24 | 'name' => 'Post', 25 | 'displayField' => 'title', 26 | 'fields' => [ 27 | { 28 | 'id' => 'title', 29 | 'name' => 'Post Title', 30 | 'type' => 'Symbol' 31 | }, 32 | { 33 | 'id' => 'content', 34 | 'name' => 'Content', 35 | 'type' => 'Text' 36 | }, 37 | { 38 | 'id' => 'author', 39 | 'name' => 'Author', 40 | 'type' => 'Link', 41 | 'linkType' => 'Entry' 42 | } 43 | ] 44 | } 45 | ] 46 | end 47 | 48 | def entries 49 | { 50 | 'author' => [ 51 | { 52 | 'id' => 'dan_brown', 53 | 'name' => 'Dan Brown' 54 | }, 55 | { 56 | 'id' => 'pablo_neruda', 57 | 'name' => 'Pablo Neruda' 58 | } 59 | ], 60 | 'post' => [ 61 | { 62 | 'id' => 'inferno', 63 | 'title' => 'Inferno', 64 | 'content' => 'Inferno is the last book in Dan Brown\'s collection...', 65 | 'author' => Links::Entry.new('dan_brown') 66 | }, 67 | { 68 | 'id' => 'alturas', 69 | 'title' => 'Alturas de Macchu Picchu', 70 | 'content' => 'Alturas de Macchu Picchu is one of Pablo Neruda\'s most famous poetry books...', 71 | 'author' => Links::Entry.new('pablo_neruda') 72 | } 73 | ] 74 | } 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/issue_22.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "1VX60jHERm6yuem8I2agWG", 6 | "name": "yolo", 7 | "displayField": "title", 8 | "fields": [ 9 | { 10 | "id": "title", 11 | "name": "title", 12 | "type": "Symbol" 13 | }, 14 | { 15 | "id": "assets", 16 | "name": "assets", 17 | "type": "Array", 18 | "items": { 19 | "type": "Link", 20 | "linkType": "Asset" 21 | } 22 | } 23 | ] 24 | } 25 | ], 26 | "assets": [ 27 | { 28 | "id": "2cMU9UUngYMqAEmw4Cm8Iw", 29 | "title": "Screen Shot 2015-11-18 at 11.42.12", 30 | "file": { 31 | "filename": "Screen Shot 2015-11-18 at 11.42.12", 32 | "url": "https://images.contentful.com/o2n5bdfcee61/2cMU9UUngYMqAEmw4Cm8Iw/466d36d31bb59d487115b7be995438e1/Screen_Shot_2015-11-18_at_11.42.12.png" 33 | } 34 | }, 35 | { 36 | "id": "4D5sv49qaAoMIkWgAW8g0I", 37 | "title": "Screen Shot 2015-11-16 at 21.37.19", 38 | "file": { 39 | "filename": "Screen Shot 2015-11-16 at 21.37.19", 40 | "url": "https://images.contentful.com/o2n5bdfcee61/4D5sv49qaAoMIkWgAW8g0I/b61adc1bc26a74ac9b65e19365747cf9/Screen_Shot_2015-11-16_at_21.37.19.png" 41 | } 42 | }, 43 | { 44 | "id": "4eX9aQsDdeqweg6Si00KaW", 45 | "title": "Screen Shot 2015-11-17 at 17.57.00", 46 | "file": { 47 | "filename": "Screen Shot 2015-11-17 at 17.57.00", 48 | "url": "https://images.contentful.com/o2n5bdfcee61/4eX9aQsDdeqweg6Si00KaW/16646165346d59974fbfd4e0223ea89e/Screen_Shot_2015-11-17_at_17.57.00.png" 49 | } 50 | }, 51 | { 52 | "id": "7i1Ck3BG4o4KeIEUGosEeU", 53 | "title": "Screen Shot 2015-11-17 at 18.04.02", 54 | "file": { 55 | "filename": "Screen Shot 2015-11-17 at 18.04.02", 56 | "url": "https://images.contentful.com/o2n5bdfcee61/7i1Ck3BG4o4KeIEUGosEeU/4db1a207ec69f99f0d795c48a59cbcd1/Screen_Shot_2015-11-17_at_18.04.02.png" 57 | } 58 | } 59 | ], 60 | "entries": { 61 | "1VX60jHERm6yuem8I2agWG": [ 62 | { 63 | "sys": { 64 | "id": "5nQB3z8RgW8CUG6uGyCsEw" 65 | }, 66 | "fields": { 67 | "title": "yolo", 68 | "assets": [ 69 | { 70 | "linkType": "Asset", 71 | "id": "4eX9aQsDdeqweg6Si00KaW" 72 | }, 73 | { 74 | "linkType": "Asset", 75 | "id": "4D5sv49qaAoMIkWgAW8g0I" 76 | } 77 | ] 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/commands/base.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'contentful/management' 3 | require 'contentful/bootstrap/server' 4 | require 'contentful/bootstrap/support' 5 | 6 | module Contentful 7 | module Bootstrap 8 | module Commands 9 | class Base 10 | attr_reader :space, :token, :options, :quiet, :no_input 11 | 12 | def initialize(token, space, options = {}) 13 | trigger_oauth = options.fetch(:trigger_oauth, true) 14 | 15 | @token = token 16 | @options = options 17 | @quiet = options.fetch(:quiet, false) 18 | @no_input = options.fetch(:no_input, false) 19 | 20 | configuration if trigger_oauth 21 | client if trigger_oauth 22 | @space = space 23 | end 24 | 25 | def run 26 | raise 'must implement' 27 | end 28 | 29 | def client 30 | @client ||= ::Contentful::Management::Client.new( 31 | @token.read, 32 | default_locale: options.fetch(:locale, 'en-US'), 33 | raise_errors: true, 34 | application_name: 'bootstrap', 35 | application_version: ::Contentful::Bootstrap::VERSION 36 | ) 37 | end 38 | 39 | protected 40 | 41 | def output(text = nil) 42 | Support.output(text, @quiet) 43 | end 44 | 45 | private 46 | 47 | def configuration 48 | if @token.present? 49 | output 'OAuth token found, moving on!' 50 | return 51 | end 52 | 53 | Support.input('OAuth Token not found, do you want to create a new configuration file? (Y/n): ', no_input) do |answer| 54 | if answer.casecmp('n').zero? 55 | output 'Exiting!' 56 | exit 57 | end 58 | end 59 | 60 | raise 'OAuth token required to proceed' if no_input 61 | 62 | output "Configuration will be saved on #{@token.filename}" 63 | output 'A new tab on your browser will open for requesting OAuth permissions' 64 | token_server 65 | output 66 | end 67 | 68 | def token_server 69 | Support.silence_stderr do # Don't show any WEBrick related stuff 70 | server = Contentful::Bootstrap::Server.new(@token) 71 | 72 | server.start 73 | 74 | sleep(1) until server.running? # Wait for Server Init 75 | 76 | Net::HTTP.get(URI('http://localhost:5123')) 77 | 78 | sleep(1) until @token.present? # Wait for User to do OAuth cycle 79 | 80 | server.stop 81 | end 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2018-04-17 14:00:32 +0200 using RuboCop version 0.55.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | Documentation: 10 | Enabled: false 11 | 12 | Metrics/AbcSize: 13 | Max: 62 14 | 15 | Metrics/BlockLength: 16 | Max: 39 17 | CountComments: false 18 | 19 | Metrics/ClassLength: 20 | Max: 176 21 | CountComments: false 22 | 23 | Metrics/CyclomaticComplexity: 24 | Max: 7 25 | 26 | Metrics/LineLength: 27 | Max: 167 28 | Exclude: 29 | - 'lib/contentful/bootstrap/templates/blog.rb' 30 | - 'lib/contentful/bootstrap/templates/catalogue.rb' 31 | - 'lib/contentful/bootstrap/templates/gallery.rb' 32 | - 'bin/contentful_bootstrap' 33 | 34 | Metrics/MethodLength: 35 | Max: 88 36 | CountComments: false 37 | 38 | Metrics/ParameterLists: 39 | Max: 8 40 | 41 | Metrics/PerceivedComplexity: 42 | Max: 8 43 | 44 | Naming/MethodName: 45 | EnforcedStyle: snake_case 46 | Exclude: 47 | - 'lib/contentful/bootstrap/server.rb' 48 | 49 | # Offense count: 6 50 | Style/Documentation: 51 | Exclude: 52 | - 'spec/**/*' 53 | - 'test/**/*' 54 | - 'lib/contentful/bootstrap/commands.rb' 55 | - 'lib/contentful/bootstrap/constants.rb' 56 | - 'lib/contentful/bootstrap/generator.rb' 57 | - 'lib/contentful/bootstrap/server.rb' 58 | - 'lib/contentful/bootstrap/support.rb' 59 | - 'lib/contentful/bootstrap/templates/base.rb' 60 | - 'lib/contentful/bootstrap/templates/blog.rb' 61 | - 'lib/contentful/bootstrap/templates/catalogue.rb' 62 | - 'lib/contentful/bootstrap/templates/gallery.rb' 63 | - 'lib/contentful/bootstrap/templates/json_template.rb' 64 | - 'lib/contentful/bootstrap/templates/links/asset.rb' 65 | - 'lib/contentful/bootstrap/templates/links/base.rb' 66 | - 'lib/contentful/bootstrap/templates/links/entry.rb' 67 | - 'lib/contentful/bootstrap/token.rb' 68 | - 'lib/contentful/bootstrap/version.rb' 69 | - 'lib/contentful/bootstrap/command_runner.rb' 70 | - 'lib/contentful/bootstrap/commands/base.rb' 71 | - 'lib/contentful/bootstrap/commands/create_space.rb' 72 | - 'lib/contentful/bootstrap/commands/generate_json.rb' 73 | - 'lib/contentful/bootstrap/commands/generate_token.rb' 74 | - 'lib/contentful/bootstrap/commands/update_space.rb' 75 | 76 | Style/FormatString: 77 | Exclude: 78 | - 'lib/contentful/bootstrap/commands/create_space.rb' 79 | 80 | Style/FormatStringToken: 81 | EnforcedStyle: unannotated 82 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/check_staging_environment_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cdn.contentful.com/spaces/9utsm1g0t7f5/environments/staging/entries 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | X-Contentful-User-Agent: 11 | - sdk contentful.rb/2.6.0; platform ruby/2.5.1; os macOS/16; 12 | Authorization: 13 | - Bearer a67d4d9011f6d6c1dfe4169d838114d3d3849ab6df6fb1d322cf3ee91690fae4 14 | Content-Type: 15 | - application/vnd.contentful.delivery.v1+json 16 | Accept-Encoding: 17 | - gzip 18 | Connection: 19 | - close 20 | Host: 21 | - cdn.contentful.com 22 | User-Agent: 23 | - http.rb/2.2.2 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Access-Control-Allow-Headers: 30 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature 31 | Access-Control-Allow-Methods: 32 | - GET,HEAD,OPTIONS 33 | Access-Control-Allow-Origin: 34 | - "*" 35 | Access-Control-Expose-Headers: 36 | - Etag 37 | Access-Control-Max-Age: 38 | - '86400' 39 | Cache-Control: 40 | - max-age=0 41 | Content-Encoding: 42 | - gzip 43 | Content-Type: 44 | - application/vnd.contentful.delivery.v1+json 45 | Etag: 46 | - W/"10a24d49d1e7c0a015867c7b311521f5" 47 | Server: 48 | - Contentful 49 | X-Content-Type-Options: 50 | - nosniff 51 | X-Contentful-Request-Id: 52 | - '089705eade747eb544acfdd4be14dd87' 53 | Content-Length: 54 | - '408' 55 | Accept-Ranges: 56 | - bytes 57 | Date: 58 | - Wed, 18 Apr 2018 12:13:17 GMT 59 | Via: 60 | - 1.1 varnish 61 | Age: 62 | - '0' 63 | Connection: 64 | - close 65 | X-Served-By: 66 | - cache-hhn1535-HHN 67 | X-Cache: 68 | - MISS 69 | X-Cache-Hits: 70 | - '0' 71 | X-Timer: 72 | - S1524053597.392641,VS0,VE255 73 | Vary: 74 | - Accept-Encoding 75 | body: 76 | encoding: ASCII-8BIT 77 | string: !binary |- 78 | H4sIAAAAAAAAA91UTWvDMAy991cEn9diZ0k/ciul7NBB2dLtsDFGSNximthZ7BZC6X+fP5LUDYM0x84nS0h6z0+STwPHAbzkIHBO8ioNUeZYWmBeFFEJpO/8oGIEE1Eq/a62+J7k0oDaSElGhLQQNDYROFMFP3VBU7aFopF4HsUKqo4wTouLcmhnzemZ0D1QmJcj0el+U3EOdcVWAEnUc2YHwTO0g2Ky9dWr6nNu7vqd5gCTs2Xs+5AnkbBrNmSWVBSlBQbiAsvQZK60AC5E0yH0hmi6QSjwxoGPRu7Y/7ATTO0eCZgeScFohqnC6NbNPIOLaEforq3LraIuLdBO4Qp8JJwwqsbB0jNmVEjSVZ+6id/KbWHV/bPtsoXd3U5ZHKV66DEdvoV1QjMQYEtwmlx2RA0JoFGmUzaYC6fq5NU0GGoqJGQZdioJmuJaygrijndkXL4nxeHVe+KrtUtW65+X0F2EthB1L/vsizsL/McRnLh99gWOoAevEu5qX/TPWv0//3xf+u+JlOZrcB78AiLrrpavBgAA 79 | http_version: 80 | recorded_at: Wed, 18 Apr 2018 12:13:17 GMT 81 | recorded_with: VCR 2.9.3 82 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/commands/generate_token_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class ResponseDouble 4 | def object 5 | {'accessToken' => 'foo'} 6 | end 7 | end 8 | 9 | class ErrorDouble < Contentful::Management::Error 10 | def initialize 11 | end 12 | 13 | def object 14 | self.class.new 15 | end 16 | end 17 | 18 | describe Contentful::Bootstrap::Commands::GenerateToken do 19 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 20 | let(:token) { Contentful::Bootstrap::Token.new path } 21 | subject { described_class.new token, 'foo', token_name: 'bar', trigger_oauth: false, quiet: true } 22 | let(:space_double) { SpaceDouble.new } 23 | 24 | describe 'instance methods' do 25 | before do 26 | subject.send(:client) 27 | end 28 | 29 | describe '#run' do 30 | it 'fetches space from api if space is a string' do 31 | allow(Contentful::Bootstrap::Support).to receive(:gets) { 'n' } 32 | expect_any_instance_of(Contentful::Management::ClientSpaceMethodsFactory).to receive(:find).with('foo') { space_double } 33 | 34 | subject.run 35 | end 36 | 37 | it 'uses space if space is not a string' do 38 | allow(Contentful::Bootstrap::Support).to receive(:gets) { 'n' } 39 | expect_any_instance_of(Contentful::Management::ClientSpaceMethodsFactory).not_to receive(:find).with('foo') { space_double } 40 | subject.instance_variable_set(:@space, space_double) 41 | 42 | subject.run 43 | end 44 | 45 | it 'returns access token' do 46 | allow(Contentful::Bootstrap::Support).to receive(:gets) { 'n' } 47 | allow_any_instance_of(Contentful::Management::ClientSpaceMethodsFactory).to receive(:find).with('foo') { space_double } 48 | expect(space_double).to receive(:api_keys).and_call_original 49 | 50 | expect(subject.run).to eq 'random_api_key' 51 | end 52 | 53 | it 'fails if API returns an error' do 54 | allow(Contentful::Bootstrap::Support).to receive(:gets) { 'n' } 55 | allow_any_instance_of(Contentful::Management::ClientSpaceMethodsFactory).to receive(:find).with('foo') { space_double } 56 | expect(space_double).to receive(:api_keys).and_raise(ErrorDouble.new) 57 | 58 | expect { subject.run }.to raise_error ErrorDouble 59 | end 60 | 61 | it 'token gets written if user input is other than no' do 62 | allow(Contentful::Bootstrap::Support).to receive(:gets) { 'y' } 63 | allow_any_instance_of(Contentful::Management::ClientSpaceMethodsFactory).to receive(:find).with('foo') { space_double } 64 | expect(space_double).to receive(:api_keys).and_call_original 65 | 66 | expect(token).to receive(:write_access_token).with('foobar', 'random_api_key') 67 | expect(token).to receive(:write_space_id).with('foobar', 'foobar') 68 | 69 | subject.run 70 | end 71 | end 72 | end 73 | 74 | describe 'attributes' do 75 | it ':token_name' do 76 | expect(subject.token_name).to eq 'bar' 77 | end 78 | end 79 | 80 | describe 'integration' do 81 | before do 82 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 83 | end 84 | 85 | it 'generates a token for a given space' do 86 | command = described_class.new token, 'zred3m25k5em', token_name: 'foo', quiet: true 87 | 88 | vcr('generate_token') { 89 | command.run 90 | } 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/generate_json_content_types_only.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cdn.contentful.com/spaces/wl1z0pal05vy/environments/master/content_types 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | User-Agent: 11 | - RubyContentfulGem/0.7.0 12 | Authorization: 13 | - Bearer foobar 14 | Content-Type: 15 | - application/vnd.contentful.delivery.v1+json 16 | Accept-Encoding: 17 | - gzip 18 | Connection: 19 | - close 20 | Host: 21 | - cdn.contentful.com 22 | response: 23 | status: 24 | code: 200 25 | message: OK 26 | headers: 27 | Access-Control-Allow-Headers: 28 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation 29 | Access-Control-Allow-Methods: 30 | - GET,HEAD,OPTIONS 31 | Access-Control-Allow-Origin: 32 | - "*" 33 | Access-Control-Expose-Headers: 34 | - Etag 35 | Access-Control-Max-Age: 36 | - '86400' 37 | Cache-Control: 38 | - max-age=0 39 | Content-Encoding: 40 | - gzip 41 | Content-Type: 42 | - application/vnd.contentful.delivery.v1+json 43 | Server: 44 | - nginx 45 | X-Contentful-Request-Id: 46 | - c40-1967993529 47 | Content-Length: 48 | - '986' 49 | Accept-Ranges: 50 | - bytes 51 | Date: 52 | - Thu, 19 Nov 2015 19:42:11 GMT 53 | Via: 54 | - 1.1 varnish 55 | Age: 56 | - '0' 57 | Connection: 58 | - close 59 | X-Served-By: 60 | - cache-iad2140-IAD 61 | X-Cache: 62 | - MISS 63 | X-Cache-Hits: 64 | - '0' 65 | Vary: 66 | - Accept-Encoding 67 | body: 68 | encoding: ASCII-8BIT 69 | string: !binary |- 70 | H4sIAAAAAAAAA91XS2/iMBC+91dE2euWJuFRyo3tUqnbh7oiVR+rHkxiqEVe 71 | 2E7ZtOp/XztOwE6MCGy1KzUHhGfsGdsz883ntwPDMElGzIHxxv6yAc0SyEbm 72 | EGOQmUz2/pXPoTEFAZO38xGZo4QNrHwQoBBRNrItMUYUhtzgr9ygMMumTREM 73 | /LVc1uWOIxDmjm9w7KceNfIxd1B+JvK5PhH665q63LkLf1N1IYaLFGHIl1Oc 74 | wpXN/GjiK7epbGUcpDPdFkhNXvoeZ+EkDvi1iW+bh++QeBglFMWRzlFx1o2z 75 | lCM3djpGr/DIZXE+Oo2DGOscEzaFG/fqE/Y96nkIZtqAorpCTUI1CYrskgMm 76 | pe0liubKgfIEjeb8uHlaEwLpOj4svRtfmwtmRHdXtCbff/vV/Nllf6eAwlmM 77 | EdTu0tug3X+v2656FFGcVWPxAgLkA57vKhTUq1BI8uCdxhGFES1iKHClrLBi 78 | Wu9+mbhgjNC5M5w7d0srRr0FkAMtJj5JgMIl6/Dzkaxtnhg3GHnazE7qivK6 79 | r9NwAnFzoPiGQeTrsm9SV5Q+agFSKkGEp3H2/0xBRBFVA1pA8kKrK7dxzoI3 80 | 2+Ws44tb3UnJPFXFpYNq0WwD3eELQAGYBNAAlUYhOswSTghrYg2dFTf4VKJU 81 | tZGtzCh9lmebSRLAEqdsvUUmy91YiHaAt3FuUQZMtrg4VWC/WgkIrO5L3tfL 82 | T5vnKxogl97abGHSuVlM729/LEf9xfjhIr2ybnvL/pXk3sTwBRHe3Bg9sKX1 83 | HoYMj/wh5w2mY9mdQ9s5tC3X7gzs40HXah332o+yoTRhqCEt6B5a9mGbLWgP 84 | 2vag3W/1T04ey2OtEsD0pf7KPLnPiBieQBODp4/hwymKIDHoMzQIIwceTTE0 85 | 4qkBjKL5Ggx5JBwzfUSSAGRnnM5U+Uh+rYX3nYnPaRwmIMo2Eh9P6P8F8bmM 86 | Z7GuBIOavBnWVLrutgLdyHdEJhcXsXFWuaecCDYGuDtd1e8DCFtIpbtElDI4 87 | 1FBbqlPti3KjkKGczgusK/b1cfMcR9D4ovOScJWqKL2Ip8UurK6K8Aot2gjA 88 | as/8fPBLzl7dx8k4vep78WiZzeDtg8L9ZPTtNQNfpzuwei3HaQy+tj1w+i3r 89 | xPlg8M1pjXG0AuEQROkU5OiMtyGyDJR/hcguooGW2DEiVFUooKPk9r6vz3PW 90 | qHSVhWry/wLC4j2R7YrCG6u1eL6sCebnK9gNjxSpNCW+1GlYsb1Bu93qdvpN 91 | 6RKr2M5xq9M7/uCKLelS8dCU4lhlTKJ6RGWy36eD94M/hymdIIgSAAA= 92 | http_version: 93 | recorded_at: Thu, 19 Nov 2015 19:42:11 GMT 94 | recorded_with: VCR 2.9.3 95 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/gallery.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/templates/base' 2 | require 'contentful/bootstrap/templates/links' 3 | 4 | module Contentful 5 | module Bootstrap 6 | module Templates 7 | class Gallery < Base 8 | def content_types 9 | [ 10 | { 11 | 'id' => 'author', 12 | 'name' => 'Author', 13 | 'displayField' => 'name', 14 | 'fields' => [ 15 | { 16 | 'name' => 'Name', 17 | 'id' => 'name', 18 | 'type' => 'Symbol' 19 | } 20 | ] 21 | }, 22 | { 23 | 'id' => 'image', 24 | 'name' => 'Image', 25 | 'displayField' => 'title', 26 | 'fields' => [ 27 | { 28 | 'id' => 'title', 29 | 'name' => 'Title', 30 | 'type' => 'Symbol' 31 | }, 32 | { 33 | 'id' => 'photo', 34 | 'name' => 'Photo', 35 | 'type' => 'Link', 36 | 'linkType' => 'Asset' 37 | } 38 | ] 39 | }, 40 | { 41 | 'id' => 'gallery', 42 | 'name' => 'Gallery', 43 | 'displayField' => 'title', 44 | 'fields' => [ 45 | { 46 | 'id' => 'title', 47 | 'name' => 'Title', 48 | 'type' => 'Symbol' 49 | }, 50 | { 51 | 'id' => 'author', 52 | 'name' => 'Author', 53 | 'type' => 'Link', 54 | 'linkType' => 'Entry' 55 | }, 56 | { 57 | 'id' => 'images', 58 | 'name' => 'Images', 59 | 'type' => 'Array', 60 | 'items' => { 61 | 'type' => 'Link', 62 | 'linkType' => 'Entry' 63 | } 64 | } 65 | ] 66 | } 67 | ] 68 | end 69 | 70 | def assets 71 | [ 72 | { 73 | 'id' => 'pie', 74 | 'title' => 'Pie in the Sky', 75 | 'file' => create_file('pie.jpg', 'https://c2.staticflickr.com/6/5245/5335909339_d307a7cbcf_b.jpg') 76 | }, 77 | { 78 | 'id' => 'flower', 79 | 'title' => 'The Flower', 80 | 'file' => create_file('flower.jpg', 'http://c2.staticflickr.com/4/3922/15045568809_b24591e318_b.jpg') 81 | } 82 | ] 83 | end 84 | 85 | def entries 86 | { 87 | 'author' => [ 88 | { 89 | 'id' => 'dave', 90 | 'name' => 'Dave' 91 | } 92 | ], 93 | 'image' => [ 94 | { 95 | 'id' => 'pie_entry', 96 | 'title' => 'A Pie in the Sky', 97 | 'photo' => Links::Asset.new('pie') 98 | }, 99 | { 100 | 'id' => 'flower_entry', 101 | 'title' => 'The Flower', 102 | 'photo' => Links::Asset.new('flower') 103 | } 104 | ], 105 | 'gallery' => [ 106 | { 107 | 'id' => 'gallery', 108 | 'title' => 'Photo Gallery', 109 | 'author' => Links::Entry.new('dave'), 110 | 'images' => [Links::Entry.new('pie_entry'), Links::Entry.new('flower_entry')] 111 | } 112 | ] 113 | } 114 | end 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/server.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | require 'webrick' 3 | require 'launchy' 4 | require 'contentful/bootstrap/constants' 5 | require 'contentful/bootstrap/token' 6 | 7 | module Contentful 8 | module Bootstrap 9 | class OAuthEchoView 10 | def render 11 | <<-JS 12 | 13 | 14 | 15 | 16 | 22 | 23 | JS 24 | end 25 | end 26 | 27 | class ThanksView 28 | def render 29 | <<-HTML 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |

Contentful Bootstrap

38 |

Thanks! The OAuth Token has been generated

39 |

The Space you specified will now start to create. You can close this window freely

40 |
41 |
42 | 43 | HTML 44 | end 45 | end 46 | 47 | class IndexController < WEBrick::HTTPServlet::AbstractServlet 48 | def do_GET(_request, response) 49 | client_id = Contentful::Bootstrap::Constants::OAUTH_APP_ID 50 | redirect_uri = Contentful::Bootstrap::Constants::OAUTH_CALLBACK_URL 51 | scope = 'content_management_manage' 52 | Launchy.open("https://be.contentful.com/oauth/authorize?response_type=token&client_id=#{client_id}&redirect_uri=#{redirect_uri}&scope=#{scope}") 53 | response.status = 200 54 | response.body = '' 55 | end 56 | end 57 | 58 | class OAuthCallbackController < WEBrick::HTTPServlet::AbstractServlet 59 | def do_GET(_request, response) 60 | response.status = 200 61 | response.content_type = 'text/html' 62 | response.body = OAuthEchoView.new.render 63 | end 64 | end 65 | 66 | class SaveTokenController < WEBrick::HTTPServlet::AbstractServlet 67 | attr_reader :token 68 | 69 | def initialize(server, token, *options) 70 | super(server, options) 71 | @token = token 72 | end 73 | 74 | def do_GET(request, response) 75 | @token.write(request.query['token']) 76 | response.status = 200 77 | response.content_type = 'text/html' 78 | response.body = ThanksView.new.render 79 | end 80 | end 81 | 82 | class Server 83 | attr_reader :server 84 | 85 | def initialize(token) 86 | @server = WEBrick::HTTPServer.new(Port: 5123) 87 | @server.mount '/', IndexController 88 | @server.mount '/oauth_callback', OAuthCallbackController 89 | @server.mount '/save_token', SaveTokenController, token 90 | end 91 | 92 | def start 93 | Thread.new { @server.start } 94 | end 95 | 96 | def stop 97 | @server.shutdown 98 | end 99 | 100 | def running? 101 | @server.status != :Stop 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/wl1z0pal05vy_content_types_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "2PqfXUJwE8qSYKuM0U6w8M", 6 | "name": "Product", 7 | "displayField": "productName", 8 | "fields": [ 9 | { 10 | "id": "productName", 11 | "name": "Product name", 12 | "type": "Text" 13 | }, 14 | { 15 | "id": "slug", 16 | "name": "Slug", 17 | "type": "Symbol" 18 | }, 19 | { 20 | "id": "productDescription", 21 | "name": "Description", 22 | "type": "Text" 23 | }, 24 | { 25 | "id": "sizetypecolor", 26 | "name": "Size/Type/Color", 27 | "type": "Symbol" 28 | }, 29 | { 30 | "id": "image", 31 | "name": "Image", 32 | "type": "Array", 33 | "items": { 34 | "type": "Link", 35 | "linkType": "Asset" 36 | } 37 | }, 38 | { 39 | "id": "tags", 40 | "name": "Tags", 41 | "type": "Array", 42 | "items": { 43 | "type": "Symbol" 44 | } 45 | }, 46 | { 47 | "id": "categories", 48 | "name": "Categories", 49 | "type": "Array", 50 | "items": { 51 | "type": "Link", 52 | "linkType": "Entry" 53 | } 54 | }, 55 | { 56 | "id": "price", 57 | "name": "Price", 58 | "type": "Number" 59 | }, 60 | { 61 | "id": "brand", 62 | "name": "Brand", 63 | "type": "Link", 64 | "linkType": "Entry" 65 | }, 66 | { 67 | "id": "quantity", 68 | "name": "Quantity", 69 | "type": "Integer" 70 | }, 71 | { 72 | "id": "sku", 73 | "name": "SKU", 74 | "type": "Symbol" 75 | }, 76 | { 77 | "id": "website", 78 | "name": "Available at", 79 | "type": "Symbol" 80 | } 81 | ] 82 | }, 83 | { 84 | "id": "6XwpTaSiiI2Ak2Ww0oi6qa", 85 | "name": "Category", 86 | "displayField": "title", 87 | "fields": [ 88 | { 89 | "id": "title", 90 | "name": "Title", 91 | "type": "Text" 92 | }, 93 | { 94 | "id": "icon", 95 | "name": "Icon", 96 | "type": "Link", 97 | "linkType": "Asset" 98 | }, 99 | { 100 | "id": "categoryDescription", 101 | "name": "Description", 102 | "type": "Text" 103 | } 104 | ] 105 | }, 106 | { 107 | "id": "sFzTZbSuM8coEwygeUYes", 108 | "name": "Brand", 109 | "displayField": "companyName", 110 | "fields": [ 111 | { 112 | "id": "companyName", 113 | "name": "Company name", 114 | "type": "Text" 115 | }, 116 | { 117 | "id": "logo", 118 | "name": "Logo", 119 | "type": "Link", 120 | "linkType": "Asset" 121 | }, 122 | { 123 | "id": "companyDescription", 124 | "name": "Description", 125 | "type": "Text" 126 | }, 127 | { 128 | "id": "website", 129 | "name": "Website", 130 | "type": "Symbol" 131 | }, 132 | { 133 | "id": "twitter", 134 | "name": "Twitter", 135 | "type": "Symbol" 136 | }, 137 | { 138 | "id": "email", 139 | "name": "Email", 140 | "type": "Symbol" 141 | }, 142 | { 143 | "id": "phone", 144 | "name": "Phone #", 145 | "type": "Array", 146 | "items": { 147 | "type": "Symbol" 148 | } 149 | } 150 | ] 151 | } 152 | ], 153 | "assets": [ 154 | ], 155 | "entries": { 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/commands/generate_json_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Commands::GenerateJson do 4 | subject { described_class.new('foo', 'bar') } 5 | 6 | describe 'instance methods' do 7 | describe '#run' do 8 | it 'exits if access_token is nil' do 9 | subject.instance_variable_set(:@access_token, nil) 10 | subject.instance_variable_set(:@quiet, true) 11 | 12 | expect { subject.run }.to raise_error SystemExit 13 | end 14 | 15 | it 'calls generator' do 16 | allow(subject).to receive(:write).with('json') 17 | 18 | expect_any_instance_of(Contentful::Bootstrap::Generator).to receive(:generate_json) { 'json' } 19 | 20 | subject.run 21 | end 22 | 23 | it 'writes json' do 24 | vcr('generate_json') { 25 | subject.instance_variable_set(:@space_id, 'wl1z0pal05vy') 26 | subject.instance_variable_set(:@access_token, '48d7db7d4cd9d09df573c251d456f4acc72141b92f36e57f8684b36cf5cfff6e') 27 | subject.instance_variable_set(:@quiet, true) 28 | 29 | json_fixture('wl1z0pal05vy') { |json| 30 | expect(subject).to receive(:write).with(JSON.pretty_generate(json)) 31 | } 32 | 33 | subject.run 34 | } 35 | end 36 | 37 | it 'can use the preview api' do 38 | vcr('generate_json_preview') { 39 | subject.instance_variable_set(:@space_id, 'f3abi4dqvrhg') 40 | subject.instance_variable_set(:@access_token, '06c28ef41823bb636714dfd812066fa026a49e95041a0e94903d6cf016bba50e') 41 | subject.instance_variable_set(:@use_preview, true) 42 | subject.instance_variable_set(:@quiet, true) 43 | 44 | json_fixture('f3abi4dqvrhg_preview') { |json| 45 | expect(subject).to receive(:write).with(JSON.pretty_generate(json)) 46 | } 47 | 48 | subject.run 49 | } 50 | end 51 | 52 | describe 'specific content types' do 53 | it 'can select a single content type' do 54 | vcr('generate_json_single_ct') { 55 | subject.instance_variable_set(:@space_id, 'cfexampleapi') 56 | subject.instance_variable_set(:@access_token, 'b4c0n73n7fu1') 57 | subject.instance_variable_set(:@content_type_ids, ['cat']) 58 | subject.instance_variable_set(:@quiet, true) 59 | 60 | json_fixture('cfexampleapi_cat') { |json| 61 | expect(subject).to receive(:write).with(JSON.pretty_generate(json)) 62 | } 63 | 64 | subject.run 65 | } 66 | end 67 | 68 | it 'can select multiple content types' do 69 | vcr('generate_json_multi_ct') { 70 | subject.instance_variable_set(:@space_id, 'cfexampleapi') 71 | subject.instance_variable_set(:@access_token, 'b4c0n73n7fu1') 72 | subject.instance_variable_set(:@content_type_ids, ['cat', 'human']) 73 | subject.instance_variable_set(:@quiet, true) 74 | 75 | json_fixture('cfexampleapi_cat_human') { |json| 76 | expect(subject).to receive(:write).with(JSON.pretty_generate(json)) 77 | } 78 | 79 | subject.run 80 | } 81 | end 82 | end 83 | end 84 | 85 | describe '#write' do 86 | it 'outputs json to stoud if no filename provided' do 87 | expect { subject.write('json') }.to output("json\n").to_stdout 88 | end 89 | 90 | it 'writes to file if filename provided' do 91 | subject.instance_variable_set(:@filename, 'foo') 92 | 93 | expect(::File).to receive(:write).with('foo', 'json') 94 | 95 | expect { subject.write('json') }.to output("Saving JSON template to 'foo'\n").to_stdout 96 | end 97 | end 98 | end 99 | 100 | describe 'attributes' do 101 | it ':space_id' do 102 | expect(subject.space_id).to eq 'foo' 103 | end 104 | 105 | it ':access_token' do 106 | expect(subject.access_token).to eq 'bar' 107 | end 108 | 109 | it ':filename' do 110 | expect(subject.filename).to eq nil 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/cfexampleapi_cat.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "cat", 6 | "name": "Cat", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Text" 13 | }, 14 | { 15 | "id": "likes", 16 | "name": "Likes", 17 | "type": "Array", 18 | "items": { 19 | "type": "Symbol" 20 | } 21 | }, 22 | { 23 | "id": "color", 24 | "name": "Color", 25 | "type": "Symbol" 26 | }, 27 | { 28 | "id": "bestFriend", 29 | "name": "Best Friend", 30 | "type": "Link", 31 | "linkType": "Entry" 32 | }, 33 | { 34 | "id": "birthday", 35 | "name": "Birthday", 36 | "type": "Date" 37 | }, 38 | { 39 | "id": "lifes", 40 | "name": "Lifes left", 41 | "type": "Integer" 42 | }, 43 | { 44 | "id": "lives", 45 | "name": "Lives left", 46 | "type": "Integer" 47 | }, 48 | { 49 | "id": "image", 50 | "name": "Image", 51 | "type": "Link", 52 | "linkType": "Asset" 53 | } 54 | ] 55 | } 56 | ], 57 | "assets": [ 58 | { 59 | "id": "1x0xpXu4pSGS4OukSyWGUK", 60 | "title": "Doge", 61 | "file": { 62 | "filename": "doge", 63 | "url": "https://images.contentful.com/cfexampleapi/1x0xpXu4pSGS4OukSyWGUK/cc1239c6385428ef26f4180190532818/doge.jpg" 64 | } 65 | }, 66 | { 67 | "id": "happycat", 68 | "title": "Happy Cat", 69 | "file": { 70 | "filename": "happycatw", 71 | "url": "https://images.contentful.com/cfexampleapi/3MZPnjZTIskAIIkuuosCss/382a48dfa2cb16c47aa2c72f7b23bf09/happycatw.jpg" 72 | } 73 | }, 74 | { 75 | "id": "jake", 76 | "title": "Jake", 77 | "file": { 78 | "filename": "jake", 79 | "url": "https://images.contentful.com/cfexampleapi/4hlteQAXS8iS0YCMU6QMWg/2a4d826144f014109364ccf5c891d2dd/jake.png" 80 | } 81 | }, 82 | { 83 | "id": "nyancat", 84 | "title": "Nyan Cat", 85 | "file": { 86 | "filename": "Nyan_cat_250px_frame", 87 | "url": "https://images.contentful.com/cfexampleapi/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png" 88 | } 89 | } 90 | ], 91 | "entries": { 92 | "cat": [ 93 | { 94 | "sys": { 95 | "id": "nyancat" 96 | }, 97 | "fields": { 98 | "name": "Nyan Cat", 99 | "likes": [ 100 | "rainbows", 101 | "fish" 102 | ], 103 | "color": "rainbow", 104 | "bestFriend": { 105 | "linkType": "Entry", 106 | "id": "happycat" 107 | }, 108 | "birthday": "2011-04-04T22:00:00+00:00", 109 | "lives": 1337, 110 | "image": { 111 | "linkType": "Asset", 112 | "id": "nyancat" 113 | } 114 | } 115 | }, 116 | { 117 | "sys": { 118 | "id": "happycat" 119 | }, 120 | "fields": { 121 | "name": "Happy Cat", 122 | "likes": [ 123 | "cheezburger" 124 | ], 125 | "color": "gray", 126 | "bestFriend": { 127 | "linkType": "Entry", 128 | "id": "nyancat" 129 | }, 130 | "birthday": "2003-10-28T23:00:00+00:00", 131 | "lives": 1, 132 | "image": { 133 | "linkType": "Asset", 134 | "id": "happycat" 135 | } 136 | } 137 | }, 138 | { 139 | "sys": { 140 | "id": "garfield" 141 | }, 142 | "fields": { 143 | "name": "Garfield", 144 | "likes": [ 145 | "lasagna" 146 | ], 147 | "color": "orange", 148 | "birthday": "1979-06-18T23:00:00+00:00", 149 | "lives": 9 150 | } 151 | } 152 | ] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/commands/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'net/http' 3 | require 'contentful/bootstrap/commands/base' 4 | 5 | describe Contentful::Bootstrap::Commands::Base do 6 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 7 | let(:no_token_path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'no_token.ini')) } 8 | let(:token) { Contentful::Bootstrap::Token.new path } 9 | let(:non_management_token) { Contentful::Bootstrap::Token.new no_token_path } 10 | subject { described_class.new(token, 'foo', trigger_oauth: false, quiet: true) } 11 | 12 | describe 'abstract methods' do 13 | it '#run' do 14 | expect { subject.run }.to raise_error 'must implement' 15 | end 16 | end 17 | 18 | describe 'initialize' do 19 | describe 'configuration' do 20 | it 'runs configuration when trigger_oauth is true' do 21 | expect_any_instance_of(described_class).to receive(:configuration) 22 | described_class.new(token, 'foo', quiet: true) 23 | end 24 | 25 | it 'doesnt run configuration when trigger_oauth is false' do 26 | expect_any_instance_of(described_class).not_to receive(:configuration) 27 | described_class.new(token, 'foo', trigger_oauth: false, quiet: true) 28 | end 29 | end 30 | end 31 | 32 | describe 'instance methods' do 33 | it '#client' do 34 | allow_any_instance_of(described_class).to receive(:configuration) 35 | expect(Contentful::Management::Client).to receive(:new).with( 36 | token.read, 37 | default_locale: 'en-US', 38 | raise_errors: true, 39 | application_name: 'bootstrap', 40 | application_version: Contentful::Bootstrap::VERSION 41 | ) 42 | 43 | described_class.new(token, 'foo', quiet: true) 44 | end 45 | 46 | describe '#configuration' do 47 | before do 48 | allow_any_instance_of(subject.class).to receive(:client) 49 | end 50 | 51 | it 'passes if token is found' do 52 | expect { described_class.new(token, 'foo') }.to output("OAuth token found, moving on!\n").to_stdout 53 | end 54 | 55 | describe 'token not found' do 56 | it 'exits if answer is no' do 57 | expect(Contentful::Bootstrap::Support).to receive(:gets) { "n" } 58 | expect { described_class.new(non_management_token, 'foo', quiet: true) }.to raise_error SystemExit 59 | end 60 | 61 | it 'runs token_server if other answer' do 62 | expect(Contentful::Bootstrap::Support).to receive(:gets) { "y" } 63 | expect_any_instance_of(described_class).to receive(:token_server) 64 | 65 | described_class.new(non_management_token, 'foo', quiet: true) 66 | end 67 | end 68 | end 69 | 70 | it '#token_server' do 71 | allow_any_instance_of(described_class).to receive(:client) 72 | 73 | expect(Contentful::Bootstrap::Support).to receive(:gets) { "y" } 74 | expect_any_instance_of(Contentful::Bootstrap::Server).to receive(:start) {} 75 | expect_any_instance_of(Contentful::Bootstrap::Server).to receive(:running?) { true } 76 | expect(Net::HTTP).to receive(:get).with(URI('http://localhost:5123')) {} 77 | expect(non_management_token).to receive(:present?).and_return(false, true) 78 | expect_any_instance_of(Contentful::Bootstrap::Server).to receive(:stop) {} 79 | 80 | described_class.new(non_management_token, 'foo', quiet: true) 81 | end 82 | end 83 | 84 | describe 'attributes' do 85 | it ':space' do 86 | expect(subject.space).to eq 'foo' 87 | end 88 | 89 | it ':token' do 90 | expect(subject.token == Contentful::Bootstrap::Token.new(path)).to be_truthy 91 | end 92 | end 93 | 94 | describe 'additional options' do 95 | describe ':no_input' do 96 | it ':no_input will disallow all input operations' do 97 | expect_any_instance_of(described_class).not_to receive(:token_server) 98 | expect(Contentful::Bootstrap::Support).not_to receive(:gets) 99 | expect { described_class.new(non_management_token, 'foo', quiet: true, no_input: true) }.to raise_error "OAuth token required to proceed" 100 | end 101 | 102 | it 'without :no_input input operations are allowed' do 103 | expect_any_instance_of(described_class).to receive(:token_server) 104 | expect(Contentful::Bootstrap::Support).to receive(:gets) { "y" } 105 | allow(non_management_token).to receive(:read) { true } 106 | described_class.new(non_management_token, 'foo', quiet: true, no_input: false) 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/commands/create_space.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/management' 2 | require 'contentful/management/error' 3 | require 'contentful/bootstrap/templates' 4 | require 'contentful/bootstrap/commands/base' 5 | require 'contentful/bootstrap/commands/generate_token' 6 | 7 | module Contentful 8 | module Bootstrap 9 | module Commands 10 | class CreateSpace < Base 11 | attr_reader :template_name, :json_template 12 | def initialize(token, space, options = {}) 13 | @template_name = options.fetch(:template, nil) 14 | @json_template = options.fetch(:json_template, nil) 15 | @mark_processed = options.fetch(:mark_processed, false) 16 | @locale = options.fetch(:locale, 'en-US') 17 | @no_publish = options.fetch(:no_publish, false) 18 | @environment = 'master' # Can only add content to a new space through the master environment by default 19 | 20 | super(token, space, options) 21 | end 22 | 23 | def run 24 | output "Creating Space '#{@space}'" 25 | 26 | new_space = fetch_space 27 | 28 | output "Space '#{@space}' created!" 29 | output 30 | 31 | create_template(new_space) unless @template_name.nil? 32 | create_json_template(new_space) unless @json_template.nil? 33 | 34 | access_token = generate_token(new_space) 35 | output 36 | output "Space ID: '#{new_space.id}'" 37 | output "Access Token: '#{access_token}'" 38 | output 39 | output 'You can now insert those values into your configuration blocks' 40 | output "Manage your content at https://app.contentful.com/spaces/#{new_space.id}" 41 | 42 | new_space 43 | end 44 | 45 | protected 46 | 47 | def fetch_space 48 | new_space = nil 49 | begin 50 | options = { 51 | name: @space, 52 | defaultLocale: @locale 53 | } 54 | options[:organization_id] = @token.read_organization_id unless @token.read_organization_id.nil? 55 | new_space = client.spaces.create(options) 56 | rescue Contentful::Management::NotFound 57 | raise 'Organization ID is required, provide it in Configuration File' if no_input 58 | 59 | output 'Your account has multiple organizations:' 60 | output organizations.join("\n") 61 | Support.input('Please insert the Organization ID you\'d want to create the spaces for: ', no_input) do |answer| 62 | organization_id = answer 63 | @token.write_organization_id(organization_id) 64 | output 'Your Organization ID has been stored as the default organization.' 65 | new_space = client.spaces.create( 66 | name: @space, 67 | defaultLocale: @locale, 68 | organization_id: organization_id 69 | ) 70 | end 71 | end 72 | 73 | new_space 74 | end 75 | 76 | private 77 | 78 | def organizations 79 | organizations = client.organizations.all 80 | organization_ids = organizations.map do |organization| 81 | sprintf('%-30s %s', organization.name, organization.id) 82 | end 83 | organization_ids.sort 84 | end 85 | 86 | def templates 87 | { 88 | blog: Templates::Blog, 89 | gallery: Templates::Gallery, 90 | catalogue: Templates::Catalogue 91 | } 92 | end 93 | 94 | def create_template(space) 95 | if templates.key? @template_name.to_sym 96 | output "Creating Template '#{@template_name}'" 97 | 98 | templates[@template_name.to_sym].new(space, @environment, quiet).run 99 | output "Template '#{@template_name}' created!" 100 | else 101 | output "Template '#{@template_name}' not found. Valid templates are '#{templates.keys.map(&:to_s).join('\', \'')}'" 102 | end 103 | output 104 | end 105 | 106 | def create_json_template(space) 107 | if ::File.exist?(@json_template) 108 | output "Creating JSON Template '#{@json_template}'" 109 | Templates::JsonTemplate.new(space, @json_template, @environment, @mark_processed, true, quiet, false, @no_publish).run 110 | output "JSON Template '#{@json_template}' created!" 111 | else 112 | output "JSON Template '#{@json_template}' does not exist. Please check that you specified the correct file name." 113 | end 114 | output 115 | end 116 | 117 | def generate_token(space) 118 | Contentful::Bootstrap::Commands::GenerateToken.new( 119 | @token, space, options 120 | ).run 121 | end 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::OAuthEchoView do 4 | describe 'instance methods' do 5 | it '#render' do 6 | expect(subject.render).to include( 7 | 'html', 8 | 'script', 9 | 'access_token', 10 | 'window.location.replace', 11 | 'save_token' 12 | ) 13 | end 14 | end 15 | end 16 | 17 | describe Contentful::Bootstrap::ThanksView do 18 | describe 'instance methods' do 19 | it '#render' do 20 | expect(subject.render).to include( 21 | 'html', 22 | 'Contentful Bootstrap', 23 | 'Thanks!' 24 | ) 25 | end 26 | end 27 | end 28 | 29 | describe Contentful::Bootstrap::IndexController do 30 | subject { Contentful::Bootstrap::IndexController.new(ServerDouble.new) } 31 | 32 | describe 'instance methods' do 33 | describe '#do_GET' do 34 | it 'launches browser' do 35 | expect(Launchy).to receive(:open) 36 | subject.do_GET(RequestDouble.new, ResponseDouble.new) 37 | end 38 | end 39 | end 40 | end 41 | 42 | describe Contentful::Bootstrap::OAuthCallbackController do 43 | subject { Contentful::Bootstrap::OAuthCallbackController.new(ServerDouble.new) } 44 | let(:response_double) { ResponseDouble.new } 45 | 46 | describe 'instance methods' do 47 | describe '#do_GET' do 48 | it 'renders OAuthEchoView' do 49 | subject.do_GET(RequestDouble.new, response_double) 50 | expect(response_double.body).to eq Contentful::Bootstrap::OAuthEchoView.new.render 51 | end 52 | end 53 | end 54 | end 55 | 56 | describe Contentful::Bootstrap::SaveTokenController do 57 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 58 | let(:token) { Contentful::Bootstrap::Token.new path } 59 | subject { Contentful::Bootstrap::SaveTokenController.new(ServerDouble.new, token) } 60 | let(:response_double) { ResponseDouble.new } 61 | let(:request_double) { RequestDouble.new } 62 | 63 | describe 'instance methods' do 64 | describe '#do_GET' do 65 | it 'writes token' do 66 | request_double.query = {'token' => 'foo'} 67 | 68 | expect(token).to receive(:write).with('foo') 69 | 70 | subject.do_GET(request_double, response_double) 71 | end 72 | 73 | it 'renders ThanksView' do 74 | request_double.query = {'token' => 'foo'} 75 | allow(token).to receive(:write) 76 | 77 | subject.do_GET(request_double, response_double) 78 | 79 | expect(response_double.body).to eq Contentful::Bootstrap::ThanksView.new.render 80 | end 81 | end 82 | end 83 | 84 | describe 'attributes' do 85 | it ':token' do 86 | expect(subject.token).to eq token 87 | end 88 | end 89 | end 90 | 91 | describe Contentful::Bootstrap::Server do 92 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 93 | let(:token) { Contentful::Bootstrap::Token.new path } 94 | subject { Contentful::Bootstrap::Server.new(token) } 95 | 96 | before do 97 | allow_any_instance_of(WEBrick::HTTPServer).to receive(:start) 98 | allow(WEBrick::Utils).to receive(:create_listeners) { [] } # Mock TCP Port binding 99 | end 100 | 101 | describe 'instance methods' do 102 | describe '#start' do 103 | it 'runs server in a new thread' do 104 | expect(Thread).to receive(:new) 105 | subject.start 106 | end 107 | end 108 | 109 | describe '#stop' do 110 | it 'shutdowns webrick' do 111 | expect(subject.server).to receive(:shutdown) 112 | subject.stop 113 | end 114 | end 115 | 116 | describe '#running?' do 117 | it 'returns true if webrick is running' do 118 | expect(subject.server).to receive(:status) { :Running } 119 | expect(subject.running?).to be_truthy 120 | end 121 | 122 | it 'returns false if webrick is not running' do 123 | expect(subject.server).to receive(:status) { :Stop } 124 | expect(subject.running?).to be_falsey 125 | end 126 | end 127 | end 128 | 129 | describe 'attributes' do 130 | describe ':server' do 131 | it 'creates the webrick server' do 132 | expect(subject.server).to be_kind_of(WEBrick::HTTPServer) 133 | end 134 | 135 | describe 'mounted endpoints' do 136 | before do 137 | @mount_table = subject.server.instance_variable_get(:@mount_tab) 138 | end 139 | 140 | it '/' do 141 | expect(@mount_table['/'][0]).to eq(Contentful::Bootstrap::IndexController) 142 | end 143 | 144 | it '/oauth_callback' do 145 | expect(@mount_table['/oauth_callback'][0]).to eq(Contentful::Bootstrap::OAuthCallbackController) 146 | end 147 | 148 | it '/save_token' do 149 | expect(@mount_table['/save_token'][0]).to eq(Contentful::Bootstrap::SaveTokenController) 150 | end 151 | end 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/command_runner_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::CommandRunner do 4 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 5 | subject { Contentful::Bootstrap::CommandRunner.new(path) } 6 | 7 | describe 'instance methods' do 8 | describe '#create_space' do 9 | before do 10 | allow_any_instance_of(Contentful::Bootstrap::Commands::CreateSpace).to receive(:run) 11 | end 12 | 13 | it 'default values' do 14 | expect(Contentful::Bootstrap::Commands::CreateSpace).to receive(:new).with( 15 | subject.token, 'foo', {} 16 | ).and_call_original 17 | 18 | subject.create_space('foo') 19 | end 20 | 21 | it 'with options' do 22 | expect(Contentful::Bootstrap::Commands::CreateSpace).to receive(:new).with( 23 | subject.token, 'foo', template: 'bar', json_template: 'baz', mark_processed: true, trigger_oauth: false 24 | ).and_call_original 25 | 26 | subject.create_space('foo', template: 'bar', json_template: 'baz', mark_processed: true, trigger_oauth: false) 27 | end 28 | 29 | it 'runs command' do 30 | expect_any_instance_of(Contentful::Bootstrap::Commands::CreateSpace).to receive(:run) 31 | 32 | subject.create_space('foo', template: 'bar', json_template: 'baz', trigger_oauth: false) 33 | end 34 | end 35 | 36 | describe '#update_space' do 37 | before do 38 | allow_any_instance_of(Contentful::Bootstrap::Commands::UpdateSpace).to receive(:run) 39 | end 40 | 41 | it 'default values' do 42 | expect(Contentful::Bootstrap::Commands::UpdateSpace).to receive(:new).with( 43 | subject.token, 'foo', {} 44 | ).and_call_original 45 | 46 | subject.update_space('foo') 47 | end 48 | 49 | it 'with options' do 50 | expect(Contentful::Bootstrap::Commands::UpdateSpace).to receive(:new).with( 51 | subject.token, 'foo', json_template: 'bar', mark_processed: true, trigger_oauth: false 52 | ).and_call_original 53 | 54 | subject.update_space('foo', json_template: 'bar', mark_processed: true, trigger_oauth: false) 55 | end 56 | 57 | it 'runs command' do 58 | expect_any_instance_of(Contentful::Bootstrap::Commands::UpdateSpace).to receive(:run) 59 | 60 | subject.update_space('foo', json_template: 'bar', trigger_oauth: false) 61 | end 62 | end 63 | 64 | describe '#generate_token' do 65 | before do 66 | allow_any_instance_of(Contentful::Bootstrap::Commands::GenerateToken).to receive(:run) 67 | allow_any_instance_of(Contentful::Bootstrap::Commands::GenerateToken).to receive(:fetch_space) 68 | end 69 | 70 | it 'default values' do 71 | expect(Contentful::Bootstrap::Commands::GenerateToken).to receive(:new).with( 72 | subject.token, 'foo', {} 73 | ).and_call_original 74 | 75 | subject.generate_token('foo') 76 | end 77 | 78 | it 'with options' do 79 | expect(Contentful::Bootstrap::Commands::GenerateToken).to receive(:new).with( 80 | subject.token, 'foo', name: 'bar', trigger_oauth: false 81 | ).and_call_original 82 | 83 | subject.generate_token('foo', name: 'bar', trigger_oauth: false) 84 | end 85 | 86 | it 'runs command' do 87 | expect_any_instance_of(Contentful::Bootstrap::Commands::GenerateToken).to receive(:run) 88 | 89 | subject.generate_token('foo', name: 'bar', trigger_oauth: false) 90 | end 91 | end 92 | 93 | describe '#generate_json' do 94 | it 'requires access token to run' do 95 | expect { 96 | subject.generate_json('foo') 97 | }.to raise_error('Access Token required') 98 | end 99 | 100 | it 'default values' do 101 | allow_any_instance_of(Contentful::Bootstrap::Commands::GenerateJson).to receive(:run) 102 | 103 | expect(Contentful::Bootstrap::Commands::GenerateJson).to receive(:new).with( 104 | 'foo', 'bar', 'master', nil, false, false, false, [] 105 | ).and_call_original 106 | 107 | subject.generate_json('foo', access_token: 'bar') 108 | end 109 | 110 | it 'with options' do 111 | allow_any_instance_of(Contentful::Bootstrap::Commands::GenerateJson).to receive(:run) 112 | 113 | expect(Contentful::Bootstrap::Commands::GenerateJson).to receive(:new).with( 114 | 'foo', 'bar', 'master', 'baz', true, false, false, [] 115 | ).and_call_original 116 | 117 | subject.generate_json('foo', access_token: 'bar', filename: 'baz', content_types_only: true) 118 | end 119 | 120 | it 'runs command' do 121 | expect_any_instance_of(Contentful::Bootstrap::Commands::GenerateJson).to receive(:run) 122 | 123 | subject.generate_json('foo', access_token: 'bar', filename: 'baz') 124 | end 125 | end 126 | end 127 | 128 | describe 'attributes' do 129 | it ':config_path' do 130 | expect(subject.config_path).to eq path 131 | end 132 | 133 | it ':token' do 134 | expect(subject.token == Contentful::Bootstrap::Token.new(path)).to be_truthy 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/generator.rb: -------------------------------------------------------------------------------- 1 | require 'contentful' 2 | require 'inifile' 3 | require 'json' 4 | require 'zlib' 5 | require 'contentful/bootstrap/version' 6 | 7 | module Contentful 8 | module Bootstrap 9 | class Generator 10 | DELIVERY_API_URL = 'cdn.contentful.com'.freeze 11 | PREVIEW_API_URL = 'preview.contentful.com'.freeze 12 | 13 | attr_reader :content_types_only, :content_type_ids, :client 14 | 15 | def initialize(space_id, access_token, environment, content_types_only, use_preview, content_type_ids) 16 | @client = Contentful::Client.new( 17 | space: space_id, 18 | access_token: access_token, 19 | environment: environment, 20 | application_name: 'bootstrap', 21 | application_version: ::Contentful::Bootstrap::VERSION, 22 | api_url: use_preview ? PREVIEW_API_URL : DELIVERY_API_URL 23 | ) 24 | @content_types_only = content_types_only 25 | @content_type_ids = content_type_ids 26 | end 27 | 28 | def generate_json 29 | template = {} 30 | template['version'] = Contentful::Bootstrap.major_version 31 | template['contentTypes'] = content_types 32 | template['assets'] = assets 33 | template['entries'] = entries 34 | JSON.pretty_generate(template) 35 | end 36 | 37 | private 38 | 39 | def content_types 40 | query = {} 41 | query['sys.id[in]'] = content_type_ids.join(',') unless content_type_ids.empty? 42 | 43 | proccessed_content_types = @client.content_types(query).map do |type| 44 | result = { 'id' => type.sys[:id], 'name' => type.name } 45 | result['displayField'] = type.display_field unless type.display_field.nil? 46 | 47 | result['fields'] = type.fields.map do |field| 48 | map_field_properties(field) 49 | end 50 | 51 | result 52 | end 53 | proccessed_content_types.sort_by { |item| item['id'] } 54 | end 55 | 56 | def assets 57 | return [] if content_types_only 58 | 59 | processed_assets = [] 60 | 61 | query = { order: 'sys.createdAt', limit: 1000 } 62 | assets_count = @client.assets(limit: 1).total 63 | ((assets_count / 1000) + 1).times do |i| 64 | query[:skip] = i * 1000 65 | 66 | @client.assets(query).each do |asset| 67 | processed_asset = { 68 | 'id' => asset.sys[:id], 69 | 'title' => asset.title, 70 | 'file' => { 71 | 'filename' => ::File.basename(asset.file.file_name, '.*'), 72 | 'url' => "https:#{asset.file.url}" 73 | } 74 | } 75 | processed_assets << processed_asset 76 | end 77 | end 78 | 79 | processed_assets.sort_by { |item| item['id'] } 80 | end 81 | 82 | def entries 83 | return {} if content_types_only 84 | 85 | entries = {} 86 | 87 | query = { order: 'sys.createdAt', limit: 1000 } 88 | count_query = { limit: 1 } 89 | 90 | unless content_type_ids.empty? 91 | search_key = 'sys.contentType.sys.id[in]' 92 | ids = content_type_ids.join(',') 93 | 94 | query[search_key] = ids 95 | count_query[search_key] = ids 96 | end 97 | 98 | entries_count = @client.entries(count_query).total 99 | ((entries_count / 1000) + 1).times do |i| 100 | query[:skip] = i * 1000 101 | 102 | @client.entries(query).each do |entry| 103 | result = { 'sys' => { 'id' => entry.sys[:id] }, 'fields' => {} } 104 | 105 | entry.fields.each do |key, value| 106 | value = map_field(value) 107 | result['fields'][field_id(entry, key)] = value unless value.nil? 108 | end 109 | 110 | ct_id = entry.content_type.sys[:id] 111 | entries[ct_id] = [] if entries[ct_id].nil? 112 | entries[ct_id] << result 113 | end 114 | end 115 | 116 | entries 117 | end 118 | 119 | def field_id(entry, field_name) 120 | entry.raw['fields'].keys.detect { |f| f == field_name.to_s || f == Support.camel_case(field_name.to_s).to_s } 121 | end 122 | 123 | def map_field(value) 124 | return value.map { |v| map_field(v) } if value.is_a? ::Array 125 | 126 | if value.is_a?(Contentful::Asset) || value.is_a?(Contentful::Entry) 127 | return { 128 | 'linkType' => value.class.name.split('::').last, 129 | 'id' => value.sys[:id] 130 | } 131 | end 132 | 133 | return nil if value.is_a?(Contentful::Link) 134 | 135 | value 136 | end 137 | 138 | def map_field_properties(field) 139 | properties = {} 140 | 141 | %i[id name type link_type required localized].each do |property| 142 | value = field.public_send(property) if field.respond_to?(property) 143 | properties[Support.camel_case(property.to_s).to_sym] = value unless value.nil? || %i[required localized].include?(property) 144 | end 145 | 146 | items = field.items if field.respond_to?(:items) 147 | properties[:items] = map_field_properties(items) unless items.nil? 148 | 149 | properties 150 | end 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /spec/fixtures/json_fixtures/cfexampleapi_cat_human.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "cat", 6 | "name": "Cat", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Name", 12 | "type": "Text" 13 | }, 14 | { 15 | "id": "likes", 16 | "name": "Likes", 17 | "type": "Array", 18 | "items": { 19 | "type": "Symbol" 20 | } 21 | }, 22 | { 23 | "id": "color", 24 | "name": "Color", 25 | "type": "Symbol" 26 | }, 27 | { 28 | "id": "bestFriend", 29 | "name": "Best Friend", 30 | "type": "Link", 31 | "linkType": "Entry" 32 | }, 33 | { 34 | "id": "birthday", 35 | "name": "Birthday", 36 | "type": "Date" 37 | }, 38 | { 39 | "id": "lifes", 40 | "name": "Lifes left", 41 | "type": "Integer" 42 | }, 43 | { 44 | "id": "lives", 45 | "name": "Lives left", 46 | "type": "Integer" 47 | }, 48 | { 49 | "id": "image", 50 | "name": "Image", 51 | "type": "Link", 52 | "linkType": "Asset" 53 | } 54 | ] 55 | }, 56 | { 57 | "id": "human", 58 | "name": "Human", 59 | "displayField": "name", 60 | "fields": [ 61 | { 62 | "id": "name", 63 | "name": "Name", 64 | "type": "Text" 65 | }, 66 | { 67 | "id": "description", 68 | "name": "Description", 69 | "type": "Text" 70 | }, 71 | { 72 | "id": "likes", 73 | "name": "Likes", 74 | "type": "Array", 75 | "items": { 76 | "type": "Symbol" 77 | } 78 | }, 79 | { 80 | "id": "image", 81 | "name": "Image", 82 | "type": "Array", 83 | "items": { 84 | "type": "Link", 85 | "linkType": "Asset" 86 | } 87 | } 88 | ] 89 | } 90 | ], 91 | "assets": [ 92 | { 93 | "id": "1x0xpXu4pSGS4OukSyWGUK", 94 | "title": "Doge", 95 | "file": { 96 | "filename": "doge", 97 | "url": "https://images.contentful.com/cfexampleapi/1x0xpXu4pSGS4OukSyWGUK/cc1239c6385428ef26f4180190532818/doge.jpg" 98 | } 99 | }, 100 | { 101 | "id": "happycat", 102 | "title": "Happy Cat", 103 | "file": { 104 | "filename": "happycatw", 105 | "url": "https://images.contentful.com/cfexampleapi/3MZPnjZTIskAIIkuuosCss/382a48dfa2cb16c47aa2c72f7b23bf09/happycatw.jpg" 106 | } 107 | }, 108 | { 109 | "id": "jake", 110 | "title": "Jake", 111 | "file": { 112 | "filename": "jake", 113 | "url": "https://images.contentful.com/cfexampleapi/4hlteQAXS8iS0YCMU6QMWg/2a4d826144f014109364ccf5c891d2dd/jake.png" 114 | } 115 | }, 116 | { 117 | "id": "nyancat", 118 | "title": "Nyan Cat", 119 | "file": { 120 | "filename": "Nyan_cat_250px_frame", 121 | "url": "https://images.contentful.com/cfexampleapi/4gp6taAwW4CmSgumq2ekUm/9da0cd1936871b8d72343e895a00d611/Nyan_cat_250px_frame.png" 122 | } 123 | } 124 | ], 125 | "entries": { 126 | "cat": [ 127 | { 128 | "sys": { 129 | "id": "nyancat" 130 | }, 131 | "fields": { 132 | "name": "Nyan Cat", 133 | "likes": [ 134 | "rainbows", 135 | "fish" 136 | ], 137 | "color": "rainbow", 138 | "bestFriend": { 139 | "linkType": "Entry", 140 | "id": "happycat" 141 | }, 142 | "birthday": "2011-04-04T22:00:00+00:00", 143 | "lives": 1337, 144 | "image": { 145 | "linkType": "Asset", 146 | "id": "nyancat" 147 | } 148 | } 149 | }, 150 | { 151 | "sys": { 152 | "id": "happycat" 153 | }, 154 | "fields": { 155 | "name": "Happy Cat", 156 | "likes": [ 157 | "cheezburger" 158 | ], 159 | "color": "gray", 160 | "bestFriend": { 161 | "linkType": "Entry", 162 | "id": "nyancat" 163 | }, 164 | "birthday": "2003-10-28T23:00:00+00:00", 165 | "lives": 1, 166 | "image": { 167 | "linkType": "Asset", 168 | "id": "happycat" 169 | } 170 | } 171 | }, 172 | { 173 | "sys": { 174 | "id": "garfield" 175 | }, 176 | "fields": { 177 | "name": "Garfield", 178 | "likes": [ 179 | "lasagna" 180 | ], 181 | "color": "orange", 182 | "birthday": "1979-06-18T23:00:00+00:00", 183 | "lives": 9 184 | } 185 | } 186 | ], 187 | "human": [ 188 | { 189 | "sys": { 190 | "id": "finn" 191 | }, 192 | "fields": { 193 | "name": "Finn", 194 | "description": "Fearless adventurer! Defender of pancakes.", 195 | "likes": [ 196 | "adventure" 197 | ] 198 | } 199 | } 200 | ] 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/token_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Token do 4 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 5 | let(:no_token_path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'no_token.ini')) } 6 | let(:sections_path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'sections.ini')) } 7 | let(:no_global_path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'no_global.ini')) } 8 | let(:with_org_id) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'orgid.ini')) } 9 | subject { Contentful::Bootstrap::Token.new(path) } 10 | 11 | describe 'attributes' do 12 | it ':config_path' do 13 | expect(subject.config_path).to eq path 14 | end 15 | end 16 | 17 | describe 'initialize' do 18 | it 'uses provided config_path' do 19 | expect(subject.class.new('bar').config_path).to eq 'bar' 20 | end 21 | end 22 | 23 | describe 'instance methods' do 24 | describe '#present?' do 25 | it 'non existing file returns false' do 26 | expect(subject.class.new('foobar').present?).to be_falsey 27 | end 28 | 29 | it 'existing file without management token returns false' do 30 | expect(subject.class.new(no_token_path).present?).to be_falsey 31 | end 32 | 33 | it 'existing file with management token returns true' do 34 | expect(subject.present?).to be_truthy 35 | end 36 | end 37 | 38 | describe '#filename' do 39 | it 'returns default path if config_path is empty' do 40 | expect(subject.class.new.filename).to eq ::File.join(ENV['HOME'], subject.class::DEFAULT_PATH) 41 | end 42 | 43 | it 'returns config_path' do 44 | expect(subject.filename).to eq path 45 | end 46 | end 47 | 48 | describe '#config_section' do 49 | it 'returns default section by default' do 50 | expect(subject.config_section).to eq 'global' 51 | end 52 | 53 | it 'returns default section if ENV["CONTENTFUL_ENV"] section does not exist' do 54 | ENV['CONTENTFUL_ENV'] = 'blah' 55 | expect(subject.class.new(sections_path).config_section).to eq 'global' 56 | ENV['CONTENTFUL_ENV'] = '' 57 | end 58 | 59 | it 'returns ENV["CONTENTFUL_ENV"] section if exists' do 60 | ENV['CONTENTFUL_ENV'] = 'other_section' 61 | expect(subject.class.new(sections_path).config_section).to eq 'other_section' 62 | ENV['CONTENTFUL_ENV'] = '' 63 | end 64 | end 65 | 66 | describe '#read' do 67 | it 'fails if "global" section does not exist' do 68 | expect { subject.class.new(no_global_path).read }.to raise_error 'Token not found' 69 | end 70 | 71 | it 'fails if management token is not found' do 72 | expect { subject.class.new(no_token_path).read }.to raise_error 'Token not found' 73 | end 74 | 75 | it 'returns token if its found' do 76 | expect(subject.read).to eq 'foobar' 77 | end 78 | end 79 | 80 | describe '#read_organization_id' do 81 | it 'nil if default org id is not set' do 82 | expect(subject.read_organization_id).to be_nil 83 | end 84 | 85 | it 'returns value of org id is set' do 86 | expect(subject.class.new(with_org_id).read_organization_id).to eq 'my_org' 87 | end 88 | end 89 | 90 | describe 'write methods' do 91 | before do 92 | @file = subject.config_file 93 | expect(@file).to receive(:save) 94 | end 95 | 96 | it '#write_access_token' do 97 | 98 | expect(@file.has_section?('some_space')).to be_falsey 99 | 100 | subject.write_access_token('some_space', 'asd') 101 | 102 | expect(@file['some_space']['CONTENTFUL_DELIVERY_ACCESS_TOKEN']).to eq 'asd' 103 | end 104 | 105 | it '#write_space_id' do 106 | expect(@file.has_section?('some_space')).to be_falsey 107 | 108 | subject.write_space_id('some_space', 'asd') 109 | 110 | expect(@file['some_space']['SPACE_ID']).to eq 'asd' 111 | end 112 | 113 | it '#write_organization_id' do 114 | subject.write_organization_id('foo') 115 | expect(@file['global']['CONTENTFUL_ORGANIZATION_ID']).to eq 'foo' 116 | end 117 | 118 | describe '#write' do 119 | it 'writes management tokens when only value is sent' do 120 | expect(@file['global']['CONTENTFUL_MANAGEMENT_ACCESS_TOKEN']).to eq 'foobar' 121 | 122 | subject.write('baz') 123 | 124 | expect(@file['global']['CONTENTFUL_MANAGEMENT_ACCESS_TOKEN']).to eq 'baz' 125 | end 126 | 127 | it 'can write management tokens to any section' do 128 | expect(@file.has_section?('blah')).to be_falsey 129 | 130 | subject.write('baz', 'blah') 131 | 132 | expect(@file['blah']['CONTENTFUL_MANAGEMENT_ACCESS_TOKEN']).to eq 'baz' 133 | end 134 | 135 | it 'can write any key to any section' do 136 | expect(@file.has_section?('blah')).to be_falsey 137 | 138 | subject.write('foo', 'bar', 'baz') 139 | 140 | expect(@file['bar']['baz']).to eq 'foo' 141 | end 142 | end 143 | end 144 | 145 | describe '#==' do 146 | it 'returns false when other is not a token' do 147 | expect(subject == 1).to be_falsey 148 | end 149 | 150 | it 'returns false when other token does not have same config_path' do 151 | expect(subject == subject.class.new('foo')).to be_falsey 152 | end 153 | 154 | it 'returns true when other token has same config_path' do 155 | expect(subject == subject.class.new(path)).to be_truthy 156 | end 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/check_created_space.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cdn.contentful.com/spaces/vsy1ouf6jdcq/environments/master/content_types?limit=1000 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | X-Contentful-User-Agent: 11 | - sdk contentful.rb/2.1.3; platform ruby/2.4.1; os macOS/16; 12 | Authorization: 13 | - Bearer 90e1b4964c3631cc9c751c42339814635623b001a53aec5aad23377299445433 14 | Content-Type: 15 | - application/vnd.contentful.delivery.v1+json 16 | Accept-Encoding: 17 | - gzip 18 | Connection: 19 | - close 20 | Host: 21 | - cdn.contentful.com 22 | User-Agent: 23 | - http.rb/2.2.2 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Access-Control-Allow-Headers: 30 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Experimental-Feature 31 | Access-Control-Allow-Methods: 32 | - GET,HEAD,OPTIONS 33 | Access-Control-Allow-Origin: 34 | - "*" 35 | Access-Control-Expose-Headers: 36 | - Etag 37 | Access-Control-Max-Age: 38 | - '86400' 39 | Cache-Control: 40 | - max-age=0 41 | Content-Type: 42 | - application/vnd.contentful.delivery.v1+json 43 | Etag: 44 | - '"c283f5e14b648b925eac46aa3ee07ca3"' 45 | Server: 46 | - Contentful 47 | X-Content-Type-Options: 48 | - nosniff 49 | X-Contentful-Request-Id: 50 | - d1e40cd5d4cfb17b8122b3b8256bf31a 51 | Content-Length: 52 | - '98' 53 | Accept-Ranges: 54 | - bytes 55 | Date: 56 | - Fri, 18 Aug 2017 21:03:09 GMT 57 | Via: 58 | - 1.1 varnish 59 | Age: 60 | - '0' 61 | Connection: 62 | - close 63 | X-Served-By: 64 | - cache-atl6236-ATL 65 | X-Cache: 66 | - MISS 67 | X-Cache-Hits: 68 | - '0' 69 | X-Timer: 70 | - S1503090189.214686,VS0,VE206 71 | Vary: 72 | - Accept-Encoding 73 | body: 74 | encoding: ASCII-8BIT 75 | string: | 76 | { 77 | "sys": { 78 | "type": "Array" 79 | }, 80 | "total": 0, 81 | "skip": 0, 82 | "limit": 1000, 83 | "items": [] 84 | } 85 | http_version: 86 | recorded_at: Fri, 18 Aug 2017 21:03:09 GMT 87 | - request: 88 | method: get 89 | uri: https://cdn.contentful.com/spaces/vsy1ouf6jdcq 90 | body: 91 | encoding: US-ASCII 92 | string: '' 93 | headers: 94 | X-Contentful-User-Agent: 95 | - sdk contentful.rb/2.1.3; platform ruby/2.4.1; os macOS/16; 96 | Authorization: 97 | - Bearer 90e1b4964c3631cc9c751c42339814635623b001a53aec5aad23377299445433 98 | Content-Type: 99 | - application/vnd.contentful.delivery.v1+json 100 | Accept-Encoding: 101 | - gzip 102 | Connection: 103 | - close 104 | Host: 105 | - cdn.contentful.com 106 | User-Agent: 107 | - http.rb/2.2.2 108 | response: 109 | status: 110 | code: 200 111 | message: OK 112 | headers: 113 | Access-Control-Allow-Headers: 114 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Experimental-Feature 115 | Access-Control-Allow-Methods: 116 | - GET,HEAD,OPTIONS 117 | Access-Control-Allow-Origin: 118 | - "*" 119 | Access-Control-Expose-Headers: 120 | - Etag 121 | Access-Control-Max-Age: 122 | - '86400' 123 | Cache-Control: 124 | - max-age=0 125 | Content-Type: 126 | - application/vnd.contentful.delivery.v1+json 127 | Etag: 128 | - '"41e2e178aad9b651bb9c230458dacaa0"' 129 | Server: 130 | - Contentful 131 | X-Content-Type-Options: 132 | - nosniff 133 | X-Contentful-Request-Id: 134 | - 07c944a494b440cb72fbfc4a8aaee17b 135 | Content-Length: 136 | - '229' 137 | Accept-Ranges: 138 | - bytes 139 | Date: 140 | - Fri, 18 Aug 2017 21:03:10 GMT 141 | Via: 142 | - 1.1 varnish 143 | Age: 144 | - '0' 145 | Connection: 146 | - close 147 | X-Served-By: 148 | - cache-atl6239-ATL 149 | X-Cache: 150 | - MISS 151 | X-Cache-Hits: 152 | - '0' 153 | X-Timer: 154 | - S1503090190.215703,VS0,VE174 155 | Vary: 156 | - Accept-Encoding 157 | body: 158 | encoding: ASCII-8BIT 159 | string: | 160 | { 161 | "sys": { 162 | "type": "Space", 163 | "id": "vsy1ouf6jdcq" 164 | }, 165 | "name": "B.rb - locale creation", 166 | "locales": [ 167 | { 168 | "code": "es-AR", 169 | "default": true, 170 | "name": "es-AR", 171 | "fallbackCode": null 172 | } 173 | ] 174 | } 175 | http_version: 176 | recorded_at: Fri, 18 Aug 2017 21:03:10 GMT 177 | recorded_with: VCR 2.9.3 178 | -------------------------------------------------------------------------------- /bin/contentful_bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require 'contentful/bootstrap/command_runner' 5 | 6 | options = {} 7 | 8 | subcommands = { 9 | 'create_space' => OptionParser.new do |opts| 10 | opts.banner = 'Usage: create_space [--template TEMPLATE_NAME] [--json-template JSON_PATH] [--locale LOCALE] [--mark-processed] [--config CONFIG_PATH]' 11 | opts.on('-t TEMPLATE', '--template TEMPLATE', 'Specify Template', 'Available Templates: blog, catalogue, gallery') do |t| 12 | options[:template] = t 13 | end 14 | opts.on('-j JSON_PATH', '--json-template JSON_PATH', 'Specify JSON Template Path') do |j| 15 | options[:json_template] = j 16 | end 17 | opts.on('-l LOCALE', '--locale LOCALE', 'Set the Default Locale for the Space', "Defaults to 'en-US'") do |l| 18 | options[:locale] = l 19 | end 20 | opts.on('-m', '--mark-processed', 'Mark Processed Items on JSON Templates') do |m| 21 | options[:mark_processed] = m 22 | end 23 | opts.on('-c CONFIG_PATH', '--config CONFIG_PATH', 'Specify Configuration Path') do |c| 24 | options[:config_path] = c 25 | end 26 | opts.on('-q', '--quiet', "Don't output to STDOUT") do |q| 27 | options[:quiet] = q 28 | end 29 | opts.on('-n', '--no-publish', "Don't publish") do |n| 30 | options[:no_publish] = n 31 | end 32 | opts.on_tail('-h', '--help', 'Print this message') do 33 | puts opts 34 | end 35 | end, 36 | 'update_space' => OptionParser.new do |opts| 37 | opts.banner = 'Usage: update_space --json-template JSON_PATH [--environment ENVIRONMENT_ID] [--locale LOCALE] [--mark-processed] [--config CONFIG_PATH]' 38 | opts.on('-j JSON_PATH', '--json-template JSON_PATH', 'Specify JSON Template Path') do |j| 39 | options[:json_template] = j 40 | end 41 | opts.on('-e ENVIRONMENT_ID', '--environment ENVIRONMENT_ID', 'Specify target environment ID', "Defaults to 'master'") do |e| 42 | options[:environment] = e 43 | end 44 | opts.on('-l LOCALE', '--locale LOCALE', 'Set the Default Locale for the Space', "Defaults to 'en-US'") do |l| 45 | options[:locale] = l 46 | end 47 | opts.on('-m', '--mark-processed', 'Mark Processed Items on JSON Templates') do |m| 48 | options[:mark_processed] = m 49 | end 50 | opts.on('-T', '--skip-content-types', 'Skip Content Types on Space update') do |t| 51 | options[:skip_content_types] = t 52 | end 53 | opts.on('-c CONFIG_PATH', '--config CONFIG_PATH', 'Specify Configuration Path') do |c| 54 | options[:config_path] = c 55 | end 56 | opts.on('-n', '--no-publish', "Don't publish") do |n| 57 | options[:no_publish] = n 58 | end 59 | opts.on('-q', '--quiet', "Don't output to STDOUT") do |q| 60 | options[:quiet] = q 61 | end 62 | opts.on_tail('-h', '--help', 'Print this message') do 63 | puts opts 64 | end 65 | end, 66 | 'generate_token' => OptionParser.new do |opts| 67 | opts.banner = 'Usage: generate_token [--name TOKEN_NAME] [--config CONFIG_PATH]' 68 | opts.on('-n NAME', '--name TOKEN_NAME', 'Specify Token Name') do |n| 69 | options[:name] = n 70 | end 71 | opts.on('-c CONFIG_PATH', '--config CONFIG_PATH', 'Specify Configuration Path') do |c| 72 | options[:config_path] = c 73 | end 74 | opts.on('-q', '--quiet', "Don't output to STDOUT") do |q| 75 | options[:quiet] = q 76 | end 77 | opts.on_tail('-h', '--help', 'Print this message') do 78 | puts opts 79 | end 80 | end, 81 | 'generate_json' => OptionParser.new do |opts| 82 | opts.banner = 'Usage: generate_json [--environment ENVIRONMENT_ID] [--output-file OUTPUT_PATH] [--content-types-only] [--use-preview] [--content-type-ids , ]' 83 | opts.on('-e ENVIRONMENT_ID', '--environment ENVIRONMENT_ID', 'Specify target environment ID', "Defaults to 'master'") do |e| 84 | options[:environment] = e 85 | end 86 | opts.on('-t', '--content-types-only', 'Only fetch Content Types') do |t| 87 | options[:content_types_only] = t 88 | end 89 | opts.on('-p', '--use-preview', 'Use Preview API') do |p| 90 | options[:use_preview] = p 91 | end 92 | opts.on('-i', '--content-type-ids ,', ::Array, 'Array of content type IDs to import') do |ids| 93 | options[:content_type_ids] = ids 94 | end 95 | opts.on('-o OUTPUT_PATH', '--output-file OUTPUT_PATH', 'Specify Output File') do |f| 96 | options[:filename] = f 97 | end 98 | opts.on('-q', '--quiet', "Don't output to STDOUT") do |q| 99 | options[:quiet] = q 100 | end 101 | opts.on_tail('-h', '--help', 'Print this message') do 102 | puts opts 103 | end 104 | end 105 | } 106 | 107 | global = OptionParser.new do |opts| 108 | opts.banner = 'Usage: contentful_bootstrap [options]' 109 | opts.separator '' 110 | opts.separator <<-HELP 111 | Available commands are: 112 | \t#{subcommands.keys.sort.join("\n\t")} 113 | HELP 114 | opts.on_tail('-v', '--version', 'Show Version') do 115 | puts "Contentful Bootstrap: #{Contentful::Bootstrap::VERSION}" 116 | exit(0) 117 | end 118 | end 119 | 120 | global.order! 121 | command = ARGV.shift 122 | 123 | is_help = ARGV.include?('-h') || ARGV.include?('--help') 124 | space = ARGV.shift unless is_help 125 | 126 | options[:access_token] = ARGV.shift if command == 'generate_json' && !is_help 127 | 128 | if subcommands.key? command 129 | subcommands[command].order! 130 | 131 | unless STDIN.tty? && STDOUT.tty? 132 | $stderr.write "This tool requires user interaction\n" 133 | $stderr.write "Exiting!\n" 134 | exit(1) 135 | end 136 | 137 | exit if is_help 138 | 139 | if space.nil? || space.empty? 140 | ARGV << '-h' 141 | subcommands[command].order! 142 | puts 143 | puts 'Required Arguments not specified. Exiting!' 144 | exit 145 | end 146 | 147 | options[:trigger_oauth] = true 148 | 149 | Contentful::Bootstrap::CommandRunner.new(options.fetch(:config_path, '')).send(command, space, options) 150 | else 151 | puts 'Usage: contentful_bootstrap [options]' 152 | puts 153 | puts <<-HELP 154 | Subcommand not available or missing 155 | Available commands are: 156 | HELP 157 | puts "\t#{subcommands.keys.sort.join("\n\t")}" 158 | end 159 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/json_template.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'contentful/bootstrap/templates/base' 3 | require 'contentful/bootstrap/templates/links' 4 | 5 | module Contentful 6 | module Bootstrap 7 | module Templates 8 | class JsonTemplate < Base 9 | CONTENT_TYPES_KEY = 'contentTypes'.freeze 10 | ENTRIES_KEY = 'entries'.freeze 11 | ASSETS_KEY = 'assets'.freeze 12 | BOOTSTRAP_PROCCESSED_KEY = 'bootstrapProcessed'.freeze 13 | DISPLAY_FIELD_KEY = 'displayField'.freeze 14 | ALTERNATE_DISPLAY_FIELD_KEY = 'display_field'.freeze 15 | SYS_KEY = 'sys'.freeze 16 | 17 | attr_reader :assets, :entries, :content_types 18 | 19 | def initialize(space, file, environment = 'master', mark_processed = false, all = true, quiet = false, skip_content_types = false, no_publish = false) 20 | @file = file 21 | json 22 | check_version 23 | 24 | super(space, environment, quiet, skip_content_types, no_publish) 25 | 26 | @all = all 27 | @mark_processed = mark_processed 28 | 29 | @assets = process_assets 30 | @entries = process_entries 31 | @content_types = process_content_types 32 | end 33 | 34 | def after_run 35 | return unless mark_processed? 36 | 37 | # Re-parse JSON to avoid side effects from `Templates::Base` 38 | @json = parse_json 39 | 40 | json.fetch(CONTENT_TYPES_KEY, []).each do |content_type| 41 | content_type[BOOTSTRAP_PROCCESSED_KEY] = true 42 | end 43 | 44 | json.fetch(ASSETS_KEY, []).each do |asset| 45 | asset[BOOTSTRAP_PROCCESSED_KEY] = true 46 | end 47 | 48 | json.fetch(ENTRIES_KEY, {}).each do |_content_type_name, entry_list| 49 | entry_list.each do |entry| 50 | if entry.key?(SYS_KEY) 51 | entry[SYS_KEY][BOOTSTRAP_PROCCESSED_KEY] = true 52 | else 53 | entry[SYS_KEY] = { BOOTSTRAP_PROCCESSED_KEY => true } 54 | end 55 | end 56 | end 57 | 58 | ::File.write(@file, JSON.pretty_generate(@json)) 59 | end 60 | 61 | def json 62 | @json ||= parse_json 63 | end 64 | 65 | private 66 | 67 | def parse_json 68 | ::JSON.parse(::File.read(@file)) 69 | rescue StandardError 70 | output 'File is not JSON. Exiting!' 71 | exit(1) 72 | end 73 | 74 | def check_version 75 | json_version = json.fetch('version', 0) 76 | gem_major = Contentful::Bootstrap.major_version 77 | raise "JSON Templates Version Mismatch. Current Version: #{gem_major}" unless gem_major == json_version 78 | end 79 | 80 | def process_content_types 81 | processed_content_types = json.fetch(CONTENT_TYPES_KEY, []) 82 | 83 | unless all? 84 | processed_content_types = processed_content_types.reject do |content_type| 85 | content_type.fetch(BOOTSTRAP_PROCCESSED_KEY, false) 86 | end 87 | end 88 | 89 | processed_content_types.each do |content_type| 90 | content_type[DISPLAY_FIELD_KEY] = if content_type.key?(ALTERNATE_DISPLAY_FIELD_KEY) 91 | content_type.delete(ALTERNATE_DISPLAY_FIELD_KEY) 92 | else 93 | content_type[DISPLAY_FIELD_KEY] 94 | end 95 | end 96 | 97 | processed_content_types 98 | end 99 | 100 | def process_assets 101 | unprocessed_assets = json.fetch(ASSETS_KEY, []) 102 | 103 | unless all? 104 | unprocessed_assets = unprocessed_assets.reject do |asset| 105 | asset.fetch(BOOTSTRAP_PROCCESSED_KEY, false) 106 | end 107 | end 108 | 109 | unprocessed_assets.map do |asset| 110 | asset['file'] = create_file( 111 | asset['file']['filename'], 112 | asset['file']['url'], 113 | contentType: asset['file'].fetch('contentType', 'image/jpeg') 114 | ) 115 | asset 116 | end 117 | end 118 | 119 | def process_entry(entry) 120 | processed_entry = {} 121 | processed_entry['id'] = entry[SYS_KEY]['id'] if entry.key?(SYS_KEY) && entry[SYS_KEY].key?('id') 122 | 123 | entry.fetch('fields', {}).each do |field, value| 124 | if link?(value) 125 | processed_entry[field] = create_link(value) 126 | next 127 | elsif array?(value) 128 | processed_entry[field] = value.map { |i| link?(i) ? create_link(i) : i } 129 | next 130 | end 131 | 132 | processed_entry[field] = value 133 | end 134 | 135 | processed_entry 136 | end 137 | 138 | def process_entries 139 | unprocessed_entries = json.fetch(ENTRIES_KEY, {}) 140 | unprocessed_entries.each_with_object({}) do |(content_type_id, entry_list), processed_entries| 141 | entries_for_content_type = [] 142 | 143 | unless all? 144 | entry_list = entry_list.reject do |entry| 145 | entry[SYS_KEY].fetch(BOOTSTRAP_PROCCESSED_KEY, false) 146 | end 147 | end 148 | 149 | entry_list.each do |entry| 150 | entries_for_content_type << process_entry(entry) 151 | end 152 | 153 | processed_entries[content_type_id] = entries_for_content_type 154 | end 155 | end 156 | 157 | def create_link(link_properties) 158 | link_type = link_properties['linkType'].capitalize 159 | id = link_properties['id'] 160 | case link_type 161 | when 'Entry' 162 | Contentful::Bootstrap::Templates::Links::Entry.new(id) 163 | when 'Asset' 164 | Contentful::Bootstrap::Templates::Links::Asset.new(id) 165 | end 166 | end 167 | 168 | def all? 169 | @all 170 | end 171 | 172 | def mark_processed? 173 | @mark_processed 174 | end 175 | 176 | def link?(value) 177 | value.is_a?(::Hash) && value.key?('id') && value.key?('linkType') 178 | end 179 | 180 | def array?(value) 181 | value.is_a?(::Array) 182 | end 183 | end 184 | end 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/create_space.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.contentful.com/spaces 6 | body: 7 | encoding: UTF-8 8 | string: '{"name":"blog","defaultLocale":"en-US"}' 9 | headers: 10 | User-Agent: 11 | - RubyContentfulManagementGem/0.7.2 12 | Authorization: 13 | - Bearer foobar 14 | Content-Type: 15 | - application/vnd.contentful.management.v1+json 16 | Connection: 17 | - close 18 | Host: 19 | - api.contentful.com 20 | response: 21 | status: 22 | code: 201 23 | message: Created 24 | headers: 25 | Accept-Ranges: 26 | - bytes 27 | Access-Control-Allow-Headers: 28 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation 29 | Access-Control-Allow-Methods: 30 | - DELETE,GET,HEAD,POST,PUT,OPTIONS 31 | Access-Control-Allow-Origin: 32 | - "*" 33 | Access-Control-Expose-Headers: 34 | - Etag 35 | Access-Control-Max-Age: 36 | - '1728000' 37 | Cache-Control: 38 | - max-age=0 39 | Content-Type: 40 | - application/vnd.contentful.management.v1+json 41 | Date: 42 | - Wed, 18 Nov 2015 15:18:44 GMT 43 | Etag: 44 | - '"5ff2a2eea2a7ca0d643293849f3a7295"' 45 | Location: 46 | - https://api.contentful.com/spaces/imf7gr7jg4ed 47 | Server: 48 | - nginx 49 | Status: 50 | - 201 Created 51 | Strict-Transport-Security: 52 | - max-age=15768000 53 | X-Content-Type-Options: 54 | - nosniff 55 | X-Contentful-Request-Id: 56 | - 0b5-124655605 57 | Content-Length: 58 | - '447' 59 | Connection: 60 | - Close 61 | body: 62 | encoding: UTF-8 63 | string: |+ 64 | { 65 | "sys":{ 66 | "type":"Space", 67 | "id":"imf7gr7jg4ed", 68 | "version":1, 69 | "createdBy":{ 70 | "sys":{ 71 | "type":"Link", 72 | "linkType":"User", 73 | "id":"4SejVrWT96dvL9IV4Nb7sQ" 74 | } 75 | }, 76 | "createdAt":"2015-11-18T15:18:43Z", 77 | "updatedBy":{ 78 | "sys":{ 79 | "type":"Link", 80 | "linkType":"User", 81 | "id":"4SejVrWT96dvL9IV4Nb7sQ" 82 | } 83 | }, 84 | "updatedAt":"2015-11-18T15:18:43Z" 85 | }, 86 | "name":"blog" 87 | } 88 | 89 | http_version: 90 | recorded_at: Wed, 18 Nov 2015 15:18:44 GMT 91 | - request: 92 | method: post 93 | uri: https://api.contentful.com/spaces/imf7gr7jg4ed/api_keys 94 | body: 95 | encoding: UTF-8 96 | string: '{"name":"Bootstrap Token","description":"Created with ''contentful_bootstrap.rb 97 | v2.0.2''"}' 98 | headers: 99 | User-Agent: 100 | - RubyContentfulManagementGem/0.7.2 101 | Authorization: 102 | - Bearer foobar 103 | Content-Type: 104 | - application/vnd.contentful.management.v1+json 105 | Connection: 106 | - close 107 | Host: 108 | - api.contentful.com 109 | response: 110 | status: 111 | code: 201 112 | message: Created 113 | headers: 114 | Accept-Ranges: 115 | - bytes 116 | Access-Control-Allow-Headers: 117 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation 118 | Access-Control-Allow-Methods: 119 | - DELETE,GET,HEAD,POST,PUT,OPTIONS 120 | Access-Control-Allow-Origin: 121 | - "*" 122 | Access-Control-Expose-Headers: 123 | - Etag 124 | Access-Control-Max-Age: 125 | - '1728000' 126 | Cache-Control: 127 | - max-age=0 128 | Content-Type: 129 | - application/vnd.contentful.management.v1+json 130 | Date: 131 | - Wed, 18 Nov 2015 15:18:45 GMT 132 | Etag: 133 | - '"f8404c82e215f0cc39614ed00adc75b5"' 134 | Location: 135 | - https://api.contentful.com/spaces/imf7gr7jg4ed/api_keys/4y83USq9HGzofldbLvDrW0 136 | Server: 137 | - nginx 138 | Status: 139 | - 201 Created 140 | Strict-Transport-Security: 141 | - max-age=15768000 142 | X-Content-Type-Options: 143 | - nosniff 144 | X-Contentful-Request-Id: 145 | - 0b5-124655625 146 | Content-Length: 147 | - '954' 148 | Connection: 149 | - Close 150 | body: 151 | encoding: UTF-8 152 | string: |+ 153 | { 154 | "sys":{ 155 | "type":"ApiKey", 156 | "id":"4y83USq9HGzofldbLvDrW0", 157 | "version":0, 158 | "space":{ 159 | "sys":{ 160 | "type":"Link", 161 | "linkType":"Space", 162 | "id":"imf7gr7jg4ed" 163 | } 164 | }, 165 | "createdBy":{ 166 | "sys":{ 167 | "type":"Link", 168 | "linkType":"User", 169 | "id":"4SejVrWT96dvL9IV4Nb7sQ" 170 | } 171 | }, 172 | "createdAt":"2015-11-18T15:18:45Z", 173 | "updatedBy":{ 174 | "sys":{ 175 | "type":"Link", 176 | "linkType":"User", 177 | "id":"4SejVrWT96dvL9IV4Nb7sQ" 178 | } 179 | }, 180 | "updatedAt":"2015-11-18T15:18:45Z" 181 | }, 182 | "name":"Bootstrap Token", 183 | "description":"Created with 'contentful_bootstrap.rb v2.0.2'", 184 | "accessToken":"362afdb99e627ed0e70d71747cc905e1be3a2a85c798e57513ed6a2c0c2721f4", 185 | "policies":[ 186 | { 187 | "effect":"allow", 188 | "actions":"all" 189 | } 190 | ], 191 | "preview_api_key":{ 192 | "sys":{ 193 | "type":"Link", 194 | "linkType":"PreviewApiKey", 195 | "id":"4y9pGFQBeSYI2AQQqJ84Ok" 196 | } 197 | } 198 | } 199 | 200 | http_version: 201 | recorded_at: Wed, 18 Nov 2015 15:18:45 GMT 202 | recorded_with: VCR 2.9.3 203 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/check_update_space_localized.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cdn.contentful.com/spaces/vsy1ouf6jdcq/environments/master/content_types?limit=1000 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | X-Contentful-User-Agent: 11 | - sdk contentful.rb/2.1.3; platform ruby/2.4.1; os macOS/16; 12 | Authorization: 13 | - Bearer 90e1b4964c3631cc9c751c42339814635623b001a53aec5aad23377299445433 14 | Content-Type: 15 | - application/vnd.contentful.delivery.v1+json 16 | Accept-Encoding: 17 | - gzip 18 | Connection: 19 | - close 20 | Host: 21 | - cdn.contentful.com 22 | User-Agent: 23 | - http.rb/2.2.2 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Access-Control-Allow-Headers: 30 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Experimental-Feature 31 | Access-Control-Allow-Methods: 32 | - GET,HEAD,OPTIONS 33 | Access-Control-Allow-Origin: 34 | - "*" 35 | Access-Control-Expose-Headers: 36 | - Etag 37 | Access-Control-Max-Age: 38 | - '86400' 39 | Cache-Control: 40 | - max-age=0 41 | Content-Type: 42 | - application/vnd.contentful.delivery.v1+json 43 | Etag: 44 | - '"594786b7b50ce50203bbce682cdb5782"' 45 | Server: 46 | - Contentful 47 | X-Content-Type-Options: 48 | - nosniff 49 | X-Contentful-Request-Id: 50 | - 32854106ee78a518ebdd3a86aa4aa8a8 51 | Content-Length: 52 | - '780' 53 | Accept-Ranges: 54 | - bytes 55 | Date: 56 | - Wed, 23 Aug 2017 14:39:47 GMT 57 | Via: 58 | - 1.1 varnish 59 | Age: 60 | - '0' 61 | Connection: 62 | - close 63 | X-Served-By: 64 | - cache-gru17131-GRU 65 | X-Cache: 66 | - MISS 67 | X-Cache-Hits: 68 | - '0' 69 | X-Timer: 70 | - S1503499187.395554,VS0,VE263 71 | Vary: 72 | - Accept-Encoding 73 | body: 74 | encoding: ASCII-8BIT 75 | string: | 76 | { 77 | "sys": { 78 | "type": "Array" 79 | }, 80 | "total": 1, 81 | "skip": 0, 82 | "limit": 1000, 83 | "items": [ 84 | { 85 | "sys": { 86 | "space": { 87 | "sys": { 88 | "type": "Link", 89 | "linkType": "Space", 90 | "id": "vsy1ouf6jdcq" 91 | } 92 | }, 93 | "id": "test", 94 | "type": "ContentType", 95 | "createdAt": "2017-08-23T14:02:48.328Z", 96 | "updatedAt": "2017-08-23T14:02:48.328Z", 97 | "revision": 1 98 | }, 99 | "displayField": "text", 100 | "name": "Test", 101 | "description": "", 102 | "fields": [ 103 | { 104 | "id": "text", 105 | "name": "Text", 106 | "type": "Symbol", 107 | "localized": true, 108 | "required": false, 109 | "disabled": false, 110 | "omitted": false 111 | } 112 | ] 113 | } 114 | ] 115 | } 116 | http_version: 117 | recorded_at: Wed, 23 Aug 2017 14:39:47 GMT 118 | - request: 119 | method: get 120 | uri: https://cdn.contentful.com/spaces/vsy1ouf6jdcq/environments/master/entries?locale=es-AR 121 | body: 122 | encoding: US-ASCII 123 | string: '' 124 | headers: 125 | X-Contentful-User-Agent: 126 | - sdk contentful.rb/2.1.3; platform ruby/2.4.1; os macOS/16; 127 | Authorization: 128 | - Bearer 90e1b4964c3631cc9c751c42339814635623b001a53aec5aad23377299445433 129 | Content-Type: 130 | - application/vnd.contentful.delivery.v1+json 131 | Accept-Encoding: 132 | - gzip 133 | Connection: 134 | - close 135 | Host: 136 | - cdn.contentful.com 137 | User-Agent: 138 | - http.rb/2.2.2 139 | response: 140 | status: 141 | code: 200 142 | message: OK 143 | headers: 144 | Access-Control-Allow-Headers: 145 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Experimental-Feature 146 | Access-Control-Allow-Methods: 147 | - GET,HEAD,OPTIONS 148 | Access-Control-Allow-Origin: 149 | - "*" 150 | Access-Control-Expose-Headers: 151 | - Etag 152 | Access-Control-Max-Age: 153 | - '86400' 154 | Cache-Control: 155 | - max-age=0 156 | Content-Encoding: 157 | - gzip 158 | Content-Type: 159 | - application/vnd.contentful.delivery.v1+json 160 | Etag: 161 | - W/"4b2e84b99e4024a461dd9b1a98e6743c" 162 | Server: 163 | - Contentful 164 | X-Content-Type-Options: 165 | - nosniff 166 | X-Contentful-Request-Id: 167 | - 8f60a93bc7915a078a7b094062c8d915 168 | Content-Length: 169 | - '322' 170 | Accept-Ranges: 171 | - bytes 172 | Date: 173 | - Wed, 23 Aug 2017 14:39:48 GMT 174 | Via: 175 | - 1.1 varnish 176 | Age: 177 | - '0' 178 | Connection: 179 | - close 180 | X-Served-By: 181 | - cache-gru17131-GRU 182 | X-Cache: 183 | - MISS 184 | X-Cache-Hits: 185 | - '0' 186 | X-Timer: 187 | - S1503499188.241671,VS0,VE140 188 | Vary: 189 | - Accept-Encoding 190 | body: 191 | encoding: ASCII-8BIT 192 | string: !binary |- 193 | H4sIAAAAAAAAA+VSy07DMBC85ysin0mVVxvILSA4cYKeQAhZiSuZpHawtxVWlX/Hdh61UiRUqRfEnjybnX1M5uD5PpJKotw/6KcGoFqiESqEwArpXHdlaoADbnQ+tkjWtNUgtKChWwoaRWGPKZCtafhqG/ZtZ1PsJNni0owaK/qks4tJ2OS40yNlNTIzj6Gns3o97PxsO84KaGXO2UsV8d1m9VGVn+aqMbrpbe/sA/UcIBLeI6fftMg9A6HcD6UgGEhVGB1QHEZZEF4HcbKO0jy5ydN0kYXZi0vYtdV5BEH2VFLOjNDOpiVnQBgMClxOyjun74+CGnF+F7LhJW6sn4gMiqeRMGmNNpQ01dF+Rn8E5Mvq+MD5RLD/ZqD9dUvFl7DUarFMludY6pTwDy11i8XMUtpYb17nfQPclY1+CQUAAA== 194 | http_version: 195 | recorded_at: Wed, 23 Aug 2017 14:39:48 GMT 196 | recorded_with: VCR 2.9.3 197 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/check_original_staging_environment_status.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://cdn.contentful.com/spaces/9utsm1g0t7f5/environments/staging/content_types?limit=1000 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | X-Contentful-User-Agent: 11 | - sdk contentful.rb/2.6.0; platform ruby/2.5.1; os macOS/16; 12 | Authorization: 13 | - Bearer a67d4d9011f6d6c1dfe4169d838114d3d3849ab6df6fb1d322cf3ee91690fae4 14 | Content-Type: 15 | - application/vnd.contentful.delivery.v1+json 16 | Accept-Encoding: 17 | - gzip 18 | Connection: 19 | - close 20 | Host: 21 | - cdn.contentful.com 22 | User-Agent: 23 | - http.rb/2.2.2 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Access-Control-Allow-Headers: 30 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature 31 | Access-Control-Allow-Methods: 32 | - GET,HEAD,OPTIONS 33 | Access-Control-Allow-Origin: 34 | - "*" 35 | Access-Control-Expose-Headers: 36 | - Etag 37 | Access-Control-Max-Age: 38 | - '86400' 39 | Cache-Control: 40 | - max-age=0 41 | Content-Encoding: 42 | - gzip 43 | Content-Type: 44 | - application/vnd.contentful.delivery.v1+json 45 | Etag: 46 | - W/"16b325fc276a429445c1bb5f86871c51" 47 | Server: 48 | - Contentful 49 | X-Content-Type-Options: 50 | - nosniff 51 | X-Contentful-Request-Id: 52 | - 42ce7683a009a3589203be2d29e2713c 53 | Content-Length: 54 | - '397' 55 | Accept-Ranges: 56 | - bytes 57 | Date: 58 | - Wed, 18 Apr 2018 11:39:53 GMT 59 | Via: 60 | - 1.1 varnish 61 | Age: 62 | - '0' 63 | Connection: 64 | - close 65 | X-Served-By: 66 | - cache-hhn1549-HHN 67 | X-Cache: 68 | - MISS 69 | X-Cache-Hits: 70 | - '0' 71 | X-Timer: 72 | - S1524051594.639483,VS0,VE247 73 | Vary: 74 | - Accept-Encoding 75 | body: 76 | encoding: ASCII-8BIT 77 | string: !binary |- 78 | H4sIAAAAAAAAA8VTPW/CMBDd+RXIc6kSoC2woapMVReYWjGYxEEnHDu1DWqK+O/12UlwIlDpVC/Jfb57z+djr98nutRk1j/aX2uYsmDWInOlaEms73SHOUYayq0/dpbeQWGNyBkccjAYiiLvAMNy7PjhOvq+HRgHpQuaIFad4Z3BMOhwznqoVxA7gqDnY+HFblUNvXQdOwmQIp/p3ug83kbmKXtAWvU5Nf+OqD/E12RSBs2aKZ6lMEwYBxrUJIpRw9I5akGGUTwZRONBPFnF8Ww4nY0e76PR+D0s2Bfp3wqYOICSIrfgN8nmWWhDtyC2XVlu1fQlAP1VN8UOoEEKXIcquZGVpKALTssFMO6uRND8LKC3rHcRiE5SphMFhfEtScOBZNjkvGR4ba018tRbCJjToLyF2C5S67Es843kLbUIlwnl8M1w7IxyzcIVI4p97kFdCVrSdMOvBKV9OXZl6q6XVvECq8TvX3tER9XKVy1nO1hzW7Ev88/MKo5r98XHt+6dej+I71T9hwQAAA== 79 | http_version: 80 | recorded_at: Wed, 18 Apr 2018 11:39:54 GMT 81 | - request: 82 | method: get 83 | uri: https://cdn.contentful.com/spaces/9utsm1g0t7f5/environments/staging/entries 84 | body: 85 | encoding: US-ASCII 86 | string: '' 87 | headers: 88 | X-Contentful-User-Agent: 89 | - sdk contentful.rb/2.6.0; platform ruby/2.5.1; os macOS/16; 90 | Authorization: 91 | - Bearer a67d4d9011f6d6c1dfe4169d838114d3d3849ab6df6fb1d322cf3ee91690fae4 92 | Content-Type: 93 | - application/vnd.contentful.delivery.v1+json 94 | Accept-Encoding: 95 | - gzip 96 | Connection: 97 | - close 98 | Host: 99 | - cdn.contentful.com 100 | User-Agent: 101 | - http.rb/2.2.2 102 | response: 103 | status: 104 | code: 200 105 | message: OK 106 | headers: 107 | Access-Control-Allow-Headers: 108 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature 109 | Access-Control-Allow-Methods: 110 | - GET,HEAD,OPTIONS 111 | Access-Control-Allow-Origin: 112 | - "*" 113 | Access-Control-Expose-Headers: 114 | - Etag 115 | Access-Control-Max-Age: 116 | - '86400' 117 | Cache-Control: 118 | - max-age=0 119 | Content-Type: 120 | - application/vnd.contentful.delivery.v1+json 121 | Etag: 122 | - '"a59f5ecde9dc32b551b923b6121939a2"' 123 | Server: 124 | - Contentful 125 | X-Content-Type-Options: 126 | - nosniff 127 | X-Contentful-Request-Id: 128 | - d5f7331306705cc373bdfeb7d8593878 129 | Content-Length: 130 | - '907' 131 | Accept-Ranges: 132 | - bytes 133 | Date: 134 | - Wed, 18 Apr 2018 11:39:54 GMT 135 | Via: 136 | - 1.1 varnish 137 | Age: 138 | - '0' 139 | Connection: 140 | - close 141 | X-Served-By: 142 | - cache-hhn1536-HHN 143 | X-Cache: 144 | - MISS 145 | X-Cache-Hits: 146 | - '0' 147 | X-Timer: 148 | - S1524051594.976805,VS0,VE305 149 | Vary: 150 | - Accept-Encoding 151 | body: 152 | encoding: ASCII-8BIT 153 | string: | 154 | { 155 | "sys": { 156 | "type": "Array" 157 | }, 158 | "total": 1, 159 | "skip": 0, 160 | "limit": 100, 161 | "items": [ 162 | { 163 | "sys": { 164 | "space": { 165 | "sys": { 166 | "type": "Link", 167 | "linkType": "Space", 168 | "id": "9utsm1g0t7f5" 169 | } 170 | }, 171 | "id": "6yVdruR4GsKO2iKOqQS2CS", 172 | "type": "Entry", 173 | "createdAt": "2018-04-18T11:29:53.072Z", 174 | "updatedAt": "2018-04-18T11:29:53.072Z", 175 | "environment": { 176 | "sys": { 177 | "id": "staging", 178 | "type": "Link", 179 | "linkType": "Environment" 180 | } 181 | }, 182 | "revision": 1, 183 | "contentType": { 184 | "sys": { 185 | "type": "Link", 186 | "linkType": "ContentType", 187 | "id": "foo" 188 | } 189 | }, 190 | "locale": "en-US" 191 | }, 192 | "fields": { 193 | "name": "Test", 194 | "content": "Some content" 195 | } 196 | } 197 | ] 198 | } 199 | http_version: 200 | recorded_at: Wed, 18 Apr 2018 11:39:54 GMT 201 | recorded_with: VCR 2.9.3 202 | -------------------------------------------------------------------------------- /examples/templates/catalogue.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "contentTypes": [ 4 | { 5 | "id": "brand", 6 | "name": "Brand", 7 | "displayField": "name", 8 | "fields": [ 9 | { 10 | "id": "name", 11 | "name": "Company Name", 12 | "type": "Symbol" 13 | }, 14 | { 15 | "id": "website", 16 | "name": "Website", 17 | "type": "Symbol" 18 | }, 19 | { 20 | "id": "logo", 21 | "name": "Logo", 22 | "type": "Link", 23 | "linkType": "Asset" 24 | } 25 | ] 26 | }, 27 | { 28 | "id": "category", 29 | "name": "Category", 30 | "displayField": "title", 31 | "fields": [ 32 | { 33 | "id": "title", 34 | "name": "Title", 35 | "type": "Symbol" 36 | }, 37 | { 38 | "id": "description", 39 | "name": "Description", 40 | "type": "Text" 41 | }, 42 | { 43 | "id": "icon", 44 | "name": "Icon", 45 | "type": "Link", 46 | "linkType": "Asset" 47 | } 48 | ] 49 | }, 50 | { 51 | "id": "product", 52 | "name": "Product", 53 | "displayField": "name", 54 | "fields": [ 55 | { 56 | "id": "name", 57 | "name": "name", 58 | "type": "Symbol" 59 | }, 60 | { 61 | "id": "description", 62 | "name": "Description", 63 | "type": "Text" 64 | }, 65 | { 66 | "id": "image", 67 | "name": "Image", 68 | "type": "Link", 69 | "linkType": "Asset" 70 | }, 71 | { 72 | "id": "brand", 73 | "name": "Brand", 74 | "type": "Link", 75 | "linkType": "Entry" 76 | }, 77 | { 78 | "id": "category", 79 | "name": "Category", 80 | "type": "Link", 81 | "linkType": "Entry" 82 | }, 83 | { 84 | "id": "url", 85 | "name": "Available at", 86 | "type": "Symbol" 87 | } 88 | ] 89 | } 90 | ], 91 | "assets": [ 92 | { 93 | "id": "playsam_image", 94 | "title": "Playsam", 95 | "file": { 96 | "filename": "playsam_image", 97 | "url": "https://images.contentful.com/liicpxzmg1q0/4zj1ZOfHgQ8oqgaSKm4Qo2/3be82d54d45b5297e951aee9baf920da/playsam.jpg?h=250&" 98 | } 99 | }, 100 | { 101 | "id": "normann_image", 102 | "title": "Normann", 103 | "file": { 104 | "filename": "normann_image", 105 | "url": "https://images.contentful.com/liicpxzmg1q0/3wtvPBbBjiMKqKKga8I2Cu/75c7c92f38f7953a741591d215ad61d4/zJYzDlGk.jpeg?h=250&" 106 | } 107 | }, 108 | { 109 | "id": "toy_image", 110 | "title": "Toys", 111 | "file": { 112 | "filename": "toy_image", 113 | "url": "https://images.contentful.com/liicpxzmg1q0/6t4HKjytPi0mYgs240wkG/866ef53a11af9c6bf5f3808a8ce1aab2/toys_512pxGREY.png?h=250&" 114 | } 115 | }, 116 | { 117 | "id": "kitchen_image", 118 | "title": "Kitchen and Home", 119 | "file": { 120 | "filename": "kitchen_image", 121 | "url": "https://images.contentful.com/liicpxzmg1q0/6m5AJ9vMPKc8OUoQeoCS4o/ffc20f5a8f2a71cca4801bc9c51b966a/1418244847_Streamline-18-256.png?h=250&" 122 | } 123 | }, 124 | { 125 | "id": "toy_car", 126 | "title": "Playsam Toy Car", 127 | "file": { 128 | "filename": "toy_car", 129 | "url": "https://images.contentful.com/liicpxzmg1q0/wtrHxeu3zEoEce2MokCSi/acef70d12fe019228c4238aa791bdd48/quwowooybuqbl6ntboz3.jpg?h=250&" 130 | } 131 | }, 132 | { 133 | "id": "whiskers", 134 | "title": "Normann Whisk Beaters", 135 | "file": { 136 | "filename": "whiskers", 137 | "url": "https://images.contentful.com/liicpxzmg1q0/10TkaLheGeQG6qQGqWYqUI/d510dde5e575d40288cf75b42383aa53/ryugj83mqwa1asojwtwb.jpg?h=250&" 138 | } 139 | } 140 | ], 141 | "entries": { 142 | "brand": [ 143 | { 144 | "sys": { 145 | "id": "playsam" 146 | }, 147 | "fields": { 148 | "name": "Playsam, Inc", 149 | "website": "http://www.playsam.com", 150 | "logo": { 151 | "linkType": "Asset", 152 | "id": "playsam_image" 153 | } 154 | } 155 | }, 156 | { 157 | "sys": { 158 | "id": "normann" 159 | }, 160 | "fields": { 161 | "name": "Normann Copenhagen, Inc", 162 | "website": "http://www.normann-copenhagen.com/", 163 | "logo": { 164 | "linkType": "Asset", 165 | "id": "normann_image" 166 | } 167 | } 168 | } 169 | ], 170 | "category": [ 171 | { 172 | "sys": { 173 | "id": "toys" 174 | }, 175 | "fields": { 176 | "title": "Toys", 177 | "description": "Toys for children", 178 | "icon": { 179 | "linkType": "Asset", 180 | "id": "toy_image" 181 | } 182 | } 183 | }, 184 | { 185 | "sys": { 186 | "id": "kitchen" 187 | }, 188 | "fields": { 189 | "title": "House and Kitchen", 190 | "description": "House and Kitchen accessories", 191 | "icon": { 192 | "linkType": "Asset", 193 | "id": "kitchen_image" 194 | } 195 | } 196 | } 197 | ], 198 | "product": [ 199 | { 200 | "sys": { 201 | "id": "playsam_car" 202 | }, 203 | "fields": { 204 | "name": "Playsam Streamliner Classic Car, Espresso", 205 | "description": "A classic Playsam design, the Streamliner Classic Car has been selected as Swedish Design Classic by the Swedish National Museum for its inventive style and sleek surface. It's no wonder that this wooden car has also been a long-standing favorite for children both big and small!", 206 | "image": { 207 | "linkType": "Asset", 208 | "id": "toy_car" 209 | }, 210 | "brand": { 211 | "linkType": "Entry", 212 | "id": "playsam" 213 | }, 214 | "category": { 215 | "linkType": "Entry", 216 | "id": "toys" 217 | }, 218 | "url": "http://www.amazon.com/dp/B001R6JUZ2/" 219 | } 220 | }, 221 | { 222 | "sys": { 223 | "id": "whisk_beater" 224 | }, 225 | "fields": { 226 | "name": "Whisk Beater", 227 | "description": "A creative little whisk that comes in 8 different colors. Handy and easy to clean after use. A great gift idea.", 228 | "image": { 229 | "linkType": "Asset", 230 | "id": "whiskers" 231 | }, 232 | "brand": { 233 | "linkType": "Entry", 234 | "id": "normann" 235 | }, 236 | "category": { 237 | "linkType": "Entry", 238 | "id": "kitchen" 239 | }, 240 | "url": "http://www.amazon.com/dp/B0081F2CCK/" 241 | } 242 | } 243 | ] 244 | } 245 | } 246 | 247 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/base.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'contentful/management' 3 | require 'contentful/bootstrap/templates/links/base' 4 | 5 | module Contentful 6 | module Bootstrap 7 | module Templates 8 | class Base 9 | attr_reader :environment, :skip_content_types 10 | 11 | def initialize(space, environment_id = 'master', quiet = false, skip_content_types = false, no_publish = false) 12 | @environment = space.environments.find(environment_id) 13 | @quiet = quiet 14 | @skip_content_types = skip_content_types 15 | @no_publish = no_publish 16 | end 17 | 18 | def run 19 | create_content_types unless skip_content_types 20 | create_assets 21 | create_entries 22 | 23 | after_run 24 | rescue Contentful::Management::Error => e 25 | error = e.error 26 | output "Error at: #{error[:url]}" 27 | output "Message: #{error[:message]}" 28 | output "Details: #{error[:details]}" 29 | 30 | raise e 31 | end 32 | 33 | def content_types 34 | [] 35 | end 36 | 37 | def entries 38 | {} 39 | end 40 | 41 | def assets 42 | [] 43 | end 44 | 45 | def after_run; end 46 | 47 | protected 48 | 49 | def output(text = nil) 50 | Support.output(text, @quiet) 51 | end 52 | 53 | def create_file(name, url, properties = {}) 54 | image = Contentful::Management::File.new 55 | image.properties[:contentType] = properties.fetch(:contentType, 'image/jpeg') 56 | image.properties[:fileName] = name.to_s 57 | image.properties[:upload] = url 58 | image 59 | end 60 | 61 | private 62 | 63 | def create_content_types 64 | content_types.each do |ct| 65 | begin 66 | output "Creating Content Type '#{ct['name']}'" 67 | 68 | fields = [] 69 | ct['fields'].each do |f| 70 | field = Contentful::Management::Field.new 71 | field.id = f['id'] 72 | field.name = f['name'] 73 | field.type = f['type'] 74 | field.link_type = f['linkType'] if link?(f) 75 | 76 | if array_field?(f) 77 | array_field = Contentful::Management::Field.new 78 | array_field.type = f['items']['type'] 79 | array_field.link_type = f['items']['linkType'] 80 | field.items = array_field 81 | end 82 | 83 | fields << field 84 | end 85 | 86 | content_type = environment.content_types.create( 87 | id: ct['id'], 88 | name: ct['name'], 89 | displayField: ct['displayField'], 90 | description: ct['description'], 91 | fields: fields 92 | ) 93 | 94 | content_type.activate 95 | rescue Contentful::Management::Conflict 96 | output "ContentType '#{ct['id']}' already created! Skipping" 97 | next 98 | end 99 | end 100 | end 101 | 102 | def link?(field) 103 | field.key?('linkType') 104 | end 105 | 106 | def array_field?(field) 107 | field.key?('items') 108 | end 109 | 110 | def create_assets 111 | assets.each do |asset_json| 112 | begin 113 | output "Creating Asset '#{asset_json['title']}'" 114 | asset = environment.assets.create( 115 | id: asset_json['id'], 116 | title: asset_json['title'], 117 | file: asset_json['file'] 118 | ) 119 | asset.process_file 120 | rescue Contentful::Management::Conflict 121 | output "Asset '#{asset_json['id']}' already created! Updating instead." 122 | 123 | asset = environment.assets.find(asset_json['id']).tap do |a| 124 | a.title = asset_json['title'] 125 | a.file = asset_json['file'] 126 | end 127 | 128 | asset.save 129 | asset.process_file 130 | end 131 | end 132 | 133 | assets.each do |asset_json| 134 | attempts = 0 135 | while attempts < 10 136 | asset = environment.assets.find(asset_json['id']) 137 | unless asset.file.url.nil? 138 | asset.publish unless @no_publish 139 | break 140 | end 141 | 142 | sleep(1) # Wait for Process 143 | attempts += 1 144 | end 145 | end 146 | end 147 | 148 | def create_entries 149 | content_types = [] 150 | processed_entries = entries.map do |content_type_id, entry_list| 151 | content_type = environment.content_types.find(content_type_id) 152 | content_types << content_type 153 | 154 | entry_list.each.map do |e| 155 | array_fields = [] 156 | regular_fields = [] 157 | e.each do |field_name, value| 158 | if value.is_a? ::Array 159 | array_fields << field_name 160 | next 161 | end 162 | 163 | regular_fields << field_name 164 | end 165 | 166 | array_fields.each do |af| 167 | e[af].map! do |item| 168 | if item.is_a? ::Contentful::Bootstrap::Templates::Links::Base 169 | item.to_management_object 170 | else 171 | item 172 | end 173 | end 174 | e[af.to_sym] = e.delete(af) 175 | end 176 | 177 | regular_fields.each do |rf| 178 | value = e.delete(rf) 179 | value = value.to_management_object if value.is_a? ::Contentful::Bootstrap::Templates::Links::Base 180 | e[rf.to_sym] = value 181 | end 182 | 183 | begin 184 | output "Creating Entry #{e[:id]}" 185 | entry = content_type.entries.create(id: e[:id]) 186 | entry.save 187 | 188 | e = e.clone 189 | e[:id] = entry.id # in case no ID was specified in template 190 | rescue Contentful::Management::Conflict 191 | output "Entry '#{e[:id]}' already exists! Skipping" 192 | ensure 193 | next e 194 | end 195 | end 196 | end.flatten 197 | 198 | processed_entries = processed_entries.map do |e| 199 | output "Populating Entry #{e[:id]}" 200 | 201 | entry = environment.entries.find(e[:id]) 202 | e.delete(:id) 203 | entry.update(e) 204 | entry.save 205 | 206 | 10.times do 207 | break if environment.entries.find(entry.id).sys[:version] >= 4 208 | sleep(0.5) 209 | end 210 | 211 | entry.id 212 | end 213 | 214 | processed_entries.each do |e| 215 | output "Publishing Entry #{e}" 216 | environment.entries.find(e).publish 217 | end unless @no_publish 218 | end 219 | end 220 | end 221 | end 222 | end 223 | -------------------------------------------------------------------------------- /lib/contentful/bootstrap/templates/catalogue.rb: -------------------------------------------------------------------------------- 1 | require 'contentful/bootstrap/templates/base' 2 | require 'contentful/bootstrap/templates/links' 3 | 4 | module Contentful 5 | module Bootstrap 6 | module Templates 7 | class Catalogue < Base 8 | def content_types 9 | [ 10 | { 11 | 'id' => 'brand', 12 | 'name' => 'Brand', 13 | 'displayField' => 'name', 14 | 'fields' => [ 15 | { 16 | 'id' => 'name', 17 | 'name' => 'Company Name', 18 | 'type' => 'Symbol' 19 | }, 20 | { 21 | 'id' => 'website', 22 | 'name' => 'Website', 23 | 'type' => 'Symbol' 24 | }, 25 | { 26 | 'id' => 'logo', 27 | 'name' => 'Logo', 28 | 'type' => 'Link', 29 | 'linkType' => 'Asset' 30 | } 31 | ] 32 | }, 33 | { 34 | 'id' => 'category', 35 | 'name' => 'Category', 36 | 'displayField' => 'title', 37 | 'fields' => [ 38 | { 39 | 'id' => 'title', 40 | 'name' => 'Title', 41 | 'type' => 'Symbol' 42 | }, 43 | { 44 | 'id' => 'description', 45 | 'name' => 'Description', 46 | 'type' => 'Text' 47 | }, 48 | { 49 | 'id' => 'icon', 50 | 'name' => 'Icon', 51 | 'type' => 'Link', 52 | 'linkType' => 'Asset' 53 | } 54 | ] 55 | }, 56 | { 57 | 'id' => 'product', 58 | 'name' => 'Product', 59 | 'displayField' => 'name', 60 | 'fields' => [ 61 | { 62 | 'id' => 'name', 63 | 'name' => 'name', 64 | 'type' => 'Symbol' 65 | }, 66 | { 67 | 'id' => 'description', 68 | 'name' => 'Description', 69 | 'type' => 'Text' 70 | }, 71 | { 72 | 'id' => 'image', 73 | 'name' => 'Image', 74 | 'type' => 'Link', 75 | 'linkType' => 'Asset' 76 | }, 77 | { 78 | 'id' => 'brand', 79 | 'name' => 'Brand', 80 | 'type' => 'Link', 81 | 'linkType' => 'Entry' 82 | }, 83 | { 84 | 'id' => 'category', 85 | 'name' => 'Category', 86 | 'type' => 'Link', 87 | 'linkType' => 'Entry' 88 | }, 89 | { 90 | 'id' => 'url', 91 | 'name' => 'Available at', 92 | 'type' => 'Symbol' 93 | } 94 | ] 95 | } 96 | ] 97 | end 98 | 99 | def assets 100 | [ 101 | { 102 | 'id' => 'playsam_image', 103 | 'title' => 'Playsam', 104 | 'file' => create_file('playsam_image.jpg', 'https://images.contentful.com/liicpxzmg1q0/4zj1ZOfHgQ8oqgaSKm4Qo2/3be82d54d45b5297e951aee9baf920da/playsam.jpg?h=250&') 105 | }, 106 | { 107 | 'id' => 'normann_image', 108 | 'title' => 'Normann', 109 | 'file' => create_file('normann_image.jpg', 'https://images.contentful.com/liicpxzmg1q0/3wtvPBbBjiMKqKKga8I2Cu/75c7c92f38f7953a741591d215ad61d4/zJYzDlGk.jpeg?h=250&') 110 | }, 111 | { 112 | 'id' => 'toy_image', 113 | 'title' => 'Toys', 114 | 'file' => create_file('toy_image.jpg', 'https://images.contentful.com/liicpxzmg1q0/6t4HKjytPi0mYgs240wkG/866ef53a11af9c6bf5f3808a8ce1aab2/toys_512pxGREY.png?h=250&') 115 | }, 116 | { 117 | 'id' => 'kitchen_image', 118 | 'title' => 'Kitchen and Home', 119 | 'file' => create_file('kitchen_image.jpg', 'https://images.contentful.com/liicpxzmg1q0/6m5AJ9vMPKc8OUoQeoCS4o/ffc20f5a8f2a71cca4801bc9c51b966a/1418244847_Streamline-18-256.png?h=250&') 120 | }, 121 | { 122 | 'id' => 'toy_car', 123 | 'title' => 'Playsam Toy Car', 124 | 'file' => create_file('toy_car.jpg', 'https://images.contentful.com/liicpxzmg1q0/wtrHxeu3zEoEce2MokCSi/acef70d12fe019228c4238aa791bdd48/quwowooybuqbl6ntboz3.jpg?h=250&') 125 | }, 126 | { 127 | 'id' => 'whiskers', 128 | 'title' => 'Normann Whisk Beaters', 129 | 'file' => create_file('whiskers.jpg', 'https://images.contentful.com/liicpxzmg1q0/10TkaLheGeQG6qQGqWYqUI/d510dde5e575d40288cf75b42383aa53/ryugj83mqwa1asojwtwb.jpg?h=250&') 130 | } 131 | ] 132 | end 133 | 134 | def entries 135 | { 136 | 'brand' => [ 137 | { 138 | 'id' => 'playsam', 139 | 'name' => 'Playsam, Inc', 140 | 'website' => 'http://www.playsam.com', 141 | 'logo' => Links::Asset.new('playsam_image') 142 | }, 143 | { 144 | 'id' => 'normann', 145 | 'name' => 'Normann Copenhagen, Inc', 146 | 'website' => 'http://www.normann-copenhagen.com/', 147 | 'logo' => Links::Asset.new('normann_image') 148 | } 149 | ], 150 | 'category' => [ 151 | { 152 | 'id' => 'toys', 153 | 'title' => 'Toys', 154 | 'description' => 'Toys for children', 155 | 'icon' => Links::Asset.new('toy_image') 156 | }, 157 | { 158 | 'id' => 'kitchen', 159 | 'title' => 'House and Kitchen', 160 | 'description' => 'House and Kitchen accessories', 161 | 'icon' => Links::Asset.new('kitchen_image') 162 | } 163 | ], 164 | 'product' => [ 165 | { 166 | 'id' => 'playsam_car', 167 | 'name' => 'Playsam Streamliner Classic Car, Espresso', 168 | 'description' => 'A classic Playsam design, the Streamliner Classic Car has been selected as Swedish Design Classic by the Swedish National Museum for its inventive style and sleek surface. It\'s no wonder that this wooden car has also been a long-standing favorite for children both big and small!', 169 | 'image' => Links::Asset.new('toy_car'), 170 | 'brand' => Links::Entry.new('playsam'), 171 | 'category' => Links::Entry.new('toys'), 172 | 'url' => 'http://www.amazon.com/dp/B001R6JUZ2/' 173 | }, 174 | { 175 | 'id' => 'whisk_beater', 176 | 'name' => 'Whisk Beater', 177 | 'description' => 'A creative little whisk that comes in 8 different colors. Handy and easy to clean after use. A great gift idea.', 178 | 'image' => Links::Asset.new('whiskers'), 179 | 'brand' => Links::Entry.new('normann'), 180 | 'category' => Links::Entry.new('kitchen'), 181 | 'url' => 'http://www.amazon.com/dp/B0081F2CCK/' 182 | } 183 | ] 184 | } 185 | end 186 | end 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /spec/fixtures/vcr_fixtures/multiple_organizations.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api.contentful.com/spaces 6 | body: 7 | encoding: UTF-8 8 | string: '{"name":"foo","defaultLocale":"en-US"}' 9 | headers: 10 | X-Contentful-User-Agent: 11 | - sdk contentful-management.rb/1.10.0; integration bootstrap/3.7.0; platform 12 | ruby/2.4.1; os macOS/16; 13 | Authorization: 14 | - Bearer foo 15 | Content-Type: 16 | - application/vnd.contentful.management.v1+json 17 | Connection: 18 | - close 19 | Host: 20 | - api.contentful.com 21 | User-Agent: 22 | - http.rb/2.2.2 23 | response: 24 | status: 25 | code: 404 26 | message: Not Found 27 | headers: 28 | Accept-Ranges: 29 | - bytes 30 | Access-Control-Allow-Headers: 31 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Experimental-Feature 32 | Access-Control-Allow-Methods: 33 | - DELETE,GET,HEAD,POST,PUT,OPTIONS 34 | Access-Control-Allow-Origin: 35 | - "*" 36 | Access-Control-Expose-Headers: 37 | - Etag 38 | Access-Control-Max-Age: 39 | - '1728000' 40 | Cache-Control: 41 | - max-age=0 42 | Content-Type: 43 | - application/vnd.contentful.management.v1+json 44 | Date: 45 | - Wed, 08 Nov 2017 09:43:18 GMT 46 | Server: 47 | - Contentful 48 | Set-Cookie: 49 | - _auth_new_session=ac54e1df2700ab38d711c6bed86b8598; path=/; expires=Wed, 22 50 | Nov 2017 09:43:18 -0000; secure; HttpOnly 51 | - incap_ses_408_673446=diigVgCKkncIdqEZxIKpBbbRAloAAAAAjLj7VcFHL5qxg9P593j6sQ==; 52 | path=/; Domain=.contentful.com 53 | - nlbi_673446=yMlRcftUokXK5lq96lKYhQAAAAC4qCDoH1n9W8NuANYcytI+; path=/; Domain=.contentful.com 54 | - visid_incap_673446=SkWRQ4PkT9abJpCX7ieh/rbRAloAAAAAQUIPAAAAAAA8KRGJif+0o9Hi2xP/5W8C; 55 | expires=Wed, 07 Nov 2018 21:13:21 GMT; path=/; Domain=.contentful.com 56 | Strict-Transport-Security: 57 | - max-age=15768000 58 | X-Content-Type-Options: 59 | - nosniff 60 | X-Contentful-Request-Id: 61 | - 5d3cb1a9281d9d3bb95293024d459af3 62 | X-Frame-Options: 63 | - ALLOWALL 64 | X-Xss-Protection: 65 | - 1; mode=block 66 | Content-Length: 67 | - '285' 68 | Connection: 69 | - Close 70 | X-Iinfo: 71 | - 3-3465080-3465084 NNNN CT(100 92 0) RT(1510134197914 31) q(0 0 2 -1) r(3 3) 72 | U5 73 | X-Cdn: 74 | - Incapsula 75 | body: 76 | encoding: ASCII-8BIT 77 | string: '{"requestId":"5d3cb1a9281d9d3bb95293024d459af3","message":"This User 78 | has multiple Organizations with permissions to create a Space. Please pass 79 | the X-Contentful-Organization header in which Organization to create the Space.","sys":{"type":"Error","id":"MissingOrganizationParameter"}} 80 | 81 | ' 82 | http_version: 83 | recorded_at: Wed, 08 Nov 2017 09:43:18 GMT 84 | - request: 85 | method: get 86 | uri: https://api.contentful.com/organizations 87 | body: 88 | encoding: US-ASCII 89 | string: '' 90 | headers: 91 | X-Contentful-User-Agent: 92 | - sdk contentful-management.rb/1.10.0; integration bootstrap/3.7.0; platform 93 | ruby/2.4.1; os macOS/16; 94 | Authorization: 95 | - Bearer foo 96 | Content-Type: 97 | - application/vnd.contentful.management.v1+json 98 | Connection: 99 | - close 100 | Host: 101 | - api.contentful.com 102 | User-Agent: 103 | - http.rb/2.2.2 104 | response: 105 | status: 106 | code: 200 107 | message: OK 108 | headers: 109 | Accept-Ranges: 110 | - bytes 111 | Access-Control-Allow-Headers: 112 | - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Experimental-Feature 113 | Access-Control-Allow-Methods: 114 | - DELETE,GET,HEAD,POST,PUT,OPTIONS 115 | Access-Control-Allow-Origin: 116 | - "*" 117 | Access-Control-Expose-Headers: 118 | - Etag 119 | Access-Control-Max-Age: 120 | - '1728000' 121 | Cache-Control: 122 | - max-age=0 123 | Content-Type: 124 | - application/vnd.contentful.management.v1+json 125 | Date: 126 | - Wed, 08 Nov 2017 09:43:19 GMT 127 | Etag: 128 | - W/"44c5cb3db8991a0968f988a1ba73470a" 129 | Server: 130 | - Contentful 131 | Strict-Transport-Security: 132 | - max-age=15768000 133 | X-Content-Type-Options: 134 | - nosniff 135 | X-Contentful-Request-Id: 136 | - fe4ada72e4e21d4ce196ea177cc538fb 137 | X-Frame-Options: 138 | - ALLOWALL 139 | X-Xss-Protection: 140 | - 1; mode=block 141 | Content-Length: 142 | - '1595' 143 | Connection: 144 | - Close 145 | Set-Cookie: 146 | - incap_ses_408_673446=+wAibagqxUBgdqEZxIKpBbbRAloAAAAANWD79znTuGxbfDBOJImupw==; 147 | path=/; Domain=.contentful.com 148 | - nlbi_673446=igoOWQaRGS189dtW6lKYhQAAAADk7QU5KcgD4rxu8Gv7kRku; path=/; Domain=.contentful.com 149 | - visid_incap_673446=4JhySUJERPCjGygLVEdeNLbRAloAAAAAQUIPAAAAAAA6ypi8lhIjQOEQge8NzJhr; 150 | expires=Wed, 07 Nov 2018 21:13:21 GMT; path=/; Domain=.contentful.com 151 | X-Iinfo: 152 | - 3-3465136-3465142 NNNN CT(95 91 0) RT(1510134198306 34) q(0 0 2 -1) r(3 3) 153 | U5 154 | X-Cdn: 155 | - Incapsula 156 | body: 157 | encoding: ASCII-8BIT 158 | string: '{"total":9,"limit":25,"skip":0,"sys":{"type":"Array"},"items":[ 159 | {"name":"foo1","sys":{"type":"Organization","id":"foo1","version":55,"createdAt":"2013-05-06T07:53:34Z","updatedAt":"2017-06-30T11:57:20Z"}}, 160 | {"name":"foo2","sys":{"type":"Organization","id":"foo2","version":22,"createdAt":"2015-01-07T16:26:56Z","updatedAt":"2016-06-28T08:58:37Z"}}, 161 | {"name":"foo3","sys":{"type":"Organization","id":"foo3","version":14,"createdAt":"2015-09-17T23:13:27Z","updatedAt":"2016-06-28T08:58:39Z"}}, 162 | {"name":"foo4","sys":{"type":"Organization","id":"foo4","version":5,"createdAt":"2015-12-15T13:09:13Z","updatedAt":"2017-11-01T12:12:05Z"}}, 163 | {"name":"foo5","sys":{"type":"Organization","id":"foo5","version":0,"createdAt":"2016-03-11T18:00:48Z","updatedAt":"2016-03-11T18:00:48Z"}}, 164 | {"name":"foo6","sys":{"type":"Organization","id":"foo6","version":1,"createdAt":"2017-01-09T09:20:00Z","updatedAt":"2017-01-09T10:21:02Z"}}, 165 | {"name":"foo7","sys":{"type":"Organization","id":"foo7","version":0,"createdAt":"2017-04-19T08:59:40Z","updatedAt":"2017-04-19T08:59:40Z"}}, 166 | {"name":"foo8","sys":{"type":"Organization","id":"foo8","version":1,"createdAt":"2017-08-08T12:53:09Z","updatedAt":"2017-08-28T11:33:05Z"}}, 167 | {"name":"foo9","sys":{"type":"Organization","id":"foo9","version":0,"createdAt":"2017-11-01T12:11:30Z","updatedAt":"2017-11-01T12:11:30Z"}}]}' 168 | http_version: 169 | recorded_at: Wed, 08 Nov 2017 09:43:19 GMT 170 | recorded_with: VCR 2.9.3 171 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/commands/update_space_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Commands::UpdateSpace do 4 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 5 | let(:token) { Contentful::Bootstrap::Token.new path } 6 | subject { described_class.new token, 'foo', environment: 'master', json_template: 'bar', mark_processed: false, trigger_oauth: false, quiet: true } 7 | let(:space_double) { SpaceDouble.new } 8 | 9 | before do 10 | allow(::File).to receive(:write) 11 | end 12 | 13 | describe 'instance methods' do 14 | describe '#run' do 15 | it 'with all non nil attributes' do 16 | expect(subject).to receive(:fetch_space) { space_double } 17 | expect(subject).to receive(:update_json_template).with(space_double) 18 | 19 | expect(subject.run).to eq(space_double) 20 | end 21 | 22 | it 'exits if JSON not sent' do 23 | update_space_command = described_class.new(token, 'foo', mark_processed: false, quiet: true) 24 | 25 | expect { update_space_command.run }.to raise_error SystemExit 26 | end 27 | 28 | it 'exits if space is not found' do 29 | update_space_command = described_class.new(token, 'foo', json_template: 'bar', quiet: true) 30 | 31 | expect_any_instance_of(::Contentful::Management::ClientSpaceMethodsFactory).to receive(:find).with('foo').and_raise(::Contentful::Management::NotFound.new(ErrorRequestDouble.new)) 32 | 33 | expect { update_space_command.run }.to raise_error SystemExit 34 | end 35 | 36 | it 'exits if JSON template is not an existing file' do 37 | expect(subject).to receive(:fetch_space) { space_double } 38 | 39 | expect { subject.run }.to raise_error SystemExit 40 | end 41 | 42 | describe 'runs JSON Template without already processed elements' do 43 | [true, false].each do |mark_processed| 44 | context "mark_processed is #{mark_processed}" do 45 | subject { described_class.new token, 'foo', environment: 'master', json_template: 'bar', mark_processed: mark_processed, trigger_oauth: false, quiet: true} 46 | 47 | it "calls JsonTemplate with mark_processed as #{mark_processed}" do 48 | allow(::File).to receive(:exist?) { true } 49 | 50 | mock_template = Object.new 51 | 52 | expect(subject).to receive(:fetch_space) { space_double } 53 | expect(mock_template).to receive(:run) 54 | 55 | expect(::Contentful::Bootstrap::Templates::JsonTemplate).to receive(:new).with(space_double, 'bar', 'master', mark_processed, true, true, false, false) { mock_template } 56 | 57 | subject.run 58 | end 59 | end 60 | end 61 | end 62 | 63 | context 'with skip_content_types set to true' do 64 | subject { described_class.new token, 'foo', json_template: 'bar', trigger_oauth: false, skip_content_types: true, quiet: true } 65 | 66 | it 'calls JsonTemplate with skip_content_types' do 67 | allow(::File).to receive(:exist?) { true } 68 | 69 | mock_template = Object.new 70 | 71 | expect(subject).to receive(:fetch_space) { space_double } 72 | expect(mock_template).to receive(:run) 73 | 74 | expect(::Contentful::Bootstrap::Templates::JsonTemplate).to receive(:new).with(space_double, 'bar', 'master', false, true, true, true, false) { mock_template } 75 | 76 | subject.run 77 | end 78 | end 79 | 80 | context 'with no_publish set to true' do 81 | subject { described_class.new token, 'foo', environment: 'master', json_template: 'bar', trigger_oauth: false, skip_content_types: true, quiet: true, no_publish: true } 82 | 83 | it 'calls JsonTemplate with no_publish' do 84 | allow(::File).to receive(:exist?) { true } 85 | 86 | mock_template = Object.new 87 | 88 | expect(subject).to receive(:fetch_space) { space_double } 89 | expect(mock_template).to receive(:run) 90 | 91 | expect(::Contentful::Bootstrap::Templates::JsonTemplate).to receive(:new).with(space_double, 'bar', 'master', false, true, true, true, true) { mock_template } 92 | 93 | subject.run 94 | end 95 | end 96 | end 97 | end 98 | 99 | describe 'attributes' do 100 | it ':json_template' do 101 | expect(subject.json_template).to eq 'bar' 102 | end 103 | end 104 | 105 | describe 'integration' do 106 | it 'can update localized spaces' do 107 | vcr('update_space_localized') { 108 | json_path = File.expand_path(File.join('spec', 'fixtures', 'json_fixtures', 'update_space_localized.json')) 109 | subject = described_class.new(token, 'vsy1ouf6jdcq', environment: 'master', locale: 'es-AR', json_template: json_path, mark_processed: false, trigger_oauth: false, quiet: true) 110 | 111 | subject.run 112 | } 113 | 114 | vcr('check_update_space_localized') { 115 | client = Contentful::Client.new( 116 | space: 'vsy1ouf6jdcq', 117 | access_token: '90e1b4964c3631cc9c751c42339814635623b001a53aec5aad23377299445433', 118 | dynamic_entries: :auto, 119 | raise_errors: true 120 | ) 121 | 122 | entries = client.entries(locale: 'es-AR') 123 | 124 | expect(entries.map(&:text)).to eq ['Foo', 'Bar'] 125 | } 126 | end 127 | 128 | it 'can update an existing asset and keep it as draft' do 129 | vcr('update_existing_asset') { 130 | json_path = File.expand_path(File.join('spec', 'fixtures', 'json_fixtures', 'assets_draft.json')) 131 | subject = described_class.new(token, 'f3abi4dqvrhg', environment: 'master', json_template: json_path, no_publish: true, trigger_oauth: false, quiet: true) 132 | 133 | subject.run 134 | } 135 | 136 | vcr('check_update_space_with_draft_content') { 137 | delivery_client = Contentful::Client.new( 138 | space: 'f3abi4dqvrhg', 139 | access_token: 'efab52abe735b200abb0f053ad8a3d0da633487c0c98cf03dc806c2b3bd049a1', 140 | dynamic_entries: :auto, 141 | raise_errors: true 142 | ) 143 | 144 | preview_client = Contentful::Client.new( 145 | space: 'f3abi4dqvrhg', 146 | access_token: '06c28ef41823bb636714dfd812066fa026a49e95041a0e94903d6cf016bba50e', 147 | dynamic_entries: :auto, 148 | api_url: 'preview.contentful.com', 149 | raise_errors: true 150 | ) 151 | 152 | delivery_cat = delivery_client.assets.first 153 | preview_cat = preview_client.assets.first 154 | 155 | expect(preview_cat.title).not_to eq delivery_cat 156 | expect(preview_cat.title).to eq 'Cat' 157 | expect(delivery_cat.title).to eq 'Foo' 158 | } 159 | end 160 | 161 | it 'can update a specific environment different than master' do 162 | delivery_client = nil 163 | vcr('check_original_staging_environment_status') { 164 | delivery_client = Contentful::Client.new( 165 | space: '9utsm1g0t7f5', 166 | access_token: 'a67d4d9011f6d6c1dfe4169d838114d3d3849ab6df6fb1d322cf3ee91690fae4', 167 | environment: 'staging', 168 | dynamic_entries: :auto, 169 | raise_errors: true 170 | ) 171 | 172 | expect(delivery_client.entries.size).to eq 1 173 | } 174 | 175 | vcr('update_with_environment') { 176 | json_path = File.expand_path(File.join('spec', 'fixtures', 'json_fixtures', 'environment_template.json')) 177 | subject = described_class.new(token, '9utsm1g0t7f5', environment: 'staging', json_template: json_path, trigger_oauth: false, quiet: true) 178 | 179 | subject.run 180 | } 181 | 182 | vcr('check_staging_environment_status') { 183 | entries = delivery_client.entries 184 | expect(entries.size).to eq 2 185 | expect(entries.items.detect { |i| i.id == 'foo_update' }.name).to eq 'Test updated' 186 | } 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /spec/contentful/bootstrap/commands/create_space_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Contentful::Bootstrap::Commands::CreateSpace do 4 | let(:path) { File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'contentfulrc.ini')) } 5 | let(:token) { Contentful::Bootstrap::Token.new path } 6 | subject { described_class.new token, 'foo', template: 'bar', json_template: 'baz', trigger_oauth: false, quiet: true } 7 | let(:space_double) { SpaceDouble.new } 8 | 9 | before do 10 | allow(::File).to receive(:write) 11 | end 12 | 13 | describe 'instance methods' do 14 | describe '#run' do 15 | it 'with all non nil attributes' do 16 | expect(subject).to receive(:fetch_space) { space_double } 17 | expect(subject).to receive(:create_template).with(space_double) 18 | expect(subject).to receive(:create_json_template).with(space_double) 19 | expect(subject).to receive(:generate_token).with(space_double) 20 | 21 | subject.run 22 | end 23 | 24 | it 'does not create template when template_name is nil' do 25 | create_space_command = described_class.new(token, 'foo', json_template: 'baz', trigger_oauth: false, quiet: true) 26 | 27 | expect(create_space_command.template_name).to eq nil 28 | 29 | expect(create_space_command).to receive(:fetch_space) { space_double } 30 | expect(create_space_command).not_to receive(:create_template).with(space_double) 31 | expect(create_space_command).to receive(:create_json_template).with(space_double) 32 | expect(create_space_command).to receive(:generate_token).with(space_double) 33 | 34 | create_space_command.run 35 | end 36 | 37 | it 'does not create json template when json_template is nil' do 38 | create_space_command = described_class.new(token, 'foo', template: 'bar', trigger_oauth: false, quiet: true) 39 | 40 | expect(create_space_command.json_template).to eq nil 41 | 42 | expect(create_space_command).to receive(:fetch_space) { space_double } 43 | expect(create_space_command).to receive(:create_template).with(space_double) 44 | expect(create_space_command).not_to receive(:create_json_template).with(space_double) 45 | expect(create_space_command).to receive(:generate_token).with(space_double) 46 | 47 | create_space_command.run 48 | end 49 | end 50 | end 51 | 52 | describe 'attributes' do 53 | it ':template_name' do 54 | expect(subject.template_name).to eq 'bar' 55 | end 56 | 57 | it ':json_template' do 58 | expect(subject.json_template).to eq 'baz' 59 | end 60 | end 61 | 62 | describe 'localization' do 63 | it 'can create a space with a different locale' do 64 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('y') 65 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 66 | 67 | command = described_class.new(token, 'B.rb - locale creation', locale: 'es-AR') 68 | 69 | vcr('space_with_different_locale') { 70 | command.run 71 | } 72 | 73 | vcr('check_created_space') { 74 | client = ::Contentful::Client.new( 75 | space: 'vsy1ouf6jdcq', 76 | access_token: '90e1b4964c3631cc9c751c42339814635623b001a53aec5aad23377299445433', 77 | dynamic_entries: :auto, 78 | raise_errors: true 79 | ) 80 | 81 | space = client.space 82 | 83 | expect(space.name).to eq("B.rb - locale creation") 84 | expect(space.locales.first.code).to eq "es-AR" 85 | expect(space.locales.first.default).to be_truthy 86 | } 87 | end 88 | end 89 | 90 | describe 'issues' do 91 | it 'Importing asset array values does not work #22' do 92 | json_path = File.expand_path(File.join('spec', 'fixtures', 'json_fixtures', 'issue_22.json')) 93 | 94 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('y') 95 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 96 | 97 | command = described_class.new(token, 'issue_22', json_template: json_path, quiet: true) 98 | 99 | vcr('issue_22') { 100 | command.run 101 | } 102 | end 103 | 104 | it 'assets can be created with any content type #39' do 105 | json_path = File.expand_path(File.join('spec', 'fixtures', 'json_fixtures', 'asset_no_transform.json')) 106 | 107 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('y') 108 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 109 | 110 | command = described_class.new(token, 'asset_no_transform', json_template: json_path, mark_processed: false, quiet: true) 111 | 112 | vcr('asset_no_transform') { 113 | command.run 114 | } 115 | end 116 | 117 | it 'doesnt fail on multiple organizations #54' do 118 | vcr('multiple_organizations') { 119 | path = File.expand_path(File.join('spec', 'fixtures', 'ini_fixtures', 'no_org.ini')) 120 | token = Contentful::Bootstrap::Token.new path 121 | 122 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('y') 123 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 124 | subject = described_class.new token, 'foo', quiet: true 125 | 126 | expect(subject).to receive(:generate_token).with(space_double) 127 | expect(Contentful::Bootstrap::Support).to receive(:gets) { 'foobar' } 128 | expect(token).to receive(:write_organization_id).with('foobar') 129 | expect(subject.client).to receive(:spaces).and_call_original 130 | space_proxy_double = Object.new 131 | expect(subject.client).to receive(:spaces) { space_proxy_double } 132 | expect(space_proxy_double).to receive(:create).with(name: 'foo', defaultLocale: 'en-US', organization_id: 'foobar') { space_double } 133 | 134 | subject.run 135 | } 136 | end 137 | 138 | context 'with no_publish set to true' do 139 | subject { described_class.new token, 'foo', environment: 'master', json_template: 'bar', trigger_oauth: false, quiet: true, no_publish: true } 140 | 141 | it 'calls JsonTemplate with no_publish' do 142 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('y') 143 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 144 | allow(::File).to receive(:exist?) { true } 145 | 146 | mock_template = Object.new 147 | 148 | expect(subject).to receive(:fetch_space) { space_double } 149 | expect(mock_template).to receive(:run) 150 | 151 | expect(::Contentful::Bootstrap::Templates::JsonTemplate).to receive(:new).with(space_double, 'bar', 'master', false, true, true, false, true) { mock_template } 152 | 153 | subject.run 154 | end 155 | end 156 | end 157 | 158 | describe 'integration' do 159 | before do 160 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('y') 161 | allow(Contentful::Bootstrap::Support).to receive(:gets).and_return('n') 162 | end 163 | 164 | it 'create space' do 165 | command = described_class.new token, 'some_space', quiet: true 166 | 167 | vcr('create_space') { 168 | command.run 169 | } 170 | end 171 | 172 | it 'create space with blog template' do 173 | command = described_class.new token, 'blog_space', template: 'blog', quiet: true 174 | 175 | vcr('create_space_with_blog_template') { 176 | command.run 177 | } 178 | end 179 | 180 | it 'create space with gallery template' do 181 | command = described_class.new token, 'gallery_space', template: 'gallery', quiet: true 182 | 183 | vcr('create_space_with_gallery_template') { 184 | command.run 185 | } 186 | end 187 | 188 | it 'create space with catalogue template' do 189 | command = described_class.new token, 'catalogue_space', template: 'catalogue', quiet: true 190 | 191 | vcr('create_space_with_catalogue_template') { 192 | command.run 193 | } 194 | end 195 | 196 | it 'create space with json template' do 197 | skip 'covered by create_space_spec:issues/#22' 198 | end 199 | 200 | it 'create space with json template with no ids' do 201 | json_path = File.expand_path(File.join('spec', 'fixtures', 'json_fixtures', 'no_ids.json')) 202 | command = described_class.new token, 'no_ids_space', json_template: json_path, quiet: true 203 | 204 | vcr('no_ids') { 205 | command.run 206 | } 207 | end 208 | end 209 | end 210 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Unreleased 4 | ## v3.12.0 5 | ### Added 6 | * Added logging for publishing actions. [#71](https://github.com/contentful/contentful-bootstrap.rb/pull/71) 7 | 8 | ## v3.11.1 9 | ### Fixed 10 | * Fixed array importing. [#70](https://github.com/contentful/contentful-bootstrap.rb/pull/70) 11 | 12 | ## v3.11.0 13 | ### Added 14 | * Added `--environment` support to `update_space` and `generate_json`. 15 | 16 | ### Changed 17 | * Updated to the new CMA and CDA SDK versions. 18 | 19 | ## v3.10.0 20 | ### Added 21 | * Added `--content-type-ids` filter to `generate_json` command to allow selecting which content types to import. 22 | 23 | ## v3.9.1 24 | ### Fixed 25 | * Fixed an issue in which assets save as `Contentful::Management::File` objects instead of as JSON when using `--mark-processed`. 26 | 27 | ## v3.9.0 28 | ### Fixed 29 | * Fixed `quiet` not being forwarded properly to `generate_json` and `update_space` 30 | * Fixed `X-Contentful-User-Agent` headers now use `application` instead of `integration` header. 31 | 32 | ### Added 33 | * Added `--use-preview` flag to `generate_json` command [#62](https://github.com/contentful/contentful-bootstrap.rb/issues/62) 34 | * Added `--no-publish` flag to `create_space` and `update_space` commands [#62](https://github.com/contentful/contentful-bootstrap.rb/issues/62) 35 | 36 | ### Changed 37 | * `generate_json` now imports all available content in your space [#62](https://github.com/contentful/contentful-bootstrap.rb/issues/62) 38 | * Assets can now be updated when using `update_space`. 39 | 40 | ## v3.8.0 41 | ### Changed 42 | * Changed `Contentful::Bootstrap::CreateCommand#organizations` to use the new public `/organizations` endpoint. 43 | 44 | ## v3.7.0 45 | ### Fixed 46 | * Fixed `skip_content_types` option on `update_space` [#59](https://github.com/contentful/contentful-bootstrap.rb/pull/59) 47 | 48 | ### Added 49 | * Added `--locale` option to `create_space` and `update_space` in order to create Spaces on a locale different to `en-US` 50 | 51 | ## v3.6.1 52 | 53 | ### Changed 54 | * Changed User Agent Headers to use the new format 55 | * Updated versions of CMA and CDA SDK to latest available 56 | 57 | ## v3.5.2 58 | 59 | ### Fixed 60 | * Fixed compatibility with CDA 2.0.0 SDK 61 | 62 | ## v3.5.1 63 | ### Fixed 64 | * Fixed organization fetching when no organization ID is provided on the configuration file [#54](https://github.com/contentful/contentful-bootstrap.rb/issues/54) 65 | 66 | ## v3.5.0 67 | 68 | ### Added 69 | * Add `-q` and `--quiet` flags to the CLI Tool and their respective command classes [#48](https://github.com/contentful/contentful-bootstrap.rb/issues/48) 70 | * Add `:no_input` option to library commands [#48](https://github.com/contentful/contentful-bootstrap.rb/issues/48) 71 | 72 | ### Changed 73 | * Refactored internals to allow more option flexibility and simplified the `CommandRunner`. 74 | * Updated dependencies to the newest available SDKs 75 | 76 | ### Fixed 77 | * Fixed Object Field parsing for JSON Templates [#51](https://github.com/contentful/contentful-bootstrap.rb/issues/51) 78 | 79 | 80 | ## v3.4.0 81 | ### Added 82 | * Add `-v` and `--version` flags to output current version 83 | * Add possibility to define `CONTENTFUL_ORGANIZATION_ID` in your `.contentfulrc` file [#44](https://github.com/contentful/contentful-bootstrap.rb/issues/44) 84 | 85 | ## v3.3.0 86 | ### Added 87 | * Adds possibility to change `contentType` for Assets [#39](https://github.com/contentful/contentful-bootstrap.rb/issues/39) 88 | * Adds `--content-types-only` option to `generate_json` 89 | * Adds `--skip-content-types` option to `update_space` 90 | 91 | ### Fixed 92 | * Fixes a bug in built-in templates using obsolete `link_type` properties 93 | 94 | ## v3.2.0 95 | ### Added 96 | * Adds `update_space` command to update already existing spaces using JSON Templates 97 | * Adds `--mark-processed` option to `create_space` and `update_space` to enforce marking which resources have already been processed 98 | 99 | ## v3.1.1 100 | ### Fixed 101 | * Fixes a bug where `display_field` was not properly populated when being generated from JSON templates [#35](https://github.com/contentful/contentful-bootstrap.rb/issues/35) 102 | 103 | ## v3.1.0 104 | ### Fixed 105 | * Version Locked Webmock causing all our VCRs to fail 106 | * Version Locked Contentful Management SDK 107 | 108 | ### Added 109 | * Custom User-Agent header 110 | 111 | ## v3.0.0 112 | ### Changed 113 | * Change JSON Template format to resemble the API more closely 114 | * Create Entries on 2 Steps [#27](https://github.com/contentful-labs/contentful-bootstrap.rb/pull/27) 115 | * Add JSON Template Version Check [#31](https://github.com/contentful-labs/contentful-bootstrap.rb/issues/31) 116 | 117 | ## v2.0.2 118 | ### Fixed 119 | * Array and Link handling 120 | 121 | ### Changed 122 | * Command now provide better help on incomplete command 123 | 124 | ## v2.0.1 [YANKED] 125 | ### Changed 126 | * Scoped File Usage on GenerateJson Command 127 | 128 | ## v2.0.0 [YANKED] 129 | ### Changed 130 | * Refactored `Commands` into new classes 131 | * Renamed `Commands` to `CommandRunner`, kept external interface, moved internal logic to new `Commands` classes 132 | * Refactored `Token` to be an Object instead of a collection of static behavior 133 | * General code refactoring and cleanup 134 | 135 | ### Added 136 | * More robust mechanism for waiting on processed assets 137 | * JSON Template generator `generate_json` command 138 | * Added Specs for almost all the code 139 | * Applied Rubocop Style guide 140 | 141 | ## v1.6.0 142 | ### Added 143 | * Support for Symbols in Array fields 144 | * Support for Links of other Entries that are not yet saved 145 | 146 | ## v1.5.1 147 | ### Fixed 148 | * Array fields were getting overwritten with `nil` 149 | 150 | ## v1.5.0 151 | ### Added 152 | * Multiple Organization ID fetch 153 | 154 | ## v1.4.0 155 | ### Added 156 | * Contentful Space URL after creation 157 | * Support for Array fields in JSON Templates 158 | 159 | ## v1.3.2 160 | ### Added 161 | * Error out when no STDIN is detected 162 | 163 | ## v1.3.1 164 | ### Fixed 165 | * Fix help messages. [#5](https://github.com/contentful-labs/contentful-bootstrap.rb/issues/5) 166 | 167 | ## v1.3.0 168 | ### Added 169 | * Spaces now get their own section in `~/.contentfulrc` 170 | 171 | ### Changed 172 | * Delivery API Tokens now saved per Space 173 | 174 | ## v1.2.0 175 | ### Added 176 | * JSON Template Parser 177 | * `catalogue.json` Template Example 178 | 179 | ### Changed 180 | * Changed existing templates from using `Symbol` entries as keys to `String` 181 | * Changed command optional parameters to `Hash` to allow better flexibility in commands 182 | 183 | ## v1.1.0 184 | ### Removed 185 | * Removed `init` command as `v1.0.0` refactor removed it's necessity 186 | 187 | ## v1.0.1 [YANKED] 188 | ### Fixed 189 | * Release is now on `master` 190 | 191 | ## v1.0.0 [YANKED] 192 | ### Changed 193 | * Changed namespace from `ContentfulBootstrap` to `Contentful::Bootstrap` to mimic other libraries 194 | * Changed repository name from `contentful_bootstrap.rb` to `contentful-bootstrap.rb` to mimic other libraries 195 | * Delivery API Token will always get created when using `contentful_bootstrap` commands to create a space 196 | * Configuration now read from `~/.contentfulrc` 197 | * Tool now requests user to allow to write configuration files 198 | 199 | ### Added 200 | * `contentful-bootstrap.rb` version number to API Token description 201 | * `inifile` as runtime dependency 202 | * Add optional `--config CONFIG_PATH` parameter to commands 203 | 204 | ## v0.0.7 205 | ### Fixed 206 | * Redirected Favicon fetch to Contentful's favicon, as some browsers would ping the server indefinitely 207 | 208 | ## v0.0.6 209 | ### Fixed 210 | * Token was not being returned on `generate_token` call 211 | 212 | ### Added 213 | * `catalogue` template now available 214 | * Added small sleep window between asset processing and publishing 215 | 216 | 217 | ## v0.0.5 218 | ### Added 219 | * API Token Generation 220 | * `generate_token` command added to binary 221 | * Better Formatting of current steps 222 | * Space and Access Token Values displayed at end of command 223 | 224 | ## v0.0.4 225 | ### Added 226 | * Added support for users with multiple Organizations 227 | * Added `gallery` template 228 | 229 | ### Changed 230 | * Removed `deklarativna` dependency 231 | * Removed `sinatra` dependency 232 | * Removed unnecessary `Gemfile.lock` file 233 | 234 | ## v0.0.3 235 | ### Added 236 | * Added `contentful_bootstrap` command 237 | * Added `blog` template 238 | 239 | ### Fixed 240 | * Fixed Dependencies 241 | 242 | ## v0.0.2 [YANKED] 243 | 244 | ## v0.0.1 [YANKED] 245 | --------------------------------------------------------------------------------