@flare-desktop/cli 0.1.1 → 0.1.3

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 CHANGED
@@ -33,6 +33,15 @@ Global options: `--project=<path>`, `--config=<path>`, `--environment=<name>`, `
33
33
 
34
34
  ## Frontend directory
35
35
 
36
+ ## Self-contained application configuration
37
+
38
+ The scaffold generates a typed C++ `Flare::Config` header at build time from `flare.config.json` and its platform,
39
+ environment and local layers. The executable does not read JSON from disk. Rebuild after changing any layer; the
40
+ build system tracks them. `flare dev` embeds the development layer and `flare build` embeds production. The `--config`
41
+ and `--environment` CLI flags select the input at build time. On Windows the generated CMake project enables
42
+ `FLARE_NATIVE_WEBVIEW` before fetching the C++ core; the CLI verifies that the native backend was enabled.
43
+ The generated project requires a CLI release containing `src/lib/embed-config-cli.js` (0.1.2 or newer). The new scaffold targets C++ core `v0.1.1`; publish that tag and its SDK release before using prebuilt mode.
44
+
36
45
  `flare build` passes `build.frontendDist` to CMake as `FLARE_FRONTEND_DIR`. The scaffolded `CMakeLists.txt` uses
37
46
  that variable for `FlareEmbedFrontend` and for the development directory. Change the config key, not the CMake
38
47
  file:
@@ -75,3 +84,19 @@ binaries are staged next to the application binary.
75
84
 
76
85
  The configuration is validated against
77
86
  [`schemas/flare.config.schema.json`](https://raw.githubusercontent.com/flare-desktop/flare/main/schemas/flare.config.schema.json).
87
+
88
+ ## Frontend templates, prebuilt SDK and codegen
89
+
90
+ `npm create flare-desktop@latest` offers JavaScript (the existing plain frontend) and TypeScript + Vite.
91
+ TypeScript projects copy version-matched `core.js` and `core.d.ts` into `frontend/`; `tsconfig.json` maps
92
+ `/core.js` to the declaration, while Vite bundles the JavaScript frontend into `dist/` before CMake embeds it.
93
+
94
+ Choose `--sdk=source` (FetchContent of core tag `v0.1.1`) or `--sdk=prebuilt` (download the matching
95
+ GitHub Release SDK archives into `vendor/flare`). The first binary SDK matrix covers Windows x64 MSVC
96
+ Debug/Release with MD/MT, Linux x64 GCC Debug/Release and macOS x64/arm64 Clang Debug/Release.
97
+ Unsupported architectures/ABIs must use source mode. The current C++ core's native webview runtime is
98
+ implemented on Windows; macOS and Linux builds remain headless. Release SDK archives do not exist until
99
+ the core v0.1.1 tag workflow finishes.
100
+
101
+ The former C++ `codegen` executable is replaced by `npx flare codegen --language=header|cpp|json` with
102
+ `--config`, `--out`, `--namespace`, `--embed-assets` and `--frontend`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flare-desktop/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Command line interface for the flare desktop toolkit",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,9 +36,12 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "scripts": {
39
- "test": "node --test \"test/*.test.js\""
39
+ "test": "node --test"
40
40
  },
41
41
  "devDependencies": {
42
42
  "ajv": "^8.20.0"
43
+ },
44
+ "dependencies": {
45
+ "tar": "^7.5.22"
43
46
  }
44
47
  }
Binary file
package/src/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
 
3
3
  import { buildCommand } from "./commands/build.js";
4
+ import { codegenCommand } from "./commands/codegen.js";
4
5
  import { bundleCommand } from "./commands/bundle.js";
5
6
  import { completionsCommand } from "./commands/completions.js";
6
7
  import { devCommand } from "./commands/dev.js";
@@ -24,6 +25,8 @@ async function initCommand(context) {
24
25
  identifier: context.flags.identifier,
25
26
  appVersion: context.flags.appVersion ?? "0.1.0",
26
27
  force: Boolean(context.flags.force),
28
+ template: context.flags.template || "javascript",
29
+ sdkMode: context.flags.sdk || "source",
27
30
  logger: context.logger
28
31
  });
29
32
  return 0;
@@ -35,6 +38,7 @@ const handlers = {
35
38
  build: buildCommand,
36
39
  bundle: bundleCommand,
37
40
  icon: iconCommand,
41
+ codegen: codegenCommand,
38
42
  info: infoCommand,
39
43
  inspect: inspectCommand,
40
44
  doctor: doctorCommand,
@@ -21,6 +21,8 @@ export async function buildCommand(context) {
21
21
  const { binary, directory } = await compileApplication({
22
22
  projectDirectory: context.projectDirectory,
23
23
  config,
24
+ configFile: context.flags.config || "flare.config.json",
25
+ environment: context.flags.environment,
24
26
  mode,
25
27
  logger: context.logger
26
28
  });
@@ -21,6 +21,8 @@ export async function bundleCommand(context) {
21
21
  const { binary } = await compileApplication({
22
22
  projectDirectory: context.projectDirectory,
23
23
  config,
24
+ configFile: context.flags.config || "flare.config.json",
25
+ environment: context.flags.environment,
24
26
  mode: "Release",
25
27
  logger: context.logger
26
28
  });
@@ -0,0 +1,53 @@
1
+ import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { generateEmbeddedConfig } from "../lib/embedded-config.js";
5
+ import { FlareError } from "../lib/errors.js";
6
+ import { loadProject, mergeConfig, parseConfig, toKebabCase } from "../lib/project.js";
7
+
8
+ function filesIn(directory) {
9
+ const result = [];
10
+ const walk = (current) => {
11
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
12
+ const absolute = path.join(current, entry.name);
13
+ if (entry.isDirectory()) walk(absolute);
14
+ else if (entry.isFile()) result.push({ key: path.relative(directory, absolute).replaceAll(path.sep, "/"), size: statSync(absolute).size });
15
+ }
16
+ };
17
+ walk(directory);
18
+ return result.sort((a, b) => a.key.localeCompare(b.key));
19
+ }
20
+
21
+ const MIME = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png" };
22
+
23
+ export async function codegenCommand(context) {
24
+ const { config, files } = loadProject({
25
+ projectDirectory: context.projectDirectory,
26
+ configFile: context.flags.config,
27
+ environment: context.flags.environment
28
+ });
29
+ const output = path.resolve(context.projectDirectory, context.flags.out || "generated");
30
+ const language = context.flags.language || "header";
31
+ if (!["header", "hpp", "cpp", "source", "json"].includes(language)) throw new FlareError(`unsupported codegen language '${language}'`);
32
+ const namespace = context.flags.namespace || "FlareGenerated";
33
+ if (!/^[A-Za-z_][A-Za-z_0-9]*$/.test(namespace)) throw new FlareError("invalid C++ namespace");
34
+ mkdirSync(output, { recursive: true });
35
+ let raw = {};
36
+ for (const file of files) raw = mergeConfig(raw, parseConfig(readFileSync(file, "utf8"), file));
37
+ if (language === "json") {
38
+ const name = config.app.identifier.replace(/[^A-Za-z0-9]/g, "_");
39
+ writeFileSync(path.join(output, `${name}-config.json`), JSON.stringify(config, null, 2) + "\n");
40
+ } else {
41
+ const name = `${toKebabCase(config.app.productName)}-config.h`;
42
+ writeFileSync(path.join(output, name), generateEmbeddedConfig(config, raw, namespace));
43
+ const commands = (config.build?.removeUnusedCommands || []).map((item) => ` application.Commands().Unregister(${JSON.stringify(item)});`).join("\n");
44
+ writeFileSync(path.join(output, "commands.cpp"), `#include <flare/core/application.h>\n\nnamespace ${namespace} {\ninline void RegisterCommands(Flare::Application& application) {\n${commands}\n (void)application;\n}\n}\n`);
45
+ }
46
+ if (context.flags.embedAssets) {
47
+ const directory = path.resolve(context.projectDirectory, context.flags.frontend || config.build?.frontendDist || "frontend");
48
+ const assets = filesIn(directory).map(({ key, size }) => ({ key, size, mimeType: MIME[path.extname(key).toLowerCase()] || "application/octet-stream" }));
49
+ writeFileSync(path.join(output, "asset-manifest.json"), JSON.stringify({ count: assets.length, totalBytes: assets.reduce((a, b) => a + b.size, 0), assets }, null, 2) + "\n");
50
+ }
51
+ context.logger.info(`generated configuration in '${output}'`);
52
+ return 0;
53
+ }
@@ -1,6 +1,6 @@
1
1
  import { setTimeout as delay } from "node:timers/promises";
2
2
 
3
- import { compileApplication, runHook } from "../lib/build.js";
3
+ import { compileApplication, ensureFrontendDirectory, runHook } from "../lib/build.js";
4
4
  import { FlareError } from "../lib/errors.js";
5
5
  import { loadProject, productName } from "../lib/project.js";
6
6
  import { run, startShell, terminate } from "../lib/process.js";
@@ -48,6 +48,12 @@ export async function devCommand(context) {
48
48
  process.once("SIGTERM", sigtermHandler);
49
49
 
50
50
  try {
51
+ await ensureFrontendDirectory({
52
+ projectDirectory: context.projectDirectory,
53
+ config,
54
+ logger: context.logger
55
+ });
56
+
51
57
  if (config.build.beforeDevCommand) {
52
58
  context.logger.info(`before dev command: ${config.build.beforeDevCommand}`);
53
59
  server = startShell(config.build.beforeDevCommand, { cwd: context.projectDirectory });
@@ -61,6 +67,8 @@ export async function devCommand(context) {
61
67
  const { binary } = await compileApplication({
62
68
  projectDirectory: context.projectDirectory,
63
69
  config,
70
+ configFile: context.flags.config || "flare.config.json",
71
+ environment: context.flags.environment,
64
72
  mode: "Debug",
65
73
  logger: context.logger
66
74
  });
@@ -1,11 +1,13 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { FlareError } from "../lib/errors.js";
5
5
  import { placeholderIcon } from "../lib/icons.js";
6
6
  import { isValidAppName, toKebabCase } from "../lib/project.js";
7
+ import { installSdk } from "../lib/sdk.js";
7
8
  import { writePng } from "../lib/png.js";
8
- import { VERSION } from "../version.js";
9
+ import { runShell } from "../lib/process.js";
10
+ import { CORE_VERSION, MIN_CLI_VERSION } from "../release-versions.js";
9
11
  import {
10
12
  cmakeTemplate,
11
13
  configTemplate,
@@ -14,6 +16,9 @@ import {
14
16
  packageJsonTemplate,
15
17
  readmeTemplate,
16
18
  scriptTemplate,
19
+ typescriptTemplate,
20
+ tsconfigTemplate,
21
+ viteConfigTemplate,
17
22
  sourceTemplate,
18
23
  styleTemplate
19
24
  } from "../templates/project.js";
@@ -28,10 +33,16 @@ export async function initProject({
28
33
  baseDirectory = ".",
29
34
  identifier,
30
35
  appVersion = "0.1.0",
31
- coreVersion = VERSION,
36
+ coreVersion = CORE_VERSION,
37
+ template = "javascript",
38
+ sdkMode = "source",
39
+ sdkFetch,
40
+ installDependencies = false,
32
41
  force = false,
33
42
  logger
34
43
  }) {
44
+ if (!["javascript", "typescript"].includes(template)) throw new FlareError("template must be javascript or typescript");
45
+ if (!["source", "prebuilt"].includes(sdkMode)) throw new FlareError("SDK mode must be source or prebuilt");
35
46
  const projectName = toKebabCase(name);
36
47
  if (!isValidAppName(projectName)) {
37
48
  throw new FlareError(
@@ -58,7 +69,7 @@ export async function initProject({
58
69
  {
59
70
  file: "flare.config.json",
60
71
  content: `${JSON.stringify(
61
- configTemplate({ name: projectName, identifier: appIdentifier, version: appVersion }),
72
+ configTemplate({ name: projectName, identifier: appIdentifier, version: appVersion, template }),
62
73
  null,
63
74
  2
64
75
  )}\n`
@@ -66,23 +77,30 @@ export async function initProject({
66
77
  {
67
78
  file: "package.json",
68
79
  content: `${JSON.stringify(
69
- packageJsonTemplate({ name: projectName, version: appVersion, cliVersion: coreVersion }),
80
+ packageJsonTemplate({ name: projectName, version: appVersion, cliVersion: MIN_CLI_VERSION, template }),
70
81
  null,
71
82
  2
72
83
  )}\n`
73
84
  },
74
85
  {
75
86
  file: "CMakeLists.txt",
76
- content: cmakeTemplate({ name: projectName, version: appVersion, coreVersion })
87
+ content: cmakeTemplate({ name: projectName, version: appVersion, coreVersion, sdkMode })
77
88
  },
78
89
  {
79
90
  file: "source/main.cpp",
80
- content: sourceTemplate({ name: projectName, identifier: appIdentifier, version: appVersion })
91
+ content: sourceTemplate({ name: projectName })
81
92
  },
82
- { file: "frontend/index.html", content: indexHtmlTemplate(projectName) },
93
+ { file: "frontend/index.html", content: indexHtmlTemplate(projectName, template) },
83
94
  { file: "frontend/style.css", content: styleTemplate() },
84
- { file: "frontend/main.js", content: scriptTemplate() },
95
+ ...(template === "typescript" ? [
96
+ { file: "frontend/main.ts", content: typescriptTemplate() },
97
+ { file: "frontend/core.js", content: readFileSync(new URL("../templates/core.js", import.meta.url), "utf8") },
98
+ { file: "frontend/core.d.ts", content: readFileSync(new URL("../templates/core.d.ts", import.meta.url), "utf8") },
99
+ { file: "tsconfig.json", content: JSON.stringify(tsconfigTemplate(), null, 2) + "\n" },
100
+ { file: "vite.config.mjs", content: viteConfigTemplate() }
101
+ ] : [{ file: "frontend/main.js", content: scriptTemplate() }]),
85
102
  { file: ".gitignore", content: gitignoreTemplate() },
103
+ { file: ".clang-format", content: readFileSync(new URL("../templates/clang-format.txt", import.meta.url), "utf8") },
86
104
  { file: "README.md", content: readmeTemplate(projectName) }
87
105
  ];
88
106
 
@@ -90,19 +108,42 @@ export async function initProject({
90
108
  write(path.join(targetDirectory, entry.file), entry.content);
91
109
  }
92
110
 
111
+ if (sdkMode === "prebuilt") {
112
+ if (logger) logger.info(`downloading flare SDK v${coreVersion} for ${process.platform}/${process.arch}`);
113
+ try {
114
+ await installSdk({ destination: path.join(targetDirectory, "vendor", "flare"), version: coreVersion, ...(sdkFetch ? { fetchImpl: sdkFetch } : {}) });
115
+ } catch (error) {
116
+ if (!force) rmSync(targetDirectory, { recursive: true, force: true });
117
+ throw error;
118
+ }
119
+ }
120
+
93
121
  writeFileSync(path.join(targetDirectory, "icons", "icon.png"), writePng(placeholderIcon()));
94
122
  writeFileSync(path.join(targetDirectory, "icons", "32x32.png"), writePng(placeholderIcon(32)));
95
123
 
96
124
  const written = [...files.map((entry) => entry.file), "icons/icon.png", "icons/32x32.png"];
97
125
 
126
+ if (installDependencies) {
127
+ if (logger) logger.info("installing project dependencies with npm");
128
+ let result;
129
+ try {
130
+ result = await runShell("npm install --no-audit --no-fund", { cwd: targetDirectory });
131
+ } catch (error) {
132
+ throw new FlareError(`npm install could not start in '${targetDirectory}': ${error.message}; run npm install manually`);
133
+ }
134
+ if (result.code !== 0) {
135
+ throw new FlareError(`npm install failed in '${targetDirectory}' (code ${result.code}); the project was kept, run npm install manually`);
136
+ }
137
+ }
138
+
98
139
  if (logger) {
99
140
  logger.info(`created project '${projectName}' in '${targetDirectory}'`);
100
141
  logger.info("");
101
142
  logger.info("next steps:");
102
143
  logger.step(`cd ${path.relative(process.cwd(), targetDirectory) || "."}`);
103
- logger.step("npm install");
144
+ if (!installDependencies) logger.step("npm install");
104
145
  logger.step("npm run dev");
105
146
  }
106
147
 
107
- return { directory: targetDirectory, name: projectName, identifier: appIdentifier, files: written };
148
+ return { directory: targetDirectory, name: projectName, identifier: appIdentifier, template, sdkMode, files: written };
108
149
  }
package/src/help.js CHANGED
@@ -11,7 +11,7 @@ export function helpText() {
11
11
  ];
12
12
 
13
13
  for (const command of COMMANDS) {
14
- lines.push(` ${command.usage.padEnd(26)}${command.description}`);
14
+ lines.push(` ${command.usage.padEnd(39)}${command.description}`);
15
15
  }
16
16
 
17
17
  lines.push(
@@ -20,6 +20,12 @@ export function helpText() {
20
20
  " --project=<path> project directory (default: .)",
21
21
  " --config=<path> configuration file (default: flare.config.json)",
22
22
  " --environment=<name> add the environment configuration layer",
23
+ " --template=<javascript|typescript> frontend template (init)",
24
+ " --sdk=<source|prebuilt> C++ dependency mode (init)",
25
+ " --language=<header|cpp|json> codegen format",
26
+ " --namespace=<name> codegen C++ namespace",
27
+ " --frontend=<path> codegen asset directory",
28
+ " --embed-assets codegen asset manifest",
23
29
  " --bundles=<list> bundle targets, for example nsis,msi,deb",
24
30
  " --out=<path> bundle output directory (default: target/bundle)",
25
31
  " --json machine readable output",
package/src/lib/args.js CHANGED
@@ -8,6 +8,11 @@ const VALUE_FLAGS = new Set([
8
8
  "format",
9
9
  "identifier",
10
10
  "app-version",
11
+ "sdk",
12
+ "template",
13
+ "language",
14
+ "namespace",
15
+ "frontend",
11
16
  "dev-timeout"
12
17
  ]);
13
18
 
package/src/lib/build.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { existsSync } from "node:fs";
2
+
1
3
  import { buildDirectory, buildProject, configureProject, findExecutable } from "./cmake.js";
2
4
  import { FlareError } from "./errors.js";
3
5
  import { binaryName, frontendDirectory } from "./project.js";
@@ -10,14 +12,37 @@ export async function runHook(command, { cwd, env, logger, label }) {
10
12
  if (result.code !== 0) throw new FlareError(`${label} failed with code ${result.code}`);
11
13
  }
12
14
 
13
- export async function compileApplication({ projectDirectory, config, mode, env, logger }) {
15
+ export async function ensureFrontendDirectory({ projectDirectory, config, logger }) {
16
+ const frontend = frontendDirectory(projectDirectory, config);
17
+ if (existsSync(frontend)) return;
18
+ if (!config.build.beforeBuildCommand) {
19
+ throw new FlareError(`frontend directory '${frontend}' is missing and no beforeBuildCommand is configured`);
20
+ }
21
+ logger.info("preparing frontend assets for CMake embedding");
22
+ await runHook(config.build.beforeBuildCommand, {
23
+ cwd: projectDirectory,
24
+ logger,
25
+ label: "frontend build command"
26
+ });
27
+ if (!existsSync(frontend)) {
28
+ throw new FlareError(`frontend build command did not create '${frontend}'`);
29
+ }
30
+ }
31
+
32
+ export async function compileApplication({ projectDirectory, config, mode, env, logger, configFile, environment }) {
33
+ const frontend = frontendDirectory(projectDirectory, config);
34
+ if (!existsSync(frontend)) {
35
+ throw new FlareError(`frontend directory '${frontend}' is missing; run the frontend build command before compiling`);
36
+ }
14
37
  const directory = buildDirectory(projectDirectory, mode);
15
38
  await configureProject(projectDirectory, {
16
39
  directory,
17
40
  mode,
18
41
  env,
19
42
  logger,
20
- frontendDirectory: frontendDirectory(projectDirectory, config)
43
+ frontendDirectory: frontend,
44
+ configFile,
45
+ environment
21
46
  });
22
47
  await buildProject(projectDirectory, { directory, mode, env });
23
48
  const binary = findExecutable(directory, binaryName(config));
package/src/lib/cmake.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import { FlareError } from "./errors.js";
@@ -35,29 +35,47 @@ export function buildDirectory(projectDirectory, mode) {
35
35
  return path.join(projectDirectory, "build", mode === "Debug" ? "debug" : "release");
36
36
  }
37
37
 
38
- export async function configureProject(projectDirectory, { directory, mode, env, logger, frontendDirectory }) {
38
+ export function msvcUtf8Arguments(directory, { platform = process.platform, cxxFlags = process.env.CXXFLAGS ?? "" } = {}) {
39
+ if (platform !== "win32") return [];
40
+ const cacheFile = path.join(directory, "CMakeCache.txt");
41
+ const cache = existsSync(cacheFile) ? readFileSync(cacheFile, "utf8") : "";
42
+ const existing = cache.match(/^CMAKE_CXX_FLAGS:STRING=(.*)$/m)?.[1] ?? cxxFlags;
43
+ if (/(?:^|\s)\/utf-8(?:\s|$)/i.test(existing)) return [];
44
+ return [`-DCMAKE_CXX_FLAGS:STRING=${[existing.trim(), "/utf-8"].filter(Boolean).join(" ")}`];
45
+ }
46
+
47
+ export async function configureProject(projectDirectory, { directory, mode, env, logger, frontendDirectory, configFile = "flare.config.json", environment = "production" }) {
39
48
  if (!cmakeVersion()) throw new FlareError("cmake was not found in PATH");
40
49
  const outputDirectory = path.join(directory, "bin");
41
50
  const configuration = mode.toUpperCase();
51
+ const generator = process.env.CMAKE_GENERATOR ?? "";
52
+ const multiConfiguration = generator
53
+ ? /^(Visual Studio|Xcode|Ninja Multi-Config)/.test(generator)
54
+ : process.platform === "win32";
55
+ const prebuiltSdk = existsSync(path.join(projectDirectory, "vendor", "flare", "sdk-manifest.json"));
42
56
  const args = [
43
57
  "-S",
44
58
  projectDirectory,
45
59
  "-B",
46
60
  directory,
47
61
  ...generatorArguments(),
48
- `-DCMAKE_BUILD_TYPE=${mode}`,
62
+ ...(!multiConfiguration ? [`-DCMAKE_BUILD_TYPE=${mode}`] : []),
49
63
  `-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${outputDirectory}`,
50
64
  `-DCMAKE_RUNTIME_OUTPUT_DIRECTORY_${configuration}=${outputDirectory}`,
51
65
  "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
52
66
  `-DFLARE_FRONTEND_DIR=${frontendDirectory ?? path.join(projectDirectory, "frontend")}`,
53
- "-DFLARE_BUILD_TOOLS=OFF",
54
- "-DFLARE_BUILD_EXAMPLES=OFF",
55
- "-DFLARE_BUILD_TESTS=OFF",
56
- "-DFLARE_INSTALL=OFF"
67
+ ...(!prebuiltSdk ? ["-DFLARE_BUILD_EXAMPLES=OFF", "-DFLARE_BUILD_TESTS=OFF", "-DFLARE_INSTALL=OFF"] : []),
68
+ ...msvcUtf8Arguments(directory, { cxxFlags: env?.CXXFLAGS ?? process.env.CXXFLAGS ?? "" })
57
69
  ];
58
70
  logger.debug(`cmake ${args.join(" ")}`);
59
71
  const result = await run("cmake", args, { cwd: projectDirectory, env });
60
72
  if (result.code !== 0) throw new FlareError("cmake configuration failed");
73
+ if (process.platform === "win32") {
74
+ const cache = readFileSync(path.join(directory, "CMakeCache.txt"), "utf8");
75
+ if (!/^FLARE_NATIVE_WEBVIEW:BOOL=ON$/m.test(cache)) {
76
+ throw new FlareError("Windows build has no native webview backend; configure with -DFLARE_NATIVE_WEBVIEW=ON");
77
+ }
78
+ }
61
79
  }
62
80
 
63
81
  export async function buildProject(projectDirectory, { directory, mode, target, env }) {
@@ -3,6 +3,7 @@ export const COMMANDS = [
3
3
  { name: "dev", usage: "dev", description: "build and run the application in development mode" },
4
4
  { name: "build", usage: "build [--debug]", description: "build the application" },
5
5
  { name: "bundle", usage: "bundle", description: "build and prepare the platform bundle targets" },
6
+ { name: "codegen", usage: "codegen [--language=header|json]", description: "generate a typed C++ config, JSON or an asset manifest" },
6
7
  { name: "icon", usage: "icon <source.png>", description: "generate the icon set from a square png" },
7
8
  { name: "info", usage: "info", description: "print project and platform information" },
8
9
  { name: "inspect", usage: "inspect", description: "print the resolved configuration layers" },
@@ -0,0 +1,14 @@
1
+ import { writeEmbeddedConfig } from "./embedded-config.js";
2
+
3
+ const [projectDirectory, configFile, environment, output] = process.argv.slice(2);
4
+ if (!projectDirectory || !configFile || !environment || !output) {
5
+ process.stderr.write("usage: embed-config-cli.js <project> <config> <environment> <output>\n");
6
+ process.exitCode = 1;
7
+ } else {
8
+ try {
9
+ writeEmbeddedConfig({ projectDirectory, configFile, environment, output });
10
+ } catch (error) {
11
+ process.stderr.write(`failed to embed configuration: ${error.message}\n`);
12
+ process.exitCode = 1;
13
+ }
14
+ }