contentful-migrations 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|