@morlay/dsh-desktopify 0.1.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.
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/cli/index.mjs +1252 -0
- package/dist/index.mjs +860 -0
- package/dist/preload-app.cjs +4 -0
- package/dist/preload.cjs +4 -0
- package/dist/seed-Ca0AMFtp.mjs +152 -0
- package/package.json +51 -0
package/dist/preload.cjs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { dirname, join, relative } from "node:path";
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
//#region src/appconfig.ts
|
|
4
|
+
/**
|
|
5
|
+
* Runtime configuration contract between the bundle step and the Electron shell.
|
|
6
|
+
* Written beside the shell executable as `appconfig.json`; the shell reads it at
|
|
7
|
+
* startup. `dshHome` follows the reference desktop semantics:
|
|
8
|
+
* - `xdg` (default) — XDG data home (`~/Library/Application Support` on
|
|
9
|
+
* macOS, `$XDG_DATA_HOME` on Linux) joined with the application name;
|
|
10
|
+
* - `env` — leave `DSH_HOME` unset and inherit the environment;
|
|
11
|
+
* - an absolute path — pin `DSH_HOME` to that directory.
|
|
12
|
+
* @module @morlay/dsh-desktopify
|
|
13
|
+
*/
|
|
14
|
+
/** The dsh profile the desktop shell hosts (upstream desktop semantics). */
|
|
15
|
+
const PROFILE_NAME = "desktop";
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null;
|
|
18
|
+
}
|
|
19
|
+
function windowConfig(value) {
|
|
20
|
+
const record = isRecord(value) ? value : {};
|
|
21
|
+
return {
|
|
22
|
+
width: numberOr(record.width, 1280),
|
|
23
|
+
height: numberOr(record.height, 800),
|
|
24
|
+
minWidth: numberOr(record.minWidth, 800),
|
|
25
|
+
minHeight: numberOr(record.minHeight, 600)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function numberOr(value, fallback) {
|
|
29
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
30
|
+
}
|
|
31
|
+
/** Read and validate the shell configuration beside the executable. */
|
|
32
|
+
function loadAppConfig(exeDir) {
|
|
33
|
+
const path = join(exeDir, "appconfig.json");
|
|
34
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
35
|
+
if (!isRecord(value) || typeof value.name !== "string" || value.name === "" || typeof value.id !== "string" || value.id === "" || typeof value.version !== "string" || value.version === "" || value.profile !== "desktop" || value.dshHome !== "xdg" && value.dshHome !== "env" && typeof value.dshHome !== "string") throw new Error(`dsh desktop: invalid shell configuration ${path}`);
|
|
36
|
+
return {
|
|
37
|
+
name: value.name,
|
|
38
|
+
id: value.id,
|
|
39
|
+
version: value.version,
|
|
40
|
+
profile: PROFILE_NAME,
|
|
41
|
+
dshHome: value.dshHome,
|
|
42
|
+
window: windowConfig(value.window)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Write the shell configuration beside the executable (bundle step). */
|
|
46
|
+
function writeAppConfig(exeDir, config) {
|
|
47
|
+
writeFileSync(join(exeDir, "appconfig.json"), `${JSON.stringify(config, void 0, 2)}\n`, { mode: 384 });
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/seed.ts
|
|
51
|
+
/**
|
|
52
|
+
* Packaged profile seed: the bundle step writes the workspace profile into
|
|
53
|
+
* `dsh-home/profiles/desktop` beside the shell executable — package.json,
|
|
54
|
+
* cordis.patch.yml, the declared `files` entries, and a real copy of the
|
|
55
|
+
* production dependency closure as `node_modules` (the upstream desktop host
|
|
56
|
+
* resolves its own entry and every profile bundle inside the project
|
|
57
|
+
* directory, so links to the app closure would fail its containment check).
|
|
58
|
+
* The seed is stamped with a `.seed-hash` fingerprint of the workspace
|
|
59
|
+
* content. At startup the shell forces the runtime home's profile to be a
|
|
60
|
+
* real copy of that seed: a mismatched fingerprint (or a stale symlink)
|
|
61
|
+
* replaces the profile, a matching fingerprint skips the copy so the closure
|
|
62
|
+
* is not re-copied on every launch. User data lives at the home root and is
|
|
63
|
+
* never touched by profile replacement.
|
|
64
|
+
* @module @morlay/dsh-desktopify
|
|
65
|
+
*/
|
|
66
|
+
/** Fingerprint file name inside the seeded profile. */
|
|
67
|
+
const SEED_HASH_NAME = ".seed-hash";
|
|
68
|
+
/** Directories skipped when copying the seed (installation bookkeeping). */
|
|
69
|
+
const SEED_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
70
|
+
".nub-store",
|
|
71
|
+
".store",
|
|
72
|
+
".nub"
|
|
73
|
+
]);
|
|
74
|
+
function readSeedHash(path) {
|
|
75
|
+
try {
|
|
76
|
+
return readFileSync(path, "utf8");
|
|
77
|
+
} catch {
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isDirectory(path) {
|
|
82
|
+
try {
|
|
83
|
+
return lstatSync(path).isDirectory();
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Force the runtime home's profile to a real copy of the packaged seed.
|
|
90
|
+
* @param seedDir - packaged seed root (`<exeDir>/../dsh-home`).
|
|
91
|
+
* @param home - resolved runtime `DSH_HOME`.
|
|
92
|
+
* @returns whether the profile was (re)written.
|
|
93
|
+
*/
|
|
94
|
+
function ensureSeedProfile(seedDir, home) {
|
|
95
|
+
const seedProfile = join(seedDir, "profiles", PROFILE_NAME);
|
|
96
|
+
if (!isDirectory(seedProfile)) return false;
|
|
97
|
+
const profileDir = join(home, "profiles", PROFILE_NAME);
|
|
98
|
+
const seedHash = readSeedHash(join(seedProfile, SEED_HASH_NAME));
|
|
99
|
+
if (seedHash !== "") try {
|
|
100
|
+
if (!lstatSync(profileDir).isSymbolicLink() && readSeedHash(join(profileDir, ".seed-hash")) === seedHash) return false;
|
|
101
|
+
} catch {}
|
|
102
|
+
try {
|
|
103
|
+
const stat = lstatSync(profileDir);
|
|
104
|
+
if (stat.isSymbolicLink()) unlinkSync(profileDir);
|
|
105
|
+
else if (stat.isDirectory()) rmSync(profileDir, { recursive: true });
|
|
106
|
+
else unlinkSync(profileDir);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code !== "ENOENT") throw error;
|
|
109
|
+
}
|
|
110
|
+
copySeed(seedDir, home);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Copy the seed into the home, skipping bookkeeping directories and never
|
|
115
|
+
* overwriting user data. The seed's node_modules is a self-contained pnpm
|
|
116
|
+
* isolated layout (real copies in `.pnpm`, relative links everywhere else),
|
|
117
|
+
* so symlinks are recreated as-is — dereferencing would break the layout.
|
|
118
|
+
*/
|
|
119
|
+
function copySeed(seedDir, home) {
|
|
120
|
+
mkdirSync(home, { recursive: true });
|
|
121
|
+
const visit = (directory) => {
|
|
122
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
123
|
+
const source = join(directory, entry.name);
|
|
124
|
+
const target = join(home, relative(seedDir, source));
|
|
125
|
+
if (entry.isSymbolicLink()) {
|
|
126
|
+
if (existsSync(target)) continue;
|
|
127
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
128
|
+
symlinkSync(readlinkSync(source), target, process.platform === "win32" ? "junction" : "dir");
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let stat;
|
|
132
|
+
try {
|
|
133
|
+
stat = lstatSync(source);
|
|
134
|
+
} catch {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (stat.isDirectory()) {
|
|
138
|
+
if (SEED_SKIP_DIRS.has(entry.name)) continue;
|
|
139
|
+
mkdirSync(target, { recursive: true });
|
|
140
|
+
visit(source);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!stat.isFile() || existsSync(target)) continue;
|
|
144
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
145
|
+
copyFileSync(source, target);
|
|
146
|
+
chmodSync(target, stat.mode & 511);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
visit(seedDir);
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
export { writeAppConfig as a, loadAppConfig as i, ensureSeedProfile as n, PROFILE_NAME as r, SEED_HASH_NAME as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morlay/dsh-desktopify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Desktop packaging tool for dsh workspaces: hosts the upstream dsh-desktop-host child over framed byte pipes, with dev (workspace-linked) and bundle (static, unsigned) modes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"desktop",
|
|
7
|
+
"dsh",
|
|
8
|
+
"electron",
|
|
9
|
+
"electron-builder",
|
|
10
|
+
"packaging"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "morlay",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/morlay/better-session.git"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"dsh-desktopify": "./dist/cli/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.mjs",
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": "./dist/index.mjs",
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@deepseek-ai/dsh": "^0.1.5-rc.1",
|
|
33
|
+
"@deepseek-ai/dsh-desktop-host": "^0.1.5-rc.1",
|
|
34
|
+
"commander": "^15.0.0",
|
|
35
|
+
"electron": "^44.3.0",
|
|
36
|
+
"electron-builder": "^26.16.1",
|
|
37
|
+
"extract-zip": "^2.0.1",
|
|
38
|
+
"sharp": "^0.35.4",
|
|
39
|
+
"tar": "^7.5.0",
|
|
40
|
+
"tsdown": "0.22.x"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^26.5.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=24.11.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "pnpm exec tsdown"
|
|
50
|
+
}
|
|
51
|
+
}
|