@flare-desktop/cli 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flare contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # @flare-desktop/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The command line interface of [flare](https://github.com/flare-desktop/flare), a C++20 desktop toolkit that renders
4
+ an HTML frontend in the system webview.
5
+
6
+ ```bash
7
+ npm install --save-dev @flare-desktop/cli
8
+ npx flare --help
9
+ ```
10
+
11
+ Create an application:
12
+
13
+ ```bash
14
+ npm create flare-desktop@latest hello
15
+ cd hello
16
+ npm install
17
+ npm run dev
18
+ ```
19
+
20
+ | Command | Effect |
21
+ |:--|:--|
22
+ | `flare init [name]` | scaffold a project: `flare.config.json`, `package.json`, CMake, C++ entry point and `frontend/` |
23
+ | `flare dev` | build the debug binary and run it with `FLARE_DEV=1` |
24
+ | `flare build [--debug]` | build the project; release by default |
25
+ | `flare bundle` | build a release binary and native packages under `target/bundle` |
26
+ | `flare icon <source.png>` | write the png, ico and icns icon set |
27
+ | `flare info` | version, platform, toolchain and project information |
28
+ | `flare inspect` | list the resolved configuration layers and the merged configuration |
29
+ | `flare doctor` | check Node, CMake, the compiler, git and the configuration |
30
+ | `flare completions <shell>` | bash, zsh, fish or powershell completions |
31
+
32
+ Global options: `--project=<path>`, `--config=<path>`, `--environment=<name>`, `--json`, `--verbose`.
33
+
34
+ ## Frontend directory
35
+
36
+ `flare build` passes `build.frontendDist` to CMake as `FLARE_FRONTEND_DIR`. The scaffolded `CMakeLists.txt` uses
37
+ that variable for `FlareEmbedFrontend` and for the development directory. Change the config key, not the CMake
38
+ file:
39
+
40
+ ```json
41
+ {
42
+ "build": {
43
+ "frontendDist": "web"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Native packaging
49
+
50
+ `flare bundle` performs a Release build and writes real packages. `--bundles` overrides `bundle.targets`:
51
+
52
+ ```bash
53
+ npx flare bundle
54
+ npx flare bundle --bundles=nsis
55
+ npx flare bundle --bundles=nsis,msi --out=release
56
+ ```
57
+
58
+ | Target | Platform | Artifact | Required tool |
59
+ |:--|:--|:--|:--|
60
+ | `nsis` | Windows | `.exe` installer | `makensis` (NSIS 3) |
61
+ | `msi` | Windows | `.msi` installer | `candle.exe`, `light.exe` (WiX 3.14) |
62
+ | `deb` | Linux | `.deb` package | `dpkg-deb` |
63
+ | `rpm` | Linux | `.rpm` package | `rpmbuild` |
64
+ | `appimage` | Linux | `.AppImage` | `appimagetool` |
65
+ | `app` | macOS | `.app` bundle | none |
66
+ | `dmg` | macOS | `.dmg` disk image | `hdiutil` |
67
+
68
+ With an empty `bundle.targets`, the targets of the current platform are used. Each artifact is written to
69
+ `target/bundle/<target>/`. The command fails before compiling when a required tool is missing, and the error names
70
+ the tool and the expected installation.
71
+
72
+ Packaging uses `bundle.icon` (a square png), `bundle.licenseFile`, `bundle.resources`, `bundle.externalBin`,
73
+ `bundle.publisher`, `bundle.homepage` and `bundle.shortDescription` when they are present. Resources and external
74
+ binaries are staged next to the application binary.
75
+
76
+ The configuration is validated against
77
+ [`schemas/flare.config.schema.json`](https://raw.githubusercontent.com/flare-desktop/flare/main/schemas/flare.config.schema.json).
package/bin/flare.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "../src/index.js";
3
+
4
+ process.exitCode = await run(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@flare-desktop/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Command line interface for the flare desktop toolkit",
5
+ "type": "module",
6
+ "bin": {
7
+ "flare": "bin/flare.js"
8
+ },
9
+ "main": "src/index.js",
10
+ "exports": {
11
+ ".": "./src/index.js",
12
+ "./schema": "./src/schema.js"
13
+ },
14
+ "files": [
15
+ "bin",
16
+ "src",
17
+ "README.md"
18
+ ],
19
+ "keywords": [
20
+ "flare",
21
+ "desktop",
22
+ "webview",
23
+ "cpp",
24
+ "cli"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/flare-desktop/flare-cli.git"
29
+ },
30
+ "homepage": "https://flare-desktop.github.io/",
31
+ "bugs": {
32
+ "url": "https://github.com/flare-desktop/flare/issues"
33
+ },
34
+ "license": "MIT",
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "scripts": {
39
+ "test": "node --test \"test/*.test.js\""
40
+ },
41
+ "devDependencies": {
42
+ "ajv": "^8.20.0"
43
+ }
44
+ }
package/src/cli.js ADDED
@@ -0,0 +1,93 @@
1
+ import path from "node:path";
2
+
3
+ import { buildCommand } from "./commands/build.js";
4
+ import { bundleCommand } from "./commands/bundle.js";
5
+ import { completionsCommand } from "./commands/completions.js";
6
+ import { devCommand } from "./commands/dev.js";
7
+ import { doctorCommand } from "./commands/doctor.js";
8
+ import { iconCommand } from "./commands/icon.js";
9
+ import { infoCommand } from "./commands/info.js";
10
+ import { initProject } from "./commands/init.js";
11
+ import { inspectCommand } from "./commands/inspect.js";
12
+ import { parseArguments } from "./lib/args.js";
13
+ import { FlareError } from "./lib/errors.js";
14
+ import { createLogger } from "./lib/logger.js";
15
+ import { helpText } from "./help.js";
16
+ import { VERSION } from "./version.js";
17
+
18
+ export { createLogger };
19
+
20
+ async function initCommand(context) {
21
+ await initProject({
22
+ name: context.positionals[0] ?? "flare-app",
23
+ baseDirectory: context.projectDirectory,
24
+ identifier: context.flags.identifier,
25
+ appVersion: context.flags.appVersion ?? "0.1.0",
26
+ force: Boolean(context.flags.force),
27
+ logger: context.logger
28
+ });
29
+ return 0;
30
+ }
31
+
32
+ const handlers = {
33
+ init: initCommand,
34
+ dev: devCommand,
35
+ build: buildCommand,
36
+ bundle: bundleCommand,
37
+ icon: iconCommand,
38
+ info: infoCommand,
39
+ inspect: inspectCommand,
40
+ doctor: doctorCommand,
41
+ completions: completionsCommand,
42
+ version: (context) => {
43
+ context.logger.info(VERSION);
44
+ return 0;
45
+ },
46
+ help: (context) => {
47
+ context.logger.info(helpText().trimEnd());
48
+ return 0;
49
+ }
50
+ };
51
+
52
+ export async function run(argv = []) {
53
+ const { flags, positionals } = parseArguments(argv);
54
+ const logger = createLogger({ verbose: Boolean(flags.verbose) });
55
+
56
+ if (flags.version) {
57
+ logger.info(VERSION);
58
+ return 0;
59
+ }
60
+
61
+ if (flags.help) {
62
+ logger.info(helpText().trimEnd());
63
+ return 0;
64
+ }
65
+
66
+ const commandName = positionals.shift() ?? "help";
67
+ const handler = handlers[commandName];
68
+ if (!handler) {
69
+ logger.error(`unknown command '${commandName}'`);
70
+ logger.info("run 'flare help' to list the commands");
71
+ return 1;
72
+ }
73
+
74
+ flags.environment ??= commandName === "dev" ? "development" : "production";
75
+
76
+ const context = {
77
+ projectDirectory: path.resolve(flags.project ?? "."),
78
+ flags,
79
+ positionals,
80
+ logger
81
+ };
82
+
83
+ try {
84
+ return await handler(context);
85
+ } catch (error) {
86
+ if (error instanceof FlareError) {
87
+ logger.error(error.message);
88
+ return error.exitCode;
89
+ }
90
+ logger.error(error?.stack ?? String(error));
91
+ return 1;
92
+ }
93
+ }
@@ -0,0 +1,37 @@
1
+ import { compileApplication, runHook } from "../lib/build.js";
2
+ import { loadProject, productName } from "../lib/project.js";
3
+
4
+ export async function buildCommand(context) {
5
+ const { config, files } = loadProject({
6
+ projectDirectory: context.projectDirectory,
7
+ configFile: context.flags.config,
8
+ environment: context.flags.environment
9
+ });
10
+ const mode = context.flags.debug ? "Debug" : "Release";
11
+
12
+ context.logger.debug(`configuration layers: ${files.join(", ")}`);
13
+ context.logger.info(`building '${productName(config)}' ${config.app.version} (${mode.toLowerCase()})`);
14
+
15
+ await runHook(config.build.beforeBuildCommand, {
16
+ cwd: context.projectDirectory,
17
+ logger: context.logger,
18
+ label: "before build command"
19
+ });
20
+
21
+ const { binary, directory } = await compileApplication({
22
+ projectDirectory: context.projectDirectory,
23
+ config,
24
+ mode,
25
+ logger: context.logger
26
+ });
27
+
28
+ await runHook(config.build.afterBuildCommand, {
29
+ cwd: context.projectDirectory,
30
+ logger: context.logger,
31
+ label: "after build command"
32
+ });
33
+
34
+ context.logger.info(`built '${productName(config)}' into '${directory}'`);
35
+ context.logger.step(`binary ${binary}`);
36
+ return 0;
37
+ }
@@ -0,0 +1,44 @@
1
+ import { compileApplication, runHook } from "../lib/build.js";
2
+ import { assertBundleTools, bundleTargets, createBundles } from "../lib/packaging/index.js";
3
+ import { loadProject } from "../lib/project.js";
4
+
5
+ export async function bundleCommand(context) {
6
+ const { config } = loadProject({
7
+ projectDirectory: context.projectDirectory,
8
+ configFile: context.flags.config,
9
+ environment: context.flags.environment
10
+ });
11
+ const targets = bundleTargets(config, context.flags.bundles);
12
+
13
+ assertBundleTools(targets, config);
14
+
15
+ await runHook(config.build.beforeBuildCommand, {
16
+ cwd: context.projectDirectory,
17
+ logger: context.logger,
18
+ label: "before build command"
19
+ });
20
+
21
+ const { binary } = await compileApplication({
22
+ projectDirectory: context.projectDirectory,
23
+ config,
24
+ mode: "Release",
25
+ logger: context.logger
26
+ });
27
+
28
+ await runHook(config.build.afterBuildCommand, {
29
+ cwd: context.projectDirectory,
30
+ logger: context.logger,
31
+ label: "after build command"
32
+ });
33
+
34
+ await createBundles({
35
+ config,
36
+ projectDirectory: context.projectDirectory,
37
+ binary,
38
+ targets,
39
+ output: context.flags.out,
40
+ logger: context.logger
41
+ });
42
+
43
+ return 0;
44
+ }
@@ -0,0 +1,45 @@
1
+ import { COMMAND_NAMES } from "../lib/commands.js";
2
+ import { FlareError } from "../lib/errors.js";
3
+
4
+ const FLAGS = ["--project", "--config", "--environment", "--json", "--verbose", "--help", "--version"];
5
+
6
+ export async function completionsCommand(context) {
7
+ const shell = context.positionals[0] ?? "bash";
8
+ const commands = COMMAND_NAMES.join(" ");
9
+
10
+ if (shell === "bash") {
11
+ context.logger.info(`_flare_complete() {
12
+ local current="\${COMP_WORDS[COMP_CWORD]}"
13
+ COMPREPLY=( $(compgen -W "${commands} ${FLAGS.join(" ")}" -- "$current") )
14
+ }
15
+ complete -F _flare_complete flare`);
16
+ return 0;
17
+ }
18
+
19
+ if (shell === "zsh") {
20
+ context.logger.info(`#compdef flare
21
+
22
+ _arguments '1:command:(${commands})'`);
23
+ return 0;
24
+ }
25
+
26
+ if (shell === "fish") {
27
+ for (const command of COMMAND_NAMES) {
28
+ context.logger.info(`complete -c flare -f -a "${command}"`);
29
+ }
30
+ return 0;
31
+ }
32
+
33
+ if (shell === "powershell") {
34
+ const list = [...COMMAND_NAMES, ...FLAGS].map((value) => `'${value}'`).join(", ");
35
+ context.logger.info(`Register-ArgumentCompleter -Native -CommandName flare -ScriptBlock {
36
+ param($wordToComplete, $commandAst, $cursorPosition)
37
+ ${list} | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
38
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
39
+ }
40
+ }`);
41
+ return 0;
42
+ }
43
+
44
+ throw new FlareError(`unsupported shell '${shell}'`);
45
+ }
@@ -0,0 +1,91 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+
3
+ import { compileApplication, runHook } from "../lib/build.js";
4
+ import { FlareError } from "../lib/errors.js";
5
+ import { loadProject, productName } from "../lib/project.js";
6
+ import { run, startShell, terminate } from "../lib/process.js";
7
+
8
+ async function waitForUrl(url, { timeout, logger }) {
9
+ const deadline = Date.now() + timeout;
10
+ let attempts = 0;
11
+ while (Date.now() < deadline) {
12
+ attempts += 1;
13
+ try {
14
+ const response = await fetch(url, { signal: AbortSignal.timeout(2000) });
15
+ if (response.status < 500) return;
16
+ } catch {
17
+ logger.debug(`waiting for ${url} (attempt ${attempts})`);
18
+ }
19
+ await delay(250);
20
+ }
21
+ throw new FlareError(`development server at '${url}' did not answer within ${Math.round(timeout / 1000)}s`);
22
+ }
23
+
24
+ export async function devCommand(context) {
25
+ const { config, files } = loadProject({
26
+ projectDirectory: context.projectDirectory,
27
+ configFile: context.flags.config,
28
+ environment: context.flags.environment
29
+ });
30
+ const timeout = Number(context.flags.devTimeout ?? 60000);
31
+
32
+ context.logger.info(`development mode for '${productName(config)}'`);
33
+ context.logger.debug(`configuration layers: ${files.join(", ")}`);
34
+
35
+ let server = null;
36
+ let application = null;
37
+
38
+ const stopServer = () => {
39
+ if (server) terminate(server);
40
+ };
41
+ const stopOnSignal = (code) => () => {
42
+ stopServer();
43
+ process.exit(code);
44
+ };
45
+ const sigintHandler = stopOnSignal(130);
46
+ const sigtermHandler = stopOnSignal(143);
47
+ process.once("SIGINT", sigintHandler);
48
+ process.once("SIGTERM", sigtermHandler);
49
+
50
+ try {
51
+ if (config.build.beforeDevCommand) {
52
+ context.logger.info(`before dev command: ${config.build.beforeDevCommand}`);
53
+ server = startShell(config.build.beforeDevCommand, { cwd: context.projectDirectory });
54
+ }
55
+
56
+ if (config.build.devUrl) {
57
+ context.logger.info(`waiting for ${config.build.devUrl}`);
58
+ await waitForUrl(config.build.devUrl, { timeout, logger: context.logger });
59
+ }
60
+
61
+ const { binary } = await compileApplication({
62
+ projectDirectory: context.projectDirectory,
63
+ config,
64
+ mode: "Debug",
65
+ logger: context.logger
66
+ });
67
+
68
+ const env = { FLARE_DEV: "1" };
69
+ if (config.build.devUrl) env.FLARE_DEV_URL = config.build.devUrl;
70
+
71
+ context.logger.info(`running ${binary}`);
72
+ application = await run(binary, [], { cwd: context.projectDirectory, env });
73
+ } finally {
74
+ stopServer();
75
+ process.off("SIGINT", sigintHandler);
76
+ process.off("SIGTERM", sigtermHandler);
77
+ if (application && config.build.afterDevCommand) {
78
+ await runHook(config.build.afterDevCommand, {
79
+ cwd: context.projectDirectory,
80
+ logger: context.logger,
81
+ label: "after dev command"
82
+ });
83
+ }
84
+ }
85
+
86
+ if (!application) return 0;
87
+ if (application.code !== 0) {
88
+ context.logger.warn(`the application exited with code ${application.code}`);
89
+ }
90
+ return application.code;
91
+ }
@@ -0,0 +1,88 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ import { cmakeVersion, compilerVersion, ninjaVersion } from "../lib/cmake.js";
4
+ import { frontendDirectory, loadProject, platformDisplayName, platformName } from "../lib/project.js";
5
+ import { capture, firstLine } from "../lib/process.js";
6
+
7
+ function gitVersion() {
8
+ const result = capture("git", ["--version"]);
9
+ if (result.error || result.status !== 0) return null;
10
+ return firstLine(result.stdout);
11
+ }
12
+
13
+ function hintsFor(platform) {
14
+ if (platform === "linux") {
15
+ return [
16
+ "install libwebkit2gtk-4.1-dev, libgtk-3-dev and build-essential for the native webview",
17
+ "libwebkit2gtk-4.0-dev works too when the distribution does not ship 4.1"
18
+ ];
19
+ }
20
+ if (platform === "windows") {
21
+ return [
22
+ "the WebView2 runtime ships with Windows 11 and current Windows 10 updates",
23
+ "install the Visual Studio Build Tools with the desktop C++ workload"
24
+ ];
25
+ }
26
+ if (platform === "macos") {
27
+ return ["WKWebView ships with macOS; install the Xcode command line tools for the compiler"];
28
+ }
29
+ return [];
30
+ }
31
+
32
+ export async function doctorCommand(context) {
33
+ const checks = [];
34
+ const add = (status, label, detail) => checks.push({ status, label, detail });
35
+ const platform = platformName();
36
+
37
+ add("ok", "platform", platformDisplayName());
38
+
39
+ const nodeMajor = Number(process.versions.node.split(".")[0]);
40
+ add(nodeMajor >= 18 ? "ok" : "fail", "node", `${process.version}${nodeMajor >= 18 ? "" : " (18 or newer required)"}`);
41
+
42
+ const cmake = cmakeVersion();
43
+ add(cmake ? "ok" : "fail", "cmake", cmake ?? "not found");
44
+
45
+ const compiler = compilerVersion();
46
+ add(compiler ? "ok" : "warn", "compiler", compiler ?? "not found");
47
+
48
+ const ninja = ninjaVersion();
49
+ add(ninja ? "ok" : "warn", "ninja", ninja ?? "not found, the default cmake generator is used");
50
+
51
+ const git = gitVersion();
52
+ add(git ? "ok" : "warn", "git", git ?? "not found, FetchContent cannot clone flare");
53
+
54
+ let config = null;
55
+ try {
56
+ const project = loadProject({
57
+ projectDirectory: context.projectDirectory,
58
+ configFile: context.flags.config,
59
+ environment: context.flags.environment
60
+ });
61
+ config = project.config;
62
+ add("ok", "configuration", project.configPath);
63
+ } catch (error) {
64
+ add("fail", "configuration", error.message);
65
+ }
66
+
67
+ if (config) {
68
+ const directory = frontendDirectory(context.projectDirectory, config);
69
+ add(existsSync(directory) ? "ok" : "warn", "frontend", directory);
70
+ }
71
+
72
+ const hints = hintsFor(platform);
73
+
74
+ if (context.flags.json) {
75
+ context.logger.info(JSON.stringify({ platform, checks, hints }, null, 2));
76
+ } else {
77
+ context.logger.info("flare doctor");
78
+ for (const check of checks) {
79
+ context.logger.info(` ${check.status.padEnd(5)}${check.label.padEnd(16)}${check.detail}`);
80
+ }
81
+ if (hints.length > 0) {
82
+ context.logger.info(" hints:");
83
+ for (const hint of hints) context.logger.info(` ${hint}`);
84
+ }
85
+ }
86
+
87
+ return checks.some((check) => check.status === "fail") ? 1 : 0;
88
+ }
@@ -0,0 +1,31 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { FlareError } from "../lib/errors.js";
5
+ import { buildIconSet } from "../lib/icons.js";
6
+ import { readPng } from "../lib/png.js";
7
+
8
+ export async function iconCommand(context) {
9
+ const source = context.positionals[0];
10
+ if (!source) throw new FlareError("usage: flare icon <source.png>");
11
+
12
+ const sourcePath = path.resolve(context.projectDirectory, source);
13
+ if (!existsSync(sourcePath)) throw new FlareError(`'${sourcePath}' does not exist`);
14
+ if (path.extname(sourcePath).toLowerCase() !== ".png") {
15
+ throw new FlareError("only png sources are supported; export the image as png first");
16
+ }
17
+
18
+ const image = readPng(readFileSync(sourcePath));
19
+ const files = buildIconSet(image);
20
+ const output = path.resolve(context.projectDirectory, context.flags.out || "icons");
21
+ mkdirSync(output, { recursive: true });
22
+
23
+ for (const file of files) {
24
+ const target = path.join(output, file.name);
25
+ writeFileSync(target, file.data);
26
+ context.logger.step(`wrote ${target}`);
27
+ }
28
+
29
+ context.logger.info(`generated ${files.length} icon(s) from '${sourcePath}'`);
30
+ return 0;
31
+ }
@@ -0,0 +1,65 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { architectureName, loadProject, platformDisplayName } from "../lib/project.js";
5
+ import { buildDirectory, cmakeVersion, compilerVersion, executableNames } from "../lib/cmake.js";
6
+ import { VERSION } from "../version.js";
7
+
8
+ function pad(label) {
9
+ return label.padEnd(18);
10
+ }
11
+
12
+ export async function infoCommand(context) {
13
+ const { config, files } = loadProject({
14
+ projectDirectory: context.projectDirectory,
15
+ configFile: context.flags.config,
16
+ environment: context.flags.environment
17
+ });
18
+
19
+ const binary = path.join(
20
+ buildDirectory(context.projectDirectory, "Release"),
21
+ "bin",
22
+ executableNames(config.app.mainBinaryName || config.app.productName || "flare-app")[0]
23
+ );
24
+
25
+ const report = {
26
+ flare: VERSION,
27
+ node: process.versions.node,
28
+ platform: platformDisplayName(),
29
+ architecture: architectureName(),
30
+ cmake: cmakeVersion(),
31
+ compiler: compilerVersion(),
32
+ project: context.projectDirectory,
33
+ configuration: files,
34
+ productName: config.app.productName,
35
+ identifier: config.app.identifier,
36
+ version: config.app.version,
37
+ windows: config.windows.length,
38
+ frontendDist: path.resolve(context.projectDirectory, config.build.frontendDist || "frontend"),
39
+ bundleTargets: config.bundle.targets,
40
+ binary: existsSync(binary) ? binary : null
41
+ };
42
+
43
+ if (context.flags.json) {
44
+ context.logger.info(JSON.stringify(report, null, 2));
45
+ return 0;
46
+ }
47
+
48
+ context.logger.info(`${pad("flare")}${VERSION}`);
49
+ context.logger.info(`${pad("node")}${process.versions.node}`);
50
+ context.logger.info(`${pad("platform")}${platformDisplayName()}`);
51
+ context.logger.info(`${pad("architecture")}${architectureName()}`);
52
+ context.logger.info(`${pad("cmake")}${report.cmake ?? "not found"}`);
53
+ context.logger.info(`${pad("compiler")}${report.compiler ?? "not found"}`);
54
+ context.logger.info(`${pad("project")}${context.projectDirectory}`);
55
+ context.logger.info(`${pad("product name")}${config.app.productName}`);
56
+ context.logger.info(`${pad("identifier")}${config.app.identifier}`);
57
+ context.logger.info(`${pad("version")}${config.app.version}`);
58
+ context.logger.info(`${pad("windows")}${config.windows.length}`);
59
+ context.logger.info(`${pad("frontend")}${report.frontendDist}`);
60
+ context.logger.info(
61
+ `${pad("bundle targets")}${config.bundle.targets.length > 0 ? config.bundle.targets.join(", ") : "platform defaults"}`
62
+ );
63
+ if (report.binary) context.logger.info(`${pad("binary")}${report.binary}`);
64
+ return 0;
65
+ }