reset-framework-cli 1.2.1 → 1.2.2
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/package.json +8 -6
- package/src/commands/build.js +20 -2
- package/src/commands/dev.js +24 -3
- package/src/commands/doctor.js +7 -0
- package/src/commands/init.js +82 -2
- package/src/lib/backend.js +123 -0
- package/src/lib/output.js +17 -2
- package/src/lib/project.js +38 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reset-framework-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Command-line tooling for Reset Framework.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
"node": ">=20.19.0"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@reset-framework/
|
|
15
|
-
"@reset-framework/
|
|
14
|
+
"@reset-framework/backend": "1.2.2",
|
|
15
|
+
"@reset-framework/schema": "1.2.2",
|
|
16
|
+
"@reset-framework/sdk": "1.2.2",
|
|
17
|
+
"esbuild": "^0.25.10"
|
|
16
18
|
},
|
|
17
19
|
"optionalDependencies": {
|
|
18
|
-
"@reset-framework/runtime-darwin-arm64": "1.2.
|
|
19
|
-
"@reset-framework/runtime-darwin-x64": "1.2.
|
|
20
|
-
"@reset-framework/runtime-win32-x64": "1.2.
|
|
20
|
+
"@reset-framework/runtime-darwin-arm64": "1.2.2",
|
|
21
|
+
"@reset-framework/runtime-darwin-x64": "1.2.2",
|
|
22
|
+
"@reset-framework/runtime-win32-x64": "1.2.2"
|
|
21
23
|
},
|
|
22
24
|
"bin": {
|
|
23
25
|
"reset-framework-cli": "src/index.js"
|
package/src/commands/build.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareFrameworkRuntime } from "../lib/framework.js"
|
|
2
|
+
import { prepareAppBackend } from "../lib/backend.js"
|
|
2
3
|
import { stageMacOSAppBundle, stageWindowsAppBundle } from "../lib/output.js"
|
|
3
4
|
import { resolvePackageManagerCommand, runCommand } from "../lib/process.js"
|
|
4
5
|
import {
|
|
@@ -12,7 +13,8 @@ import {
|
|
|
12
13
|
resolveFrontendDir,
|
|
13
14
|
resolveFrameworkBuildPaths,
|
|
14
15
|
resolveFrameworkRuntimeStrategy,
|
|
15
|
-
resolveFrameworkPaths
|
|
16
|
+
resolveFrameworkPaths,
|
|
17
|
+
resolveOptionalBackendEntry
|
|
16
18
|
} from "../lib/project.js"
|
|
17
19
|
import {
|
|
18
20
|
createProgress,
|
|
@@ -41,11 +43,13 @@ export async function run(context) {
|
|
|
41
43
|
const outputPaths = resolveAppOutputPaths(appPaths, config)
|
|
42
44
|
const configPath = resolveConfigPath(appPaths)
|
|
43
45
|
const frontendDir = resolveFrontendDir(appPaths, config)
|
|
46
|
+
const backendEntry = resolveOptionalBackendEntry(appPaths)
|
|
44
47
|
const frameworkBuildPaths = resolveFrameworkBuildPaths(appPaths)
|
|
45
48
|
const runtimeStrategy = resolveFrameworkRuntimeStrategy(frameworkPaths, { preferSource })
|
|
46
49
|
const steps = [
|
|
47
50
|
!skipFrontend ? "Build frontend" : null,
|
|
48
51
|
!skipRuntime ? "Prepare runtime" : null,
|
|
52
|
+
!skipStage && backendEntry ? "Bundle backend sidecar" : null,
|
|
49
53
|
!skipStage ? "Stage desktop bundle" : null
|
|
50
54
|
].filter(Boolean)
|
|
51
55
|
|
|
@@ -54,6 +58,7 @@ export async function run(context) {
|
|
|
54
58
|
printKeyValueTable([
|
|
55
59
|
["Config", configPath],
|
|
56
60
|
["Frontend", frontendDir],
|
|
61
|
+
["Backend", backendEntry ?? "not configured"],
|
|
57
62
|
["Package manager", packageManager],
|
|
58
63
|
["Output", outputPaths.appBundlePath],
|
|
59
64
|
["Runtime", `${runtimeStrategy.kind === "prebuilt" ? "bundled" : "source"} (${runtimeStrategy.label})`]
|
|
@@ -70,6 +75,11 @@ export async function run(context) {
|
|
|
70
75
|
? (runtimeStrategy.kind === "prebuilt" ? "Reuse the bundled runtime package" : "Skip the source runtime build")
|
|
71
76
|
: (runtimeStrategy.kind === "prebuilt" ? "Use the bundled runtime package" : "Configure and build the runtime from source")
|
|
72
77
|
],
|
|
78
|
+
[
|
|
79
|
+
backendEntry && !skipStage ? "ready" : "skip",
|
|
80
|
+
"Backend",
|
|
81
|
+
backendEntry && !skipStage ? "Bundle the optional TypeScript sidecar backend" : "No backend staging is required"
|
|
82
|
+
],
|
|
73
83
|
[skipStage ? "skip" : "ready", "Bundle", skipStage ? "Skipping app bundle staging" : "Assemble the final desktop app bundle"]
|
|
74
84
|
])
|
|
75
85
|
|
|
@@ -94,6 +104,8 @@ export async function run(context) {
|
|
|
94
104
|
console.log("")
|
|
95
105
|
const progress = createProgress(steps.length, "Build")
|
|
96
106
|
|
|
107
|
+
let backendArtifact = null
|
|
108
|
+
|
|
97
109
|
if (!skipFrontend) {
|
|
98
110
|
await runCommand(resolvePackageManagerCommand(packageManager), ["run", "build"], {
|
|
99
111
|
cwd: frontendDir,
|
|
@@ -111,13 +123,19 @@ export async function run(context) {
|
|
|
111
123
|
progress.tick(preparedRuntime.kind === "prebuilt" ? "Bundled runtime ready" : "Native runtime built")
|
|
112
124
|
}
|
|
113
125
|
|
|
126
|
+
if (!skipStage && backendEntry) {
|
|
127
|
+
backendArtifact = await prepareAppBackend(frameworkPaths, appPaths, "release")
|
|
128
|
+
progress.tick("Backend sidecar bundled")
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
if (!skipStage) {
|
|
115
132
|
const stageBundle = process.platform === "win32" ? stageWindowsAppBundle : stageMacOSAppBundle
|
|
116
133
|
await stageBundle(frameworkPaths, frameworkBuildPaths, config, {
|
|
117
134
|
dryRun,
|
|
118
135
|
outputPaths,
|
|
119
136
|
configPath,
|
|
120
|
-
preferSource
|
|
137
|
+
preferSource,
|
|
138
|
+
backendArtifact
|
|
121
139
|
})
|
|
122
140
|
progress.tick("Desktop app bundle staged")
|
|
123
141
|
}
|
package/src/commands/dev.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareFrameworkRuntime } from "../lib/framework.js"
|
|
2
|
+
import { prepareAppBackend } from "../lib/backend.js"
|
|
2
3
|
import {
|
|
3
4
|
registerChildCleanup,
|
|
4
5
|
resolvePackageManagerCommand,
|
|
@@ -18,7 +19,8 @@ import {
|
|
|
18
19
|
resolveFrameworkBuildPaths,
|
|
19
20
|
resolveFrameworkRuntimeBinary,
|
|
20
21
|
resolveFrameworkRuntimeStrategy,
|
|
21
|
-
resolveFrameworkPaths
|
|
22
|
+
resolveFrameworkPaths,
|
|
23
|
+
resolveOptionalBackendEntry
|
|
22
24
|
} from "../lib/project.js"
|
|
23
25
|
import {
|
|
24
26
|
createProgress,
|
|
@@ -47,10 +49,12 @@ export async function run(context) {
|
|
|
47
49
|
const devServer = resolveDevServerOptions(config)
|
|
48
50
|
const configPath = resolveConfigPath(appPaths)
|
|
49
51
|
const frontendDir = resolveFrontendDir(appPaths, config)
|
|
52
|
+
const backendEntry = resolveOptionalBackendEntry(appPaths)
|
|
50
53
|
const frameworkBuildPaths = resolveFrameworkBuildPaths(appPaths)
|
|
51
54
|
const runtimeStrategy = resolveFrameworkRuntimeStrategy(frameworkPaths, { preferSource })
|
|
52
55
|
const steps = [
|
|
53
56
|
!skipRuntime ? "Prepare runtime" : null,
|
|
57
|
+
backendEntry ? "Bundle backend sidecar" : null,
|
|
54
58
|
!skipFrontend ? "Start frontend dev server" : null,
|
|
55
59
|
!noOpen ? "Launch desktop window" : null
|
|
56
60
|
].filter(Boolean)
|
|
@@ -60,6 +64,7 @@ export async function run(context) {
|
|
|
60
64
|
printKeyValueTable([
|
|
61
65
|
["Config", configPath],
|
|
62
66
|
["Frontend", frontendDir],
|
|
67
|
+
["Backend", backendEntry ?? "not configured"],
|
|
63
68
|
["Package manager", packageManager],
|
|
64
69
|
["Dev URL", config.frontend.devUrl],
|
|
65
70
|
["Runtime", `${runtimeStrategy.kind === "prebuilt" ? "bundled" : "source"} (${runtimeStrategy.label})`]
|
|
@@ -75,6 +80,7 @@ export async function run(context) {
|
|
|
75
80
|
? (runtimeStrategy.kind === "prebuilt" ? "Reuse the bundled runtime package" : "Reuse the existing source runtime build")
|
|
76
81
|
: (runtimeStrategy.kind === "prebuilt" ? "Use the bundled runtime package" : "Configure and build the runtime from source")
|
|
77
82
|
],
|
|
83
|
+
[backendEntry ? "ready" : "skip", "Backend", backendEntry ? "Bundle the optional TypeScript sidecar backend" : "No backend entry was found under backend/"],
|
|
78
84
|
[skipFrontend ? "skip" : "ready", "Frontend", skipFrontend ? "Expecting an already running dev server" : "Start the frontend dev server and wait for readiness"],
|
|
79
85
|
[noOpen ? "skip" : "ready", "Window", noOpen ? "Do not launch the desktop window" : "Start the native host with the active project config"]
|
|
80
86
|
])
|
|
@@ -110,6 +116,12 @@ export async function run(context) {
|
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
const children = []
|
|
119
|
+
let backendArtifact = null
|
|
120
|
+
|
|
121
|
+
if (backendEntry) {
|
|
122
|
+
backendArtifact = await prepareAppBackend(frameworkPaths, appPaths, "dev")
|
|
123
|
+
progress.tick("Backend sidecar bundled")
|
|
124
|
+
}
|
|
113
125
|
|
|
114
126
|
if (!skipFrontend) {
|
|
115
127
|
const frontendProcess = spawnCommand(resolvePackageManagerCommand(packageManager), [
|
|
@@ -142,7 +154,15 @@ export async function run(context) {
|
|
|
142
154
|
cwd: appPaths.appRoot,
|
|
143
155
|
env: {
|
|
144
156
|
RESET_CONFIG_PATH: configPath,
|
|
145
|
-
RESET_FRONTEND_DEV_URL: config.frontend.devUrl
|
|
157
|
+
RESET_FRONTEND_DEV_URL: config.frontend.devUrl,
|
|
158
|
+
...(backendArtifact
|
|
159
|
+
? {
|
|
160
|
+
RESET_BACKEND_EXECUTABLE: backendArtifact.runtimeExecutablePath,
|
|
161
|
+
RESET_BACKEND_RUNNER: backendArtifact.runnerPath,
|
|
162
|
+
RESET_BACKEND_ENTRY: backendArtifact.bundleEntryPath,
|
|
163
|
+
RESET_BACKEND_WORKING_DIRECTORY: backendArtifact.workingDirectory
|
|
164
|
+
}
|
|
165
|
+
: {})
|
|
146
166
|
},
|
|
147
167
|
dryRun
|
|
148
168
|
})
|
|
@@ -161,7 +181,8 @@ export async function run(context) {
|
|
|
161
181
|
printSection("Runtime")
|
|
162
182
|
printStatusTable([
|
|
163
183
|
["done", "Frontend", config.frontend.devUrl],
|
|
164
|
-
["done", "Config", configPath]
|
|
184
|
+
["done", "Config", configPath],
|
|
185
|
+
["done", "Backend", backendArtifact ? backendArtifact.bundleEntryPath : "disabled"]
|
|
165
186
|
])
|
|
166
187
|
console.log(" Press Ctrl+C to stop")
|
|
167
188
|
|
package/src/commands/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getFrameworkRuntimeModeSummary,
|
|
8
8
|
hasFrameworkSourcePackage,
|
|
9
9
|
loadResetConfig,
|
|
10
|
+
resolveOptionalBackendEntry,
|
|
10
11
|
resolveFrontendDir,
|
|
11
12
|
resolveAppPaths,
|
|
12
13
|
resolveConfigPath,
|
|
@@ -48,6 +49,10 @@ export async function run(context) {
|
|
|
48
49
|
if (config) {
|
|
49
50
|
appChecks.unshift(["frontend", resolveFrontendDir(appPaths, config)])
|
|
50
51
|
}
|
|
52
|
+
const backendEntry = resolveOptionalBackendEntry(appPaths)
|
|
53
|
+
if (backendEntry) {
|
|
54
|
+
appChecks.splice(1, 0, ["backend", backendEntry])
|
|
55
|
+
}
|
|
51
56
|
|
|
52
57
|
printStatusTable(
|
|
53
58
|
appChecks.map(([label, filePath]) => [
|
|
@@ -97,6 +102,7 @@ export async function run(context) {
|
|
|
97
102
|
? `${frameworkPaths.frameworkPackage.packageName}@${frameworkPaths.frameworkPackage.version}`
|
|
98
103
|
: "Not installed"
|
|
99
104
|
],
|
|
105
|
+
["Backend", `${frameworkPaths.backendPackage.packageName}@${frameworkPaths.backendPackage.version}`],
|
|
100
106
|
["SDK", `${frameworkPaths.sdkPackage.packageName}@${frameworkPaths.sdkPackage.version}`],
|
|
101
107
|
["Schema", `${frameworkPaths.schemaPackage.packageName}@${frameworkPaths.schemaPackage.version}`]
|
|
102
108
|
])
|
|
@@ -129,6 +135,7 @@ export async function run(context) {
|
|
|
129
135
|
["Product", config.productName],
|
|
130
136
|
["App ID", config.appId],
|
|
131
137
|
["Frontend", config.project.frontendDir],
|
|
138
|
+
["Backend", backendEntry ?? "disabled"],
|
|
132
139
|
["Styling", config.project.styling],
|
|
133
140
|
["Dev URL", config.frontend.devUrl],
|
|
134
141
|
["Output", config.build.outputDir]
|
package/src/commands/init.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../lib/process.js"
|
|
11
11
|
import {
|
|
12
12
|
makeAppMetadata,
|
|
13
|
+
resolveBackendDependencySpec,
|
|
13
14
|
resolveCliDependencySpec,
|
|
14
15
|
resolveFrameworkPaths,
|
|
15
16
|
resolveSdkDependencySpec
|
|
@@ -30,6 +31,40 @@ import {
|
|
|
30
31
|
const sdkRuntimeReexport = `export * from '@reset-framework/sdk'
|
|
31
32
|
`
|
|
32
33
|
|
|
34
|
+
const backendTsconfig = `{
|
|
35
|
+
"compilerOptions": {
|
|
36
|
+
"target": "ES2023",
|
|
37
|
+
"module": "ESNext",
|
|
38
|
+
"moduleResolution": "bundler",
|
|
39
|
+
"strict": true,
|
|
40
|
+
"skipLibCheck": true,
|
|
41
|
+
"verbatimModuleSyntax": true,
|
|
42
|
+
"noEmit": true
|
|
43
|
+
},
|
|
44
|
+
"include": ["src"]
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
const backendStarterSource = `import { defineBackend } from '@reset-framework/backend'
|
|
49
|
+
|
|
50
|
+
export default defineBackend(({ handle, process }) => {
|
|
51
|
+
handle('backend.ping', async () => {
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
runtime: globalThis.process.release?.name ?? 'node',
|
|
55
|
+
version: globalThis.process.version
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
handle('backend.execVersion', async () => {
|
|
60
|
+
return process.run({
|
|
61
|
+
command: globalThis.process.execPath,
|
|
62
|
+
args: ['--version']
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
`
|
|
67
|
+
|
|
33
68
|
const standaloneViteConfig = `import { defineConfig } from 'vite'
|
|
34
69
|
import react from '@vitejs/plugin-react'
|
|
35
70
|
|
|
@@ -431,7 +466,7 @@ function createRootPackageJson(options) {
|
|
|
431
466
|
}
|
|
432
467
|
|
|
433
468
|
if (options.frontendDir !== ".") {
|
|
434
|
-
base.workspaces = [options.frontendDir]
|
|
469
|
+
base.workspaces = [options.frontendDir, "backend"]
|
|
435
470
|
base.scripts["dev:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "dev")
|
|
436
471
|
base.scripts["build:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "build")
|
|
437
472
|
base.scripts["lint:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "lint")
|
|
@@ -466,10 +501,37 @@ ${options.frontendDir === "." ? `- \`${run} dev:web\`: run the Vite frontend onl
|
|
|
466
501
|
|
|
467
502
|
- \`reset.config.json\`: desktop app metadata and runtime configuration
|
|
468
503
|
- \`${webPath}\`: web frontend source
|
|
504
|
+
- \`backend/\`: optional TypeScript sidecar backend
|
|
469
505
|
- \`.reset/\`: generated build output and native runtime cache
|
|
470
506
|
`
|
|
471
507
|
}
|
|
472
508
|
|
|
509
|
+
function createBackendPackageJson(options) {
|
|
510
|
+
return {
|
|
511
|
+
name: `${options.appName}-backend`,
|
|
512
|
+
private: true,
|
|
513
|
+
version: "0.1.0",
|
|
514
|
+
type: "module",
|
|
515
|
+
dependencies: {
|
|
516
|
+
"@reset-framework/backend": options.backendDependencySpec
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function writeBackendFiles(options) {
|
|
522
|
+
const backendDir = path.join(options.targetDir, "backend")
|
|
523
|
+
const backendSrcDir = path.join(backendDir, "src")
|
|
524
|
+
|
|
525
|
+
await mkdir(backendSrcDir, { recursive: true })
|
|
526
|
+
await writeFile(
|
|
527
|
+
path.join(backendDir, "package.json"),
|
|
528
|
+
JSON.stringify(createBackendPackageJson(options), null, 2) + "\n",
|
|
529
|
+
"utf8"
|
|
530
|
+
)
|
|
531
|
+
await writeFile(path.join(backendDir, "tsconfig.json"), backendTsconfig, "utf8")
|
|
532
|
+
await writeFile(path.join(backendSrcDir, "index.ts"), backendStarterSource, "utf8")
|
|
533
|
+
}
|
|
534
|
+
|
|
473
535
|
async function writeRootPackageFiles(options) {
|
|
474
536
|
await writeFile(
|
|
475
537
|
path.join(options.targetDir, "README.md"),
|
|
@@ -501,6 +563,16 @@ async function writeProjectFiles(options) {
|
|
|
501
563
|
frontendDir: options.frontendDir,
|
|
502
564
|
styling: options.styling
|
|
503
565
|
},
|
|
566
|
+
security: {
|
|
567
|
+
permissions: Array.from(
|
|
568
|
+
new Set([
|
|
569
|
+
...(Array.isArray(templateConfig.security?.permissions)
|
|
570
|
+
? templateConfig.security.permissions
|
|
571
|
+
: []),
|
|
572
|
+
"backend.*"
|
|
573
|
+
])
|
|
574
|
+
)
|
|
575
|
+
},
|
|
504
576
|
window: {
|
|
505
577
|
...templateConfig.window,
|
|
506
578
|
title: options.productName
|
|
@@ -567,6 +639,7 @@ async function collectCreateAppOptions(context, templates) {
|
|
|
567
639
|
templateDir: path.join(frameworkPaths.templatesDir, defaults.templateName),
|
|
568
640
|
templateConfigPath: path.join(frameworkPaths.templatesDir, defaults.templateName, "reset.config.json"),
|
|
569
641
|
sourceFrontendDir: path.join(frameworkPaths.templatesDir, defaults.templateName, "frontend"),
|
|
642
|
+
backendDependencySpec: resolveBackendDependencySpec(frameworkPaths),
|
|
570
643
|
cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
|
|
571
644
|
sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
|
|
572
645
|
packageManager: resolvePackageManagerOption(context),
|
|
@@ -723,6 +796,7 @@ async function collectCreateAppOptions(context, templates) {
|
|
|
723
796
|
templateDir: path.join(frameworkPaths.templatesDir, selected.templateName),
|
|
724
797
|
templateConfigPath: path.join(frameworkPaths.templatesDir, selected.templateName, "reset.config.json"),
|
|
725
798
|
sourceFrontendDir: path.join(frameworkPaths.templatesDir, selected.templateName, "frontend"),
|
|
799
|
+
backendDependencySpec: resolveBackendDependencySpec(frameworkPaths),
|
|
726
800
|
cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
|
|
727
801
|
sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
|
|
728
802
|
packageManagerVersion: await resolvePackageManagerVersion(selected.packageManager),
|
|
@@ -777,6 +851,8 @@ export async function run(context) {
|
|
|
777
851
|
["plan", "Write config", path.join(options.targetDir, "reset.config.json")],
|
|
778
852
|
["plan", "Write ignore file", path.join(options.targetDir, ".gitignore")],
|
|
779
853
|
["plan", "Write app package", path.join(options.targetDir, "package.json")],
|
|
854
|
+
["plan", "Write backend package", path.join(options.targetDir, "backend", "package.json")],
|
|
855
|
+
["plan", "Write backend entry", path.join(options.targetDir, "backend", "src", "index.ts")],
|
|
780
856
|
["plan", "Write SDK bridge", path.join(targetFrontendDir, "src", "lib", "reset.ts")],
|
|
781
857
|
["plan", "Write Vite config", path.join(targetFrontendDir, "vite.config.ts")],
|
|
782
858
|
["plan", "Write TS config", path.join(targetFrontendDir, "tsconfig.app.json")]
|
|
@@ -797,7 +873,7 @@ export async function run(context) {
|
|
|
797
873
|
}
|
|
798
874
|
|
|
799
875
|
console.log("")
|
|
800
|
-
const progress = createProgress(options.installDependencies ?
|
|
876
|
+
const progress = createProgress(options.installDependencies ? 7 : 6, "Scaffold")
|
|
801
877
|
|
|
802
878
|
await mkdir(options.targetDir, { recursive: true })
|
|
803
879
|
progress.tick("Prepared project directory")
|
|
@@ -812,6 +888,9 @@ export async function run(context) {
|
|
|
812
888
|
await applyFrontendOverrides(targetFrontendDir, options)
|
|
813
889
|
progress.tick("Applied frontend package wiring")
|
|
814
890
|
|
|
891
|
+
await writeBackendFiles(options)
|
|
892
|
+
progress.tick("Wrote backend starter")
|
|
893
|
+
|
|
815
894
|
const packageJsonPath = path.join(targetFrontendDir, "package.json")
|
|
816
895
|
const frontendPackage = JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
817
896
|
|
|
@@ -821,6 +900,7 @@ export async function run(context) {
|
|
|
821
900
|
frontendPackage.private = true
|
|
822
901
|
frontendPackage.version = "0.1.0"
|
|
823
902
|
frontendPackage.description = `${options.productName} desktop app`
|
|
903
|
+
frontendPackage.workspaces = ["backend"]
|
|
824
904
|
frontendPackage.devDependencies = {
|
|
825
905
|
...frontendPackage.devDependencies,
|
|
826
906
|
"reset-framework-cli": options.cliDependencySpec
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { chmod, copyFile, mkdir, realpath } from "node:fs/promises"
|
|
3
|
+
import { existsSync } from "node:fs"
|
|
4
|
+
|
|
5
|
+
import { build as buildWithEsbuild } from "esbuild"
|
|
6
|
+
|
|
7
|
+
import { logger } from "./logger.js"
|
|
8
|
+
import { resolveOptionalBackendEntry } from "./project.js"
|
|
9
|
+
|
|
10
|
+
function isRuntimeExecutableName(name) {
|
|
11
|
+
const normalized = name.toLowerCase()
|
|
12
|
+
return normalized === "node" || normalized === "node.exe" || normalized === "bun" || normalized === "bun.exe"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function uniqueNonEmpty(items) {
|
|
16
|
+
return [...new Set(items.filter(Boolean))]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolvePathExecutable(commandName) {
|
|
20
|
+
const pathValue = process.env.PATH ?? ""
|
|
21
|
+
const searchDirectories = pathValue.split(path.delimiter).filter(Boolean)
|
|
22
|
+
const extensions =
|
|
23
|
+
process.platform === "win32"
|
|
24
|
+
? uniqueNonEmpty(["", ...(process.env.PATHEXT?.split(";") ?? [".EXE", ".CMD", ".BAT", ".COM"])])
|
|
25
|
+
: [""]
|
|
26
|
+
|
|
27
|
+
for (const directory of searchDirectories) {
|
|
28
|
+
for (const extension of extensions) {
|
|
29
|
+
const candidate = path.join(directory, `${commandName}${extension}`)
|
|
30
|
+
if (existsSync(candidate)) {
|
|
31
|
+
return candidate
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveSidecarRuntimeBinary() {
|
|
40
|
+
const candidates = uniqueNonEmpty([
|
|
41
|
+
process.execPath,
|
|
42
|
+
resolvePathExecutable("node"),
|
|
43
|
+
resolvePathExecutable("bun")
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
for (const candidate of candidates) {
|
|
47
|
+
if (!candidate || !existsSync(candidate)) {
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isRuntimeExecutableName(path.basename(candidate))) {
|
|
52
|
+
return candidate
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveBackendBuildDir(appPaths, mode) {
|
|
60
|
+
return path.join(appPaths.appRoot, ".reset", "backend", mode)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function prepareAppBackend(frameworkPaths, appPaths, mode) {
|
|
64
|
+
const entryPath = resolveOptionalBackendEntry(appPaths)
|
|
65
|
+
if (!entryPath) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const runtimeExecutablePath = resolveSidecarRuntimeBinary()
|
|
70
|
+
if (!runtimeExecutablePath) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"Could not resolve a JavaScript runtime for the Reset backend sidecar. Install Node.js or Bun."
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const outputDir = resolveBackendBuildDir(appPaths, mode)
|
|
77
|
+
const bundleEntryPath = path.join(outputDir, "app.mjs")
|
|
78
|
+
const runnerPath = path.join(frameworkPaths.backendPackage.packageRoot, "src", "runner.js")
|
|
79
|
+
const supportModulePath = path.join(frameworkPaths.backendPackage.packageRoot, "src", "index.js")
|
|
80
|
+
|
|
81
|
+
await mkdir(outputDir, { recursive: true })
|
|
82
|
+
await buildWithEsbuild({
|
|
83
|
+
absWorkingDir: appPaths.appRoot,
|
|
84
|
+
entryPoints: [entryPath],
|
|
85
|
+
outfile: bundleEntryPath,
|
|
86
|
+
bundle: true,
|
|
87
|
+
platform: "node",
|
|
88
|
+
format: "esm",
|
|
89
|
+
target: "node20",
|
|
90
|
+
sourcemap: mode === "dev" ? "inline" : false,
|
|
91
|
+
logLevel: "silent"
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
entryPath,
|
|
96
|
+
bundleEntryPath,
|
|
97
|
+
runnerPath,
|
|
98
|
+
supportModulePath,
|
|
99
|
+
runtimeExecutablePath,
|
|
100
|
+
runtimeName: path.basename(runtimeExecutablePath),
|
|
101
|
+
workingDirectory: appPaths.appRoot
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function stageAppBackend(backendArtifact, outputPaths) {
|
|
106
|
+
if (!backendArtifact) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
logger.info(`Staging backend sidecar into ${outputPaths.bundledBackendDir}`)
|
|
111
|
+
|
|
112
|
+
const resolvedRuntimePath = await realpath(backendArtifact.runtimeExecutablePath)
|
|
113
|
+
|
|
114
|
+
await mkdir(outputPaths.bundledBackendDir, { recursive: true })
|
|
115
|
+
await copyFile(backendArtifact.bundleEntryPath, outputPaths.bundledBackendEntryPath)
|
|
116
|
+
await copyFile(backendArtifact.runnerPath, outputPaths.bundledBackendRunnerPath)
|
|
117
|
+
await copyFile(backendArtifact.supportModulePath, outputPaths.bundledBackendSupportModulePath)
|
|
118
|
+
await copyFile(resolvedRuntimePath, outputPaths.bundledBackendRuntimePath)
|
|
119
|
+
|
|
120
|
+
if (process.platform !== "win32") {
|
|
121
|
+
await chmod(outputPaths.bundledBackendRuntimePath, 0o755)
|
|
122
|
+
}
|
|
123
|
+
}
|
package/src/lib/output.js
CHANGED
|
@@ -2,6 +2,7 @@ import path from "node:path"
|
|
|
2
2
|
import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises"
|
|
3
3
|
import { existsSync } from "node:fs"
|
|
4
4
|
|
|
5
|
+
import { stageAppBackend } from "./backend.js"
|
|
5
6
|
import { logger } from "./logger.js"
|
|
6
7
|
import { runCommand } from "./process.js"
|
|
7
8
|
import {
|
|
@@ -118,7 +119,7 @@ function escapePowerShellLiteral(value) {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
|
|
121
|
-
const { dryRun = false, outputPaths, configPath, preferSource = false } = options
|
|
122
|
+
const { dryRun = false, outputPaths, configPath, preferSource = false, backendArtifact = null } = options
|
|
122
123
|
const runtimeSourceBundle = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
|
|
123
124
|
mustExist: !dryRun,
|
|
124
125
|
preferSource
|
|
@@ -140,6 +141,12 @@ export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, c
|
|
|
140
141
|
logger.info(`- copy ${runtimeSourceBundle} -> ${outputPaths.appBundlePath}`)
|
|
141
142
|
logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
|
|
142
143
|
logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
|
|
144
|
+
if (backendArtifact) {
|
|
145
|
+
logger.info(`- copy ${backendArtifact.bundleEntryPath} -> ${outputPaths.bundledBackendEntryPath}`)
|
|
146
|
+
logger.info(`- copy ${backendArtifact.runnerPath} -> ${outputPaths.bundledBackendRunnerPath}`)
|
|
147
|
+
logger.info(`- copy ${backendArtifact.supportModulePath} -> ${outputPaths.bundledBackendSupportModulePath}`)
|
|
148
|
+
logger.info(`- copy ${backendArtifact.runtimeExecutablePath} -> ${outputPaths.bundledBackendRuntimePath}`)
|
|
149
|
+
}
|
|
143
150
|
return outputPaths
|
|
144
151
|
}
|
|
145
152
|
|
|
@@ -151,6 +158,7 @@ export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, c
|
|
|
151
158
|
await cp(configPath, outputPaths.bundledConfigPath, { force: true })
|
|
152
159
|
await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
|
|
153
160
|
await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
|
|
161
|
+
await stageAppBackend(backendArtifact, outputPaths)
|
|
154
162
|
await writeFile(
|
|
155
163
|
path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
|
|
156
164
|
buildMacOSInfoPlist(config),
|
|
@@ -161,7 +169,7 @@ export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, c
|
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
|
|
164
|
-
const { dryRun = false, outputPaths, configPath, preferSource = false } = options
|
|
172
|
+
const { dryRun = false, outputPaths, configPath, preferSource = false, backendArtifact = null } = options
|
|
165
173
|
const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, "release", {
|
|
166
174
|
mustExist: !dryRun,
|
|
167
175
|
preferSource
|
|
@@ -188,6 +196,12 @@ export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths,
|
|
|
188
196
|
logger.info(`- rename ${runtimeSourceBinary} -> ${outputPaths.appExecutablePath}`)
|
|
189
197
|
logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
|
|
190
198
|
logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
|
|
199
|
+
if (backendArtifact) {
|
|
200
|
+
logger.info(`- copy ${backendArtifact.bundleEntryPath} -> ${outputPaths.bundledBackendEntryPath}`)
|
|
201
|
+
logger.info(`- copy ${backendArtifact.runnerPath} -> ${outputPaths.bundledBackendRunnerPath}`)
|
|
202
|
+
logger.info(`- copy ${backendArtifact.supportModulePath} -> ${outputPaths.bundledBackendSupportModulePath}`)
|
|
203
|
+
logger.info(`- copy ${backendArtifact.runtimeExecutablePath} -> ${outputPaths.bundledBackendRuntimePath}`)
|
|
204
|
+
}
|
|
191
205
|
return outputPaths
|
|
192
206
|
}
|
|
193
207
|
|
|
@@ -215,6 +229,7 @@ export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths,
|
|
|
215
229
|
await cp(configPath, outputPaths.bundledConfigPath, { force: true })
|
|
216
230
|
await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
|
|
217
231
|
await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
|
|
232
|
+
await stageAppBackend(backendArtifact, outputPaths)
|
|
218
233
|
|
|
219
234
|
return outputPaths
|
|
220
235
|
}
|
package/src/lib/project.js
CHANGED
|
@@ -326,6 +326,11 @@ export function resolveFrameworkPaths() {
|
|
|
326
326
|
path.join(packagesDir, "sdk"),
|
|
327
327
|
resolvePackageInfo("@reset-framework/sdk", path.join(packagesDir, "sdk"))
|
|
328
328
|
)
|
|
329
|
+
const backendPackage = resolveWorkspacePackageInfo(
|
|
330
|
+
"@reset-framework/backend",
|
|
331
|
+
path.join(packagesDir, "backend"),
|
|
332
|
+
resolvePackageInfo("@reset-framework/backend", path.join(packagesDir, "backend"))
|
|
333
|
+
)
|
|
329
334
|
const schemaPackage = resolveWorkspacePackageInfo(
|
|
330
335
|
"@reset-framework/schema",
|
|
331
336
|
path.join(packagesDir, "schema"),
|
|
@@ -334,7 +339,7 @@ export function resolveFrameworkPaths() {
|
|
|
334
339
|
const runtimePackage = resolveRuntimePackage(packagesDir)
|
|
335
340
|
const isWorkspaceLayout =
|
|
336
341
|
isWorkspacePackagesDir &&
|
|
337
|
-
[cliDir, sdkPackage.packageRoot, schemaPackage.packageRoot].every((packageRoot) =>
|
|
342
|
+
[cliDir, backendPackage.packageRoot, sdkPackage.packageRoot, schemaPackage.packageRoot].every((packageRoot) =>
|
|
338
343
|
isPathInside(packagesDir, packageRoot)
|
|
339
344
|
)
|
|
340
345
|
|
|
@@ -351,6 +356,7 @@ export function resolveFrameworkPaths() {
|
|
|
351
356
|
frameworkRoot: nativePackage?.packageRoot ?? null,
|
|
352
357
|
frameworkPackage: nativePackage,
|
|
353
358
|
isWorkspaceLayout,
|
|
359
|
+
backendPackage,
|
|
354
360
|
sdkPackage,
|
|
355
361
|
schemaPackage,
|
|
356
362
|
runtimePackage,
|
|
@@ -551,12 +557,24 @@ export function resolveAppPaths(appRoot) {
|
|
|
551
557
|
return {
|
|
552
558
|
appRoot,
|
|
553
559
|
appPackageJsonPath: path.join(appRoot, "package.json"),
|
|
560
|
+
backendDir: path.join(appRoot, "backend"),
|
|
554
561
|
frontendDir: path.join(appRoot, "frontend"),
|
|
555
562
|
resetConfigPath: path.join(appRoot, "reset.config.json"),
|
|
556
563
|
legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
|
|
557
564
|
}
|
|
558
565
|
}
|
|
559
566
|
|
|
567
|
+
export function resolveOptionalBackendEntry(appPaths) {
|
|
568
|
+
const candidates = [
|
|
569
|
+
path.join(appPaths.backendDir, "src", "index.ts"),
|
|
570
|
+
path.join(appPaths.backendDir, "src", "index.js"),
|
|
571
|
+
path.join(appPaths.backendDir, "index.ts"),
|
|
572
|
+
path.join(appPaths.backendDir, "index.js")
|
|
573
|
+
]
|
|
574
|
+
|
|
575
|
+
return resolveExistingCandidate(candidates)
|
|
576
|
+
}
|
|
577
|
+
|
|
560
578
|
export function resolveConfigPath(appPaths) {
|
|
561
579
|
if (existsSync(appPaths.resetConfigPath)) {
|
|
562
580
|
return appPaths.resetConfigPath
|
|
@@ -571,6 +589,7 @@ export function resolveConfigPath(appPaths) {
|
|
|
571
589
|
|
|
572
590
|
export function getFrameworkChecks(frameworkPaths) {
|
|
573
591
|
return [
|
|
592
|
+
["backend", frameworkPaths.backendPackage.packageJsonPath],
|
|
574
593
|
["sdk", frameworkPaths.sdkPackage.packageJsonPath],
|
|
575
594
|
["schema", frameworkPaths.schemaPackage.packageJsonPath],
|
|
576
595
|
["templates", frameworkPaths.templatesDir]
|
|
@@ -638,6 +657,7 @@ export function assertFrameworkInstall(frameworkPaths) {
|
|
|
638
657
|
export function assertFrameworkSourceInstall(frameworkPaths) {
|
|
639
658
|
const missing = [
|
|
640
659
|
["native source", frameworkPaths.frameworkPackage?.packageJsonPath],
|
|
660
|
+
["backend", frameworkPaths.backendPackage.packageJsonPath],
|
|
641
661
|
["sdk", frameworkPaths.sdkPackage.packageJsonPath],
|
|
642
662
|
["cmake", frameworkPaths.rootCMakePath],
|
|
643
663
|
["runtime", frameworkPaths.runtimeDir],
|
|
@@ -784,6 +804,15 @@ export function resolveAppOutputPaths(appPaths, config) {
|
|
|
784
804
|
: undefined,
|
|
785
805
|
resourcesDir,
|
|
786
806
|
bundledConfigPath: path.join(resourcesDir, "reset.config.json"),
|
|
807
|
+
bundledBackendDir: path.join(resourcesDir, "backend"),
|
|
808
|
+
bundledBackendEntryPath: path.join(resourcesDir, "backend", "app.mjs"),
|
|
809
|
+
bundledBackendRunnerPath: path.join(resourcesDir, "backend", "runner.mjs"),
|
|
810
|
+
bundledBackendSupportModulePath: path.join(resourcesDir, "backend", "index.js"),
|
|
811
|
+
bundledBackendRuntimePath: path.join(
|
|
812
|
+
resourcesDir,
|
|
813
|
+
"backend",
|
|
814
|
+
process.platform === "win32" ? "backend-runtime.exe" : "backend-runtime"
|
|
815
|
+
),
|
|
787
816
|
bundledFrontendDir: path.join(resourcesDir, config.frontend.distDir),
|
|
788
817
|
frontendDistDir: path.resolve(frontendDir, config.frontend.distDir),
|
|
789
818
|
frontendEntryFile: path.resolve(
|
|
@@ -830,6 +859,14 @@ export function resolveSdkDependencySpec(frameworkPaths) {
|
|
|
830
859
|
return `^${frameworkPaths.sdkPackage.version}`
|
|
831
860
|
}
|
|
832
861
|
|
|
862
|
+
export function resolveBackendDependencySpec(frameworkPaths) {
|
|
863
|
+
if (frameworkPaths.isWorkspaceLayout || frameworkPaths.backendPackage.localFallback) {
|
|
864
|
+
return `file:${frameworkPaths.backendPackage.packageRoot}`
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return `^${frameworkPaths.backendPackage.version}`
|
|
868
|
+
}
|
|
869
|
+
|
|
833
870
|
export function resolveCliDependencySpec(frameworkPaths) {
|
|
834
871
|
if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
|
|
835
872
|
return `file:${frameworkPaths.cliPackage.packageRoot}`
|