@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 +25 -0
- package/package.json +5 -2
- package/src/assets/flare-mark.png +0 -0
- package/src/cli.js +4 -0
- package/src/commands/build.js +2 -0
- package/src/commands/bundle.js +2 -0
- package/src/commands/codegen.js +53 -0
- package/src/commands/dev.js +9 -1
- package/src/commands/init.js +52 -11
- package/src/help.js +7 -1
- package/src/lib/args.js +5 -0
- package/src/lib/build.js +27 -2
- package/src/lib/cmake.js +25 -7
- package/src/lib/commands.js +1 -0
- package/src/lib/embed-config-cli.js +14 -0
- package/src/lib/embedded-config.js +181 -0
- package/src/lib/icons.js +7 -17
- package/src/lib/project.js +4 -3
- package/src/lib/sdk.js +54 -0
- package/src/release-versions.js +2 -0
- package/src/templates/clang-format.txt +32 -0
- package/src/templates/core.d.ts +135 -0
- package/src/templates/core.js +278 -0
- package/src/templates/project.js +87 -28
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.
|
|
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
|
|
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,
|
package/src/commands/build.js
CHANGED
|
@@ -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
|
});
|
package/src/commands/bundle.js
CHANGED
|
@@ -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
|
+
}
|
package/src/commands/dev.js
CHANGED
|
@@ -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
|
});
|
package/src/commands/init.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
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
|
|
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:
|
|
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
|
|
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
|
-
"-
|
|
54
|
-
"
|
|
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 }) {
|
package/src/lib/commands.js
CHANGED
|
@@ -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
|
+
}
|