contentful-migrations 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/contentful_migrations.rb +9 -0
- data/lib/contentful_migrations/migration.rb +42 -0
- data/lib/contentful_migrations/migration_content_type.rb +45 -0
- data/lib/contentful_migrations/migration_proxy.rb +35 -0
- data/lib/contentful_migrations/migrator.rb +127 -0
- data/lib/contentful_migrations/utils.rb +51 -0
- data/lib/contentful_migrations/version.rb +3 -0
- data/lib/generators/contentful_migration/USAGE +8 -0
- data/lib/generators/contentful_migration/contentful_migration_generator.rb +31 -0
- data/lib/tasks/contentful_migrations.rake +17 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0c1bda0be1633e800fda617dde80e5c119ac55a447d41a40d2657b276d63d1c4
|
4
|
+
data.tar.gz: 433a6db64e7e1ad24b718ef1e4609e10ee4bbf20d9e61428f8a742fad837de70
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4d22786b8b8bb1f6abab61cd4551adff371491f32881a0b3aadb133646139b18abed22ba10f7a0e53a3e24335e95c89d55a8c55b8c81fa7c45c9f474c9c6416a
|
7
|
+
data.tar.gz: 400571dac6d75bd1da0a1708a36de9a3f45da0e6678f3c1fa8e1e35ec483b77715793f96d2689f2128466dd4c1bb2d2df666235d7a4952db79ec0bda2d21a4c1
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'contentful/management'
|
2
|
+
require 'contentful_migrations/utils'
|
3
|
+
require 'contentful_migrations/version'
|
4
|
+
require 'contentful_migrations/migration_content_type'
|
5
|
+
require 'contentful_migrations/migration_proxy'
|
6
|
+
require 'contentful_migrations/migration'
|
7
|
+
require 'contentful_migrations/migrator'
|
8
|
+
|
9
|
+
load 'tasks/contentful_migrations.rake' if defined?(Rails)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ContentfulMigrations
|
2
|
+
class Migration
|
3
|
+
attr_reader :name, :version, :contentful_client, :contentful_space
|
4
|
+
|
5
|
+
def initialize(name = self.class.name, version = nil, client = nil, space = nil)
|
6
|
+
@name = name
|
7
|
+
@version = version
|
8
|
+
@contentful_client = client
|
9
|
+
@contentful_space = space
|
10
|
+
end
|
11
|
+
|
12
|
+
def migrate(direction, client, space)
|
13
|
+
@contentful_client = client
|
14
|
+
@contentful_space = space
|
15
|
+
send(direction)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_space
|
20
|
+
yield(contentful_space)
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_editor_interfaces
|
24
|
+
yield(contentful_client.editor_interfaces)
|
25
|
+
end
|
26
|
+
|
27
|
+
def record_migration(migration_content_type)
|
28
|
+
entry = migration_content_type.entries.create(version: version)
|
29
|
+
entry.save
|
30
|
+
entry.publish
|
31
|
+
entry
|
32
|
+
end
|
33
|
+
|
34
|
+
def erase_migration(migration_content_type)
|
35
|
+
entry = migration_content_type.entries.all.find { |m| m.version.to_i == version.to_i }
|
36
|
+
return unless entry
|
37
|
+
entry.unpublish
|
38
|
+
entry.destroy
|
39
|
+
entry
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ContentfulMigrations
|
2
|
+
class MigrationContentType
|
3
|
+
DEFAULT_MIGRATION_CONTENT_TYPE = 'migrations'.freeze
|
4
|
+
|
5
|
+
attr_reader :access_token, :space_id, :client, :space,
|
6
|
+
:migration_content_type_name, :logger
|
7
|
+
|
8
|
+
def initialize(client:,
|
9
|
+
space:,
|
10
|
+
logger:,
|
11
|
+
migration_content_type_name: DEFAULT_MIGRATION_CONTENT_TYPE)
|
12
|
+
@client = client
|
13
|
+
@space = space
|
14
|
+
@logger = logger
|
15
|
+
@migration_content_type_name = migration_content_type_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def resolve
|
19
|
+
@migration_content_type ||= find_or_create_migration_content_type
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def find_or_create_migration_content_type
|
25
|
+
content_type = space.content_types.find(migration_content_type_name)
|
26
|
+
if content_type.nil? || content_type.is_a?(Contentful::Management::Error)
|
27
|
+
build_migration_content_type
|
28
|
+
else
|
29
|
+
content_type
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_migration_content_type
|
34
|
+
content_type = space.content_types.create(
|
35
|
+
name: migration_content_type_name,
|
36
|
+
id: migration_content_type_name,
|
37
|
+
description: 'Migration Table for interal use only, do not delete'
|
38
|
+
)
|
39
|
+
content_type.fields.create(id: 'version', name: 'version', type: 'Integer')
|
40
|
+
content_type.save
|
41
|
+
content_type.publish
|
42
|
+
content_type
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'contentful_migrations/utils'
|
3
|
+
|
4
|
+
module ContentfulMigrations
|
5
|
+
# MigrationProxy is used to defer loading of the actual migration classes
|
6
|
+
# until they are needed. This implementation is borrowed from activerecord's
|
7
|
+
# migration library.
|
8
|
+
|
9
|
+
MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
|
10
|
+
extend Forwardable
|
11
|
+
include Utils
|
12
|
+
|
13
|
+
def initialize(name, version, filename, scope)
|
14
|
+
super
|
15
|
+
@migration = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def basename
|
19
|
+
File.basename(filename)
|
20
|
+
end
|
21
|
+
|
22
|
+
delegate %i[migrate record_migration erase_migration] => :migration
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def migration
|
27
|
+
@migration ||= load_migration
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_migration
|
31
|
+
require(File.expand_path(filename))
|
32
|
+
constantize(name).new(name, version)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ContentfulMigrations
|
2
|
+
class Migrator
|
3
|
+
include Utils
|
4
|
+
|
5
|
+
class InvalidMigrationPath < StandardError #:nodoc:
|
6
|
+
def initialize(migrations_path)
|
7
|
+
super("#{migrations_path} is not a valid directory.")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
DEFAULT_MIGRATION_PATH = 'db/contentful_migrations'.freeze
|
12
|
+
|
13
|
+
def self.migrate(args = {})
|
14
|
+
new(parse_options(args)).migrate
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.rollback(args = {})
|
18
|
+
new(parse_options(args)).rollback
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.pending(args = {})
|
22
|
+
new(parse_options(args)).pending
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :migrations_path, :access_token, :space_id, :client, :space,
|
26
|
+
:migration_content_type_name, :logger
|
27
|
+
|
28
|
+
def initialize(migrations_path:,
|
29
|
+
access_token:,
|
30
|
+
space_id:,
|
31
|
+
migration_content_type_name:,
|
32
|
+
logger:)
|
33
|
+
@migrations_path = migrations_path
|
34
|
+
@access_token = access_token
|
35
|
+
@logger = logger
|
36
|
+
@space_id = space_id
|
37
|
+
@migration_content_type_name = migration_content_type_name
|
38
|
+
@client = Contentful::Management::Client.new(access_token)
|
39
|
+
@space = @client.spaces.find(space_id)
|
40
|
+
validate_options
|
41
|
+
end
|
42
|
+
|
43
|
+
def migrate
|
44
|
+
runnable = migrations(migrations_path).reject { |m| ran?(m) }
|
45
|
+
if runnable.empty?
|
46
|
+
logger.info('No migrations to run, everything up to date!')
|
47
|
+
end
|
48
|
+
|
49
|
+
runnable.each do |migration|
|
50
|
+
logger.info("running migration #{migration.version} #{migration.name} ")
|
51
|
+
migration.migrate(:up, client, space)
|
52
|
+
migration.record_migration(migration_content_type)
|
53
|
+
end
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def rollback
|
58
|
+
already_migrated = migrations(migrations_path).select { |m| ran?(m) }
|
59
|
+
migration = already_migrated.pop
|
60
|
+
logger.info("Rolling back migration #{migration.version} #{migration.name} ")
|
61
|
+
migration.migrate(:down, client, space)
|
62
|
+
migration.erase_migration(migration_content_type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def pending
|
66
|
+
runnable = migrations(migrations_path).reject { |m| ran?(m) }
|
67
|
+
|
68
|
+
runnable.each do |migration|
|
69
|
+
logger.info("Pending #{migration.version} #{migration.name} ")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def self.parse_options(args)
|
76
|
+
{
|
77
|
+
migrations_path: ENV.fetch('MIGRATION_PATH', DEFAULT_MIGRATION_PATH),
|
78
|
+
access_token: ENV['CONTENTFUL_MANAGEMENT_ACCESS_TOKEN'],
|
79
|
+
space_id: ENV['CONTENTFUL_SPACE_ID'],
|
80
|
+
migration_content_type_name: MigrationContentType::DEFAULT_MIGRATION_CONTENT_TYPE,
|
81
|
+
logger: Logger.new(STDOUT)
|
82
|
+
}.merge(args)
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_options
|
86
|
+
raise InvalidMigrationPath, migrations_path unless File.directory?(migrations_path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def ran?(migration)
|
90
|
+
migrated.include?(migration.version.to_i)
|
91
|
+
end
|
92
|
+
|
93
|
+
def migrated
|
94
|
+
@migrated ||= load_migrated
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_migrated
|
98
|
+
migration_content_type.entries.all.map { |m| m.version.to_i }
|
99
|
+
end
|
100
|
+
|
101
|
+
def migrations(paths)
|
102
|
+
paths = Array(paths)
|
103
|
+
migrations = migration_files(paths).map do |file|
|
104
|
+
version, name, scope = parse_migration_filename(file)
|
105
|
+
ContentfulMigrations::MigrationProxy.new(camelize(name), version.to_i, file, scope)
|
106
|
+
end
|
107
|
+
|
108
|
+
migrations.sort_by(&:version)
|
109
|
+
end
|
110
|
+
|
111
|
+
def migration_files(paths)
|
112
|
+
Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
|
113
|
+
end
|
114
|
+
|
115
|
+
def migration_content_type
|
116
|
+
@migration_content_type ||= MigrationContentType.new(
|
117
|
+
space: space, client: client, logger: logger
|
118
|
+
).resolve
|
119
|
+
end
|
120
|
+
|
121
|
+
MIGRATION_FILENAME_REGEX = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/
|
122
|
+
|
123
|
+
def parse_migration_filename(filename)
|
124
|
+
File.basename(filename).scan(MIGRATION_FILENAME_REGEX).first
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ContentfulMigrations
|
2
|
+
module Utils
|
3
|
+
# This method was taken from ActiveSupport::Inflector to avoid having
|
4
|
+
# a dependency on ActiveSupport in this project.
|
5
|
+
# http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize
|
6
|
+
def camelize(term, uppercase_first_letter = true)
|
7
|
+
string = term.to_s
|
8
|
+
string = if uppercase_first_letter
|
9
|
+
string.sub(/^[a-z\d]*/, &:capitalize)
|
10
|
+
else
|
11
|
+
string.sub(/^((?=\b|[A-Z_])|\w)/, &:downcase)
|
12
|
+
end
|
13
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
|
14
|
+
string.gsub!('/'.freeze, '::'.freeze)
|
15
|
+
string
|
16
|
+
end
|
17
|
+
# This method was taken from ActiveSupport::Inflector to avoid having
|
18
|
+
# a dependency on ActiveSupport in this project.
|
19
|
+
# http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize
|
20
|
+
|
21
|
+
def constantize(camel_cased_word)
|
22
|
+
names = camel_cased_word.split('::'.freeze)
|
23
|
+
|
24
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
25
|
+
Object.const_get(camel_cased_word) if names.empty?
|
26
|
+
|
27
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
28
|
+
names.shift if names.size > 1 && names.first.empty?
|
29
|
+
|
30
|
+
names.inject(Object) do |constant, name|
|
31
|
+
if constant == Object
|
32
|
+
constant.const_get(name)
|
33
|
+
else
|
34
|
+
candidate = constant.const_get(name)
|
35
|
+
next candidate if constant.const_defined?(name, false)
|
36
|
+
next candidate unless Object.const_defined?(name)
|
37
|
+
|
38
|
+
# Go down the ancestors to check if it is owned directly. The check
|
39
|
+
# stops when we reach Object or the end of ancestors tree.
|
40
|
+
constant = constant.ancestors.each_with_object(constant) do |ancestor, const|
|
41
|
+
break const if ancestor == Object
|
42
|
+
break ancestor if ancestor.const_defined?(name, false)
|
43
|
+
end
|
44
|
+
|
45
|
+
# owner is in Object, so raise
|
46
|
+
constant.const_get(name, false)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class ContentfulMigrationGenerator < Rails::Generators::NamedBase
|
4
|
+
# source_root File.expand_path("../templates", __FILE__)
|
5
|
+
|
6
|
+
def copy_initializer_file
|
7
|
+
name = file_name.to_s
|
8
|
+
migration_file = "db/contentful_migrations/#{next_migration_number}_#{name.underscore}.rb"
|
9
|
+
create_file migration_file, <<-FILE.strip_heredoc
|
10
|
+
class #{name.camelize} < ContentfulMigrations::Migration
|
11
|
+
|
12
|
+
def up
|
13
|
+
with_space do |space|
|
14
|
+
# TODO: use contentful-management.rb here
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def down
|
19
|
+
with_space do |space|
|
20
|
+
# TODO: use contentful-management.rb here
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
FILE
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_migration_number
|
29
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'contentful_migrations'
|
2
|
+
namespace :contentful_migrations do
|
3
|
+
desc 'Migrate the contentful space, runs all pending migrations'
|
4
|
+
task :migrate, [:contentful_space] do |_t, _args|
|
5
|
+
ContentfulMigrations::Migrator.migrate
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Rollback previous contentful migration'
|
9
|
+
task :rollback, [:contentful_space] do |_t, _args|
|
10
|
+
ContentfulMigrations::Migrator.rollback
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'List any pending contentful migrations'
|
14
|
+
task :pending, [:contentful_space] do |_t, _args|
|
15
|
+
ContentfulMigrations::Migrator.pending
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contentful-migrations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin English
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: contentful-management
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.10.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.10.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 12.3.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 12.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 10.0.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 10.0.0
|
83
|
+
description: |-
|
84
|
+
Migration library system for Contentful API dependent on
|
85
|
+
contentful-management gem and plagarized from activerecord.
|
86
|
+
email:
|
87
|
+
- me@kenglish.co
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- lib/contentful_migrations.rb
|
93
|
+
- lib/contentful_migrations/migration.rb
|
94
|
+
- lib/contentful_migrations/migration_content_type.rb
|
95
|
+
- lib/contentful_migrations/migration_proxy.rb
|
96
|
+
- lib/contentful_migrations/migrator.rb
|
97
|
+
- lib/contentful_migrations/utils.rb
|
98
|
+
- lib/contentful_migrations/version.rb
|
99
|
+
- lib/generators/contentful_migration/USAGE
|
100
|
+
- lib/generators/contentful_migration/contentful_migration_generator.rb
|
101
|
+
- lib/tasks/contentful_migrations.rake
|
102
|
+
homepage: https://github.com/monkseal/contentful-migrations.rb
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
metadata:
|
106
|
+
allowed_push_host: https://rubygems.org
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.7.3
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: Contentful Migrations in Ruby
|
127
|
+
test_files: []
|