@struktur/app 2.6.0

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.
Files changed (34) hide show
  1. package/AGENTS.md +117 -0
  2. package/LICENSE +110 -0
  3. package/README.md +127 -0
  4. package/assets/icon.iconset/icon_128x128.png +0 -0
  5. package/assets/icon.iconset/icon_128x128@2x.png +0 -0
  6. package/assets/icon.iconset/icon_16x16.png +0 -0
  7. package/assets/icon.iconset/icon_16x16@2x.png +0 -0
  8. package/assets/icon.iconset/icon_256x256.png +0 -0
  9. package/assets/icon.iconset/icon_256x256@2x.png +0 -0
  10. package/assets/icon.iconset/icon_32x32.png +0 -0
  11. package/assets/icon.iconset/icon_32x32@2x.png +0 -0
  12. package/assets/icon.iconset/icon_512x512.png +0 -0
  13. package/assets/icon.iconset/icon_512x512@2x.png +0 -0
  14. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Info.plist +18 -0
  15. package/build/dev-macos-arm64/Struktur-dev.app/Contents/MacOS/bspatch +0 -0
  16. package/build/dev-macos-arm64/Struktur-dev.app/Contents/MacOS/bun +0 -0
  17. package/build/dev-macos-arm64/Struktur-dev.app/Contents/MacOS/launcher +0 -0
  18. package/build/dev-macos-arm64/Struktur-dev.app/Contents/MacOS/libNativeWrapper.dylib +0 -0
  19. package/build/dev-macos-arm64/Struktur-dev.app/Contents/MacOS/libasar.dylib +0 -0
  20. package/build/dev-macos-arm64/Struktur-dev.app/Contents/MacOS/zig-zstd +0 -0
  21. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/AppIcon.icns +0 -0
  22. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/app/bun/index.js +231552 -0
  23. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/app/views/main/index.html +45 -0
  24. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/app/views/main/index.js +35 -0
  25. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/build.json +1 -0
  26. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/main.js +164 -0
  27. package/build/dev-macos-arm64/Struktur-dev.app/Contents/Resources/version.json +1 -0
  28. package/electrobun.config.ts +30 -0
  29. package/package.json +29 -0
  30. package/src/bun/index.ts +23 -0
  31. package/src/main/index.html +45 -0
  32. package/src/main/index.ts +48 -0
  33. package/src/shared/types.ts +307 -0
  34. package/tsconfig.json +26 -0
@@ -0,0 +1,45 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Struktur</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 100%; height: 100%; }
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
12
+ background: #f5efe6;
13
+ color: #2d1b0e;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ }
18
+ .loading {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ gap: 1rem;
23
+ }
24
+ .spinner {
25
+ width: 40px;
26
+ height: 40px;
27
+ border: 3px solid #d4c8b8;
28
+ border-top-color: #7a5c3a;
29
+ border-radius: 50%;
30
+ animation: spin 1s linear infinite;
31
+ }
32
+ @keyframes spin { to { transform: rotate(360deg); } }
33
+ .text { color: #7a5c3a; font-size: 14px; }
34
+ </style>
35
+ </head>
36
+ <body>
37
+ <div id="root">
38
+ <div class="loading">
39
+ <div class="spinner"></div>
40
+ <div class="text">Loading Struktur...</div>
41
+ </div>
42
+ </div>
43
+ <script type="module" src="./index.js"></script>
44
+ </body>
45
+ </html>
@@ -0,0 +1,35 @@
1
+ // src/main/index.ts
2
+ console.log("Struktur Desktop - Production Mode");
3
+ var root = document.getElementById("root");
4
+ if (root) {
5
+ root.innerHTML = `
6
+ <div style="
7
+ display: flex;
8
+ flex-direction: column;
9
+ align-items: center;
10
+ justify-content: center;
11
+ height: 100%;
12
+ gap: 20px;
13
+ padding: 40px;
14
+ text-align: center;
15
+ ">
16
+ <h1 style="color: #7a5c3a; font-size: 28px; font-weight: 600;">
17
+ Struktur Desktop
18
+ </h1>
19
+ <p style="color: #a0926f; font-size: 16px; max-width: 400px;">
20
+ Production build placeholder.
21
+ </p>
22
+ <p style="color: #5c4b3d; font-size: 14px; margin-top: 20px;">
23
+ To use the full app, run in development mode:
24
+ </p>
25
+ <code style="
26
+ background: #ede5d8;
27
+ padding: 12px 16px;
28
+ border-radius: 6px;
29
+ font-family: monospace;
30
+ font-size: 13px;
31
+ color: #2d1b0e;
32
+ ">bun run dev</code>
33
+ </div>
34
+ `;
35
+ }
@@ -0,0 +1 @@
1
+ {"defaultRenderer":"native","availableRenderers":["native"],"runtime":{"exitOnLastWindowClosed":true},"bunVersion":"1.3.9"}
@@ -0,0 +1,164 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // src/launcher/main.ts
5
+ import { join, dirname, resolve } from "path";
6
+ import { dlopen, suffix, ptr, toArrayBuffer } from "bun:ffi";
7
+ import { existsSync, writeFileSync } from "fs";
8
+ import { tmpdir } from "os";
9
+ var pathToMacOS = dirname(process.argv0);
10
+ var libPath = join(pathToMacOS, `libNativeWrapper.${suffix}`);
11
+ var absoluteLibPath = resolve(libPath);
12
+ function main() {
13
+ let channel = "";
14
+ let identifier = "";
15
+ let name = "";
16
+ try {
17
+ const pathToLauncherBin2 = process.argv0;
18
+ const pathToBinDir2 = dirname(pathToLauncherBin2);
19
+ const versionJsonPath = join(pathToBinDir2, "..", "Resources", "version.json");
20
+ if (existsSync(versionJsonPath)) {
21
+ const versionInfo = __require(versionJsonPath);
22
+ if (versionInfo.identifier) {
23
+ identifier = versionInfo.identifier;
24
+ }
25
+ if (versionInfo.name) {
26
+ name = versionInfo.name;
27
+ }
28
+ if (versionInfo.channel) {
29
+ channel = versionInfo.channel;
30
+ }
31
+ console.log(`[LAUNCHER] Loaded identifier: ${identifier}, name: ${name}, channel: ${channel}`);
32
+ }
33
+ } catch (error) {
34
+ console.error(`[LAUNCHER] Warning: Could not read version.json:`, error);
35
+ }
36
+ if (process.platform === "linux") {
37
+ const cefLibs = [
38
+ join(pathToMacOS, "libcef.so"),
39
+ join(pathToMacOS, "libvk_swiftshader.so")
40
+ ];
41
+ const existingCefLibs = cefLibs.filter((lib2) => existsSync(lib2));
42
+ if (existingCefLibs.length > 0 && !process.env["LD_PRELOAD"]) {
43
+ console.error(`[LAUNCHER] ERROR: CEF libraries found but LD_PRELOAD not set!`);
44
+ console.error(`[LAUNCHER] Please run through the wrapper script: ./run.sh`);
45
+ console.error(`[LAUNCHER] Or set: LD_PRELOAD="${existingCefLibs.join(":")}" before starting.`);
46
+ const { spawn } = __require("child_process");
47
+ const env = { ...process.env, LD_PRELOAD: existingCefLibs.join(":") };
48
+ const child = spawn(process.argv[0], process.argv.slice(1), {
49
+ env,
50
+ stdio: "inherit"
51
+ });
52
+ child.on("exit", (code) => process.exit(code ?? 1));
53
+ return;
54
+ }
55
+ }
56
+ let lib;
57
+ try {
58
+ if (!process.env["LD_LIBRARY_PATH"]?.includes(".")) {
59
+ process.env["LD_LIBRARY_PATH"] = `.${process.env["LD_LIBRARY_PATH"] ? ":" + process.env["LD_LIBRARY_PATH"] : ""}`;
60
+ }
61
+ lib = dlopen(libPath, {
62
+ startEventLoop: {
63
+ args: ["cstring", "cstring", "cstring"],
64
+ returns: "void"
65
+ },
66
+ forceExit: {
67
+ args: ["i32"],
68
+ returns: "void"
69
+ }
70
+ });
71
+ } catch (error) {
72
+ console.error(`[LAUNCHER] Failed to load library: ${error.message}`);
73
+ try {
74
+ lib = dlopen(absoluteLibPath, {
75
+ startEventLoop: {
76
+ args: ["cstring", "cstring", "cstring"],
77
+ returns: "void"
78
+ },
79
+ forceExit: {
80
+ args: ["i32"],
81
+ returns: "void"
82
+ }
83
+ });
84
+ } catch (absError) {
85
+ console.error(`[LAUNCHER] Library loading failed. Try running: ldd ${libPath}`);
86
+ throw error;
87
+ }
88
+ }
89
+ const pathToLauncherBin = process.argv0;
90
+ const pathToBinDir = dirname(pathToLauncherBin);
91
+ const resourcesDir = join(pathToBinDir, "..", "Resources");
92
+ const asarPath = join(resourcesDir, "app.asar");
93
+ const appFolderPath = join(resourcesDir, "app");
94
+ let appEntrypointPath;
95
+ if (existsSync(asarPath)) {
96
+ console.log(`[LAUNCHER] Loading app code from ASAR: ${asarPath}`);
97
+ let asarLibPath;
98
+ let asarLib;
99
+ if (process.platform === "win32") {
100
+ asarLibPath = libPath;
101
+ console.log(`[LAUNCHER] Using native wrapper's ASAR reader: ${asarLibPath}`);
102
+ } else {
103
+ asarLibPath = join(pathToMacOS, `libasar.${suffix}`);
104
+ }
105
+ try {
106
+ asarLib = dlopen(asarLibPath, {
107
+ asar_open: { args: ["cstring"], returns: "ptr" },
108
+ asar_read_file: { args: ["ptr", "cstring", "ptr"], returns: "ptr" },
109
+ asar_free_buffer: { args: ["ptr", "u64"], returns: "void" },
110
+ asar_close: { args: ["ptr"], returns: "void" }
111
+ });
112
+ } catch (error) {
113
+ console.error(`[LAUNCHER] Failed to load ASAR library: ${error.message}`);
114
+ throw error;
115
+ }
116
+ const asarArchive = asarLib.symbols.asar_open(ptr(new Uint8Array(Buffer.from(asarPath + "\x00", "utf8"))));
117
+ if (!asarArchive || asarArchive === 0n) {
118
+ console.error(`[LAUNCHER] Failed to open ASAR archive at: ${asarPath}`);
119
+ throw new Error("Failed to open ASAR archive");
120
+ }
121
+ const filePath = "bun/index.js";
122
+ const sizeBuffer = new BigUint64Array(1);
123
+ const fileDataPtr = asarLib.symbols.asar_read_file(asarArchive, ptr(new Uint8Array(Buffer.from(filePath + "\x00", "utf8"))), ptr(sizeBuffer));
124
+ if (!fileDataPtr || fileDataPtr === 0n) {
125
+ console.error(`[LAUNCHER] Failed to read ${filePath} from ASAR`);
126
+ asarLib.symbols.asar_close(asarArchive);
127
+ throw new Error(`Failed to read ${filePath} from ASAR`);
128
+ }
129
+ const fileSize = Number(sizeBuffer[0]);
130
+ console.log(`[LAUNCHER] Read ${fileSize} bytes from ASAR for ${filePath}`);
131
+ const arrayBuffer = toArrayBuffer(fileDataPtr, 0, fileSize);
132
+ const fileData = Buffer.from(arrayBuffer);
133
+ const systemTmpDir = tmpdir();
134
+ const randomFileName = `electrobun-${Date.now()}-${Math.random().toString(36).substring(7)}.js`;
135
+ appEntrypointPath = join(systemTmpDir, randomFileName);
136
+ const wrappedFileData = `
137
+ // Auto-delete temp file after Worker loads it
138
+ const __tempFilePath = "${appEntrypointPath}";
139
+ setTimeout(() => {
140
+ try {
141
+ require("fs").unlinkSync(__tempFilePath);
142
+ console.log("[LAUNCHER] Deleted temp file:", __tempFilePath);
143
+ } catch (error) {
144
+ console.warn("[LAUNCHER] Failed to delete temp file:", error.message);
145
+ }
146
+ }, 100);
147
+
148
+ ${fileData.toString("utf8")}
149
+ `;
150
+ writeFileSync(appEntrypointPath, wrappedFileData);
151
+ console.log(`[LAUNCHER] Wrote app entrypoint to: ${appEntrypointPath}`);
152
+ asarLib.symbols.asar_free_buffer(fileDataPtr, BigInt(fileSize));
153
+ asarLib.symbols.asar_close(asarArchive);
154
+ } else {
155
+ console.log(`[LAUNCHER] Loading app code from flat files`);
156
+ appEntrypointPath = join(appFolderPath, "bun", "index.js");
157
+ }
158
+ process.on("SIGINT", () => {});
159
+ process.on("SIGTERM", () => {});
160
+ new Worker(appEntrypointPath, {});
161
+ lib.symbols.startEventLoop(ptr(new Uint8Array(Buffer.from(identifier + "\x00", "utf8"))), ptr(new Uint8Array(Buffer.from(name + "\x00", "utf8"))), ptr(new Uint8Array(Buffer.from(channel + "\x00", "utf8"))));
162
+ lib.symbols.forceExit(0);
163
+ }
164
+ main();
@@ -0,0 +1 @@
1
+ {"version":"1.0.0","hash":"dev","channel":"dev","baseUrl":"","name":"Struktur-dev","identifier":"com.struktur.app"}
@@ -0,0 +1,30 @@
1
+ import type { ElectrobunConfig } from "electrobun";
2
+
3
+ export default {
4
+ app: {
5
+ name: "Struktur",
6
+ identifier: "com.struktur.app",
7
+ version: "1.0.0",
8
+ },
9
+
10
+ runtime: {
11
+ exitOnLastWindowClosed: true,
12
+ },
13
+
14
+ build: {
15
+ bun: {
16
+ entrypoint: "src/bun/index.ts",
17
+ },
18
+ views: {
19
+ main: {
20
+ entrypoint: "src/main/index.ts",
21
+ },
22
+ },
23
+ copy: {
24
+ "src/main/index.html": "views/main/index.html",
25
+ },
26
+ mac: {
27
+ icons: "assets/icon.iconset",
28
+ },
29
+ },
30
+ } satisfies ElectrobunConfig;
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@struktur/app",
3
+ "version": "2.6.0",
4
+ "license": "FSL-1.1-MIT",
5
+ "type": "module",
6
+ "main": "src/bun/index.ts",
7
+ "dependencies": {
8
+ "electrobun": "^1.15.1",
9
+ "zod": "^4.3.6",
10
+ "@struktur/sdk": "2.6.0"
11
+ },
12
+ "devDependencies": {
13
+ "@types/bun": "latest",
14
+ "@types/node": "^22.10.2",
15
+ "concurrently": "^9.1.2",
16
+ "typescript": "^5.7.2"
17
+ },
18
+ "scripts": {
19
+ "dev": "concurrently --kill-others --names 'WEB,APP' --prefix-colors 'blue,green' 'bun --filter @struktur/web dev' 'sleep 3 && electrobun dev'",
20
+ "dev:app": "electrobun dev",
21
+ "dev:web": "bun --filter @struktur/web dev",
22
+ "build": "electrobun build",
23
+ "build:release": "electrobun build --release",
24
+ "package": "electrobun package",
25
+ "package:release": "electrobun package --release",
26
+ "start": "electrobun run",
27
+ "typecheck": "tsc --noEmit"
28
+ }
29
+ }
@@ -0,0 +1,23 @@
1
+ import { BrowserWindow } from "electrobun/bun";
2
+
3
+ // Determine if we're in development mode
4
+ const isDev = process.env.NODE_ENV === "development" || process.env.ELECTROBUN_BUILD_ENV === "dev";
5
+
6
+ // Web dev server URL - add desktop mode query param for the web UI
7
+ const WEB_DEV_URL = "http://localhost:3030?desktop=true";
8
+
9
+ // Create the main window with transparent title bar
10
+ const _win = new BrowserWindow({
11
+ title: "Struktur",
12
+ url: isDev ? WEB_DEV_URL : "views://main/index.html",
13
+ frame: {
14
+ x: 0,
15
+ y: 0,
16
+ width: 1400,
17
+ height: 900,
18
+ },
19
+ titleBarStyle: "hiddenInset",
20
+ });
21
+
22
+ console.log(`Struktur Desktop started in ${isDev ? "development" : "production"} mode`);
23
+ console.log(`Loading from: ${isDev ? WEB_DEV_URL : "views://main/index.html"}`);
@@ -0,0 +1,45 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Struktur</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 100%; height: 100%; }
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
12
+ background: #f5efe6;
13
+ color: #2d1b0e;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ }
18
+ .loading {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ gap: 1rem;
23
+ }
24
+ .spinner {
25
+ width: 40px;
26
+ height: 40px;
27
+ border: 3px solid #d4c8b8;
28
+ border-top-color: #7a5c3a;
29
+ border-radius: 50%;
30
+ animation: spin 1s linear infinite;
31
+ }
32
+ @keyframes spin { to { transform: rotate(360deg); } }
33
+ .text { color: #7a5c3a; font-size: 14px; }
34
+ </style>
35
+ </head>
36
+ <body>
37
+ <div id="root">
38
+ <div class="loading">
39
+ <div class="spinner"></div>
40
+ <div class="text">Loading Struktur...</div>
41
+ </div>
42
+ </div>
43
+ <script type="module" src="./index.js"></script>
44
+ </body>
45
+ </html>
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Struktur Desktop App - Production Webview Entry
3
+ *
4
+ * This file is only used in production builds.
5
+ * In development, the app loads directly from http://localhost:3030
6
+ *
7
+ * TODO: Replace this with the production build of @struktur/web
8
+ */
9
+
10
+ console.log("Struktur Desktop - Production Mode");
11
+
12
+ // Show production placeholder
13
+ const root = document.getElementById("root");
14
+ if (root) {
15
+ root.innerHTML = `
16
+ <div style="
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ justify-content: center;
21
+ height: 100%;
22
+ gap: 20px;
23
+ padding: 40px;
24
+ text-align: center;
25
+ ">
26
+ <h1 style="color: #7a5c3a; font-size: 28px; font-weight: 600;">
27
+ Struktur Desktop
28
+ </h1>
29
+ <p style="color: #a0926f; font-size: 16px; max-width: 400px;">
30
+ Production build placeholder.
31
+ </p>
32
+ <p style="color: #5c4b3d; font-size: 14px; margin-top: 20px;">
33
+ To use the full app, run in development mode:
34
+ </p>
35
+ <code style="
36
+ background: #ede5d8;
37
+ padding: 12px 16px;
38
+ border-radius: 6px;
39
+ font-family: monospace;
40
+ font-size: 13px;
41
+ color: #2d1b0e;
42
+ ">bun run dev</code>
43
+ </div>
44
+ `;
45
+ }
46
+
47
+ // TODO: When building for production, bundle @struktur/web and replace this file
48
+ // with the actual web app entry point