@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const protocol = 'flare';
|
|
2
|
+
const defaultHost = 'localhost';
|
|
3
|
+
|
|
4
|
+
const coreCommands = new Map();
|
|
5
|
+
const eventListeners = new Map();
|
|
6
|
+
const channels = new Map();
|
|
7
|
+
|
|
8
|
+
let listenerSequence = 0;
|
|
9
|
+
let channelSequence = 0;
|
|
10
|
+
|
|
11
|
+
function resolveBridge() {
|
|
12
|
+
const bridge = globalThis.__FLARE_BRIDGE__;
|
|
13
|
+
if (!bridge) {
|
|
14
|
+
throw new Error('flare: the ipc bridge is not available in this context');
|
|
15
|
+
}
|
|
16
|
+
return bridge;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeCallback(callback) {
|
|
20
|
+
if (callback === undefined || callback === null) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (typeof callback === 'function') {
|
|
24
|
+
return callback;
|
|
25
|
+
}
|
|
26
|
+
throw new TypeError('flare: callback must be a function');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isFlare() {
|
|
30
|
+
return typeof globalThis.__FLARE_BRIDGE__ !== 'undefined';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function metadata() {
|
|
34
|
+
const bridge = globalThis.__FLARE_BRIDGE__;
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
protocol,
|
|
37
|
+
host: (bridge && bridge.host) || defaultHost,
|
|
38
|
+
version: (bridge && bridge.version) || '0.0.0'
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function invoke(command, payload = {}, options = {}) {
|
|
43
|
+
if (typeof command !== 'string' || command.length === 0) {
|
|
44
|
+
throw new TypeError('flare: command must be a non empty string');
|
|
45
|
+
}
|
|
46
|
+
const bridge = resolveBridge();
|
|
47
|
+
const request = {
|
|
48
|
+
id: options.id || `${Date.now().toString(36)}-${(listenerSequence += 1).toString(36)}`,
|
|
49
|
+
command,
|
|
50
|
+
payload: payload === undefined ? {} : payload,
|
|
51
|
+
window: options.window || '',
|
|
52
|
+
webview: options.webview || ''
|
|
53
|
+
};
|
|
54
|
+
const response = await bridge.invoke(request);
|
|
55
|
+
if (response === undefined || response === null) {
|
|
56
|
+
throw new Error(`flare: command '${command}' returned no response`);
|
|
57
|
+
}
|
|
58
|
+
if (response.successful === false) {
|
|
59
|
+
const error = response.error || {};
|
|
60
|
+
const failure = new Error(error.message || `flare: command '${command}' failed`);
|
|
61
|
+
failure.code = error.code || 'command-failed';
|
|
62
|
+
failure.details = error.details;
|
|
63
|
+
throw failure;
|
|
64
|
+
}
|
|
65
|
+
return response.result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function listen(event, handler, options = {}) {
|
|
69
|
+
if (typeof event !== 'string' || event.length === 0) {
|
|
70
|
+
throw new TypeError('flare: event name must be a non empty string');
|
|
71
|
+
}
|
|
72
|
+
const callback = normalizeCallback(handler);
|
|
73
|
+
listenerSequence += 1;
|
|
74
|
+
const id = listenerSequence;
|
|
75
|
+
const window = options.window || '';
|
|
76
|
+
if (!eventListeners.has(event)) {
|
|
77
|
+
eventListeners.set(event, new Map());
|
|
78
|
+
}
|
|
79
|
+
eventListeners.get(event).set(id, { callback, window, once: false });
|
|
80
|
+
const bridge = resolveBridge();
|
|
81
|
+
if (typeof bridge.attachListener === 'function') {
|
|
82
|
+
bridge.attachListener(event, id, callback);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await bridge.listen({ event, id, window });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
eventListeners.get(event)?.delete(id);
|
|
88
|
+
await bridge.unlisten?.({ event, id });
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
return () => unlisten(event, id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function once(event, handler, options = {}) {
|
|
95
|
+
const remove = await listen(
|
|
96
|
+
event,
|
|
97
|
+
(payload) => {
|
|
98
|
+
remove();
|
|
99
|
+
if (typeof handler === 'function') {
|
|
100
|
+
handler(payload);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
options
|
|
104
|
+
);
|
|
105
|
+
return remove;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function unlisten(event, id) {
|
|
109
|
+
const entries = eventListeners.get(event);
|
|
110
|
+
if (entries) {
|
|
111
|
+
entries.delete(id);
|
|
112
|
+
if (entries.size === 0) {
|
|
113
|
+
eventListeners.delete(event);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const bridge = globalThis.__FLARE_BRIDGE__;
|
|
117
|
+
if (bridge && typeof bridge.unlisten === 'function') {
|
|
118
|
+
await bridge.unlisten({ event, id });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function emit(event, payload = {}) {
|
|
123
|
+
const bridge = resolveBridge();
|
|
124
|
+
await bridge.emit({ event, payload });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function dispatch(event, payload = {}) {
|
|
128
|
+
const entries = eventListeners.get(event);
|
|
129
|
+
if (!entries) {
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
const pending = [];
|
|
133
|
+
for (const entry of entries.values()) {
|
|
134
|
+
pending.push(entry);
|
|
135
|
+
}
|
|
136
|
+
for (const entry of pending) {
|
|
137
|
+
if (entry.once) {
|
|
138
|
+
entries.delete(entry);
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
entry.callback(payload);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(`flare: listener for '${event}' failed`, error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return pending.length;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function createChannel(onMessage) {
|
|
150
|
+
channelSequence += 1;
|
|
151
|
+
const id = channelSequence;
|
|
152
|
+
const name = `channel-${id}`;
|
|
153
|
+
channels.set(id, { onMessage, closed: false });
|
|
154
|
+
return {
|
|
155
|
+
identifier: id,
|
|
156
|
+
name,
|
|
157
|
+
async send(payload = {}) {
|
|
158
|
+
const bridge = resolveBridge();
|
|
159
|
+
await bridge.channel({ id, payload });
|
|
160
|
+
},
|
|
161
|
+
deliver(message) {
|
|
162
|
+
if (onMessage) {
|
|
163
|
+
onMessage(message);
|
|
164
|
+
}
|
|
165
|
+
if (message && message.state === 'closed') {
|
|
166
|
+
channels.delete(id);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
close() {
|
|
170
|
+
channels.delete(id);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function currentWindow() {
|
|
176
|
+
const label = (globalThis.__FLARE_BRIDGE__ && globalThis.__FLARE_BRIDGE__.window) || 'main';
|
|
177
|
+
return {
|
|
178
|
+
label,
|
|
179
|
+
async close() {
|
|
180
|
+
return invoke('flare:window:close', { label });
|
|
181
|
+
},
|
|
182
|
+
async setTitle(title) {
|
|
183
|
+
return invoke('flare:window:set-title', { label, title });
|
|
184
|
+
},
|
|
185
|
+
async show() {
|
|
186
|
+
return invoke('flare:window:show', { label });
|
|
187
|
+
},
|
|
188
|
+
async hide() {
|
|
189
|
+
return invoke('flare:window:hide', { label });
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const app = Object.freeze({
|
|
195
|
+
version: () => invoke('flare:app:version'),
|
|
196
|
+
describe: () => invoke('flare:app:describe'),
|
|
197
|
+
config: () => invoke('flare:app:config'),
|
|
198
|
+
platform: () => invoke('flare:app:platform'),
|
|
199
|
+
exit: () => invoke('flare:app:exit')
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
export const window = Object.freeze({
|
|
203
|
+
list: () => invoke('flare:window:list'),
|
|
204
|
+
emit: (event, payload = {}) => invoke('flare:window:emit', { event, payload })
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
export const plugins = Object.freeze({
|
|
208
|
+
list: () => invoke('flare:plugin:list')
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
export const menu = Object.freeze({
|
|
212
|
+
describe: () => invoke('flare:menu:describe'),
|
|
213
|
+
set: (items) => invoke('flare:menu:set', { items }),
|
|
214
|
+
dispatch: (id) => invoke('flare:menu:dispatch', { id }),
|
|
215
|
+
onCommand: (handler) => listen('flare:menu:command', (payload) => handler(payload.id, payload))
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
export const commands = Object.freeze({
|
|
219
|
+
register(name, handler) {
|
|
220
|
+
if (typeof name !== 'string' || typeof handler !== 'function') {
|
|
221
|
+
throw new TypeError('flare: register expects a name and a handler');
|
|
222
|
+
}
|
|
223
|
+
coreCommands.set(name, handler);
|
|
224
|
+
},
|
|
225
|
+
unregister(name) {
|
|
226
|
+
return coreCommands.delete(name);
|
|
227
|
+
},
|
|
228
|
+
has(name) {
|
|
229
|
+
return coreCommands.has(name);
|
|
230
|
+
},
|
|
231
|
+
names() {
|
|
232
|
+
return Array.from(coreCommands.keys());
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
export const log = Object.freeze({
|
|
237
|
+
trace: (message) => emit('flare:log:trace', { message }),
|
|
238
|
+
debug: (message) => emit('flare:log:debug', { message }),
|
|
239
|
+
info: (message) => emit('flare:log:info', { message }),
|
|
240
|
+
warn: (message) => emit('flare:log:warn', { message }),
|
|
241
|
+
error: (message) => emit('flare:log:error', { message })
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
export const path = Object.freeze({
|
|
245
|
+
resolve: (name) => invoke('flare:path:resolve', { name }),
|
|
246
|
+
all: () => invoke('flare:path:all')
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export const tray = Object.freeze({
|
|
250
|
+
describe: () => invoke('flare:tray:describe'),
|
|
251
|
+
setIcon: (icon) => invoke('flare:tray:set-icon', { icon }),
|
|
252
|
+
setTooltip: (tooltip) => invoke('flare:tray:set-tooltip', { tooltip }),
|
|
253
|
+
setTitle: (title) => invoke('flare:tray:set-title', { title }),
|
|
254
|
+
setVisible: (visible) => invoke('flare:tray:set-visible', { visible }),
|
|
255
|
+
setMenu: (items) => invoke('flare:tray:set-menu', { items }),
|
|
256
|
+
dispatch: (id) => invoke('flare:tray:dispatch', { id })
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export default {
|
|
260
|
+
app,
|
|
261
|
+
commands,
|
|
262
|
+
createChannel,
|
|
263
|
+
currentWindow,
|
|
264
|
+
dispatch,
|
|
265
|
+
emit,
|
|
266
|
+
invoke,
|
|
267
|
+
isFlare,
|
|
268
|
+
listen,
|
|
269
|
+
log,
|
|
270
|
+
menu,
|
|
271
|
+
metadata,
|
|
272
|
+
once,
|
|
273
|
+
path,
|
|
274
|
+
plugins,
|
|
275
|
+
tray,
|
|
276
|
+
unlisten,
|
|
277
|
+
window
|
|
278
|
+
};
|
package/src/templates/project.js
CHANGED
|
@@ -7,7 +7,7 @@ export function embeddedIdentifier(name) {
|
|
|
7
7
|
.join("");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function configTemplate({ name, identifier, version }) {
|
|
10
|
+
export function configTemplate({ name, identifier, version, template = "javascript" }) {
|
|
11
11
|
return {
|
|
12
12
|
$schema: SCHEMA_URL,
|
|
13
13
|
package: { productName: name, version },
|
|
@@ -18,11 +18,11 @@ export function configTemplate({ name, identifier, version }) {
|
|
|
18
18
|
version
|
|
19
19
|
},
|
|
20
20
|
build: {
|
|
21
|
-
frontendDist: "frontend",
|
|
22
|
-
devUrl: "",
|
|
23
|
-
beforeDevCommand: "",
|
|
21
|
+
frontendDist: template === "typescript" ? "dist" : "frontend",
|
|
22
|
+
devUrl: template === "typescript" ? "http://127.0.0.1:5173" : "",
|
|
23
|
+
beforeDevCommand: template === "typescript" ? "npm run frontend:dev" : "",
|
|
24
24
|
afterDevCommand: "",
|
|
25
|
-
beforeBuildCommand: "",
|
|
25
|
+
beforeBuildCommand: template === "typescript" ? "npm run frontend:build" : "",
|
|
26
26
|
afterBuildCommand: ""
|
|
27
27
|
},
|
|
28
28
|
bundle: {
|
|
@@ -51,7 +51,7 @@ export function configTemplate({ name, identifier, version }) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export function packageJsonTemplate({ name, version, cliVersion }) {
|
|
54
|
+
export function packageJsonTemplate({ name, version, cliVersion, template = "javascript" }) {
|
|
55
55
|
return {
|
|
56
56
|
name,
|
|
57
57
|
version,
|
|
@@ -61,15 +61,17 @@ export function packageJsonTemplate({ name, version, cliVersion }) {
|
|
|
61
61
|
dev: "flare dev",
|
|
62
62
|
build: "flare build",
|
|
63
63
|
bundle: "flare bundle",
|
|
64
|
-
icon: "flare icon icons/icon.png"
|
|
64
|
+
icon: "flare icon icons/icon.png",
|
|
65
|
+
...(template === "typescript" ? { "frontend:dev": "vite --host 127.0.0.1", "frontend:build": "tsc --noEmit && vite build" } : {})
|
|
65
66
|
},
|
|
66
67
|
devDependencies: {
|
|
67
|
-
"@flare-desktop/cli": `^${cliVersion}
|
|
68
|
+
"@flare-desktop/cli": `^${cliVersion}`,
|
|
69
|
+
...(template === "typescript" ? { typescript: "^5.9.3", vite: "^6.4.5" } : {})
|
|
68
70
|
}
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
export function cmakeTemplate({ name, version, coreVersion }) {
|
|
74
|
+
export function cmakeTemplate({ name, version, coreVersion, sdkMode = "source" }) {
|
|
73
75
|
return `cmake_minimum_required(VERSION 3.24)
|
|
74
76
|
|
|
75
77
|
project(${name} VERSION ${version} LANGUAGES CXX)
|
|
@@ -77,17 +79,48 @@ project(${name} VERSION ${version} LANGUAGES CXX)
|
|
|
77
79
|
set(CMAKE_CXX_STANDARD 20)
|
|
78
80
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
if(WIN32 AND NOT DEFINED FLARE_NATIVE_WEBVIEW)
|
|
83
|
+
set(FLARE_NATIVE_WEBVIEW ON CACHE BOOL "Use the Windows system webview")
|
|
84
|
+
endif()
|
|
85
|
+
|
|
86
|
+
${sdkMode === "prebuilt" ? `add_subdirectory("\${CMAKE_CURRENT_SOURCE_DIR}/vendor/flare" "\${CMAKE_CURRENT_BINARY_DIR}/flare-sdk")` : `include(FetchContent)
|
|
81
87
|
|
|
82
88
|
FetchContent_Declare(
|
|
83
89
|
flare
|
|
84
90
|
GIT_REPOSITORY https://github.com/flare-desktop/flare.git
|
|
85
91
|
GIT_TAG v${coreVersion}
|
|
86
92
|
GIT_SHALLOW TRUE)
|
|
87
|
-
FetchContent_MakeAvailable(flare)
|
|
93
|
+
FetchContent_MakeAvailable(flare)`}
|
|
88
94
|
|
|
89
95
|
add_executable(${name} source/main.cpp)
|
|
90
96
|
target_link_libraries(${name} PRIVATE flare::core flare::assets)
|
|
97
|
+
if(MSVC)
|
|
98
|
+
target_compile_options(${name} PRIVATE /utf-8)
|
|
99
|
+
endif()
|
|
100
|
+
|
|
101
|
+
find_program(FLARE_NODE_EXECUTABLE node REQUIRED)
|
|
102
|
+
set(FLARE_CONFIG_FILE "flare.config.json" CACHE STRING "Source application configuration")
|
|
103
|
+
set(FLARE_CONFIG_ENVIRONMENT "production" CACHE STRING "Build configuration layer")
|
|
104
|
+
get_filename_component(FLARE_CONFIG_ABSOLUTE "\${FLARE_CONFIG_FILE}" ABSOLUTE BASE_DIR "\${CMAKE_CURRENT_SOURCE_DIR}")
|
|
105
|
+
get_filename_component(FLARE_CONFIG_DIRECTORY "\${FLARE_CONFIG_ABSOLUTE}" DIRECTORY)
|
|
106
|
+
get_filename_component(FLARE_CONFIG_NAME "\${FLARE_CONFIG_ABSOLUTE}" NAME_WE)
|
|
107
|
+
file(GLOB FLARE_CONFIG_LAYERS CONFIGURE_DEPENDS "\${FLARE_CONFIG_DIRECTORY}/\${FLARE_CONFIG_NAME}*.json")
|
|
108
|
+
set(FLARE_CONFIG_GENERATOR "\${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@flare-desktop/cli/src/lib/embed-config-cli.js")
|
|
109
|
+
set(FLARE_CONFIG_IMPLEMENTATION "\${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@flare-desktop/cli/src/lib/embedded-config.js")
|
|
110
|
+
set(FLARE_CONFIG_LOADER "\${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@flare-desktop/cli/src/lib/project.js")
|
|
111
|
+
set(FLARE_GENERATED_CONFIG "\${CMAKE_CURRENT_BINARY_DIR}/flare-embedded-config.h")
|
|
112
|
+
file(GENERATE OUTPUT "\${CMAKE_CURRENT_BINARY_DIR}/flare-config-selection.txt"
|
|
113
|
+
CONTENT "\${FLARE_CONFIG_FILE}\n\${FLARE_CONFIG_ENVIRONMENT}\n")
|
|
114
|
+
add_custom_command(
|
|
115
|
+
OUTPUT "\${FLARE_GENERATED_CONFIG}"
|
|
116
|
+
COMMAND "\${FLARE_NODE_EXECUTABLE}" "\${FLARE_CONFIG_GENERATOR}"
|
|
117
|
+
"\${CMAKE_CURRENT_SOURCE_DIR}" "\${FLARE_CONFIG_FILE}" "\${FLARE_CONFIG_ENVIRONMENT}"
|
|
118
|
+
"\${FLARE_GENERATED_CONFIG}"
|
|
119
|
+
DEPENDS "\${FLARE_CONFIG_GENERATOR}" "\${FLARE_CONFIG_IMPLEMENTATION}" "\${FLARE_CONFIG_LOADER}"
|
|
120
|
+
"\${CMAKE_CURRENT_BINARY_DIR}/flare-config-selection.txt" \${FLARE_CONFIG_LAYERS}
|
|
121
|
+
VERBATIM)
|
|
122
|
+
target_sources(${name} PRIVATE "\${FLARE_GENERATED_CONFIG}")
|
|
123
|
+
target_include_directories(${name} PRIVATE "\${CMAKE_CURRENT_BINARY_DIR}")
|
|
91
124
|
|
|
92
125
|
set(FLARE_FRONTEND_DIR "frontend" CACHE PATH "Frontend directory")
|
|
93
126
|
file(TO_CMAKE_PATH "\${FLARE_FRONTEND_DIR}" FLARE_FRONTEND_DIR)
|
|
@@ -96,13 +129,14 @@ FlareEmbedFrontend(${name} "\${FLARE_FRONTEND_DIR}")
|
|
|
96
129
|
`;
|
|
97
130
|
}
|
|
98
131
|
|
|
99
|
-
export function sourceTemplate({ name
|
|
132
|
+
export function sourceTemplate({ name }) {
|
|
100
133
|
const embedded = embeddedIdentifier(name);
|
|
101
134
|
return `#include <flare/config/config.h>
|
|
102
135
|
#include <flare/core/application.h>
|
|
103
136
|
#include <flare/log/log.h>
|
|
104
137
|
|
|
105
138
|
#include <${name}-frontend.h>
|
|
139
|
+
#include <flare-embedded-config.h>
|
|
106
140
|
|
|
107
141
|
#include <cstdlib>
|
|
108
142
|
#include <string>
|
|
@@ -115,20 +149,9 @@ int main() {
|
|
|
115
149
|
const bool development = std::getenv("FLARE_DEV") != nullptr;
|
|
116
150
|
|
|
117
151
|
Flare::ApplicationOptions options;
|
|
118
|
-
options.config =
|
|
119
|
-
options.config.app.productName = "${name}";
|
|
120
|
-
options.config.windows.front().title = "${name}";
|
|
152
|
+
options.config = FlareGenerated::EmbeddedConfig();
|
|
121
153
|
options.development = development;
|
|
122
154
|
|
|
123
|
-
std::string error;
|
|
124
|
-
Flare::ConfigLoader loader(
|
|
125
|
-
Flare::ConfigMergeOptions{"flare.config.json", development ? "development" : "production", "", {}});
|
|
126
|
-
if (const auto loaded = loader.Load(error); loaded.has_value()) {
|
|
127
|
-
options.config = *loaded;
|
|
128
|
-
} else {
|
|
129
|
-
Flare::LogWarn("using the built-in configuration: {}", error);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
155
|
const char* devUrl = std::getenv("FLARE_DEV_URL");
|
|
133
156
|
if (devUrl != nullptr) options.devUrl = devUrl;
|
|
134
157
|
if (development && options.devUrl.empty()) options.frontendDirectory = FLARE_FRONTEND_DIR;
|
|
@@ -150,7 +173,7 @@ int main() {
|
|
|
150
173
|
`;
|
|
151
174
|
}
|
|
152
175
|
|
|
153
|
-
export function indexHtmlTemplate(name) {
|
|
176
|
+
export function indexHtmlTemplate(name, template = "javascript") {
|
|
154
177
|
return `<!doctype html>
|
|
155
178
|
<html lang="en">
|
|
156
179
|
<head>
|
|
@@ -166,7 +189,7 @@ export function indexHtmlTemplate(name) {
|
|
|
166
189
|
<button id="greet">call the backend</button>
|
|
167
190
|
<pre id="output"></pre>
|
|
168
191
|
</main>
|
|
169
|
-
<script type="module" src="main.js"></script>
|
|
192
|
+
<script type="module" src="${template === "typescript" ? "main.ts" : "main.js"}"></script>
|
|
170
193
|
</body>
|
|
171
194
|
</html>
|
|
172
195
|
`;
|
|
@@ -246,8 +269,44 @@ document.querySelector("#greet").addEventListener("click", async () => {
|
|
|
246
269
|
`;
|
|
247
270
|
}
|
|
248
271
|
|
|
272
|
+
export function typescriptTemplate() {
|
|
273
|
+
return `import { app, invoke } from "/core.js";
|
|
274
|
+
|
|
275
|
+
const output = document.querySelector<HTMLPreElement>("#output");
|
|
276
|
+
const button = document.querySelector<HTMLButtonElement>("#greet");
|
|
277
|
+
|
|
278
|
+
button?.addEventListener("click", async () => {
|
|
279
|
+
const message = await invoke<string>("greet", { name: "flare" });
|
|
280
|
+
const version = await app.version();
|
|
281
|
+
if (output) output.textContent = \`\${message} (\${version})\`;
|
|
282
|
+
});
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function tsconfigTemplate() {
|
|
287
|
+
return {
|
|
288
|
+
compilerOptions: {
|
|
289
|
+
target: "ES2022", lib: ["ES2022", "DOM"], module: "ESNext", moduleResolution: "Bundler",
|
|
290
|
+
strict: true, noEmit: true, skipLibCheck: true, types: ["vite/client"],
|
|
291
|
+
paths: { "/core.js": ["./frontend/core.d.ts"] }
|
|
292
|
+
},
|
|
293
|
+
include: ["frontend/main.ts"]
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function viteConfigTemplate() {
|
|
298
|
+
return `import { defineConfig } from "vite";
|
|
299
|
+
export default defineConfig({
|
|
300
|
+
root: "frontend",
|
|
301
|
+
base: "./",
|
|
302
|
+
build: { outDir: "../dist", emptyOutDir: true },
|
|
303
|
+
server: { host: "127.0.0.1", port: 5173, strictPort: true }
|
|
304
|
+
});
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
|
|
249
308
|
export function gitignoreTemplate() {
|
|
250
|
-
return "build/\ntarget/\nnode_modules/\n.DS_Store\n";
|
|
309
|
+
return "build/\ntarget/\nnode_modules/\ndist/\n.DS_Store\n";
|
|
251
310
|
}
|
|
252
311
|
|
|
253
312
|
export function readmeTemplate(name) {
|
|
@@ -270,7 +329,7 @@ npm run bundle
|
|
|
270
329
|
\`\`\`
|
|
271
330
|
|
|
272
331
|
\`npm run dev\` builds the C++ core and serves \`frontend/\` in the system webview.
|
|
273
|
-
\`npm run build\` compiles the frontend into the executable
|
|
332
|
+
\`npm run build\` compiles the frontend and the effective typed configuration into the executable. No JSON file is needed at runtime. On Windows, the native webview is enabled automatically. A configuration change requires rebuilding.
|
|
274
333
|
\`npm run bundle\` assembles the configured bundle targets under \`target/bundle\`.
|
|
275
334
|
|
|
276
335
|
## Icons
|