arkana 0.1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92172ad1f72c9c868c0a2a56c3d4b39ff925c7f12e87726e49b899feaced2665
4
- data.tar.gz: 902c779d2285c30fd456450fe3487ae66e990c2c96b3f2d71e96e8c2d77f06e9
3
+ metadata.gz: 5f21dca107c8705dd68f62e1ebc9f726b2cff328915fc70f6d9f8eb0a488ea42
4
+ data.tar.gz: 62bdbaeed35fbc9b1e696916627aaf5d7a4b28ad74e547fe62837f1bcd895d07
5
5
  SHA512:
6
- metadata.gz: a9c93524a1aae1403ce277ca40cbd9befa86c6f0f32b9762a03fcc7f52c6cc79fb6e900fe8a8c3b5a5ad95d23fd559c8da9afe3cac21d9e746d8f11dd20c67b5
7
- data.tar.gz: ce59a330a1dc11e689e2fac24e4dbc1fcabcc2114ca648d205eb5b85181e3eb541b9aebbed6fa51469791275f98c6787b93890b9ff9f41d3258ef94c1dad27c9
6
+ metadata.gz: fcb406e5b832dadbfbf465f493225b784304a2d7e10d56f9a8802414f953a6af00a44509d029f21809d9308382979560557f703c23d803c1bb9f1b06ae726f6f
7
+ data.tar.gz: 2eb298ec9f709f7b2928b297834a474a63a29d1fa65ec4fbd422c71d4881e9c06fc2777cb4c66fe00b44811b17908e5f12fabb47b111d917210b2e8007a2ffb5
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,20 @@
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
+
13
+ def self.protocol_getter(declaration_strategy)
14
+ case declaration_strategy
15
+ when "lazy var" then "mutating get"
16
+ when "var", "let" then "get"
17
+ else raise "Unknown declaration strategy '#{declaration_strategy}' received.'"
18
+ end
19
+ end
20
+ 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,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "fileutils"
5
+ require_relative "helpers/string"
6
+
7
+ # Responsible for generating Swift source and test files.
8
+ module SwiftCodeGenerator
9
+ # Generates Swift code and test files for the given template arguments.
10
+ def self.generate(template_arguments:, config:)
11
+ interface_swift_package_dir = File.join(config.result_path, "#{config.import_name}Interfaces")
12
+ set_up_interfaces_swift_package(interface_swift_package_dir, template_arguments, config)
13
+
14
+ swift_package_dir = File.join(config.result_path, config.import_name)
15
+ set_up_swift_package(swift_package_dir, template_arguments, config)
16
+ end
17
+
18
+ def self.set_up_interfaces_swift_package(path, template_arguments, config)
19
+ dirname = File.dirname(__FILE__)
20
+ readme_template = File.read("#{dirname}/templates/interfaces_readme.erb")
21
+ package_template = File.read("#{dirname}/templates/interfaces_package.swift.erb")
22
+ podspec_template = File.read("#{dirname}/templates/interfaces.podspec.erb")
23
+ sources_dir = File.join(path, "Sources")
24
+ source_template = File.read("#{dirname}/templates/arkana_protocol.swift.erb")
25
+ FileUtils.mkdir_p(path)
26
+ FileUtils.mkdir_p(sources_dir)
27
+ render(podspec_template, template_arguments, File.join(path, "#{config.pod_name.capitalize_first_letter}Interfaces.podspec")) if config.package_manager == "cocoapods"
28
+ render(readme_template, template_arguments, File.join(path, "README.md"))
29
+ render(package_template, template_arguments, File.join(path, "Package.swift"))
30
+ render(source_template, template_arguments, File.join(sources_dir, "#{config.import_name}Interfaces.swift"))
31
+ end
32
+
33
+ def self.set_up_swift_package(path, template_arguments, config)
34
+ dirname = File.dirname(__FILE__)
35
+ readme_template = File.read("#{dirname}/templates/readme.erb")
36
+ package_template = File.read("#{dirname}/templates/package.swift.erb")
37
+ sources_dir = File.join(path, "Sources")
38
+ source_template = File.read("#{dirname}/templates/arkana.swift.erb")
39
+ tests_dir = File.join(path, "Tests") if config.should_generate_unit_tests
40
+ tests_template = File.read("#{dirname}/templates/arkana_tests.swift.erb")
41
+ podspec_template = File.read("#{dirname}/templates/arkana.podspec.erb")
42
+ FileUtils.mkdir_p(path)
43
+ FileUtils.mkdir_p(sources_dir)
44
+ FileUtils.mkdir_p(tests_dir) if config.should_generate_unit_tests
45
+ render(readme_template, template_arguments, File.join(path, "README.md"))
46
+ render(package_template, template_arguments, File.join(path, "Package.swift"))
47
+ render(podspec_template, template_arguments, File.join(path, "#{config.pod_name.capitalize_first_letter}.podspec")) if config.package_manager == "cocoapods"
48
+ render(source_template, template_arguments, File.join(sources_dir, "#{config.import_name}.swift"))
49
+ render(tests_template, template_arguments, File.join(tests_dir, "#{config.import_name}Tests.swift")) if config.should_generate_unit_tests
50
+ end
51
+
52
+ def self.render(template, template_arguments, destination_file)
53
+ renderer = ERB.new(template, trim_mode: ">") # Don't automatically add newlines at the end of each template tag
54
+ result = renderer.result(template_arguments.get_binding)
55
+ File.write(destination_file, result)
56
+ end
57
+ 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,55 @@
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
+ @inline(__always)
9
+ fileprivate static let salt: [UInt8] = [
10
+ <%= @salt.formatted %>
11
+
12
+ ]
13
+
14
+ static func decode(encoded: [UInt8], cipher: [UInt8]) -> String {
15
+ return String(decoding: encoded.enumerated().map { offset, element in
16
+ element ^ cipher[offset % cipher.count]
17
+ }, as: UTF8.self)
18
+ }
19
+ }
20
+
21
+ public extension <%= @namespace %> {
22
+ struct Global: <%= @namespace %>GlobalProtocol {
23
+ public init() {}
24
+ <% for secret in @global_secrets %>
25
+
26
+ @inline(__always)
27
+ public <%= @swift_declaration_strategy %> <%= secret.key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> = {
28
+ let encoded: [UInt8] = [
29
+ <%= secret.encoded_value %>
30
+
31
+ ]
32
+ return <%= @namespace %>.decode(encoded: encoded, cipher: <%= @namespace %>.salt)
33
+ }()
34
+ <% end %>
35
+ }
36
+ }
37
+
38
+ <% for environment in @environments %>
39
+ public extension <%= @namespace %> {
40
+ struct <%= environment %>: <%= @namespace %>EnvironmentProtocol {
41
+ public init() {}
42
+ <% for secret in environment_protocol_secrets(environment) %>
43
+
44
+ @inline(__always)
45
+ public <%= @swift_declaration_strategy %> <%= secret.protocol_key.camel_case %>: <%= SwiftTemplateHelper.swift_type(secret.type) %> = {
46
+ let encoded: [UInt8] = [
47
+ <%= secret.encoded_value %>
48
+
49
+ ]
50
+ return <%= @namespace %>.decode(encoded: encoded, cipher: <%= @namespace %>.salt)
51
+ }()
52
+ <% end %>
53
+ }
54
+ }
55
+ <% 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) %> { <%= SwiftTemplateHelper.protocol_getter(@swift_declaration_strategy) %> }
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) %> { <%= SwiftTemplateHelper.protocol_getter(@swift_declaration_strategy) %> }
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.1.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.1.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-05 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.