plum-e2e 2.8.6 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +20 -19
  2. package/backend/_scaffold/utils/browser.ts +7 -63
  3. package/backend/_scaffold/utils/hooks.ts +5 -20
  4. package/backend/app.js +0 -5
  5. package/backend/config/scripts/generate-report.js +2 -2
  6. package/backend/config/scripts/run-tests.js +5 -1
  7. package/backend/constants/socketEvents.js +7 -4
  8. package/backend/lib/plumTestRuntime.js +262 -0
  9. package/backend/lib/reportFilename.js +1 -2
  10. package/backend/lib/{screenshotPoller.js → rrwebPoller.js} +7 -4
  11. package/backend/lib/serverBootstrap.js +14 -0
  12. package/backend/logs/runner-cmtbz5b1l0000mr0110b27w5k.log +8 -0
  13. package/backend/mcp/server.js +3 -47
  14. package/backend/package-lock.json +199 -1
  15. package/backend/package.json +3 -1
  16. package/backend/prisma/migrations/20260828120000_add_recording_and_split_runner_worker_count/migration.sql +39 -0
  17. package/backend/prisma/migrations/20260828140000_add_recording_started_ended_at/migration.sql +5 -0
  18. package/backend/prisma/migrations/20260828150000_strip_screenshot_refs_from_reports/migration.sql +34 -0
  19. package/backend/prisma/migrations/20260828160000_add_backup_include_reports/migration.sql +4 -0
  20. package/backend/prisma/schema.prisma +97 -70
  21. package/backend/routes/backup.routes.js +47 -1
  22. package/backend/routes/reports.routes.js +23 -0
  23. package/backend/server.js +1 -1
  24. package/backend/services/backupCronService.js +1 -1
  25. package/backend/services/backupService.js +134 -44
  26. package/backend/services/cronService.js +23 -15
  27. package/backend/services/nodeExecutionService.js +41 -15
  28. package/backend/services/nodeStreamRegistry.js +24 -0
  29. package/backend/services/reportService.js +167 -83
  30. package/backend/services/runnerService.js +19 -7
  31. package/backend/services/settingsService.js +6 -3
  32. package/backend/services/triggerService.js +20 -13
  33. package/backend/websockets/nodeSocketHandler.js +40 -0
  34. package/backend/websockets/socketHandler.js +9 -7
  35. package/bin/plum.js +58 -1
  36. package/frontend/.svelte-kit/ambient.d.ts +28 -28
  37. package/frontend/.svelte-kit/generated/server/internal.js +1 -1
  38. package/frontend/package-lock.json +121 -32
  39. package/frontend/package.json +1 -0
  40. package/frontend/src/lib/api/reports.js +13 -4
  41. package/frontend/src/lib/api/settings.js +16 -1
  42. package/frontend/src/lib/components/layout/RunnerPanel.svelte +9 -24
  43. package/frontend/src/lib/components/reports/ElementInspector.svelte +141 -0
  44. package/frontend/src/lib/components/reports/LiveReplayer.svelte +110 -0
  45. package/frontend/src/lib/components/reports/MultiTabTimeline.svelte +115 -0
  46. package/frontend/src/lib/components/reports/RecordingPlayer.svelte +786 -0
  47. package/frontend/src/lib/components/reports/StepsRail.svelte +109 -0
  48. package/frontend/src/lib/components/ui/CodeViewer.svelte +61 -0
  49. package/frontend/src/lib/constants.js +0 -1
  50. package/frontend/src/lib/copy/reports.js +18 -8
  51. package/frontend/src/lib/copy/settings.js +25 -4
  52. package/frontend/src/lib/socketEvents.js +7 -4
  53. package/frontend/src/lib/stores/runner.js +26 -3
  54. package/frontend/src/lib/styles/tokens.css +7 -0
  55. package/frontend/src/lib/utils/format.js +108 -2
  56. package/frontend/src/lib/utils/inspectElement.js +34 -0
  57. package/frontend/src/routes/reports/+page.svelte +79 -1
  58. package/frontend/src/routes/reports/[id]/+page.svelte +304 -495
  59. package/frontend/src/routes/reports/live/+page.svelte +236 -260
  60. package/frontend/src/routes/settings/+page.svelte +246 -8
  61. package/package.json +1 -1
  62. package/backend/playwright.config.js +0 -85
@@ -8,8 +8,8 @@ const fs = require('fs');
8
8
  const os = require('os');
9
9
  const { spawn } = require('child_process');
10
10
  const { randomUUID } = require('crypto');
11
- const { startSsPoller } = require('../lib/screenshotPoller');
12
- const { TRIGGER_TYPE } = require('../constants/triggers');
11
+ const { startRRwebPoller } = require('../lib/rrwebPoller');
12
+ const { TRIGGER_TYPE, BUILT_IN_RUNNER_ID } = require('../constants/triggers');
13
13
  const { DEFAULT_BROWSER } = require('../constants/defaults');
14
14
  const { PLUM_MODE_NODE } = require('../constants/env');
15
15
  const { SOCKET_EVENTS } = require('../constants/socketEvents');
@@ -52,7 +52,7 @@ function runAttempt({
52
52
  baseUrl,
53
53
  suppressSave,
54
54
  onLog,
55
- onScreenshot
55
+ onRRwebBatch
56
56
  }) {
57
57
  return new Promise((resolve) => {
58
58
  const ssDir = path.join(os.tmpdir(), `plum-trigger-ss-${jobId}-${Date.now()}`);
@@ -73,7 +73,7 @@ function runAttempt({
73
73
 
74
74
  const proc = spawn('npm', ['run', 'test'], { env, shell: true, cwd: BACKEND_DIR });
75
75
 
76
- const ssPoller = startSsPoller(ssDir, onScreenshot);
76
+ const ssPoller = startRRwebPoller(ssDir, onRRwebBatch);
77
77
 
78
78
  proc.stdout.on('data', (d) => onLog(d.toString()));
79
79
  proc.stderr.on('data', (d) => onLog(`[ERROR] ${d.toString()}`));
@@ -96,7 +96,7 @@ function runNoRetry({
96
96
  baseUrl,
97
97
  startedAt,
98
98
  onLog,
99
- onScreenshot
99
+ onRRwebBatch
100
100
  }) {
101
101
  runAttempt({
102
102
  jobId,
@@ -108,7 +108,7 @@ function runNoRetry({
108
108
  baseUrl,
109
109
  suppressSave: false,
110
110
  onLog,
111
- onScreenshot
111
+ onRRwebBatch
112
112
  }).then(async ({ code }) => {
113
113
  let reportId = null;
114
114
  try {
@@ -142,7 +142,7 @@ function runWithRetriesAndSave({
142
142
  maxRetries,
143
143
  startedAt,
144
144
  onLog,
145
- onScreenshot
145
+ onRRwebBatch
146
146
  }) {
147
147
  runWithRetries({
148
148
  maxRetries,
@@ -157,7 +157,7 @@ function runWithRetriesAndSave({
157
157
  baseUrl,
158
158
  suppressSave: true,
159
159
  onLog,
160
- onScreenshot
160
+ onRRwebBatch
161
161
  });
162
162
  return { code, rawJson: raw ? JSON.parse(raw) : [] };
163
163
  },
@@ -169,6 +169,7 @@ function runWithRetriesAndSave({
169
169
  rawCucumberJson: rawJson,
170
170
  tags: tag,
171
171
  triggerType: trigger,
172
+ workerCount: workers,
172
173
  browser,
173
174
  testRunId: testRunId ?? null,
174
175
  duration: Date.now() - startedAt,
@@ -191,8 +192,8 @@ function runWithRetriesAndSave({
191
192
 
192
193
  // Starts a background test run for the HTTP trigger API and returns the jobId
193
194
  // immediately — the run continues asynchronously, reporting progress via
194
- // socket.io events (bg-run-start/log/screenshot/done) and the job store
195
- // (polled through getJob).
195
+ // socket.io events (bg-run-start/log/done) and the job store (polled through
196
+ // getJob).
196
197
  async function startRun({
197
198
  tag = '',
198
199
  browser = DEFAULT_BROWSER,
@@ -218,8 +219,14 @@ async function startRun({
218
219
  const onLog = (text) => {
219
220
  if (_io) _io.emit(SOCKET_EVENTS.BG_RUN_LOG, { runId: jobId, log: text });
220
221
  };
221
- const onScreenshot = ({ stepName, data }) => {
222
- if (_io) _io.emit(SOCKET_EVENTS.BG_RUN_SCREENSHOT, { runId: jobId, stepName, data });
222
+ const onRRwebBatch = (batch) => {
223
+ if (_io) {
224
+ _io.emit(SOCKET_EVENTS.BG_RUN_LANE_RRWEB_BATCH, {
225
+ runId: jobId,
226
+ id: BUILT_IN_RUNNER_ID,
227
+ ...batch
228
+ });
229
+ }
223
230
  };
224
231
 
225
232
  const runParams = {
@@ -232,7 +239,7 @@ async function startRun({
232
239
  baseUrl,
233
240
  startedAt,
234
241
  onLog,
235
- onScreenshot
242
+ onRRwebBatch
236
243
  };
237
244
  if (maxRetries === 0) {
238
245
  runNoRetry(runParams);
@@ -0,0 +1,40 @@
1
+ /*
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ */
5
+
6
+ const runnerService = require('../services/runnerService');
7
+ const nodeStreamRegistry = require('../services/nodeStreamRegistry');
8
+
9
+ // A separate namespace from the browser-facing default one — a node connects
10
+ // with its own runner token (the same credential it already uses to call back
11
+ // into the primary's HTTP control routes, see runnerService.isValidToken) and
12
+ // announces the jobId it's streaming for, which nodeStreamRegistry maps back
13
+ // to whichever dispatch call is waiting on it.
14
+ function nodeSocketHandler(io) {
15
+ const nodeIo = io.of('/node-stream');
16
+
17
+ nodeIo.use(async (socket, next) => {
18
+ const token = socket.handshake.auth?.token;
19
+ if (await runnerService.isValidToken(token)) return next();
20
+ next(new Error('unauthorized'));
21
+ });
22
+
23
+ nodeIo.on('connection', (socket) => {
24
+ let jobId = null;
25
+
26
+ socket.on('join', (id) => {
27
+ jobId = id;
28
+ });
29
+
30
+ socket.on('rrweb-batch', (batch) => {
31
+ if (jobId) nodeStreamRegistry.getRelay(jobId)?.onRRwebBatch?.(batch);
32
+ });
33
+
34
+ socket.on('log', (text) => {
35
+ if (jobId) nodeStreamRegistry.getRelay(jobId)?.onLog?.(text);
36
+ });
37
+ });
38
+ }
39
+
40
+ module.exports = nodeSocketHandler;
@@ -13,7 +13,7 @@ const reportService = require('../services/reportService');
13
13
  const settingsService = require('../services/settingsService');
14
14
  const notificationService = require('../services/notificationService');
15
15
  const activeRunsService = require('../services/activeRunsService');
16
- const { startSsPoller } = require('../lib/screenshotPoller');
16
+ const { startRRwebPoller } = require('../lib/rrwebPoller');
17
17
  const { runWithRetries } = require('../lib/retryRunner');
18
18
  const { TRIGGER_TYPE, BUILT_IN_RUNNER_ID, TRIGGER_REMOTE } = require('../constants/triggers');
19
19
  const { DEFAULT_BROWSER } = require('../constants/defaults');
@@ -243,8 +243,8 @@ function runBuiltInAttempt({
243
243
  const proc = spawn('npm', ['run', 'test'], { env, shell: true });
244
244
  activeProcs.add(proc);
245
245
 
246
- const ssPoller = startSsPoller(ssDir, ({ stepName, data }) => {
247
- socket.emit(SOCKET_EVENTS.STEP_SCREENSHOT, { stepName, data });
246
+ const ssPoller = startRRwebPoller(ssDir, (batch) => {
247
+ socket.emit(SOCKET_EVENTS.RUNNER_LANE_RRWEB_BATCH, { id: BUILT_IN_RUNNER_ID, ...batch });
248
248
  });
249
249
 
250
250
  proc.stdout.on('data', (d) => onLog(d.toString()));
@@ -362,6 +362,7 @@ async function runBuiltIn(
362
362
  rawCucumberJson: rawJson,
363
363
  tags: tag,
364
364
  triggerType: TRIGGER_TYPE.MANUAL,
365
+ workerCount: workers,
365
366
  browser,
366
367
  testRunId: testRunId ?? null,
367
368
  logs: logBuffer || null,
@@ -468,6 +469,7 @@ async function runDistributed(
468
469
  .saveCombinedReport({
469
470
  reports: collectedReports,
470
471
  runners: laneInfos,
472
+ workers,
471
473
  overallCode,
472
474
  tag,
473
475
  triggerType: TRIGGER_TYPE.MANUAL,
@@ -543,8 +545,8 @@ async function runDistributed(
543
545
  const proc = spawn('npm', ['run', 'test'], { env, shell: true });
544
546
  activeProcs.add(proc);
545
547
 
546
- const ssPoller = startSsPoller(ssDir, ({ stepName, data }) => {
547
- socket.emit(SOCKET_EVENTS.RUNNER_LANE_SCREENSHOT, { id: laneId, stepName, data });
548
+ const ssPoller = startRRwebPoller(ssDir, (batch) => {
549
+ socket.emit(SOCKET_EVENTS.RUNNER_LANE_RRWEB_BATCH, { id: laneId, ...batch });
548
550
  });
549
551
 
550
552
  proc.stdout.on('data', (d) => onLog(d.toString()));
@@ -588,8 +590,8 @@ async function runDistributed(
588
590
  makeSyntheticFailReport(lane.name, chunkIds, 'could not fetch report from runner');
589
591
  resolve({ code, rawJson: JSON.parse(raw) });
590
592
  },
591
- ({ stepName, data }) => {
592
- socket.emit(SOCKET_EVENTS.RUNNER_LANE_SCREENSHOT, { id: laneId, stepName, data });
593
+ (batch) => {
594
+ socket.emit(SOCKET_EVENTS.RUNNER_LANE_RRWEB_BATCH, { id: laneId, ...batch });
593
595
  }
594
596
  );
595
597
  });
package/bin/plum.js CHANGED
@@ -62,6 +62,48 @@ function scaffoldPluginsFile() {
62
62
  clack.log.success('plum.plugins.json created.');
63
63
  }
64
64
 
65
+ // Files under tests/ that are Plum's own wiring rather than customer content —
66
+ // `plum init` only writes these once, so a project scaffolded before a Plum
67
+ // upgrade keeps running whatever version shipped at init time (e.g. an old
68
+ // screenshot-based browser.ts after Plum has moved to rrweb recording) unless
69
+ // something explicitly re-syncs them. Never touches customer-owned files
70
+ // (features/, pages/, step_definitions/, utils/constants.ts, utils/utils.ts).
71
+ const INFRA_SCAFFOLD_FILES = ['utils/browser.ts', 'utils/hooks.ts'];
72
+
73
+ // Re-syncs INFRA_SCAFFOLD_FILES from the installed Plum version's scaffold
74
+ // into an existing tests/ directory, backing up anything it overwrites so a
75
+ // customer's own edits to these files (unsupported, but possible) aren't
76
+ // silently lost.
77
+ function syncScaffoldInfraFiles(testsDir) {
78
+ if (!fs.existsSync(testsDir)) {
79
+ clack.log.warn(`No \`tests/\` folder found at ${testsDir} — skipping scaffold sync.`);
80
+ return;
81
+ }
82
+
83
+ let updated = 0;
84
+ for (const relPath of INFRA_SCAFFOLD_FILES) {
85
+ const src = path.join(scaffoldTestsPath, relPath);
86
+ const dest = path.join(testsDir, relPath);
87
+ if (!fs.existsSync(src) || !fs.existsSync(dest)) continue;
88
+
89
+ const current = fs.readFileSync(dest, 'utf8');
90
+ const latest = fs.readFileSync(src, 'utf8');
91
+ if (current === latest) continue;
92
+
93
+ const backupPath = `${dest}.bak-${Date.now()}`;
94
+ fs.copyFileSync(dest, backupPath);
95
+ fs.copyFileSync(src, dest);
96
+ updated++;
97
+ clack.log.success(
98
+ `Updated ${relPath} (previous version backed up to ${path.basename(backupPath)})`
99
+ );
100
+ }
101
+
102
+ if (updated === 0) {
103
+ clack.log.info('Test scaffold wiring is already up to date.');
104
+ }
105
+ }
106
+
65
107
  // Install user plugins listed in plum.plugins.json into the backend
66
108
  function installPlugins() {
67
109
  const pluginsPath = path.join(process.cwd(), 'plum.plugins.json');
@@ -511,6 +553,9 @@ async function serverUpdate() {
511
553
  // logic no matter how new the just-installed files on disk actually are.
512
554
  for (const dir of getInstalls('server')) {
513
555
  if (!fs.existsSync(path.join(dir, '.plum-server.json'))) continue;
556
+ if (fs.existsSync(path.join(dir, 'tests'))) {
557
+ syncScaffoldInfraFiles(path.join(dir, 'tests'));
558
+ }
514
559
  clack.log.step(`Rebuilding server at ${dir}…`);
515
560
  try {
516
561
  execSync('plum server restart', { stdio: 'inherit', cwd: dir });
@@ -523,6 +568,9 @@ async function serverUpdate() {
523
568
  for (const dir of getInstalls('node')) {
524
569
  const nodeCfg = loadNodeConfig(dir);
525
570
  if (!nodeCfg.id) continue;
571
+ if (fs.existsSync(path.join(dir, 'tests'))) {
572
+ syncScaffoldInfraFiles(path.join(dir, 'tests'));
573
+ }
526
574
  // Always attempt the restart rather than gating on the local PID
527
575
  // registry: that registry goes stale (manager restarts, pre-existing
528
576
  // installs from before this tracking existed, etc.), and skipping the
@@ -1093,6 +1141,12 @@ switch (command) {
1093
1141
  await serverUpdate();
1094
1142
  break;
1095
1143
 
1144
+ case 'sync-scaffold':
1145
+ clack.intro(pc.bgMagenta(pc.white(' 🟣 Plum — Sync Test Scaffold ')));
1146
+ syncScaffoldInfraFiles(userTestsPath);
1147
+ clack.outro(pc.green('Done.'));
1148
+ break;
1149
+
1096
1150
  case 'run-test': {
1097
1151
  const runHelpArgs = process.argv.slice(3);
1098
1152
  if (anyFlags(runHelpArgs, ['--help', '-h'])) {
@@ -1299,7 +1353,10 @@ switch (command) {
1299
1353
  console.log(' server stop Stop the server (data preserved)');
1300
1354
  console.log(' server reconfig Re-enter server settings without starting');
1301
1355
  console.log(
1302
- ' update Update Plum and restart whichever is running (server/node)'
1356
+ ' update Update Plum, restart whichever is running (server/node), and re-sync tests/ wiring'
1357
+ );
1358
+ console.log(
1359
+ ' sync-scaffold Re-sync browser.ts/hooks.ts in tests/ from the installed Plum version'
1303
1360
  );
1304
1361
  console.log(' node start Start a runner node (interactive), then open runner menu');
1305
1362
  console.log(' --primary <url> Primary Plum server to auto-register with');
@@ -58,41 +58,42 @@ declare module '$env/static/private' {
58
58
  export const VIKUNJA_API_KEY: string;
59
59
  export const USER: string;
60
60
  export const COMMAND_MODE: string;
61
- export const npm_config_globalconfig: string;
62
61
  export const OUTLINE_BASE_URL: string;
62
+ export const npm_config_globalconfig: string;
63
63
  export const CLAUDE_CODE_SSE_PORT: string;
64
64
  export const SSH_AUTH_SOCK: string;
65
65
  export const VSCODE_PROFILE_INITIALIZED: string;
66
66
  export const __CF_USER_TEXT_ENCODING: string;
67
67
  export const npm_execpath: string;
68
68
  export const PATH: string;
69
- export const npm_package_json: string;
69
+ export const npm_package_bin_plum: string;
70
70
  export const npm_config_engine_strict: string;
71
71
  export const _: string;
72
- export const npm_config_userconfig: string;
73
- export const npm_config_init_module: string;
72
+ export const npm_package_json: string;
74
73
  export const USER_ZDOTDIR: string;
75
74
  export const __CFBundleIdentifier: string;
76
- export const npm_command: string;
75
+ export const npm_config_init_module: string;
76
+ export const npm_config_userconfig: string;
77
77
  export const PWD: string;
78
- export const npm_lifecycle_event: string;
79
- export const EDITOR: string;
78
+ export const npm_command: string;
80
79
  export const OUTLINE_API_KEY: string;
81
- export const npm_package_name: string;
80
+ export const EDITOR: string;
81
+ export const npm_lifecycle_event: string;
82
82
  export const LANG: string;
83
- export const npm_config_npm_version: string;
83
+ export const npm_package_name: string;
84
84
  export const VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
85
85
  export const XPC_FLAGS: string;
86
+ export const npm_config_npm_version: string;
86
87
  export const npm_config_node_gyp: string;
87
- export const npm_package_version: string;
88
88
  export const XPC_SERVICE_NAME: string;
89
+ export const npm_package_version: string;
89
90
  export const VSCODE_INJECTION: string;
90
91
  export const SHLVL: string;
91
92
  export const HOME: string;
92
93
  export const VSCODE_GIT_ASKPASS_MAIN: string;
93
94
  export const CLAUDE_CODE_EXECPATH: string;
94
- export const npm_config_cache: string;
95
95
  export const LOGNAME: string;
96
+ export const npm_config_cache: string;
96
97
  export const npm_lifecycle_script: string;
97
98
  export const VSCODE_GIT_IPC_HANDLE: string;
98
99
  export const COREPACK_ENABLE_AUTO_PIN: string;
@@ -103,10 +104,9 @@ declare module '$env/static/private' {
103
104
  export const OSLogRateLimit: string;
104
105
  export const CLAUDECODE: string;
105
106
  export const CLAUDE_CODE_MESSAGING_SOCKET: string;
106
- export const npm_node_execpath: string;
107
- export const npm_config_prefix: string;
108
107
  export const COLORTERM: string;
109
- export const NODE_ENV: string;
108
+ export const npm_config_prefix: string;
109
+ export const npm_node_execpath: string;
110
110
  }
111
111
 
112
112
  /**
@@ -165,41 +165,42 @@ declare module '$env/dynamic/private' {
165
165
  VIKUNJA_API_KEY: string;
166
166
  USER: string;
167
167
  COMMAND_MODE: string;
168
- npm_config_globalconfig: string;
169
168
  OUTLINE_BASE_URL: string;
169
+ npm_config_globalconfig: string;
170
170
  CLAUDE_CODE_SSE_PORT: string;
171
171
  SSH_AUTH_SOCK: string;
172
172
  VSCODE_PROFILE_INITIALIZED: string;
173
173
  __CF_USER_TEXT_ENCODING: string;
174
174
  npm_execpath: string;
175
175
  PATH: string;
176
- npm_package_json: string;
176
+ npm_package_bin_plum: string;
177
177
  npm_config_engine_strict: string;
178
178
  _: string;
179
- npm_config_userconfig: string;
180
- npm_config_init_module: string;
179
+ npm_package_json: string;
181
180
  USER_ZDOTDIR: string;
182
181
  __CFBundleIdentifier: string;
183
- npm_command: string;
182
+ npm_config_init_module: string;
183
+ npm_config_userconfig: string;
184
184
  PWD: string;
185
- npm_lifecycle_event: string;
186
- EDITOR: string;
185
+ npm_command: string;
187
186
  OUTLINE_API_KEY: string;
188
- npm_package_name: string;
187
+ EDITOR: string;
188
+ npm_lifecycle_event: string;
189
189
  LANG: string;
190
- npm_config_npm_version: string;
190
+ npm_package_name: string;
191
191
  VSCODE_GIT_ASKPASS_EXTRA_ARGS: string;
192
192
  XPC_FLAGS: string;
193
+ npm_config_npm_version: string;
193
194
  npm_config_node_gyp: string;
194
- npm_package_version: string;
195
195
  XPC_SERVICE_NAME: string;
196
+ npm_package_version: string;
196
197
  VSCODE_INJECTION: string;
197
198
  SHLVL: string;
198
199
  HOME: string;
199
200
  VSCODE_GIT_ASKPASS_MAIN: string;
200
201
  CLAUDE_CODE_EXECPATH: string;
201
- npm_config_cache: string;
202
202
  LOGNAME: string;
203
+ npm_config_cache: string;
203
204
  npm_lifecycle_script: string;
204
205
  VSCODE_GIT_IPC_HANDLE: string;
205
206
  COREPACK_ENABLE_AUTO_PIN: string;
@@ -210,10 +211,9 @@ declare module '$env/dynamic/private' {
210
211
  OSLogRateLimit: string;
211
212
  CLAUDECODE: string;
212
213
  CLAUDE_CODE_MESSAGING_SOCKET: string;
213
- npm_node_execpath: string;
214
- npm_config_prefix: string;
215
214
  COLORTERM: string;
216
- NODE_ENV: string;
215
+ npm_config_prefix: string;
216
+ npm_node_execpath: string;
217
217
  [key: `PUBLIC_${string}`]: undefined;
218
218
  [key: `${string}`]: string | undefined;
219
219
  }
@@ -26,7 +26,7 @@ export const options = {
26
26
  app: ({ head, body, assets, nonce, env }) => "<!--\nThis file is part of Plum.\nLicensed under the MIT License. See LICENSE file in the project root for details.\n-->\n<!doctype html>\n<html lang=\"en\" data-theme=\"light\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.ico\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n\t\t<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n\t\t<link\n\t\t\thref=\"https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap\"\n\t\t\trel=\"stylesheet\"\n\t\t/>\n\t\t<!-- Prevent theme flash before Svelte hydrates -->\n\t\t<script>\n\t\t\ttry {\n\t\t\t\tconst t = localStorage.getItem('plum-theme');\n\t\t\t\tif (t) document.documentElement.setAttribute('data-theme', t);\n\t\t\t} catch (e) {}\n\t\t</script>\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
27
27
  error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
28
28
  },
29
- version_hash: "1cz1h1z"
29
+ version_hash: "19o4i41"
30
30
  };
31
31
 
32
32
  export async function get_hooks() {