Class: Appship::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/appship/cache.rb

Constant Summary collapse

DEFAULT_PATH =
File.expand_path("~/.appship/.config")
DEFAULT_BUILD_DIR =
File.expand_path("~/.appship/build")
LEGACY_PATH =
File.expand_path("~/.appship/.env")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = DEFAULT_PATH) ⇒ Cache

Returns a new instance of Cache.



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/appship/cache.rb', line 94

def initialize(path = DEFAULT_PATH)
  @path = File.expand_path(path)
  raise ConfigurationError, "本机缓存文件不存在: #{@path}\n请使用 --env-file 指定路径" unless File.file?(@path)

  @entries = parse(File.read(@path))
  raise ConfigurationError, "缓存文件必须是字典数组: #{@path}" unless @entries.is_a?(Array)
  @entries = @entries.map { |entry| normalize(entry) }
  raise ConfigurationError, "缓存文件中没有有效项目: #{@path}" if @entries.empty?
rescue Psych::SyntaxError => e
  raise ConfigurationError, "缓存文件解析失败: #{e.message}"
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



9
10
11
# File 'lib/appship/cache.rb', line 9

def entries
  @entries
end

#pathObject (readonly)

Returns the value of attribute path.



9
10
11
# File 'lib/appship/cache.rb', line 9

def path
  @path
end

Class Method Details

.collect_interactively!(path, project_dir: Dir.pwd, provider: "pgyer") ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/appship/cache.rb', line 44

def self.collect_interactively!(path, project_dir: Dir.pwd, provider: "pgyer")
  project_name = infer_project_name!(project_dir)
  provider_name = provider.to_s == "fir" ? "fir.im" : "pgyer"
  credential_key = credential_key_for(provider)
  puts ""
  puts "首次使用 appship #{provider_name},请填写本机项目缓存。"
  puts "缓存文件: #{path}"
  puts ""
  puts "✅ 已从 #{File.basename(project_name)}.xcodeproj 识别 project_name: #{project_name}"
  app_name = prompt_required("app_name")
  data = {
    "project_name" => project_name,
    "app_name" => app_name,
    credential_key => prompt_required(credential_key)
  }
  password_key = provider.to_s == "fir" ? "fir_password" : "pgyer_password"
  data[password_key] = prompt_optional(password_key)
  File.write(path, JSON.pretty_generate([data]) + "\n")
  File.chmod(0o600, path)
  puts ""
  puts "✅ 本机缓存已保存: #{path}"
  new(path)
end

.create_file!(path) ⇒ Object



36
37
38
39
40
41
42
# File 'lib/appship/cache.rb', line 36

def self.create_file!(path)
  FileUtils.mkdir_p(File.dirname(path))
  File.chmod(0o700, File.dirname(path)) if File.directory?(File.dirname(path))
  File.write(path, "[]\n") unless File.exist?(path)
  File.chmod(0o600, path)
  path
end

.credential_key_for(provider) ⇒ Object



140
141
142
# File 'lib/appship/cache.rb', line 140

def self.credential_key_for(provider)
  provider.to_s == "fir" ? "fir_api_key" : "pgyer_api_key"
end

.infer_project_name!(project_dir) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/appship/cache.rb', line 68

def self.infer_project_name!(project_dir)
  xcodeprojects = Dir[File.join(File.expand_path(project_dir), "*.xcodeproj")]
  if xcodeprojects.empty?
    raise ConfigurationError, "当前目录找不到 .xcodeproj,无法自动设置 project_name: #{File.expand_path(project_dir)}"
  end
  if xcodeprojects.length > 1
    names = xcodeprojects.map { |path| File.basename(path, ".xcodeproj") }.join(", ")
    raise ConfigurationError, "当前目录找到多个 .xcodeproj,无法确定 project_name: #{names}"
  end

  File.basename(xcodeprojects.first, ".xcodeproj")
end

.load(path = DEFAULT_PATH, project_dir: Dir.pwd, interactive: true, provider: "pgyer") ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/appship/cache.rb', line 11

def self.load(path = DEFAULT_PATH, project_dir: Dir.pwd, interactive: true, provider: "pgyer")
  expanded = File.expand_path(path)
  # 兼容早期版本的 ~/.appship/.env;发现旧缓存时迁移到新的 .config。
  if expanded == DEFAULT_PATH && !File.file?(expanded) && File.file?(LEGACY_PATH)
    FileUtils.mkdir_p(File.dirname(expanded))
    FileUtils.cp(LEGACY_PATH, expanded)
    File.chmod(0o600, expanded)
  end

  unless File.file?(expanded)
    create_file!(expanded)
    return collect_interactively!(expanded, project_dir: project_dir, provider: provider) if interactive && $stdin.tty?

    raise ConfigurationError, missing_credentials_message(expanded, provider, created: true)
  end

  if File.read(expanded).strip.empty?
    return collect_interactively!(expanded, project_dir: project_dir, provider: provider) if interactive && $stdin.tty?

    raise ConfigurationError, missing_credentials_message(expanded, provider, empty: true)
  end

  new(expanded)
end

.missing_credentials_message(path, provider, created: false, empty: false) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/appship/cache.rb', line 144

def self.missing_credentials_message(path, provider, created: false, empty: false)
  prefix = if created
             "已创建本机缓存文件: #{path}"
           elsif empty
             "本机缓存文件为空: #{path}"
           else
             "本机缓存文件无效: #{path}"
           end
  key = credential_key_for(provider)
  password_key = key == "pgyer_api_key" ? "pgyer_password" : "fir_password"
  suffix = "#{password_key} 可为空"
  "#{prefix}\n请填写 project_name、app_name、#{key}#{suffix}"
end

.prompt_optional(name) ⇒ Object



89
90
91
92
# File 'lib/appship/cache.rb', line 89

def self.prompt_optional(name)
  print "请输入 #{name}(可为空,直接回车表示不设置密码): "
  $stdin.gets.to_s.strip
end

.prompt_required(name) ⇒ Object

Raises:



81
82
83
84
85
86
87
# File 'lib/appship/cache.rb', line 81

def self.prompt_required(name)
  print "请输入 #{name}: "
  value = $stdin.gets&.strip
  raise ConfigurationError, "#{name} 不能为空" if value.to_s.empty?

  value
end

Instance Method Details

#resolve(project_name: nil, project_dir: Dir.pwd, provider: "pgyer", interactive: true, credential_overrides: {}) ⇒ Object

Raises:



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
# File 'lib/appship/cache.rb', line 106

def resolve(project_name: nil, project_dir: Dir.pwd, provider: "pgyer", interactive: true, credential_overrides: {})
  candidate_name = project_name.to_s.strip
  if candidate_name.empty? && @entries.length > 1
    directory_name = File.basename(File.expand_path(project_dir))
    candidate_name = directory_name if @entries.any? { |entry| entry["project_name"] == directory_name }
  end

  matches = if candidate_name.empty?
              @entries
            else
              @entries.select { |entry| entry["project_name"] == candidate_name }
  end
  if matches.length == 1
    update_profile!(matches.first, credential_overrides) unless credential_overrides.empty?
    ensure_provider_credential!(matches.first, provider, interactive: interactive)
    return matches.first
  end
  if matches.empty?
    available = @entries.filter_map { |entry| entry["project_name"] }.join(", ")
    raise ConfigurationError, "找不到项目缓存 #{candidate_name.inspect},可用项目: #{available}"
  end

  names = matches.filter_map { |entry| entry["project_name"] }.join(", ")
  raise ConfigurationError, "本机缓存包含多个项目,请使用 --project-name 指定: #{names}"
end

#update_profile!(entry, values) ⇒ Object



132
133
134
135
136
# File 'lib/appship/cache.rb', line 132

def update_profile!(entry, values)
  values.each { |key, value| entry[key.to_s] = value unless value.nil? }
  persist!
  entry
end