@nissyai/desktop 0.2.55 → 0.2.59
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/bin/nissy.mjs +144 -142
- package/package.json +4 -4
- package/src/cli.d.mts +13 -0
- package/src/cli.mjs +63 -5
- package/src/demo-preflight.d.mts +35 -0
- package/src/demo-preflight.mjs +129 -0
- package/src/mac-store.d.mts +8 -0
- package/src/mac-store.mjs +664 -0
- package/src/resolve.d.mts +4 -0
- package/src/resolve.mjs +0 -17
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Check a Demo Mode template BEFORE launching, so a stale one is reported in
|
|
2
|
+
// the terminal the user just typed `nissy --demo` into.
|
|
3
|
+
//
|
|
4
|
+
// Without this the launcher spawns the app detached and returns immediately,
|
|
5
|
+
// the app refuses the template and shows a dialog, and the terminal says
|
|
6
|
+
// nothing at all. The desktop's own check stays the authority: this is an
|
|
7
|
+
// early, advisory copy of the part of it that can be decided from outside.
|
|
8
|
+
//
|
|
9
|
+
// It compares migration NAMES: the template's manifest records the migrations
|
|
10
|
+
// it was built against, and the installed app embeds the names of the ones it
|
|
11
|
+
// bundles. The desktop compares a fingerprint over names AND contents, so a
|
|
12
|
+
// name mismatch here always means the desktop would refuse too. The reverse
|
|
13
|
+
// is not guaranteed, which is why every "cannot tell" path below lets the
|
|
14
|
+
// launch go ahead and leaves the verdict to the app. This must never be the
|
|
15
|
+
// reason `--demo` fails to start when the app itself would have run.
|
|
16
|
+
|
|
17
|
+
import { dirname, join, resolve } from "node:path";
|
|
18
|
+
|
|
19
|
+
export const DEMO_FLAG = "--demo";
|
|
20
|
+
|
|
21
|
+
/** The fix, stated once, so every message points at the same command. */
|
|
22
|
+
const REFRESH = "pnpm demo:refresh";
|
|
23
|
+
const BUILD = `Build one from a Nissy checkout with: ${REFRESH}`;
|
|
24
|
+
const REBUILD = `Rebuild it from a Nissy checkout with: ${REFRESH}`;
|
|
25
|
+
|
|
26
|
+
/** How the app bundles its migrations inside app.asar. */
|
|
27
|
+
const EMBEDDED_MIGRATION = /migrations\/(\d{4}_[a-z0-9_]+\.sql)/g;
|
|
28
|
+
|
|
29
|
+
/** Where Electron keeps userData for an app with this name. */
|
|
30
|
+
export function userDataDir({ platform, home, env, userDataName }) {
|
|
31
|
+
if (platform === "win32") {
|
|
32
|
+
return env.APPDATA === undefined ? null : join(env.APPDATA, userDataName);
|
|
33
|
+
}
|
|
34
|
+
if (platform === "darwin") return join(home, "Library", "Application Support", userDataName);
|
|
35
|
+
// Electron's own rule on Linux, not a guess: XDG_CONFIG_HOME, else ~/.config.
|
|
36
|
+
return join(env.XDG_CONFIG_HOME ?? join(home, ".config"), userDataName);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The template the app will look for. Mirrors the desktop's
|
|
41
|
+
* `resolveDemoTemplatePath`: the profile's demo directory, except that an
|
|
42
|
+
* internal build honours NISSY_DEMO_TEMPLATE_PATH and a production build does
|
|
43
|
+
* not.
|
|
44
|
+
*/
|
|
45
|
+
export function demoTemplatePath({ userData, flavor, env }) {
|
|
46
|
+
const override = env.NISSY_DEMO_TEMPLATE_PATH;
|
|
47
|
+
if (flavor === "internal" && override !== undefined && override.length > 0) return resolve(override);
|
|
48
|
+
return join(userData, "demo", "nissy-demo.db");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The app.asar beside the executable being launched, or null. */
|
|
52
|
+
export function asarBeside(exePath, exists) {
|
|
53
|
+
const candidates = [
|
|
54
|
+
join(dirname(exePath), "resources", "app.asar"), // Windows, Linux
|
|
55
|
+
join(dirname(exePath), "..", "Resources", "app.asar"), // macOS bundle
|
|
56
|
+
];
|
|
57
|
+
return candidates.find((candidate) => exists(candidate)) ?? null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function embeddedMigrations(asarText) {
|
|
61
|
+
const names = new Set();
|
|
62
|
+
for (const match of asarText.matchAll(EMBEDDED_MIGRATION)) names.add(match[1]);
|
|
63
|
+
return [...names].sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {{
|
|
68
|
+
* args: readonly string[],
|
|
69
|
+
* templatePath: string,
|
|
70
|
+
* asarPath: string | null,
|
|
71
|
+
* appLabel: string,
|
|
72
|
+
* command: string,
|
|
73
|
+
* exists: (path: string) => boolean,
|
|
74
|
+
* readText: (path: string, encoding: BufferEncoding) => string,
|
|
75
|
+
* }} input
|
|
76
|
+
* @returns {{ ok: true } | { ok: false, message: string }}
|
|
77
|
+
*/
|
|
78
|
+
export function demoPreflight({ args, templatePath, asarPath, appLabel, command, exists, readText }) {
|
|
79
|
+
if (!args.includes(DEMO_FLAG)) return { ok: true };
|
|
80
|
+
|
|
81
|
+
const manifestPath = `${templatePath}.manifest.json`;
|
|
82
|
+
const retry = `Then run: ${command} ${DEMO_FLAG}`;
|
|
83
|
+
|
|
84
|
+
if (!exists(templatePath) || !exists(manifestPath)) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
message:
|
|
88
|
+
`${DEMO_FLAG} needs a demo template, and there is none at\n ${templatePath}\n` +
|
|
89
|
+
`${BUILD}\n${retry}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let manifest;
|
|
94
|
+
try {
|
|
95
|
+
manifest = JSON.parse(readText(manifestPath, "utf8"));
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
message: `the demo template manifest is unreadable:\n ${manifestPath}\n${REBUILD}\n${retry}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A template from before manifests recorded their migrations cannot be
|
|
104
|
+
// checked from here. The app still validates it at launch.
|
|
105
|
+
if (!Array.isArray(manifest.migrations)) return { ok: true };
|
|
106
|
+
if (asarPath === null) return { ok: true };
|
|
107
|
+
|
|
108
|
+
let installed;
|
|
109
|
+
try {
|
|
110
|
+
installed = embeddedMigrations(readText(asarPath, "latin1"));
|
|
111
|
+
} catch {
|
|
112
|
+
return { ok: true };
|
|
113
|
+
}
|
|
114
|
+
if (installed.length === 0) return { ok: true };
|
|
115
|
+
|
|
116
|
+
const inTemplate = new Set(manifest.migrations);
|
|
117
|
+
const inApp = new Set(installed);
|
|
118
|
+
const appHasMore = installed.filter((name) => !inTemplate.has(name));
|
|
119
|
+
const templateHasMore = manifest.migrations.filter((name) => !inApp.has(name));
|
|
120
|
+
if (appHasMore.length === 0 && templateHasMore.length === 0) return { ok: true };
|
|
121
|
+
|
|
122
|
+
const reason = appHasMore.length > 0
|
|
123
|
+
? `${appLabel} has ${appHasMore.join(", ")}, which the template was built without`
|
|
124
|
+
: `the template was built for a newer Nissy (it includes ${templateHasMore.join(", ")})`;
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
message: `the demo template does not match ${appLabel}: ${reason}.\n${REBUILD}\n${retry}`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Types for the parts of the macOS store the DESKTOP consumes.
|
|
2
|
+
//
|
|
3
|
+
// Declared, like `store.d.mts`, only so `apps/desktop`'s parity test imports a
|
|
4
|
+
// typed function rather than `any`. Nothing outside the launcher should use it.
|
|
5
|
+
|
|
6
|
+
/** Whether an extracted bundle stays inside `root`, symlinks included. Mirrored
|
|
7
|
+
* by the app's `mac-bundle.ts`; enforced by `mac-bundle.test.ts`. */
|
|
8
|
+
export declare function isExtractedBundleInside(root: string, bundlePath: string): boolean;
|