electron-cli 0.3.0-alpha.0 → 0.3.0-alpha.10

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/Cargo.toml CHANGED
@@ -1,14 +1,23 @@
1
1
  [package]
2
2
  name = "electron-cli"
3
- version = "0.3.0-alpha.0"
3
+ version = "0.3.0-alpha.10"
4
4
  edition = "2021"
5
5
  description = "Experimental Rust CLI for Electron project diagnostics and workflow automation"
6
6
  license = "MIT"
7
- repository = "https://github.com/roderik/electron-cli"
7
+ repository = "https://github.com/Ikana/electron-cli"
8
8
 
9
9
  [dependencies]
10
10
  anyhow = "1.0"
11
+ apple-dmg = "0.5"
11
12
  camino = { version = "1.1", features = ["serde1"] }
12
13
  clap = { version = "4.6", features = ["derive"] }
14
+ fatfs = "0.3"
15
+ flate2 = { version = "1.1", default-features = false, features = ["rust_backend"] }
16
+ fscommon = "0.1"
17
+ md5 = "0.7"
18
+ plist = "1"
19
+ rpm = { version = "0.24", default-features = false, features = ["payload", "gzip-compression"] }
13
20
  serde = { version = "1.0", features = ["derive"] }
14
21
  serde_json = "1.0"
22
+ tar = "0.4"
23
+ zip = { version = "8.6.0", default-features = false, features = ["deflate-flate2-zlib-rs"] }
package/README.md CHANGED
@@ -6,7 +6,7 @@ This is an independent learning project. It is not affiliated with Electron, Ele
6
6
 
7
7
  ## Status
8
8
 
9
- This repository is intentionally small and public-learning friendly. The first useful surface area is inspection and diagnostics, because those commands are valuable for humans and easy for agents to consume safely.
9
+ This repository is intentionally small and public-learning friendly. The first useful surface area is inspection and diagnostics, because those commands are valuable for humans and easy for agents to consume safely. The next surface area is a Rust-owned version of the main Electron Forge flow: initialize, start, package, make, and eventually publish.
10
10
 
11
11
  Current commands:
12
12
 
@@ -14,31 +14,72 @@ Current commands:
14
14
  electron-cli inspect
15
15
  electron-cli doctor
16
16
  electron-cli plan
17
+ electron-cli init my-app
18
+ electron-cli start
19
+ electron-cli package
20
+ electron-cli make
21
+ electron-cli publish
17
22
  electron-cli inspect --json
18
23
  electron-cli doctor --json
19
24
  electron-cli plan --json
25
+ electron-cli init my-app --dry-run --json
26
+ electron-cli start --dry-run --json
27
+ electron-cli package --dry-run --json
28
+ electron-cli make --dry-run --json
29
+ electron-cli publish --dry-run --json
20
30
  ```
21
31
 
22
32
  Planned commands:
23
33
 
24
34
  ```sh
25
- electron-cli dev
26
- electron-cli init
27
- electron-cli package
28
- electron-cli make
35
+ electron-cli publish --publisher github
29
36
  ```
30
37
 
31
- The planned workflow commands may start by wrapping Electron Forge or other established tools. Rust-native implementations can replace narrow pieces over time when there is a clear reason.
38
+ The default `init` template is `minimal`, a built-in starter written by this project. Non-native template names are still passed to `create-electron-app` as an escape hatch while this project grows.
39
+
40
+ The Rust-native flow currently owns:
41
+
42
+ - `init --template minimal`: writes a local Electron starter without Electron Forge.
43
+ - `start`: launches the installed Electron runtime directly.
44
+ - `package`: copies the installed Electron runtime, app files, installed production dependency closure, app metadata, macOS icon, and extra resources into a local app bundle for the current platform and architecture.
45
+ - `make`: runs `package` and writes a distributable under `out/make/<target>/<platform>/<arch>/`; ZIP works on all platforms, `--target dmg` writes a basic macOS disk image, and `--target deb` / `--target rpm` write Linux packages.
46
+ - `publish`: runs `make` and publishes the distributable to a local directory with a manifest.
47
+
48
+ Remote publishers such as GitHub Releases are not implemented yet. The DMG maker is currently a pure-Rust FAT32 image with the app bundle and an Applications entry; HFS+/APFS layout customization, Windows installers, Windows/Linux icon embedding, signing, and notarization are still TODO.
49
+
50
+ Package metadata can be configured in `package.json`:
51
+
52
+ ```json
53
+ {
54
+ "productName": "My App",
55
+ "electronCli": {
56
+ "packagerConfig": {
57
+ "appBundleId": "com.example.my-app",
58
+ "appCategoryType": "public.app-category.developer-tools",
59
+ "icon": "assets/icon",
60
+ "extraResource": "assets/config.json"
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ The package command also reads JSON-shaped `config.forge.packagerConfig` and `electronPackagerConfig` entries for the same fields. JavaScript Forge config files are not evaluated.
32
67
 
33
68
  ## Install
34
69
 
35
- During the experimental phase, the npm package runs from Rust source when a prebuilt binary is not available. You need Node.js and Rust installed.
70
+ During the experimental phase, the npm package downloads a prebuilt binary from GitHub Releases when one is available. If a prebuilt binary is not available for your platform, it falls back to running from Rust source.
36
71
 
37
72
  ```sh
38
- npm install -g electron-cli
73
+ npm install -g electron-cli@alpha
39
74
  electron-cli doctor
40
75
  ```
41
76
 
77
+ To skip binary download and use the Cargo fallback:
78
+
79
+ ```sh
80
+ ELECTRON_CLI_SKIP_DOWNLOAD=1 npm install -g electron-cli@alpha
81
+ ```
82
+
42
83
  For local development:
43
84
 
44
85
  ```sh
@@ -53,6 +94,14 @@ Or use Cargo directly:
53
94
  ```sh
54
95
  cargo run -- doctor
55
96
  cargo run -- inspect --json
97
+ cargo run -- init my-app
98
+ cargo run -- start --dry-run
99
+ cargo run -- package --dry-run
100
+ cargo run -- make --dry-run
101
+ cargo run -- make --target dmg --dry-run
102
+ cargo run -- make --target deb --dry-run
103
+ cargo run -- make --target rpm --dry-run
104
+ cargo run -- publish --dry-run
56
105
  ```
57
106
 
58
107
  ## Design Goals
@@ -60,7 +109,7 @@ cargo run -- inspect --json
60
109
  - Learn Rust through a real developer tool.
61
110
  - Make Electron project state easy to inspect.
62
111
  - Prefer structured output for agentic workflows.
63
- - Wrap proven ecosystem tools before replacing them.
112
+ - Replace the main Forge-style app flow with narrow Rust-owned pieces.
64
113
  - Keep the project clearly independent and experimental.
65
114
 
66
115
  ## Non-Goals
@@ -71,8 +120,13 @@ cargo run -- inspect --json
71
120
 
72
121
  ## JSON Output
73
122
 
74
- Both initial commands support `--json` so agents and scripts can consume project state without scraping terminal output.
123
+ The inspection and planning commands support `--json` so agents and scripts can consume project state without scraping terminal output.
75
124
  `plan` is designed around that workflow: it recommends stable commands and reports missing project conventions as structured data.
125
+ `init --dry-run --json` shows whether the CLI will write native template files or delegate to `create-electron-app`.
126
+ `start --dry-run --json` shows the Electron executable that will be launched.
127
+ `package --dry-run --json` shows the runtime, app file, metadata, icon, and extra-resource copy plan.
128
+ `make --dry-run --json` shows the package prerequisite and selected maker artifact path.
129
+ `publish --dry-run --json` shows the make prerequisite, destination artifact, and manifest path.
76
130
 
77
131
  ```sh
78
132
  electron-cli plan --json
@@ -7,11 +7,14 @@ const path = require("node:path");
7
7
  const root = path.resolve(__dirname, "..");
8
8
  const exe = process.platform === "win32" ? "electron-cli.exe" : "electron-cli";
9
9
  const args = process.argv.slice(2);
10
+ const envBinary = process.env.ELECTRON_CLI_BINARY;
10
11
 
11
12
  const candidates = [
13
+ envBinary,
14
+ path.join(root, "bin", "downloaded", exe),
12
15
  path.join(root, "target", "release", exe),
13
16
  path.join(root, "target", "debug", exe),
14
- ];
17
+ ].filter(Boolean);
15
18
 
16
19
  const binary = candidates.find((candidate) => fs.existsSync(candidate));
17
20
 
@@ -28,8 +31,8 @@ if (!fs.existsSync(manifest)) {
28
31
  process.exit(1);
29
32
  }
30
33
 
31
- console.error("electron-cli is experimental and needs Rust to run from npm source packages.");
32
- console.error("Building/running through cargo; install Rust from https://rustup.rs if this fails.");
34
+ console.error("electron-cli could not find a prebuilt binary for this install.");
35
+ console.error("Building/running through Cargo fallback; install Rust from https://rustup.rs if this fails.");
33
36
 
34
37
  exitWith(
35
38
  spawnSync(cargo, ["run", "--quiet", "--manifest-path", manifest, "--", ...args], {
package/package.json CHANGED
@@ -1,22 +1,24 @@
1
1
  {
2
2
  "name": "electron-cli",
3
- "version": "0.3.0-alpha.0",
3
+ "version": "0.3.0-alpha.10",
4
4
  "description": "Experimental Rust CLI for Electron project diagnostics and workflow automation",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/roderik/electron-cli.git"
8
+ "url": "git+https://github.com/Ikana/electron-cli.git"
9
9
  },
10
- "homepage": "https://github.com/roderik/electron-cli#readme",
10
+ "homepage": "https://github.com/Ikana/electron-cli#readme",
11
11
  "bugs": {
12
- "url": "https://github.com/roderik/electron-cli/issues"
12
+ "url": "https://github.com/Ikana/electron-cli/issues"
13
13
  },
14
14
  "bin": {
15
15
  "electron-cli": "bin/electron-cli.js"
16
16
  },
17
17
  "files": [
18
- "bin",
18
+ "bin/electron-cli.js",
19
+ "scripts",
19
20
  "src",
21
+ "templates",
20
22
  "tests",
21
23
  "Cargo.toml",
22
24
  "Cargo.lock",
@@ -30,6 +32,7 @@
30
32
  "format": "cargo fmt",
31
33
  "lint": "cargo fmt --check && cargo clippy --all-targets -- -D warnings",
32
34
  "test": "cargo test",
35
+ "postinstall": "node scripts/install.js",
33
36
  "prepack": "cargo build --release"
34
37
  },
35
38
  "engines": {
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const https = require("node:https");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const { spawnSync } = require("node:child_process");
8
+
9
+ const root = path.resolve(__dirname, "..");
10
+ const packageJson = require(path.join(root, "package.json"));
11
+ const version = packageJson.version;
12
+ const installDir = path.join(root, "bin", "downloaded");
13
+ const exe = process.platform === "win32" ? "electron-cli.exe" : "electron-cli";
14
+ const destination = path.join(installDir, exe);
15
+ const target = resolveTarget();
16
+
17
+ if (process.env.ELECTRON_CLI_SKIP_DOWNLOAD === "1") {
18
+ process.exit(0);
19
+ }
20
+
21
+ if (!target) {
22
+ warn(`No prebuilt binary is available for ${process.platform}/${process.arch}; Cargo fallback remains available.`);
23
+ process.exit(0);
24
+ }
25
+
26
+ const assetName = `electron-cli-v${version}-${target}${process.platform === "win32" ? ".exe" : ""}`;
27
+ const baseUrl = process.env.ELECTRON_CLI_DOWNLOAD_BASE_URL || "https://github.com/Ikana/electron-cli/releases/download";
28
+ const url = `${baseUrl}/v${version}/${assetName}`;
29
+ const tempFile = path.join(os.tmpdir(), `${assetName}.${process.pid}.tmp`);
30
+
31
+ main().catch((error) => {
32
+ warn(`Could not install prebuilt binary: ${error.message}`);
33
+
34
+ if (process.env.ELECTRON_CLI_STRICT_INSTALL === "1") {
35
+ process.exit(1);
36
+ }
37
+
38
+ warn("Install completed with Cargo fallback enabled.");
39
+ });
40
+
41
+ async function main() {
42
+ fs.mkdirSync(installDir, { recursive: true });
43
+
44
+ await download(url, tempFile);
45
+ fs.renameSync(tempFile, destination);
46
+
47
+ if (process.platform !== "win32") {
48
+ fs.chmodSync(destination, 0o755);
49
+ }
50
+
51
+ const result = spawnSync(destination, ["--version"], { encoding: "utf8" });
52
+ if (result.error || result.status !== 0) {
53
+ fs.rmSync(destination, { force: true });
54
+ throw new Error(result.error ? result.error.message : result.stderr.trim() || "downloaded binary failed verification");
55
+ }
56
+
57
+ console.error(`electron-cli installed prebuilt binary ${assetName}`);
58
+ }
59
+
60
+ function resolveTarget() {
61
+ const key = `${process.platform}-${process.arch}`;
62
+
63
+ return {
64
+ "darwin-arm64": "darwin-arm64",
65
+ "darwin-x64": "darwin-x64",
66
+ "linux-x64": "linux-x64",
67
+ "win32-x64": "win32-x64",
68
+ }[key];
69
+ }
70
+
71
+ function download(url, destinationPath) {
72
+ return new Promise((resolve, reject) => {
73
+ const request = https.get(url, (response) => {
74
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
75
+ response.resume();
76
+ download(response.headers.location, destinationPath).then(resolve, reject);
77
+ return;
78
+ }
79
+
80
+ if (response.statusCode !== 200) {
81
+ response.resume();
82
+ reject(new Error(`download failed with HTTP ${response.statusCode}: ${url}`));
83
+ return;
84
+ }
85
+
86
+ const file = fs.createWriteStream(destinationPath, { mode: 0o755 });
87
+ response.pipe(file);
88
+ file.on("finish", () => file.close(resolve));
89
+ file.on("error", reject);
90
+ });
91
+
92
+ request.on("error", reject);
93
+ request.setTimeout(30_000, () => {
94
+ request.destroy(new Error("download timed out"));
95
+ });
96
+ });
97
+ }
98
+
99
+ function warn(message) {
100
+ console.error(`electron-cli postinstall: ${message}`);
101
+ }
package/src/cli.rs CHANGED
@@ -1,6 +1,6 @@
1
1
  use std::path::PathBuf;
2
2
 
3
- use clap::{Args, Parser, Subcommand};
3
+ use clap::{Args, Parser, Subcommand, ValueEnum};
4
4
 
5
5
  #[derive(Debug, Parser)]
6
6
  #[command(
@@ -18,10 +18,20 @@ pub struct Cli {
18
18
  pub enum Commands {
19
19
  /// Check whether the current project looks ready for Electron development.
20
20
  Doctor(CommandArgs),
21
+ /// Bootstrap a new Electron app.
22
+ Init(InitArgs),
21
23
  /// Print a structured snapshot of the current JavaScript/Electron project.
22
24
  Inspect(CommandArgs),
25
+ /// Create distributable artifacts from a packaged Electron app.
26
+ Make(MakeArgs),
27
+ /// Create a local Electron application bundle without Electron Forge.
28
+ Package(PackageArgs),
23
29
  /// Recommend next commands and risks from the project snapshot.
24
30
  Plan(CommandArgs),
31
+ /// Publish made artifacts to a distribution target.
32
+ Publish(PublishArgs),
33
+ /// Launch the current Electron app without Electron Forge.
34
+ Start(StartArgs),
25
35
  }
26
36
 
27
37
  #[derive(Debug, Clone, Args)]
@@ -34,3 +44,252 @@ pub struct CommandArgs {
34
44
  #[arg(long)]
35
45
  pub json: bool,
36
46
  }
47
+
48
+ #[derive(Debug, Clone, Args)]
49
+ pub struct InitArgs {
50
+ /// Directory to initialize. Defaults to the current directory.
51
+ #[arg(default_value = ".", value_name = "DIR")]
52
+ pub dir: PathBuf,
53
+
54
+ /// Directory to run the create command from.
55
+ #[arg(long, default_value = ".", value_name = "PATH")]
56
+ pub cwd: PathBuf,
57
+
58
+ /// Template to use. "minimal" is native; other names are passed to create-electron-app.
59
+ #[arg(long, short = 't', default_value = "minimal")]
60
+ pub template: String,
61
+
62
+ /// Package manager command strategy to use.
63
+ #[arg(long, value_enum)]
64
+ pub package_manager: Option<PackageManager>,
65
+
66
+ /// Set a specific Electron version, or use latest/beta/nightly.
67
+ #[arg(long, value_name = "VERSION")]
68
+ pub electron_version: Option<String>,
69
+
70
+ /// Copy template CI files when the Forge template supports them.
71
+ #[arg(long)]
72
+ pub copy_ci_files: bool,
73
+
74
+ /// Overwrite an existing target directory.
75
+ #[arg(long)]
76
+ pub force: bool,
77
+
78
+ /// Skip initializing a git repository in the created project.
79
+ #[arg(long)]
80
+ pub skip_git: bool,
81
+
82
+ /// Print the command and metadata without creating files.
83
+ #[arg(long)]
84
+ pub dry_run: bool,
85
+
86
+ /// Emit machine-readable JSON.
87
+ #[arg(long)]
88
+ pub json: bool,
89
+ }
90
+
91
+ #[derive(Debug, Clone, Args)]
92
+ pub struct StartArgs {
93
+ /// Project directory to launch.
94
+ #[arg(long, default_value = ".", value_name = "PATH")]
95
+ pub cwd: PathBuf,
96
+
97
+ /// Print the launch command without starting Electron.
98
+ #[arg(long)]
99
+ pub dry_run: bool,
100
+
101
+ /// Emit machine-readable JSON.
102
+ #[arg(long)]
103
+ pub json: bool,
104
+
105
+ /// Extra arguments passed to Electron after `--`.
106
+ #[arg(last = true, value_name = "ELECTRON_ARG")]
107
+ pub electron_args: Vec<String>,
108
+ }
109
+
110
+ #[derive(Debug, Clone, Args)]
111
+ pub struct PackageArgs {
112
+ /// Project directory to package.
113
+ #[arg(long, default_value = ".", value_name = "PATH")]
114
+ pub cwd: PathBuf,
115
+
116
+ /// Output directory for packaged app bundles.
117
+ #[arg(long, default_value = "out", value_name = "PATH")]
118
+ pub out_dir: PathBuf,
119
+
120
+ /// Override the packaged application name.
121
+ #[arg(long)]
122
+ pub name: Option<String>,
123
+
124
+ /// Target platform label. Defaults to the current platform.
125
+ #[arg(long)]
126
+ pub platform: Option<String>,
127
+
128
+ /// Target architecture label. Defaults to the current architecture.
129
+ #[arg(long)]
130
+ pub arch: Option<String>,
131
+
132
+ /// Overwrite an existing package output directory.
133
+ #[arg(long)]
134
+ pub force: bool,
135
+
136
+ /// Print the package plan without creating files.
137
+ #[arg(long)]
138
+ pub dry_run: bool,
139
+
140
+ /// Emit machine-readable JSON.
141
+ #[arg(long)]
142
+ pub json: bool,
143
+ }
144
+
145
+ #[derive(Debug, Clone, Args)]
146
+ pub struct MakeArgs {
147
+ /// Project directory to make distributables for.
148
+ #[arg(long, default_value = ".", value_name = "PATH")]
149
+ pub cwd: PathBuf,
150
+
151
+ /// Output directory used for package and make artifacts.
152
+ #[arg(long, default_value = "out", value_name = "PATH")]
153
+ pub out_dir: PathBuf,
154
+
155
+ /// Override the application name.
156
+ #[arg(long)]
157
+ pub name: Option<String>,
158
+
159
+ /// Target platform label. Defaults to the current platform.
160
+ #[arg(long)]
161
+ pub platform: Option<String>,
162
+
163
+ /// Target architecture label. Defaults to the current architecture.
164
+ #[arg(long)]
165
+ pub arch: Option<String>,
166
+
167
+ /// Maker target to run.
168
+ #[arg(long, value_enum, default_value_t = MakeTarget::Zip)]
169
+ pub target: MakeTarget,
170
+
171
+ /// Reuse an existing package output instead of running package first.
172
+ #[arg(long)]
173
+ pub skip_package: bool,
174
+
175
+ /// Overwrite existing package and make artifacts.
176
+ #[arg(long)]
177
+ pub force: bool,
178
+
179
+ /// Print the make plan without creating files.
180
+ #[arg(long)]
181
+ pub dry_run: bool,
182
+
183
+ /// Emit machine-readable JSON.
184
+ #[arg(long)]
185
+ pub json: bool,
186
+ }
187
+
188
+ #[derive(Debug, Clone, Args)]
189
+ pub struct PublishArgs {
190
+ /// Project directory to publish from.
191
+ #[arg(long, default_value = ".", value_name = "PATH")]
192
+ pub cwd: PathBuf,
193
+
194
+ /// Output directory used for package, make, and publish artifacts.
195
+ #[arg(long, default_value = "out", value_name = "PATH")]
196
+ pub out_dir: PathBuf,
197
+
198
+ /// Override the application name.
199
+ #[arg(long)]
200
+ pub name: Option<String>,
201
+
202
+ /// Target platform label. Defaults to the current platform.
203
+ #[arg(long)]
204
+ pub platform: Option<String>,
205
+
206
+ /// Target architecture label. Defaults to the current architecture.
207
+ #[arg(long)]
208
+ pub arch: Option<String>,
209
+
210
+ /// Maker target whose artifact should be published.
211
+ #[arg(long, value_enum, default_value_t = MakeTarget::Zip)]
212
+ pub target: MakeTarget,
213
+
214
+ /// Publisher target to use.
215
+ #[arg(long, value_enum, default_value_t = PublishTarget::Local)]
216
+ pub publisher: PublishTarget,
217
+
218
+ /// Destination for local published artifacts.
219
+ #[arg(long, default_value = "out/publish/local", value_name = "PATH")]
220
+ pub to: PathBuf,
221
+
222
+ /// Release channel label written into the publish manifest.
223
+ #[arg(long, default_value = "default")]
224
+ pub channel: String,
225
+
226
+ /// Reuse an existing make artifact instead of running package and make first.
227
+ #[arg(long)]
228
+ pub skip_make: bool,
229
+
230
+ /// Overwrite existing publish artifacts.
231
+ #[arg(long)]
232
+ pub force: bool,
233
+
234
+ /// Print the publish plan without creating files.
235
+ #[arg(long)]
236
+ pub dry_run: bool,
237
+
238
+ /// Emit machine-readable JSON.
239
+ #[arg(long)]
240
+ pub json: bool,
241
+ }
242
+
243
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
244
+ #[value(rename_all = "lower")]
245
+ pub enum PackageManager {
246
+ Npm,
247
+ Pnpm,
248
+ Yarn,
249
+ Bun,
250
+ }
251
+
252
+ impl PackageManager {
253
+ pub fn as_str(self) -> &'static str {
254
+ match self {
255
+ PackageManager::Npm => "npm",
256
+ PackageManager::Pnpm => "pnpm",
257
+ PackageManager::Yarn => "yarn",
258
+ PackageManager::Bun => "bun",
259
+ }
260
+ }
261
+ }
262
+
263
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
264
+ #[value(rename_all = "lower")]
265
+ pub enum MakeTarget {
266
+ Deb,
267
+ Dmg,
268
+ Rpm,
269
+ Zip,
270
+ }
271
+
272
+ impl MakeTarget {
273
+ pub fn as_str(self) -> &'static str {
274
+ match self {
275
+ MakeTarget::Deb => "deb",
276
+ MakeTarget::Dmg => "dmg",
277
+ MakeTarget::Rpm => "rpm",
278
+ MakeTarget::Zip => "zip",
279
+ }
280
+ }
281
+ }
282
+
283
+ #[derive(Debug, Clone, Copy, ValueEnum)]
284
+ #[value(rename_all = "lower")]
285
+ pub enum PublishTarget {
286
+ Local,
287
+ }
288
+
289
+ impl PublishTarget {
290
+ pub fn as_str(self) -> &'static str {
291
+ match self {
292
+ PublishTarget::Local => "local",
293
+ }
294
+ }
295
+ }