@stacksjs/ts-cloud 0.5.31 → 0.5.32

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,50 @@
1
+ /**
2
+ * App-framework drivers.
3
+ *
4
+ * ts-cloud runs a site's background work — the scheduler, queue workers, and
5
+ * daemons — as systemd units / cron on the box. HOW those are invoked differs
6
+ * per app framework: Stacks (Bun, `buddy …`) vs Laravel (PHP, `artisan …`), and
7
+ * the runtime environment a systemd/cron process needs differs too (bun on PATH
8
+ * vs pantry's php env). This module isolates those differences behind one small
9
+ * driver so the reconciler (`app-services.ts`) stays framework-agnostic.
10
+ *
11
+ * Stacks-first: `getAppFrameworkDriver()` defaults to the Stacks driver.
12
+ */
13
+ import type { QueueWorkerConfig, SiteConfig } from '@ts-cloud/core';
14
+ export type FrameworkId = 'stacks' | 'laravel';
15
+ /**
16
+ * Resolve which framework driver a site uses. An explicit `framework` wins; a
17
+ * PHP-oriented `type` (laravel/php/statamic/wordpress) implies Laravel for
18
+ * backward compatibility; everything else uses the Stacks-first default.
19
+ */
20
+ export declare function resolveSiteFramework(site: Pick<SiteConfig, 'framework' | 'type'>): FrameworkId;
21
+ export interface FrameworkExecContext {
22
+ /** The active-release directory (`/var/www/<site>/current`). */
23
+ current: string;
24
+ }
25
+ export interface AppFrameworkDriver {
26
+ readonly id: FrameworkId;
27
+ /**
28
+ * Wrap a bare command so a systemd unit / cron entry runs it with the
29
+ * framework's runtime on PATH (systemd units inherit no shell env).
30
+ */
31
+ wrapExec: (command: string) => string;
32
+ /**
33
+ * The command cron runs every minute to fire due scheduled tasks. Includes
34
+ * its own env setup + output redirection; the reconciler may append a
35
+ * heartbeat ping with `&&`.
36
+ */
37
+ schedulerCommand: (ctx: FrameworkExecContext) => string;
38
+ /** `ExecStart` for a single queue-worker process. */
39
+ queueWorkerCommand: (worker: QueueWorkerConfig, ctx: FrameworkExecContext) => string;
40
+ }
41
+ /**
42
+ * Stacks (Bun) — the default. The scheduler is cron-driven (`buddy schedule:run`
43
+ * is one-shot); queue workers are long-running (`buddy queue:work`). bun lives
44
+ * at an absolute path installed by the box bootstrap.
45
+ */
46
+ export declare const stacksDriver: AppFrameworkDriver;
47
+ /** Laravel (PHP / Artisan) — the original Forge-style behavior, now a driver. */
48
+ export declare const laravelDriver: AppFrameworkDriver;
49
+ /** Resolve the app-framework driver for a site. Stacks-first default. */
50
+ export declare function getAppFrameworkDriver(framework?: FrameworkId): AppFrameworkDriver;
@@ -1,8 +1,12 @@
1
1
  /**
2
- * Generate the per-site runtime services for a Forge-style PHP box: queue
3
- * workers / Horizon (systemd), the Laravel scheduler (cron), and arbitrary
4
- * daemons (systemd). Reconciled on every deploy so units track the config —
5
- * units no longer in the config are stopped and removed.
2
+ * Generate the per-site runtime services for a server-app box: queue workers
3
+ * (systemd), the app scheduler (cron), and arbitrary daemons (systemd).
4
+ * Reconciled on every deploy so units track the config — units no longer in the
5
+ * config are stopped and removed.
6
+ *
7
+ * HOW the scheduler + queue workers are invoked (Stacks/Bun vs Laravel/PHP) is
8
+ * delegated to an {@link AppFrameworkDriver} selected by `site.framework`
9
+ * (Stacks-first default). This file only owns unit/cron plumbing + pruning.
6
10
  *
7
11
  * Unit naming (so a site's units can be globbed for pruning):
8
12
  * <slug>-<site>-queue-<i>.service
@@ -13,18 +17,21 @@
13
17
  * so workers/daemons always run the live code; `queue:restart` (run by the
14
18
  * deploy's $RESTART_QUEUES macro) cycles them onto the new release.
15
19
  */
16
- import type { DaemonConfig, SiteConfig } from '@ts-cloud/core';
20
+ import type { SiteConfig } from '@ts-cloud/core';
17
21
  export interface SiteServicesOptions {
18
22
  slug: string;
19
23
  siteName: string;
20
24
  site: SiteConfig;
21
- /** PHP version selecting the `phpX.Y` binary. @default '8.3' */
25
+ /** PHP version selecting the `phpX.Y` binary (Laravel only). @default '8.3' */
22
26
  phpVersion?: string;
23
27
  /** Site base dir. @default `/var/www/<siteName>` */
24
28
  appBase?: string;
25
29
  }
26
30
  export declare function queueUnitName(slug: string, siteName: string, index: number): string;
27
- export declare function daemonUnitName(slug: string, siteName: string, daemon: DaemonConfig, index: number): string;
31
+ export declare function daemonUnitName(slug: string, siteName: string, daemon: {
32
+ name?: string;
33
+ command: string;
34
+ }, index: number): string;
28
35
  /** Path of the scheduler cron file for a site. */
29
36
  export declare function schedulerCronPath(slug: string, siteName: string): string;
30
37
  /**
package/dist/index.js CHANGED
@@ -87729,11 +87729,67 @@ function buildLaravelDeployScript(options) {
87729
87729
  return out;
87730
87730
  }
87731
87731
 
87732
- // src/drivers/shared/laravel-services.ts
87732
+ // src/drivers/shared/app-frameworks.ts
87733
+ var PHP_SITE_TYPES2 = new Set(["laravel", "php", "statamic", "wordpress"]);
87734
+ function resolveSiteFramework(site) {
87735
+ if (site.framework)
87736
+ return site.framework;
87737
+ if (site.type && PHP_SITE_TYPES2.has(site.type))
87738
+ return "laravel";
87739
+ return "stacks";
87740
+ }
87733
87741
  var PANTRY_ENV_EVAL = `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
87734
- function pantryExec(cmd) {
87735
- return `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${cmd}'`;
87742
+ var BUN_BIN = "/usr/local/bin/bun";
87743
+ var STACKS_CLI = "storage/framework/core/buddy/src/cli.ts";
87744
+ function bunEnvWrap(command) {
87745
+ return `/bin/sh -lc 'export PATH="/usr/local/bin:$PATH"; export BUN_INSTALL="/root/.bun"; exec ${command}'`;
87746
+ }
87747
+ var stacksDriver = {
87748
+ id: "stacks",
87749
+ wrapExec: bunEnvWrap,
87750
+ schedulerCommand: ({ current }) => `cd ${current} && ${BUN_BIN} ${STACKS_CLI} schedule:run >> /dev/null 2>&1`,
87751
+ queueWorkerCommand: (worker, { current }) => {
87752
+ const flags = [
87753
+ `--queue=${worker.queue || "default"}`,
87754
+ `--sleep=${worker.sleep ?? 3}`,
87755
+ `--tries=${worker.tries ?? 3}`,
87756
+ `--timeout=${worker.timeout ?? 60}`
87757
+ ];
87758
+ return `${BUN_BIN} ${current}/${STACKS_CLI} queue:work ${flags.join(" ")}`;
87759
+ }
87760
+ };
87761
+ var laravelDriver = {
87762
+ id: "laravel",
87763
+ wrapExec: (command) => `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${command}'`,
87764
+ schedulerCommand: ({ current }) => `cd ${current} && ${PANTRY_ENV_EVAL} && php artisan schedule:run >> /dev/null 2>&1`,
87765
+ queueWorkerCommand: (worker, { current }) => {
87766
+ const artisan = `${current}/artisan`;
87767
+ if (worker.horizon)
87768
+ return `php ${artisan} horizon`;
87769
+ const flags = [
87770
+ worker.connection || "default",
87771
+ `--queue=${worker.queue || "default"}`,
87772
+ `--sleep=${worker.sleep ?? 3}`,
87773
+ `--tries=${worker.tries ?? 3}`,
87774
+ `--timeout=${worker.timeout ?? 60}`,
87775
+ `--memory=${worker.memory ?? 128}`
87776
+ ];
87777
+ if (worker.maxJobs)
87778
+ flags.push(`--max-jobs=${worker.maxJobs}`);
87779
+ if (worker.maxTime)
87780
+ flags.push(`--max-time=${worker.maxTime}`);
87781
+ return `php ${artisan} queue:work ${flags.join(" ")}`;
87782
+ }
87783
+ };
87784
+ var DRIVERS = {
87785
+ stacks: stacksDriver,
87786
+ laravel: laravelDriver
87787
+ };
87788
+ function getAppFrameworkDriver(framework) {
87789
+ return DRIVERS[framework] ?? stacksDriver;
87736
87790
  }
87791
+
87792
+ // src/drivers/shared/app-services.ts
87737
87793
  function reEscape(value) {
87738
87794
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
87739
87795
  }
@@ -87749,23 +87805,6 @@ function queueUnitName(slug, siteName, index) {
87749
87805
  function daemonUnitName(slug, siteName, daemon, index) {
87750
87806
  return `${slug}-${siteName}-daemon-${daemon.name ? slugify(daemon.name) : slugify(daemon.command).slice(0, 32) || String(index)}`;
87751
87807
  }
87752
- function queueExecStart(worker, phpBin, artisan) {
87753
- if (worker.horizon)
87754
- return `${phpBin} ${artisan} horizon`;
87755
- const flags = [
87756
- worker.connection || "default",
87757
- `--queue=${worker.queue || "default"}`,
87758
- `--sleep=${worker.sleep ?? 3}`,
87759
- `--tries=${worker.tries ?? 3}`,
87760
- `--timeout=${worker.timeout ?? 60}`,
87761
- `--memory=${worker.memory ?? 128}`
87762
- ];
87763
- if (worker.maxJobs)
87764
- flags.push(`--max-jobs=${worker.maxJobs}`);
87765
- if (worker.maxTime)
87766
- flags.push(`--max-time=${worker.maxTime}`);
87767
- return `${phpBin} ${artisan} queue:work ${flags.join(" ")}`;
87768
- }
87769
87808
  function systemdUnit(opts) {
87770
87809
  const lines = [
87771
87810
  "[Unit]",
@@ -87800,10 +87839,10 @@ function schedulerCronPath(slug, siteName) {
87800
87839
  }
87801
87840
  function buildSiteServicesScript(options) {
87802
87841
  const { slug, siteName, site } = options;
87803
- const phpBin = "php";
87804
87842
  const base = options.appBase ?? `/var/www/${siteName}`;
87805
87843
  const current = `${base}/current`;
87806
- const artisan = `${current}/artisan`;
87844
+ const driver = getAppFrameworkDriver(resolveSiteFramework(site));
87845
+ const ctx = { current };
87807
87846
  const out = [];
87808
87847
  const desiredUnits = [];
87809
87848
  const queues = site.queues || [];
@@ -87815,7 +87854,7 @@ function buildSiteServicesScript(options) {
87815
87854
  out.push(...writeUnitScript(name, systemdUnit({
87816
87855
  description: `${siteName} queue worker ${qIndex}.${p} (managed by ts-cloud)`,
87817
87856
  workingDir: current,
87818
- execStart: pantryExec(queueExecStart(worker, phpBin, artisan)),
87857
+ execStart: driver.wrapExec(driver.queueWorkerCommand(worker, ctx)),
87819
87858
  stopWaitSecs: worker.stopWaitSecs ?? 90
87820
87859
  })));
87821
87860
  }
@@ -87829,7 +87868,7 @@ function buildSiteServicesScript(options) {
87829
87868
  out.push(...writeUnitScript(name, systemdUnit({
87830
87869
  description: `${siteName} daemon ${daemon.name || daemon.command} (managed by ts-cloud)`,
87831
87870
  workingDir: daemon.directory || current,
87832
- execStart: pantryExec(daemon.command),
87871
+ execStart: driver.wrapExec(daemon.command),
87833
87872
  restart: daemon.restart,
87834
87873
  user: daemon.user
87835
87874
  })));
@@ -87845,7 +87884,7 @@ function buildSiteServicesScript(options) {
87845
87884
  const schedulerEnabled = scheduler2 === true || typeof scheduler2 === "object" && scheduler2 !== null;
87846
87885
  if (schedulerEnabled) {
87847
87886
  const heartbeat = typeof scheduler2 === "object" && scheduler2 !== null ? scheduler2 : undefined;
87848
- let command = `cd ${current} && ${PANTRY_ENV_EVAL} && ${phpBin} artisan schedule:run >> /dev/null 2>&1`;
87887
+ let command = driver.schedulerCommand(ctx);
87849
87888
  if (heartbeat?.heartbeatUrl) {
87850
87889
  const method = heartbeat.heartbeatMethod || "GET";
87851
87890
  const methodFlag = method === "GET" ? "" : `-X ${method} `;