fastlane-plugin-pharen 0.1.0 → 0.2.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/CHANGELOG.md +22 -2
- data/README.md +38 -29
- data/lib/fastlane/plugin/pharen/actions/pharen_release_action.rb +45 -30
- data/lib/fastlane/plugin/pharen/actions/pharen_upload_build_action.rb +14 -15
- data/lib/fastlane/plugin/pharen/actions/pharen_upload_symbols_action.rb +13 -11
- data/lib/fastlane/plugin/pharen/helper/pharen_helper.rb +12 -14
- data/lib/fastlane/plugin/pharen/version.rb +1 -1
- metadata +15 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e730459c397f4e9585f12a2c687f6216c8fb2c1679c5a45a5e85e8649b1398a
|
|
4
|
+
data.tar.gz: 465ba4d2a4551dd462333f6e70d8e1e361d6642aafcb67d0505b84f7cb7f9e54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6a0a84cfaa13a65a383e60cfe3cc0d55bc4996e66053fac349f7c5eb98fc4634dbde73395d8a1c417e994fde64b7e050e0fbf59870431f9a3767afe218c0eec
|
|
7
|
+
data.tar.gz: ba90879161e346e4b626a198de1824851a129407411bd03bd597b405847d337b37553393a30078fb6f67a5de50fd70f698ad28cc05a669906dc6e7ae9a0b960b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `fastlane-plugin-pharen` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.0
|
|
6
|
+
|
|
7
|
+
- **Requires `@pharen-ai/cli` 0.1.0 or later** (`npm install -g @pharen-ai/cli`). The
|
|
8
|
+
actions call it by its current commands — `releases create`, `symbols upload`,
|
|
9
|
+
`builds upload`. 0.1.0 called them by older names the CLI does not accept, and because
|
|
10
|
+
0.1.0 warned rather than failed, a lane on it would skip every step without uploading
|
|
11
|
+
anything.
|
|
12
|
+
- **`strict` defaults to `true`.** A step that fails now fails the lane; pass
|
|
13
|
+
`strict: false` (or `PHAREN_STRICT=false`) to warn and continue as 0.1.0 did. A build
|
|
14
|
+
shipped without its symbols produces crashes that stay unreadable until they arrive,
|
|
15
|
+
which a warning in a long lane log too easily hides.
|
|
16
|
+
- **`PHAREN_STRICT` takes `true` or `false`** (also `yes`/`no`, `on`/`off`, each in
|
|
17
|
+
lowercase or all caps). Any other value, `1`, `0` and an empty one included, now fails
|
|
18
|
+
the lane instead of being read as `true`; unset it to take the default.
|
|
19
|
+
- **`pharen_release`'s closing line says what happened** — release registered or not,
|
|
20
|
+
symbols uploaded or not — rather than claiming all three steps after one was skipped.
|
|
21
|
+
- **Credentials are the CLI's.** The CLI uses `PHAREN_AUTH_TOKEN` when it is exported, a
|
|
22
|
+
CI job's own identity once its pipeline is bound, or the session `pharen auth login`
|
|
23
|
+
stored; the plugin still never reads, prints, or passes any of them.
|
|
24
|
+
|
|
5
25
|
## 0.1.0 — initial release
|
|
6
26
|
|
|
7
27
|
Three actions, a thin wrapper over the `pharen` CLI (auth, `.pharen.yml` resolution,
|
|
@@ -20,8 +40,8 @@ debug-id parsing and server-side dedupe all live there):
|
|
|
20
40
|
**Failure posture:** warn-don't-fail by default — a telemetry step should never be the
|
|
21
41
|
reason a release build fails. Pass `strict: true` (or set `PHAREN_STRICT=1`) in CI lanes
|
|
22
42
|
where an unsymbolicated release must be a hard stop. A missing-dSYM situation always
|
|
23
|
-
warns loudly regardless of mode, since a build shipped without symbols
|
|
24
|
-
|
|
43
|
+
warns loudly regardless of mode, since a build shipped without symbols produces crashes
|
|
44
|
+
that stay unreadable until its symbols are uploaded.
|
|
25
45
|
|
|
26
46
|
**Auth:** `PHAREN_AUTH_TOKEN` is read from the environment only — the plugin never
|
|
27
47
|
reads, prints, or passes it on a command line.
|
data/README.md
CHANGED
|
@@ -18,9 +18,10 @@ fastlane add_plugin pharen
|
|
|
18
18
|
|
|
19
19
|
Once per repository, you also need:
|
|
20
20
|
|
|
21
|
-
- the `pharen` CLI on `PATH`
|
|
21
|
+
- the `pharen` CLI on `PATH` — `npm install -g @pharen-ai/cli` (or point
|
|
22
|
+
`pharen_binary:` / `PHAREN_BINARY` at it),
|
|
22
23
|
- a committed `.pharen.yml` at the repo root (`org:` and `app:` — no secrets),
|
|
23
|
-
-
|
|
24
|
+
- a credential the CLI can use — see [Configuration](#configuration).
|
|
24
25
|
|
|
25
26
|
## Quickstart
|
|
26
27
|
|
|
@@ -47,11 +48,12 @@ The whole integration in one call after `build_app`.
|
|
|
47
48
|
|---|---|---|---|
|
|
48
49
|
| `ipa` | `PHAREN_IPA` | lane context (`IPA_OUTPUT_PATH`) | Path to the `.ipa`. |
|
|
49
50
|
| `dsym_paths` | `PHAREN_DSYM_PATHS` | lane context (`DSYM_OUTPUT_PATH` + `DSYM_PATHS`) | dSYM bundles or `.zip`s to upload. |
|
|
50
|
-
| `pharen_binary` | `PHAREN_BINARY` | `pharen` | Command that invokes the CLI (may include arguments, e.g. `
|
|
51
|
-
| `strict` | `PHAREN_STRICT` | `
|
|
51
|
+
| `pharen_binary` | `PHAREN_BINARY` | `pharen` | Command that invokes the CLI (may include arguments, e.g. `npx --yes @pharen-ai/cli`). |
|
|
52
|
+
| `strict` | `PHAREN_STRICT` | `true` | Fail the lane on any step's error. `false` warns and continues instead. |
|
|
52
53
|
|
|
53
|
-
Returns the install link
|
|
54
|
-
strict
|
|
54
|
+
Returns the install link once the build uploads, even if registration or the symbols
|
|
55
|
+
step failed with `strict: false`. It returns `nil` when there is no IPA, its `Info.plist`
|
|
56
|
+
cannot be read, or the build upload fails (with `strict: false`).
|
|
55
57
|
|
|
56
58
|
### `pharen_upload_build`
|
|
57
59
|
|
|
@@ -65,7 +67,7 @@ pharen_upload_build # IPA from the lane context → install link
|
|
|
65
67
|
|---|---|---|---|
|
|
66
68
|
| `ipa` | `PHAREN_IPA` | lane context (`IPA_OUTPUT_PATH`) | Path to the `.ipa`. |
|
|
67
69
|
| `pharen_binary` | `PHAREN_BINARY` | `pharen` | Command that invokes the CLI. |
|
|
68
|
-
| `strict` | `PHAREN_STRICT` | `
|
|
70
|
+
| `strict` | `PHAREN_STRICT` | `true` | Fail the lane on error. `false` warns and continues instead. |
|
|
69
71
|
|
|
70
72
|
### `pharen_upload_symbols`
|
|
71
73
|
|
|
@@ -80,52 +82,59 @@ pharen_upload_symbols(dsym_paths: ["path/to/App.dSYM"]) # or explicit
|
|
|
80
82
|
|---|---|---|---|
|
|
81
83
|
| `dsym_paths` | `PHAREN_DSYM_PATHS` | lane context (`DSYM_OUTPUT_PATH` + `DSYM_PATHS`) | `.dSYM` bundles, dSYM `.zip`s (as produced by `gym`/`download_dsyms`), or raw DWARF binaries. |
|
|
82
84
|
| `pharen_binary` | `PHAREN_BINARY` | `pharen` | Command that invokes the CLI. |
|
|
83
|
-
| `strict` | `PHAREN_STRICT` | `
|
|
85
|
+
| `strict` | `PHAREN_STRICT` | `true` | Fail the lane on error. `false` warns and continues instead. |
|
|
84
86
|
|
|
85
87
|
## Configuration
|
|
86
88
|
|
|
87
|
-
Two things live outside the lane
|
|
89
|
+
Two things live outside the lane:
|
|
88
90
|
|
|
89
91
|
- **`.pharen.yml`** — committed at the repo root, holding `org:` and `app:`. No secrets
|
|
90
92
|
belong in it. The CLI walks up from its working directory to find it, so it resolves
|
|
91
93
|
whether fastlane runs from the repo root or the `fastlane/` directory.
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
- **A credential** — the CLI finds its own, in this order: `PHAREN_AUTH_TOKEN` when it is
|
|
95
|
+
exported (a CI secret), then a CI job's own identity once an organization owner has
|
|
96
|
+
bound that pipeline (no secret at all), then the session `pharen auth login` stored on
|
|
97
|
+
your machine. The plugin never reads, prints, or passes a credential, so none lands in a
|
|
98
|
+
log line or a process argument list. The [CLI reference](https://www.pharen.ai/docs/cli)
|
|
99
|
+
covers each.
|
|
96
100
|
|
|
97
101
|
## Failure posture
|
|
98
102
|
|
|
99
|
-
The default is **
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
The default is **strict**: if a step errors, the lane fails. A build that ships without
|
|
104
|
+
its symbols produces crashes that stay unreadable until they arrive, so the plugin stops
|
|
105
|
+
rather than letting that pass unnoticed.
|
|
102
106
|
|
|
103
|
-
Pass `strict:
|
|
104
|
-
|
|
107
|
+
Pass `strict: false` (or set `PHAREN_STRICT=false`) in lanes where a Pharen step must
|
|
108
|
+
never be the reason a build fails; then any step's error is logged as a loud warning and
|
|
109
|
+
the lane continues. `PHAREN_STRICT` takes `true` or `false` (also `yes`/`no`,
|
|
110
|
+
`on`/`off`, each in lowercase or all caps); any other value, an empty one included, fails
|
|
111
|
+
the lane rather than being guessed at. Unset it to take the default.
|
|
105
112
|
|
|
106
113
|
Two things hold regardless of mode:
|
|
107
114
|
|
|
108
|
-
- **Missing dSYMs
|
|
109
|
-
crashes that
|
|
110
|
-
|
|
111
|
-
`DEBUG_INFORMATION_FORMAT=dwarf-with-dsym`
|
|
115
|
+
- **Missing dSYMs are never quiet.** A build shipped without its symbols produces
|
|
116
|
+
crashes that stay unreadable until the symbols arrive. Uploading them later
|
|
117
|
+
re-symbolicates and re-groups recent crashes, at most the last seven days of them;
|
|
118
|
+
older ones may stay as they first grouped. Archive with `DEBUG_INFORMATION_FORMAT=dwarf-with-dsym`
|
|
119
|
+
so the dSYMs exist to upload with the build.
|
|
112
120
|
- **Re-runs are safe.** The server dedupes symbols by debug id, and the CLI exits 0 when
|
|
113
121
|
every file is already uploaded — so retrying a lane never double-uploads or errors on
|
|
114
122
|
work already done.
|
|
115
123
|
|
|
116
124
|
## Troubleshooting
|
|
117
125
|
|
|
118
|
-
- **`pharen` not found.** The CLI isn't on `PATH`. Install it
|
|
119
|
-
|
|
120
|
-
arguments, e.g. `
|
|
126
|
+
- **`pharen` not found.** The CLI isn't on `PATH`. Install it with
|
|
127
|
+
`npm install -g @pharen-ai/cli`, or point `pharen_binary:` / `PHAREN_BINARY` at a
|
|
128
|
+
command that runs it (it may include arguments, e.g. `npx --yes @pharen-ai/cli`).
|
|
121
129
|
- **"no IPA" or "no dSYMs in the lane context".** Run the action after `build_app`/`gym`
|
|
122
130
|
(and `download_dsyms`, if you fetch symbols from App Store Connect), or pass `ipa:` /
|
|
123
131
|
`dsym_paths:` explicitly.
|
|
124
|
-
- **Release registration warns but the build still uploads.** Expected
|
|
125
|
-
registration records provenance, so a failed registration
|
|
126
|
-
stopping distribution.
|
|
132
|
+
- **Release registration warns but the build still uploads.** Expected with
|
|
133
|
+
`strict: false`: registration records provenance (the commit), so a failed registration
|
|
134
|
+
loses that metadata without stopping distribution. Leave `strict` at its default if you
|
|
135
|
+
need it to stop the lane.
|
|
127
136
|
- **Not using fastlane?** The `pharen` CLI runs the same three steps directly. See the
|
|
128
|
-
|
|
137
|
+
[CLI reference](https://www.pharen.ai/docs/cli).
|
|
129
138
|
|
|
130
139
|
## License
|
|
131
140
|
|
|
@@ -8,15 +8,16 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Zero-arg: the IPA and dSYMs default from the lane context (gym/download_dsyms);
|
|
10
10
|
# version / build number / bundle id are read from the IPA's own Info.plist; the
|
|
11
|
-
# commit sha from git; org/app from the committed .pharen.yml; auth from
|
|
12
|
-
#
|
|
13
|
-
# (releases
|
|
11
|
+
# commit sha from git; org/app from the committed .pharen.yml; auth from the CLI's own
|
|
12
|
+
# credential (never passed or echoed). It folds Pharen's three CLI steps
|
|
13
|
+
# (releases create → symbols upload → builds upload) into one, so registering a release
|
|
14
14
|
# is never a step you have to remember to run.
|
|
15
15
|
#
|
|
16
|
-
# SYMBOLS ARE NOT OPTIONAL: a build shipped without its dSYMs
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# strict
|
|
16
|
+
# SYMBOLS ARE NOT OPTIONAL: a build shipped without its dSYMs produces crashes that stay
|
|
17
|
+
# unreadable until the dSYMs arrive (a late upload re-symbolicates and re-groups recent
|
|
18
|
+
# crashes only, at most the last seven days of them). Strict mode, the default, aborts
|
|
19
|
+
# the lane when no dSYM is found; with strict: false it is a LOUD warning and the build
|
|
20
|
+
# still uploads.
|
|
20
21
|
|
|
21
22
|
require "fastlane/action"
|
|
22
23
|
require_relative "../helper/pharen_helper"
|
|
@@ -47,18 +48,20 @@ module Fastlane
|
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
# -- step 1: register the release (idempotent server-side; commit = provenance)
|
|
50
|
-
release_args = ["releases", "
|
|
51
|
+
release_args = ["releases", "create", "--platform", "ios", "--version", info[:version], "--build", info[:build]]
|
|
51
52
|
sha = helper.git_commit_sha
|
|
52
53
|
release_args += ["--commit", sha] if sha
|
|
53
54
|
release = helper.run_pharen(params[:pharen_binary], release_args)
|
|
54
55
|
unless release[:ok]
|
|
55
|
-
# Not fatal in warn mode:
|
|
56
|
-
# registration
|
|
57
|
-
|
|
56
|
+
# Not fatal in warn mode: an uploaded build or an arriving crash records its
|
|
57
|
+
# release anyway, so a failed registration loses provenance (the commit), not
|
|
58
|
+
# the release.
|
|
59
|
+
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen releases create", release))
|
|
58
60
|
return nil if params[:strict]
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
# -- step 2: symbols (every dSYM the lane produced) ---------------------------
|
|
64
|
+
symbols_ok = false
|
|
62
65
|
dsym_inputs = params[:dsym_paths]
|
|
63
66
|
if dsym_inputs.nil? || Array(dsym_inputs).empty?
|
|
64
67
|
candidates = []
|
|
@@ -70,18 +73,19 @@ module Fastlane
|
|
|
70
73
|
helper.fail_or_warn(
|
|
71
74
|
params[:strict],
|
|
72
75
|
"pharen_release: no dSYMs in the lane context — crashes from this build " \
|
|
73
|
-
"
|
|
74
|
-
"or pass dsym_paths:"
|
|
76
|
+
"stay unreadable until its dSYMs are uploaded. Archive with " \
|
|
77
|
+
"DEBUG_INFORMATION_FORMAT=dwarf-with-dsym, or pass dsym_paths:"
|
|
75
78
|
)
|
|
76
79
|
else
|
|
77
80
|
begin
|
|
78
81
|
dsyms = helper.expand_dsym_inputs(dsym_inputs)
|
|
79
|
-
symbols = helper.run_pharen(params[:pharen_binary], ["
|
|
82
|
+
symbols = helper.run_pharen(params[:pharen_binary], ["symbols", "upload", *dsyms])
|
|
80
83
|
if symbols[:ok]
|
|
84
|
+
symbols_ok = true
|
|
81
85
|
summary = symbols[:json].is_a?(Hash) ? symbols[:json]["summary"] : nil
|
|
82
86
|
UI.success("pharen_release: symbols — #{summary || "uploaded #{dsyms.length} dSYM(s)"}")
|
|
83
87
|
else
|
|
84
|
-
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen upload
|
|
88
|
+
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen symbols upload", symbols))
|
|
85
89
|
end
|
|
86
90
|
rescue Helper::PharenHelper::Failure => e
|
|
87
91
|
helper.fail_or_warn(params[:strict], "pharen_release: #{e.message}")
|
|
@@ -90,7 +94,8 @@ module Fastlane
|
|
|
90
94
|
|
|
91
95
|
# -- step 3: the build itself → install link ----------------------------------
|
|
92
96
|
build_args = [
|
|
93
|
-
"
|
|
97
|
+
"builds",
|
|
98
|
+
"upload",
|
|
94
99
|
"--platform", "ios",
|
|
95
100
|
"--version", info[:version],
|
|
96
101
|
"--build", info[:build],
|
|
@@ -99,13 +104,20 @@ module Fastlane
|
|
|
99
104
|
]
|
|
100
105
|
build = helper.run_pharen(params[:pharen_binary], build_args)
|
|
101
106
|
unless build[:ok]
|
|
102
|
-
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen upload
|
|
107
|
+
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen builds upload", build))
|
|
103
108
|
return nil
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
install_url = build[:json].is_a?(Hash) ? build[:json]["install_url"] : nil
|
|
107
112
|
Actions.lane_context[SharedValues::PHAREN_INSTALL_URL] = install_url if install_url
|
|
108
|
-
|
|
113
|
+
# Says what actually happened: outside strict mode a step can fail and the lane
|
|
114
|
+
# still reaches here, and a line claiming all three would hide it.
|
|
115
|
+
outcome = [
|
|
116
|
+
release[:ok] ? "release registered" : "release NOT registered",
|
|
117
|
+
symbols_ok ? "symbols uploaded" : "symbols NOT uploaded",
|
|
118
|
+
"build uploaded"
|
|
119
|
+
]
|
|
120
|
+
UI.success("pharen_release: #{info[:bundle_id]} #{info[:version]} (#{info[:build]}) — #{outcome.join(', ')}")
|
|
109
121
|
UI.important("Install (Safari on the phone): #{install_url}") if install_url
|
|
110
122
|
install_url
|
|
111
123
|
end
|
|
@@ -119,16 +131,16 @@ module Fastlane
|
|
|
119
131
|
end
|
|
120
132
|
|
|
121
133
|
def self.authors
|
|
122
|
-
["Pharen
|
|
134
|
+
["Pharen"]
|
|
123
135
|
end
|
|
124
136
|
|
|
125
137
|
def self.details
|
|
126
|
-
"
|
|
127
|
-
"upload
|
|
138
|
+
"Runs the Pharen CLI's three steps (releases create → symbols upload → " \
|
|
139
|
+
"builds upload) with everything derived: IPA + dSYMs from the lane context, " \
|
|
128
140
|
"version/build/bundle id from the IPA's Info.plist, commit sha from git, " \
|
|
129
|
-
"org/app from the committed .pharen.yml, auth from
|
|
130
|
-
"Missing dSYMs
|
|
131
|
-
"symbols ships crashes that
|
|
141
|
+
"org/app from the committed .pharen.yml, auth from the CLI's own credential. " \
|
|
142
|
+
"Missing dSYMs abort the lane (or warn LOUDLY with strict: false) — a build " \
|
|
143
|
+
"without symbols ships crashes that stay unreadable until its symbols are uploaded."
|
|
132
144
|
end
|
|
133
145
|
|
|
134
146
|
def self.available_options
|
|
@@ -145,27 +157,30 @@ module Fastlane
|
|
|
145
157
|
is_string: false),
|
|
146
158
|
FastlaneCore::ConfigItem.new(key: :pharen_binary,
|
|
147
159
|
env_name: "PHAREN_BINARY",
|
|
148
|
-
description: "Command that invokes the Pharen CLI (may include arguments, e.g. `
|
|
160
|
+
description: "Command that invokes the Pharen CLI (may include arguments, e.g. `npx --yes @pharen-ai/cli`)",
|
|
149
161
|
optional: true,
|
|
150
162
|
default_value: "pharen",
|
|
151
163
|
type: String),
|
|
152
164
|
FastlaneCore::ConfigItem.new(key: :strict,
|
|
153
165
|
env_name: "PHAREN_STRICT",
|
|
154
|
-
description: "Fail the lane on any step's error (true) instead of warning and
|
|
166
|
+
description: "Fail the lane on any step's error (true, default) instead of warning and " \
|
|
167
|
+
"continuing. Silent continuation ships builds whose symbols never arrived; " \
|
|
168
|
+
"pass strict: false only where that is an acceptable trade",
|
|
155
169
|
optional: true,
|
|
156
|
-
default_value:
|
|
157
|
-
|
|
170
|
+
default_value: true,
|
|
171
|
+
type: Fastlane::Boolean)
|
|
158
172
|
]
|
|
159
173
|
end
|
|
160
174
|
|
|
161
175
|
def self.output
|
|
162
176
|
[
|
|
163
|
-
["PHAREN_INSTALL_URL", "The itms-services install link
|
|
177
|
+
["PHAREN_INSTALL_URL", "The itms-services install link Pharen returned for the build"]
|
|
164
178
|
]
|
|
165
179
|
end
|
|
166
180
|
|
|
167
181
|
def self.return_value
|
|
168
|
-
"The install_url string
|
|
182
|
+
"The install_url string once the build uploads; nil when there is no IPA, its Info.plist " \
|
|
183
|
+
"cannot be read, or the build upload fails (with strict: false)"
|
|
169
184
|
end
|
|
170
185
|
|
|
171
186
|
def self.is_supported?(platform)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
# pharen_upload_build — upload the IPA to Pharen
|
|
2
|
-
# install link back.
|
|
1
|
+
# pharen_upload_build — upload the IPA to Pharen and get the install link back.
|
|
3
2
|
#
|
|
4
3
|
# Zero-arg capable: the IPA defaults from gym/build_app's SharedValues::IPA_OUTPUT_PATH,
|
|
5
4
|
# and version / build number / bundle id are read out of the IPA's own Info.plist
|
|
6
5
|
# (unzip + plutil — dependency-free), so the customer passes nothing. org/app come from
|
|
7
|
-
# the committed .pharen.yml; auth from
|
|
6
|
+
# the committed .pharen.yml; auth from the CLI's own credential (never passed or echoed).
|
|
8
7
|
|
|
9
8
|
require "fastlane/action"
|
|
10
9
|
require_relative "../helper/pharen_helper"
|
|
@@ -37,7 +36,8 @@ module Fastlane
|
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
args = [
|
|
40
|
-
"
|
|
39
|
+
"builds",
|
|
40
|
+
"upload",
|
|
41
41
|
"--platform", "ios",
|
|
42
42
|
"--version", info[:version],
|
|
43
43
|
"--build", info[:build],
|
|
@@ -46,7 +46,7 @@ module Fastlane
|
|
|
46
46
|
]
|
|
47
47
|
result = helper.run_pharen(params[:pharen_binary], args)
|
|
48
48
|
unless result[:ok]
|
|
49
|
-
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen upload
|
|
49
|
+
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen builds upload", result))
|
|
50
50
|
return nil
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -62,18 +62,17 @@ module Fastlane
|
|
|
62
62
|
#####################################################
|
|
63
63
|
|
|
64
64
|
def self.description
|
|
65
|
-
"Upload the IPA to Pharen
|
|
65
|
+
"Upload the IPA to Pharen and return the OTA install link (zero-arg after gym)"
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def self.authors
|
|
69
|
-
["Pharen
|
|
69
|
+
["Pharen"]
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def self.details
|
|
73
|
-
"Thin wrapper over `pharen upload
|
|
73
|
+
"Thin wrapper over `pharen builds upload`: the IPA defaults from " \
|
|
74
74
|
"SharedValues::IPA_OUTPUT_PATH; version, build number and bundle id are " \
|
|
75
|
-
"extracted from the IPA's Info.plist so nothing needs passing. The
|
|
76
|
-
"owns the dl layout, opaque slug and manifest.plist; the returned " \
|
|
75
|
+
"extracted from the IPA's Info.plist so nothing needs passing. The returned " \
|
|
77
76
|
"install_url is this action's return value and lands in " \
|
|
78
77
|
"lane_context[SharedValues::PHAREN_INSTALL_URL]."
|
|
79
78
|
end
|
|
@@ -87,22 +86,22 @@ module Fastlane
|
|
|
87
86
|
type: String),
|
|
88
87
|
FastlaneCore::ConfigItem.new(key: :pharen_binary,
|
|
89
88
|
env_name: "PHAREN_BINARY",
|
|
90
|
-
description: "Command that invokes the Pharen CLI (may include arguments, e.g. `
|
|
89
|
+
description: "Command that invokes the Pharen CLI (may include arguments, e.g. `npx --yes @pharen-ai/cli`)",
|
|
91
90
|
optional: true,
|
|
92
91
|
default_value: "pharen",
|
|
93
92
|
type: String),
|
|
94
93
|
FastlaneCore::ConfigItem.new(key: :strict,
|
|
95
94
|
env_name: "PHAREN_STRICT",
|
|
96
|
-
description: "Fail the lane on error (true) instead of warning and continuing
|
|
95
|
+
description: "Fail the lane on error (true, default) instead of warning and continuing",
|
|
97
96
|
optional: true,
|
|
98
|
-
default_value:
|
|
99
|
-
|
|
97
|
+
default_value: true,
|
|
98
|
+
type: Fastlane::Boolean)
|
|
100
99
|
]
|
|
101
100
|
end
|
|
102
101
|
|
|
103
102
|
def self.output
|
|
104
103
|
[
|
|
105
|
-
["PHAREN_INSTALL_URL", "The itms-services install link
|
|
104
|
+
["PHAREN_INSTALL_URL", "The itms-services install link Pharen returned for the build"]
|
|
106
105
|
]
|
|
107
106
|
end
|
|
108
107
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# Zero-arg capable: with no options it takes the dSYMs the lane already produced —
|
|
4
4
|
# gym/build_app's SharedValues::DSYM_OUTPUT_PATH and download_dsyms'
|
|
5
5
|
# SharedValues::DSYM_PATHS. The CLI needs ONLY paths: debug ids are parsed from the
|
|
6
|
-
# binaries, org/app come from the customer's committed .pharen.yml, auth from
|
|
7
|
-
#
|
|
6
|
+
# binaries, org/app come from the customer's committed .pharen.yml, auth from the CLI's
|
|
7
|
+
# own credential (never echoed, never on a command line).
|
|
8
8
|
|
|
9
9
|
require "fastlane/action"
|
|
10
10
|
require_relative "../helper/pharen_helper"
|
|
@@ -33,9 +33,9 @@ module Fastlane
|
|
|
33
33
|
return nil
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
result = helper.run_pharen(params[:pharen_binary], ["
|
|
36
|
+
result = helper.run_pharen(params[:pharen_binary], ["symbols", "upload"] + expanded)
|
|
37
37
|
unless result[:ok]
|
|
38
|
-
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen upload
|
|
38
|
+
helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen symbols upload", result))
|
|
39
39
|
return nil
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -63,14 +63,14 @@ module Fastlane
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def self.authors
|
|
66
|
-
["Pharen
|
|
66
|
+
["Pharen"]
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def self.details
|
|
70
|
-
"Thin wrapper over `pharen upload
|
|
70
|
+
"Thin wrapper over `pharen symbols upload`: dSYM paths default from " \
|
|
71
71
|
"SharedValues::DSYM_OUTPUT_PATH (gym) and SharedValues::DSYM_PATHS " \
|
|
72
72
|
"(download_dsyms); .zip inputs are expanded to .dSYM bundles. Debug ids, " \
|
|
73
|
-
"org/app (.pharen.yml) and auth
|
|
73
|
+
"org/app (.pharen.yml) and auth are the CLI's job. " \
|
|
74
74
|
"Server dedupes by debug id, so re-runs are safe."
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -83,16 +83,18 @@ module Fastlane
|
|
|
83
83
|
type: Array),
|
|
84
84
|
FastlaneCore::ConfigItem.new(key: :pharen_binary,
|
|
85
85
|
env_name: "PHAREN_BINARY",
|
|
86
|
-
description: "Command that invokes the Pharen CLI (may include arguments, e.g. `
|
|
86
|
+
description: "Command that invokes the Pharen CLI (may include arguments, e.g. `npx --yes @pharen-ai/cli`)",
|
|
87
87
|
optional: true,
|
|
88
88
|
default_value: "pharen",
|
|
89
89
|
type: String),
|
|
90
90
|
FastlaneCore::ConfigItem.new(key: :strict,
|
|
91
91
|
env_name: "PHAREN_STRICT",
|
|
92
|
-
description: "Fail the lane on error (true) instead of warning and continuing
|
|
92
|
+
description: "Fail the lane on error (true, default) instead of warning and continuing. " \
|
|
93
|
+
"A build shipped without its dSYMs produces crashes that stay unreadable until " \
|
|
94
|
+
"the symbols arrive — pass strict: false only where that is an acceptable trade",
|
|
93
95
|
optional: true,
|
|
94
|
-
default_value:
|
|
95
|
-
|
|
96
|
+
default_value: true,
|
|
97
|
+
type: Fastlane::Boolean)
|
|
96
98
|
]
|
|
97
99
|
end
|
|
98
100
|
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
# Dependency-free by design: macOS system tools only (unzip, plutil, git) — every box
|
|
10
10
|
# that can produce an IPA has them — so the gem adds NO runtime dependencies.
|
|
11
11
|
#
|
|
12
|
-
# SECURITY:
|
|
13
|
-
#
|
|
12
|
+
# SECURITY: the CLI resolves its own credential — the session `pharen auth login` stored, a
|
|
13
|
+
# CI job's identity, or PHAREN_AUTH_TOKEN from the environment. This helper never reads,
|
|
14
|
+
# prints, or passes one, so no command line ever contains it.
|
|
14
15
|
|
|
15
16
|
require "fastlane_core/ui/ui"
|
|
16
17
|
require "json"
|
|
@@ -100,12 +101,12 @@ module Fastlane
|
|
|
100
101
|
|
|
101
102
|
# Run `<binary> <args...>` and interpret the CLI's contract: JSON on stdout,
|
|
102
103
|
# exit 0 on success. `binary` is Shellwords-split so `pharen_binary` may carry a
|
|
103
|
-
# command with arguments (e.g. "
|
|
104
|
+
# command with arguments (e.g. "npx --yes @pharen-ai/cli"). Never raises on CLI
|
|
104
105
|
# failure — callers decide strict-vs-warn from the returned hash.
|
|
105
106
|
def run_pharen(binary, args, chdir: nil)
|
|
106
107
|
argv = Shellwords.split(binary.to_s) + args.map(&:to_s)
|
|
107
108
|
dir = chdir || project_dir
|
|
108
|
-
UI.command(Shellwords.join(argv)) # never contains
|
|
109
|
+
UI.command(Shellwords.join(argv)) # never contains a credential (the CLI resolves its own)
|
|
109
110
|
out, err, status = capture(argv, chdir: dir)
|
|
110
111
|
json = parse_json(out)
|
|
111
112
|
{ ok: status == 0, status: status, stdout: out, stderr: err, json: json }
|
|
@@ -121,8 +122,8 @@ module Fastlane
|
|
|
121
122
|
if error.is_a?(Hash) && error["message"]
|
|
122
123
|
"#{step} failed: #{error['message']} (#{error['code']})"
|
|
123
124
|
elsif result[:not_found]
|
|
124
|
-
"#{step} failed: #{result[:stderr]} — install the Pharen CLI
|
|
125
|
-
"
|
|
125
|
+
"#{step} failed: #{result[:stderr]} — install the Pharen CLI with " \
|
|
126
|
+
"`npm install -g @pharen-ai/cli` so `pharen` is on PATH, or set the pharen_binary option"
|
|
126
127
|
else
|
|
127
128
|
detail = result[:stderr].to_s.strip
|
|
128
129
|
detail = result[:stdout].to_s.strip if detail.empty?
|
|
@@ -134,11 +135,8 @@ module Fastlane
|
|
|
134
135
|
# Where to run the CLI so it finds the customer's committed .pharen.yml.
|
|
135
136
|
# fastlane executes lanes from the fastlane/ directory (Dir.chdir in
|
|
136
137
|
# LaneManager), but .pharen.yml lives at the repo root by convention — step up
|
|
137
|
-
# one level when that is clearly the layout.
|
|
138
|
-
#
|
|
139
|
-
# (The CLI itself also walks up from its cwd looking for .pharen.yml, so this
|
|
140
|
-
# special-case is now a belt-and-suspenders optimization, not the only path —
|
|
141
|
-
# kept as-is since it still resolves correctly and needs no change.)
|
|
138
|
+
# one level when that is clearly the layout. Any other layout is left to the
|
|
139
|
+
# CLI, which walks up from its working directory to the nearest .pharen.yml.
|
|
142
140
|
def project_dir
|
|
143
141
|
cwd = Dir.pwd
|
|
144
142
|
return cwd if File.exist?(File.join(cwd, ".pharen.yml"))
|
|
@@ -152,7 +150,7 @@ module Fastlane
|
|
|
152
150
|
|
|
153
151
|
# -- misc ------------------------------------------------------------------------
|
|
154
152
|
|
|
155
|
-
# HEAD sha for `releases
|
|
153
|
+
# HEAD sha for `releases create --commit`; nil outside a git checkout (fine — the
|
|
156
154
|
# flag is optional provenance).
|
|
157
155
|
def git_commit_sha
|
|
158
156
|
out, _err, status = capture(%w[git rev-parse HEAD])
|
|
@@ -161,8 +159,8 @@ module Fastlane
|
|
|
161
159
|
sha.empty? ? nil : sha
|
|
162
160
|
end
|
|
163
161
|
|
|
164
|
-
# strict: abort the lane
|
|
165
|
-
# a
|
|
162
|
+
# strict (the default): abort the lane. strict: false: warn loudly and let the
|
|
163
|
+
# build continue, for a lane where a Pharen step must never fail the build.
|
|
166
164
|
def fail_or_warn(strict, message)
|
|
167
165
|
if strict
|
|
168
166
|
UI.user_error!(message)
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-pharen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
- Pharen
|
|
7
|
+
- Pharen
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-10-24 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: fastlane
|
|
@@ -51,12 +52,12 @@ dependencies:
|
|
|
51
52
|
- - ">="
|
|
52
53
|
- !ruby/object:Gem::Version
|
|
53
54
|
version: '0'
|
|
54
|
-
description: 'Fastlane plugin for Pharen (https://pharen.ai): a thin wrapper over
|
|
55
|
-
the `pharen` CLI that adds one zero-argument action,
|
|
56
|
-
— it registers the release, uploads every dSYM,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
description: 'Fastlane plugin for Pharen (https://www.pharen.ai): a thin wrapper over
|
|
56
|
+
the `pharen` CLI (npm install -g @pharen-ai/cli) that adds one zero-argument action,
|
|
57
|
+
pharen_release, after build_app — it registers the release, uploads every dSYM,
|
|
58
|
+
uploads the build, and returns the OTA install link. A step that fails fails the
|
|
59
|
+
lane by default, so a build never ships without its symbols unnoticed; `strict:
|
|
60
|
+
false` lets a lane warn and continue instead.'
|
|
60
61
|
email: hello@pharen.ai
|
|
61
62
|
executables: []
|
|
62
63
|
extensions: []
|
|
@@ -71,12 +72,13 @@ files:
|
|
|
71
72
|
- lib/fastlane/plugin/pharen/actions/pharen_upload_symbols_action.rb
|
|
72
73
|
- lib/fastlane/plugin/pharen/helper/pharen_helper.rb
|
|
73
74
|
- lib/fastlane/plugin/pharen/version.rb
|
|
74
|
-
homepage: https://pharen.ai
|
|
75
|
+
homepage: https://www.pharen.ai/docs/fastlane
|
|
75
76
|
licenses:
|
|
76
77
|
- MIT
|
|
77
78
|
metadata:
|
|
78
|
-
homepage_uri: https://pharen.ai
|
|
79
|
+
homepage_uri: https://www.pharen.ai/docs/fastlane
|
|
79
80
|
rubygems_mfa_required: 'true'
|
|
81
|
+
post_install_message:
|
|
80
82
|
rdoc_options: []
|
|
81
83
|
require_paths:
|
|
82
84
|
- lib
|
|
@@ -91,7 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
91
93
|
- !ruby/object:Gem::Version
|
|
92
94
|
version: '0'
|
|
93
95
|
requirements: []
|
|
94
|
-
rubygems_version:
|
|
96
|
+
rubygems_version: 3.0.3.1
|
|
97
|
+
signing_key:
|
|
95
98
|
specification_version: 4
|
|
96
99
|
summary: 'Pharen build distribution + crash symbols from your lane: pharen_release
|
|
97
100
|
/ pharen_upload_build / pharen_upload_symbols'
|