laneyard 0.2.0 → 0.4.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.
- package/README.md +295 -20
- package/dist/src/cli/detect.js +69 -16
- package/dist/src/cli/prompt.js +61 -0
- package/dist/src/cli/secret-import.js +162 -0
- package/dist/src/cli/secret.js +162 -1
- package/dist/src/cli/setup.js +319 -0
- package/dist/src/cli/style.js +31 -0
- package/dist/src/cli/user.js +127 -0
- package/dist/src/config/accounts.js +163 -0
- package/dist/src/config/load.js +61 -1
- package/dist/src/config/schema.js +26 -1
- package/dist/src/config/store.js +10 -0
- package/dist/src/config/yaml.js +14 -0
- package/dist/src/credentials/kinds.js +120 -0
- package/dist/src/db/credentials.js +75 -0
- package/dist/src/db/schema.sql +39 -0
- package/dist/src/db/secrets.js +28 -0
- package/dist/src/db/sessions.js +61 -0
- package/dist/src/git/workspace.js +32 -6
- package/dist/src/heuristics/android-root.js +49 -0
- package/dist/src/heuristics/android-signing.js +98 -0
- package/dist/src/heuristics/appfile.js +60 -0
- package/dist/src/heuristics/blocking-actions.js +22 -0
- package/dist/src/heuristics/env-example.js +36 -0
- package/dist/src/heuristics/platforms.js +136 -0
- package/dist/src/heuristics/readiness.js +697 -25
- package/dist/src/main.js +58 -8
- package/dist/src/runner/gradle-properties.js +187 -0
- package/dist/src/runner/materialise.js +92 -0
- package/dist/src/runner/orchestrate.js +91 -2
- package/dist/src/secrets/vault.js +106 -5
- package/dist/src/server/app.js +94 -15
- package/dist/src/server/auth.js +127 -19
- package/dist/src/server/permissions.js +75 -0
- package/dist/src/server/required-secrets.js +30 -0
- package/dist/src/server/routes/account.js +57 -0
- package/dist/src/server/routes/credentials.js +108 -0
- package/dist/src/server/routes/projects.js +42 -0
- package/dist/src/server/routes/readiness.js +175 -6
- package/dist/src/server/routes/secrets.js +101 -0
- package/dist/src/server/routes/users.js +64 -0
- package/dist/src/sidecar/bridge.js +37 -1
- package/dist/src/sidecar/fastlane-dir.js +23 -0
- package/dist/src/sidecar/lanes.js +6 -0
- package/dist/src/sidecar/uses.js +21 -5
- package/dist/web/assets/{Editor-D5Q4uj4n.js → Editor-DynuBC2l.js} +1 -1
- package/dist/web/assets/index-CpwrNE-K.css +1 -0
- package/dist/web/assets/index-lyZs-Y7J.js +62 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/ruby/introspect.rb +122 -9
- package/dist/web/assets/index-D9_EL8LA.js +0 -62
- package/dist/web/assets/index-ZUqTX6d1.css +0 -1
package/dist/web/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>laneyard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-lyZs-Y7J.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CpwrNE-K.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/ruby/introspect.rb
CHANGED
|
@@ -100,31 +100,144 @@ def literal_args(call)
|
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
# Every keyword this call was given, whether or not its value could be read.
|
|
104
|
+
#
|
|
105
|
+
# `args` deliberately holds literals only — a checklist that guessed at
|
|
106
|
+
# `ENV["X"]` would be believed. But dropping the whole entry loses something the
|
|
107
|
+
# literal value was never needed for: that the argument *was passed at all*.
|
|
108
|
+
#
|
|
109
|
+
# The two are not the same question, and treating them as one produced a
|
|
110
|
+
# visible inconsistency. A lane calling `app_store_connect_api_key(...)` was
|
|
111
|
+
# recognised by the action's name and reported as "could not tell", while the
|
|
112
|
+
# very same lane calling `upload_to_play_store(json_key: ENV.fetch("..."))` left
|
|
113
|
+
# no trace of `json_key` and was reported as having no credential at all — a
|
|
114
|
+
# warning and a shrug, for one situation.
|
|
115
|
+
def arg_names(call)
|
|
116
|
+
hash = (call.arguments&.arguments || []).find { |a| a.is_a?(Prism::KeywordHashNode) }
|
|
117
|
+
return [] unless hash
|
|
118
|
+
|
|
119
|
+
hash.elements.filter_map do |el|
|
|
120
|
+
el.key.unescaped if el.is_a?(Prism::AssocNode) && el.key.is_a?(Prism::SymbolNode)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# How far a chain of helper methods is followed, and how the same method being
|
|
125
|
+
# called twice is kept from being walked twice. Eight is far past anything a
|
|
126
|
+
# real Fastfile does; the limit is there so that a pathological file costs a
|
|
127
|
+
# bounded walk rather than the checklist.
|
|
128
|
+
MAX_CALL_DEPTH = 8
|
|
129
|
+
|
|
130
|
+
# Every method this Fastfile defines, indexed by the name a call would use.
|
|
131
|
+
#
|
|
132
|
+
# `def deploy_ios` under "deploy_ios", and `def self.ship` inside `module
|
|
133
|
+
# Helpers` under "Helpers.ship". Both exist because both are how a real Fastfile
|
|
134
|
+
# is factored, and a Fastfile that factors its lanes is a well-written one — not
|
|
135
|
+
# an edge case. Reading only the lane bodies meant a project whose every action
|
|
136
|
+
# sat one method call away looked like a project that called no actions at all,
|
|
137
|
+
# and the Play Store check answered "no lane uploads to the Play Store" for a
|
|
138
|
+
# project that uploads to the Play Store on every run. A confident tick is worse
|
|
139
|
+
# than a warning: it is the one nobody checks.
|
|
140
|
+
def collect_defs(node, scope = nil, out = {})
|
|
141
|
+
node.compact_child_nodes.each do |child|
|
|
142
|
+
case child
|
|
143
|
+
when Prism::ModuleNode, Prism::ClassNode
|
|
144
|
+
# Its own scope, and nothing outside it needs to be walked again.
|
|
145
|
+
collect_defs(child, child.constant_path.slice, out)
|
|
146
|
+
next
|
|
147
|
+
when Prism::DefNode
|
|
148
|
+
key = child.receiver.nil? ? child.name.to_s : (scope ? "#{scope}.#{child.name}" : nil)
|
|
149
|
+
out[key] = child.body if key
|
|
150
|
+
end
|
|
151
|
+
collect_defs(child, scope, out)
|
|
152
|
+
end
|
|
153
|
+
out
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The name a call would be indexed under, or nil when it cannot be one: a
|
|
157
|
+
# receiver that is itself computed (`thing.ship`) is not something a file can
|
|
158
|
+
# resolve, and guessing would be worse than the silence.
|
|
159
|
+
def resolvable_name(call)
|
|
160
|
+
return call.name.to_s if call.receiver.nil?
|
|
161
|
+
call.receiver.is_a?(Prism::ConstantReadNode) ? "#{call.receiver.slice}.#{call.name}" : nil
|
|
162
|
+
end
|
|
163
|
+
|
|
103
164
|
# Every call inside a lane's block, however deeply nested: a call inside an `if`
|
|
104
165
|
# is still a call the lane may make, and the checklist cares about what could
|
|
105
166
|
# happen, not only about what always happens.
|
|
106
|
-
|
|
167
|
+
#
|
|
168
|
+
# A call to a method this Fastfile defines is followed into that method, so the
|
|
169
|
+
# actions it makes are the lane's actions too — which is what they are. `seen`
|
|
170
|
+
# is the cycle guard: two helpers that call each other must cost one walk, not
|
|
171
|
+
# a stack overflow inside a checklist that promised never to throw.
|
|
172
|
+
# The name of the environment variable this call reads, or nil.
|
|
173
|
+
#
|
|
174
|
+
# `ENV.fetch("X")`, `ENV["X"]` and `ENV.fetch("X") { … }` are the forms that
|
|
175
|
+
# occur. A computed name — `ENV[some_var]` — is not a question this can answer,
|
|
176
|
+
# and is left out rather than guessed at.
|
|
177
|
+
def env_read(call)
|
|
178
|
+
return nil unless call.receiver.is_a?(Prism::ConstantReadNode)
|
|
179
|
+
return nil unless call.receiver.slice == "ENV"
|
|
180
|
+
return nil unless %w[fetch []].include?(call.name.to_s)
|
|
181
|
+
|
|
182
|
+
first = call.arguments&.arguments&.first
|
|
183
|
+
first.is_a?(Prism::StringNode) ? first.unescaped : nil
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def calls_within(node, defs, seen = [], out = [], env = [])
|
|
187
|
+
return out if node.nil?
|
|
188
|
+
|
|
107
189
|
node.compact_child_nodes.each do |child|
|
|
108
|
-
if child.is_a?(Prism::CallNode)
|
|
109
|
-
|
|
190
|
+
if child.is_a?(Prism::CallNode)
|
|
191
|
+
# Only receiverless calls are reported: `UI.message` and `File.join` are
|
|
192
|
+
# Ruby, not fastlane, and a checklist reading them would be reading noise.
|
|
193
|
+
if child.receiver.nil?
|
|
194
|
+
out << { name: child.name.to_s, args: literal_args(child), given: arg_names(child) }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The exception to the receiverless rule above, because it is not noise: a
|
|
198
|
+
# variable the lane reads is a variable the run needs, and a run that meets
|
|
199
|
+
# an absent one either stops or builds the wrong thing. It is the only
|
|
200
|
+
# thing a Fastfile says about what the project requires from outside it.
|
|
201
|
+
name = env_read(child)
|
|
202
|
+
env << name if name
|
|
203
|
+
|
|
204
|
+
key = resolvable_name(child)
|
|
205
|
+
if key && defs.key?(key) && !seen.include?(key) && seen.length < MAX_CALL_DEPTH
|
|
206
|
+
calls_within(defs[key], defs, seen + [key], out, env)
|
|
207
|
+
end
|
|
110
208
|
end
|
|
111
|
-
calls_within(child, out)
|
|
209
|
+
calls_within(child, defs, seen, out, env)
|
|
112
210
|
end
|
|
113
211
|
out
|
|
114
212
|
end
|
|
115
213
|
|
|
214
|
+
# Does this Fastfile pull in lanes from somewhere else?
|
|
215
|
+
#
|
|
216
|
+
# `import` and `import_from_git` bring in another file's lanes, and no amount of
|
|
217
|
+
# reading this one will say what they call. Reported rather than resolved: the
|
|
218
|
+
# checklist turns it into "could not tell" instead of a tick, which is the whole
|
|
219
|
+
# difference between a checklist that is quiet and one that is wrong.
|
|
220
|
+
def imports_elsewhere?(source)
|
|
221
|
+
source.lines.any? { |line| line.match?(/^\s*import(_from_git)?[\s(]/) }
|
|
222
|
+
end
|
|
223
|
+
|
|
116
224
|
def collect_uses(fastfile_path)
|
|
117
|
-
|
|
225
|
+
source = File.read(fastfile_path)
|
|
226
|
+
result = Prism.parse(source)
|
|
118
227
|
raise "Fastfile could not be parsed" unless result.success?
|
|
119
228
|
|
|
229
|
+
defs = collect_defs(result.value)
|
|
120
230
|
lanes = []
|
|
121
231
|
walk = lambda do |node|
|
|
122
232
|
node.compact_child_nodes.each do |child|
|
|
123
233
|
if child.is_a?(Prism::CallNode) && %w[lane private_lane].include?(child.name.to_s)
|
|
124
234
|
name = child.arguments&.arguments&.first
|
|
235
|
+
env = []
|
|
236
|
+
actions = child.block ? calls_within(child.block, defs, [], [], env) : []
|
|
125
237
|
lanes << {
|
|
126
238
|
lane: name.is_a?(Prism::SymbolNode) ? name.unescaped : "?",
|
|
127
|
-
actions:
|
|
239
|
+
actions: actions,
|
|
240
|
+
env: env.uniq.sort
|
|
128
241
|
}
|
|
129
242
|
else
|
|
130
243
|
# Not a lane: keep descending, so a `platform :ios do` block is seen through.
|
|
@@ -133,7 +246,7 @@ def collect_uses(fastfile_path)
|
|
|
133
246
|
end
|
|
134
247
|
end
|
|
135
248
|
walk.call(result.value)
|
|
136
|
-
lanes
|
|
249
|
+
{ lanes: lanes, imports: imports_elsewhere?(source) }
|
|
137
250
|
end
|
|
138
251
|
|
|
139
252
|
case command
|
|
@@ -155,13 +268,13 @@ when "lanes"
|
|
|
155
268
|
respond({ ok: true, lanes: lanes })
|
|
156
269
|
when "uses"
|
|
157
270
|
begin
|
|
158
|
-
|
|
271
|
+
used = collect_uses(fastfile_path)
|
|
159
272
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
160
273
|
# Same shape as "lanes" above, and the same reason: parsing can raise
|
|
161
274
|
# anything, and `respond`'s `exit 0` must not be caught by this rescue.
|
|
162
275
|
fail_with("Could not parse the Fastfile: #{e.message}")
|
|
163
276
|
end
|
|
164
|
-
respond({ ok: true, lanes: lanes })
|
|
277
|
+
respond({ ok: true, lanes: used[:lanes], imports: used[:imports] })
|
|
165
278
|
else
|
|
166
279
|
fail_with("Unknown command: #{command.inspect}")
|
|
167
280
|
end
|