@absolutejs/absolute 0.19.0-beta.681 → 0.19.0-beta.683

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1145,17 +1145,17 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
1145
1145
  }, shellEscape = (value) => `'${value.replaceAll("'", "'\\''")}'`, runShell = async (name, command) => run(name, ["/bin/bash", "-lc", command]), findBin = (name) => {
1146
1146
  const local = resolve8("node_modules", ".bin", name);
1147
1147
  return existsSync10(local) ? local : null;
1148
- }, stripAnsi2 = (str) => str.replace(/\x1b\[[0-9;]*m/g, ""), formatSvelteOutput = (output) => {
1148
+ }, stripAnsi3 = (str) => str.replace(/\x1b\[[0-9;]*m/g, ""), formatSvelteOutput = (output) => {
1149
1149
  const cwd = `${process.cwd()}/`;
1150
- const summaryMatch = stripAnsi2(output).match(/svelte-check found (\d+) error/);
1150
+ const summaryMatch = stripAnsi3(output).match(/svelte-check found (\d+) error/);
1151
1151
  const errorCount = summaryMatch ? parseInt(summaryMatch[1] ?? "0", 10) : 0;
1152
1152
  const formatted = output.split(`
1153
1153
  `).filter((line) => {
1154
- const plain = stripAnsi2(line);
1154
+ const plain = stripAnsi3(line);
1155
1155
  return !plain.startsWith("Loading svelte-check") && !plain.startsWith("Getting Svelte") && !plain.startsWith("====") && !plain.startsWith("svelte-check found") && !/^\d+ (START|COMPLETED)/.test(plain) && plain.trim() !== "";
1156
1156
  }).flatMap((line) => {
1157
1157
  const result = line.replaceAll(cwd, "");
1158
- const plain = stripAnsi2(result);
1158
+ const plain = stripAnsi3(result);
1159
1159
  const pathMatch = plain.match(/^(\S+\.svelte):(\d+:\d+)$/);
1160
1160
  if (pathMatch) {
1161
1161
  return [
@@ -1163,9 +1163,9 @@ var isCommandService3 = (service) => service.kind === "command" || Array.isArray
1163
1163
  ];
1164
1164
  }
1165
1165
  if (result.includes("\x1B[35m")) {
1166
- const plainLine = stripAnsi2(result);
1167
- const before = stripAnsi2(result.split("\x1B[35m")[0] ?? "");
1168
- const token = stripAnsi2((result.split("\x1B[35m")[1] ?? "").split(/\x1b\[3[69]m/)[0] ?? "");
1166
+ const plainLine = stripAnsi3(result);
1167
+ const before = stripAnsi3(result.split("\x1B[35m")[0] ?? "");
1168
+ const token = stripAnsi3((result.split("\x1B[35m")[1] ?? "").split(/\x1b\[3[69]m/)[0] ?? "");
1169
1169
  if (!token)
1170
1170
  return [result];
1171
1171
  const expanded = before.replace(/\t/g, " ");
@@ -2485,7 +2485,13 @@ var start = async (serverEntry, outdir, configPath2) => {
2485
2485
  init_constants();
2486
2486
  init_loadConfig();
2487
2487
  init_getDurationString();
2488
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
2488
+ import {
2489
+ appendFileSync,
2490
+ existsSync as existsSync8,
2491
+ mkdirSync as mkdirSync4,
2492
+ readFileSync as readFileSync8,
2493
+ writeFileSync as writeFileSync2
2494
+ } from "fs";
2489
2495
  import { createConnection } from "net";
2490
2496
  import { resolve as resolve6 } from "path";
2491
2497
 
@@ -3127,7 +3133,44 @@ var createWorkspaceTui = ({
3127
3133
 
3128
3134
  // src/cli/scripts/workspace.ts
3129
3135
  init_utils();
3136
+ var ANSI_REGEX2 = /\x1B\[[0-?]*[ -/]*[@-~]/g;
3130
3137
  var sleep = (ms) => new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
3138
+ var stripAnsi2 = (value) => value.replace(ANSI_REGEX2, "");
3139
+ var sanitizeLogFileName = (value) => value.replace(/[^a-zA-Z0-9._-]/g, "_") || "unknown";
3140
+ var createWorkspaceLogSink = (appendLog) => {
3141
+ const logDirectory = resolve6(".absolutejs", "workspace", "logs");
3142
+ mkdirSync4(logDirectory, { recursive: true });
3143
+ for (const file of ["all.log", "workspace.log"]) {
3144
+ writeFileSync2(resolve6(logDirectory, file), "");
3145
+ }
3146
+ const initializedSources = new Set(["workspace"]);
3147
+ const writeLog = (source, message, level) => {
3148
+ const cleanMessage = stripAnsi2(message).trimEnd();
3149
+ if (!cleanMessage) {
3150
+ return;
3151
+ }
3152
+ const timestamp = new Date().toISOString();
3153
+ const line = `[${timestamp}] [${level}] [${source}] ${cleanMessage}
3154
+ `;
3155
+ const sourceFile = resolve6(logDirectory, `${sanitizeLogFileName(source)}.log`);
3156
+ if (!initializedSources.has(source)) {
3157
+ writeFileSync2(sourceFile, "");
3158
+ initializedSources.add(source);
3159
+ }
3160
+ appendFileSync(sourceFile, line);
3161
+ appendFileSync(resolve6(logDirectory, "all.log"), line);
3162
+ if (source === "workspace") {
3163
+ appendFileSync(resolve6(logDirectory, "workspace.log"), line);
3164
+ }
3165
+ };
3166
+ return {
3167
+ logDirectory,
3168
+ appendLog: (source, message, level = "info") => {
3169
+ writeLog(source, message, level);
3170
+ appendLog(source, message, level);
3171
+ }
3172
+ };
3173
+ };
3131
3174
  var readPackageVersion2 = (candidate) => {
3132
3175
  try {
3133
3176
  const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
@@ -3320,6 +3363,51 @@ var waitForReady = async (service) => {
3320
3363
  }
3321
3364
  throw new Error(resolved.type === "http" ? `service did not become ready within ${resolved.timeoutMs}ms (${resolved.url})` : resolved.type === "tcp" ? `service did not become ready within ${resolved.timeoutMs}ms (tcp://${resolved.host}:${resolved.port})` : `service did not become ready within ${resolved.timeoutMs}ms (${resolved.command.join(" ")})`);
3322
3365
  };
3366
+ var resolveShutdownHook = (shutdown) => {
3367
+ if (!shutdown) {
3368
+ return null;
3369
+ }
3370
+ if (Array.isArray(shutdown)) {
3371
+ return {
3372
+ command: shutdown,
3373
+ timeoutMs: 1e4
3374
+ };
3375
+ }
3376
+ return {
3377
+ command: shutdown.command,
3378
+ timeoutMs: shutdown.timeoutMs ?? 1e4
3379
+ };
3380
+ };
3381
+ var runShutdownHook = async (service, onLog) => {
3382
+ const hook = resolveShutdownHook(service.service.shutdown);
3383
+ if (!hook) {
3384
+ return;
3385
+ }
3386
+ onLog("workspace", `Running ${service.name} shutdown hook...`, "info");
3387
+ const processHandle = Bun.spawn(hook.command, {
3388
+ cwd: service.cwd,
3389
+ env: service.env,
3390
+ stderr: "pipe",
3391
+ stdin: "ignore",
3392
+ stdout: "pipe"
3393
+ });
3394
+ pipeProcessLogs(service.name, processHandle, onLog);
3395
+ const timeout = setTimeout(() => {
3396
+ try {
3397
+ processHandle.kill();
3398
+ } catch {}
3399
+ }, hook.timeoutMs);
3400
+ try {
3401
+ const exitCode = await processHandle.exited;
3402
+ if (exitCode === 0) {
3403
+ onLog("workspace", `${service.name} shutdown hook finished.`, "success");
3404
+ return;
3405
+ }
3406
+ onLog("workspace", `${service.name} shutdown hook exited with code ${exitCode || 1}.`, "warn");
3407
+ } finally {
3408
+ clearTimeout(timeout);
3409
+ }
3410
+ };
3323
3411
  var topologicallySortServices = (services) => {
3324
3412
  const ordered = [];
3325
3413
  const visiting = new Set;
@@ -3487,6 +3575,9 @@ var workspace = async (subcommand, options) => {
3487
3575
  }),
3488
3576
  version: absoluteVersion
3489
3577
  });
3578
+ const workspaceLogs = createWorkspaceLogSink(tui.addLog);
3579
+ const addLog = workspaceLogs.appendLog;
3580
+ addLog("workspace", `Writing workspace logs to ${workspaceLogs.logDirectory}`, "info");
3490
3581
  const killProcesses = async () => {
3491
3582
  const snapshot = [...running];
3492
3583
  running.length = 0;
@@ -3496,6 +3587,14 @@ var workspace = async (subcommand, options) => {
3496
3587
  } catch {}
3497
3588
  }
3498
3589
  await Promise.all(snapshot.map((service) => service.process.exited));
3590
+ for (const service of snapshot.reverse()) {
3591
+ try {
3592
+ await runShutdownHook(service.resolved, addLog);
3593
+ } catch (error) {
3594
+ const message = error instanceof Error ? error.message : String(error);
3595
+ addLog("workspace", `${service.name} shutdown hook failed: ${message}`, "warn");
3596
+ }
3597
+ }
3499
3598
  };
3500
3599
  const printFailureSummary = (exitCode) => {
3501
3600
  const servicesSnapshot = tui.getServiceSnapshot();
@@ -3563,7 +3662,7 @@ var workspace = async (subcommand, options) => {
3563
3662
  const port = (resolved.service.port ?? Number(resolved.env.PORT ?? "")) || DEFAULT_PORT;
3564
3663
  if (port > 0) {
3565
3664
  killStaleProcesses(port, (message) => {
3566
- tui.addLog("workspace", message, "warn");
3665
+ addLog("workspace", message, "warn");
3567
3666
  });
3568
3667
  }
3569
3668
  if (isAbsoluteService(resolved.service) && resolved.configPath && !existsSync8(resolved.configPath)) {
@@ -3578,7 +3677,7 @@ var workspace = async (subcommand, options) => {
3578
3677
  stdin: "ignore",
3579
3678
  stdout: "pipe"
3580
3679
  });
3581
- pipeProcessLogs(name, processHandle, tui.addLog);
3680
+ pipeProcessLogs(name, processHandle, addLog);
3582
3681
  const runningService = {
3583
3682
  name,
3584
3683
  process: processHandle,
@@ -3593,7 +3692,7 @@ var workspace = async (subcommand, options) => {
3593
3692
  return;
3594
3693
  }
3595
3694
  tui.setServiceStatus(name, "error", `exit code ${exitCode || 1}`);
3596
- tui.addLog("workspace", `${name} exited with code ${exitCode || 1}. Shutting down workspace.`, "error");
3695
+ addLog("workspace", `${name} exited with code ${exitCode || 1}. Shutting down workspace.`, "error");
3597
3696
  shutdown(exitCode || 1);
3598
3697
  });
3599
3698
  await waitForReady(resolved);
@@ -3613,7 +3712,7 @@ var workspace = async (subcommand, options) => {
3613
3712
  }
3614
3713
  paused = false;
3615
3714
  }
3616
- tui.addLog("workspace", "Restarting workspace...", "info");
3715
+ addLog("workspace", "Restarting workspace...", "info");
3617
3716
  for (const name of orderedNames) {
3618
3717
  tui.setServiceStatus(name, "restarting");
3619
3718
  }
@@ -3622,7 +3721,7 @@ var workspace = async (subcommand, options) => {
3622
3721
  workspaceBootStartedAt = performance.now();
3623
3722
  await startServices();
3624
3723
  tui.setReadyDuration(performance.now() - workspaceBootStartedAt);
3625
- tui.addLog("workspace", "Workspace ready.", "success");
3724
+ addLog("workspace", "Workspace ready.", "success");
3626
3725
  };
3627
3726
  const togglePause = () => {
3628
3727
  if (paused) {
@@ -3631,14 +3730,14 @@ var workspace = async (subcommand, options) => {
3631
3730
  tui.setServiceStatus(service.name, "ready");
3632
3731
  }
3633
3732
  paused = false;
3634
- tui.addLog("workspace", "Workspace resumed.", "success");
3733
+ addLog("workspace", "Workspace resumed.", "success");
3635
3734
  } else {
3636
3735
  for (const service of running) {
3637
3736
  sendSignalToService(service.process, "SIGSTOP");
3638
3737
  tui.setServiceStatus(service.name, "paused");
3639
3738
  }
3640
3739
  paused = true;
3641
- tui.addLog("workspace", "Workspace paused.", "warn");
3740
+ addLog("workspace", "Workspace paused.", "warn");
3642
3741
  }
3643
3742
  };
3644
3743
  const runShellCommand2 = async (command) => {
@@ -3647,19 +3746,19 @@ var workspace = async (subcommand, options) => {
3647
3746
  stderr: "pipe",
3648
3747
  stdout: "pipe"
3649
3748
  });
3650
- pipeProcessLogs("shell", processHandle, tui.addLog);
3749
+ pipeProcessLogs("shell", processHandle, addLog);
3651
3750
  const exitCode = await processHandle.exited;
3652
3751
  if (exitCode === 0) {
3653
- tui.addLog("workspace", `Shell command finished: ${command}`, "success");
3752
+ addLog("workspace", `Shell command finished: ${command}`, "success");
3654
3753
  return;
3655
3754
  }
3656
- tui.addLog("workspace", `Shell command failed with exit code ${exitCode}: ${command}`, "error");
3755
+ addLog("workspace", `Shell command failed with exit code ${exitCode}: ${command}`, "error");
3657
3756
  };
3658
3757
  const openInBrowser = async () => {
3659
3758
  const publicService = orderedNames.map((name) => services[name]).find((service) => service && getVisibility(service) === "public");
3660
3759
  const url = publicService ? getServiceUrl(publicService) : null;
3661
3760
  if (!url) {
3662
- tui.addLog("workspace", "No public service to open.", "warn");
3761
+ addLog("workspace", "No public service to open.", "warn");
3663
3762
  return;
3664
3763
  }
3665
3764
  const { platform: platform4 } = process;
@@ -3680,9 +3779,9 @@ var workspace = async (subcommand, options) => {
3680
3779
  stderr: "ignore",
3681
3780
  stdout: "ignore"
3682
3781
  });
3683
- tui.addLog("workspace", `Opening ${url}`, "info");
3782
+ addLog("workspace", `Opening ${url}`, "info");
3684
3783
  } catch {
3685
- tui.addLog("workspace", `Could not open browser automatically. Visit ${url}`, "warn");
3784
+ addLog("workspace", `Could not open browser automatically. Visit ${url}`, "warn");
3686
3785
  }
3687
3786
  };
3688
3787
  process.on("SIGINT", () => {
@@ -3692,10 +3791,10 @@ var workspace = async (subcommand, options) => {
3692
3791
  shutdown(0);
3693
3792
  });
3694
3793
  tui.start();
3695
- tui.addLog("workspace", `Workspace booting ${orderedNames.length} services.`, "info");
3794
+ addLog("workspace", `Workspace booting ${orderedNames.length} services.`, "info");
3696
3795
  await startServices();
3697
3796
  tui.setReadyDuration(performance.now() - workspaceBootStartedAt);
3698
- tui.addLog("workspace", "Workspace ready.", "success");
3797
+ addLog("workspace", "Workspace ready.", "success");
3699
3798
  await new Promise(() => {});
3700
3799
  };
3701
3800
 
@@ -51,6 +51,10 @@ export type DelayReadyConfig = {
51
51
  ms: number;
52
52
  };
53
53
  export type ServiceReadyConfig = false | string | HttpReadyConfig | TcpReadyConfig | CommandReadyConfig | DelayReadyConfig;
54
+ export type ServiceShutdownConfig = false | string[] | {
55
+ command: string[];
56
+ timeoutMs?: number;
57
+ };
54
58
  export type ServiceVisibility = 'public' | 'internal';
55
59
  export type BaseBuildConfig = {
56
60
  buildDirectory?: string;
@@ -96,6 +100,7 @@ export type AbsoluteServiceConfig = BaseBuildConfig & {
96
100
  entry?: string;
97
101
  env?: Record<string, string>;
98
102
  ready?: ServiceReadyConfig;
103
+ shutdown?: ServiceShutdownConfig;
99
104
  port?: number;
100
105
  visibility?: ServiceVisibility;
101
106
  command?: never;
@@ -107,6 +112,7 @@ export type CommandServiceConfig = {
107
112
  dependsOn?: string[];
108
113
  env?: Record<string, string>;
109
114
  ready?: ServiceReadyConfig;
115
+ shutdown?: ServiceShutdownConfig;
110
116
  port?: number;
111
117
  visibility?: ServiceVisibility;
112
118
  entry?: never;
package/package.json CHANGED
@@ -302,5 +302,5 @@
302
302
  "typecheck": "bun run src/cli/index.ts typecheck --config example/absolute.config.ts"
303
303
  },
304
304
  "types": "./dist/src/index.d.ts",
305
- "version": "0.19.0-beta.681"
305
+ "version": "0.19.0-beta.683"
306
306
  }