dev-prism 0.3.0 → 0.4.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.
@@ -0,0 +1,95 @@
1
+ // src/lib/store.ts
2
+ import Database from "better-sqlite3";
3
+ import { existsSync, mkdirSync } from "fs";
4
+ import { dirname, join } from "path";
5
+ import { homedir } from "os";
6
+ var SCHEMA = `
7
+ CREATE TABLE IF NOT EXISTS sessions (
8
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
9
+ session_id TEXT NOT NULL,
10
+ project_root TEXT NOT NULL,
11
+ session_dir TEXT NOT NULL,
12
+ branch TEXT NOT NULL DEFAULT '',
13
+ mode TEXT NOT NULL DEFAULT 'docker',
14
+ in_place INTEGER NOT NULL DEFAULT 0,
15
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
16
+ destroyed_at TEXT,
17
+ UNIQUE(session_id, project_root)
18
+ );
19
+ `;
20
+ function defaultDbPath() {
21
+ return join(homedir(), ".dev-prism", "sessions.db");
22
+ }
23
+ var SessionStore = class {
24
+ db;
25
+ constructor(dbPath) {
26
+ const path = dbPath ?? defaultDbPath();
27
+ if (path !== ":memory:") {
28
+ const dir = dirname(path);
29
+ if (!existsSync(dir)) {
30
+ mkdirSync(dir, { recursive: true });
31
+ }
32
+ }
33
+ this.db = new Database(path);
34
+ this.db.pragma("journal_mode = WAL");
35
+ this.db.pragma("busy_timeout = 3000");
36
+ this.db.exec(SCHEMA);
37
+ }
38
+ insert(row) {
39
+ const stmt = this.db.prepare(`
40
+ INSERT INTO sessions (session_id, project_root, session_dir, branch, mode, in_place)
41
+ VALUES (@session_id, @project_root, @session_dir, @branch, @mode, @in_place)
42
+ `);
43
+ const info = stmt.run({
44
+ session_id: row.sessionId,
45
+ project_root: row.projectRoot,
46
+ session_dir: row.sessionDir,
47
+ branch: row.branch ?? "",
48
+ mode: row.mode ?? "docker",
49
+ in_place: row.inPlace ? 1 : 0
50
+ });
51
+ return this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(info.lastInsertRowid);
52
+ }
53
+ /**
54
+ * Atomically remove any old destroyed row and insert a new one.
55
+ * Uses a transaction so the UNIQUE constraint prevents concurrent creates
56
+ * from claiming the same session ID.
57
+ */
58
+ reserve(row) {
59
+ return this.db.transaction(() => {
60
+ this.remove(row.projectRoot, row.sessionId);
61
+ return this.insert(row);
62
+ })();
63
+ }
64
+ listByProject(projectRoot) {
65
+ return this.db.prepare("SELECT * FROM sessions WHERE project_root = ? AND destroyed_at IS NULL ORDER BY session_id").all(projectRoot);
66
+ }
67
+ listAll() {
68
+ return this.db.prepare("SELECT * FROM sessions WHERE destroyed_at IS NULL ORDER BY project_root, session_id").all();
69
+ }
70
+ findSession(projectRoot, sessionId) {
71
+ return this.db.prepare("SELECT * FROM sessions WHERE project_root = ? AND session_id = ? AND destroyed_at IS NULL").get(projectRoot, sessionId);
72
+ }
73
+ findByDir(sessionDir) {
74
+ return this.db.prepare("SELECT * FROM sessions WHERE session_dir = ? AND destroyed_at IS NULL").get(sessionDir);
75
+ }
76
+ getUsedSessionIds(projectRoot) {
77
+ const rows = this.db.prepare("SELECT session_id FROM sessions WHERE project_root = ? AND destroyed_at IS NULL").all(projectRoot);
78
+ return new Set(rows.map((r) => r.session_id));
79
+ }
80
+ markDestroyed(projectRoot, sessionId) {
81
+ const info = this.db.prepare("UPDATE sessions SET destroyed_at = datetime('now') WHERE project_root = ? AND session_id = ? AND destroyed_at IS NULL").run(projectRoot, sessionId);
82
+ return info.changes > 0;
83
+ }
84
+ remove(projectRoot, sessionId) {
85
+ const info = this.db.prepare("DELETE FROM sessions WHERE project_root = ? AND session_id = ?").run(projectRoot, sessionId);
86
+ return info.changes > 0;
87
+ }
88
+ close() {
89
+ this.db.close();
90
+ }
91
+ };
92
+
93
+ export {
94
+ SessionStore
95
+ };
@@ -0,0 +1,203 @@
1
+ import {
2
+ writeAppEnvFiles,
3
+ writeEnvFile
4
+ } from "./chunk-J36LRUXM.js";
5
+ import {
6
+ calculatePorts,
7
+ formatPortsTable
8
+ } from "./chunk-3ATDGV4Y.js";
9
+ import {
10
+ createWorktree,
11
+ findNextSessionId,
12
+ generateDefaultBranchName,
13
+ removeWorktree
14
+ } from "./chunk-Y3GR6XK7.js";
15
+ import {
16
+ down,
17
+ logs,
18
+ up
19
+ } from "./chunk-GBN67HYD.js";
20
+ import {
21
+ getSessionDir,
22
+ getSessionsDir,
23
+ loadConfig
24
+ } from "./chunk-25WQHUYW.js";
25
+ import {
26
+ SessionStore
27
+ } from "./chunk-H4HPDIY3.js";
28
+
29
+ // src/commands/create.ts
30
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync } from "fs";
31
+ import { basename, join } from "path";
32
+ import chalk from "chalk";
33
+ import { execa } from "execa";
34
+ function updateEnvDatabaseUrl(envPath, newDbUrl) {
35
+ if (!existsSync(envPath)) return;
36
+ let content = readFileSync(envPath, "utf-8");
37
+ if (content.includes("DATABASE_URL=")) {
38
+ content = content.replace(/^DATABASE_URL=.*/m, `DATABASE_URL=${newDbUrl}`);
39
+ } else {
40
+ content += `
41
+ DATABASE_URL=${newDbUrl}
42
+ `;
43
+ }
44
+ writeFileSync(envPath, content);
45
+ }
46
+ async function createSession(projectRoot, sessionId, options) {
47
+ const config = await loadConfig(projectRoot);
48
+ const sessionsDir = getSessionsDir(config, projectRoot);
49
+ const store = new SessionStore();
50
+ try {
51
+ if (!sessionId) {
52
+ sessionId = findNextSessionId(store.getUsedSessionIds(projectRoot));
53
+ console.log(chalk.gray(`Auto-assigned session ID: ${sessionId}`));
54
+ }
55
+ if (!/^\d{3}$/.test(sessionId)) {
56
+ console.error(chalk.red("Error: Session ID must be exactly 3 digits (001-999)"));
57
+ process.exit(1);
58
+ }
59
+ const inPlace = options.inPlace ?? false;
60
+ const branchName = options.branch || generateDefaultBranchName(sessionId);
61
+ const mode = options.mode || "docker";
62
+ console.log(chalk.blue(`Creating session ${sessionId} (${mode} mode${inPlace ? ", in-place" : ""})...`));
63
+ if (!inPlace) {
64
+ console.log(chalk.gray(`Branch: ${branchName}`));
65
+ }
66
+ const ports = calculatePorts(config, sessionId);
67
+ console.log(chalk.gray("\nPorts:"));
68
+ console.log(chalk.gray(formatPortsTable(ports)));
69
+ const sessionDir = inPlace ? projectRoot : getSessionDir(config, projectRoot, sessionId);
70
+ try {
71
+ store.reserve({
72
+ sessionId,
73
+ projectRoot,
74
+ sessionDir,
75
+ branch: inPlace ? "" : branchName,
76
+ mode,
77
+ inPlace
78
+ });
79
+ } catch {
80
+ console.error(chalk.red(`Error: Session ${sessionId} is already being created by another process.`));
81
+ process.exit(1);
82
+ }
83
+ let profiles;
84
+ try {
85
+ if (inPlace) {
86
+ console.log(chalk.blue("\nUsing current directory (in-place mode)..."));
87
+ console.log(chalk.green(` Directory: ${sessionDir}`));
88
+ } else {
89
+ if (!existsSync(sessionsDir)) {
90
+ mkdirSync(sessionsDir, { recursive: true });
91
+ }
92
+ console.log(chalk.blue("\nCreating git worktree..."));
93
+ await createWorktree(projectRoot, sessionDir, branchName);
94
+ console.log(chalk.green(` Created: ${sessionDir}`));
95
+ const sessionDbUrl = `postgresql://postgres:postgres@localhost:${ports.POSTGRES_PORT}/postgres`;
96
+ const envFilesToCopy = config.envFiles ?? [];
97
+ for (const envFile of envFilesToCopy) {
98
+ const srcPath = join(projectRoot, envFile);
99
+ const destPath = join(sessionDir, envFile);
100
+ if (existsSync(srcPath)) {
101
+ copyFileSync(srcPath, destPath);
102
+ updateEnvDatabaseUrl(destPath, sessionDbUrl);
103
+ console.log(chalk.green(` Copied: ${envFile} (updated DATABASE_URL)`));
104
+ }
105
+ }
106
+ }
107
+ console.log(chalk.blue("\nGenerating .env.session..."));
108
+ const projectName = config.projectName ?? basename(projectRoot);
109
+ const envPath = writeEnvFile(sessionDir, sessionId, ports, projectName);
110
+ console.log(chalk.green(` Written: ${envPath}`));
111
+ const appEnvFiles = writeAppEnvFiles(config, sessionDir, sessionId, ports);
112
+ for (const file of appEnvFiles) {
113
+ console.log(chalk.green(` Written: ${file}`));
114
+ }
115
+ console.log(chalk.blue("\nStarting Docker services..."));
116
+ if (mode === "docker") {
117
+ const allApps = config.apps ?? [];
118
+ const excludeApps = options.without ?? [];
119
+ profiles = allApps.filter((app) => !excludeApps.includes(app));
120
+ if (excludeApps.length > 0) {
121
+ console.log(chalk.gray(` Excluding apps: ${excludeApps.join(", ")}`));
122
+ }
123
+ }
124
+ await up({ cwd: sessionDir, profiles });
125
+ console.log(chalk.blue("Waiting for services to be ready..."));
126
+ await new Promise((resolve) => setTimeout(resolve, 3e3));
127
+ if (config.setup.length > 0) {
128
+ console.log(chalk.blue("\nRunning setup commands..."));
129
+ const setupEnv = {
130
+ ...process.env,
131
+ SESSION_ID: sessionId,
132
+ // Add DATABASE_URL for db commands
133
+ DATABASE_URL: `postgresql://postgres:postgres@localhost:${ports.POSTGRES_PORT}/postgres`
134
+ };
135
+ for (const [name, port] of Object.entries(ports)) {
136
+ setupEnv[name] = String(port);
137
+ }
138
+ for (const cmd of config.setup) {
139
+ console.log(chalk.gray(` Running: ${cmd}`));
140
+ const [command, ...args] = cmd.split(" ");
141
+ try {
142
+ await execa(command, args, {
143
+ cwd: sessionDir,
144
+ stdio: "inherit",
145
+ env: setupEnv
146
+ });
147
+ } catch {
148
+ console.warn(chalk.yellow(` Warning: Command failed: ${cmd}`));
149
+ }
150
+ }
151
+ }
152
+ } catch (error) {
153
+ console.error(chalk.red("Session creation failed. Cleaning up..."));
154
+ store.remove(projectRoot, sessionId);
155
+ try {
156
+ await down({ cwd: sessionDir });
157
+ } catch {
158
+ }
159
+ if (!inPlace) {
160
+ try {
161
+ await removeWorktree(projectRoot, sessionDir, branchName);
162
+ } catch {
163
+ }
164
+ }
165
+ throw error;
166
+ }
167
+ console.log(chalk.green(`
168
+ Session ${sessionId} ready!`));
169
+ console.log(chalk.gray(`Directory: ${sessionDir}`));
170
+ if (mode === "docker") {
171
+ console.log(chalk.gray("\nDocker mode - all services in containers."));
172
+ console.log(chalk.gray("View logs: docker compose -f docker-compose.session.yml logs -f"));
173
+ } else {
174
+ console.log(chalk.gray("\nNative mode - run apps with: pnpm dev"));
175
+ }
176
+ console.log(chalk.gray("\nPorts:"));
177
+ for (const [name, port] of Object.entries(ports)) {
178
+ console.log(chalk.cyan(` ${name}: http://localhost:${port}`));
179
+ }
180
+ if (options.detach === false) {
181
+ console.log(chalk.blue("\nStreaming logs (Ctrl+C to stop)..."));
182
+ console.log(chalk.gray("\u2500".repeat(60)));
183
+ try {
184
+ await logs({ cwd: sessionDir, profiles });
185
+ } catch (error) {
186
+ const execaError = error;
187
+ if (execaError.signal === "SIGINT") {
188
+ console.log(chalk.gray("\n\u2500".repeat(60)));
189
+ console.log(chalk.yellow("\nLog streaming stopped. Services are still running."));
190
+ console.log(chalk.gray(`Resume logs: cd ${sessionDir} && docker compose -f docker-compose.session.yml --env-file .env.session logs -f`));
191
+ } else {
192
+ throw error;
193
+ }
194
+ }
195
+ }
196
+ } finally {
197
+ store.close();
198
+ }
199
+ }
200
+
201
+ export {
202
+ createSession
203
+ };
@@ -0,0 +1,78 @@
1
+ import {
2
+ removeWorktree
3
+ } from "./chunk-FKTFCSU7.js";
4
+ import {
5
+ down
6
+ } from "./chunk-GBN67HYD.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-25WQHUYW.js";
10
+ import {
11
+ SessionStore
12
+ } from "./chunk-6YMQTISJ.js";
13
+
14
+ // src/commands/destroy.ts
15
+ import { existsSync } from "fs";
16
+ import { resolve } from "path";
17
+ import chalk from "chalk";
18
+ async function destroySession(projectRoot, sessionId, options) {
19
+ const config = await loadConfig(projectRoot);
20
+ const store = new SessionStore();
21
+ try {
22
+ if (options.all) {
23
+ console.log(chalk.blue("Destroying all sessions..."));
24
+ const sessions = store.listByProject(projectRoot);
25
+ if (sessions.length === 0) {
26
+ console.log(chalk.gray("No sessions found."));
27
+ return;
28
+ }
29
+ for (const session2 of sessions) {
30
+ await destroySingleSession(projectRoot, session2.session_id, session2.session_dir, session2.branch, session2.in_place === 1);
31
+ store.markDestroyed(projectRoot, session2.session_id);
32
+ }
33
+ console.log(chalk.green(`
34
+ Destroyed ${sessions.length} session(s).`));
35
+ return;
36
+ }
37
+ if (!sessionId) {
38
+ console.error(chalk.red("Error: Session ID required. Use --all to destroy all sessions."));
39
+ process.exit(1);
40
+ }
41
+ if (!/^\d{3}$/.test(sessionId)) {
42
+ console.error(chalk.red("Error: Session ID must be exactly 3 digits (001-999)"));
43
+ process.exit(1);
44
+ }
45
+ const session = store.findSession(projectRoot, sessionId);
46
+ if (!session) {
47
+ console.error(chalk.red(`Error: Session ${sessionId} not found.`));
48
+ process.exit(1);
49
+ }
50
+ await destroySingleSession(projectRoot, sessionId, session.session_dir, session.branch, session.in_place === 1);
51
+ store.markDestroyed(projectRoot, sessionId);
52
+ console.log(chalk.green(`
53
+ Session ${sessionId} destroyed.`));
54
+ } finally {
55
+ store.close();
56
+ }
57
+ }
58
+ async function destroySingleSession(projectRoot, sessionId, sessionDir, branchName, inPlace) {
59
+ console.log(chalk.blue(`
60
+ Destroying session ${sessionId}...`));
61
+ const envFile = resolve(sessionDir, ".env.session");
62
+ if (existsSync(envFile)) {
63
+ console.log(chalk.gray(" Stopping Docker containers..."));
64
+ try {
65
+ await down({ cwd: sessionDir });
66
+ } catch {
67
+ }
68
+ }
69
+ if (!inPlace) {
70
+ console.log(chalk.gray(" Removing git worktree..."));
71
+ await removeWorktree(projectRoot, sessionDir, branchName);
72
+ }
73
+ console.log(chalk.green(` Session ${sessionId} destroyed.`));
74
+ }
75
+
76
+ export {
77
+ destroySession
78
+ };
@@ -0,0 +1,77 @@
1
+ import {
2
+ calculatePorts
3
+ } from "./chunk-3ATDGV4Y.js";
4
+ import {
5
+ isRunning
6
+ } from "./chunk-GBN67HYD.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-25WQHUYW.js";
10
+ import {
11
+ SessionStore
12
+ } from "./chunk-H4HPDIY3.js";
13
+
14
+ // src/commands/list.ts
15
+ import { existsSync } from "fs";
16
+ import { resolve } from "path";
17
+ import chalk from "chalk";
18
+ async function listSessions(projectRoot, options) {
19
+ const showAll = options?.all ?? false;
20
+ const config = await loadConfig(projectRoot);
21
+ const store = new SessionStore();
22
+ let sessions;
23
+ try {
24
+ const projectSessions = store.listByProject(projectRoot);
25
+ if (showAll || projectSessions.length === 0) {
26
+ sessions = store.listAll();
27
+ } else {
28
+ sessions = projectSessions;
29
+ }
30
+ } finally {
31
+ store.close();
32
+ }
33
+ if (sessions.length === 0) {
34
+ console.log(chalk.gray("No active sessions found."));
35
+ console.log(chalk.gray("\nTo create a session:"));
36
+ console.log(chalk.cyan(" dev-prism create"));
37
+ return;
38
+ }
39
+ console.log(chalk.blue("Active Sessions:"));
40
+ console.log(chalk.gray("================\n"));
41
+ for (const session of sessions) {
42
+ const sessionConfig = await loadConfig(session.project_root).catch(() => config);
43
+ const status = await getSessionStatus(session.session_id, session.session_dir, session.branch, sessionConfig);
44
+ printSessionStatus(status);
45
+ }
46
+ }
47
+ async function getSessionStatus(sessionId, path, branch, config) {
48
+ const ports = calculatePorts(config, sessionId);
49
+ let running = false;
50
+ const envFile = resolve(path, ".env.session");
51
+ if (existsSync(envFile)) {
52
+ running = await isRunning({ cwd: path });
53
+ }
54
+ return {
55
+ sessionId,
56
+ path,
57
+ branch,
58
+ running,
59
+ ports
60
+ };
61
+ }
62
+ function printSessionStatus(status) {
63
+ const statusIcon = status.running ? chalk.green("\u25CF") : chalk.red("\u25CB");
64
+ const statusText = status.running ? chalk.green("running") : chalk.gray("stopped");
65
+ console.log(`${statusIcon} Session ${chalk.bold(status.sessionId)} ${statusText}`);
66
+ console.log(chalk.gray(` Path: ${status.path}`));
67
+ console.log(chalk.gray(` Branch: ${status.branch}`));
68
+ console.log(chalk.gray(" Ports:"));
69
+ for (const [name, port] of Object.entries(status.ports)) {
70
+ console.log(chalk.cyan(` ${name}: http://localhost:${port}`));
71
+ }
72
+ console.log("");
73
+ }
74
+
75
+ export {
76
+ listSessions
77
+ };
@@ -0,0 +1,67 @@
1
+ import {
2
+ isRunning
3
+ } from "./chunk-GBN67HYD.js";
4
+ import {
5
+ loadConfig
6
+ } from "./chunk-25WQHUYW.js";
7
+ import {
8
+ SessionStore
9
+ } from "./chunk-H4HPDIY3.js";
10
+
11
+ // src/commands/stop-all.ts
12
+ import { existsSync } from "fs";
13
+ import { resolve } from "path";
14
+ import chalk from "chalk";
15
+ import { execa } from "execa";
16
+ async function stopAllSessions(projectRoot) {
17
+ const config = await loadConfig(projectRoot);
18
+ const store = new SessionStore();
19
+ let sessions;
20
+ try {
21
+ sessions = store.listByProject(projectRoot);
22
+ } finally {
23
+ store.close();
24
+ }
25
+ if (sessions.length === 0) {
26
+ console.log(chalk.gray("No sessions found."));
27
+ return;
28
+ }
29
+ const runningSessions = [];
30
+ for (const session of sessions) {
31
+ const envFile = resolve(session.session_dir, ".env.session");
32
+ if (existsSync(envFile)) {
33
+ const running = await isRunning({ cwd: session.session_dir });
34
+ if (running) {
35
+ runningSessions.push({ sessionId: session.session_id, path: session.session_dir });
36
+ }
37
+ }
38
+ }
39
+ if (runningSessions.length === 0) {
40
+ console.log(chalk.gray("No running sessions found."));
41
+ return;
42
+ }
43
+ console.log(chalk.blue(`Stopping ${runningSessions.length} running session(s)...
44
+ `));
45
+ const allApps = config.apps ?? [];
46
+ const profileFlags = allApps.flatMap((p) => ["--profile", p]);
47
+ const allServices = ["postgres", "mailpit", ...allApps];
48
+ for (const session of runningSessions) {
49
+ console.log(chalk.gray(` Stopping session ${session.sessionId}...`));
50
+ try {
51
+ await execa(
52
+ "docker",
53
+ ["compose", "-f", "docker-compose.session.yml", "--env-file", ".env.session", ...profileFlags, "stop", ...allServices],
54
+ { cwd: session.path, stdio: "pipe" }
55
+ );
56
+ console.log(chalk.green(` Session ${session.sessionId} stopped.`));
57
+ } catch {
58
+ console.log(chalk.yellow(` Warning: Could not stop session ${session.sessionId}`));
59
+ }
60
+ }
61
+ console.log(chalk.green(`
62
+ Stopped ${runningSessions.length} session(s).`));
63
+ }
64
+
65
+ export {
66
+ stopAllSessions
67
+ };
@@ -0,0 +1,49 @@
1
+ import {
2
+ loadConfig
3
+ } from "./chunk-25WQHUYW.js";
4
+ import {
5
+ SessionStore
6
+ } from "./chunk-H4HPDIY3.js";
7
+
8
+ // src/commands/logs.ts
9
+ import chalk from "chalk";
10
+ import { execa } from "execa";
11
+ async function streamLogs(projectRoot, sessionId, options) {
12
+ const config = await loadConfig(projectRoot);
13
+ const store = new SessionStore();
14
+ let sessionDir;
15
+ try {
16
+ const session = store.findSession(projectRoot, sessionId);
17
+ if (!session) {
18
+ console.error(chalk.red(`Error: Session ${sessionId} not found.`));
19
+ process.exit(1);
20
+ }
21
+ sessionDir = session.session_dir;
22
+ } finally {
23
+ store.close();
24
+ }
25
+ let profileFlags = [];
26
+ if (options.mode === "docker") {
27
+ const allApps = config.apps ?? [];
28
+ const excludeApps = options.without ?? [];
29
+ const profiles = allApps.filter((app) => !excludeApps.includes(app));
30
+ profileFlags = profiles.flatMap((p) => ["--profile", p]);
31
+ }
32
+ const args = [
33
+ "compose",
34
+ "-f",
35
+ "docker-compose.session.yml",
36
+ "--env-file",
37
+ ".env.session",
38
+ ...profileFlags,
39
+ "logs",
40
+ "-f",
41
+ "--tail",
42
+ options.tail ?? "50"
43
+ ];
44
+ await execa("docker", args, { cwd: sessionDir, stdio: "inherit" });
45
+ }
46
+
47
+ export {
48
+ streamLogs
49
+ };
@@ -0,0 +1,30 @@
1
+ import {
2
+ SessionStore
3
+ } from "./chunk-H4HPDIY3.js";
4
+
5
+ // src/commands/stop.ts
6
+ import chalk from "chalk";
7
+ import { execa } from "execa";
8
+ async function stopSession(projectRoot, sessionId) {
9
+ const store = new SessionStore();
10
+ let sessionDir;
11
+ try {
12
+ const session = store.findSession(projectRoot, sessionId);
13
+ if (!session) {
14
+ console.error(chalk.red(`Error: Session ${sessionId} not found.`));
15
+ process.exit(1);
16
+ }
17
+ sessionDir = session.session_dir;
18
+ } finally {
19
+ store.close();
20
+ }
21
+ await execa(
22
+ "docker",
23
+ ["compose", "-f", "docker-compose.session.yml", "--env-file", ".env.session", "stop"],
24
+ { cwd: sessionDir, stdio: "inherit" }
25
+ );
26
+ }
27
+
28
+ export {
29
+ stopSession
30
+ };