runwork 0.6.1 → 0.8.2

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 (80) hide show
  1. package/dist/__tests__/install-scripts.test.d.ts +1 -0
  2. package/dist/__tests__/install-scripts.test.js +247 -0
  3. package/dist/agents/__tests__/claude-code-plugin.test.d.ts +1 -0
  4. package/dist/agents/__tests__/claude-code-plugin.test.js +47 -0
  5. package/dist/agents/__tests__/claude-code-stats.test.js +143 -78
  6. package/dist/agents/__tests__/claude-desktop-plugin-tree.test.d.ts +1 -0
  7. package/dist/agents/__tests__/claude-desktop-plugin-tree.test.js +161 -0
  8. package/dist/agents/__tests__/claude-desktop-plugin.test.d.ts +1 -0
  9. package/dist/agents/__tests__/claude-desktop-plugin.test.js +117 -0
  10. package/dist/agents/__tests__/claude-desktop-rpm.test.d.ts +1 -0
  11. package/dist/agents/__tests__/claude-desktop-rpm.test.js +126 -0
  12. package/dist/agents/__tests__/codex-instructions.test.d.ts +1 -0
  13. package/dist/agents/__tests__/codex-instructions.test.js +57 -0
  14. package/dist/agents/__tests__/codex-stats.test.js +9 -0
  15. package/dist/agents/__tests__/graceful-degradation.test.js +3 -3
  16. package/dist/agents/__tests__/intro-skill.test.js +11 -2
  17. package/dist/agents/__tests__/skill-slug.test.d.ts +1 -0
  18. package/dist/agents/__tests__/skill-slug.test.js +77 -0
  19. package/dist/agents/claude-code.d.ts +3 -1
  20. package/dist/agents/claude-code.js +233 -58
  21. package/dist/agents/claude-desktop-plugin-tree.d.ts +44 -0
  22. package/dist/agents/claude-desktop-plugin-tree.js +92 -0
  23. package/dist/agents/claude-desktop.d.ts +20 -1
  24. package/dist/agents/claude-desktop.js +238 -61
  25. package/dist/agents/cline.d.ts +3 -1
  26. package/dist/agents/cline.js +44 -2
  27. package/dist/agents/codex.d.ts +3 -1
  28. package/dist/agents/codex.js +85 -14
  29. package/dist/agents/cursor.d.ts +12 -1
  30. package/dist/agents/cursor.js +98 -25
  31. package/dist/agents/gemini.d.ts +3 -1
  32. package/dist/agents/gemini.js +42 -4
  33. package/dist/agents/generic-adapter.d.ts +3 -1
  34. package/dist/agents/generic-adapter.js +59 -4
  35. package/dist/agents/intro-skill.js +77 -31
  36. package/dist/agents/types.d.ts +42 -2
  37. package/dist/agents/types.js +61 -6
  38. package/dist/agents/utils/instruction-hint.d.ts +10 -0
  39. package/dist/agents/utils/instruction-hint.js +46 -0
  40. package/dist/agents/utils/json-config.d.ts +6 -1
  41. package/dist/agents/utils/json-config.js +31 -4
  42. package/dist/agents/windsurf.d.ts +3 -1
  43. package/dist/agents/windsurf.js +37 -2
  44. package/dist/commands/__tests__/sync-telemetry.test.d.ts +1 -0
  45. package/dist/commands/__tests__/sync-telemetry.test.js +281 -0
  46. package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
  47. package/dist/commands/__tests__/upgrade.test.js +174 -0
  48. package/dist/commands/build-plugin.d.ts +2 -0
  49. package/dist/commands/build-plugin.js +135 -0
  50. package/dist/commands/init.d.ts +8 -2
  51. package/dist/commands/init.js +19 -7
  52. package/dist/commands/sync-telemetry.d.ts +33 -0
  53. package/dist/commands/sync-telemetry.js +186 -0
  54. package/dist/commands/sync.d.ts +1 -0
  55. package/dist/commands/sync.js +84 -118
  56. package/dist/commands/uninstall.d.ts +2 -0
  57. package/dist/commands/uninstall.js +145 -0
  58. package/dist/commands/upgrade.d.ts +12 -0
  59. package/dist/commands/upgrade.js +67 -16
  60. package/dist/generated/bundled-types.js +36 -36
  61. package/dist/generated/version.d.ts +1 -1
  62. package/dist/generated/version.js +1 -1
  63. package/dist/health/__tests__/cli-distribution-checks.test.d.ts +1 -0
  64. package/dist/health/__tests__/cli-distribution-checks.test.js +199 -0
  65. package/dist/health/checks.d.ts +2 -0
  66. package/dist/health/checks.js +131 -18
  67. package/dist/health/runner.js +6 -2
  68. package/dist/index.js +4 -0
  69. package/dist/sync/executor.d.ts +2 -0
  70. package/dist/sync/executor.js +8 -4
  71. package/dist/types.d.ts +2 -0
  72. package/dist/ui/banner.js +15 -6
  73. package/dist/utils/__tests__/sqlite.test.js +84 -72
  74. package/dist/utils/sqlite-adapter.d.ts +48 -0
  75. package/dist/utils/sqlite-adapter.js +147 -0
  76. package/dist/utils/sqlite.d.ts +12 -5
  77. package/dist/utils/sqlite.js +12 -22
  78. package/dist/utils/zip.d.ts +6 -0
  79. package/dist/utils/zip.js +28 -3
  80. package/package.json +2 -4
@@ -1,77 +1,89 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import path from 'path';
3
- // Mock better-sqlite3 for controlled tests
4
- vi.mock('better-sqlite3', () => {
5
- const MockDatabase = vi.fn();
6
- return { default: MockDatabase };
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
- import Database from 'better-sqlite3';
9
- import { querySqlite } from '../sqlite.js';
10
- const FIXTURE_DB = path.resolve(__dirname, '../../agents/__tests__/fixtures/codex/state.sqlite');
11
- beforeEach(() => {
12
- vi.restoreAllMocks();
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('querySqlite', () => {
15
- // ── Tests using the real fixture database ──────────────────────────
16
- describe('with real fixture database', () => {
17
- beforeEach(() => {
18
- // Restore the real better-sqlite3 implementation for fixture tests
19
- vi.mocked(Database).mockImplementation((...args) => {
20
- const RealDatabase = vi.importActual('better-sqlite3');
21
- // importActual returns a promise, but we need synchronous access;
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
- // ── Tests using mocked database ────────────────────────────────────
43
- describe('error handling', () => {
44
- it('returns empty string when database does not exist', () => {
45
- vi.mocked(Database).mockImplementation(() => {
46
- throw new Error('SQLITE_CANTOPEN: unable to open database file');
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
+ }
@@ -1,7 +1,14 @@
1
1
  /**
2
- * Query a SQLite database using better-sqlite3.
3
- * Cross-platform, no CLI dependency.
4
- * Opens database in readonly mode with a 5-second busy timeout.
5
- * Returns empty string on any error (missing DB, locked, corrupted).
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 declare function querySqlite(dbPath: string, sql: string): string;
13
+ export { queryReadonlySqlite as querySqlite } from './sqlite-adapter.js';
14
+ export { openWritableSqlite, sqliteAvailable, sqliteFlavor, type WritableSqlite } from './sqlite-adapter.js';
@@ -1,24 +1,14 @@
1
- import Database from 'better-sqlite3';
2
1
  /**
3
- * Query a SQLite database using better-sqlite3.
4
- * Cross-platform, no CLI dependency.
5
- * Opens database in readonly mode with a 5-second busy timeout.
6
- * Returns empty string on any error (missing DB, locked, corrupted).
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 function querySqlite(dbPath, sql) {
9
- let db = null;
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';
@@ -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.6.1",
3
+ "version": "0.8.2",
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": ">=18"
59
+ "node": ">=22.5.0"
62
60
  },
63
61
  "files": [
64
62
  "dist/**/*.js",