arkana 0.1.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92172ad1f72c9c868c0a2a56c3d4b39ff925c7f12e87726e49b899feaced2665
4
- data.tar.gz: 902c779d2285c30fd456450fe3487ae66e990c2c96b3f2d71e96e8c2d77f06e9
3
+ metadata.gz: 17be699093098399339947f5a8d0df3d5ff20fdb8186cfc9bd92fa1c3aa330d2
4
+ data.tar.gz: e7b919f59bf5a491bfa9d4f166753f374a94a33a5023275e5d6d35549a0d1e58
5
5
  SHA512:
6
- metadata.gz: a9c93524a1aae1403ce277ca40cbd9befa86c6f0f32b9762a03fcc7f52c6cc79fb6e900fe8a8c3b5a5ad95d23fd559c8da9afe3cac21d9e746d8f11dd20c67b5
7
- data.tar.gz: ce59a330a1dc11e689e2fac24e4dbc1fcabcc2114ca648d205eb5b85181e3eb541b9aebbed6fa51469791275f98c6787b93890b9ff9f41d3258ef94c1dad27c9
6
+ metadata.gz: fde3754bfdb500ef9607827f14927ab0e0d678f70ee30398b238d56ba07729d27c967ab890c0b933432898cf8577e6ed3e642ed1ec442a85020c11d54f6d0b0f
7
+ data.tar.gz: 83880b13c6eeea494d7b85d5a73fe01cba9834156eb5d5d3a51d5cc9f7030a069e312eb98d3ab017861c67b818fcc9d1abe83de3bfb0850e3ddb520df4e80e07
data/bin/arkana CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require 'arkana'
4
- Arkana::run(ARGV)
4
+ require "arkana"
5
+ require "arkana/models/arguments"
6
+
7
+ arguments = Arguments.new # Parses ARGV
8
+ Arkana.run(arguments)
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "arkana"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require_relative "models/config"
5
+ require_relative "helpers/ui"
6
+
7
+ # The config parser is responsible for parsing user CLI arguments, reading the config file and returning a `Config` object.
8
+ module ConfigParser
9
+ # Parses the config file defined by the user (if any), returning a Config object.
10
+ #
11
+ # @return [Config] the config parsed from the user's options.
12
+ def self.parse(arguments)
13
+ yaml = YAML.load_file(arguments.config_filepath)
14
+ config = Config.new(yaml)
15
+ config.current_flavor = arguments.flavor
16
+ config.dotenv_filepath = arguments.dotenv_filepath
17
+ UI.warn("Dotenv file was specified but couldn't be found at '#{config.dotenv_filepath}'") if config.dotenv_filepath && !File.exist?(config.dotenv_filepath)
18
+ config
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "arkana/helpers/string"
4
+
5
+ # The encoder is responsible for finding the env vars for given keys, encoding them, and creating Secrets based on the generated data.
6
+ module Encoder
7
+ # Fetches values of each key from ENV, and encodes them using the given salt.
8
+ #
9
+ # @return [Secret[]] an array of Secret objects, which contain their keys and encoded values.
10
+ def self.encode!(keys:, salt:, current_flavor:, environments:)
11
+ keys.map do |key|
12
+ secret = find_secret!(key: key, current_flavor: current_flavor)
13
+ encoded_value = encode(secret, salt.raw)
14
+ secret_type = Type.new(string_value: secret)
15
+ protocol_key = protocol_key(key: key, environments: environments)
16
+ Secret.new(key: key, protocol_key: protocol_key, encoded_value: encoded_value, type: secret_type)
17
+ end
18
+ end
19
+
20
+ # Encodes the given string, using the given cipher.
21
+ def self.encode(string, cipher)
22
+ bytes = string.encode("utf-8")
23
+ result = []
24
+ (0...bytes.length).each do |index|
25
+ byte = bytes[index].ord # Convert to its codepoint representation
26
+ result << (byte ^ cipher[index % cipher.length]) # XOR operation with a value of the cipher array.
27
+ end
28
+
29
+ encoded_key = []
30
+ result.each do |element|
31
+ # Warning: this might be specific to Swift implementation. When generating code for other languages, beware.
32
+ encoded_key << format("%#x", element) # Format the binary number to "0xAB" format.
33
+ end
34
+
35
+ encoded_key.join(", ")
36
+ end
37
+
38
+ def self.find_secret!(key:, current_flavor:)
39
+ flavor_key = "#{current_flavor.capitalize_first_letter}#{key}" if current_flavor
40
+ secret = ENV[flavor_key] if flavor_key
41
+ secret ||= ENV[key] || raise("Secret '#{flavor_key || key}' was declared but couldn't be found in the environment variables nor in the specified dotenv file.")
42
+ secret
43
+ end
44
+
45
+ def self.protocol_key(key:, environments:)
46
+ environments.filter_map do |env|
47
+ key.delete_suffix(env) if key.end_with?(env)
48
+ end.first || key
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dotenv"
4
+
5
+ # This helper is a mere utility used to facilitate and orchestrate the loading of multiple Dotenv files.
6
+ module DotenvHelper
7
+ # Loads the appropriate dotenv file(s).
8
+ def self.load(config)
9
+ Dotenv.load(config.dotenv_filepath) if config.dotenv_filepath
10
+ # Must be loaded after loading the `config.dotenv_filepath` so they override each other in the right order
11
+ Dotenv.load(flavor_dotenv_filepath(config)) if config.current_flavor
12
+ end
13
+
14
+ def self.flavor_dotenv_filepath(config)
15
+ dotenv_dirname = File.dirname(config.dotenv_filepath)
16
+ flavor_dotenv_filename = ".env.#{config.current_flavor.downcase}"
17
+ File.join(dotenv_dirname, flavor_dotenv_filename)
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # String extensions and utilities.
4
+ class String
5
+ # Returns a string converted from an assumed PascalCase, to camelCase.
6
+ def camel_case
7
+ return self if empty?
8
+
9
+ copy = dup
10
+ copy[0] = copy[0].downcase
11
+ copy
12
+ end
13
+
14
+ # Returns a string with its first character capitalized.
15
+ def capitalize_first_letter
16
+ return self if empty?
17
+
18
+ copy = dup
19
+ copy[0] = copy[0].upcase
20
+ copy
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Utilities to reduce the amount of boilerplate code in `.swift.erb` template files.
4
+ module SwiftTemplateHelper
5
+ def self.swift_type(type)
6
+ case type
7
+ when :string then "String"
8
+ when :boolean then "Bool"
9
+ else raise "Unknown variable type '#{type}' received.'"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "colorize"
5
+
6
+ # Contains utilities related to display information to the user on the Terminal (the user's interface).
7
+ module UI
8
+ # Logs the message in red color and raise an error with the given message.
9
+ def self.crash(message)
10
+ logger.fatal(message.red)
11
+ raise message
12
+ end
13
+
14
+ # Logs the message in cyan color.
15
+ def self.debug(message)
16
+ logger.debug(message.cyan)
17
+ end
18
+
19
+ # Logs the message in green color.
20
+ def self.success(message)
21
+ logger.info(message.green)
22
+ end
23
+
24
+ # Logs the message in yellow color.
25
+ def self.warn(message)
26
+ logger.warn(message.yellow)
27
+ end
28
+
29
+ # Logger used to log all the messages.
30
+ def self.logger
31
+ @logger ||= Logger.new($stdout)
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ # Model that parses and documents the CLI options, using `OptionParser`.
6
+ class Arguments
7
+ # @returns [string]
8
+ attr_reader :config_filepath
9
+ # @returns [string]
10
+ attr_reader :dotenv_filepath
11
+ # @returns [string]
12
+ attr_reader :flavor
13
+
14
+ def initialize
15
+ # Default values
16
+ @config_filepath = ".arkana.yml"
17
+ @dotenv_filepath = ".env" if File.exist?(".env")
18
+ @flavor = nil
19
+
20
+ OptionParser.new do |opt|
21
+ opt.on("-c", "--config-filepath /path/to/your/.arkana.yml", "Path to your config file. Defaults to '.arkana.yml'") do |o|
22
+ @config_filepath = o
23
+ end
24
+ opt.on("-e", "--dotenv-filepath /path/to/your/.env", "Path to your dotenv file. Defaults to '.env' if one exists.") do |o|
25
+ @dotenv_filepath = o
26
+ end
27
+ opt.on("-f", "--flavor FrostedFlakes", "Flavors are useful, for instance, when generating secrets for white-label projects. See the README for more information") do |o|
28
+ @flavor = o
29
+ end
30
+ end.parse!
31
+ end
32
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Model used to hold all the configuration set up by the user (both from CLI arguments and from the config file).
4
+ class Config
5
+ # @returns [string[]]
6
+ attr_reader :environments
7
+ # @returns [string[]]
8
+ attr_reader :global_secrets
9
+ # @returns [string[]]
10
+ attr_reader :environment_secrets
11
+ # @returns [string]
12
+ attr_reader :import_name
13
+ # @returns [string]
14
+ attr_reader :namespace
15
+ # @returns [string]
16
+ attr_reader :pod_name
17
+ # @returns [string]
18
+ attr_reader :result_path
19
+ # @returns [string[]]
20
+ attr_reader :flavors
21
+ # @returns [string]
22
+ attr_reader :swift_declaration_strategy
23
+ # @returns [boolean]
24
+ attr_reader :should_generate_unit_tests
25
+ # @returns [string]
26
+ attr_reader :package_manager
27
+
28
+ # @returns [string]
29
+ attr_accessor :current_flavor
30
+ # @returns [string]
31
+ attr_accessor :dotenv_filepath
32
+
33
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
34
+ def initialize(yaml)
35
+ @environments = (yaml["environments"] || []).map(&:capitalize)
36
+ @environment_secrets = yaml["environment_secrets"] || []
37
+ @global_secrets = yaml["global_secrets"] || []
38
+ default_name = "ArkanaKeys"
39
+ @namespace = yaml["namespace"] || default_name
40
+ @import_name = yaml["import_name"] || default_name
41
+ @pod_name = yaml["pod_name"] || default_name
42
+ @result_path = yaml["result_path"] || default_name
43
+ @flavors = yaml["flavors"] || []
44
+ @swift_declaration_strategy = yaml["swift_declaration_strategy"] || "let"
45
+ @should_generate_unit_tests = yaml["should_generate_unit_tests"]
46
+ @should_generate_unit_tests = true if @should_generate_unit_tests.nil?
47
+ @package_manager = yaml["package_manager"] || "spm"
48
+ end
49
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
50
+
51
+ # TODO: Consider renaming this and environment_secrets, cuz they're confusing.
52
+ def environment_keys
53
+ result = environment_secrets.map do |k|
54
+ environments.map { |e| k + e }
55
+ end
56
+ result.flatten
57
+ end
58
+
59
+ def all_keys
60
+ global_secrets + environment_keys
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "type"
4
+
5
+ # Model used to hold the metadata for the salt bytes.
6
+ class Salt
7
+ attr_reader :raw
8
+ # Salt, formatted as a string of comma-separated hexadecimal numbers, e.g. "0x94, 0x11, 0x1b, 0x6, 0x49, 0, 0xa7"
9
+ attr_reader :formatted
10
+
11
+ def initialize(raw:)
12
+ @raw = raw
13
+ formatted_salt = []
14
+ raw.each do |element|
15
+ # Warning: this might be specific to Swift implementation. When generating code for other languages, beware.
16
+ formatted_salt << format("%#x", element)
17
+ end
18
+ @formatted = formatted_salt.join(", ")
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "type"
4
+
5
+ # Model used to hold the metadata for a secret, such as its env var key, its protocol key, its type, and encoded value.
6
+ class Secret
7
+ # @returns [string]
8
+ attr_accessor :key
9
+ # The same string as the key, except without the "environment" suffix.
10
+ # Used in the declaration of the protocol that defines the environment secrets.
11
+ # @returns [string]
12
+ attr_accessor :protocol_key
13
+ # Encoded value, formatted as a string of comma-separated hexadecimal numbers, e.g. "0x94, 0x11, 0x1b, 0x6, 0x49, 0, 0xa7"
14
+ # @returns [string]
15
+ attr_reader :encoded_value
16
+ # The type of the variable, as a language-agnostic enum.
17
+ # @returns [Type]
18
+ attr_reader :type
19
+
20
+ def initialize(key:, protocol_key:, encoded_value:, type:)
21
+ @key = key
22
+ @protocol_key = protocol_key
23
+ @encoded_value = encoded_value
24
+ @type = type
25
+ end
26
+
27
+ def environment
28
+ return :global if key == protocol_key
29
+ return key.delete_prefix(protocol_key) if key.start_with?(protocol_key)
30
+
31
+ raise("Precondition failure: the protocol_key '#{protocol_key}' is not the same as the key '#{key}' nor is its prefix. This state shouldn't be possible.")
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "arkana/models/secret"
4
+
5
+ # A class that defines template arguments in a language-agnostic form.
6
+ class TemplateArguments
7
+ # Generates template arguments for the given secrets and config specifications.
8
+ def initialize(environment_secrets:, global_secrets:, config:, salt:)
9
+ # The environments declared in the config file.
10
+ @environments = config.environments
11
+ # The salt used to encode all the secrets.
12
+ @salt = salt
13
+ # The encoded environment-specific secrets.
14
+ @environment_secrets = environment_secrets
15
+ # The encoded global secrets.
16
+ @global_secrets = global_secrets
17
+ # Name of the import statements (or the interfaces' prefixes).
18
+ @import_name = config.import_name
19
+ # Name of the pod that should be generated.
20
+ @pod_name = config.pod_name
21
+ # The top level namespace in which the keys will be generated. Often an enum.
22
+ @namespace = config.namespace
23
+ # The property declaration strategy declared in the config file.
24
+ @swift_declaration_strategy = config.swift_declaration_strategy
25
+ # Whether unit tests should be generated.
26
+ @should_generate_unit_tests = config.should_generate_unit_tests
27
+ end
28
+
29
+ def environment_protocol_secrets(environment)
30
+ @environment_secrets.select do |secret|
31
+ secret.environment == environment
32
+ end
33
+ end
34
+
35
+ # Generates a new test secret for a given key, using the salt stored.
36
+ def generate_test_secret(key:)
37
+ # Yes, we encode the key as the value because this is just for testing purposes
38
+ encoded_value = Encoder.encode(key, @salt.raw)
39
+ Secret.new(key: key, protocol_key: key, encoded_value: encoded_value, type: :string)
40
+ end
41
+
42
+ # Expose private `binding` method.
43
+ # rubocop:disable Naming/AccessorMethodName
44
+ def get_binding
45
+ binding
46
+ end
47
+ # rubocop:enable Naming/AccessorMethodName
48
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A namespace that defines language-agnostic variable types.
4
+ module Type
5
+ STRING = :string
6
+ BOOLEAN = :boolean
7
+
8
+ def self.new(string_value:)
9
+ case string_value
10
+ when "true", "false"
11
+ BOOLEAN
12
+ else
13
+ STRING
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "models/salt"
5
+
6
+ # Responsible for generating the salt.
7
+ module SaltGenerator
8
+ SALT_LENGTH = 64
9
+
10
+ # Generates and returns a new Salt object.
11
+ #
12
+ # @return [Salt] the salt that was just generated.
13
+ def self.generate
14
+ chars = SecureRandom.random_bytes(SALT_LENGTH).chars # Convert array of random bytes to array of characters
15
+ codepoints = chars.map(&:ord) # Convert each character to its codepoint representation
16
+ Salt.new(raw: codepoints)
17
+ end
18
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require_relative "helpers/string"
5
+
6
+ # Responsible for generating Swift source and test files.
7
+ module SwiftCodeGenerator
8
+ # Generates Swift code and test files for the given template arguments.
9
+ def self.generate(template_arguments:, config:)
10
+ interface_swift_package_dir = File.join(config.result_path, "#{config.import_name}Interfaces")
11
+ set_up_interfaces_swift_package(interface_swift_package_dir, template_arguments, config)
12
+
13
+ swift_package_dir = File.join(config.result_path, config.import_name)
14
+ set_up_swift_package(swift_package_dir, template_arguments, config)
15
+ end
16
+
17
+ def self.set_up_interfaces_swift_package(path, template_arguments, config)
18
+ dirname = File.dirname(__FILE__)
19
+ readme_template = File.read("#{dirname}/templates/interfaces_readme.erb")
20
+ package_template = File.read("#{dirname}/templates/interfaces_package.swift.erb")
21
+ podspec_template = File.read("#{dirname}/templates/interfaces.podspec.erb")
22
+ sources_dir = File.join(path, "Sources")
23
+ source_template = File.read("#{dirname}/templates/arkana_protocol.swift.erb")
24
+ FileUtils.mkdir_p(path)
25
+ FileUtils.mkdir_p(sources_dir)
26
+ render(podspec_template, template_arguments, File.join(path, "#{config.pod_name.capitalize_first_letter}Interfaces.podspec")) if config.package_manager == "cocoapods"
27
+ render(readme_template, template_arguments, File.join(path, "README.md"))
28
+ render(package_template, template_arguments, File.join(path, "Package.swift"))
29
+ render(source_template, template_arguments, File.join(sources_dir, "#{config.import_name}Interfaces.swift"))
30
+ end
31
+
32
+ def self.set_up_swift_package(path, template_arguments, config)
33
+ dirname = File.dirname(__FILE__)
34
+ readme_template = File.read("#{dirname}/templates/readme.erb")
35
+ package_template = File.read("#{dirname}/templates/package.swift.erb")
36
+ sources_dir = File.join(path, "Sources")
37
+ source_template = File.read("#{dirname}/templates/arkana.swift.erb")
38
+ tests_dir = File.join(path, "Tests") if config.should_generate_unit_tests
39
+ tests_template = File.read("#{dirname}/templates/arkana_tests.swift.erb")
40
+ podspec_template = File.read("#{dirname}/templates/arkana.podspec.erb")
41
+ FileUtils.mkdir_p(path)
42
+ FileUtils.mkdir_p(sources_dir)
43
+ FileUtils.mkdir_p(tests_dir) if config.should_generate_unit_tests
44
+ render(readme_template, template_arguments, File.join(path, "README.md"))
45
+ render(package_template, template_arguments, File.join(path, "Package.swift"))
46
+ render(podspec_template, template_arguments, File.join(path, "#{config.pod_name.capitalize_first_letter}.podspec")) if config.package_manager == "cocoapods"
47
+ render(source_template, template_arguments, File.join(sources_dir, "#{config.import_name}.swift"))
48
+ render(tests_template, template_arguments, File.join(tests_dir, "#{config.import_name}Tests.swift")) if config.should_generate_unit_tests
49
+ end
50
+
51
+ def self.render(template, template_arguments, destination_file)
52
+ renderer = ERB.new(template, trim_mode: ">") # Don't automatically add newlines at the end of each template tag
53
+ result = renderer.result(template_arguments.get_binding)
54
+ File.write(destination_file, result)
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated by Arkana. Do not attempt to modify it.
4
+
5
+ Pod::Spec.new do |spec|
6
+ spec.name = '<%= @pod_name %>'
7
+ spec.summary = 'Contains env vars and secrets. Autogenerated by Arkana.'
8
+ spec.homepage = 'https://github.com/rogerluan/arkana'
9
+ spec.version = '1.0.0'
10
+ spec.authors = { 'Arkana' => 'https://github.com/rogerluan/arkana' }
11
+ spec.source = { path: "./" }
12
+ spec.source_files = 'Sources/**/*.swift'
13
+ spec.ios.deployment_target = '11.0'
14
+ spec.swift_version = '5.0'
15
+ end
@@ -0,0 +1,52 @@
1
+ <% require 'arkana/helpers/string' %>
2
+ <% require 'arkana/helpers/swift_template_helper' %>
3
+ <% # TODO: Sort these import statements alphabetically %>
4
+ import Foundation
5
+ import <%= @import_name %>Interfaces
6
+
7
+ public enum <%= @namespace %> {
8
+ fileprivate static let salt: [UInt8] = [
9
+ <%= @salt.formatted %>
10
+
11
+ ]
12
+
13
+ static func decode(encoded: [UInt8], cipher: [UInt8]) -> String {
14
+ return String(decoding: encoded.enumerated().map { (offset, element) in
15
+ element ^ cipher[offset % cipher.count]
16
+ }, as: UTF8.self)
17
+ }
18
+ }
19
+
20
+ public extension <%= @namespace %> {
21
+ struct Global: <%= @import_name %>GlobalProtocol {
22
+ public init() {}
23
+ <% for secret in @global_secrets %>
24
+
25
+ public <%= @swift_declaration_strategy %> <%= secret.key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> = {
26
+ let encoded: [UInt8] = [
27
+ <%= secret.encoded_value %>
28
+
29
+ ]
30
+ return <%= @namespace %>.decode(encoded: encoded, cipher: <%= @namespace %>.salt)
31
+ }()
32
+ <% end %>
33
+ }
34
+ }
35
+
36
+ <% for environment in @environments %>
37
+ public extension <%= @namespace %> {
38
+ struct <%= environment %>: <%= @import_name %>EnvironmentProtocol {
39
+ public init() {}
40
+ <% for secret in environment_protocol_secrets(environment) %>
41
+
42
+ public <%= @swift_declaration_strategy %> <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> = {
43
+ let encoded: [UInt8] = [
44
+ <%= secret.encoded_value %>
45
+
46
+ ]
47
+ return <%= @namespace %>.decode(encoded: encoded, cipher: <%= @namespace %>.salt)
48
+ }()
49
+ <% end %>
50
+ }
51
+ }
52
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <% require 'arkana/helpers/string' %>
2
+ <% require 'arkana/helpers/swift_template_helper' %>
3
+ import Foundation
4
+
5
+ public protocol <%= @namespace %>GlobalProtocol {
6
+ <% for secret in @global_secrets %>
7
+ var <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> { get }
8
+ <% end %>
9
+ }
10
+
11
+ public protocol <%= @namespace %>EnvironmentProtocol {
12
+ <% for secret in @environment_secrets.uniq(&:protocol_key) %>
13
+ var <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> { get }
14
+ <% end %>
15
+ }
@@ -0,0 +1,41 @@
1
+ import Foundation
2
+ import XCTest
3
+ @testable import <%= @import_name %>
4
+
5
+
6
+ final class <%= @namespace %>Tests: XCTestCase {
7
+ private var salt: [UInt8] = [
8
+ <%= @salt.formatted %>
9
+
10
+ ]
11
+
12
+ func test_decodeRandomHexKey_shouldDecode() {
13
+ <% hex_key = SecureRandom.hex(64) %>
14
+ <% secret = generate_test_secret(key: hex_key) %>
15
+ let encoded: [UInt8] = [
16
+ <%= secret.encoded_value %>
17
+
18
+ ]
19
+ XCTAssertEqual(<%= @namespace %>.decode(encoded: encoded, cipher: salt), "<%= hex_key %>")
20
+ }
21
+
22
+ func test_decodeRandomBase64Key_shouldDecode() {
23
+ <% base64_key = SecureRandom.base64(64) %>
24
+ <% secret = generate_test_secret(key: base64_key) %>
25
+ let encoded: [UInt8] = [
26
+ <%= secret.encoded_value %>
27
+
28
+ ]
29
+ XCTAssertEqual(<%= @namespace %>.decode(encoded: encoded, cipher: salt), "<%= base64_key %>")
30
+ }
31
+
32
+ func test_decodeUUIDKey_shouldDecode() {
33
+ <% uuid_key = SecureRandom.uuid %>
34
+ <% secret = generate_test_secret(key: uuid_key) %>
35
+ let encoded: [UInt8] = [
36
+ <%= secret.encoded_value %>
37
+
38
+ ]
39
+ XCTAssertEqual(<%= @namespace %>.decode(encoded: encoded, cipher: salt), "<%= uuid_key %>")
40
+ }
41
+ }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file was autogenerated by Arkana. Do not attempt to modify it.
4
+
5
+ Pod::Spec.new do |spec|
6
+ spec.name = '<%= @pod_name %>Interfaces'
7
+ spec.summary = 'Contains env vars and secrets. Autogenerated by Arkana.'
8
+ spec.homepage = 'https://github.com/rogerluan/arkana'
9
+ spec.version = '1.0.0'
10
+ spec.authors = { 'Arkana' => 'https://github.com/rogerluan/arkana' }
11
+ spec.source = { path: "./" }
12
+ spec.source_files = 'Sources/**/*.swift'
13
+ spec.ios.deployment_target = '11.0'
14
+ spec.swift_version = '5.0'
15
+ end
@@ -0,0 +1,27 @@
1
+ // swift-tools-version: 5.6
2
+
3
+ // This file was autogenerated by Arkana. Do not attempt to modify it.
4
+
5
+ import PackageDescription
6
+
7
+ let package = Package(
8
+ name: "<%= @import_name %>Interfaces",
9
+ platforms: [
10
+ .macOS(.v11),
11
+ .iOS(.v11),
12
+ ],
13
+ products: [
14
+ .library(
15
+ name: "<%= @import_name %>Interfaces",
16
+ targets: ["<%= @import_name %>Interfaces"]
17
+ ),
18
+ ],
19
+ dependencies: [],
20
+ targets: [
21
+ .target(
22
+ name: "<%= @import_name %>Interfaces",
23
+ dependencies: [],
24
+ path: "Sources"
25
+ ),
26
+ ]
27
+ )
@@ -0,0 +1,3 @@
1
+ # <%= @namespace %>Interfaces
2
+
3
+ This Package was autogenerated by [Arkana](https://github.com/rogerluan/arkana). Do not attempt to modify it manually, otherwise your changes will be overriden in the next code generation process. Please visit [Arkana](https://github.com/rogerluan/arkana) to read more.
@@ -0,0 +1,36 @@
1
+ // swift-tools-version: 5.6
2
+
3
+ // This file was autogenerated by Arkana. Do not attempt to modify it.
4
+
5
+ import PackageDescription
6
+
7
+ let package = Package(
8
+ name: "<%= @import_name %>",
9
+ platforms: [
10
+ .macOS(.v11),
11
+ .iOS(.v11),
12
+ ],
13
+ products: [
14
+ .library(
15
+ name: "<%= @import_name %>",
16
+ targets: ["<%= @import_name %>"]
17
+ ),
18
+ ],
19
+ dependencies: [
20
+ .package(name: "<%= @import_name%>Interfaces", path: "../<%= @import_name%>Interfaces"),
21
+ ],
22
+ targets: [
23
+ .target(
24
+ name: "<%= @import_name %>",
25
+ dependencies: ["<%= @import_name %>Interfaces"],
26
+ path: "Sources"
27
+ ),
28
+ <% if @should_generate_unit_tests %>
29
+ .testTarget(
30
+ name: "<%= @import_name %>Tests",
31
+ dependencies: ["<%= @import_name %>"],
32
+ path: "Tests"
33
+ ),
34
+ <% end %>
35
+ ]
36
+ )
@@ -0,0 +1,4 @@
1
+ # <%= @namespace %>
2
+
3
+
4
+ This Package is autogenerated by [Arkana](https://github.com/rogerluan/arkana). Do not attempt to modify it manually, otherwise your changes will be overriden on the next code generation process. Please visit [Arkana](https://github.com/rogerluan/arkana) to read more.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arkana
4
+ VERSION = "1.0.0"
5
+ end
data/lib/arkana.rb ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "arkana/config_parser"
4
+ require_relative "arkana/encoder"
5
+ require_relative "arkana/helpers/dotenv_helper"
6
+ require_relative "arkana/models/template_arguments"
7
+ require_relative "arkana/salt_generator"
8
+ require_relative "arkana/swift_code_generator"
9
+ require_relative "arkana/version"
10
+
11
+ # Top-level namespace for Arkana's execution entry point. When ran from CLI, `Arkana.run` is what is invoked.
12
+ module Arkana
13
+ def self.run(arguments)
14
+ config = ConfigParser.parse(arguments)
15
+ salt = SaltGenerator.generate
16
+ DotenvHelper.load(config)
17
+
18
+ begin
19
+ environment_secrets = Encoder.encode!(
20
+ keys: config.environment_keys,
21
+ salt: salt,
22
+ current_flavor: config.current_flavor,
23
+ environments: config.environments,
24
+ )
25
+ global_secrets = Encoder.encode!(
26
+ keys: config.global_secrets,
27
+ salt: salt,
28
+ current_flavor: config.current_flavor,
29
+ environments: config.environments,
30
+ )
31
+ rescue StandardError => e
32
+ # TODO: Improve this by creating an Env/Debug helper
33
+ puts("Something went wrong when parsing and encoding your secrets.")
34
+ puts("Current Flavor: #{config.current_flavor}")
35
+ puts("Dotenv Filepath: #{config.dotenv_filepath}")
36
+ puts("Config Filepath: #{arguments.config_filepath}")
37
+ raise e
38
+ end
39
+ template_arguments = TemplateArguments.new(
40
+ environment_secrets: environment_secrets,
41
+ global_secrets: global_secrets,
42
+ config: config,
43
+ salt: salt,
44
+ )
45
+ SwiftCodeGenerator.generate(
46
+ template_arguments: template_arguments,
47
+ config: config,
48
+ )
49
+ end
50
+ end
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arkana
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roger Oba
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-21 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2022-07-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yaml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
13
55
  description:
14
56
  email:
15
57
  - rogerluan.oba@gmail.com
@@ -18,14 +60,42 @@ executables:
18
60
  extensions: []
19
61
  extra_rdoc_files: []
20
62
  files:
21
- - LICENSE
22
63
  - bin/arkana
64
+ - bin/console
65
+ - bin/setup
66
+ - lib/arkana.rb
67
+ - lib/arkana/config_parser.rb
68
+ - lib/arkana/encoder.rb
69
+ - lib/arkana/helpers/dotenv_helper.rb
70
+ - lib/arkana/helpers/string.rb
71
+ - lib/arkana/helpers/swift_template_helper.rb
72
+ - lib/arkana/helpers/ui.rb
73
+ - lib/arkana/models/arguments.rb
74
+ - lib/arkana/models/config.rb
75
+ - lib/arkana/models/salt.rb
76
+ - lib/arkana/models/secret.rb
77
+ - lib/arkana/models/template_arguments.rb
78
+ - lib/arkana/models/type.rb
79
+ - lib/arkana/salt_generator.rb
80
+ - lib/arkana/swift_code_generator.rb
81
+ - lib/arkana/templates/arkana.podspec.erb
82
+ - lib/arkana/templates/arkana.swift.erb
83
+ - lib/arkana/templates/arkana_protocol.swift.erb
84
+ - lib/arkana/templates/arkana_tests.swift.erb
85
+ - lib/arkana/templates/interfaces.podspec.erb
86
+ - lib/arkana/templates/interfaces_package.swift.erb
87
+ - lib/arkana/templates/interfaces_readme.erb
88
+ - lib/arkana/templates/package.swift.erb
89
+ - lib/arkana/templates/readme.erb
90
+ - lib/arkana/version.rb
23
91
  homepage: https://github.com/rogerluan/arkana
24
- licenses: []
92
+ licenses:
93
+ - BSD-2-Clause
25
94
  metadata:
26
95
  homepage_uri: https://github.com/rogerluan/arkana
27
96
  source_code_uri: https://github.com/rogerluan/arkana
28
97
  changelog_uri: https://github.com/rogerluan/arkana/blob/main/CHANGELOG.md
98
+ rubygems_mfa_required: 'true'
29
99
  post_install_message:
30
100
  rdoc_options: []
31
101
  require_paths:
@@ -34,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
34
104
  requirements:
35
105
  - - ">="
36
106
  - !ruby/object:Gem::Version
37
- version: 2.6.0
107
+ version: 2.7.0
38
108
  required_rubygems_version: !ruby/object:Gem::Requirement
39
109
  requirements:
40
110
  - - ">="
@@ -44,5 +114,6 @@ requirements: []
44
114
  rubygems_version: 3.1.6
45
115
  signing_key:
46
116
  specification_version: 4
47
- summary: Store your iOS keys and secrets away from your source code
117
+ summary: Store your keys and secrets away from your source code. Designed for Android
118
+ and iOS projects.
48
119
  test_files: []
data/LICENSE DELETED
@@ -1,25 +0,0 @@
1
- BSD 2-Clause License
2
-
3
- Copyright (c) 2022, Roger Oba
4
- All rights reserved.
5
-
6
- Redistribution and use in source and binary forms, with or without
7
- modification, are permitted provided that the following conditions are met:
8
-
9
- 1. Redistributions of source code must retain the above copyright notice, this
10
- list of conditions and the following disclaimer.
11
-
12
- 2. Redistributions in binary form must reproduce the above copyright notice,
13
- this list of conditions and the following disclaimer in the documentation
14
- and/or other materials provided with the distribution.
15
-
16
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.