@ts-cloud/core 0.4.1 → 0.5.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.
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from './presets/traditional-web-app';
29
29
  export * from './presets/laravel';
30
30
  export * from './presets/serverless-laravel';
31
31
  export * from './presets/dashboard';
32
+ export * from './presets/management-dashboard';
32
33
  export * from './presets/extend';
33
34
  export * from './aws/signature';
34
35
  export * from './aws/credentials';
package/dist/index.js CHANGED
@@ -22307,6 +22307,57 @@ function createDashboardSite(options) {
22307
22307
  }
22308
22308
  };
22309
22309
  }
22310
+ // src/presets/management-dashboard.ts
22311
+ function apexOf(domain) {
22312
+ const parts = domain.split(".").filter(Boolean);
22313
+ return parts.length <= 2 ? domain : parts.slice(-2).join(".");
22314
+ }
22315
+ function resolveDashboardDomain(config, environment, explicit) {
22316
+ if (explicit)
22317
+ return explicit;
22318
+ const candidates = [];
22319
+ for (const site of Object.values(config.sites ?? {})) {
22320
+ const d = site?.domain;
22321
+ if (typeof d === "string")
22322
+ candidates.push(d);
22323
+ else if (Array.isArray(d))
22324
+ candidates.push(...d.filter((x) => typeof x === "string"));
22325
+ }
22326
+ if (environment && config.environments?.[environment]?.domain)
22327
+ candidates.push(config.environments[environment].domain);
22328
+ const dnsDomain = config.infrastructure?.dns?.domain;
22329
+ if (dnsDomain)
22330
+ candidates.push(dnsDomain);
22331
+ const base = candidates.find((d) => d && !d.startsWith("dashboard."));
22332
+ if (!base)
22333
+ return null;
22334
+ return `dashboard.${apexOf(base)}`;
22335
+ }
22336
+ function hasManagementDashboardSite(config) {
22337
+ return Object.entries(config.sites ?? {}).some(([name, site]) => {
22338
+ if (!site)
22339
+ return false;
22340
+ const root = site.root ?? "";
22341
+ return name === "dashboard" || root === "ui/dist" || root.endsWith("/ui/dist") || root.endsWith("/dist/ui");
22342
+ });
22343
+ }
22344
+ function resolveManagementDashboardSite(config, environment, opts) {
22345
+ if (hasManagementDashboardSite(config))
22346
+ return null;
22347
+ const domain = resolveDashboardDomain(config, environment, opts.domain);
22348
+ if (!domain)
22349
+ return null;
22350
+ const site = {
22351
+ root: opts.uiRoot,
22352
+ deploy: "server",
22353
+ type: "static",
22354
+ domain,
22355
+ ssl: { provider: "letsencrypt" },
22356
+ ...opts.build === false || opts.build === undefined ? {} : { build: opts.build },
22357
+ ...opts.password ? { auth: { username: opts.username || "admin", password: opts.password, realm: opts.realm } } : {}
22358
+ };
22359
+ return { name: "dashboard", site };
22360
+ }
22310
22361
  // src/presets/extend.ts
22311
22362
  function deepMerge(target, source) {
22312
22363
  const result = { ...target };
@@ -45909,6 +45960,7 @@ function composeServerlessAppTemplate(opts) {
45909
45960
  var PHP_LAYER_EXTENSIONS = [
45910
45961
  "cli",
45911
45962
  "fpm",
45963
+ "curl",
45912
45964
  "mbstring",
45913
45965
  "xml",
45914
45966
  "pdo",
@@ -45931,13 +45983,18 @@ function phpLayerBuildStage(phpVersion, asName) {
45931
45983
  const scl = `php${phpVersion.replace(".", "")}`;
45932
45984
  const packages = phpLayerPackages(phpVersion).join(" \\\n ");
45933
45985
  const sclRoot = `/opt/remi/${scl}/root`;
45934
- const from = asName ? `FROM amazonlinux:2023 AS ${asName}` : "FROM amazonlinux:2023";
45986
+ const sclEtc = `/etc/opt/remi/${scl}`;
45987
+ const from = asName ? `FROM almalinux:9 AS ${asName}` : "FROM almalinux:9";
45935
45988
  return `${from}
45936
45989
 
45937
- # Remi provides version-isolated PHP SCL packages for EL9 (AL2023 compatible).
45938
- RUN dnf -y install dnf-plugins-core 'dnf-command(config-manager)' && \\
45990
+ # Build PHP on AlmaLinux 9 (where Remi's version-isolated SCL packages install
45991
+ # cleanly Remi's release RPM rejects Amazon Linux 2023). AlmaLinux 9 shares
45992
+ # glibc 2.34 with the provided.al2023 Lambda runtime, and the relocation step
45993
+ # below bundles every non-glibc shared lib under /opt/php/lib, so the resulting
45994
+ # /opt tree runs on Lambda's provided.al2023.
45995
+ RUN dnf -y install epel-release dnf-plugins-core 'dnf-command(config-manager)' && \\
45996
+ dnf config-manager --set-enabled crb && \\
45939
45997
  dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm && \\
45940
- dnf -y update && \\
45941
45998
  dnf -y install \\
45942
45999
  ${packages} \\
45943
46000
  findutils tar gzip && \\
@@ -45950,7 +46007,10 @@ RUN set -eux; \\
45950
46007
  cp ${sclRoot}/usr/sbin/php-fpm /opt/php/sbin/php-fpm; \\
45951
46008
  EXT_DIR="$(${sclRoot}/usr/bin/php -r 'echo ini_get("extension_dir");')"; \\
45952
46009
  cp -a "$EXT_DIR"/*.so /opt/php/lib/php/modules/ || true; \\
45953
- cp -a ${sclRoot}/etc/php.d/*.ini /opt/php/etc/php.d/ 2>/dev/null || true; \\
46010
+ cp -a ${sclEtc}/php.d/*.ini /opt/php/etc/php.d/ 2>/dev/null || true; \\
46011
+ # Rewrite any absolute extension/zend_extension paths to bare names so they
46012
+ # resolve against the relocated extension_dir.
46013
+ sed -i -E "s#(zend_)?extension *= *.*/([^/]+\\.so)#\\1extension=\\2#" /opt/php/etc/php.d/*.ini 2>/dev/null || true; \\
45954
46014
  # Copy shared-library dependencies of php, php-fpm, and the extension modules.
45955
46015
  for bin in /opt/php/bin/php /opt/php/sbin/php-fpm /opt/php/lib/php/modules/*.so; do \\
45956
46016
  ldd "$bin" 2>/dev/null | awk '/=>/{print $3}/ld-linux/{print $1}' | sort -u | while read -r lib; do \\
@@ -45958,10 +46018,9 @@ RUN set -eux; \\
45958
46018
  done; \\
45959
46019
  done
45960
46020
 
45961
- # Point PHP at the relocated config + extension dir.
46021
+ # Build the relocated main php.ini: extension_dir + every per-extension directive.
45962
46022
  RUN printf 'extension_dir=/opt/php/lib/php/modules\\n' > /opt/php/etc/php.ini && \\
45963
46023
  cat /opt/php/etc/php.d/*.ini >> /opt/php/etc/php.ini 2>/dev/null || true
45964
- ENV PHP_INI_SCAN_DIR=/opt/php/etc/php.d
45965
46024
 
45966
46025
  # Runtime assets (bootstrap, runtime loops, fpm config) are added by the build
45967
46026
  # orchestrator after the image is produced. Export /opt as the layer payload.
@@ -46278,7 +46337,7 @@ var LARAVEL_SERVERLESS_BUILD_STEPS = [
46278
46337
  ];
46279
46338
  // src/serverless-php/layer-build.ts
46280
46339
  import { execFileSync as execFileSync2 } from "node:child_process";
46281
- import { existsSync as existsSync5, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
46340
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
46282
46341
  import { tmpdir as tmpdir3 } from "node:os";
46283
46342
  import { join as join9, relative as relative2 } from "node:path";
46284
46343
  function* walk(dir) {
@@ -46300,16 +46359,17 @@ function buildPhpRuntimeLayerZip(options = {}) {
46300
46359
  writeFileSync5(join9(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
46301
46360
  step("Building PHP runtime image (docker)");
46302
46361
  execFileSync2("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
46303
- step("Extracting /opt from image");
46362
+ step("Extracting /opt/php from image");
46304
46363
  const cid = execFileSync2("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
46305
46364
  const optDir = join9(stage, "opt");
46365
+ mkdirSync2(optDir, { recursive: true });
46306
46366
  try {
46307
- execFileSync2("docker", ["cp", `${cid}:/opt/.`, optDir], { stdio: "inherit" });
46367
+ execFileSync2("docker", ["cp", `${cid}:/opt/php`, join9(optDir, "php")], { stdio: "inherit" });
46308
46368
  } finally {
46309
46369
  execFileSync2("docker", ["rm", cid], { stdio: "ignore" });
46310
46370
  }
46311
- if (!existsSync5(optDir))
46312
- throw new Error("layer build produced no /opt directory");
46371
+ if (!existsSync5(join9(optDir, "php", "bin", "php")))
46372
+ throw new Error("layer build produced no /opt/php/bin/php");
46313
46373
  const entries = [];
46314
46374
  for (const file of walk(optDir)) {
46315
46375
  const rel = relative2(optDir, file).replace(/\\/g, "/");
@@ -46901,7 +46961,9 @@ export {
46901
46961
  resolveRegion,
46902
46962
  resolveQueueNames,
46903
46963
  resolveProjectStackName,
46964
+ resolveManagementDashboardSite,
46904
46965
  resolveDeployBucketName,
46966
+ resolveDashboardDomain,
46905
46967
  resolveCredentials,
46906
46968
  resolveCloudProvider,
46907
46969
  resolveApp,
@@ -46955,6 +47017,7 @@ export {
46955
47017
  hashFile,
46956
47018
  hashDirectory,
46957
47019
  hashBuffer,
47020
+ hasManagementDashboardSite,
46958
47021
  guardDutyManager,
46959
47022
  globalResourceManager,
46960
47023
  getTimestamp,
@@ -0,0 +1,44 @@
1
+ import type { CloudConfig, EnvironmentType, SiteConfig } from '../types';
2
+ /**
3
+ * Auto-deployed management dashboard (the `@ts-cloud/ui` stx app — the Server +
4
+ * Serverless views). When a server is provisioned, ts-cloud injects this as a
5
+ * server-static site so the dashboard ships automatically with every box.
6
+ *
7
+ * It is served behind HTTP Basic auth (htpasswd) whose password comes from an
8
+ * environment value (`TS_CLOUD_UI_PASSWORD`). If that value is NOT set, the
9
+ * dashboard is served WITHOUT auth — no password is invented.
10
+ */
11
+ export interface ManagementDashboardOptions {
12
+ /** Directory shipped as the static site root (built UI, or source dir + build). */
13
+ uiRoot: string;
14
+ /** Build command producing {@link uiRoot}, or false when it is already built. */
15
+ build?: string | false;
16
+ /** Explicit domain (e.g. from `TS_CLOUD_UI_DOMAIN`); else derived. */
17
+ domain?: string;
18
+ /** Basic-auth username. @default 'admin' */
19
+ username?: string;
20
+ /**
21
+ * Basic-auth password. When empty/undefined the dashboard is served WITHOUT
22
+ * htpasswd (no default password is invented).
23
+ */
24
+ password?: string;
25
+ /** Browser auth realm. */
26
+ realm?: string;
27
+ }
28
+ /**
29
+ * Derive a dashboard hostname (`dashboard.<apex>`) from the project's configured
30
+ * domains. Prefers `explicit`, then any site domain, then the environment domain,
31
+ * then `infrastructure.dns.domain`. Returns null when nothing is available.
32
+ */
33
+ export declare function resolveDashboardDomain(config: Pick<CloudConfig, 'sites' | 'environments' | 'infrastructure'>, environment?: EnvironmentType, explicit?: string): string | null;
34
+ /** Does the config already define the management dashboard as a site? */
35
+ export declare function hasManagementDashboardSite(config: Pick<CloudConfig, 'sites'>): boolean;
36
+ /**
37
+ * Build the management-dashboard site to auto-inject on server deploys, or null
38
+ * when no domain can be resolved (the static-site model is domain-routed) or it
39
+ * is already configured.
40
+ */
41
+ export declare function resolveManagementDashboardSite(config: Pick<CloudConfig, 'sites' | 'environments' | 'infrastructure'>, environment: EnvironmentType, opts: ManagementDashboardOptions): {
42
+ name: string;
43
+ site: SiteConfig;
44
+ } | null;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Generates the Dockerfile that builds the ts-cloud PHP runtime layer for AWS
3
- * Lambda (`provided.al2023`). Built against Amazon Linux 2023 so glibc and the
4
- * shared libraries match the Lambda execution environment.
3
+ * Lambda (`provided.al2023`). Built on AlmaLinux 9 it shares glibc 2.34 with
4
+ * the Lambda runtime, and (unlike Amazon Linux 2023) the Remi repository's
5
+ * release RPM installs there, giving version-isolated PHP SCL packages
6
+ * (`php83-php-*` under `/opt/remi/php83/`) so the same recipe builds PHP 8.1–8.4.
5
7
  *
6
- * AL2023's default repos can't select an arbitrary PHP minor version, so we use
7
- * the Remi repository's SCL-style packages (`php83-php-*`, installed under
8
- * `/opt/remi/php83/`), which lets the same recipe build PHP 8.1–8.4. The PHP
9
- * binary, php-fpm, extensions, and their shared-library dependencies are then
10
- * relocated under `/opt` so the archive works as a Lambda layer.
8
+ * The PHP binary, php-fpm, extensions, and all their non-glibc shared-library
9
+ * dependencies are relocated + bundled under `/opt` so the archive runs as a
10
+ * provided.al2023 Lambda layer.
11
11
  *
12
12
  * Build it in CI (Docker required) — see {@link buildPhpRuntimeLayerZip}. The
13
13
  * resulting /opt tree is published as a Lambda layer.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Core CloudFormation generation library for ts-cloud",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@ts-cloud/aws-types": "0.4.1"
34
+ "@ts-cloud/aws-types": "0.5.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^5.9.3"