ruby-sfn-local 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/sfn/aws_cli.rb +29 -0
- data/lib/sfn/collection.rb +25 -0
- data/lib/sfn/configuration.rb +12 -0
- data/lib/sfn/execution.rb +28 -0
- data/lib/sfn/execution_log.rb +77 -0
- data/lib/sfn/mock_data.rb +28 -0
- data/lib/sfn/mock_macros/api_gateway.rb +26 -0
- data/lib/sfn/mock_macros/lambda.rb +25 -0
- data/lib/sfn/mock_macros/sns.rb +25 -0
- data/lib/sfn/mock_macros/step_function.rb +25 -0
- data/lib/sfn/mock_macros.rb +51 -0
- data/lib/sfn/state_machine.rb +74 -0
- data/lib/sfn.rb +13 -0
- data/lib/string.rb +7 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1c56a390adcd8cefff1fa95e14db7b966e40058f81fe7d775edf31256d7eaf76
|
4
|
+
data.tar.gz: 7da9325fb01e571bf5769e6d890547c91f684742cd872d43decbd0b6bd798f1c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e88364b74f3ed18ba23d4aab7b1e7df243a56893693af3c30531ef04c9cbc0e6f91657eb59f291eeef6e131ed3b6e55a3720cf7ce9dedb68096cce2e26c1ec1b
|
7
|
+
data.tar.gz: 3091356de9b879a01978babcd45ca8939f4d3b8f4e28a84e9e1c5f7dc1df4d2c8662867cdebaf677553adbfeee8401824d5ee0ddb61ba00b99455aabbd0dbade
|
data/lib/sfn/aws_cli.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Sfn
|
6
|
+
class AwsCli
|
7
|
+
class ExecutionError < StandardError; end
|
8
|
+
|
9
|
+
def self.run(mod, command, params, key = nil, debug_info = '')
|
10
|
+
cmd = "#{Sfn.configuration.aws_command || 'aws'} #{mod} #{command} \
|
11
|
+
--endpoint #{Sfn.configuration.aws_endpoint} \
|
12
|
+
#{params.map do |k, v|
|
13
|
+
"--#{k}=#{v}"
|
14
|
+
end.join(' ')} \
|
15
|
+
--no-cli-pager"
|
16
|
+
|
17
|
+
stdout, stderr, _status = Open3.capture3(cmd)
|
18
|
+
raise raise ExecutionError, "#{stderr.strip}\n#{debug_info}" unless stderr.strip.empty?
|
19
|
+
|
20
|
+
stdout = `#{cmd}`
|
21
|
+
unless key.nil?
|
22
|
+
data = JSON.parse(stdout)
|
23
|
+
return data[key].strip if data.key?(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
stdout
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Sfn
|
6
|
+
class Collection
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_reader :all
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
response = AwsCli.run('stepfunctions', 'list-state-machines', {})
|
13
|
+
parsed_response = JSON.parse(response)
|
14
|
+
@all = parsed_response['stateMachines'] || []
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(state_machine)
|
18
|
+
@all.push(state_machine.slice('stateMachineArn', 'name'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_by_arn(state_machine_arn)
|
22
|
+
@all.delete_if { |sf| sf['stateMachineArn'] == state_machine_arn }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
class Execution
|
5
|
+
attr_accessor :uuid, :state_machine, :test_case, :arn, :output, :profile
|
6
|
+
|
7
|
+
def self.call(state_machine, test_case, mock_data, input)
|
8
|
+
new(state_machine, test_case).exec(mock_data, input)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(state_machine, test_case)
|
12
|
+
self.uuid = SecureRandom.uuid
|
13
|
+
self.state_machine = state_machine
|
14
|
+
self.test_case = test_case.camelize
|
15
|
+
end
|
16
|
+
|
17
|
+
def exec(mock_data, input)
|
18
|
+
MockData.write_context(state_machine.name, test_case, mock_data)
|
19
|
+
self.arn = AwsCli.run('stepfunctions', 'start-execution',
|
20
|
+
{ name: uuid,
|
21
|
+
'state-machine': "#{state_machine.arn}##{test_case}",
|
22
|
+
input: "'#{input.to_json}'" },
|
23
|
+
'executionArn')
|
24
|
+
self.output, self.profile = ExecutionLog.parse(arn)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
class ExecutionError < RuntimeError
|
5
|
+
attr_accessor :code, :events
|
6
|
+
|
7
|
+
def initialize(msg, code, events)
|
8
|
+
self.code = code
|
9
|
+
self.events = JSON.parse(events)
|
10
|
+
super(msg)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ExecutionLog
|
15
|
+
attr_accessor :event
|
16
|
+
|
17
|
+
EVENTS = %w[stateEnteredEventDetails stateExitedEventDetails executionSucceededEventDetails
|
18
|
+
executionFailedEventDetails].freeze
|
19
|
+
def self.parse(execution_arn)
|
20
|
+
profile = {}
|
21
|
+
output = nil
|
22
|
+
error = nil
|
23
|
+
events_json = AwsCli.run('stepfunctions', 'get-execution-history',
|
24
|
+
{ 'execution-arn': execution_arn.to_s, query: "'events[?#{EVENTS.join(' || ')}]'" })
|
25
|
+
|
26
|
+
JSON.parse(events_json).each do |event|
|
27
|
+
parsed_event = new(event)
|
28
|
+
|
29
|
+
output ||= parsed_event.output
|
30
|
+
error ||= parsed_event.error(events_json)
|
31
|
+
state_name = parsed_event.state_name
|
32
|
+
|
33
|
+
next if state_name.nil?
|
34
|
+
|
35
|
+
profile[state_name] ||= { input: [], output: [] }
|
36
|
+
profile[state_name][:input] << parsed_event.profile[:input] unless parsed_event.profile[:input].nil?
|
37
|
+
profile[state_name][:output] << parsed_event.profile[:output] unless parsed_event.profile[:output].nil?
|
38
|
+
end
|
39
|
+
[output, profile]
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(event)
|
43
|
+
self.event = event
|
44
|
+
end
|
45
|
+
|
46
|
+
def state_name
|
47
|
+
event.dig('stateEnteredEventDetails', 'name') || event.dig('stateExitedEventDetails', 'name')
|
48
|
+
end
|
49
|
+
|
50
|
+
def output
|
51
|
+
try_parse(event.dig('executionSucceededEventDetails', 'output'))
|
52
|
+
end
|
53
|
+
|
54
|
+
def error(events_json = '{}')
|
55
|
+
return if event['executionFailedEventDetails'].nil?
|
56
|
+
|
57
|
+
raise ExecutionError.new(event['executionFailedEventDetails']['cause'],
|
58
|
+
event['executionFailedEventDetails']['error'],
|
59
|
+
events_json)
|
60
|
+
end
|
61
|
+
|
62
|
+
def profile
|
63
|
+
{
|
64
|
+
input: try_parse(event.dig('stateEnteredEventDetails', 'input')),
|
65
|
+
output: try_parse(event.dig('stateExitedEventDetails', 'output'))
|
66
|
+
}.compact
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def try_parse(json_string)
|
72
|
+
JSON.parse(json_string)
|
73
|
+
rescue StandardError
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
module Sfn
|
7
|
+
module MockData
|
8
|
+
def self.write_context(state_machine_name, context, mock_data = {})
|
9
|
+
data = {
|
10
|
+
'StateMachines' => {
|
11
|
+
state_machine_name.to_s => {
|
12
|
+
'TestCases' => {
|
13
|
+
context.to_s => { 'foo' => 'bar' }
|
14
|
+
}
|
15
|
+
}
|
16
|
+
},
|
17
|
+
'MockedResponses' => { 'bar' => {} }
|
18
|
+
}
|
19
|
+
|
20
|
+
mock_data.each do |step, response|
|
21
|
+
uuid = OpenSSL::Digest::SHA512.digest({ step: step }.merge(response).to_json).camelize
|
22
|
+
data['StateMachines'][state_machine_name.to_s]['TestCases'][context.to_s][step.to_s] = uuid
|
23
|
+
data['MockedResponses'][uuid] = response
|
24
|
+
end
|
25
|
+
File.write(Sfn.configuration.mock_file_path, JSON.pretty_generate(data))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
module MockMacros
|
5
|
+
module ApiGateway
|
6
|
+
def self.response(data)
|
7
|
+
data = [data] if data.is_a?(Hash)
|
8
|
+
data.map! do |val|
|
9
|
+
if val.key?(:error)
|
10
|
+
{ Throw: { Error: val[:error], Cause: val[:cause] } }
|
11
|
+
else
|
12
|
+
val[:response] ||= val[:payload]
|
13
|
+
val[:response] ||= val[:output]
|
14
|
+
{ Return: { Headers: val[:headers], ResponseBody: val[:response], StatusCode: val[:status],
|
15
|
+
StatusText: 'OK' } }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
out = {}
|
19
|
+
data.each_with_index do |val, idx|
|
20
|
+
out[idx.to_s] = val
|
21
|
+
end
|
22
|
+
out
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
module MockMacros
|
5
|
+
module Lambda
|
6
|
+
def self.response(data)
|
7
|
+
data = [data] if data.is_a?(Hash)
|
8
|
+
data.map! do |val|
|
9
|
+
if val.key?(:error)
|
10
|
+
{ Throw: { Error: val[:error], Cause: val[:cause] } }
|
11
|
+
else
|
12
|
+
val[:payload] ||= val[:response]
|
13
|
+
val[:payload] ||= val[:output]
|
14
|
+
{ Return: { Payload: val[:payload], StatusCode: 200 } }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
out = {}
|
18
|
+
data.each_with_index do |val, idx|
|
19
|
+
out[idx.to_s] = val
|
20
|
+
end
|
21
|
+
out
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
module MockMacros
|
5
|
+
module Sns
|
6
|
+
def self.response(data)
|
7
|
+
data = [data] if data.is_a?(Hash)
|
8
|
+
data.map! do |val|
|
9
|
+
if val.key?(:error)
|
10
|
+
{ Throw: { Error: val[:error], Cause: val[:cause] } }
|
11
|
+
else
|
12
|
+
val[:uuid] ||= SecureRandom.uuid
|
13
|
+
val[:sequence] ||= 10_000_000_000_000_003_000
|
14
|
+
{ Return: { MessageId: val[:uuid], SequenceNumber: val[:sequence] } }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
out = {}
|
18
|
+
data.each_with_index do |val, idx|
|
19
|
+
out[idx.to_s] = val
|
20
|
+
end
|
21
|
+
out
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sfn
|
4
|
+
module MockMacros
|
5
|
+
module StepFunction
|
6
|
+
def self.response(data)
|
7
|
+
data = [data] if data.is_a?(Hash)
|
8
|
+
data.map! do |val|
|
9
|
+
if val.key?(:error)
|
10
|
+
{ Throw: { Error: val[:error], Cause: val[:cause] } }
|
11
|
+
else
|
12
|
+
val[:output] ||= val[:payload]
|
13
|
+
val[:output] ||= val[:response]
|
14
|
+
{ Return: { Output: val[:output].to_json, Status: 'SUCCEDED' } }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
out = {}
|
18
|
+
data.each_with_index do |val, idx|
|
19
|
+
out[idx.to_s] = val
|
20
|
+
end
|
21
|
+
out
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sfn/mock_macros/lambda'
|
4
|
+
require 'sfn/mock_macros/step_function'
|
5
|
+
require 'sfn/mock_macros/api_gateway'
|
6
|
+
require 'sfn/mock_macros/sns'
|
7
|
+
|
8
|
+
module Sfn
|
9
|
+
module MockMacros
|
10
|
+
include Lambda
|
11
|
+
include StepFunction
|
12
|
+
include ApiGateway
|
13
|
+
include Sns
|
14
|
+
|
15
|
+
def self.gateway_response(data)
|
16
|
+
ApiGateway.response(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.lambda_response(data)
|
20
|
+
Lambda.response(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.sns_response(data)
|
24
|
+
Sns.response(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.step_function_response(data)
|
28
|
+
StepFunction.response(data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.gateway_payload(data)
|
32
|
+
warn '[DEPRECATION] `gateway_payload` is deprecated. Please use `gateway_response` instead.'
|
33
|
+
ApiGateway.response(data)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.lambda_payload(data)
|
37
|
+
warn '[DEPRECATION] `lambda_payload` is deprecated. Please use `lambda_response` instead.'
|
38
|
+
Lambda.response(data)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.sns_payload(data)
|
42
|
+
warn '[DEPRECATION] `sns_payload` is deprecated. Please use `sns_response` instead.'
|
43
|
+
Sns.response(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.step_function_payload(data)
|
47
|
+
warn '[DEPRECATION] `step_function_payload` is deprecated. Please use `step_function_response` instead.'
|
48
|
+
StepFunction.response(data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'openssl'
|
5
|
+
|
6
|
+
module Sfn
|
7
|
+
class DefinitionError < RuntimeError; end
|
8
|
+
|
9
|
+
class StateMachine
|
10
|
+
ROLE = 'arn:aws:iam::123456789012:role/DummyRole'
|
11
|
+
|
12
|
+
attr_accessor :name, :definition, :arn, :executions, :execution_arn
|
13
|
+
|
14
|
+
def self.all
|
15
|
+
Collection.instance.all.map { |sf| new(sf['name'], sf['stateMachineArn']) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.destroy_all
|
19
|
+
all.each(&:destroy)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find_by_name(name)
|
23
|
+
all.find { |sf| sf.name == name }
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.find_by_arn(arn)
|
27
|
+
all.find { |sf| sf.arn == arn }
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(name, arn = nil)
|
31
|
+
self.name = name
|
32
|
+
self.arn = arn || self.class.find_by_name(name)&.arn || create_state_machine
|
33
|
+
self.executions = {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
AwsCli.run('stepfunctions', 'delete-state-machine',
|
38
|
+
{ 'state-machine-arn': arn })
|
39
|
+
Collection.instance.delete_by_arn(arn)
|
40
|
+
end
|
41
|
+
|
42
|
+
def run(mock_data = {}, input = {}, test_name = nil)
|
43
|
+
test_name ||= OpenSSL::Digest::SHA512.digest(mock_data.merge(input).to_json)
|
44
|
+
executions[test_name] ||= Execution.call(self, test_name, mock_data, input)
|
45
|
+
executions[test_name]
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_hash
|
49
|
+
{ 'stateMachineArn' => arn, 'name' => name }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def create_state_machine
|
55
|
+
self.arn = AwsCli.run('stepfunctions', 'create-state-machine',
|
56
|
+
{ definition: load_definition(name), name: name, 'role-arn': ROLE }, 'stateMachineArn')
|
57
|
+
raise Sf::DefinitionError if arn.empty?
|
58
|
+
|
59
|
+
Collection.instance.add(to_hash)
|
60
|
+
arn
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_definition(_name)
|
64
|
+
local_definition_path = Tempfile.new(['name', '.json']).path
|
65
|
+
remote_definition_path = "#{Sfn.configuration.definition_path}/#{name}.json"
|
66
|
+
|
67
|
+
definition = File.read(remote_definition_path)
|
68
|
+
local_definition = definition.gsub(/"MaxConcurrency": [0-9]+/, '"MaxConcurrency": 1')
|
69
|
+
|
70
|
+
File.open(local_definition_path, 'w') { |file| file.puts local_definition }
|
71
|
+
"file://#{local_definition_path}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/sfn.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'sfn/configuration'
|
6
|
+
require 'sfn/aws_cli'
|
7
|
+
require 'sfn/collection'
|
8
|
+
require 'sfn/state_machine'
|
9
|
+
require 'sfn/mock_data'
|
10
|
+
require 'sfn/mock_macros'
|
11
|
+
require 'sfn/execution'
|
12
|
+
require 'sfn/execution_log'
|
13
|
+
require 'string'
|
data/lib/string.rb
ADDED
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-sfn-local
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gianni Mazza
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-release
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.28'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.28'
|
55
|
+
description: A convenient gem to test step function locally
|
56
|
+
email: gianni.mazza81rg@gmail.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- lib/sfn.rb
|
62
|
+
- lib/sfn/aws_cli.rb
|
63
|
+
- lib/sfn/collection.rb
|
64
|
+
- lib/sfn/configuration.rb
|
65
|
+
- lib/sfn/execution.rb
|
66
|
+
- lib/sfn/execution_log.rb
|
67
|
+
- lib/sfn/mock_data.rb
|
68
|
+
- lib/sfn/mock_macros.rb
|
69
|
+
- lib/sfn/mock_macros/api_gateway.rb
|
70
|
+
- lib/sfn/mock_macros/lambda.rb
|
71
|
+
- lib/sfn/mock_macros/sns.rb
|
72
|
+
- lib/sfn/mock_macros/step_function.rb
|
73
|
+
- lib/sfn/state_machine.rb
|
74
|
+
- lib/string.rb
|
75
|
+
homepage: https://github.com/redvex/ruby-sfn-local
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata:
|
79
|
+
rubygems_mfa_required: 'true'
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.7.0
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.1.2
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Step Functions local ruby wrapper!
|
99
|
+
test_files: []
|