quilltap 4.11.0-dev.97 → 4.12.0-dev.1
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/README.md +1 -1
- package/bin/quilltap.js +4 -4
- package/lib/__tests__/platform-paths.test.js +62 -0
- package/lib/db-helpers.js +2 -5
- package/lib/lock-helpers.js +1 -4
- package/lib/native-modules.js +83 -155
- package/lib/platform-paths.js +45 -0
- package/lib/theme-commands.js +2 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ quilltap --update
|
|
|
65
65
|
|
|
66
66
|
## How It Works
|
|
67
67
|
|
|
68
|
-
The `quilltap` npm package is a lightweight CLI launcher (~10 KB). On first run, it downloads the pre-built application from [GitHub Releases](https://github.com/foundry-9/quilltap-server/releases) and caches it in a platform-specific directory. Native modules (`better-sqlite3`, `sharp`)
|
|
68
|
+
The `quilltap` npm package is a lightweight CLI launcher (~10 KB). On first run, it downloads the pre-built application from [GitHub Releases](https://github.com/foundry-9/quilltap-server/releases) and caches it in a platform-specific directory. Native modules (`better-sqlite3`, `sharp`) ship as prebuilt binaries for your platform — nothing is compiled on install. On Linux, the SQLCipher driver needs glibc 2.35 or newer (Ubuntu 22.04, Debian 12, RHEL 9 or newer); older hosts should run the Docker image.
|
|
69
69
|
|
|
70
70
|
### Cache Locations
|
|
71
71
|
|
package/bin/quilltap.js
CHANGED
|
@@ -347,7 +347,7 @@ async function main() {
|
|
|
347
347
|
process.exit(1);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
// Ensure native modules
|
|
350
|
+
// Ensure native modules load under the current Node.js version
|
|
351
351
|
ensureNativeModules();
|
|
352
352
|
|
|
353
353
|
// Symlink native modules into standalone dir so standard resolution finds them
|
|
@@ -1199,9 +1199,9 @@ const subName = subIdx >= 0 ? cliArgs[subIdx] : '';
|
|
|
1199
1199
|
const subArgs = subIdx >= 0 ? [...cliArgs.slice(0, subIdx), ...cliArgs.slice(subIdx + 1)] : [];
|
|
1200
1200
|
|
|
1201
1201
|
// Subcommands load the SQLCipher binding directly and never reach main()'s
|
|
1202
|
-
// native-module
|
|
1203
|
-
//
|
|
1204
|
-
//
|
|
1202
|
+
// native-module check, so check the driver here first. Silent when healthy;
|
|
1203
|
+
// when the prebuilt binary won't load (unsupported platform, old glibc) it
|
|
1204
|
+
// explains why with a warning rather than an error.
|
|
1205
1205
|
// `file-verify` is excluded: it's a pure-fs recovery tool that never opens the
|
|
1206
1206
|
// database, so it must work even when the native binding is broken.
|
|
1207
1207
|
if (subName && subName !== 'file-verify') {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform default instance root, including the Docker case.
|
|
3
|
+
*
|
|
4
|
+
* The bug this guards: inside the Docker image `quilltap db --lock-status`
|
|
5
|
+
* fell through to `~/.quilltap` (a home directory the image never creates),
|
|
6
|
+
* reported "No instance lock found" while the server held one, and
|
|
7
|
+
* `db --tables` reported a missing database.
|
|
8
|
+
*
|
|
9
|
+
* @jest-environment node
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { getDefaultBaseDir, isDockerContainer, DOCKER_BASE_DIR } = require('../platform-paths');
|
|
18
|
+
const { resolveDataDir } = require('../db-helpers');
|
|
19
|
+
|
|
20
|
+
const savedEnv = { ...process.env };
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.env = { ...savedEnv };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('platform default instance root', () => {
|
|
27
|
+
it('is /app/quilltap inside the Docker image', () => {
|
|
28
|
+
process.env.DOCKER_CONTAINER = 'true';
|
|
29
|
+
expect(isDockerContainer()).toBe(true);
|
|
30
|
+
expect(getDefaultBaseDir()).toBe(DOCKER_BASE_DIR);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('is the per-OS location outside a container', () => {
|
|
34
|
+
delete process.env.DOCKER_CONTAINER;
|
|
35
|
+
if (isDockerContainer()) return; // the suite itself is running in a container
|
|
36
|
+
const expected = process.platform === 'darwin'
|
|
37
|
+
? path.join(os.homedir(), 'Library', 'Application Support', 'Quilltap')
|
|
38
|
+
: process.platform === 'win32'
|
|
39
|
+
? path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Quilltap')
|
|
40
|
+
: path.join(os.homedir(), '.quilltap');
|
|
41
|
+
expect(getDefaultBaseDir()).toBe(expected);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('resolveDataDir', () => {
|
|
46
|
+
it('defaults to the Docker data directory inside the image', () => {
|
|
47
|
+
process.env.DOCKER_CONTAINER = 'true';
|
|
48
|
+
delete process.env.QUILLTAP_DATA_DIR;
|
|
49
|
+
expect(resolveDataDir()).toBe('/app/quilltap/data');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('still lets QUILLTAP_DATA_DIR win inside the image', () => {
|
|
53
|
+
process.env.DOCKER_CONTAINER = 'true';
|
|
54
|
+
process.env.QUILLTAP_DATA_DIR = '/elsewhere';
|
|
55
|
+
expect(resolveDataDir()).toBe(path.join('/elsewhere', 'data'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('still lets an explicit override win', () => {
|
|
59
|
+
process.env.DOCKER_CONTAINER = 'true';
|
|
60
|
+
expect(resolveDataDir('/explicit')).toBe(path.join('/explicit', 'data'));
|
|
61
|
+
});
|
|
62
|
+
});
|
package/lib/db-helpers.js
CHANGED
|
@@ -15,14 +15,11 @@ function resolveDataDir(overrideDir) {
|
|
|
15
15
|
: overrideDir;
|
|
16
16
|
return path.join(resolved, 'data');
|
|
17
17
|
}
|
|
18
|
-
const os = require('os');
|
|
19
18
|
if (process.env.QUILLTAP_DATA_DIR) {
|
|
20
19
|
return path.join(process.env.QUILLTAP_DATA_DIR, 'data');
|
|
21
20
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
if (process.platform === 'win32') return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Quilltap', 'data');
|
|
25
|
-
return path.join(home, '.quilltap', 'data');
|
|
21
|
+
const { getDefaultBaseDir } = require('./platform-paths');
|
|
22
|
+
return path.join(getDefaultBaseDir(), 'data');
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
// Resolve the data dir + database passphrase a subcommand should use, given
|
package/lib/lock-helpers.js
CHANGED
|
@@ -206,10 +206,7 @@ let exitHandlersRegistered = false;
|
|
|
206
206
|
function detectEnvironmentType() {
|
|
207
207
|
if (process.versions && process.versions.electron) return 'electron';
|
|
208
208
|
if (process.env.ELECTRON_DEV) return 'electron';
|
|
209
|
-
if (
|
|
210
|
-
try {
|
|
211
|
-
if (fs.existsSync('/.dockerenv')) return 'docker';
|
|
212
|
-
} catch { /* not Docker */ }
|
|
209
|
+
if (require('./platform-paths').isDockerContainer()) return 'docker';
|
|
213
210
|
return 'local';
|
|
214
211
|
}
|
|
215
212
|
|
package/lib/native-modules.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Shared helpers for keeping native modules
|
|
4
|
-
//
|
|
5
|
-
// `postinstall` hook
|
|
6
|
-
//
|
|
3
|
+
// Shared helpers for keeping native modules *usable* under the running Node.
|
|
4
|
+
// Used by both the runtime CLI entry (bin/quilltap.js) and the package's
|
|
5
|
+
// `postinstall` hook.
|
|
6
|
+
//
|
|
7
|
+
// The SQLCipher driver (better-sqlite3-multiple-ciphers, aliased as
|
|
8
|
+
// better-sqlite3) is a Node-API addon that ships one prebuilt binary per
|
|
9
|
+
// platform inside its npm tarball, so there is nothing to compile and no Node
|
|
10
|
+
// ABI to drift. The only ways it can fail to load are a platform with no
|
|
11
|
+
// prebuild or a host libc older than the binary; we detect and explain those,
|
|
12
|
+
// never rebuild. sharp and node-pty keep their `npm rebuild` heal.
|
|
7
13
|
|
|
8
14
|
const path = require('path');
|
|
9
15
|
const { execSync } = require('child_process');
|
|
@@ -21,123 +27,61 @@ function resolveModuleDir(moduleName) {
|
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
const SUPPORTED_DRIVER_TARGETS = [
|
|
31
|
+
'darwin-x64', 'darwin-arm64',
|
|
32
|
+
'linux-x64', 'linux-arm64',
|
|
33
|
+
'linuxmusl-x64', 'linuxmusl-arm64',
|
|
34
|
+
'win32-x64', 'win32-arm64',
|
|
35
|
+
];
|
|
36
|
+
const GLIBC_FLOOR = '2.35';
|
|
37
|
+
|
|
38
|
+
// Which SQLCipher binding file would the driver's loader pick on this host?
|
|
39
|
+
// Asks the package's own `lib/binding.js#getPrebuildPath()` (required by
|
|
40
|
+
// absolute path on purpose: the package's `exports` map blocks the subpath
|
|
41
|
+
// form). A v12 or from-source copy has no prebuilds, so fall back to the
|
|
42
|
+
// from-source build/ location to describe that install truthfully. Returns the absolute
|
|
43
|
+
// path, or null when neither exists.
|
|
26
44
|
function betterSqlite3BindingPath() {
|
|
45
|
+
const fs = require('fs');
|
|
27
46
|
const modDir = resolveModuleDir('better-sqlite3-multiple-ciphers')
|
|
28
47
|
|| resolveModuleDir('better-sqlite3');
|
|
29
48
|
if (!modDir) return null;
|
|
30
|
-
return path.join(modDir, 'build', 'Release', 'better_sqlite3.node');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Read the Node ABI (NODE_MODULE_VERSION) a node-gyp/NAN addon was compiled
|
|
34
|
-
// against, by scanning for its `node_register_module_v<ABI>` export — a few-KB
|
|
35
|
-
// byte read, no dlopen, no rebuild. Returns the ABI as a string, or null when
|
|
36
|
-
// the symbol is absent (e.g. an N-API build, which is ABI-stable) or unreadable.
|
|
37
|
-
function readCompiledAbi(bindingPath) {
|
|
38
49
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
return m ? m[1] : null;
|
|
50
|
+
const prebuild = require(path.join(modDir, 'lib', 'binding.js')).getPrebuildPath();
|
|
51
|
+
if (prebuild) return prebuild;
|
|
42
52
|
} catch {
|
|
43
|
-
|
|
53
|
+
// older layout — no lib/binding.js
|
|
44
54
|
}
|
|
55
|
+
const built = path.join(modDir, 'build', 'Release', 'better_sqlite3.node');
|
|
56
|
+
return fs.existsSync(built) ? built : null;
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
require(bindingPath);
|
|
59
|
-
return false;
|
|
60
|
-
} catch (err) {
|
|
61
|
-
return !!(err.message && err.message.includes('NODE_MODULE_VERSION'));
|
|
59
|
+
// Turn a failed driver load into a reason the user can act on. Pure; exported
|
|
60
|
+
// for tests. Returns { kind, message, hint }.
|
|
61
|
+
function classifyDriverLoadFailure({ platform, arch, prebuildPath, error }) {
|
|
62
|
+
if (!prebuildPath) {
|
|
63
|
+
return {
|
|
64
|
+
kind: 'unsupported-platform',
|
|
65
|
+
message: `the SQLCipher driver has no prebuilt binary for ${platform}-${arch}.`,
|
|
66
|
+
hint: `Supported targets: ${SUPPORTED_DRIVER_TARGETS.join(', ')}. ` +
|
|
67
|
+
'On anything else, run the Quilltap Docker image.',
|
|
68
|
+
};
|
|
62
69
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
path.join(pkgDir, 'node_modules', '.bin'), // nested install
|
|
73
|
-
path.join(pkgDir, '..', '.bin'), // hoisted alongside the package
|
|
74
|
-
];
|
|
75
|
-
for (const dir of dirs) {
|
|
76
|
-
for (const name of names) {
|
|
77
|
-
const candidate = path.join(dir, name);
|
|
78
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
79
|
-
}
|
|
70
|
+
const errMessage = (error && error.message) || String(error || 'unknown error');
|
|
71
|
+
const glibc = /GLIBC_(\d+\.\d+)/.exec(errMessage);
|
|
72
|
+
if (glibc) {
|
|
73
|
+
return {
|
|
74
|
+
kind: 'glibc-too-old',
|
|
75
|
+
message: `the SQLCipher driver needs glibc ${GLIBC_FLOOR} or newer ` +
|
|
76
|
+
`(this host lacks GLIBC_${glibc[1]}).`,
|
|
77
|
+
hint: 'Use Ubuntu 22.04, Debian 12, RHEL 9 or newer, or run the Quilltap Docker image.',
|
|
78
|
+
};
|
|
80
79
|
}
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Deliberately does NOT go through `npm rebuild <name>`, which fails this job
|
|
87
|
-
// two different ways:
|
|
88
|
-
//
|
|
89
|
-
// 1. npm refuses install scripts for any package not listed in the root
|
|
90
|
-
// package.json `allowScripts` map (keyed `name@version`). The root
|
|
91
|
-
// SQLCipher copy is installed under the ALIAS `better-sqlite3`, which is
|
|
92
|
-
// not a key there, so the rebuild dies with EALLOWSCRIPTS.
|
|
93
|
-
// 2. Asking for the real name (`better-sqlite3-multiple-ciphers`) at the root
|
|
94
|
-
// instead resolves a phantom directory: npm reports "rebuilt dependencies
|
|
95
|
-
// successfully" and the binding on disk is untouched. A success message is
|
|
96
|
-
// worse than an error.
|
|
97
|
-
//
|
|
98
|
-
// Running the package's own build chain (`prebuild-install || node-gyp rebuild`,
|
|
99
|
-
// exactly what its `install` script does) in its own directory sidesteps both:
|
|
100
|
-
// no name resolution, no npm script policy. `prebuild-install` downloads the
|
|
101
|
-
// prebuilt binary for the running ABI and needs network; node-gyp compiles and
|
|
102
|
-
// needs a toolchain.
|
|
103
|
-
//
|
|
104
|
-
// Returns { ok, reason }. Never throws. Verifies the result rather than trusting
|
|
105
|
-
// the exit code — see (2): the whole failure mode here is a rebuild that claims
|
|
106
|
-
// to have worked.
|
|
107
|
-
function rebuildNativePackage(pkgDir, bindingPath) {
|
|
108
|
-
const fs = require('fs');
|
|
109
|
-
if (!fs.existsSync(pkgDir)) return { ok: false, reason: `no package at ${pkgDir}` };
|
|
110
|
-
|
|
111
|
-
const before = fs.existsSync(bindingPath) ? readCompiledAbi(bindingPath) : null;
|
|
112
|
-
const attempts = [];
|
|
113
|
-
|
|
114
|
-
const prebuild = findBinFor(pkgDir, 'prebuild-install');
|
|
115
|
-
if (prebuild) attempts.push({ label: 'prebuild-install', cmd: `"${prebuild}"` });
|
|
116
|
-
|
|
117
|
-
const nodeGyp = findBinFor(pkgDir, 'node-gyp');
|
|
118
|
-
if (nodeGyp) attempts.push({ label: 'node-gyp rebuild', cmd: `"${nodeGyp}" rebuild --release` });
|
|
119
|
-
|
|
120
|
-
if (attempts.length === 0) {
|
|
121
|
-
return { ok: false, reason: 'neither prebuild-install nor node-gyp is installed' };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const failures = [];
|
|
125
|
-
for (const attempt of attempts) {
|
|
126
|
-
try {
|
|
127
|
-
execSync(attempt.cmd, { cwd: pkgDir, stdio: 'inherit' });
|
|
128
|
-
} catch (err) {
|
|
129
|
-
failures.push(`${attempt.label}: ${err.message.split('\n')[0]}`);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
// Trust nothing but the binary. A tool can exit 0 having done nothing.
|
|
133
|
-
const after = fs.existsSync(bindingPath) ? readCompiledAbi(bindingPath) : null;
|
|
134
|
-
if (after === process.versions.modules) return { ok: true, reason: attempt.label };
|
|
135
|
-
failures.push(
|
|
136
|
-
`${attempt.label}: exited 0 but the binding is still ${after ?? 'missing'} ` +
|
|
137
|
-
`(wanted ${process.versions.modules}${before ? `, was ${before}` : ''})`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
return { ok: false, reason: failures.join('; ') };
|
|
80
|
+
return {
|
|
81
|
+
kind: 'load-failed',
|
|
82
|
+
message: `the SQLCipher driver failed to load: ${errMessage.split('\n')[0]}`,
|
|
83
|
+
hint: 'Reinstall the quilltap package; if it persists, file an issue quoting the line above.',
|
|
84
|
+
};
|
|
141
85
|
}
|
|
142
86
|
|
|
143
87
|
// Rebuild the named native modules against the current Node ABI. Prints a
|
|
@@ -163,36 +107,30 @@ function rebuildModules(moduleNames) {
|
|
|
163
107
|
}
|
|
164
108
|
}
|
|
165
109
|
|
|
166
|
-
// Fast pre-flight for the
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// self-heals instead of throwing. Cheap no-op when already healthy. Never throws.
|
|
110
|
+
// Fast pre-flight for the SQLCipher driver every DB path needs. Loads the
|
|
111
|
+
// binding the driver would pick; when it won't load, prints why and what to do.
|
|
112
|
+
// Prints nothing when healthy, never rebuilds, never throws. Returns true when
|
|
113
|
+
// the binding loads, false otherwise.
|
|
171
114
|
function ensureDatabaseNativeModule() {
|
|
115
|
+
let prebuildPath = null;
|
|
116
|
+
let error = null;
|
|
172
117
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const bindingPath = betterSqlite3BindingPath();
|
|
179
|
-
if (!bindingPath) {
|
|
180
|
-
console.error(' Warning: could not locate the SQLCipher binding to rebuild.');
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
// build/Release/better_sqlite3.node → the package directory above it.
|
|
184
|
-
const pkgDir = path.resolve(path.dirname(bindingPath), '..', '..');
|
|
185
|
-
|
|
186
|
-
console.log(` Rebuilding the SQLCipher binding for Node.js ${process.version}...`);
|
|
187
|
-
const result = rebuildNativePackage(pkgDir, bindingPath);
|
|
188
|
-
if (result.ok) {
|
|
189
|
-
console.log(` Done (${result.reason}).`);
|
|
190
|
-
console.log('');
|
|
191
|
-
return true;
|
|
118
|
+
prebuildPath = betterSqlite3BindingPath();
|
|
119
|
+
if (prebuildPath) require(prebuildPath);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
error = err;
|
|
192
122
|
}
|
|
123
|
+
if (prebuildPath && !error) return true;
|
|
124
|
+
|
|
125
|
+
const { message, hint } = classifyDriverLoadFailure({
|
|
126
|
+
platform: process.platform,
|
|
127
|
+
arch: process.arch,
|
|
128
|
+
prebuildPath,
|
|
129
|
+
error,
|
|
130
|
+
});
|
|
193
131
|
console.error('');
|
|
194
|
-
console.error(` Warning:
|
|
195
|
-
console.error(` Try
|
|
132
|
+
console.error(` Warning: ${message}`);
|
|
133
|
+
console.error(` Try: ${hint}`);
|
|
196
134
|
console.error('');
|
|
197
135
|
return false;
|
|
198
136
|
}
|
|
@@ -239,22 +177,15 @@ function reconcileNodePtySpawnHelper() {
|
|
|
239
177
|
}
|
|
240
178
|
}
|
|
241
179
|
|
|
242
|
-
// Check
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
// Returns true if everything was healthy or successfully rebuilt; false
|
|
246
|
-
//
|
|
180
|
+
// Check that native modules load under the current Node.js version. sharp and
|
|
181
|
+
// node-pty are rebuilt when npx cached them under an older Node; the SQLCipher
|
|
182
|
+
// driver is only checked and explained (see ensureDatabaseNativeModule).
|
|
183
|
+
// Returns true if everything was healthy or successfully rebuilt; false
|
|
184
|
+
// otherwise. Never throws.
|
|
247
185
|
function ensureNativeModules() {
|
|
186
|
+
const driverOk = ensureDatabaseNativeModule();
|
|
248
187
|
const needsRebuild = [];
|
|
249
188
|
|
|
250
|
-
// Check better-sqlite3-multiple-ciphers (provides SQLCipher encryption support).
|
|
251
|
-
// The main app depends on this via an npm alias as 'better-sqlite3', so we must
|
|
252
|
-
// ensure the SQLCipher-capable version is available and link it as 'better-sqlite3'.
|
|
253
|
-
// This is the only ABI-fragile binding — detected straight from the binary.
|
|
254
|
-
if (betterSqlite3NeedsRebuild()) {
|
|
255
|
-
needsRebuild.push('better-sqlite3-multiple-ciphers');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
189
|
// Check sharp: loads its native binding eagerly on require.
|
|
259
190
|
try {
|
|
260
191
|
require.resolve('sharp', { paths: [PACKAGE_DIR] });
|
|
@@ -283,19 +214,16 @@ function ensureNativeModules() {
|
|
|
283
214
|
|
|
284
215
|
if (needsRebuild.length === 0) {
|
|
285
216
|
reconcileNodePtySpawnHelper();
|
|
286
|
-
return
|
|
217
|
+
return driverOk;
|
|
287
218
|
}
|
|
288
219
|
|
|
289
|
-
return rebuildModules(needsRebuild);
|
|
220
|
+
return rebuildModules(needsRebuild) && driverOk;
|
|
290
221
|
}
|
|
291
222
|
|
|
292
223
|
module.exports = {
|
|
293
224
|
resolveModuleDir,
|
|
294
|
-
readCompiledAbi,
|
|
295
225
|
betterSqlite3BindingPath,
|
|
296
|
-
|
|
297
|
-
findBinFor,
|
|
298
|
-
rebuildNativePackage,
|
|
226
|
+
classifyDriverLoadFailure,
|
|
299
227
|
ensureDatabaseNativeModule,
|
|
300
228
|
ensureNativeModules,
|
|
301
229
|
reconcileNodePtySpawnHelper,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where an instance lives when nothing says otherwise.
|
|
5
|
+
*
|
|
6
|
+
* The CLI's mirror of the server's platform default (lib/paths.ts). Inside the
|
|
7
|
+
* Docker image the instance is always the bind mount at /app/quilltap, and the
|
|
8
|
+
* `nextjs` user has no home directory, so falling through to `~/.quilltap`
|
|
9
|
+
* sent `docker exec quilltap quilltap db …` to a database that does not exist
|
|
10
|
+
* and reported "no instance lock" while the server held one.
|
|
11
|
+
*
|
|
12
|
+
* Docker detection matches the server's lock-environment check
|
|
13
|
+
* (DOCKER_CONTAINER or /.dockerenv), deliberately without the server's
|
|
14
|
+
* "/app exists" heuristic, which misfires on hosts that happen to have /app.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
const DOCKER_BASE_DIR = '/app/quilltap';
|
|
22
|
+
|
|
23
|
+
function isDockerContainer() {
|
|
24
|
+
if (process.env.DOCKER_CONTAINER === 'true') return true;
|
|
25
|
+
try {
|
|
26
|
+
return fs.existsSync('/.dockerenv');
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The instance root used when no flag, env var or registered instance names one. */
|
|
33
|
+
function getDefaultBaseDir() {
|
|
34
|
+
if (isDockerContainer()) return DOCKER_BASE_DIR;
|
|
35
|
+
const home = os.homedir();
|
|
36
|
+
if (process.platform === 'darwin') return path.join(home, 'Library', 'Application Support', 'Quilltap');
|
|
37
|
+
if (process.platform === 'win32') return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Quilltap');
|
|
38
|
+
return path.join(home, '.quilltap');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
DOCKER_BASE_DIR,
|
|
43
|
+
isDockerContainer,
|
|
44
|
+
getDefaultBaseDir,
|
|
45
|
+
};
|
package/lib/theme-commands.js
CHANGED
|
@@ -21,6 +21,7 @@ const os = require('os');
|
|
|
21
21
|
const crypto = require('crypto');
|
|
22
22
|
const { execSync } = require('child_process');
|
|
23
23
|
const { validateThemeBundle, validateManifest } = require('./theme-validation');
|
|
24
|
+
const { getDefaultBaseDir } = require('./platform-paths');
|
|
24
25
|
|
|
25
26
|
// ============================================================================
|
|
26
27
|
// COLOR HELPERS
|
|
@@ -54,10 +55,7 @@ function resolveBaseDir(overrideDir) {
|
|
|
54
55
|
if (process.env.QUILLTAP_DATA_DIR) {
|
|
55
56
|
return process.env.QUILLTAP_DATA_DIR;
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
-
if (process.platform === 'darwin') return path.join(home, 'Library', 'Application Support', 'Quilltap');
|
|
59
|
-
if (process.platform === 'win32') return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Quilltap');
|
|
60
|
-
return path.join(home, '.quilltap');
|
|
58
|
+
return getDefaultBaseDir();
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
function getThemesDir(overrideDir) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quilltap",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0-dev.1",
|
|
4
4
|
"description": "Self-hosted AI workspace for writers, worldbuilders, and roleplayers. Run with npx quilltap.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Charles Sebold",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@napi-rs/canvas": "^1.0.13",
|
|
40
|
-
"better-sqlite3-multiple-ciphers": "^
|
|
40
|
+
"better-sqlite3-multiple-ciphers": "^13.0.3",
|
|
41
41
|
"node-pty": "^1.1.0",
|
|
42
42
|
"sharp": "^0.35.4",
|
|
43
43
|
"tar": "^7.5.22",
|