fastlane-plugin-wpmreleasetoolkit 14.6.0 → 14.7.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 +4 -4
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/openai_ask_action.rb +116 -24
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/update_apps_cdn_build_metadata.rb +162 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_build_to_apps_cdn.rb +4 -13
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/apps_cdn_helper.rb +55 -0
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/configure_helper.rb +2 -2
- data/lib/fastlane/plugin/wpmreleasetoolkit/helper/ios/ios_l10n_linter_helper.rb +1 -1
- data/lib/fastlane/plugin/wpmreleasetoolkit/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3a14d01dbfc81f832b5d1b4c009e16c2b78ff148b3c25c592a1bc32c5e8d442
|
|
4
|
+
data.tar.gz: 8fbb44705e3d5799cb21857647c83f8321b9494548dc0342eb093781f707538d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73ee821d6f4d161a1479bedd639f7427c6022cd17551dce93d8d87cabf47d95513a9595f8aeb02523b60ada7d52141fb82bbbbbbb9538b0b9aaa2dd9d7e681ef
|
|
7
|
+
data.tar.gz: 4dee3516eb30a8bbbc97ef67568f97d541452a44dd56a16080606bbdbe790fe816890108ca97a843ee8af511fb307d39dd19f7999cb5429d2d0ee4a630c44afd
|
|
@@ -8,8 +8,10 @@ module Fastlane
|
|
|
8
8
|
module Actions
|
|
9
9
|
class OpenaiAskAction < Action
|
|
10
10
|
OPENAI_API_ENDPOINT = URI('https://api.openai.com/v1/chat/completions').freeze
|
|
11
|
+
# Preserve the previous `max_tokens` ceiling while using the current API field.
|
|
12
|
+
DEFAULT_MAX_COMPLETION_TOKENS = 2048
|
|
11
13
|
DEFAULT_MAX_TOOL_ITERATIONS = 5
|
|
12
|
-
DEFAULT_MODEL = 'gpt-
|
|
14
|
+
DEFAULT_MODEL = 'gpt-4.1'
|
|
13
15
|
|
|
14
16
|
PREDEFINED_PROMPTS = {
|
|
15
17
|
release_notes: <<~PROMPT
|
|
@@ -39,12 +41,16 @@ module Fastlane
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
# Backwards-compatible single-shot path when no tools are provided.
|
|
42
|
-
if tools.nil?
|
|
44
|
+
if tools.nil?
|
|
43
45
|
body = request_body(prompt: prompt, question: question, model: model)
|
|
44
46
|
response = Net::HTTP.post(OPENAI_API_ENDPOINT, body, headers)
|
|
45
47
|
return parse_text_response(response)
|
|
46
48
|
end
|
|
47
49
|
|
|
50
|
+
validate_tools_array!(tools)
|
|
51
|
+
validate_max_tool_iterations!(max_tool_iterations)
|
|
52
|
+
validate_tools!(tools)
|
|
53
|
+
|
|
48
54
|
run_with_tools(
|
|
49
55
|
prompt: prompt,
|
|
50
56
|
question: question,
|
|
@@ -62,7 +68,9 @@ module Fastlane
|
|
|
62
68
|
format_message(role: 'user', text: question),
|
|
63
69
|
].compact
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
tool_iterations = 0
|
|
72
|
+
|
|
73
|
+
loop do
|
|
66
74
|
body = request_body_with_messages(messages: messages, tools: tools, model: model)
|
|
67
75
|
response = Net::HTTP.post(OPENAI_API_ENDPOINT, body, headers)
|
|
68
76
|
assistant_message = parse_assistant_message(response)
|
|
@@ -71,26 +79,31 @@ module Fastlane
|
|
|
71
79
|
# No tool calls — model produced a final answer.
|
|
72
80
|
return assistant_message['content'] if tool_calls.nil? || tool_calls.empty?
|
|
73
81
|
|
|
82
|
+
if tool_iterations >= max_tool_iterations
|
|
83
|
+
UI.user_error!(
|
|
84
|
+
"OpenAI tool-use loop did not produce a final answer after #{max_tool_iterations} tool iterations. " \
|
|
85
|
+
'Refusing to execute additional tool calls. Increase `max_tool_iterations` or check that your prompt instructs the model to stop calling tools.'
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
74
89
|
# Append the assistant's tool-call message verbatim, then run each handler
|
|
75
90
|
# and append the corresponding `role: tool` results.
|
|
76
91
|
messages << assistant_message
|
|
77
92
|
tool_calls.each do |tool_call|
|
|
78
93
|
messages << execute_tool_call(tool_call, tool_handlers)
|
|
79
94
|
end
|
|
80
|
-
end
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'Increase `max_tool_iterations` or check that your prompt instructs the model to stop calling tools.'
|
|
85
|
-
)
|
|
96
|
+
tool_iterations += 1
|
|
97
|
+
end
|
|
86
98
|
end
|
|
87
99
|
|
|
88
100
|
def self.request_body(prompt:, question:, model: DEFAULT_MODEL)
|
|
89
101
|
{
|
|
90
102
|
model: model,
|
|
103
|
+
store: false,
|
|
91
104
|
response_format: { type: 'text' },
|
|
92
105
|
temperature: 1,
|
|
93
|
-
|
|
106
|
+
max_completion_tokens: DEFAULT_MAX_COMPLETION_TOKENS,
|
|
94
107
|
top_p: 1,
|
|
95
108
|
messages: [
|
|
96
109
|
format_message(role: 'system', text: prompt),
|
|
@@ -102,9 +115,10 @@ module Fastlane
|
|
|
102
115
|
def self.request_body_with_messages(messages:, tools:, model: DEFAULT_MODEL)
|
|
103
116
|
{
|
|
104
117
|
model: model,
|
|
118
|
+
store: false,
|
|
105
119
|
response_format: { type: 'text' },
|
|
106
120
|
temperature: 1,
|
|
107
|
-
|
|
121
|
+
max_completion_tokens: DEFAULT_MAX_COMPLETION_TOKENS,
|
|
108
122
|
top_p: 1,
|
|
109
123
|
messages: messages,
|
|
110
124
|
tools: tools
|
|
@@ -140,8 +154,49 @@ module Fastlane
|
|
|
140
154
|
end
|
|
141
155
|
end
|
|
142
156
|
|
|
157
|
+
def self.validate_max_tool_iterations!(max_tool_iterations)
|
|
158
|
+
UI.user_error!("Parameter `max_tool_iterations` must be an Integer (got #{max_tool_iterations.class})") unless max_tool_iterations.is_a?(Integer)
|
|
159
|
+
UI.user_error!("Parameter `max_tool_iterations` must be >= 1 (got #{max_tool_iterations})") if max_tool_iterations < 1
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def self.validate_tools_array!(tools)
|
|
163
|
+
UI.user_error!('Parameter `tools` must be a non-empty Array when provided') unless tools.is_a?(Array) && !tools.empty?
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def self.validate_tools!(tools)
|
|
167
|
+
invalid_tools = tools.each_with_index.filter_map do |tool, index|
|
|
168
|
+
type = tool_type(tool)
|
|
169
|
+
next "tools[#{index}] type #{type.nil? ? '<missing>' : type.inspect}" unless type == 'function'
|
|
170
|
+
|
|
171
|
+
function = tool[:function] || tool['function']
|
|
172
|
+
name = function[:name] || function['name'] if function.is_a?(Hash)
|
|
173
|
+
next if valid_tool_name?(name)
|
|
174
|
+
|
|
175
|
+
"tools[#{index}] missing function.name"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
return if invalid_tools.empty?
|
|
179
|
+
|
|
180
|
+
UI.user_error!(
|
|
181
|
+
'Parameter `tools` only supports OpenAI function tools with a non-empty `function.name`. ' \
|
|
182
|
+
"Invalid tool definitions: #{invalid_tools.join(', ')}"
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def self.tool_type(tool)
|
|
187
|
+
return nil unless tool.is_a?(Hash)
|
|
188
|
+
|
|
189
|
+
(tool[:type] || tool['type'])&.to_s
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def self.valid_tool_name?(name)
|
|
193
|
+
(name.is_a?(String) || name.is_a?(Symbol)) && !name.to_s.empty?
|
|
194
|
+
end
|
|
195
|
+
|
|
143
196
|
def self.execute_tool_call(tool_call, tool_handlers)
|
|
144
|
-
|
|
197
|
+
return unsupported_tool_call_result(tool_call) unless function_tool_call?(tool_call)
|
|
198
|
+
|
|
199
|
+
name = tool_call.dig('function', 'name').to_s
|
|
145
200
|
raw_args = tool_call.dig('function', 'arguments') || '{}'
|
|
146
201
|
|
|
147
202
|
result =
|
|
@@ -151,8 +206,8 @@ module Fastlane
|
|
|
151
206
|
rescue JSON::ParserError
|
|
152
207
|
# Short-circuit: the handler never sees malformed args. Tell the model the
|
|
153
208
|
# tool-call payload was invalid so it can retry with valid JSON, and log the
|
|
154
|
-
#
|
|
155
|
-
UI.error("Invalid JSON arguments for tool '#{name}'. Raw payload
|
|
209
|
+
# local failure without recording raw arguments that might contain secrets.
|
|
210
|
+
UI.error("Invalid JSON arguments for tool '#{name}' in tool call '#{tool_call['id']}'. Raw payload omitted because it may contain secrets.")
|
|
156
211
|
{ error: "Invalid JSON arguments for tool '#{name}' — payload could not be parsed. Retry with valid JSON." }
|
|
157
212
|
end
|
|
158
213
|
|
|
@@ -163,6 +218,40 @@ module Fastlane
|
|
|
163
218
|
}
|
|
164
219
|
end
|
|
165
220
|
|
|
221
|
+
def self.function_tool_call?(tool_call)
|
|
222
|
+
return false unless tool_call['type'] == 'function'
|
|
223
|
+
return false unless tool_call['function'].is_a?(Hash)
|
|
224
|
+
|
|
225
|
+
name = tool_call.dig('function', 'name')
|
|
226
|
+
valid_tool_name?(name)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def self.unsupported_tool_call_result(tool_call)
|
|
230
|
+
type = tool_call['type'] || '<missing>'
|
|
231
|
+
error =
|
|
232
|
+
if type == 'function'
|
|
233
|
+
'Function tool call is missing a non-empty function.name.'
|
|
234
|
+
else
|
|
235
|
+
"Unsupported tool call type '#{type}'. Only function tool calls are supported."
|
|
236
|
+
end
|
|
237
|
+
log_message =
|
|
238
|
+
if type == 'function'
|
|
239
|
+
"Invalid OpenAI function tool call '#{tool_call['id']}': missing a non-empty function.name."
|
|
240
|
+
else
|
|
241
|
+
"Unsupported OpenAI tool call type '#{type}' in tool call '#{tool_call['id']}'. Only function tool calls are supported."
|
|
242
|
+
end
|
|
243
|
+
UI.error(log_message)
|
|
244
|
+
|
|
245
|
+
{
|
|
246
|
+
role: 'tool',
|
|
247
|
+
tool_call_id: tool_call['id'],
|
|
248
|
+
content: serialize_tool_result(
|
|
249
|
+
name: type,
|
|
250
|
+
result: { error: error }
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
end
|
|
254
|
+
|
|
166
255
|
# Serializes a tool result to a JSON string. Handlers are contracted to return
|
|
167
256
|
# JSON-serializable values, but a buggy handler might return something like a
|
|
168
257
|
# `Pathname`, `Proc`, or a custom object whose `to_json` raises. Failing the
|
|
@@ -175,7 +264,7 @@ module Fastlane
|
|
|
175
264
|
def self.serialize_tool_result(name:, result:)
|
|
176
265
|
JSON.generate(result)
|
|
177
266
|
rescue StandardError => e
|
|
178
|
-
UI.error("Could not serialize tool result for '#{name}': #{e.class}
|
|
267
|
+
UI.error("Could not serialize tool result for '#{name}': #{e.class}. Result class: #{result.class}. Error message omitted because it may contain secrets.")
|
|
179
268
|
JSON.generate({ error: "Tool result for '#{name}' could not be serialized to JSON. Returned class: #{result.class}." })
|
|
180
269
|
end
|
|
181
270
|
|
|
@@ -185,9 +274,9 @@ module Fastlane
|
|
|
185
274
|
#
|
|
186
275
|
# - Missing or non-callable handler: structured `{ error: ... }` so the model can recover.
|
|
187
276
|
# - Handler raised: structured `{ error:, exception: }` carrying only the exception class
|
|
188
|
-
# so the model can see the failure category and adjust. The
|
|
189
|
-
# are
|
|
190
|
-
#
|
|
277
|
+
# so the model can see the failure category and adjust. The exception message and
|
|
278
|
+
# backtrace are intentionally omitted from local logs and from the model response
|
|
279
|
+
# because tool results and CI logs can expose release secrets
|
|
191
280
|
# (tokens, file contents, internal API responses). The loop keeps going rather than
|
|
192
281
|
# aborting the lane mid-conversation — the model is the better judge of whether the
|
|
193
282
|
# failure is recoverable than a global `rescue` here.
|
|
@@ -198,7 +287,7 @@ module Fastlane
|
|
|
198
287
|
begin
|
|
199
288
|
handler.call(args)
|
|
200
289
|
rescue StandardError => e
|
|
201
|
-
UI.error("Handler for tool '#{name}' raised #{e.class}
|
|
290
|
+
UI.error("Handler for tool '#{name}' raised #{e.class}. Error message and backtrace omitted because they may contain secrets.")
|
|
202
291
|
{ error: "Handler for tool '#{name}' raised an exception", exception: e.class.name }
|
|
203
292
|
end
|
|
204
293
|
end
|
|
@@ -228,7 +317,8 @@ module Fastlane
|
|
|
228
317
|
When `tools` and `tool_handlers` are provided, the action runs a tool-use (function-calling) loop:
|
|
229
318
|
on each turn, if the model calls one or more tools, the corresponding handler is invoked locally
|
|
230
319
|
and its return value is sent back to the model as a `role: tool` message. The loop ends when the
|
|
231
|
-
model returns a plain text response, or
|
|
320
|
+
model returns a plain text response, or before executing tool calls beyond `max_tool_iterations`.
|
|
321
|
+
The model gets one final API turn to answer after the last permitted local tool execution round.
|
|
232
322
|
DETAILS
|
|
233
323
|
end
|
|
234
324
|
|
|
@@ -306,19 +396,20 @@ module Fastlane
|
|
|
306
396
|
sensitive: true,
|
|
307
397
|
type: String),
|
|
308
398
|
FastlaneCore::ConfigItem.new(key: :model,
|
|
309
|
-
description: 'The OpenAI model to send the request to (e.g. `gpt-
|
|
399
|
+
description: 'The OpenAI model to send the request to (e.g. `gpt-4.1`, `gpt-4.1-mini`, `gpt-4o`). ' \
|
|
310
400
|
"Defaults to `#{DEFAULT_MODEL}`",
|
|
311
401
|
optional: true,
|
|
312
402
|
default_value: DEFAULT_MODEL,
|
|
313
403
|
type: String),
|
|
314
404
|
FastlaneCore::ConfigItem.new(key: :tools,
|
|
315
|
-
description: 'Optional array of
|
|
405
|
+
description: 'Optional array of OpenAI function tool definitions. Each definition must have a non-empty `function.name`. ' \
|
|
316
406
|
'When provided, the action runs a tool-use loop',
|
|
317
407
|
optional: true,
|
|
318
408
|
default_value: nil,
|
|
319
409
|
type: Array,
|
|
320
410
|
verify_block: proc do |value|
|
|
321
|
-
|
|
411
|
+
validate_tools_array!(value)
|
|
412
|
+
validate_tools!(value)
|
|
322
413
|
end),
|
|
323
414
|
FastlaneCore::ConfigItem.new(key: :tool_handlers,
|
|
324
415
|
description: 'Hash of tool name to a callable (e.g. a Proc) invoked when the model calls that tool. ' \
|
|
@@ -332,13 +423,14 @@ module Fastlane
|
|
|
332
423
|
UI.user_error!("Parameter `tool_handlers` values must respond to :call. Non-callable handlers: #{non_callable.keys}") if non_callable.any?
|
|
333
424
|
end),
|
|
334
425
|
FastlaneCore::ConfigItem.new(key: :max_tool_iterations,
|
|
335
|
-
description: 'Maximum number of tool
|
|
426
|
+
description: 'Maximum number of local tool execution rounds before the action fails. ' \
|
|
427
|
+
'The model can receive one final API turn to answer after the last permitted tool result. ' \
|
|
336
428
|
'Only used when `tools` are provided',
|
|
337
429
|
optional: true,
|
|
338
430
|
default_value: DEFAULT_MAX_TOOL_ITERATIONS,
|
|
339
431
|
type: Integer,
|
|
340
432
|
verify_block: proc do |value|
|
|
341
|
-
|
|
433
|
+
validate_max_tool_iterations!(value)
|
|
342
434
|
end),
|
|
343
435
|
]
|
|
344
436
|
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fastlane/action'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'json'
|
|
6
|
+
require_relative '../../helper/apps_cdn_helper'
|
|
7
|
+
|
|
8
|
+
module Fastlane
|
|
9
|
+
module Actions
|
|
10
|
+
class UpdateAppsCdnBuildMetadataAction < Action
|
|
11
|
+
def self.run(params)
|
|
12
|
+
post_ids = params[:post_ids]
|
|
13
|
+
UI.message("Updating Apps CDN build metadata for #{post_ids.size} post(s): #{post_ids.join(', ')}...")
|
|
14
|
+
|
|
15
|
+
# Build the JSON body for the dedicated Apps CDN builds endpoint, which accepts
|
|
16
|
+
# the same string-based format as the upload flow (e.g. `visibility: 'Internal'`)
|
|
17
|
+
body = {}
|
|
18
|
+
body['post_status'] = params[:post_status] if params[:post_status]
|
|
19
|
+
body['visibility'] = params[:visibility].to_s.capitalize if params[:visibility]
|
|
20
|
+
|
|
21
|
+
UI.user_error!('No metadata to update. Provide at least one of: visibility, post_status') if body.empty?
|
|
22
|
+
|
|
23
|
+
results = post_ids.map do |post_id|
|
|
24
|
+
update_single_post(site_id: params[:site_id], api_token: params[:api_token], post_id: post_id, body: body)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
UI.success("Successfully updated Apps CDN build metadata for #{results.size} post(s)")
|
|
28
|
+
results
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Update a single CDN build post with the given body via the dedicated
|
|
32
|
+
# `/wpcom/v2/sites/{site_id}/a8c-cdn/builds/{post_id}` endpoint.
|
|
33
|
+
#
|
|
34
|
+
# @param site_id [String] the WordPress.com site ID
|
|
35
|
+
# @param api_token [String] the WordPress.com API bearer token
|
|
36
|
+
# @param post_id [Integer] the ID of the build post to update
|
|
37
|
+
# @param body [Hash] the JSON body to send in the POST request
|
|
38
|
+
# @return [Integer] the ID of the updated post
|
|
39
|
+
# @raise [FastlaneCore::Interface::FastlaneError] if the API returns a non-success response
|
|
40
|
+
def self.update_single_post(site_id:, api_token:, post_id:, body:)
|
|
41
|
+
uri = Helper::AppsCdnHelper.wpcom_v2_url(site_id: site_id, path: "a8c-cdn/builds/#{post_id}")
|
|
42
|
+
|
|
43
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
44
|
+
request.body = JSON.generate(body)
|
|
45
|
+
request['Content-Type'] = 'application/json'
|
|
46
|
+
request['Accept'] = 'application/json'
|
|
47
|
+
request['Authorization'] = "Bearer #{api_token}"
|
|
48
|
+
|
|
49
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 10, read_timeout: 30) do |http|
|
|
50
|
+
http.request(request)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
case response
|
|
54
|
+
when Net::HTTPSuccess
|
|
55
|
+
result = JSON.parse(response.body)
|
|
56
|
+
updated_id = result['id']
|
|
57
|
+
|
|
58
|
+
UI.message(" Updated post #{updated_id}")
|
|
59
|
+
|
|
60
|
+
updated_id
|
|
61
|
+
else
|
|
62
|
+
UI.error("Failed to update Apps CDN build metadata for post #{post_id}: #{response.code} #{response.message}")
|
|
63
|
+
UI.error(response.body)
|
|
64
|
+
UI.user_error!("Update of Apps CDN build metadata failed for post #{post_id}")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.description
|
|
69
|
+
'Updates metadata of one or more existing builds on the Apps CDN'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.authors
|
|
73
|
+
['Automattic']
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.return_value
|
|
77
|
+
'Returns an Array of post IDs (Integer) that were successfully updated. On error, raises a FastlaneError.'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.details
|
|
81
|
+
<<~DETAILS
|
|
82
|
+
Updates metadata (such as post status or visibility) for one or more existing build posts on a WordPress blog
|
|
83
|
+
that has the Apps CDN plugin enabled, using the dedicated `/wpcom/v2/sites/{site_id}/a8c-cdn/builds/{post_id}`
|
|
84
|
+
endpoint. Standard WP REST API writes are blocked for builds, so this endpoint is the only way to update them.
|
|
85
|
+
See PCYsg-15tP-p2 internal a8c documentation for details about the Apps CDN plugin.
|
|
86
|
+
DETAILS
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.available_options
|
|
90
|
+
[
|
|
91
|
+
FastlaneCore::ConfigItem.new(
|
|
92
|
+
key: :site_id,
|
|
93
|
+
env_name: 'APPS_CDN_SITE_ID',
|
|
94
|
+
description: 'The WordPress.com CDN site ID where the build was uploaded',
|
|
95
|
+
optional: false,
|
|
96
|
+
type: String,
|
|
97
|
+
verify_block: proc do |value|
|
|
98
|
+
UI.user_error!('Site ID cannot be empty') if value.to_s.empty?
|
|
99
|
+
end
|
|
100
|
+
),
|
|
101
|
+
FastlaneCore::ConfigItem.new(
|
|
102
|
+
key: :post_ids,
|
|
103
|
+
description: 'The IDs of the build posts to update',
|
|
104
|
+
optional: false,
|
|
105
|
+
type: Array,
|
|
106
|
+
verify_block: proc do |value|
|
|
107
|
+
UI.user_error!('Post IDs must be a non-empty array') unless value.is_a?(Array) && !value.empty?
|
|
108
|
+
value.each do |id|
|
|
109
|
+
UI.user_error!("Each post ID must be a positive integer, got: #{id.inspect}") unless id.is_a?(Integer) && id.positive?
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
),
|
|
113
|
+
FastlaneCore::ConfigItem.new(
|
|
114
|
+
key: :api_token,
|
|
115
|
+
env_name: 'WPCOM_API_TOKEN',
|
|
116
|
+
description: 'The WordPress.com API token for authentication',
|
|
117
|
+
optional: false,
|
|
118
|
+
type: String,
|
|
119
|
+
verify_block: proc do |value|
|
|
120
|
+
UI.user_error!('API token cannot be empty') if value.to_s.empty?
|
|
121
|
+
end
|
|
122
|
+
),
|
|
123
|
+
FastlaneCore::ConfigItem.new(
|
|
124
|
+
key: :visibility,
|
|
125
|
+
description: 'The new visibility for the build (:internal or :external)',
|
|
126
|
+
optional: true,
|
|
127
|
+
type: Symbol,
|
|
128
|
+
verify_block: Helper::AppsCdnHelper.verify_visibility_param
|
|
129
|
+
),
|
|
130
|
+
FastlaneCore::ConfigItem.new(
|
|
131
|
+
key: :post_status,
|
|
132
|
+
description: "The new post status ('publish' or 'draft')",
|
|
133
|
+
optional: true,
|
|
134
|
+
type: String,
|
|
135
|
+
verify_block: Helper::AppsCdnHelper.verify_post_status_param
|
|
136
|
+
),
|
|
137
|
+
]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def self.is_supported?(platform)
|
|
141
|
+
true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.example_code
|
|
145
|
+
[
|
|
146
|
+
'update_apps_cdn_build_metadata(
|
|
147
|
+
site_id: "12345678",
|
|
148
|
+
api_token: ENV["WPCOM_API_TOKEN"],
|
|
149
|
+
post_ids: [98765],
|
|
150
|
+
post_status: "publish"
|
|
151
|
+
)',
|
|
152
|
+
'update_apps_cdn_build_metadata(
|
|
153
|
+
site_id: "12345678",
|
|
154
|
+
api_token: ENV["WPCOM_API_TOKEN"],
|
|
155
|
+
post_ids: [12345, 67890, 11111],
|
|
156
|
+
visibility: :external
|
|
157
|
+
)',
|
|
158
|
+
]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -4,6 +4,7 @@ require 'fastlane/action'
|
|
|
4
4
|
require 'net/http'
|
|
5
5
|
require 'uri'
|
|
6
6
|
require 'json'
|
|
7
|
+
require_relative '../../helper/apps_cdn_helper'
|
|
7
8
|
|
|
8
9
|
module Fastlane
|
|
9
10
|
module Actions
|
|
@@ -17,8 +18,6 @@ module Fastlane
|
|
|
17
18
|
class UploadBuildToAppsCdnAction < Action
|
|
18
19
|
# See https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-resource-type.php
|
|
19
20
|
RESOURCE_TYPE = 'Build'
|
|
20
|
-
# These are from the WordPress.com API, not the Apps CDN plugin
|
|
21
|
-
VALID_POST_STATUS = %w[publish draft].freeze
|
|
22
21
|
# See https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-build-type.php
|
|
23
22
|
VALID_BUILD_TYPES = %w[
|
|
24
23
|
Alpha
|
|
@@ -48,17 +47,13 @@ module Fastlane
|
|
|
48
47
|
'Full Install',
|
|
49
48
|
'Update',
|
|
50
49
|
].freeze
|
|
51
|
-
# See https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-visibility.php
|
|
52
|
-
VALID_VISIBILITIES = %i[internal external].freeze
|
|
53
|
-
|
|
54
50
|
def self.run(params)
|
|
55
51
|
UI.message('Uploading build to Apps CDN...')
|
|
56
52
|
|
|
57
53
|
file_path = params[:file_path]
|
|
58
54
|
UI.user_error!("File not found at path '#{file_path}'") unless File.exist?(file_path)
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
uri = URI.parse(api_endpoint)
|
|
56
|
+
uri = Helper::AppsCdnHelper.rest_v1_1_url(site_id: params[:site_id], path: 'media/new')
|
|
62
57
|
|
|
63
58
|
# Create the request body and headers
|
|
64
59
|
parameters = {
|
|
@@ -261,9 +256,7 @@ module Fastlane
|
|
|
261
256
|
description: 'The visibility of the build (:internal or :external)',
|
|
262
257
|
optional: false,
|
|
263
258
|
type: Symbol,
|
|
264
|
-
verify_block:
|
|
265
|
-
UI.user_error!("Visibility must be one of: #{VALID_VISIBILITIES.map { "`:#{_1}`" }.join(', ')}") unless VALID_VISIBILITIES.include?(value.to_s.downcase.to_sym)
|
|
266
|
-
end
|
|
259
|
+
verify_block: Helper::AppsCdnHelper.verify_visibility_param
|
|
267
260
|
),
|
|
268
261
|
FastlaneCore::ConfigItem.new(
|
|
269
262
|
key: :post_status,
|
|
@@ -271,9 +264,7 @@ module Fastlane
|
|
|
271
264
|
optional: true,
|
|
272
265
|
default_value: 'publish',
|
|
273
266
|
type: String,
|
|
274
|
-
verify_block:
|
|
275
|
-
UI.user_error!("Post status must be one of: #{VALID_POST_STATUS.join(', ')}") unless VALID_POST_STATUS.include?(value)
|
|
276
|
-
end
|
|
267
|
+
verify_block: Helper::AppsCdnHelper.verify_post_status_param
|
|
277
268
|
),
|
|
278
269
|
FastlaneCore::ConfigItem.new(
|
|
279
270
|
key: :version,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Fastlane
|
|
6
|
+
module Helper
|
|
7
|
+
module AppsCdnHelper
|
|
8
|
+
API_BASE_URL = 'https://public-api.wordpress.com'
|
|
9
|
+
|
|
10
|
+
# See https://github.a8c.com/Automattic/wpcom/blob/trunk/wp-content/lib/a8c/cdn/src/enums/enum-visibility.php
|
|
11
|
+
VALID_VISIBILITIES = %i[internal external].freeze
|
|
12
|
+
|
|
13
|
+
# These are from the WordPress.com API, not the Apps CDN plugin
|
|
14
|
+
VALID_POST_STATUS = %w[publish draft].freeze
|
|
15
|
+
|
|
16
|
+
# Builds a WordPress.com REST API v1.1 URI scoped to a site.
|
|
17
|
+
#
|
|
18
|
+
# @param site_id [String] the WordPress.com site ID
|
|
19
|
+
# @param path [String] the API path relative to the site (e.g. 'media/new')
|
|
20
|
+
# @return [URI] the parsed full API URI
|
|
21
|
+
def self.rest_v1_1_url(site_id:, path:)
|
|
22
|
+
URI.parse("#{API_BASE_URL}/rest/v1.1/sites/#{site_id}/#{path}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Builds a WordPress.com REST API wpcom/v2 URI scoped to a site.
|
|
26
|
+
#
|
|
27
|
+
# @param site_id [String] the WordPress.com site ID
|
|
28
|
+
# @param path [String] the API path relative to the site (e.g. 'a8c-cdn/builds/123')
|
|
29
|
+
# @return [URI] the parsed full API URI
|
|
30
|
+
def self.wpcom_v2_url(site_id:, path:)
|
|
31
|
+
URI.parse("#{API_BASE_URL}/wpcom/v2/sites/#{site_id}/#{path}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns a proc that validates a visibility parameter value against {VALID_VISIBILITIES}.
|
|
35
|
+
# Intended for use as a `verify_block` in Fastlane ConfigItem definitions.
|
|
36
|
+
#
|
|
37
|
+
# @return [Proc] a proc that raises FastlaneError if the value is invalid
|
|
38
|
+
def self.verify_visibility_param
|
|
39
|
+
proc do |value|
|
|
40
|
+
UI.user_error!("Visibility must be one of: #{VALID_VISIBILITIES.map { "`:#{_1}`" }.join(', ')}") unless VALID_VISIBILITIES.include?(value.to_s.downcase.to_sym)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns a proc that validates a post status parameter value against {VALID_POST_STATUS}.
|
|
45
|
+
# Intended for use as a `verify_block` in Fastlane ConfigItem definitions.
|
|
46
|
+
#
|
|
47
|
+
# @return [Proc] a proc that raises FastlaneError if the value is invalid
|
|
48
|
+
def self.verify_post_status_param
|
|
49
|
+
proc do |value|
|
|
50
|
+
UI.user_error!("Post status must be one of: #{VALID_POST_STATUS.join(', ')}") unless VALID_POST_STATUS.include?(value)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -151,7 +151,7 @@ module Fastlane
|
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
### A helper function to extract the distance from the provided string.
|
|
154
|
-
### (ie – this function will
|
|
154
|
+
### (ie – this function will receive "behind 2" or "ahead 6" and return 2 or 6, respectively.
|
|
155
155
|
def self.parse_distance(match)
|
|
156
156
|
distance = match.to_s.scan(/\d+/).first
|
|
157
157
|
|
|
@@ -265,7 +265,7 @@ module Fastlane
|
|
|
265
265
|
end
|
|
266
266
|
|
|
267
267
|
## Updates the project encryption key defined in ~/.mobile-secrets/keys.json
|
|
268
|
-
## The updated file is
|
|
268
|
+
## The updated file is committed and pushed to the repo
|
|
269
269
|
def self.update_project_encryption_key
|
|
270
270
|
# Update keys.json with the new key
|
|
271
271
|
keys_json = mobile_secrets_keys_json
|
|
@@ -14,7 +14,7 @@ module Fastlane
|
|
|
14
14
|
attr_reader :install_path, :version
|
|
15
15
|
|
|
16
16
|
# @param [String] install_path The path to install SwiftGen to. Usually something like "$PROJECT_DIR/vendor/swiftgen/#{SWIFTGEN_VERSION}".
|
|
17
|
-
# It's recommended to provide an absolute path here rather than a relative one, to ensure it's not
|
|
17
|
+
# It's recommended to provide an absolute path here rather than a relative one, to ensure it's not dependent on where the action is run from.
|
|
18
18
|
# @param [String] version The version of SwiftGen to use. This will be used both:
|
|
19
19
|
# - to check if the current version located in `install_path`, if it already exists, is the expected one
|
|
20
20
|
# - to know which version to download if there is not one installed in `install_path` yet
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-wpmreleasetoolkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 14.
|
|
4
|
+
version: 14.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Automattic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: buildkit
|
|
@@ -72,14 +72,14 @@ dependencies:
|
|
|
72
72
|
requirements:
|
|
73
73
|
- - "~>"
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '2.
|
|
75
|
+
version: '2.235'
|
|
76
76
|
type: :runtime
|
|
77
77
|
prerelease: false
|
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '2.
|
|
82
|
+
version: '2.235'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: gettext
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -451,6 +451,7 @@ files:
|
|
|
451
451
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/remove_branch_protection_action.rb
|
|
452
452
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/set_branch_protection_action.rb
|
|
453
453
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/set_milestone_frozen_marker_action.rb
|
|
454
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/update_apps_cdn_build_metadata.rb
|
|
454
455
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/update_assigned_milestone_action.rb
|
|
455
456
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_build_to_apps_cdn.rb
|
|
456
457
|
- lib/fastlane/plugin/wpmreleasetoolkit/actions/common/upload_to_s3.rb
|
|
@@ -479,6 +480,7 @@ files:
|
|
|
479
480
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_tools_path_helper.rb
|
|
480
481
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/android/android_version_helper.rb
|
|
481
482
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/app_size_metrics_helper.rb
|
|
483
|
+
- lib/fastlane/plugin/wpmreleasetoolkit/helper/apps_cdn_helper.rb
|
|
482
484
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/buildkite_aware_log_groups.rb
|
|
483
485
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/ci_helper.rb
|
|
484
486
|
- lib/fastlane/plugin/wpmreleasetoolkit/helper/configure_helper.rb
|