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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reset-framework-cli",
3
- "version": "1.2.1",
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/schema": "1.2.1",
15
- "@reset-framework/sdk": "1.2.1"
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.1",
19
- "@reset-framework/runtime-darwin-x64": "1.2.1",
20
- "@reset-framework/runtime-win32-x64": "1.2.1"
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"
@@ -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
  }
@@ -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
 
@@ -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]
@@ -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 ? 6 : 5, "Scaffold")
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
  }
@@ -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}`