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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8cd4f080adf3fef449642139ef7ca42ee6f186325ca8948ee05dc7fdab939c3a
4
- data.tar.gz: 4db24993e8d906973ab4c91e8df26d26bd01104c00c332c3d2419a85b62f4b54
3
+ metadata.gz: 8e730459c397f4e9585f12a2c687f6216c8fb2c1679c5a45a5e85e8649b1398a
4
+ data.tar.gz: 465ba4d2a4551dd462333f6e70d8e1e361d6642aafcb67d0505b84f7cb7f9e54
5
5
  SHA512:
6
- metadata.gz: a05b26ce9c1de6c6c1fa251948a7d6af63bd6e89bf4bef1338d7247c6ca37290e7dbbbe5864ab2d83f51450ed719b23ebdd2d6d993e9e0a4140e7891af2137c2
7
- data.tar.gz: 3b6f71ab17e05f1b1f50488360a7533e2dbfe5757d0b36f536c42daef9f662f092be6a628f2b9e94bee3d4de2b9a4ff6671d6cd9dff164ad095e7253ea0b8f9a
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 leaves every
24
- crash it produces unsymbolicated forever.
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` (or point `pharen_binary:` / `PHAREN_BINARY` at it),
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
- - `PHAREN_AUTH_TOKEN` in the environment (your one secret — see [Configuration](#configuration)).
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. `node /path/to/cli.js`). |
51
- | `strict` | `PHAREN_STRICT` | `false` | Fail the lane on any step's error instead of warning and continuing. |
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 on success, or `nil` when a step is skipped or fails outside
54
- strict mode.
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` | `false` | Fail the lane on error instead of warning and continuing. |
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` | `false` | Fail the lane on error instead of warning and continuing. |
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, and only one of them is a secret:
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
- - **`PHAREN_AUTH_TOKEN`** — your one secret, read from the environment (a CI secret in
93
- practice). Only the CLI reads it, and only from the environment. The plugin never
94
- reads, prints, or passes the token, so it never lands in a log line or a process
95
- argument list.
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 **warn-don't-fail**: if a step errors, the plugin logs a loud warning and
100
- lets the build continue. A telemetry step should never be the reason a release build
101
- fails.
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: true` (or set `PHAREN_STRICT=1`) in CI lanes where an unsymbolicated or
104
- undistributed release must be a hard stop; then any step's error fails the lane.
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 always warn loudly.** A build shipped without its symbols produces
109
- crashes that can never be read, and uploading symbols later does not retroactively
110
- symbolicate the crashes it already caused. Archive with
111
- `DEBUG_INFORMATION_FORMAT=dwarf-with-dsym` so the dSYMs exist to upload.
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 and make `pharen`
119
- resolvable, or point `pharen_binary:` / `PHAREN_BINARY` at it (it may include
120
- arguments, e.g. `node /path/to/cli.js`).
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 in warn mode:
125
- registration records provenance, so a failed registration degrades metadata without
126
- stopping distribution. Use `strict: true` if you need it to stop the lane.
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
- documentation at <https://pharen.ai/docs>.
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
- # PHAREN_AUTH_TOKEN (env-only, never echoed). It folds Pharen's three CLI steps
13
- # (releases new → upload-symbols → upload-build) into one, so registering a release
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 leaves every crash it
17
- # ever produces unsymbolicated (late upload does not retroactively fix issues). In
18
- # warn mode a missing-dSYM situation is a LOUD warning and the build still uploads;
19
- # strict mode aborts the lane.
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", "new", "--platform", "ios", "--version", info[:version], "--build", info[:build]]
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: ingest also binds releases lazily, so a failed
56
- # registration degrades provenance, not the pipeline.
57
- helper.fail_or_warn(params[:strict], helper.cli_failure_message("pharen releases new", release))
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
- "can NEVER symbolicate. Archive with DEBUG_INFORMATION_FORMAT=dwarf-with-dsym, " \
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], ["upload-symbols", *dsyms])
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-symbols", symbols))
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
- "upload-build",
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-build", build))
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
- UI.success("pharen_release: #{info[:bundle_id]} #{info[:version]} (#{info[:build]}) registered + symbolicated + distributed")
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 (Lucubra LLC)"]
134
+ ["Pharen"]
123
135
  end
124
136
 
125
137
  def self.details
126
- "Orchestrates the Pharen CLI's three steps (releases new → upload-symbols → " \
127
- "upload-build) with everything derived: IPA + dSYMs from the lane context, " \
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 PHAREN_AUTH_TOKEN. " \
130
- "Missing dSYMs warn LOUDLY (or abort under strict) — a build without " \
131
- "symbols ships crashes that can never be read."
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. `node /path/to/cli.js`)",
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 continuing (false)",
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: false,
157
- is_string: false)
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 answered by the Pharen control plane"]
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 on success; nil on skipped/failed-but-not-strict"
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's dl distribution and get the
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 PHAREN_AUTH_TOKEN (env-only, never echoed).
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
- "upload-build",
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-build", result))
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 dl distribution and return the OTA install link (zero-arg after gym)"
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 (Lucubra LLC)"]
69
+ ["Pharen"]
70
70
  end
71
71
 
72
72
  def self.details
73
- "Thin wrapper over `pharen upload-build`: the IPA defaults from " \
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 server " \
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. `node /path/to/cli.js`)",
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 (false)",
95
+ description: "Fail the lane on error (true, default) instead of warning and continuing",
97
96
  optional: true,
98
- default_value: false,
99
- is_string: false)
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 answered by the Pharen control plane"]
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
- # PHAREN_AUTH_TOKEN in the environment (never echoed, never on a command line).
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], ["upload-symbols"] + expanded)
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-symbols", result))
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 (Lucubra LLC)"]
66
+ ["Pharen"]
67
67
  end
68
68
 
69
69
  def self.details
70
- "Thin wrapper over `pharen upload-symbols`: dSYM paths default from " \
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 (PHAREN_AUTH_TOKEN) are the CLI's job. " \
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. `node /path/to/cli.js`)",
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 (false)",
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: false,
95
- is_string: false)
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: PHAREN_AUTH_TOKEN is read by the CLI from the environment. This helper never
13
- # reads, prints, or passes the token — no command line ever contains it.
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. "node /path/to/cli.js"). Never raises on CLI
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 PHAREN_AUTH_TOKEN (env-only)
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 and ensure `pharen` is on PATH " \
125
- "(see https://pharen.ai/docs for install instructions), or set the pharen_binary option"
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. Explicit --org/--app flags are the
138
- # escape hatch for exotic layouts.
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 new --commit`; nil outside a git checkout (fine — the
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; default: warn loudly and let the build continue —
165
- # a telemetry step should never be the reason a release build fails.
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)
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Pharen
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "0.2.0".freeze
4
4
  end
5
5
  end
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - Pharen (Lucubra LLC)
7
+ - Pharen
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
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, pharen_release, after build_app
56
- — it registers the release, uploads every dSYM, uploads the build, and returns the
57
- OTA install link. Warn-don''t-fail by default, so a telemetry step never fails your
58
- release build; `strict: true` opts a lane into hard failure where an unsymbolicated
59
- release must be a stop.'
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: 4.0.11
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'