Module: Mysigner::CLI::ValidateCommands

Included in:
Mysigner::CLI
Defined in:
lib/mysigner/cli/validate_commands.rb

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/mysigner/cli/validate_commands.rb', line 6

def self.included(base)
  base.class_eval do
    desc 'validate', 'Validate signing configuration on the server'
    long_desc <<~DESC
      Check if your bundle ID, certificate, and provisioning profile exist and
      are valid on the My Signer server.

      WHY VALIDATE?

      The CLI does local keychain/certificate validation, but doesn't check if
      your signing assets exist on the server. This catches "forgot to sync"
      or "profile expired" errors before a build starts.

      OPTIONS:

        --bundle-id / -b   Bundle identifier (e.g., com.example.app)
                           Auto-detected from Xcode project if not provided

        --type / -t        Signing type: development, appstore, adhoc, inhouse

      EXAMPLES:

        # Validate development signing for an app
        mysigner validate --bundle-id com.example.app --type development

        # Validate App Store signing
        mysigner validate -b com.example.app -t appstore

        # Auto-detect bundle ID from current project
        mysigner validate --type development
    DESC
    method_option :bundle_id, type: :string, aliases: '-b', desc: 'Bundle identifier (e.g., com.example.app)'
    method_option :type, type: :string, aliases: '-t', desc: 'Signing type: development, appstore, adhoc, inhouse'
    def validate
      config = load_config
      client = create_client(config)

      bundle_id = options[:bundle_id] || detect_bundle_id_from_project
      signing_type = options[:type]

      unless bundle_id
        error 'Bundle ID is required. Use --bundle-id or run from an Xcode project directory.'
        say ''
        say 'Example: mysigner validate --bundle-id com.example.app --type development', :yellow
        exit 1
      end

      unless signing_type
        error 'Signing type is required. Use --type with one of: development, appstore, adhoc, inhouse'
        say ''
        say "Example: mysigner validate --bundle-id #{bundle_id} --type development", :yellow
        exit 1
      end

      valid_types = %w[development appstore adhoc inhouse]
      unless valid_types.include?(signing_type)
        error "Invalid signing type: #{signing_type}"
        say "Valid types: #{valid_types.join(', ')}", :yellow
        exit 1
      end

      say '🔍 Validating signing configuration...', :cyan
      say ''
      say "  Bundle ID: #{bundle_id}", :white
      say "  Type:      #{signing_type}", :white
      say ''

      begin
        response = client.post(
          "/api/v1/organizations/#{config.current_organization_id}/validate",
          body: {
            bundle_id: bundle_id,
            type: signing_type
          }
        )

        result = response[:data]
        checks = result['checks'] || {}
        valid = result['valid']

        # Display each check
        %w[bundle_id certificate profile].each do |check_name|
          check = checks[check_name]
          next unless check

          if check['status'] == 'pass'
            say "#{check_name.tr('_', ' ').capitalize}: #{check['message']}", :green
          else
            say "#{check_name.tr('_', ' ').capitalize}: #{check['message']}", :red
          end
        end

        say ''

        if valid
          say '✓ All checks passed! Signing configuration is valid.', :green
        else
          say '✗ Validation failed. Some checks did not pass.', :red

          suggestions = result['suggestions'] || []
          if suggestions.any?
            say ''
            say '💡 Suggestions:', :cyan
            suggestions.each do |suggestion|
              say "#{suggestion}", :yellow
            end
          end

          exit 1
        end
      rescue Mysigner::NotFoundError => e
        error "Not found: #{e.message}"
        say ''
        say '💡 Make sure your bundle ID is synced:', :cyan
        say "   → Run 'mysigner sync ios' to sync from Apple Developer Portal", :yellow
        say "   → Run 'mysigner bundleid list' to list registered bundle IDs", :yellow
        exit 1
      rescue Mysigner::ValidationError => e
        error "Validation error: #{e.message}"
        e.details&.each do |field, errors|
          errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
          say "  #{field}: #{errors_text}", :red
        end
        exit 1
      rescue Mysigner::ClientError => e
        error "Validation request failed: #{e.message}"
        say ''
        say '💡 Try these steps:', :cyan
        say '   → Check your network connection', :yellow
        say '   → Verify API token: mysigner status', :yellow
        exit 1
      end
    end

    private

    def detect_bundle_id_from_project
      # Try to find bundle ID from Xcode project in current directory
      pbxproj_files = Dir.glob('**/*.pbxproj')
      return nil if pbxproj_files.empty?

      pbxproj_files.each do |file|
        content = File.read(file)
        match = content.match(/PRODUCT_BUNDLE_IDENTIFIER\s*=\s*"?([^;"]+)"?/)
        return match[1].strip if match
      end

      nil
    rescue StandardError
      nil
    end
  end
end