laneyard 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -0
  3. package/dist/src/cli/add.js +81 -0
  4. package/dist/src/cli/detect.js +70 -0
  5. package/dist/src/config/load.js +46 -0
  6. package/dist/src/config/resolve.js +29 -0
  7. package/dist/src/config/schema.js +55 -0
  8. package/dist/src/config/store.js +71 -0
  9. package/dist/src/db/cache.js +22 -0
  10. package/dist/src/db/open.js +12 -0
  11. package/dist/src/db/runs.js +97 -0
  12. package/dist/src/db/schema.sql +45 -0
  13. package/dist/src/git/workspace.js +106 -0
  14. package/dist/src/heuristics/error-summary.js +43 -0
  15. package/dist/src/logs/store.js +66 -0
  16. package/dist/src/main.js +124 -0
  17. package/dist/src/runner/artifacts.js +59 -0
  18. package/dist/src/runner/live-steps.js +38 -0
  19. package/dist/src/runner/orchestrate.js +140 -0
  20. package/dist/src/runner/pty.js +83 -0
  21. package/dist/src/runner/report.js +86 -0
  22. package/dist/src/server/app.js +84 -0
  23. package/dist/src/server/auth.js +81 -0
  24. package/dist/src/server/routes/projects.js +35 -0
  25. package/dist/src/server/routes/runs.js +100 -0
  26. package/dist/src/server/ws.js +66 -0
  27. package/dist/src/sidecar/bridge.js +44 -0
  28. package/dist/src/sidecar/lanes.js +40 -0
  29. package/dist/src/sidecar/ruby-env.js +63 -0
  30. package/dist/tests/cli/add.test.js +64 -0
  31. package/dist/tests/cli/detect.test.js +63 -0
  32. package/dist/tests/config/load.test.js +68 -0
  33. package/dist/tests/config/resolve.test.js +44 -0
  34. package/dist/tests/config/store.test.js +58 -0
  35. package/dist/tests/db/runs.test.js +54 -0
  36. package/dist/tests/e2e/full-thread.test.js +65 -0
  37. package/dist/tests/fixtures/repos.js +30 -0
  38. package/dist/tests/git/workspace.test.js +66 -0
  39. package/dist/tests/heuristics/error-summary.test.js +31 -0
  40. package/dist/tests/logs/store.test.js +38 -0
  41. package/dist/tests/main.test.js +27 -0
  42. package/dist/tests/ruby/introspect.test.js +59 -0
  43. package/dist/tests/runner/artifacts.test.js +49 -0
  44. package/dist/tests/runner/live-steps.test.js +35 -0
  45. package/dist/tests/runner/orchestrate.test.js +124 -0
  46. package/dist/tests/runner/pty.test.js +53 -0
  47. package/dist/tests/runner/report.test.js +91 -0
  48. package/dist/tests/server/api.test.js +132 -0
  49. package/dist/tests/server/auth.test.js +53 -0
  50. package/dist/tests/server/ws.test.js +43 -0
  51. package/dist/tests/sidecar/lanes.test.js +52 -0
  52. package/dist/tests/sidecar/ruby-env.test.js +25 -0
  53. package/dist/tests/smoke.test.js +7 -0
  54. package/dist/web/assets/index-583x0xuo.css +1 -0
  55. package/dist/web/assets/index-BEpABKPS.js +61 -0
  56. package/dist/web/index.html +13 -0
  57. package/package.json +70 -0
  58. package/ruby/introspect.rb +87 -0
  59. package/scripts/fix-node-pty-permissions.mjs +26 -0
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>laneyard</title>
7
+ <script type="module" crossorigin src="/assets/index-BEpABKPS.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-583x0xuo.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "laneyard",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "laneyard": "dist/src/main.js"
7
+ },
8
+ "engines": {
9
+ "node": ">=22"
10
+ },
11
+ "scripts": {
12
+ "dev": "tsx watch src/main.ts",
13
+ "dev:web": "vite --config web/vite.config.ts",
14
+ "build:web": "vite build --config web/vite.config.ts",
15
+ "build": "tsc -p tsconfig.json && cp src/db/schema.sql dist/src/db/ && npm run build:web",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest",
18
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p web/tsconfig.json --noEmit",
19
+ "postinstall": "node scripts/fix-node-pty-permissions.mjs",
20
+ "prepare": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@fastify/cookie": "^11.0.0",
24
+ "@fastify/static": "^8.0.0",
25
+ "@fastify/websocket": "^11.0.0",
26
+ "better-sqlite3": "^11.7.0",
27
+ "fastify": "^5.2.0",
28
+ "node-pty": "^1.0.0",
29
+ "react": "^19.2.7",
30
+ "react-dom": "^19.2.7",
31
+ "react-router-dom": "^7.18.1",
32
+ "tinyglobby": "^0.2.10",
33
+ "yaml": "^2.7.0",
34
+ "zod": "^3.24.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/better-sqlite3": "^7.6.12",
38
+ "@types/node": "^22.10.0",
39
+ "@types/react": "^19.2.17",
40
+ "@types/react-dom": "^19.2.3",
41
+ "@vitejs/plugin-react": "^4.7.0",
42
+ "tsx": "^4.19.0",
43
+ "typescript": "^5.7.0",
44
+ "vite": "^5.4.21",
45
+ "vitest": "^2.1.0"
46
+ },
47
+ "license": "MIT",
48
+ "description": "A self-hosted web UI for fastlane: trigger lanes from your browser, watch builds stream live, keep your artifacts and your signing keys.",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "https://github.com/martinfrouin/laneyard.git"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "ruby",
56
+ "scripts",
57
+ "README.md",
58
+ "LICENSE"
59
+ ],
60
+ "keywords": [
61
+ "fastlane",
62
+ "ci",
63
+ "self-hosted",
64
+ "ios",
65
+ "android",
66
+ "mobile",
67
+ "build-server",
68
+ "testflight"
69
+ ]
70
+ }
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Laneyard's introspection sidecar.
5
+ #
6
+ # Launched in a project's folder — ideally via `bundle exec` — it's the only
7
+ # component that knows fastlane. It never writes anything: it reads and returns
8
+ # JSON on standard output.
9
+ #
10
+ # ruby introspect.rb lanes --fastlane-dir fastlane
11
+ # ruby introspect.rb actions --fastlane-dir fastlane
12
+ # ruby introspect.rb parse --fastlane-dir fastlane
13
+ #
14
+ # The output contract is constant: { "ok": true, ... } or { "ok": false, "error": "..." }.
15
+ # An error is a valid response, never a trace on stderr.
16
+
17
+ require "json"
18
+
19
+ # See below: the real standard output is set aside right from the start so
20
+ # that nothing but our JSON can slip into it.
21
+ REAL_STDOUT = $stdout.dup
22
+
23
+ def respond(payload)
24
+ REAL_STDOUT.puts JSON.generate(payload)
25
+ REAL_STDOUT.flush
26
+ exit 0
27
+ end
28
+
29
+ def fail_with(message)
30
+ respond({ ok: false, error: message.to_s })
31
+ end
32
+
33
+ command = ARGV[0]
34
+ dir_index = ARGV.index("--fastlane-dir")
35
+ fastlane_dir = dir_index ? ARGV[dir_index + 1] : "fastlane"
36
+ fastfile_path = File.join(Dir.pwd, fastlane_dir, "Fastfile")
37
+
38
+ fail_with("Fastfile not found: #{fastfile_path}") unless File.exist?(fastfile_path)
39
+
40
+ # fastlane readily writes to standard output — plugin warnings, deprecation
41
+ # messages, update banner. Just one of these messages would corrupt the JSON
42
+ # the caller expects. Everything therefore goes to standard error, and only
43
+ # `respond` writes to the real output.
44
+ $stdout = $stderr
45
+
46
+ begin
47
+ require "fastlane"
48
+ rescue LoadError => e
49
+ fail_with("fastlane is not available in this Ruby environment (#{e.message})")
50
+ end
51
+
52
+ def collect_lanes(fastfile_path)
53
+ ff = Fastlane::FastFile.new(fastfile_path)
54
+ lanes = []
55
+ ff.runner.lanes.each do |platform, platform_lanes|
56
+ platform_lanes.each do |name, lane|
57
+ lanes << {
58
+ name: name.to_s,
59
+ platform: platform&.to_s,
60
+ description: Array(lane.description).join(" ").strip,
61
+ private: lane.is_private
62
+ }
63
+ end
64
+ end
65
+ lanes
66
+ end
67
+
68
+ case command
69
+ when "lanes"
70
+ begin
71
+ lanes = collect_lanes(fastfile_path)
72
+ rescue Exception => e # rubocop:disable Lint/RescueException
73
+ # A Fastfile is arbitrary Ruby: loading it can raise anything at all,
74
+ # including syntax errors that don't descend from StandardError.
75
+ #
76
+ # Careful: the success case's `respond` must stay OUTSIDE this
77
+ # begin/rescue. `respond` ends with `exit 0`, which raises SystemExit —
78
+ # a subclass of Exception, so caught by this `rescue Exception` if the
79
+ # call is made inside. The symptom is a second JSON blob (the "exit"
80
+ # error) written right after the first, which corrupts the output on
81
+ # the caller's side.
82
+ fail_with("Could not load the Fastfile: #{e.message}")
83
+ end
84
+ respond({ ok: true, lanes: lanes })
85
+ else
86
+ fail_with("Unknown command: #{command.inspect}")
87
+ end
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ // npm dépose parfois le spawn-helper de node-pty sans droit d'exécution, ce qui
3
+ // fait échouer tout lancement de processus avec un message opaque. On répare au
4
+ // lieu de laisser chacun le découvrir.
5
+ import { chmod, readdir, stat } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+
8
+ const root = "node_modules/node-pty/prebuilds";
9
+
10
+ try {
11
+ for (const dir of await readdir(root)) {
12
+ const helper = join(root, dir, "spawn-helper");
13
+ try {
14
+ const info = await stat(helper);
15
+ // 0o111 : au moins un bit d'exécution.
16
+ if ((info.mode & 0o111) === 0) {
17
+ await chmod(helper, 0o755);
18
+ console.log(`node-pty : droit d'exécution rendu à ${helper}`);
19
+ }
20
+ } catch {
21
+ // Pas de helper dans ce dossier : rien à faire.
22
+ }
23
+ }
24
+ } catch {
25
+ // node-pty absent ou sans prebuilds : l'installation n'a pas à échouer pour autant.
26
+ }