diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..a049044 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,43 @@ +# Ruby CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-ruby/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/ruby:2.5 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "Gemfile" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: bundle check || bundle install --jobs=4 --retry=3 --path vendor/bundle + + - save_cache: + paths: + - ./vendor + key: v1-dependencies-{{ checksum "Gemfile" }} + + # run tests! + - run: + name: run tests + command: bundle exec rake + + # collect reports + - store_test_results: + path: ~/repo/test-results + - store_artifacts: + path: ~/repo/test-results + destination: test-results diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..56cf580 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test + +on: + push: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v1 + with: + path: vendor/bundle + key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile') }} + restore-keys: | + ${{ runner.os }}-gem- + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.5 + - name: Install dependencies + run: bundle check || bundle install --jobs=4 --retry=3 --path vendor/bundle + - name: Run tests + run: bundle exec rake + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: test-results + path: test-results diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7e3986 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.gem +Gemfile.lock + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ +fastlane/README.md +fastlane/report.xml +coverage +test-results diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..e1f89b5 --- /dev/null +++ b/.rspec @@ -0,0 +1,5 @@ +--require spec_helper +--color +--format d +--format RspecJunitFormatter +--out test-results/rspec/rspec.xml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..0a5cec9 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,196 @@ +--- +require: +- rubocop/require_tools +- rubocop-performance +AllCops: + TargetRubyVersion: 2.6 + NewCops: enable + Include: + - "**/*.rb" + - "**/*file" + - "**/*.gemspec" + - "*/lib/assets/*Template" + - "*/lib/assets/*TemplateAndroid" + Exclude: + - "**/lib/assets/custom_action_template.rb" + - "./vendor/**/*" + - "**/lib/assets/DefaultFastfileTemplate" + - "**/lib/assets/MatchfileTemplate" + - "**/spec/fixtures/broken_files/broken_file.rb" + - "**/*.provisionprofile" +Lint/ErbNewArguments: + Enabled: false +Style/SlicingWithRange: + Enabled: false +Style/MultipleComparison: + Enabled: false +Style/PercentLiteralDelimiters: + Enabled: false +Style/ClassCheck: + EnforcedStyle: kind_of? +Style/FrozenStringLiteralComment: + Enabled: false +Style/SafeNavigation: + Enabled: false +Performance/RegexpMatch: + Enabled: false +Performance/StringReplacement: + Enabled: false +Style/NumericPredicate: + Enabled: false +Metrics/BlockLength: + Enabled: false +Metrics/ModuleLength: + Enabled: false +Naming/VariableNumber: + Enabled: false +Style/MissingRespondToMissing: + Enabled: false +Style/MultilineBlockChain: + Enabled: false +Style/NumericLiteralPrefix: + Enabled: false +Style/TernaryParentheses: + Enabled: false +Style/EmptyMethod: + Enabled: false +Lint/UselessAssignment: + Exclude: + - "**/spec/**/*" +Require/MissingRequireStatement: + Exclude: + - "**/spec/**/*.rb" + - "**/spec_helper.rb" + - spaceship/lib/spaceship/babosa_fix.rb + - fastlane_core/lib/fastlane_core/ui/disable_colors.rb + - "**/Fastfile" + - "**/*.gemspec" + - rakelib/**/* + - "**/*.rake" + - "**/Rakefile" + - fastlane/**/* + - supply/**/* +Layout/FirstHashElementIndentation: + Enabled: false +Layout/HashAlignment: + Enabled: false +Layout/DotPosition: + Enabled: false +Style/DoubleNegation: + Enabled: false +Style/SymbolArray: + Enabled: false +Layout/HeredocIndentation: + Enabled: false +Style/MixinGrouping: + Exclude: + - "**/spec/**/*" +Lint/SuppressedException: + Enabled: false +Lint/UnusedBlockArgument: + Enabled: false +Lint/AmbiguousBlockAssociation: + Enabled: false +Style/GlobalVars: + Enabled: false +Style/ClassAndModuleChildren: + Enabled: false +Style/SpecialGlobalVars: + Enabled: false +Metrics/AbcSize: + Enabled: false +Metrics/MethodLength: + Enabled: false +Metrics/CyclomaticComplexity: + Enabled: false +Style/WordArray: + MinSize: 19 +Style/SignalException: + Enabled: false +Style/RedundantReturn: + Enabled: false +Style/IfUnlessModifier: + Enabled: false +Style/AndOr: + Enabled: true + EnforcedStyle: conditionals +Metrics/ClassLength: + Max: 320 +Layout/LineLength: + Max: 370 +Metrics/ParameterLists: + Max: 17 +Style/GuardClause: + Enabled: false +Style/StringLiterals: + Enabled: false +Style/QuotedSymbols: + Enabled: false +Style/ConditionalAssignment: + Enabled: false +Style/RedundantSelf: + Enabled: false +Lint/UnusedMethodArgument: + Enabled: false +Lint/ParenthesesAsGroupedExpression: + Exclude: + - "**/spec/**/*" +Naming/PredicateName: + Enabled: false +Style/PerlBackrefs: + Enabled: false +Layout/SpaceAroundOperators: + Exclude: + - "**/spec/actions_specs/xcodebuild_spec.rb" +Naming/FileName: + Exclude: + - "**/Dangerfile" + - "**/Brewfile" + - "**/Gemfile" + - "**/Podfile" + - "**/Rakefile" + - "**/Fastfile" + - "**/Deliverfile" + - "**/Snapfile" + - "**/Pluginfile" + - "**/*.gemspec" +Style/Documentation: + Enabled: false +Style/MutableConstant: + Enabled: false +Style/ZeroLengthPredicate: + Enabled: false +Style/IfInsideElse: + Enabled: false +Style/CollectionMethods: + Enabled: false +Style/MethodCallWithArgsParentheses: + Enabled: true + AllowedMethods: + - require + - require_relative + - fastlane_require + - gem + - program + - command + - raise + - attr_accessor + - attr_reader + - desc + - lane + - private_lane + - platform + - to + - not_to + - describe + - it + - be + - context + - before + - after +Bundler/OrderedGems: + Enabled: true + TreatCommentsAsGroupSeparators: false +Gemspec/DevelopmentDependencies: + Enabled: true + EnforcedStyle: Gemfile diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3b35e45 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +# os: osx # enable this if you need macOS support +language: ruby +rvm: + - 2.2.4 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..fc7fa4a --- /dev/null +++ b/Gemfile @@ -0,0 +1,27 @@ +source('https://rubygems.org') + +# Provides a consistent environment for Ruby projects by tracking and installing exact gem versions. +gem 'bundler' +# Automation tool for mobile developers. +gem 'fastlane', '>= 2.221.1' +# Provides an interactive debugging environment for Ruby. +gem 'pry' +# A simple task automation tool. +gem 'rake' +# Behavior-driven testing tool for Ruby. +gem 'rspec' +# Formatter for RSpec to generate JUnit compatible reports. +gem 'rspec_junit_formatter' +# A Ruby static code analyzer and formatter. +gem 'rubocop', '1.65.0' +# A collection of RuboCop cops for performance optimizations. +gem 'rubocop-performance' +# A RuboCop extension focused on enforcing tools. +gem 'rubocop-require_tools' +# SimpleCov is a code coverage analysis tool for Ruby. +gem 'simplecov' + +gemspec + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dc2efae --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Dominik Kapusta + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index df01a62..e829cd6 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# fastlane-plugin-ddg_release_automation \ No newline at end of file +# ddg_release_automation plugin + +[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-ddg_release_automation) + +## Getting Started + +This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-ddg_release_automation`, add it to your project by running: + +```bash +fastlane add_plugin ddg_release_automation +``` + +## About ddg_release_automation + +This plugin contains actions used in DuckDuckGo iOS and macOS release automation + +**Note to author:** Add a more detailed description about this plugin here. If your plugin contains multiple actions, make sure to mention them here. + +## Example + +Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`. + +**Note to author:** Please set up a sample project to make it easy for users to explore what your plugin does. Provide everything that is necessary to try out the plugin in this project (including a sample Xcode/Android project if necessary) + +## Run tests for this plugin + +To run both the tests, and code style validation, run + +``` +rake +``` + +To automatically fix many of the styling issues, use +``` +rubocop -a +``` + +## Issues and Feedback + +For any other issues and feedback about this plugin, please submit it to this repository. + +## Troubleshooting + +If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide. + +## Using _fastlane_ Plugins + +For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/). + +## About _fastlane_ + +_fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..094ebe2 --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'bundler/gem_tasks' + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new + +require 'rubocop/rake_task' +RuboCop::RakeTask.new(:rubocop) + +task(default: [:spec, :rubocop]) diff --git a/fastlane-plugin-ddg_release_automation.gemspec b/fastlane-plugin-ddg_release_automation.gemspec new file mode 100644 index 0000000..67a1fa5 --- /dev/null +++ b/fastlane-plugin-ddg_release_automation.gemspec @@ -0,0 +1,24 @@ +lib = File.expand_path("lib", __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'fastlane/plugin/ddg_release_automation/version' + +Gem::Specification.new do |spec| + spec.name = 'fastlane-plugin-ddg_release_automation' + spec.version = Fastlane::DdgReleaseAutomation::VERSION + spec.author = 'DuckDuckGo' + spec.email = 'ios@duckduckgo.com' + + spec.summary = 'This plugin contains actions used in DuckDuckGo iOS and macOS release automation' + spec.homepage = "https://github.com/duckduckgo/apple-toolbox" + spec.license = "MIT" + + spec.files = Dir["lib/**/*"] + %w(README.md LICENSE) + spec.require_paths = ['lib'] + spec.metadata['rubygems_mfa_required'] = 'true' + spec.required_ruby_version = '>= 2.6' + + # Don't add a dependency to fastlane or fastlane_re + # since this would cause a circular dependency + + # spec.add_dependency 'your-dependency', '~> 1.0.0' +end diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..2eec8bb --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,3 @@ +lane :test do + ddg_release_automation +end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000..e0576b0 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1 @@ +# Autogenerated by fastlane diff --git a/lib/fastlane/plugin/ddg_release_automation.rb b/lib/fastlane/plugin/ddg_release_automation.rb new file mode 100644 index 0000000..874c8b0 --- /dev/null +++ b/lib/fastlane/plugin/ddg_release_automation.rb @@ -0,0 +1,16 @@ +require 'fastlane/plugin/ddg_release_automation/version' + +module Fastlane + module DdgReleaseAutomation + # Return all .rb files inside the "actions" and "helper" directory + def self.all_classes + Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))] + end + end +end + +# By default we want to import all available actions and helpers +# A plugin can contain any number of actions and plugins +Fastlane::DdgReleaseAutomation.all_classes.each do |current| + require current +end diff --git a/lib/fastlane/plugin/ddg_release_automation/actions/asana_extract_task_id_action.rb b/lib/fastlane/plugin/ddg_release_automation/actions/asana_extract_task_id_action.rb new file mode 100644 index 0000000..5e16670 --- /dev/null +++ b/lib/fastlane/plugin/ddg_release_automation/actions/asana_extract_task_id_action.rb @@ -0,0 +1,57 @@ +require "fastlane/action" +require_relative "../helper/ddg_release_automation_helper" + +module Fastlane + module Actions + class AsanaExtractTaskIdAction < Action + TASK_URL_REGEX = %r{https://app.asana.com/[0-9]/[0-9]+/([0-9]+)(:/f)?} + ERROR_MESSAGE = "URL has incorrect format (attempted to match #{TASK_URL_REGEX})" + + def self.run(params) + task_url = params[:task_url] + + if (match = task_url.match(TASK_URL_REGEX)) + task_id = match[1] + + if Helper.is_ci? + Helper::GitHubActionsHelper.set_output("ASANA_TASK_ID", task_id) + end + + task_id + else + UI.user_error!(ERROR_MESSAGE) + end + end + + def self.description + "This action extracts the task ID from an Asana task URL" + end + + def self.authors + ["DuckDuckGo"] + end + + def self.return_value + "The task ID extracted from the Asana task URL" + end + + def self.details + # Optional: + "" + end + + def self.available_options + [ + FastlaneCore::ConfigItem.new(key: :task_url, + description: "Asana task URL", + optional: false, + type: String), + ] + end + + def self.is_supported?(platform) + true + end + end + end +end diff --git a/lib/fastlane/plugin/ddg_release_automation/helper/ddg_release_automation_helper.rb b/lib/fastlane/plugin/ddg_release_automation/helper/ddg_release_automation_helper.rb new file mode 100644 index 0000000..6447bf5 --- /dev/null +++ b/lib/fastlane/plugin/ddg_release_automation/helper/ddg_release_automation_helper.rb @@ -0,0 +1,16 @@ +require 'fastlane_core/ui/ui' + +module Fastlane + UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI) + + module Helper + class DdgReleaseAutomationHelper + # class methods that you define here become available in your action + # as `Helper::DdgReleaseAutomationHelper.your_method` + # + def self.show_message + UI.message("Hello from the ddg_release_automation plugin helper!") + end + end + end +end diff --git a/lib/fastlane/plugin/ddg_release_automation/helper/github_actions_helper.rb b/lib/fastlane/plugin/ddg_release_automation/helper/github_actions_helper.rb new file mode 100644 index 0000000..d85e2cd --- /dev/null +++ b/lib/fastlane/plugin/ddg_release_automation/helper/github_actions_helper.rb @@ -0,0 +1,13 @@ +require "fastlane_core/ui/ui" + +module Fastlane + UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI) + + module Helper + class GitHubActionsHelper + def self.set_output(key, value) + Action.sh("echo '#{key}=#{value}' >> #{ENV.fetch("GITHUB_OUTPUT", "/dev/null")}") + end + end + end +end diff --git a/lib/fastlane/plugin/ddg_release_automation/version.rb b/lib/fastlane/plugin/ddg_release_automation/version.rb new file mode 100644 index 0000000..ce99aba --- /dev/null +++ b/lib/fastlane/plugin/ddg_release_automation/version.rb @@ -0,0 +1,5 @@ +module Fastlane + module DdgReleaseAutomation + VERSION = "0.1.0" + end +end diff --git a/spec/asana_extract_task_id_action_spec.rb b/spec/asana_extract_task_id_action_spec.rb new file mode 100644 index 0000000..341c40f --- /dev/null +++ b/spec/asana_extract_task_id_action_spec.rb @@ -0,0 +1,49 @@ +describe Fastlane::Actions::AsanaExtractTaskIdAction do + describe "#run" do + it "extracts task ID" do + expect(test_action("https://app.asana.com/0/0/0")).to eq("0") + end + + it "extracts task ID when project ID is non-zero" do + expect(test_action("https://app.asana.com/0/753241/9999")).to eq("9999") + end + + it "extracts task ID when first digit is non-zero" do + expect(test_action("https://app.asana.com/4/753241/9999")).to eq("9999") + end + + it "extracts long task ID" do + expect(test_action("https://app.asana.com/0/0/12837864576817392")).to eq("12837864576817392") + end + + it "extracts task ID from a URL with a trailing /f" do + expect(test_action("https://app.asana.com/0/0/1234/f")).to eq("1234") + end + + it "does not set GHA output when not in CI" do + allow(Fastlane::Helper).to receive(:is_ci?).and_return(false) + allow(Fastlane::Helper::GitHubActionsHelper).to receive(:set_output) + + expect(test_action("https://app.asana.com/0/12837864576817392/3465387322")).to eq("3465387322") + expect(Fastlane::Helper::GitHubActionsHelper).not_to have_received(:set_output) + end + + it "sets GHA output in CI" do + allow(Fastlane::Helper).to receive(:is_ci?).and_return(true) + allow(Fastlane::Helper::GitHubActionsHelper).to receive(:set_output) + + expect(test_action("https://app.asana.com/0/12837864576817392/3465387322")).to eq("3465387322") + expect(Fastlane::Helper::GitHubActionsHelper).to have_received(:set_output).with("ASANA_TASK_ID", "3465387322") + end + + it "fails when garbage is passed" do + expect(Fastlane::UI).to receive(:user_error!).with(Fastlane::Actions::AsanaExtractTaskIdAction::ERROR_MESSAGE) + + test_action("not a URL") + end + end + + def test_action(task_url) + Fastlane::Actions::AsanaExtractTaskIdAction.run(task_url: task_url) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..34d89cc --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,15 @@ +$LOAD_PATH.unshift(File.expand_path('../lib', __dir__)) + +require 'simplecov' + +# SimpleCov.minimum_coverage 95 +SimpleCov.start + +# This module is only used to check the environment is currently a testing env +module SpecHelper +end + +require 'fastlane' # to import the Action super class +require 'fastlane/plugin/ddg_release_automation' # import the actual plugin + +Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values)