ruby-sfn-local 0.1.3
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/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: []
|