lively-electron 0.1.1 → 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
- checksums.yaml.gz.sig +0 -0
- data/bake/lively/electron/install.rb +22 -0
- data/bin/lively-electron +14 -1
- data/bin/lively-electron-server +1 -1
- data/context/getting-started.md +2 -2
- data/lib/lively/electron/environment.rb +10 -6
- data/lib/lively/electron/packager/generic.rb +98 -0
- data/lib/lively/electron/packager/npm.rb +44 -0
- data/lib/lively/electron/packager/pnpm.rb +61 -0
- data/lib/lively/electron/packager.rb +116 -0
- data/lib/lively/electron/version.rb +2 -1
- data/lib/lively/electron.rb +2 -0
- data/readme.md +5 -0
- data/releases.md +5 -0
- data/src/main.js +134 -134
- data.tar.gz.sig +0 -0
- metadata +7 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 268db954369a5f37db622d68406bb23be76ba11233dd518ac0cb27015624945f
|
|
4
|
+
data.tar.gz: 884719a50f2f34b8cdde22b694cdfceb2b206fde2bd478f6dcc1f42a159074e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a05c6e4d19c06836aba2b030b5bed3fa0294e4eaafeb161c0975784871c447804dbd890b419792f6b1899ab501a2491a8c13d6870ef397c0a4deec2f846c6a45
|
|
7
|
+
data.tar.gz: f188d608d575029e0894d7d5a70afce1f15d5ea07359069c47b30ffa08ca94533fdf1e92e108b48c4e7702bd7a0a57f84072b71afaf620d46801244280ea1d0e
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
# Creates +package.json+ in the project root with +pnpm init+ or +npm init+ when it is
|
|
7
|
+
# missing, then runs the appropriate install command for {Lively::Electron::Packager}.
|
|
8
|
+
def install
|
|
9
|
+
require "console"
|
|
10
|
+
require "lively/electron/packager"
|
|
11
|
+
|
|
12
|
+
root = context.root
|
|
13
|
+
package_json_path = File.join(root, "package.json")
|
|
14
|
+
packager = Lively::Electron::Packager.detect(root, ::ENV)
|
|
15
|
+
|
|
16
|
+
unless File.file?(package_json_path)
|
|
17
|
+
packager.setup!(root)
|
|
18
|
+
Console.info(self, "Created", package_json_path, "using", packager)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
packager.run_install_in!(root)
|
|
22
|
+
end
|
data/bin/lively-electron
CHANGED
|
@@ -8,5 +8,18 @@ if development_mode
|
|
|
8
8
|
ENV["CONSOLE_LEVEL"] = "debug"
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
require_relative "../lib/lively/electron/packager"
|
|
12
|
+
|
|
13
|
+
# Look for node_modules in the app’s working directory first; the published gem does not
|
|
14
|
+
# ship node_modules, so that is where users install electron. Fall back to the gem’s own
|
|
15
|
+
# directory for development/CI (e.g. pnpm in the repo root while cwd is examples/*).
|
|
16
|
+
project_root = File.expand_path(Dir.pwd)
|
|
17
|
+
gem_root = File.expand_path("..", __dir__)
|
|
18
|
+
packager = Lively::Electron::Packager.detect(project_root, ENV)
|
|
19
|
+
|
|
20
|
+
electron_path = Lively::Electron::Packager.resolve_electron_executable(
|
|
21
|
+
packager, [project_root, gem_root], ENV
|
|
22
|
+
)
|
|
23
|
+
|
|
11
24
|
main_js = File.join(__dir__, "..", "src", "main.js")
|
|
12
|
-
Process.exec(
|
|
25
|
+
Process.exec(electron_path, main_js, *ARGV)
|
data/bin/lively-electron-server
CHANGED
data/context/getting-started.md
CHANGED
|
@@ -10,10 +10,10 @@ Add the gem to your project:
|
|
|
10
10
|
$ bundle add lively-electron
|
|
11
11
|
~~~
|
|
12
12
|
|
|
13
|
-
Also install the
|
|
13
|
+
Also install the Node dependencies for the Electron binary (and create `package.json` if it is missing) using the task from the gem:
|
|
14
14
|
|
|
15
15
|
~~~ bash
|
|
16
|
-
$
|
|
16
|
+
$ bundle exec bake lively:electron:install
|
|
17
17
|
~~~
|
|
18
18
|
|
|
19
19
|
## Core Concepts
|
|
@@ -13,23 +13,27 @@ module Lively
|
|
|
13
13
|
module Electron
|
|
14
14
|
# @namespace
|
|
15
15
|
module Environment
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# This module provides server configuration for Electron apps, using TCP
|
|
19
|
-
# localhost binding for direct connection from Electron/Chromium.
|
|
16
|
+
# The Lively environment for a desktop Electron shell that connects to this process over a local HTTP server.
|
|
17
|
+
# The server uses TCP and may inherit a bound socket passed via `LIVELY_SERVER_DESCRIPTOR`.
|
|
20
18
|
module Application
|
|
21
19
|
include Lively::Environment::Application
|
|
22
20
|
|
|
21
|
+
# The base URL the Electron shell uses to reach the Lively server.
|
|
22
|
+
# Reads `LIVELY_URL` from the environment, or defaults to `http://localhost:0/`.
|
|
23
|
+
# @returns [String]
|
|
23
24
|
def url
|
|
24
|
-
"http://localhost:0/"
|
|
25
|
+
ENV.fetch("LIVELY_URL", "http://localhost:0/")
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# The HTTP endpoint the server listens on.
|
|
29
|
+
# When `LIVELY_SERVER_DESCRIPTOR` is set, reuses the inherited bound socket instead of binding a new one.
|
|
30
|
+
# @returns [Async::HTTP::Endpoint]
|
|
27
31
|
def endpoint
|
|
28
32
|
if descriptor = ENV["LIVELY_SERVER_DESCRIPTOR"]
|
|
29
33
|
Console.info(self, "Using inherited file descriptor.", descriptor: descriptor)
|
|
30
34
|
bound_socket = Socket.for_fd(descriptor.to_i)
|
|
31
35
|
|
|
32
|
-
# Ensure
|
|
36
|
+
# Ensure the inherited socket is non-blocking:
|
|
33
37
|
bound_socket.nonblock = true
|
|
34
38
|
|
|
35
39
|
endpoint = IO::Endpoint::BoundEndpoint.new(nil, [bound_socket])
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "lively"
|
|
7
|
+
|
|
8
|
+
# @namespace
|
|
9
|
+
module Lively
|
|
10
|
+
# @namespace
|
|
11
|
+
module Electron
|
|
12
|
+
# @namespace
|
|
13
|
+
module Packager
|
|
14
|
+
# Raised when {Packager.resolve_electron_executable} cannot produce a usable path from any candidate root, or when a custom {Generic#electron_executable_path} signals failure for every root.
|
|
15
|
+
class NotFoundError < StandardError; end
|
|
16
|
+
|
|
17
|
+
# A tool-agnostic Node project layout. Both {Npm} and {Pnpm} place CLI shims in `node_modules/.bin` after install. Subclasses implement the concrete {install_command} and {setup!} flows.
|
|
18
|
+
class Generic
|
|
19
|
+
# @attribute [String] Semver range for the `electron` dependency written by {setup!}.
|
|
20
|
+
ELECTRON_VERSION_RANGE = "^41.0.0"
|
|
21
|
+
|
|
22
|
+
# The `argv` for a top-level install with the current packager (e.g. `pnpm install` or `npm install`).
|
|
23
|
+
# @returns [Array(String)] The program name and arguments, ready for `Process.spawn`.
|
|
24
|
+
# @raises [NotImplementedError] When the concrete packager has not overridden this method.
|
|
25
|
+
# @abstract
|
|
26
|
+
def install_command
|
|
27
|
+
raise NotImplementedError, "#{self.class} must implement #install_command"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Runs {install_command} inside `package_root` (e.g. from a bake or CI task).
|
|
31
|
+
# @parameter package_root [String] The directory passed as `chdir` to the subprocess.
|
|
32
|
+
# @raises [RuntimeError] If the install command exits with a non-zero status.
|
|
33
|
+
def run_install_in!(package_root)
|
|
34
|
+
args = install_command
|
|
35
|
+
unless system(*args, chdir: File.expand_path(package_root), exception: false)
|
|
36
|
+
raise "Command failed: #{args.join(" ")} (in #{package_root})"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Creates a `package.json` when none exists using the tool's `init` flow, then adds {ELECTRON_VERSION_RANGE} as a dependency.
|
|
41
|
+
# @parameter package_root [String] The project directory to initialise.
|
|
42
|
+
# @raises [NotImplementedError] When the concrete packager has not overridden this method.
|
|
43
|
+
# @abstract
|
|
44
|
+
def setup!(package_root)
|
|
45
|
+
raise NotImplementedError, "#{self.class} must implement #setup!"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Resolves a filesystem path to the `electron` binary. A non-empty `ELECTRON` entry in `environment` wins outright; otherwise looks for an executable `node_modules/.bin/electron` shim under `package_root`.
|
|
49
|
+
# @parameter package_root [String] A directory that may contain a local `node_modules`.
|
|
50
|
+
# @parameter environment [Hash] The process environment. Defaults to `::ENV`.
|
|
51
|
+
# @returns [String] The absolute path to the `electron` binary.
|
|
52
|
+
# @raises [NotFoundError] If no usable binary is found under `package_root`.
|
|
53
|
+
def electron_executable_path(package_root, environment = ::ENV)
|
|
54
|
+
explicit = environment["ELECTRON"]&.to_s
|
|
55
|
+
if explicit && !explicit.empty?
|
|
56
|
+
return File.expand_path(explicit)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
path = local_electron_path(package_root)
|
|
60
|
+
return path if File.executable?(path)
|
|
61
|
+
|
|
62
|
+
raise NotFoundError, "Could not find electron in #{package_root}."
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A human-readable suggestion for running the install command in `root`. Useful when `electron` is absent from both `node_modules` and `PATH`.
|
|
66
|
+
# @parameter root [String] The path shown in the hint.
|
|
67
|
+
# @returns [String] A one-line string of the form `Run: ... in <absolute_path>`.
|
|
68
|
+
def install_hint(root)
|
|
69
|
+
"Run: #{install_command.join(' ')} in #{File.expand_path(root)}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# @returns [String] The expanded path to the `node_modules/.bin/electron` shim under `root`.
|
|
75
|
+
def local_electron_path(root)
|
|
76
|
+
root = File.expand_path(root)
|
|
77
|
+
bin = File.join(root, "node_modules", ".bin", "electron")
|
|
78
|
+
if Gem.win_platform? && !File.exist?(bin)
|
|
79
|
+
bin = File.join(root, "node_modules", ".bin", "electron.cmd")
|
|
80
|
+
end
|
|
81
|
+
File.expand_path(bin)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Runs a subprocess in `package_root` and raises on failure.
|
|
85
|
+
# @parameter argv [Array(String)] The program and its arguments.
|
|
86
|
+
# @parameter package_root [String] Working directory for the command.
|
|
87
|
+
# @parameter label [String] A short description used in failure messages.
|
|
88
|
+
# @raises [RuntimeError] If the process exits with a non-zero status.
|
|
89
|
+
def run_subprocess!(argv, package_root, label)
|
|
90
|
+
root = File.expand_path(package_root)
|
|
91
|
+
unless system(*argv, chdir: root, exception: false)
|
|
92
|
+
raise "Command failed: #{label} – #{argv.join(" ")} (in #{root})"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "generic"
|
|
7
|
+
|
|
8
|
+
# @namespace
|
|
9
|
+
module Lively
|
|
10
|
+
# @namespace
|
|
11
|
+
module Electron
|
|
12
|
+
# @namespace
|
|
13
|
+
module Packager
|
|
14
|
+
# An npm (`package-lock.json`) project layout with the corresponding `npm` command line.
|
|
15
|
+
class Npm < Generic
|
|
16
|
+
# @returns [String] The user-visible packager name, `"npm"`, used in messages and hints.
|
|
17
|
+
def to_s = "npm"
|
|
18
|
+
|
|
19
|
+
# @returns [Array(String)] `["npm", "install"]` for a full dependency install.
|
|
20
|
+
def install_command
|
|
21
|
+
%w[npm install]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Creates a `package.json` and installs {ELECTRON_VERSION_RANGE} when no manifest exists yet; see {Generic#setup!}.
|
|
25
|
+
# @parameter package_root [String] The project directory.
|
|
26
|
+
# @raises [RuntimeError] If any subprocess step fails.
|
|
27
|
+
def setup!(package_root)
|
|
28
|
+
manifest = File.join(File.expand_path(package_root), "package.json")
|
|
29
|
+
return if File.file?(manifest)
|
|
30
|
+
|
|
31
|
+
run_subprocess!(%w[npm init -y], package_root, "npm init -y")
|
|
32
|
+
run_subprocess!(
|
|
33
|
+
[
|
|
34
|
+
"npm", "install", "--save-prod",
|
|
35
|
+
"electron@#{ELECTRON_VERSION_RANGE}"
|
|
36
|
+
],
|
|
37
|
+
package_root,
|
|
38
|
+
"npm install electron"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "json"
|
|
7
|
+
require_relative "generic"
|
|
8
|
+
|
|
9
|
+
# @namespace
|
|
10
|
+
module Lively
|
|
11
|
+
# @namespace
|
|
12
|
+
module Electron
|
|
13
|
+
# @namespace
|
|
14
|
+
module Packager
|
|
15
|
+
# A pnpm (`pnpm-lock.yaml`) project layout with the corresponding `pnpm` command line.
|
|
16
|
+
class Pnpm < Generic
|
|
17
|
+
# @returns [String] The user-visible packager name, `"pnpm"`, used in messages and hints.
|
|
18
|
+
def to_s = "pnpm"
|
|
19
|
+
|
|
20
|
+
# @returns [Array(String)] `["pnpm", "install"]` for a full dependency install.
|
|
21
|
+
def install_command
|
|
22
|
+
%w[pnpm install]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Creates a `package.json` and installs {ELECTRON_VERSION_RANGE} when no manifest exists yet; see {Generic#setup!}. Also writes the `onlyBuiltDependencies` policy required by pnpm 10+.
|
|
26
|
+
# @parameter package_root [String] The project directory.
|
|
27
|
+
# @raises [RuntimeError] If any subprocess step fails.
|
|
28
|
+
def setup!(package_root)
|
|
29
|
+
manifest = File.join(File.expand_path(package_root), "package.json")
|
|
30
|
+
return if File.file?(manifest)
|
|
31
|
+
|
|
32
|
+
# pnpm 10 requires --bare and --init-package-manager (see `pnpm help init`):
|
|
33
|
+
run_subprocess!(%w[pnpm init --bare --init-package-manager], package_root, "pnpm init")
|
|
34
|
+
run_subprocess!(
|
|
35
|
+
[
|
|
36
|
+
"pnpm", "add",
|
|
37
|
+
"electron@#{ELECTRON_VERSION_RANGE}"
|
|
38
|
+
],
|
|
39
|
+
package_root,
|
|
40
|
+
"pnpm add electron"
|
|
41
|
+
)
|
|
42
|
+
merge_electron_postinstall_policy!(package_root)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Ensures `electron` is listed under `pnpm.onlyBuiltDependencies` in `package.json`. Without this entry, pnpm 10+ blocks `electron`'s `postinstall` script.
|
|
48
|
+
# @parameter package_root [String] The project directory containing `package.json`.
|
|
49
|
+
def merge_electron_postinstall_policy!(package_root)
|
|
50
|
+
path = File.join(File.expand_path(package_root), "package.json")
|
|
51
|
+
data = JSON.parse(File.read(path, encoding: Encoding::UTF_8))
|
|
52
|
+
pnode = data["pnpm"] || {}
|
|
53
|
+
list = pnode["onlyBuiltDependencies"] || []
|
|
54
|
+
pnode["onlyBuiltDependencies"] = (["electron"] + Array(list)).uniq
|
|
55
|
+
data["pnpm"] = pnode
|
|
56
|
+
File.write(path, JSON.pretty_generate(data) + "\n")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
require_relative "packager/generic"
|
|
9
|
+
require_relative "packager/npm"
|
|
10
|
+
require_relative "packager/pnpm"
|
|
11
|
+
|
|
12
|
+
# @namespace
|
|
13
|
+
module Lively
|
|
14
|
+
# @namespace
|
|
15
|
+
module Electron
|
|
16
|
+
# Detects the Node packager in use (npm or pnpm) and resolves the `electron` binary path. Both tools place CLI shims in `node_modules/.bin` after install; see {Generic#electron_executable_path}.
|
|
17
|
+
module Packager
|
|
18
|
+
# @attribute [String] Environment variable for explicitly selecting `npm` or `pnpm`.
|
|
19
|
+
ENV_KEY = "LIVELY_ELECTRON_PACKAGER"
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# Picks a packager for a project root.
|
|
23
|
+
#
|
|
24
|
+
# Resolution order:
|
|
25
|
+
# 1. `LIVELY_ELECTRON_PACKAGER` env var with value `npm` or `pnpm` (case-insensitive);
|
|
26
|
+
# 2. `package.json` `packageManager` field (e.g. `pnpm@10.0.0` or `npm@10.0.0`);
|
|
27
|
+
# 3. a lone lock file (`pnpm-lock.yaml` or `package-lock.json`);
|
|
28
|
+
# 4. if both lock files are present, `pnpm` (matches this gem's default);
|
|
29
|
+
# 5. if there is no clear signal, `pnpm`.
|
|
30
|
+
#
|
|
31
|
+
# @parameter package_root [String] The directory to search for `package.json` and lock files.
|
|
32
|
+
# @parameter environment [Hash] The process environment. Defaults to `::ENV`.
|
|
33
|
+
# @returns [Lively::Electron::Packager::Npm | Lively::Electron::Packager::Pnpm] A concrete {Npm} or {Pnpm} instance.
|
|
34
|
+
# @raises [ArgumentError] If `LIVELY_ELECTRON_PACKAGER` is set to something other than `npm` or `pnpm`.
|
|
35
|
+
def detect(package_root, environment = ::ENV)
|
|
36
|
+
root = File.expand_path(package_root)
|
|
37
|
+
|
|
38
|
+
if (resolved = from_environment(environment[ENV_KEY]))
|
|
39
|
+
return resolved
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if (resolved = from_package_json(root))
|
|
43
|
+
return resolved
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
has_pnpm = File.file?(File.join(root, "pnpm-lock.yaml"))
|
|
47
|
+
has_npm = File.file?(File.join(root, "package-lock.json"))
|
|
48
|
+
|
|
49
|
+
if has_pnpm && has_npm
|
|
50
|
+
return Pnpm.new
|
|
51
|
+
elsif has_pnpm
|
|
52
|
+
return Pnpm.new
|
|
53
|
+
elsif has_npm
|
|
54
|
+
return Npm.new
|
|
55
|
+
else
|
|
56
|
+
return Pnpm.new
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Calls {Generic#electron_executable_path} for each of `search_roots` in order (e.g. the app's working directory, then the gem root in development). The first concrete path wins. If no root yields a local binary, falls back to the bare program name `"electron"` for the OS to find on `PATH`.
|
|
61
|
+
# @parameter packager [Lively::Electron::Packager::Generic] A concrete {Npm} or {Pnpm} instance.
|
|
62
|
+
# @parameter search_roots [Array(String)] Candidate directories; first match wins.
|
|
63
|
+
# @parameter environment [Hash] The process environment. Defaults to `::ENV`.
|
|
64
|
+
# @returns [String] An absolute path, or `"electron"` to resolve via `PATH`.
|
|
65
|
+
def resolve_electron_executable(packager, search_roots, environment = ::ENV)
|
|
66
|
+
search_roots
|
|
67
|
+
.compact
|
|
68
|
+
.map {|path| File.expand_path(path)}
|
|
69
|
+
.uniq
|
|
70
|
+
.each do |search_root|
|
|
71
|
+
begin
|
|
72
|
+
return packager.electron_executable_path(search_root, environment)
|
|
73
|
+
rescue NotFoundError
|
|
74
|
+
next
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# No local binary found in any root; rely on the OS to find `electron` on PATH:
|
|
79
|
+
"electron"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def from_environment(value)
|
|
85
|
+
value = value&.to_s&.strip
|
|
86
|
+
return nil if value.nil? || value.empty?
|
|
87
|
+
|
|
88
|
+
case value.downcase
|
|
89
|
+
when "npm" then Npm.new
|
|
90
|
+
when "pnpm" then Pnpm.new
|
|
91
|
+
else
|
|
92
|
+
raise ArgumentError, "Invalid #{ENV_KEY} #{value.inspect} (use 'npm' or 'pnpm')."
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def from_package_json(root)
|
|
97
|
+
package_json = read_package_json(root) or return nil
|
|
98
|
+
package_manager = package_json["packageManager"].to_s
|
|
99
|
+
return Pnpm.new if package_manager.start_with?("pnpm@")
|
|
100
|
+
return Npm.new if package_manager.start_with?("npm@")
|
|
101
|
+
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def read_package_json(root)
|
|
106
|
+
path = File.join(root, "package.json")
|
|
107
|
+
return nil unless File.readable?(path)
|
|
108
|
+
|
|
109
|
+
::JSON.parse(File.read(path, encoding: Encoding::UTF_8))
|
|
110
|
+
rescue ::JSON::ParserError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/lively/electron.rb
CHANGED
data/readme.md
CHANGED
|
@@ -14,6 +14,11 @@ Please see the [project documentation](https://github.com/socketry/lively-electr
|
|
|
14
14
|
|
|
15
15
|
Please see the [project releases](https://github.com/socketry/lively-electron/releases/index) for all releases.
|
|
16
16
|
|
|
17
|
+
### v0.2.0
|
|
18
|
+
|
|
19
|
+
- Add support for `pnpm` as well as `npm`.
|
|
20
|
+
- Add `bake lively:electron:install` that installs electron locally.
|
|
21
|
+
|
|
17
22
|
### v0.1.0
|
|
18
23
|
|
|
19
24
|
- Initial implementation.
|
data/releases.md
CHANGED
data/src/main.js
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
1
|
const {app, BrowserWindow, ipcMain} = require('electron');
|
|
2
|
-
const {spawn} = require('child_process');
|
|
2
|
+
const {spawn, spawnSync} = require('child_process');
|
|
3
3
|
const http = require('http');
|
|
4
4
|
const net = require('net');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
|
|
8
8
|
class LivelyElectronApp {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
9
|
+
constructor() {
|
|
10
|
+
this.rubyProcess = null;
|
|
11
|
+
this.mainWindow = null;
|
|
12
|
+
this.serverUrl = null;
|
|
13
|
+
|
|
14
|
+
// Store the working directory (should be preserved by direct CLI)
|
|
15
|
+
this.originalCwd = process.cwd();
|
|
16
|
+
console.log('💾 Working directory:', this.originalCwd);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
isDevelopment() {
|
|
20
|
+
const variant = process.env.LIVELY_VARIANT || process.env.VARIANT;
|
|
21
|
+
return variant === "development";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async start() {
|
|
25
|
+
try {
|
|
26
|
+
// 1. Start Ruby Lively server on TCP localhost
|
|
27
|
+
await this.startLivelyServer();
|
|
28
|
+
|
|
29
|
+
// 2. Create Electron window (connects directly to Ruby server)
|
|
30
|
+
await this.createWindow();
|
|
31
|
+
|
|
32
|
+
console.log(`Lively Electron started - Ruby server: ${this.serverUrl}`);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Failed to start Lively Electron:', error);
|
|
35
|
+
app.quit();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async startLivelyServer() {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
// Forward all CLI args to the Ruby server; let the Ruby script decide the application file
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
|
|
44
|
+
// Create server and let it bind+listen, then pass handle to child
|
|
45
|
+
const server = net.createServer();
|
|
46
|
+
server.listen(0, '127.0.0.1', () => {
|
|
47
|
+
const port = server.address().port;
|
|
48
|
+
this.serverUrl = `http://localhost:${port}`;
|
|
49
|
+
|
|
50
|
+
const fd = server._handle.fd;
|
|
51
|
+
|
|
52
|
+
// Start Ruby process with FD passed via stdio and forward all CLI args
|
|
53
|
+
const livelyElectronScript = path.join(__dirname, '..', 'bin', 'lively-electron-server');
|
|
54
|
+
const childArgs = args.slice();
|
|
55
|
+
|
|
56
|
+
const child = spawn(livelyElectronScript, childArgs, {
|
|
57
|
+
stdio: [
|
|
58
|
+
'inherit', // stdin
|
|
59
|
+
'inherit', // stdout
|
|
60
|
+
'inherit', // stderr
|
|
61
|
+
fd // Pass socket FD as stdio[3]
|
|
62
|
+
],
|
|
63
|
+
env: {
|
|
64
|
+
...process.env,
|
|
65
|
+
LIVELY_SERVER_DESCRIPTOR: '3' // Tell child it's on FD 3
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
child.on('spawn', () => {
|
|
70
|
+
this.rubyProcess = child;
|
|
71
|
+
server.close((error) => {
|
|
72
|
+
if (error) {
|
|
73
|
+
reject(error);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(`✅ Ruby server should be ready: ${this.serverUrl}`);
|
|
76
|
+
resolve();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
child.on('error', (error) => {
|
|
82
|
+
server.close();
|
|
83
|
+
console.error('Failed to spawn Ruby process:', error);
|
|
84
|
+
reject(error);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
child.on('close', (code) => {
|
|
88
|
+
this.rubyProcess = null;
|
|
89
|
+
console.log(`Ruby process exited with code ${code}`);
|
|
90
|
+
if (code !== 0) {
|
|
91
|
+
reject(new Error(`Ruby process failed with code ${code}`));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async createWindow() {
|
|
99
|
+
this.mainWindow = new BrowserWindow({
|
|
100
|
+
width: 1200,
|
|
101
|
+
height: 800,
|
|
102
|
+
webPreferences: {
|
|
103
|
+
nodeIntegration: false,
|
|
104
|
+
contextIsolation: true,
|
|
105
|
+
enableRemoteModule: false
|
|
106
|
+
},
|
|
107
|
+
titleBarStyle: 'hiddenInset'
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Load the Lively app directly
|
|
111
|
+
await this.mainWindow.loadURL(this.serverUrl);
|
|
112
|
+
|
|
113
|
+
// Open DevTools in development mode
|
|
114
|
+
if (this.isDevelopment()) {
|
|
115
|
+
this.mainWindow.webContents.openDevTools();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.mainWindow.on('closed', () => {
|
|
119
|
+
this.cleanup();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
cleanup() {
|
|
124
|
+
if (this.rubyProcess) {
|
|
125
|
+
this.rubyProcess.kill();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// Electron app lifecycle
|
|
131
131
|
app.whenReady().then(() => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
console.log('Electron ready, starting Lively app...');
|
|
133
|
+
const livelyApp = new LivelyElectronApp();
|
|
134
|
+
livelyApp.start().catch(error => {
|
|
135
|
+
console.error('Failed to start Lively app:', error);
|
|
136
|
+
app.quit();
|
|
137
|
+
});
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
app.on('window-all-closed', () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
if (process.platform !== 'darwin') {
|
|
142
|
+
app.quit();
|
|
143
|
+
}
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
app.on('activate', () => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
148
|
+
const livelyApp = new LivelyElectronApp();
|
|
149
|
+
livelyApp.start();
|
|
150
|
+
}
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
app.on('before-quit', () => {
|
|
154
|
-
|
|
154
|
+
// Cleanup will be handled by window close event
|
|
155
155
|
});
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lively-electron
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -100,12 +100,17 @@ executables:
|
|
|
100
100
|
extensions: []
|
|
101
101
|
extra_rdoc_files: []
|
|
102
102
|
files:
|
|
103
|
+
- bake/lively/electron/install.rb
|
|
103
104
|
- bin/lively-electron
|
|
104
105
|
- bin/lively-electron-server
|
|
105
106
|
- context/getting-started.md
|
|
106
107
|
- context/index.yaml
|
|
107
108
|
- lib/lively/electron.rb
|
|
108
109
|
- lib/lively/electron/environment.rb
|
|
110
|
+
- lib/lively/electron/packager.rb
|
|
111
|
+
- lib/lively/electron/packager/generic.rb
|
|
112
|
+
- lib/lively/electron/packager/npm.rb
|
|
113
|
+
- lib/lively/electron/packager/pnpm.rb
|
|
109
114
|
- lib/lively/electron/version.rb
|
|
110
115
|
- license.md
|
|
111
116
|
- readme.md
|
|
@@ -132,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
132
137
|
- !ruby/object:Gem::Version
|
|
133
138
|
version: '0'
|
|
134
139
|
requirements: []
|
|
135
|
-
rubygems_version:
|
|
140
|
+
rubygems_version: 4.0.6
|
|
136
141
|
specification_version: 4
|
|
137
142
|
summary: Electron wrapper for Lively Ruby applications
|
|
138
143
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|