@data-fair/lib-dev 0.0.0-stage → 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.
Files changed (81) hide show
  1. package/README.md +38 -2
  2. package/cli.d.ts +2 -0
  3. package/cli.js +64 -0
  4. package/cli.js.map +1 -0
  5. package/cli.test.d.ts +1 -0
  6. package/cli.test.js +44 -0
  7. package/cli.test.js.map +1 -0
  8. package/compose.d.ts +24 -0
  9. package/compose.js +61 -0
  10. package/compose.js.map +1 -0
  11. package/compose.test.d.ts +1 -0
  12. package/compose.test.js +69 -0
  13. package/compose.test.js.map +1 -0
  14. package/config.d.ts +14 -0
  15. package/config.js +32 -0
  16. package/config.js.map +1 -0
  17. package/config.test.d.ts +1 -0
  18. package/config.test.js +44 -0
  19. package/config.test.js.map +1 -0
  20. package/env.d.ts +18 -0
  21. package/env.js +94 -0
  22. package/env.js.map +1 -0
  23. package/env.test.d.ts +1 -0
  24. package/env.test.js +106 -0
  25. package/env.test.js.map +1 -0
  26. package/exec.d.ts +5 -0
  27. package/exec.js +11 -0
  28. package/exec.js.map +1 -0
  29. package/exec.test.d.ts +1 -0
  30. package/exec.test.js +10 -0
  31. package/exec.test.js.map +1 -0
  32. package/launch.d.ts +13 -0
  33. package/launch.js +36 -0
  34. package/launch.js.map +1 -0
  35. package/launch.test.d.ts +1 -0
  36. package/launch.test.js +19 -0
  37. package/launch.test.js.map +1 -0
  38. package/mem-cap.sh +33 -0
  39. package/mem-cap.test.d.ts +1 -0
  40. package/mem-cap.test.js +38 -0
  41. package/mem-cap.test.js.map +1 -0
  42. package/optional.d.ts +8 -0
  43. package/optional.js +21 -0
  44. package/optional.js.map +1 -0
  45. package/optional.test.d.ts +1 -0
  46. package/optional.test.js +24 -0
  47. package/optional.test.js.map +1 -0
  48. package/package.json +29 -4
  49. package/panes.d.ts +18 -0
  50. package/panes.js +68 -0
  51. package/panes.js.map +1 -0
  52. package/panes.test.d.ts +1 -0
  53. package/panes.test.js +97 -0
  54. package/panes.test.js.map +1 -0
  55. package/setup.d.ts +6 -0
  56. package/setup.js +21 -0
  57. package/setup.js.map +1 -0
  58. package/setup.test.d.ts +1 -0
  59. package/setup.test.js +47 -0
  60. package/setup.test.js.map +1 -0
  61. package/status-pane.sh +111 -0
  62. package/status-pane.test.d.ts +1 -0
  63. package/status-pane.test.js +71 -0
  64. package/status-pane.test.js.map +1 -0
  65. package/status.d.ts +51 -0
  66. package/status.js +147 -0
  67. package/status.js.map +1 -0
  68. package/status.test.d.ts +1 -0
  69. package/status.test.js +123 -0
  70. package/status.test.js.map +1 -0
  71. package/worktree-delete.sh +123 -0
  72. package/worktree.sh +164 -0
  73. package/worktree.test.d.ts +1 -0
  74. package/worktree.test.js +91 -0
  75. package/worktree.test.js.map +1 -0
  76. package/zellij.d.ts +19 -0
  77. package/zellij.js +50 -0
  78. package/zellij.js.map +1 -0
  79. package/zellij.test.d.ts +1 -0
  80. package/zellij.test.js +50 -0
  81. package/zellij.test.js.map +1 -0
@@ -0,0 +1,91 @@
1
+ import { describe, it } from 'node:test';
2
+ import { strict as assert } from 'assert';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, realpathSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import path from 'node:path';
7
+ const script = (name) => path.join(import.meta.dirname, name);
8
+ // git and the scripts must only see the throwaway repositories: a git hook (pre-push runs the tests)
9
+ // exports GIT_DIR and friends, which would point every command below at the real repository
10
+ const isolatedEnv = {
11
+ ...Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('GIT_'))),
12
+ GIT_AUTHOR_NAME: 't',
13
+ GIT_AUTHOR_EMAIL: 't@t',
14
+ GIT_COMMITTER_NAME: 't',
15
+ GIT_COMMITTER_EMAIL: 't@t',
16
+ GIT_CONFIG_GLOBAL: '/dev/null',
17
+ GIT_CONFIG_NOSYSTEM: '1',
18
+ GIT_CEILING_DIRECTORIES: tmpdir()
19
+ };
20
+ const run = (name, args, cwd = mkdtempSync(path.join(tmpdir(), 'wt-'))) => spawnSync('bash', [script(name), ...args], { cwd, encoding: 'utf8', env: isolatedEnv });
21
+ const git = (cwd, ...args) => {
22
+ const res = spawnSync('git', args, { cwd, encoding: 'utf8', env: isolatedEnv });
23
+ assert.equal(res.status, 0, `git ${args.join(' ')}: ${res.stderr}`);
24
+ return res.stdout.trim();
25
+ };
26
+ // a main checkout <tmp>/<repo> cloned from a bare origin, on master with one commit
27
+ const checkout = (repo = 'portals') => {
28
+ const tmp = mkdtempSync(path.join(tmpdir(), 'wt-repo-'));
29
+ git(tmp, 'init', '-q', '--bare', '-b', 'master', 'origin.git');
30
+ git(tmp, 'clone', '-q', path.join(tmp, 'origin.git'), repo);
31
+ const root = path.join(tmp, repo);
32
+ // never commit anywhere but in the throwaway clone
33
+ assert.equal(git(root, 'rev-parse', '--show-toplevel'), realpathSync(root));
34
+ writeFileSync(path.join(root, 'README.md'), 'x\n');
35
+ git(root, 'add', '.');
36
+ git(root, 'commit', '-q', '-m', 'init');
37
+ git(root, 'push', '-q', 'origin', 'master');
38
+ return { tmp, root };
39
+ };
40
+ describe('worktree scripts', () => {
41
+ for (const name of ['worktree.sh', 'worktree-delete.sh']) {
42
+ it(`${name} rejects a branch name with a slash`, () => {
43
+ const res = run(name, ['feat/x']);
44
+ assert.equal(res.status, 1);
45
+ assert.match(res.stderr, /invalid branch name "feat\/x".*no "\/"/);
46
+ });
47
+ it(`${name} rejects a branch name longer than 60 characters`, () => {
48
+ const res = run(name, ['a'.repeat(61)]);
49
+ assert.equal(res.status, 1);
50
+ assert.match(res.stderr, /invalid branch name/);
51
+ });
52
+ }
53
+ it('worktree.sh refuses a branch that makes the zellij session name too long for this repository', () => {
54
+ const { root } = checkout('simple-directory');
55
+ const res = run('worktree.sh', ['a'.repeat(50)], root);
56
+ assert.equal(res.status, 1);
57
+ assert.match(res.stderr, /zellij session .* must fit in 60 characters, so at most 43 characters/);
58
+ });
59
+ for (const name of ['worktree.sh', 'worktree-delete.sh']) {
60
+ it(`${name} refuses to run from a subfolder of the repository`, () => {
61
+ const { root } = checkout();
62
+ mkdirSync(path.join(root, 'api'));
63
+ const res = run(name, ['feat-x'], path.join(root, 'api'));
64
+ assert.equal(res.status, 1);
65
+ assert.match(res.stderr, /from the root of the repository/);
66
+ });
67
+ it(`${name} refuses to run from a linked worktree`, () => {
68
+ const { tmp, root } = checkout();
69
+ git(root, 'worktree', 'add', '-q', '-b', 'feat-a', path.join(tmp, 'portals_feat-a'));
70
+ const res = run(name, ['feat-x'], path.join(tmp, 'portals_feat-a'));
71
+ assert.equal(res.status, 1);
72
+ assert.match(res.stderr, /from the main checkout, not from a worktree/);
73
+ });
74
+ }
75
+ it('worktree.sh puts the local changes back when the setup fails after stashing them', () => {
76
+ const { tmp, root } = checkout();
77
+ git(root, 'checkout', '-q', '-b', 'feat-x');
78
+ writeFileSync(path.join(root, 'README.md'), 'work in progress\n');
79
+ writeFileSync(path.join(root, 'notes.txt'), 'untracked\n');
80
+ // master is checked out by another worktree: switching the main checkout back to it fails
81
+ git(root, 'worktree', 'add', '-q', path.join(tmp, 'portals_other'), 'master');
82
+ const res = run('worktree.sh', ['feat-x'], root);
83
+ assert.notEqual(res.status, 0);
84
+ assert.match(res.stderr, /your local changes are back in/);
85
+ assert.equal(git(root, 'branch', '--show-current'), 'feat-x');
86
+ assert.equal(readFileSync(path.join(root, 'README.md'), 'utf8'), 'work in progress\n');
87
+ assert.equal(readFileSync(path.join(root, 'notes.txt'), 'utf8'), 'untracked\n');
88
+ assert.equal(git(root, 'stash', 'list'), '');
89
+ });
90
+ });
91
+ //# sourceMappingURL=worktree.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree.test.js","sourceRoot":"","sources":["worktree.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from 'node:test'\nimport { strict as assert } from 'assert'\nimport { spawnSync } from 'node:child_process'\nimport { mkdtempSync, mkdirSync, writeFileSync, readFileSync, realpathSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport path from 'node:path'\n\nconst script = (name: string) => path.join(import.meta.dirname, name)\n// git and the scripts must only see the throwaway repositories: a git hook (pre-push runs the tests)\n// exports GIT_DIR and friends, which would point every command below at the real repository\nconst isolatedEnv = {\n ...Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('GIT_'))),\n GIT_AUTHOR_NAME: 't',\n GIT_AUTHOR_EMAIL: 't@t',\n GIT_COMMITTER_NAME: 't',\n GIT_COMMITTER_EMAIL: 't@t',\n GIT_CONFIG_GLOBAL: '/dev/null',\n GIT_CONFIG_NOSYSTEM: '1',\n GIT_CEILING_DIRECTORIES: tmpdir()\n}\nconst run = (name: string, args: string[], cwd = mkdtempSync(path.join(tmpdir(), 'wt-'))) => spawnSync('bash', [script(name), ...args], { cwd, encoding: 'utf8', env: isolatedEnv })\n\nconst git = (cwd: string, ...args: string[]) => {\n const res = spawnSync('git', args, { cwd, encoding: 'utf8', env: isolatedEnv })\n assert.equal(res.status, 0, `git ${args.join(' ')}: ${res.stderr}`)\n return res.stdout.trim()\n}\n// a main checkout <tmp>/<repo> cloned from a bare origin, on master with one commit\nconst checkout = (repo = 'portals') => {\n const tmp = mkdtempSync(path.join(tmpdir(), 'wt-repo-'))\n git(tmp, 'init', '-q', '--bare', '-b', 'master', 'origin.git')\n git(tmp, 'clone', '-q', path.join(tmp, 'origin.git'), repo)\n const root = path.join(tmp, repo)\n // never commit anywhere but in the throwaway clone\n assert.equal(git(root, 'rev-parse', '--show-toplevel'), realpathSync(root))\n writeFileSync(path.join(root, 'README.md'), 'x\\n')\n git(root, 'add', '.')\n git(root, 'commit', '-q', '-m', 'init')\n git(root, 'push', '-q', 'origin', 'master')\n return { tmp, root }\n}\n\ndescribe('worktree scripts', () => {\n for (const name of ['worktree.sh', 'worktree-delete.sh']) {\n it(`${name} rejects a branch name with a slash`, () => {\n const res = run(name, ['feat/x'])\n assert.equal(res.status, 1)\n assert.match(res.stderr, /invalid branch name \"feat\\/x\".*no \"\\/\"/)\n })\n\n it(`${name} rejects a branch name longer than 60 characters`, () => {\n const res = run(name, ['a'.repeat(61)])\n assert.equal(res.status, 1)\n assert.match(res.stderr, /invalid branch name/)\n })\n }\n\n it('worktree.sh refuses a branch that makes the zellij session name too long for this repository', () => {\n const { root } = checkout('simple-directory')\n const res = run('worktree.sh', ['a'.repeat(50)], root)\n assert.equal(res.status, 1)\n assert.match(res.stderr, /zellij session .* must fit in 60 characters, so at most 43 characters/)\n })\n\n for (const name of ['worktree.sh', 'worktree-delete.sh']) {\n it(`${name} refuses to run from a subfolder of the repository`, () => {\n const { root } = checkout()\n mkdirSync(path.join(root, 'api'))\n const res = run(name, ['feat-x'], path.join(root, 'api'))\n assert.equal(res.status, 1)\n assert.match(res.stderr, /from the root of the repository/)\n })\n\n it(`${name} refuses to run from a linked worktree`, () => {\n const { tmp, root } = checkout()\n git(root, 'worktree', 'add', '-q', '-b', 'feat-a', path.join(tmp, 'portals_feat-a'))\n const res = run(name, ['feat-x'], path.join(tmp, 'portals_feat-a'))\n assert.equal(res.status, 1)\n assert.match(res.stderr, /from the main checkout, not from a worktree/)\n })\n }\n\n it('worktree.sh puts the local changes back when the setup fails after stashing them', () => {\n const { tmp, root } = checkout()\n git(root, 'checkout', '-q', '-b', 'feat-x')\n writeFileSync(path.join(root, 'README.md'), 'work in progress\\n')\n writeFileSync(path.join(root, 'notes.txt'), 'untracked\\n')\n // master is checked out by another worktree: switching the main checkout back to it fails\n git(root, 'worktree', 'add', '-q', path.join(tmp, 'portals_other'), 'master')\n const res = run('worktree.sh', ['feat-x'], root)\n assert.notEqual(res.status, 0)\n assert.match(res.stderr, /your local changes are back in/)\n assert.equal(git(root, 'branch', '--show-current'), 'feat-x')\n assert.equal(readFileSync(path.join(root, 'README.md'), 'utf8'), 'work in progress\\n')\n assert.equal(readFileSync(path.join(root, 'notes.txt'), 'utf8'), 'untracked\\n')\n assert.equal(git(root, 'stash', 'list'), '')\n })\n})\n"]}
package/zellij.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { Exec } from './exec.js';
2
+ export type SessionState = 'running' | 'exited' | 'absent';
3
+ export type Pane = {
4
+ id: number;
5
+ title: string;
6
+ exited: boolean;
7
+ exit_status: number | null;
8
+ is_plugin: boolean;
9
+ terminal_command: string | null;
10
+ };
11
+ export declare const sessionName: (root: string) => string;
12
+ export declare const parseSessions: (out: string) => Record<string, "running" | "exited">;
13
+ export declare const sessionState: (exec: Exec, name: string) => SessionState;
14
+ export declare const checkVersion: (exec: Exec) => void;
15
+ export declare const requireSession: (exec: Exec, root: string) => string;
16
+ export declare const listPanes: (exec: Exec, session: string) => Pane[];
17
+ export declare const findPane: (exec: Exec, session: string, title: string) => Pane;
18
+ export declare const dumpScreen: (exec: Exec, session: string, pane: Pane) => string;
19
+ export declare const sendKeys: (exec: Exec, session: string, pane: Pane, keys: string) => void;
package/zellij.js ADDED
@@ -0,0 +1,50 @@
1
+ import path from 'node:path';
2
+ export const sessionName = (root) => path.basename(root);
3
+ export const parseSessions = (out) => {
4
+ const sessions = {};
5
+ for (const line of out.split('\n')) {
6
+ const name = line.trim().split(/\s+/)[0];
7
+ if (name)
8
+ sessions[name] = line.includes('EXITED') ? 'exited' : 'running';
9
+ }
10
+ return sessions;
11
+ };
12
+ export const sessionState = (exec, name) => {
13
+ let out;
14
+ try {
15
+ out = exec('zellij', ['list-sessions', '--no-formatting']);
16
+ }
17
+ catch {
18
+ return 'absent'; // zellij exits 1 when there is no session at all
19
+ }
20
+ return parseSessions(out)[name] ?? 'absent';
21
+ };
22
+ // list-panes --json, dump-screen -p and send-keys -p appeared in zellij 0.44
23
+ export const checkVersion = (exec) => {
24
+ const version = exec('zellij', ['--version']).trim().split(' ')[1] ?? '0.0.0';
25
+ const [major, minor] = version.split('.').map(Number);
26
+ if (major === 0 && minor < 44)
27
+ throw new Error(`zellij ${version} is too old, df-dev needs zellij >= 0.44`);
28
+ };
29
+ export const requireSession = (exec, root) => {
30
+ checkVersion(exec);
31
+ const name = sessionName(root);
32
+ const state = sessionState(exec, name);
33
+ if (state !== 'running')
34
+ throw new Error(`zellij session "${name}" is not running (${state}): ask the user to start it with "npm run dev-zellij"`);
35
+ return name;
36
+ };
37
+ export const listPanes = (exec, session) => JSON.parse(exec('zellij', ['--session', session, 'action', 'list-panes', '--json'])).filter(p => !p.is_plugin);
38
+ export const findPane = (exec, session, title) => {
39
+ const all = listPanes(exec, session);
40
+ const pane = all.find(p => p.title === title);
41
+ if (!pane)
42
+ throw new Error(`no pane "${title}" in session ${session}, available panes: ${all.map(p => p.title).join(', ')}`);
43
+ return pane;
44
+ };
45
+ const paneRef = (pane) => `terminal_${pane.id}`;
46
+ export const dumpScreen = (exec, session, pane) => exec('zellij', ['--session', session, 'action', 'dump-screen', '-p', paneRef(pane), '--full']);
47
+ export const sendKeys = (exec, session, pane, keys) => {
48
+ exec('zellij', ['--session', session, 'action', 'send-keys', '-p', paneRef(pane), keys]);
49
+ };
50
+ //# sourceMappingURL=zellij.js.map
package/zellij.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zellij.js","sourceRoot":"","sources":["zellij.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path'\nimport type { Exec } from './exec.js'\n\nexport type SessionState = 'running' | 'exited' | 'absent'\nexport type Pane = { id: number, title: string, exited: boolean, exit_status: number | null, is_plugin: boolean, terminal_command: string | null }\n\nexport const sessionName = (root: string) => path.basename(root)\n\nexport const parseSessions = (out: string) => {\n const sessions: Record<string, 'running' | 'exited'> = {}\n for (const line of out.split('\\n')) {\n const name = line.trim().split(/\\s+/)[0]\n if (name) sessions[name] = line.includes('EXITED') ? 'exited' : 'running'\n }\n return sessions\n}\n\nexport const sessionState = (exec: Exec, name: string): SessionState => {\n let out: string\n try {\n out = exec('zellij', ['list-sessions', '--no-formatting'])\n } catch {\n return 'absent' // zellij exits 1 when there is no session at all\n }\n return parseSessions(out)[name] ?? 'absent'\n}\n\n// list-panes --json, dump-screen -p and send-keys -p appeared in zellij 0.44\nexport const checkVersion = (exec: Exec) => {\n const version = exec('zellij', ['--version']).trim().split(' ')[1] ?? '0.0.0'\n const [major, minor] = version.split('.').map(Number)\n if (major === 0 && minor < 44) throw new Error(`zellij ${version} is too old, df-dev needs zellij >= 0.44`)\n}\n\nexport const requireSession = (exec: Exec, root: string) => {\n checkVersion(exec)\n const name = sessionName(root)\n const state = sessionState(exec, name)\n if (state !== 'running') throw new Error(`zellij session \"${name}\" is not running (${state}): ask the user to start it with \"npm run dev-zellij\"`)\n return name\n}\n\nexport const listPanes = (exec: Exec, session: string): Pane[] =>\n (JSON.parse(exec('zellij', ['--session', session, 'action', 'list-panes', '--json'])) as Pane[]).filter(p => !p.is_plugin)\n\nexport const findPane = (exec: Exec, session: string, title: string) => {\n const all = listPanes(exec, session)\n const pane = all.find(p => p.title === title)\n if (!pane) throw new Error(`no pane \"${title}\" in session ${session}, available panes: ${all.map(p => p.title).join(', ')}`)\n return pane\n}\n\nconst paneRef = (pane: Pane) => `terminal_${pane.id}`\n\nexport const dumpScreen = (exec: Exec, session: string, pane: Pane) =>\n exec('zellij', ['--session', session, 'action', 'dump-screen', '-p', paneRef(pane), '--full'])\n\nexport const sendKeys = (exec: Exec, session: string, pane: Pane, keys: string) => {\n exec('zellij', ['--session', session, 'action', 'send-keys', '-p', paneRef(pane), keys])\n}\n"]}
@@ -0,0 +1 @@
1
+ export {};
package/zellij.test.js ADDED
@@ -0,0 +1,50 @@
1
+ import { describe, it } from 'node:test';
2
+ import { strict as assert } from 'assert';
3
+ import { parseSessions, sessionState, requireSession, findPane, checkVersion, sessionName } from './zellij.js';
4
+ const panes = [
5
+ { id: 0, title: 'status', exited: false, exit_status: null, is_plugin: false, terminal_command: 'npx --no df-dev status --watch' },
6
+ { id: 1, title: 'api', exited: false, exit_status: null, is_plugin: false, terminal_command: 'bash -c ...' },
7
+ { id: 0, title: 'tab-bar', exited: false, exit_status: null, is_plugin: true, terminal_command: null }
8
+ ];
9
+ const fakeExec = (outputs) => (cmd, args) => {
10
+ const key = [cmd, ...args].join(' ');
11
+ const match = Object.keys(outputs).find(k => key.includes(k));
12
+ if (!match)
13
+ throw new Error(`unexpected call: ${key}`);
14
+ const out = outputs[match];
15
+ if (out instanceof Error)
16
+ throw out;
17
+ return out;
18
+ };
19
+ describe('zellij sessions', () => {
20
+ it('names the session after the worktree dir', () => {
21
+ assert.equal(sessionName('/home/u/Documents/portals_feat-x'), 'portals_feat-x');
22
+ });
23
+ it('parses running and exited sessions', () => {
24
+ const out = 'portals [Created 2m ago] (current)\nportals_feat-x [Created 1h ago] (EXITED - attach to resurrect)\n';
25
+ assert.deepEqual(parseSessions(out), { portals: 'running', 'portals_feat-x': 'exited' });
26
+ });
27
+ it('reports absent when zellij has no session at all', () => {
28
+ const exec = fakeExec({ 'list-sessions': new Error('No active zellij sessions found.') });
29
+ assert.equal(sessionState(exec, 'portals'), 'absent');
30
+ });
31
+ it('tells the agent to ask the user when the session is not running', () => {
32
+ const exec = fakeExec({ '--version': 'zellij 0.45.1', 'list-sessions': 'portals [Created 1h ago] (EXITED - attach to resurrect)\n' });
33
+ assert.throws(() => requireSession(exec, '/x/portals'), /not running.*npm run dev-zellij/);
34
+ });
35
+ it('refuses zellij older than 0.44', () => {
36
+ assert.throws(() => checkVersion(fakeExec({ '--version': 'zellij 0.40.1' })), /0\.44/);
37
+ checkVersion(fakeExec({ '--version': 'zellij 0.45.1' }));
38
+ });
39
+ });
40
+ describe('zellij panes', () => {
41
+ it('finds a terminal pane by title', () => {
42
+ const exec = fakeExec({ 'list-panes': JSON.stringify(panes) });
43
+ assert.equal(findPane(exec, 'portals', 'api').id, 1);
44
+ });
45
+ it('lists the available titles when a pane is not found', () => {
46
+ const exec = fakeExec({ 'list-panes': JSON.stringify(panes) });
47
+ assert.throws(() => findPane(exec, 'portals', 'worker'), /no pane "worker".*status, api/);
48
+ });
49
+ });
50
+ //# sourceMappingURL=zellij.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zellij.test.js","sourceRoot":"","sources":["zellij.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from 'node:test'\nimport { strict as assert } from 'assert'\nimport { parseSessions, sessionState, requireSession, findPane, checkVersion, sessionName } from './zellij.js'\nimport type { Exec } from './exec.js'\n\nconst panes = [\n { id: 0, title: 'status', exited: false, exit_status: null, is_plugin: false, terminal_command: 'npx --no df-dev status --watch' },\n { id: 1, title: 'api', exited: false, exit_status: null, is_plugin: false, terminal_command: 'bash -c ...' },\n { id: 0, title: 'tab-bar', exited: false, exit_status: null, is_plugin: true, terminal_command: null }\n]\n\nconst fakeExec = (outputs: Record<string, string | Error>): Exec => (cmd, args) => {\n const key = [cmd, ...args].join(' ')\n const match = Object.keys(outputs).find(k => key.includes(k))\n if (!match) throw new Error(`unexpected call: ${key}`)\n const out = outputs[match]\n if (out instanceof Error) throw out\n return out\n}\n\ndescribe('zellij sessions', () => {\n it('names the session after the worktree dir', () => {\n assert.equal(sessionName('/home/u/Documents/portals_feat-x'), 'portals_feat-x')\n })\n\n it('parses running and exited sessions', () => {\n const out = 'portals [Created 2m ago] (current)\\nportals_feat-x [Created 1h ago] (EXITED - attach to resurrect)\\n'\n assert.deepEqual(parseSessions(out), { portals: 'running', 'portals_feat-x': 'exited' })\n })\n\n it('reports absent when zellij has no session at all', () => {\n const exec = fakeExec({ 'list-sessions': new Error('No active zellij sessions found.') })\n assert.equal(sessionState(exec, 'portals'), 'absent')\n })\n\n it('tells the agent to ask the user when the session is not running', () => {\n const exec = fakeExec({ '--version': 'zellij 0.45.1', 'list-sessions': 'portals [Created 1h ago] (EXITED - attach to resurrect)\\n' })\n assert.throws(() => requireSession(exec, '/x/portals'), /not running.*npm run dev-zellij/)\n })\n\n it('refuses zellij older than 0.44', () => {\n assert.throws(() => checkVersion(fakeExec({ '--version': 'zellij 0.40.1' })), /0\\.44/)\n checkVersion(fakeExec({ '--version': 'zellij 0.45.1' }))\n })\n})\n\ndescribe('zellij panes', () => {\n it('finds a terminal pane by title', () => {\n const exec = fakeExec({ 'list-panes': JSON.stringify(panes) })\n assert.equal(findPane(exec, 'portals', 'api').id, 1)\n })\n\n it('lists the available titles when a pane is not found', () => {\n const exec = fakeExec({ 'list-panes': JSON.stringify(panes) })\n assert.throws(() => findPane(exec, 'portals', 'worker'), /no pane \"worker\".*status, api/)\n })\n})\n"]}