@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
|
|
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
|
|
45938
|
-
|
|
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 ${
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
|
4
|
-
*
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
|
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.
|
|
34
|
+
"@ts-cloud/aws-types": "0.5.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"typescript": "^5.9.3"
|