runwork 0.6.2 → 0.8.3
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/dist/__tests__/install-scripts.test.d.ts +1 -0
- package/dist/__tests__/install-scripts.test.js +247 -0
- package/dist/agents/__tests__/claude-code-plugin.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-code-plugin.test.js +47 -0
- package/dist/agents/__tests__/claude-code-stats.test.js +143 -78
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +161 -0
- package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-plugin.test.js +117 -0
- package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +1 -0
- package/dist/agents/__tests__/claude-desktop-rpm.test.js +126 -0
- package/dist/agents/__tests__/codex-instructions.test.d.ts +1 -0
- package/dist/agents/__tests__/codex-instructions.test.js +57 -0
- package/dist/agents/__tests__/codex-stats.test.js +9 -0
- package/dist/agents/__tests__/graceful-degradation.test.js +3 -3
- package/dist/agents/__tests__/intro-skill.test.js +11 -2
- package/dist/agents/__tests__/skill-slug.test.d.ts +1 -0
- package/dist/agents/__tests__/skill-slug.test.js +77 -0
- package/dist/agents/claude-code.d.ts +3 -1
- package/dist/agents/claude-code.js +233 -58
- package/dist/agents/claude-desktop-plugin-tree.d.ts +44 -0
- package/dist/agents/claude-desktop-plugin-tree.js +92 -0
- package/dist/agents/claude-desktop.d.ts +20 -1
- package/dist/agents/claude-desktop.js +238 -61
- package/dist/agents/cline.d.ts +3 -1
- package/dist/agents/cline.js +44 -2
- package/dist/agents/codex.d.ts +3 -1
- package/dist/agents/codex.js +85 -14
- package/dist/agents/cursor.d.ts +12 -1
- package/dist/agents/cursor.js +98 -25
- package/dist/agents/gemini.d.ts +3 -1
- package/dist/agents/gemini.js +42 -4
- package/dist/agents/generic-adapter.d.ts +3 -1
- package/dist/agents/generic-adapter.js +59 -4
- package/dist/agents/intro-skill.js +77 -31
- package/dist/agents/types.d.ts +42 -2
- package/dist/agents/types.js +61 -6
- package/dist/agents/utils/instruction-hint.d.ts +10 -0
- package/dist/agents/utils/instruction-hint.js +46 -0
- package/dist/agents/utils/json-config.d.ts +6 -1
- package/dist/agents/utils/json-config.js +31 -4
- package/dist/agents/windsurf.d.ts +3 -1
- package/dist/agents/windsurf.js +37 -2
- package/dist/commands/__tests__/sync-telemetry.test.d.ts +1 -0
- package/dist/commands/__tests__/sync-telemetry.test.js +281 -0
- package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
- package/dist/commands/__tests__/upgrade.test.js +174 -0
- package/dist/commands/build-plugin.d.ts +2 -0
- package/dist/commands/build-plugin.js +135 -0
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/init.js +19 -7
- package/dist/commands/sync-telemetry.d.ts +33 -0
- package/dist/commands/sync-telemetry.js +186 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +84 -118
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +145 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/commands/upgrade.js +67 -16
- package/dist/generated/bundled-types.js +36 -36
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/health/__tests__/cli-distribution-checks.test.d.ts +1 -0
- package/dist/health/__tests__/cli-distribution-checks.test.js +199 -0
- package/dist/health/checks.d.ts +2 -0
- package/dist/health/checks.js +131 -18
- package/dist/health/runner.js +6 -2
- package/dist/index.js +4 -0
- package/dist/sync/executor.d.ts +2 -0
- package/dist/sync/executor.js +8 -4
- package/dist/types.d.ts +2 -0
- package/dist/ui/banner.js +15 -6
- package/dist/utils/__tests__/sqlite.test.js +84 -72
- package/dist/utils/sqlite-adapter.d.ts +48 -0
- package/dist/utils/sqlite-adapter.js +147 -0
- package/dist/utils/sqlite.d.ts +12 -5
- package/dist/utils/sqlite.js +12 -22
- package/dist/utils/zip.d.ts +6 -0
- package/dist/utils/zip.js +28 -3
- package/package.json +2 -4
|
@@ -1,77 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the runtime SQLite adapter.
|
|
3
|
+
*
|
|
4
|
+
* The adapter picks bun:sqlite (compiled binary path) or node:sqlite
|
|
5
|
+
* (npx runwork@latest on Node >= 22.5) at module load time, and falls back
|
|
6
|
+
* to an "unavailable" path that returns empty string / null so SQLite-backed
|
|
7
|
+
* telemetry features degrade gracefully instead of breaking the CLI install.
|
|
8
|
+
*
|
|
9
|
+
* Vitest under the dev machine may run on Node 20 (no node:sqlite, no Bun
|
|
10
|
+
* global), so these tests gate fixture-database coverage on sqliteAvailable()
|
|
11
|
+
* and always assert the degradation contract.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from 'vitest';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { querySqlite, sqliteAvailable, sqliteFlavor, openWritableSqlite, } from '../sqlite.js';
|
|
16
|
+
const FIXTURE_DB = fileURLToPath(new URL('../../agents/__tests__/fixtures/codex/state.sqlite', import.meta.url));
|
|
17
|
+
const SQLITE_AVAILABLE = sqliteAvailable();
|
|
18
|
+
describe('sqlite adapter runtime detection', () => {
|
|
19
|
+
it('exposes a boolean sqliteAvailable flag', () => {
|
|
20
|
+
expect(typeof SQLITE_AVAILABLE).toBe('boolean');
|
|
21
|
+
});
|
|
22
|
+
it('reports bun, node, or null as the flavor', () => {
|
|
23
|
+
const flavor = sqliteFlavor();
|
|
24
|
+
expect(flavor === 'bun' || flavor === 'node' || flavor === null).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('agrees: available iff flavor is non-null', () => {
|
|
27
|
+
expect(sqliteAvailable()).toBe(sqliteFlavor() !== null);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('querySqlite unavailable-runtime contract', () => {
|
|
31
|
+
// These assertions must pass whether or not a SQLite runtime is available,
|
|
32
|
+
// because they only exercise the graceful-degradation path.
|
|
33
|
+
it('returns empty string for a nonexistent database path', () => {
|
|
34
|
+
expect(querySqlite('/nonexistent/path/that/does/not/exist.db', 'SELECT 1')).toBe('');
|
|
35
|
+
});
|
|
36
|
+
it('returns empty string for invalid SQL against a nonexistent path', () => {
|
|
37
|
+
expect(querySqlite('/nonexistent.db', 'INVALID SQL QUERY')).toBe('');
|
|
38
|
+
});
|
|
39
|
+
it('never throws even when asked for unreachable data', () => {
|
|
40
|
+
expect(() => querySqlite('/tmp/does-not-exist.db', 'SELECT 1')).not.toThrow();
|
|
41
|
+
});
|
|
7
42
|
});
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
43
|
+
describe.runIf(SQLITE_AVAILABLE)('querySqlite against the fixture database', () => {
|
|
44
|
+
it('returns pipe-separated values for multi-column results', () => {
|
|
45
|
+
const result = querySqlite(FIXTURE_DB, "SELECT id, tokens_used FROM threads WHERE id = 'old-session'");
|
|
46
|
+
expect(result).toBe('old-session|50000');
|
|
47
|
+
});
|
|
48
|
+
it('returns newline-separated rows for multiple rows', () => {
|
|
49
|
+
const result = querySqlite(FIXTURE_DB, 'SELECT id, tokens_used FROM threads ORDER BY created_at');
|
|
50
|
+
const lines = result.split('\n');
|
|
51
|
+
expect(lines).toHaveLength(3);
|
|
52
|
+
expect(lines[0]).toContain('old-session');
|
|
53
|
+
expect(lines[1]).toContain('recent-session');
|
|
54
|
+
expect(lines[2]).toContain('newest-session');
|
|
55
|
+
});
|
|
56
|
+
it('returns a single-column single-row result without delimiters', () => {
|
|
57
|
+
const result = querySqlite(FIXTURE_DB, 'SELECT max(tokens_used) FROM threads');
|
|
58
|
+
expect(result).toBe('120000');
|
|
59
|
+
});
|
|
60
|
+
it('returns empty string on SQL error even when runtime is available', () => {
|
|
61
|
+
const result = querySqlite(FIXTURE_DB, 'INVALID SQL QUERY');
|
|
62
|
+
expect(result).toBe('');
|
|
63
|
+
});
|
|
64
|
+
it('returns empty string when the target table does not exist', () => {
|
|
65
|
+
const result = querySqlite(FIXTURE_DB, 'SELECT * FROM nonexistent_table');
|
|
66
|
+
expect(result).toBe('');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('openWritableSqlite unavailable-runtime contract', () => {
|
|
70
|
+
it('returns null for a missing database path', () => {
|
|
71
|
+
expect(openWritableSqlite('/nonexistent/writable/db.sqlite')).toBeNull();
|
|
72
|
+
});
|
|
13
73
|
});
|
|
14
|
-
describe('
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// however vi.importActual in the module-scope mock is async.
|
|
23
|
-
// Instead, re-require the real module.
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
-
const Actual = require('better-sqlite3');
|
|
26
|
-
return new Actual(...args);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
it('returns pipe-separated values for multi-column results', () => {
|
|
30
|
-
const result = querySqlite(FIXTURE_DB, "SELECT id, tokens_used FROM threads WHERE id = 'old-session'");
|
|
31
|
-
expect(result).toBe('old-session|50000');
|
|
32
|
-
});
|
|
33
|
-
it('returns newline-separated rows for multiple rows', () => {
|
|
34
|
-
const result = querySqlite(FIXTURE_DB, 'SELECT id, tokens_used FROM threads ORDER BY created_at');
|
|
35
|
-
const lines = result.split('\n');
|
|
36
|
-
expect(lines).toHaveLength(3);
|
|
37
|
-
expect(lines[0]).toContain('old-session');
|
|
38
|
-
expect(lines[1]).toContain('recent-session');
|
|
39
|
-
expect(lines[2]).toContain('newest-session');
|
|
40
|
-
});
|
|
74
|
+
describe.runIf(SQLITE_AVAILABLE)('openWritableSqlite against the fixture database', () => {
|
|
75
|
+
it('opens the fixture database when it exists and can prepare SELECTs', () => {
|
|
76
|
+
const handle = openWritableSqlite(FIXTURE_DB);
|
|
77
|
+
expect(handle).not.toBeNull();
|
|
78
|
+
expect(handle?.flavor === 'bun' || handle?.flavor === 'node').toBe(true);
|
|
79
|
+
const row = handle.prepare("SELECT id FROM threads WHERE id = 'old-session'").get();
|
|
80
|
+
expect(row?.id).toBe('old-session');
|
|
81
|
+
handle.close();
|
|
41
82
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
const result = querySqlite('/nonexistent/path.db', 'SELECT 1');
|
|
49
|
-
expect(result).toBe('');
|
|
50
|
-
});
|
|
51
|
-
it('returns empty string when SQL is invalid', () => {
|
|
52
|
-
const mockPrepare = vi.fn().mockImplementation(() => {
|
|
53
|
-
throw new Error('SQLITE_ERROR: near "INVALID": syntax error');
|
|
54
|
-
});
|
|
55
|
-
const mockClose = vi.fn();
|
|
56
|
-
vi.mocked(Database).mockImplementation(() => ({ prepare: mockPrepare, close: mockClose }));
|
|
57
|
-
const result = querySqlite('/some/db.sqlite', 'INVALID SQL QUERY');
|
|
58
|
-
expect(result).toBe('');
|
|
59
|
-
});
|
|
60
|
-
it('returns empty string when database is corrupted', () => {
|
|
61
|
-
vi.mocked(Database).mockImplementation(() => {
|
|
62
|
-
throw new Error('SQLITE_CORRUPT: database disk image is malformed');
|
|
63
|
-
});
|
|
64
|
-
const result = querySqlite('/corrupted/db.sqlite', 'SELECT 1');
|
|
65
|
-
expect(result).toBe('');
|
|
66
|
-
});
|
|
67
|
-
it('closes database even on query error', () => {
|
|
68
|
-
const mockClose = vi.fn();
|
|
69
|
-
const mockPrepare = vi.fn().mockImplementation(() => {
|
|
70
|
-
throw new Error('query failed');
|
|
71
|
-
});
|
|
72
|
-
vi.mocked(Database).mockImplementation(() => ({ prepare: mockPrepare, close: mockClose }));
|
|
73
|
-
querySqlite('/some/db.sqlite', 'SELECT 1');
|
|
74
|
-
expect(mockClose).toHaveBeenCalledTimes(1);
|
|
75
|
-
});
|
|
83
|
+
it('close() tolerates double-close without throwing', () => {
|
|
84
|
+
const handle = openWritableSqlite(FIXTURE_DB);
|
|
85
|
+
expect(handle).not.toBeNull();
|
|
86
|
+
handle.close();
|
|
87
|
+
expect(() => handle.close()).not.toThrow();
|
|
76
88
|
});
|
|
77
89
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime SQLite adapter.
|
|
3
|
+
*
|
|
4
|
+
* Selects a SQLite backend at module load time:
|
|
5
|
+
* 1. `bun:sqlite` when running under Bun (the compiled standalone CLI binary)
|
|
6
|
+
* 2. `node:sqlite` when running on Node >= 22.5 (the `npx runwork@latest` path)
|
|
7
|
+
* 3. null, in which case SQLite-backed telemetry features degrade gracefully
|
|
8
|
+
* instead of breaking install
|
|
9
|
+
*
|
|
10
|
+
* This replaces the previous hard dependency on `better-sqlite3`, which
|
|
11
|
+
* required a native npm build and failed in restricted sandboxes, locked-down
|
|
12
|
+
* corporate environments, and low-privilege containers.
|
|
13
|
+
*/
|
|
14
|
+
type SqliteFlavor = 'bun' | 'node';
|
|
15
|
+
interface StatementHandle {
|
|
16
|
+
get(...params: unknown[]): unknown;
|
|
17
|
+
all(...params: unknown[]): unknown[];
|
|
18
|
+
run(...params: unknown[]): unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns true when a SQLite runtime is available.
|
|
22
|
+
* Use this to gate telemetry / agent-stats features that can degrade gracefully.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sqliteAvailable(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the name of the SQLite backend in use, or null if none is available.
|
|
27
|
+
* Intended for doctor / diagnostic output.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sqliteFlavor(): SqliteFlavor | null;
|
|
30
|
+
export declare function queryReadonlySqlite(dbPath: string, sql: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Handle returned by `openWritableSqlite` for write operations.
|
|
33
|
+
* Callers are responsible for calling `close()` when done.
|
|
34
|
+
*/
|
|
35
|
+
export interface WritableSqlite {
|
|
36
|
+
prepare(sql: string): StatementHandle;
|
|
37
|
+
close(): void;
|
|
38
|
+
readonly flavor: SqliteFlavor;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Open a writable SQLite database. Returns null when no SQLite runtime is
|
|
42
|
+
* available, letting the caller degrade gracefully instead of crashing.
|
|
43
|
+
*
|
|
44
|
+
* On failure to open the database (missing file, permission denied, locked),
|
|
45
|
+
* this also returns null.
|
|
46
|
+
*/
|
|
47
|
+
export declare function openWritableSqlite(dbPath: string): WritableSqlite | null;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime SQLite adapter.
|
|
3
|
+
*
|
|
4
|
+
* Selects a SQLite backend at module load time:
|
|
5
|
+
* 1. `bun:sqlite` when running under Bun (the compiled standalone CLI binary)
|
|
6
|
+
* 2. `node:sqlite` when running on Node >= 22.5 (the `npx runwork@latest` path)
|
|
7
|
+
* 3. null, in which case SQLite-backed telemetry features degrade gracefully
|
|
8
|
+
* instead of breaking install
|
|
9
|
+
*
|
|
10
|
+
* This replaces the previous hard dependency on `better-sqlite3`, which
|
|
11
|
+
* required a native npm build and failed in restricted sandboxes, locked-down
|
|
12
|
+
* corporate environments, and low-privilege containers.
|
|
13
|
+
*/
|
|
14
|
+
let adapter = null;
|
|
15
|
+
async function loadAdapter() {
|
|
16
|
+
// Try bun:sqlite first. Bun defines a global `Bun` object we can sniff for.
|
|
17
|
+
const bunGlobal = globalThis.Bun;
|
|
18
|
+
if (typeof bunGlobal !== 'undefined') {
|
|
19
|
+
try {
|
|
20
|
+
const specifier = 'bun:sqlite';
|
|
21
|
+
const mod = (await import(/* @vite-ignore */ specifier));
|
|
22
|
+
if (mod?.Database) {
|
|
23
|
+
return {
|
|
24
|
+
flavor: 'bun',
|
|
25
|
+
Database: mod.Database,
|
|
26
|
+
readOnlyOptionKey: 'readonly',
|
|
27
|
+
readOnlyOptionValue: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// bun:sqlite unavailable despite Bun global; fall through to Node path
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Try node:sqlite. Available in Node >= 22.5 stable.
|
|
36
|
+
try {
|
|
37
|
+
const specifier = 'node:sqlite';
|
|
38
|
+
const mod = (await import(/* @vite-ignore */ specifier));
|
|
39
|
+
if (mod?.DatabaseSync) {
|
|
40
|
+
return {
|
|
41
|
+
flavor: 'node',
|
|
42
|
+
Database: mod.DatabaseSync,
|
|
43
|
+
readOnlyOptionKey: 'readOnly',
|
|
44
|
+
readOnlyOptionValue: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// node:sqlite unavailable (older Node, or flag off)
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Top-level await: module load blocks until an adapter is chosen.
|
|
54
|
+
// The CLI is an ESM package running on Bun or modern Node, both of which
|
|
55
|
+
// support top-level await.
|
|
56
|
+
adapter = await loadAdapter();
|
|
57
|
+
/**
|
|
58
|
+
* Returns true when a SQLite runtime is available.
|
|
59
|
+
* Use this to gate telemetry / agent-stats features that can degrade gracefully.
|
|
60
|
+
*/
|
|
61
|
+
export function sqliteAvailable() {
|
|
62
|
+
return adapter !== null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns the name of the SQLite backend in use, or null if none is available.
|
|
66
|
+
* Intended for doctor / diagnostic output.
|
|
67
|
+
*/
|
|
68
|
+
export function sqliteFlavor() {
|
|
69
|
+
return adapter?.flavor ?? null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Execute a read-only SQL query against a SQLite database and return the rows
|
|
73
|
+
* serialised as `value1|value2|value3` per row, newline separated. Matches the
|
|
74
|
+
* legacy `querySqlite` contract so existing parsers do not have to change.
|
|
75
|
+
*
|
|
76
|
+
* Returns an empty string on any failure (missing database, locked, corrupt,
|
|
77
|
+
* unavailable runtime). Callers must tolerate the empty-string path.
|
|
78
|
+
*/
|
|
79
|
+
const BUSY_TIMEOUT_MS = 5000;
|
|
80
|
+
function setBusyTimeout(db) {
|
|
81
|
+
// bun:sqlite has no `timeout` constructor option and node:sqlite had a
|
|
82
|
+
// moving target across versions, so set busy_timeout via a PRAGMA that
|
|
83
|
+
// works on every SQLite build. PRAGMA busy_timeout is a per-connection
|
|
84
|
+
// runtime setting and does not require write permissions.
|
|
85
|
+
try {
|
|
86
|
+
db.prepare(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`).run();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* some flavors refuse PRAGMA on readonly; best-effort */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function queryReadonlySqlite(dbPath, sql) {
|
|
93
|
+
if (!adapter)
|
|
94
|
+
return '';
|
|
95
|
+
let db = null;
|
|
96
|
+
try {
|
|
97
|
+
const options = {
|
|
98
|
+
[adapter.readOnlyOptionKey]: adapter.readOnlyOptionValue,
|
|
99
|
+
};
|
|
100
|
+
db = new adapter.Database(dbPath, options);
|
|
101
|
+
setBusyTimeout(db);
|
|
102
|
+
const rows = db.prepare(sql).all();
|
|
103
|
+
return rows.map((row) => Object.values(row).join('|')).join('\n');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return '';
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
try {
|
|
110
|
+
db?.close();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* ignore close errors */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Open a writable SQLite database. Returns null when no SQLite runtime is
|
|
119
|
+
* available, letting the caller degrade gracefully instead of crashing.
|
|
120
|
+
*
|
|
121
|
+
* On failure to open the database (missing file, permission denied, locked),
|
|
122
|
+
* this also returns null.
|
|
123
|
+
*/
|
|
124
|
+
export function openWritableSqlite(dbPath) {
|
|
125
|
+
if (!adapter)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
const db = new adapter.Database(dbPath);
|
|
129
|
+
setBusyTimeout(db);
|
|
130
|
+
const flavor = adapter.flavor;
|
|
131
|
+
return {
|
|
132
|
+
prepare: (sql) => db.prepare(sql),
|
|
133
|
+
close: () => {
|
|
134
|
+
try {
|
|
135
|
+
db.close();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* ignore close errors */
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
flavor,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/dist/utils/sqlite.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Read-only SQLite query helper used by agent stats readers.
|
|
3
|
+
*
|
|
4
|
+
* This file used to hard-depend on `better-sqlite3`, which required a native
|
|
5
|
+
* npm build and failed in low-privilege containers and corporate sandboxes.
|
|
6
|
+
* It now re-exports the runtime-aware adapter which picks `bun:sqlite` in the
|
|
7
|
+
* compiled standalone binary, `node:sqlite` when running under `npx`, and a
|
|
8
|
+
* structured "unavailable" path otherwise (returns empty string).
|
|
9
|
+
*
|
|
10
|
+
* The `querySqlite` name is preserved so existing call sites and mocks in
|
|
11
|
+
* tests keep working.
|
|
6
12
|
*/
|
|
7
|
-
export
|
|
13
|
+
export { queryReadonlySqlite as querySqlite } from './sqlite-adapter.js';
|
|
14
|
+
export { openWritableSqlite, sqliteAvailable, sqliteFlavor, type WritableSqlite } from './sqlite-adapter.js';
|
package/dist/utils/sqlite.js
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
import Database from 'better-sqlite3';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Read-only SQLite query helper used by agent stats readers.
|
|
3
|
+
*
|
|
4
|
+
* This file used to hard-depend on `better-sqlite3`, which required a native
|
|
5
|
+
* npm build and failed in low-privilege containers and corporate sandboxes.
|
|
6
|
+
* It now re-exports the runtime-aware adapter which picks `bun:sqlite` in the
|
|
7
|
+
* compiled standalone binary, `node:sqlite` when running under `npx`, and a
|
|
8
|
+
* structured "unavailable" path otherwise (returns empty string).
|
|
9
|
+
*
|
|
10
|
+
* The `querySqlite` name is preserved so existing call sites and mocks in
|
|
11
|
+
* tests keep working.
|
|
7
12
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
db = new Database(dbPath, { readonly: true, timeout: 5000 });
|
|
12
|
-
const rows = db.prepare(sql).all();
|
|
13
|
-
return rows.map(row => Object.values(row).join('|')).join('\n');
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return '';
|
|
17
|
-
}
|
|
18
|
-
finally {
|
|
19
|
-
try {
|
|
20
|
-
db?.close();
|
|
21
|
-
}
|
|
22
|
-
catch { /* ignore close errors */ }
|
|
23
|
-
}
|
|
24
|
-
}
|
|
13
|
+
export { queryReadonlySqlite as querySqlite } from './sqlite-adapter.js';
|
|
14
|
+
export { openWritableSqlite, sqliteAvailable, sqliteFlavor } from './sqlite-adapter.js';
|
package/dist/utils/zip.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
1
|
export declare function extractZip(zipData: ArrayBuffer, targetDir: string): void;
|
|
2
|
+
/**
|
|
3
|
+
* Walk `sourceDir` recursively and write a zip file at `outputPath`.
|
|
4
|
+
* Entry paths inside the zip are forward-slash relative paths from
|
|
5
|
+
* `sourceDir`, matching the cross-platform convention that zip tools use.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createZipFromDir(sourceDir: string, outputPath: string): void;
|
package/dist/utils/zip.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
import { unzipSync } from 'fflate';
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join, dirname, relative, sep } from 'path';
|
|
3
|
+
import { unzipSync, zipSync } from 'fflate';
|
|
4
4
|
export function extractZip(zipData, targetDir) {
|
|
5
5
|
const files = unzipSync(new Uint8Array(zipData));
|
|
6
6
|
for (const [path, data] of Object.entries(files)) {
|
|
@@ -15,3 +15,28 @@ export function extractZip(zipData, targetDir) {
|
|
|
15
15
|
writeFileSync(fullPath, data);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Walk `sourceDir` recursively and write a zip file at `outputPath`.
|
|
20
|
+
* Entry paths inside the zip are forward-slash relative paths from
|
|
21
|
+
* `sourceDir`, matching the cross-platform convention that zip tools use.
|
|
22
|
+
*/
|
|
23
|
+
export function createZipFromDir(sourceDir, outputPath) {
|
|
24
|
+
const files = {};
|
|
25
|
+
const walk = (dir) => {
|
|
26
|
+
for (const entry of readdirSync(dir)) {
|
|
27
|
+
const absPath = join(dir, entry);
|
|
28
|
+
const stats = statSync(absPath);
|
|
29
|
+
if (stats.isDirectory()) {
|
|
30
|
+
walk(absPath);
|
|
31
|
+
}
|
|
32
|
+
else if (stats.isFile()) {
|
|
33
|
+
const relPath = relative(sourceDir, absPath).split(sep).join('/');
|
|
34
|
+
files[relPath] = readFileSync(absPath);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
walk(sourceDir);
|
|
39
|
+
const zipped = zipSync(files);
|
|
40
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
41
|
+
writeFileSync(outputPath, zipped);
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runwork",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Runwork <info@runwork.ai> (https://www.runwork.ai)",
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@clack/prompts": "^1.1.0",
|
|
46
|
-
"better-sqlite3": "^12.8.0",
|
|
47
46
|
"chokidar": "^4.0.0",
|
|
48
47
|
"commander": "^13.0.0",
|
|
49
48
|
"fflate": "^0.8.2",
|
|
@@ -52,13 +51,12 @@
|
|
|
52
51
|
"smol-toml": "^1.6.1"
|
|
53
52
|
},
|
|
54
53
|
"devDependencies": {
|
|
55
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
56
54
|
"@types/node": "^22.0.0",
|
|
57
55
|
"typescript": "^5.7.0",
|
|
58
56
|
"vitest": "^3.2.0"
|
|
59
57
|
},
|
|
60
58
|
"engines": {
|
|
61
|
-
"node": ">=
|
|
59
|
+
"node": ">=22.5.0"
|
|
62
60
|
},
|
|
63
61
|
"files": [
|
|
64
62
|
"dist/**/*.js",
|