@stacksjs/ts-cloud 0.2.21 → 0.2.23
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/aws/s3.d.ts +17 -0
- package/dist/bin/cli.js +625 -566
- package/dist/deploy/index.d.ts +1 -0
- package/dist/deploy/site-target.d.ts +41 -0
- package/dist/drivers/hetzner/client.d.ts +5 -0
- package/dist/drivers/hetzner/driver.d.ts +56 -0
- package/dist/drivers/index.d.ts +2 -2
- package/dist/drivers/shared/caddyfile.d.ts +41 -1
- package/dist/drivers/shared/compute-deploy.d.ts +3 -1
- package/dist/drivers/shared/deploy-script.d.ts +20 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +643 -50
- package/dist/object-storage/index.d.ts +1 -0
- package/dist/object-storage/migrate.d.ts +125 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2123,6 +2123,67 @@ class S3Client2 {
|
|
|
2123
2123
|
});
|
|
2124
2124
|
return result;
|
|
2125
2125
|
}
|
|
2126
|
+
async getObjectBytes(bucket, key) {
|
|
2127
|
+
const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
|
|
2128
|
+
const host = this.s3VirtualHost(bucket);
|
|
2129
|
+
const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
|
|
2130
|
+
const canonicalUri = this.forcePathStyle ? `/${bucket}/${encodedKey}` : `/${encodedKey}`;
|
|
2131
|
+
const url = `https://${host}${canonicalUri}`;
|
|
2132
|
+
const now = new Date;
|
|
2133
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
2134
|
+
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
2135
|
+
const payloadHash = crypto3.createHash("sha256").update("").digest("hex");
|
|
2136
|
+
const requestHeaders = {
|
|
2137
|
+
host,
|
|
2138
|
+
"x-amz-date": amzDate,
|
|
2139
|
+
"x-amz-content-sha256": payloadHash
|
|
2140
|
+
};
|
|
2141
|
+
if (sessionToken) {
|
|
2142
|
+
requestHeaders["x-amz-security-token"] = sessionToken;
|
|
2143
|
+
}
|
|
2144
|
+
const canonicalHeaders = Object.keys(requestHeaders).sort().map((k) => `${k.toLowerCase()}:${requestHeaders[k].trim()}
|
|
2145
|
+
`).join("");
|
|
2146
|
+
const signedHeaders = Object.keys(requestHeaders).sort().map((k) => k.toLowerCase()).join(";");
|
|
2147
|
+
const canonicalRequest = [
|
|
2148
|
+
"GET",
|
|
2149
|
+
canonicalUri,
|
|
2150
|
+
"",
|
|
2151
|
+
canonicalHeaders,
|
|
2152
|
+
signedHeaders,
|
|
2153
|
+
payloadHash
|
|
2154
|
+
].join(`
|
|
2155
|
+
`);
|
|
2156
|
+
const algorithm = "AWS4-HMAC-SHA256";
|
|
2157
|
+
const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
|
|
2158
|
+
const stringToSign = [
|
|
2159
|
+
algorithm,
|
|
2160
|
+
amzDate,
|
|
2161
|
+
credentialScope,
|
|
2162
|
+
crypto3.createHash("sha256").update(canonicalRequest).digest("hex")
|
|
2163
|
+
].join(`
|
|
2164
|
+
`);
|
|
2165
|
+
const kDate = crypto3.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
|
|
2166
|
+
const kRegion = crypto3.createHmac("sha256", kDate).update(this.region).digest();
|
|
2167
|
+
const kService = crypto3.createHmac("sha256", kRegion).update("s3").digest();
|
|
2168
|
+
const kSigning = crypto3.createHmac("sha256", kService).update("aws4_request").digest();
|
|
2169
|
+
const signature = crypto3.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
|
|
2170
|
+
const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
2171
|
+
const response = await fetch(url, {
|
|
2172
|
+
method: "GET",
|
|
2173
|
+
headers: { ...requestHeaders, Authorization: authorizationHeader }
|
|
2174
|
+
});
|
|
2175
|
+
if (!response.ok) {
|
|
2176
|
+
const errorText = await response.text();
|
|
2177
|
+
throw new Error(`S3 GET failed: ${response.status} ${errorText}`);
|
|
2178
|
+
}
|
|
2179
|
+
const buffer = await response.arrayBuffer();
|
|
2180
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
2181
|
+
return {
|
|
2182
|
+
body: new Uint8Array(buffer),
|
|
2183
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
2184
|
+
contentLength: contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : undefined
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2126
2187
|
async copyObject(options) {
|
|
2127
2188
|
const headers = {
|
|
2128
2189
|
"x-amz-copy-source": `/${options.sourceBucket}/${options.sourceKey}`
|
|
@@ -65570,6 +65631,165 @@ init_client();
|
|
|
65570
65631
|
|
|
65571
65632
|
// src/object-storage/index.ts
|
|
65572
65633
|
init_s3();
|
|
65634
|
+
|
|
65635
|
+
// src/object-storage/migrate.ts
|
|
65636
|
+
function stripPrefix(key, prefix) {
|
|
65637
|
+
if (!prefix)
|
|
65638
|
+
return key;
|
|
65639
|
+
return key.startsWith(prefix) ? key.slice(prefix.length) : key;
|
|
65640
|
+
}
|
|
65641
|
+
function remapKey(sourceKey, fromPrefix, toPrefix) {
|
|
65642
|
+
const stripped = stripPrefix(sourceKey, fromPrefix);
|
|
65643
|
+
return `${toPrefix ?? ""}${stripped}`;
|
|
65644
|
+
}
|
|
65645
|
+
function keyMatchesFilters(key, include, exclude) {
|
|
65646
|
+
if (exclude && exclude.some((p) => key.startsWith(p)))
|
|
65647
|
+
return false;
|
|
65648
|
+
if (include && include.length > 0)
|
|
65649
|
+
return include.some((p) => key.startsWith(p));
|
|
65650
|
+
return true;
|
|
65651
|
+
}
|
|
65652
|
+
async function mapWithConcurrency(items, limit, worker) {
|
|
65653
|
+
let cursor = 0;
|
|
65654
|
+
const runners = [];
|
|
65655
|
+
const size = Math.max(1, Math.min(limit, items.length || 1));
|
|
65656
|
+
for (let i = 0;i < size; i++) {
|
|
65657
|
+
runners.push((async () => {
|
|
65658
|
+
while (true) {
|
|
65659
|
+
const index = cursor++;
|
|
65660
|
+
if (index >= items.length)
|
|
65661
|
+
return;
|
|
65662
|
+
await worker(items[index], index);
|
|
65663
|
+
}
|
|
65664
|
+
})());
|
|
65665
|
+
}
|
|
65666
|
+
await Promise.all(runners);
|
|
65667
|
+
}
|
|
65668
|
+
function clientFor(endpoint) {
|
|
65669
|
+
if (endpoint.client)
|
|
65670
|
+
return endpoint.client;
|
|
65671
|
+
return createObjectStorageClient({
|
|
65672
|
+
provider: endpoint.provider,
|
|
65673
|
+
region: endpoint.region,
|
|
65674
|
+
endpoint: endpoint.endpoint,
|
|
65675
|
+
forcePathStyle: endpoint.forcePathStyle,
|
|
65676
|
+
credentials: endpoint.credentials
|
|
65677
|
+
});
|
|
65678
|
+
}
|
|
65679
|
+
async function migrateObjectStorage(options) {
|
|
65680
|
+
const concurrency = options.concurrency ?? 8;
|
|
65681
|
+
const fromClient = clientFor(options.from);
|
|
65682
|
+
const toClient = clientFor(options.to);
|
|
65683
|
+
const sourceObjects = await fromClient.listAllObjects({
|
|
65684
|
+
bucket: options.from.bucket,
|
|
65685
|
+
prefix: options.from.prefix
|
|
65686
|
+
});
|
|
65687
|
+
const result = {
|
|
65688
|
+
copied: 0,
|
|
65689
|
+
skipped: 0,
|
|
65690
|
+
excluded: 0,
|
|
65691
|
+
bytesCopied: 0,
|
|
65692
|
+
errors: [],
|
|
65693
|
+
excludedKeys: [],
|
|
65694
|
+
deleted: []
|
|
65695
|
+
};
|
|
65696
|
+
const toCopy = [];
|
|
65697
|
+
for (const obj of sourceObjects) {
|
|
65698
|
+
if (!keyMatchesFilters(obj.Key, options.include, options.exclude)) {
|
|
65699
|
+
result.excluded++;
|
|
65700
|
+
result.excludedKeys.push(obj.Key);
|
|
65701
|
+
continue;
|
|
65702
|
+
}
|
|
65703
|
+
toCopy.push({ source: obj, destKey: remapKey(obj.Key, options.from.prefix, options.to.prefix) });
|
|
65704
|
+
}
|
|
65705
|
+
if (options.dryRun) {
|
|
65706
|
+
result.plan = toCopy.map(({ source, destKey }) => ({ key: source.Key, destKey, size: source.Size }));
|
|
65707
|
+
const total2 = sourceObjects.length;
|
|
65708
|
+
let index = 0;
|
|
65709
|
+
for (const obj of sourceObjects) {
|
|
65710
|
+
index++;
|
|
65711
|
+
const excluded = !keyMatchesFilters(obj.Key, options.include, options.exclude);
|
|
65712
|
+
options.onProgress?.({
|
|
65713
|
+
key: obj.Key,
|
|
65714
|
+
destKey: excluded ? "" : remapKey(obj.Key, options.from.prefix, options.to.prefix),
|
|
65715
|
+
size: obj.Size,
|
|
65716
|
+
action: excluded ? "excluded" : "planned",
|
|
65717
|
+
index,
|
|
65718
|
+
total: total2
|
|
65719
|
+
});
|
|
65720
|
+
}
|
|
65721
|
+
return result;
|
|
65722
|
+
}
|
|
65723
|
+
const total = toCopy.length;
|
|
65724
|
+
let processed = 0;
|
|
65725
|
+
await mapWithConcurrency(toCopy, concurrency, async ({ source, destKey }) => {
|
|
65726
|
+
const myIndex = ++processed;
|
|
65727
|
+
try {
|
|
65728
|
+
if (!options.force) {
|
|
65729
|
+
const head = await toClient.headObject(options.to.bucket, destKey);
|
|
65730
|
+
if (head && head.ContentLength === source.Size) {
|
|
65731
|
+
result.skipped++;
|
|
65732
|
+
options.onProgress?.({ key: source.Key, destKey, size: source.Size, action: "skipped", index: myIndex, total });
|
|
65733
|
+
return;
|
|
65734
|
+
}
|
|
65735
|
+
}
|
|
65736
|
+
const { body, contentType } = await fromClient.getObjectBytes(options.from.bucket, source.Key);
|
|
65737
|
+
await toClient.putObject({
|
|
65738
|
+
bucket: options.to.bucket,
|
|
65739
|
+
key: destKey,
|
|
65740
|
+
body,
|
|
65741
|
+
contentType
|
|
65742
|
+
});
|
|
65743
|
+
result.copied++;
|
|
65744
|
+
result.bytesCopied += body.byteLength;
|
|
65745
|
+
options.onProgress?.({ key: source.Key, destKey, size: source.Size, action: "copied", index: myIndex, total });
|
|
65746
|
+
} catch (err) {
|
|
65747
|
+
result.errors.push({ key: source.Key, message: err?.message ?? String(err) });
|
|
65748
|
+
options.onProgress?.({ key: source.Key, destKey, size: source.Size, action: "error", index: myIndex, total });
|
|
65749
|
+
}
|
|
65750
|
+
});
|
|
65751
|
+
const copiedDestKeys = new Set(toCopy.map((c) => c.destKey));
|
|
65752
|
+
if (options.deleteExtraneous) {
|
|
65753
|
+
const destObjects = await toClient.listAllObjects({ bucket: options.to.bucket, prefix: options.to.prefix });
|
|
65754
|
+
const extraneous = destObjects.filter((o) => !copiedDestKeys.has(o.Key)).map((o) => o.Key);
|
|
65755
|
+
for (const key of extraneous) {
|
|
65756
|
+
try {
|
|
65757
|
+
await toClient.deleteObject(options.to.bucket, key);
|
|
65758
|
+
result.deleted.push(key);
|
|
65759
|
+
} catch (err) {
|
|
65760
|
+
result.errors.push({ key, message: `delete failed: ${err?.message ?? String(err)}` });
|
|
65761
|
+
}
|
|
65762
|
+
}
|
|
65763
|
+
}
|
|
65764
|
+
if (options.verify) {
|
|
65765
|
+
const destObjects = await toClient.listAllObjects({ bucket: options.to.bucket, prefix: options.to.prefix });
|
|
65766
|
+
const destBySizeKey = new Map(destObjects.map((o) => [o.Key, o.Size]));
|
|
65767
|
+
const missing = [];
|
|
65768
|
+
const sizeMismatches = [];
|
|
65769
|
+
let matched = 0;
|
|
65770
|
+
for (const { source, destKey } of toCopy) {
|
|
65771
|
+
if (!destBySizeKey.has(destKey)) {
|
|
65772
|
+
missing.push(destKey);
|
|
65773
|
+
continue;
|
|
65774
|
+
}
|
|
65775
|
+
const actual = destBySizeKey.get(destKey);
|
|
65776
|
+
if (actual !== source.Size) {
|
|
65777
|
+
sizeMismatches.push({ key: destKey, expected: source.Size, actual });
|
|
65778
|
+
continue;
|
|
65779
|
+
}
|
|
65780
|
+
matched++;
|
|
65781
|
+
}
|
|
65782
|
+
result.verification = {
|
|
65783
|
+
ok: missing.length === 0 && sizeMismatches.length === 0,
|
|
65784
|
+
matched,
|
|
65785
|
+
missing,
|
|
65786
|
+
sizeMismatches
|
|
65787
|
+
};
|
|
65788
|
+
}
|
|
65789
|
+
return result;
|
|
65790
|
+
}
|
|
65791
|
+
|
|
65792
|
+
// src/object-storage/index.ts
|
|
65573
65793
|
var DEFAULT_REGION = {
|
|
65574
65794
|
aws: "us-east-1",
|
|
65575
65795
|
backblaze: "us-west-004",
|
|
@@ -80931,6 +81151,77 @@ class AcmeClient {
|
|
|
80931
81151
|
return this.accountKey;
|
|
80932
81152
|
}
|
|
80933
81153
|
}
|
|
81154
|
+
// src/deploy/site-target.ts
|
|
81155
|
+
function resolveSiteDeployTarget(site) {
|
|
81156
|
+
if (site.deploy)
|
|
81157
|
+
return site.deploy;
|
|
81158
|
+
if (site.start)
|
|
81159
|
+
return "server";
|
|
81160
|
+
return "bucket";
|
|
81161
|
+
}
|
|
81162
|
+
function resolveSiteKind(site) {
|
|
81163
|
+
const target = resolveSiteDeployTarget(site);
|
|
81164
|
+
if (target === "bucket")
|
|
81165
|
+
return "bucket";
|
|
81166
|
+
return site.start ? "server-app" : "server-static";
|
|
81167
|
+
}
|
|
81168
|
+
function hasComputeConfigured(config6) {
|
|
81169
|
+
return config6.infrastructure?.compute != null;
|
|
81170
|
+
}
|
|
81171
|
+
function validateDeploymentConfig(config6) {
|
|
81172
|
+
const errors = [];
|
|
81173
|
+
const warnings = [];
|
|
81174
|
+
const sites = config6.sites || {};
|
|
81175
|
+
const computeConfigured = hasComputeConfigured(config6);
|
|
81176
|
+
const portOwners = new Map;
|
|
81177
|
+
for (const [name, site] of Object.entries(sites)) {
|
|
81178
|
+
if (!site) {
|
|
81179
|
+
continue;
|
|
81180
|
+
}
|
|
81181
|
+
const target = resolveSiteDeployTarget(site);
|
|
81182
|
+
const kind = resolveSiteKind(site);
|
|
81183
|
+
if (target === "server" && !site.start && !site.root) {
|
|
81184
|
+
errors.push(`Site '${name}' sets deploy:'server' but declares neither \`start\` (dynamic app) nor \`root\` (static site to serve). Add one.`);
|
|
81185
|
+
continue;
|
|
81186
|
+
}
|
|
81187
|
+
if (kind === "server-app") {
|
|
81188
|
+
if (!computeConfigured) {
|
|
81189
|
+
errors.push(`Site '${name}' deploys to a server (deploy:'server'${site.deploy ? "" : " inferred from `start`"}) but no \`infrastructure.compute\` is configured. Set deploy:'bucket' or add a server (infrastructure.compute).`);
|
|
81190
|
+
}
|
|
81191
|
+
if (typeof site.port === "number") {
|
|
81192
|
+
const existing = portOwners.get(site.port);
|
|
81193
|
+
if (existing) {
|
|
81194
|
+
errors.push(`Sites '${existing}' and '${name}' both use port ${site.port}. Server apps sharing a box must use distinct ports.`);
|
|
81195
|
+
} else {
|
|
81196
|
+
portOwners.set(site.port, name);
|
|
81197
|
+
}
|
|
81198
|
+
}
|
|
81199
|
+
} else if (kind === "server-static") {
|
|
81200
|
+
if (!site.root) {
|
|
81201
|
+
errors.push(`Site '${name}' is a server static site (deploy:'server', no \`start\`) but has no \`root\` directory to serve.`);
|
|
81202
|
+
}
|
|
81203
|
+
if (!computeConfigured) {
|
|
81204
|
+
errors.push(`Site '${name}' deploys to a server (deploy:'server') but no \`infrastructure.compute\` is configured. Set deploy:'bucket' or add a server (infrastructure.compute).`);
|
|
81205
|
+
}
|
|
81206
|
+
} else {
|
|
81207
|
+
if (!site.root) {
|
|
81208
|
+
errors.push(`Site '${name}' deploys to a bucket but has no \`root\` directory to upload.`);
|
|
81209
|
+
}
|
|
81210
|
+
const serverOnly = [];
|
|
81211
|
+
if (site.start)
|
|
81212
|
+
serverOnly.push("start");
|
|
81213
|
+
if (typeof site.port === "number")
|
|
81214
|
+
serverOnly.push("port");
|
|
81215
|
+
if (site.preStart && site.preStart.length > 0)
|
|
81216
|
+
serverOnly.push("preStart");
|
|
81217
|
+
if (serverOnly.length > 0) {
|
|
81218
|
+
warnings.push(`Site '${name}' deploys to a bucket but sets server-only field(s): ${serverOnly.join(", ")}. These are ignored. Set deploy:'server' to use them.`);
|
|
81219
|
+
}
|
|
81220
|
+
}
|
|
81221
|
+
}
|
|
81222
|
+
return { errors, warnings };
|
|
81223
|
+
}
|
|
81224
|
+
|
|
80934
81225
|
// src/deploy/index.ts
|
|
80935
81226
|
init_static_site();
|
|
80936
81227
|
init_static_site_external_dns();
|
|
@@ -81159,46 +81450,202 @@ import { join as join13 } from "node:path";
|
|
|
81159
81450
|
import { execSync } from "node:child_process";
|
|
81160
81451
|
|
|
81161
81452
|
// src/drivers/shared/caddyfile.ts
|
|
81162
|
-
function
|
|
81163
|
-
|
|
81164
|
-
|
|
81165
|
-
|
|
81453
|
+
function isOnDemandDomain(domain) {
|
|
81454
|
+
return domain === "*" || domain.includes("*");
|
|
81455
|
+
}
|
|
81456
|
+
function isStaticApp(app) {
|
|
81457
|
+
return typeof app.root === "string" && app.root.length > 0;
|
|
81458
|
+
}
|
|
81459
|
+
function isProxyApp(app) {
|
|
81460
|
+
return typeof app.port === "number";
|
|
81461
|
+
}
|
|
81462
|
+
function staticSiteServerRoot(name) {
|
|
81463
|
+
return `/var/www/${name}`;
|
|
81464
|
+
}
|
|
81465
|
+
function normalizeOnDemandTls(onDemandTls) {
|
|
81466
|
+
if (!onDemandTls)
|
|
81166
81467
|
return;
|
|
81167
|
-
|
|
81168
|
-
|
|
81169
|
-
|
|
81170
|
-
|
|
81171
|
-
|
|
81468
|
+
if (onDemandTls === true)
|
|
81469
|
+
return {};
|
|
81470
|
+
return onDemandTls;
|
|
81471
|
+
}
|
|
81472
|
+
function wrapInHandle(body, path, indent) {
|
|
81473
|
+
const inner = body.split(`
|
|
81474
|
+
`).map((line) => ` ${line}`).join(`
|
|
81475
|
+
`);
|
|
81476
|
+
return `${indent}handle ${path} {
|
|
81477
|
+
${inner}
|
|
81478
|
+
${indent}}`;
|
|
81479
|
+
}
|
|
81480
|
+
function renderUpstreamBlock(app, indent) {
|
|
81481
|
+
const host = app.upstreamHost || "localhost";
|
|
81482
|
+
const upstream = `${host}:${app.port}`;
|
|
81483
|
+
const directives = app.reverseProxyDirectives ?? [];
|
|
81484
|
+
const proxyLine = directives.length === 0 ? `${indent}reverse_proxy ${upstream}` : [
|
|
81485
|
+
`${indent}reverse_proxy ${upstream} {`,
|
|
81486
|
+
...directives.map((d) => `${indent} ${d}`),
|
|
81487
|
+
`${indent}}`
|
|
81488
|
+
].join(`
|
|
81489
|
+
`);
|
|
81490
|
+
const isCatchAll = !app.path || app.path === "/";
|
|
81491
|
+
if (isCatchAll)
|
|
81492
|
+
return proxyLine;
|
|
81493
|
+
return wrapInHandle(proxyLine, app.path, indent);
|
|
81494
|
+
}
|
|
81495
|
+
function renderStaticBlock(app, indent) {
|
|
81496
|
+
const root = app.root;
|
|
81497
|
+
const lines = [`${indent}root * ${root}`];
|
|
81498
|
+
if (app.cache?.enabled) {
|
|
81499
|
+
const maxAge = app.cache.maxAge ?? 3600;
|
|
81500
|
+
lines.push(`${indent}header Cache-Control "public, max-age=${maxAge}"`);
|
|
81501
|
+
}
|
|
81502
|
+
if (app.spa) {
|
|
81503
|
+
lines.push(`${indent}try_files {path} /index.html`);
|
|
81504
|
+
} else if (app.pathRewriteStyle === "flat") {
|
|
81505
|
+
lines.push(`${indent}try_files {path} {path}.html {path}/index.html`);
|
|
81506
|
+
} else {
|
|
81507
|
+
lines.push(`${indent}try_files {path} {path}/index.html {path}.html`);
|
|
81172
81508
|
}
|
|
81509
|
+
lines.push(`${indent}file_server`);
|
|
81510
|
+
const body = lines.join(`
|
|
81511
|
+
`);
|
|
81512
|
+
const isCatchAll = !app.path || app.path === "/";
|
|
81513
|
+
if (isCatchAll)
|
|
81514
|
+
return body;
|
|
81515
|
+
return wrapInHandle(body, app.path, indent);
|
|
81516
|
+
}
|
|
81517
|
+
function renderAppBlock(app, indent) {
|
|
81518
|
+
return isStaticApp(app) ? renderStaticBlock(app, indent) : renderUpstreamBlock(app, indent);
|
|
81519
|
+
}
|
|
81520
|
+
function groupAppsByDomains(apps) {
|
|
81521
|
+
const groups = new Map;
|
|
81522
|
+
for (const app of apps) {
|
|
81523
|
+
const domains = [...app.domains].filter(Boolean);
|
|
81524
|
+
if (domains.length === 0)
|
|
81525
|
+
continue;
|
|
81526
|
+
const key = [...domains].sort().join(" ");
|
|
81527
|
+
const group = groups.get(key) ?? { domains, apps: [] };
|
|
81528
|
+
group.apps.push(app);
|
|
81529
|
+
groups.set(key, group);
|
|
81530
|
+
}
|
|
81531
|
+
return [...groups.values()];
|
|
81532
|
+
}
|
|
81533
|
+
function sortAppsByPath(apps) {
|
|
81534
|
+
return [...apps].sort((a, b) => {
|
|
81535
|
+
const aCatchAll = !a.path || a.path === "/";
|
|
81536
|
+
const bCatchAll = !b.path || b.path === "/";
|
|
81537
|
+
if (aCatchAll && !bCatchAll)
|
|
81538
|
+
return 1;
|
|
81539
|
+
if (!aCatchAll && bCatchAll)
|
|
81540
|
+
return -1;
|
|
81541
|
+
return (b.path?.length ?? 0) - (a.path?.length ?? 0);
|
|
81542
|
+
});
|
|
81543
|
+
}
|
|
81544
|
+
function buildCaddyfileFromProxy(proxy) {
|
|
81545
|
+
if (proxy.raw && proxy.raw.trim())
|
|
81546
|
+
return proxy.raw.trim();
|
|
81547
|
+
const apps = (proxy.apps ?? []).filter((app) => app.domains.length > 0 && (isProxyApp(app) || isStaticApp(app)));
|
|
81548
|
+
if (apps.length === 0)
|
|
81549
|
+
return;
|
|
81550
|
+
const onDemand = normalizeOnDemandTls(proxy.onDemandTls);
|
|
81551
|
+
const hasOnDemandDomain = apps.some((app) => app.domains.some(isOnDemandDomain));
|
|
81552
|
+
const globalLines = [];
|
|
81553
|
+
if (proxy.email)
|
|
81554
|
+
globalLines.push(`email ${proxy.email}`);
|
|
81555
|
+
if (proxy.staging)
|
|
81556
|
+
globalLines.push("acme_ca https://acme-staging-v02.api.letsencrypt.org/directory");
|
|
81557
|
+
if (onDemand) {
|
|
81558
|
+
if (onDemand.ask || onDemand.interval || onDemand.burst != null) {
|
|
81559
|
+
const inner = [];
|
|
81560
|
+
if (onDemand.ask)
|
|
81561
|
+
inner.push(` ask ${onDemand.ask}`);
|
|
81562
|
+
if (onDemand.interval || onDemand.burst != null) {
|
|
81563
|
+
const rl = [];
|
|
81564
|
+
if (onDemand.interval)
|
|
81565
|
+
rl.push(` interval ${onDemand.interval}`);
|
|
81566
|
+
if (onDemand.burst != null)
|
|
81567
|
+
rl.push(` burst ${onDemand.burst}`);
|
|
81568
|
+
inner.push(` rate_limit {
|
|
81569
|
+
${rl.join(`
|
|
81570
|
+
`)}
|
|
81571
|
+
}`);
|
|
81572
|
+
}
|
|
81573
|
+
globalLines.push(`on_demand_tls {
|
|
81574
|
+
${inner.join(`
|
|
81575
|
+
`)}
|
|
81576
|
+
}`);
|
|
81577
|
+
} else {
|
|
81578
|
+
globalLines.push(`on_demand_tls {
|
|
81579
|
+
}`);
|
|
81580
|
+
}
|
|
81581
|
+
}
|
|
81582
|
+
for (const directive of proxy.globalDirectives ?? [])
|
|
81583
|
+
globalLines.push(directive);
|
|
81173
81584
|
const blocks = [];
|
|
81174
|
-
|
|
81175
|
-
|
|
81176
|
-
|
|
81177
|
-
const bIsCatchAll = !b.path || b.path === "/";
|
|
81178
|
-
if (aIsCatchAll && !bIsCatchAll)
|
|
81179
|
-
return 1;
|
|
81180
|
-
if (!aIsCatchAll && bIsCatchAll)
|
|
81181
|
-
return -1;
|
|
81182
|
-
return (b.path?.length ?? 0) - (a.path?.length ?? 0);
|
|
81183
|
-
});
|
|
81184
|
-
const handles = sorted.map((s) => {
|
|
81185
|
-
const isCatchAll = !s.path || s.path === "/";
|
|
81186
|
-
const inner = `reverse_proxy localhost:${s.port}`;
|
|
81187
|
-
return isCatchAll ? ` handle {
|
|
81188
|
-
${inner}
|
|
81189
|
-
}` : ` handle ${s.path} {
|
|
81190
|
-
${inner}
|
|
81191
|
-
}`;
|
|
81192
|
-
});
|
|
81193
|
-
blocks.push(`${domain} {
|
|
81194
|
-
${handles.join(`
|
|
81585
|
+
if (globalLines.length > 0)
|
|
81586
|
+
blocks.push(`{
|
|
81587
|
+
${globalLines.map((line) => ` ${line}`).join(`
|
|
81195
81588
|
`)}
|
|
81196
81589
|
}`);
|
|
81590
|
+
for (const group of groupAppsByDomains(apps)) {
|
|
81591
|
+
const sorted = sortAppsByPath(group.apps);
|
|
81592
|
+
const body = sorted.map((app) => renderAppBlock(app, " ")).join(`
|
|
81593
|
+
`);
|
|
81594
|
+
const needsOnDemand = onDemand && group.domains.some(isOnDemandDomain);
|
|
81595
|
+
const tlsBlock = needsOnDemand ? `
|
|
81596
|
+
tls {
|
|
81597
|
+
on_demand
|
|
81598
|
+
}` : "";
|
|
81599
|
+
blocks.push(`${group.domains.join(", ")} {
|
|
81600
|
+
${body}${tlsBlock}
|
|
81601
|
+
}`);
|
|
81602
|
+
}
|
|
81603
|
+
if (hasOnDemandDomain && !onDemand) {
|
|
81604
|
+
blocks.unshift(`# WARNING: wildcard/catch-all domain present but on_demand_tls is not enabled.
|
|
81605
|
+
` + "# Caddy cannot provision TLS for these hosts. Set compute.proxy.onDemandTls.");
|
|
81197
81606
|
}
|
|
81198
81607
|
return blocks.join(`
|
|
81199
81608
|
|
|
81200
81609
|
`);
|
|
81201
81610
|
}
|
|
81611
|
+
function proxyConfigFromSites(sites) {
|
|
81612
|
+
const apps = [];
|
|
81613
|
+
for (const [name, site] of Object.entries(sites)) {
|
|
81614
|
+
if (typeof site.domain !== "string" || !site.domain)
|
|
81615
|
+
continue;
|
|
81616
|
+
if (typeof site.port === "number" && site.deploy !== "bucket") {
|
|
81617
|
+
apps.push({
|
|
81618
|
+
name,
|
|
81619
|
+
domains: [site.domain],
|
|
81620
|
+
port: site.port,
|
|
81621
|
+
path: site.path
|
|
81622
|
+
});
|
|
81623
|
+
} else if (resolveSiteKind(site) === "server-static") {
|
|
81624
|
+
apps.push({
|
|
81625
|
+
name,
|
|
81626
|
+
domains: [site.domain],
|
|
81627
|
+
root: staticSiteServerRoot(name),
|
|
81628
|
+
path: site.path,
|
|
81629
|
+
spa: site.spa,
|
|
81630
|
+
pathRewriteStyle: site.pathRewriteStyle,
|
|
81631
|
+
cache: site.cache
|
|
81632
|
+
});
|
|
81633
|
+
}
|
|
81634
|
+
}
|
|
81635
|
+
return { apps };
|
|
81636
|
+
}
|
|
81637
|
+
function resolveCaddyfile(sites, proxy) {
|
|
81638
|
+
if (proxy) {
|
|
81639
|
+
if (proxy.raw && proxy.raw.trim())
|
|
81640
|
+
return proxy.raw.trim();
|
|
81641
|
+
const resolved = proxy.apps && proxy.apps.length > 0 ? proxy : { ...proxy, apps: proxyConfigFromSites(sites).apps };
|
|
81642
|
+
return buildCaddyfileFromProxy(resolved);
|
|
81643
|
+
}
|
|
81644
|
+
return buildCaddyfileFromProxy(proxyConfigFromSites(sites));
|
|
81645
|
+
}
|
|
81646
|
+
function buildCaddyfile(sites) {
|
|
81647
|
+
return buildCaddyfileFromProxy(proxyConfigFromSites(sites));
|
|
81648
|
+
}
|
|
81202
81649
|
|
|
81203
81650
|
// src/drivers/hetzner/client.ts
|
|
81204
81651
|
var DEFAULT_API_URL = "https://api.hetzner.cloud/v1";
|
|
@@ -81223,10 +81670,20 @@ class HetznerClient {
|
|
|
81223
81670
|
body: body === undefined ? undefined : JSON.stringify(body)
|
|
81224
81671
|
});
|
|
81225
81672
|
const text = await response.text();
|
|
81226
|
-
|
|
81673
|
+
let data;
|
|
81674
|
+
try {
|
|
81675
|
+
data = text ? JSON.parse(text) : {};
|
|
81676
|
+
} catch {
|
|
81677
|
+
if (!response.ok) {
|
|
81678
|
+
const snippet = text.trim().slice(0, 200) || response.statusText || "Hetzner API error";
|
|
81679
|
+
throw new Error(`Hetzner API ${method} ${path} (${response.status}): ${snippet}`);
|
|
81680
|
+
}
|
|
81681
|
+
throw new Error(`Hetzner API ${method} ${path}: unexpected non-JSON response`);
|
|
81682
|
+
}
|
|
81227
81683
|
if (!response.ok) {
|
|
81228
81684
|
const message = data.error?.message || response.statusText || "Hetzner API error";
|
|
81229
|
-
|
|
81685
|
+
const code = data.error?.code ? ` [${data.error.code}]` : "";
|
|
81686
|
+
throw new Error(`Hetzner API ${method} ${path} (${response.status})${code}: ${message}`);
|
|
81230
81687
|
}
|
|
81231
81688
|
return data;
|
|
81232
81689
|
}
|
|
@@ -81270,6 +81727,12 @@ class HetznerClient {
|
|
|
81270
81727
|
});
|
|
81271
81728
|
return { firewall: data.firewall, actions: data.actions };
|
|
81272
81729
|
}
|
|
81730
|
+
async setFirewallRules(firewallId, rules) {
|
|
81731
|
+
const data = await this.request("POST", `/firewalls/${firewallId}/actions/set_rules`, {
|
|
81732
|
+
rules
|
|
81733
|
+
});
|
|
81734
|
+
return data.actions ?? [];
|
|
81735
|
+
}
|
|
81273
81736
|
async applyFirewallToResources(firewallId, applyTo) {
|
|
81274
81737
|
const data = await this.request("POST", `/firewalls/${firewallId}/actions/apply_to_resources`, {
|
|
81275
81738
|
apply_to: applyTo
|
|
@@ -81536,6 +81999,8 @@ class HetznerDriver {
|
|
|
81536
81999
|
sshPublicKeyPath;
|
|
81537
82000
|
sshUser;
|
|
81538
82001
|
location;
|
|
82002
|
+
waitForBoot;
|
|
82003
|
+
bootWait;
|
|
81539
82004
|
constructor(options = {}) {
|
|
81540
82005
|
this.client = options.client ?? new HetznerClient({
|
|
81541
82006
|
apiToken: resolveHetznerApiToken(options.apiToken)
|
|
@@ -81544,6 +82009,13 @@ class HetznerDriver {
|
|
|
81544
82009
|
this.sshPublicKeyPath = expandHome(options.sshPublicKeyPath || process.env.HCLOUD_SSH_PUBLIC_KEY || `${this.sshPrivateKeyPath}.pub`);
|
|
81545
82010
|
this.sshUser = options.sshUser || process.env.HCLOUD_SSH_USER || "root";
|
|
81546
82011
|
this.location = options.location || process.env.HCLOUD_LOCATION || "fsn1";
|
|
82012
|
+
this.waitForBoot = options.waitForBoot ?? true;
|
|
82013
|
+
this.bootWait = {
|
|
82014
|
+
sshIntervalMs: options.bootWait?.sshIntervalMs ?? 5000,
|
|
82015
|
+
sshTimeoutMs: options.bootWait?.sshTimeoutMs ?? 300000,
|
|
82016
|
+
cloudInitIntervalMs: options.bootWait?.cloudInitIntervalMs ?? 5000,
|
|
82017
|
+
cloudInitTimeoutMs: options.bootWait?.cloudInitTimeoutMs ?? 600000
|
|
82018
|
+
};
|
|
81547
82019
|
}
|
|
81548
82020
|
async provisionComputeInfrastructure(options) {
|
|
81549
82021
|
const { config: config6, environment } = options;
|
|
@@ -81553,16 +82025,32 @@ class HetznerDriver {
|
|
|
81553
82025
|
throw new Error("infrastructure.compute is required to provision Hetzner compute");
|
|
81554
82026
|
}
|
|
81555
82027
|
const stackName = resolveProjectStackName(config6, environment);
|
|
82028
|
+
const serverName = `${slug}-${environment}-app`;
|
|
81556
82029
|
const existing = await readDriverState(stackName);
|
|
81557
82030
|
if (existing?.serverId) {
|
|
81558
|
-
const server2 = await this.
|
|
81559
|
-
if (server2.status !== "off") {
|
|
82031
|
+
const server2 = await this.tryGetServer(existing.serverId);
|
|
82032
|
+
if (server2 && server2.status !== "off") {
|
|
81560
82033
|
return this.outputsFromState(existing, server2);
|
|
81561
82034
|
}
|
|
81562
82035
|
}
|
|
82036
|
+
const labels = tsCloudLabels(slug, environment, "app");
|
|
82037
|
+
const alreadyRunning = await this.findExistingServer(slug, environment, serverName);
|
|
82038
|
+
if (alreadyRunning && alreadyRunning.status !== "off") {
|
|
82039
|
+
const rehydrated = {
|
|
82040
|
+
provider: "hetzner",
|
|
82041
|
+
stackName,
|
|
82042
|
+
serverId: alreadyRunning.id,
|
|
82043
|
+
serverName: alreadyRunning.name,
|
|
82044
|
+
publicIp: alreadyRunning.public_net.ipv4?.ip,
|
|
82045
|
+
deployStoragePath: "/var/ts-cloud/staging",
|
|
82046
|
+
sshUser: this.sshUser
|
|
82047
|
+
};
|
|
82048
|
+
await writeDriverState(stackName, rehydrated);
|
|
82049
|
+
return this.outputsFromState(rehydrated, alreadyRunning);
|
|
82050
|
+
}
|
|
81563
82051
|
const sites = config6.sites || {};
|
|
81564
|
-
const
|
|
81565
|
-
const
|
|
82052
|
+
const caddyfile = resolveCaddyfile(sites, compute.proxy);
|
|
82053
|
+
const sitePorts = caddyfile ? [] : this.collectUpstreamPorts(sites, compute.proxy);
|
|
81566
82054
|
const bootstrap = generateUbuntuAppCloudInit({
|
|
81567
82055
|
runtime: compute.runtime || "bun",
|
|
81568
82056
|
runtimeVersion: compute.runtimeVersion || "latest",
|
|
@@ -81571,19 +82059,13 @@ class HetznerDriver {
|
|
|
81571
82059
|
caddyfile
|
|
81572
82060
|
});
|
|
81573
82061
|
const userData = wrapCloudInitUserData(bootstrap);
|
|
81574
|
-
const serverName = `${slug}-${environment}-app`;
|
|
81575
82062
|
const serverType = resolveHetznerServerType(compute.size);
|
|
81576
82063
|
const image = compute.image || config6.hetzner?.image || "ubuntu-24.04";
|
|
81577
|
-
const labels = tsCloudLabels(slug, environment, "app");
|
|
81578
82064
|
const firewallName = `${slug}-${environment}-app-fw`;
|
|
81579
|
-
const { firewall } = await this.
|
|
81580
|
-
|
|
81581
|
-
|
|
81582
|
-
|
|
81583
|
-
allowSsh: compute.allowSsh !== false,
|
|
81584
|
-
sitePorts
|
|
81585
|
-
})
|
|
81586
|
-
});
|
|
82065
|
+
const { firewall } = await this.ensureFirewall(firewallName, labels, buildHetznerFirewallRules({
|
|
82066
|
+
allowSsh: compute.allowSsh !== false,
|
|
82067
|
+
sitePorts
|
|
82068
|
+
}));
|
|
81587
82069
|
const sshKeyId = await this.ensureSshKey(slug, environment, labels);
|
|
81588
82070
|
const { server, action } = await this.client.createServer({
|
|
81589
82071
|
name: serverName,
|
|
@@ -81608,6 +82090,11 @@ class HetznerDriver {
|
|
|
81608
82090
|
sshUser: this.sshUser
|
|
81609
82091
|
};
|
|
81610
82092
|
await writeDriverState(stackName, state);
|
|
82093
|
+
const ip = running.public_net.ipv4?.ip;
|
|
82094
|
+
if (ip && this.waitForBoot) {
|
|
82095
|
+
await this.waitForSshReady(ip);
|
|
82096
|
+
await this.waitForCloudInit(ip);
|
|
82097
|
+
}
|
|
81611
82098
|
return this.outputsFromState(state, running);
|
|
81612
82099
|
}
|
|
81613
82100
|
async getComputeOutputs(options) {
|
|
@@ -81714,6 +82201,79 @@ class HetznerDriver {
|
|
|
81714
82201
|
});
|
|
81715
82202
|
return created.id;
|
|
81716
82203
|
}
|
|
82204
|
+
async tryGetServer(id) {
|
|
82205
|
+
try {
|
|
82206
|
+
return await this.client.getServer(id);
|
|
82207
|
+
} catch {
|
|
82208
|
+
return null;
|
|
82209
|
+
}
|
|
82210
|
+
}
|
|
82211
|
+
async findExistingServer(slug, environment, serverName) {
|
|
82212
|
+
const servers = await this.client.listServers();
|
|
82213
|
+
return servers.find((server) => matchesTsCloudLabels(server.labels, slug, environment, "app") || server.name === serverName);
|
|
82214
|
+
}
|
|
82215
|
+
async ensureFirewall(name, labels, rules) {
|
|
82216
|
+
const existing = await this.client.listFirewalls();
|
|
82217
|
+
const match = existing.find((fw) => fw.name === name);
|
|
82218
|
+
if (match) {
|
|
82219
|
+
await this.client.setFirewallRules(match.id, rules);
|
|
82220
|
+
return { firewall: match };
|
|
82221
|
+
}
|
|
82222
|
+
const { firewall } = await this.client.createFirewall({ name, labels, rules });
|
|
82223
|
+
return { firewall };
|
|
82224
|
+
}
|
|
82225
|
+
collectUpstreamPorts(sites, proxy) {
|
|
82226
|
+
const ports = new Set;
|
|
82227
|
+
for (const app of proxy?.apps ?? []) {
|
|
82228
|
+
if (typeof app.port === "number")
|
|
82229
|
+
ports.add(app.port);
|
|
82230
|
+
}
|
|
82231
|
+
for (const site of Object.values(sites)) {
|
|
82232
|
+
if (typeof site.port === "number")
|
|
82233
|
+
ports.add(site.port);
|
|
82234
|
+
}
|
|
82235
|
+
return [...ports].filter((port) => ![80, 443].includes(port));
|
|
82236
|
+
}
|
|
82237
|
+
async sleep(ms) {
|
|
82238
|
+
await new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
82239
|
+
}
|
|
82240
|
+
async waitForSshReady(host) {
|
|
82241
|
+
const { sshIntervalMs, sshTimeoutMs } = this.bootWait;
|
|
82242
|
+
const start = Date.now();
|
|
82243
|
+
let lastErr;
|
|
82244
|
+
while (Date.now() - start < sshTimeoutMs) {
|
|
82245
|
+
try {
|
|
82246
|
+
execSync(`ssh ${this.sshBaseArgs(host, ["-o", "ConnectTimeout=5"]).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} true`, {
|
|
82247
|
+
stdio: "pipe",
|
|
82248
|
+
maxBuffer: SSH_MAX_BUFFER
|
|
82249
|
+
});
|
|
82250
|
+
return;
|
|
82251
|
+
} catch (err) {
|
|
82252
|
+
lastErr = err;
|
|
82253
|
+
await this.sleep(sshIntervalMs);
|
|
82254
|
+
}
|
|
82255
|
+
}
|
|
82256
|
+
throw new Error(`Timed out waiting for SSH on ${host} after ${sshTimeoutMs}ms: ${lastErr?.message ?? "unknown error"}`);
|
|
82257
|
+
}
|
|
82258
|
+
async waitForCloudInit(host) {
|
|
82259
|
+
const { cloudInitIntervalMs, cloudInitTimeoutMs } = this.bootWait;
|
|
82260
|
+
const start = Date.now();
|
|
82261
|
+
while (Date.now() - start < cloudInitTimeoutMs) {
|
|
82262
|
+
try {
|
|
82263
|
+
const out = this.sshExec(host, "cloud-init status --long 2>/dev/null || cloud-init status 2>/dev/null || echo status:\\ done");
|
|
82264
|
+
if (/status:\s*done/.test(out))
|
|
82265
|
+
return;
|
|
82266
|
+
if (/status:\s*error/.test(out))
|
|
82267
|
+
throw new Error(`cloud-init reported an error on ${host}:
|
|
82268
|
+
${out}`);
|
|
82269
|
+
} catch (err) {
|
|
82270
|
+
if (err instanceof Error && /cloud-init reported an error/.test(err.message))
|
|
82271
|
+
throw err;
|
|
82272
|
+
}
|
|
82273
|
+
await this.sleep(cloudInitIntervalMs);
|
|
82274
|
+
}
|
|
82275
|
+
throw new Error(`Timed out waiting for cloud-init to finish on ${host} after ${cloudInitTimeoutMs}ms`);
|
|
82276
|
+
}
|
|
81717
82277
|
outputsFromState(state, server) {
|
|
81718
82278
|
return {
|
|
81719
82279
|
deployStoragePath: state.deployStoragePath || "/var/ts-cloud/staging",
|
|
@@ -81722,7 +82282,7 @@ class HetznerDriver {
|
|
|
81722
82282
|
sshUser: state.sshUser || this.sshUser
|
|
81723
82283
|
};
|
|
81724
82284
|
}
|
|
81725
|
-
sshBaseArgs(host) {
|
|
82285
|
+
sshBaseArgs(host, extra = []) {
|
|
81726
82286
|
return [
|
|
81727
82287
|
"-i",
|
|
81728
82288
|
this.sshPrivateKeyPath,
|
|
@@ -81730,6 +82290,7 @@ class HetznerDriver {
|
|
|
81730
82290
|
"StrictHostKeyChecking=accept-new",
|
|
81731
82291
|
"-o",
|
|
81732
82292
|
"BatchMode=yes",
|
|
82293
|
+
...extra,
|
|
81733
82294
|
`${this.sshUser}@${host}`
|
|
81734
82295
|
];
|
|
81735
82296
|
}
|
|
@@ -81843,6 +82404,19 @@ function buildSiteDeployScript(options) {
|
|
|
81843
82404
|
`systemctl is-active ${serviceName}`
|
|
81844
82405
|
];
|
|
81845
82406
|
}
|
|
82407
|
+
function buildStaticSiteDeployScript(options) {
|
|
82408
|
+
const { siteName, artifactFetch, preStartCommands = [] } = options;
|
|
82409
|
+
const appDir = options.appDir ?? `/var/www/${siteName}`;
|
|
82410
|
+
const preStart = preStartCommands.length > 0 ? [`cd ${appDir}`, ...preStartCommands] : [];
|
|
82411
|
+
return [
|
|
82412
|
+
"set -euo pipefail",
|
|
82413
|
+
...artifactFetch,
|
|
82414
|
+
`mkdir -p ${appDir}`,
|
|
82415
|
+
`find ${appDir} -mindepth 1 -maxdepth 1 -exec rm -rf {} +`,
|
|
82416
|
+
`tar xzf /tmp/${siteName}-release.tar.gz -C ${appDir}`,
|
|
82417
|
+
...preStart
|
|
82418
|
+
];
|
|
82419
|
+
}
|
|
81846
82420
|
function buildAwsArtifactFetch(bucket, key, region, siteName) {
|
|
81847
82421
|
return [
|
|
81848
82422
|
`aws s3 cp "s3://${bucket}/${key}" /tmp/${siteName}-release.tar.gz --region ${region}`
|
|
@@ -81892,7 +82466,11 @@ async function deploySiteRelease(driver, options, logger4 = noopLogger) {
|
|
|
81892
82466
|
targets
|
|
81893
82467
|
});
|
|
81894
82468
|
const artifactFetch = driver.name === "aws" ? buildAwsArtifactFetch(outputs.deployBucketName, remoteKey, config6.project.region || "us-east-1", siteName) : buildLocalArtifactFetch(uploadResult.artifactRef, siteName);
|
|
81895
|
-
const remoteScript =
|
|
82469
|
+
const remoteScript = resolveSiteKind(site) === "server-static" ? buildStaticSiteDeployScript({
|
|
82470
|
+
siteName,
|
|
82471
|
+
artifactFetch,
|
|
82472
|
+
preStartCommands: site.preStart
|
|
82473
|
+
}) : buildSiteDeployScript({
|
|
81896
82474
|
siteName,
|
|
81897
82475
|
slug,
|
|
81898
82476
|
artifactFetch,
|
|
@@ -81931,8 +82509,11 @@ async function deployAllComputeSites(options) {
|
|
|
81931
82509
|
const slug = config6.project.slug;
|
|
81932
82510
|
const sites = config6.sites || {};
|
|
81933
82511
|
const deployable = Object.entries(sites).filter(([name, site]) => {
|
|
81934
|
-
if (!site
|
|
81935
|
-
|
|
82512
|
+
if (!site)
|
|
82513
|
+
return false;
|
|
82514
|
+
const kind = resolveSiteKind(site);
|
|
82515
|
+
if (kind === "bucket") {
|
|
82516
|
+
logger4.warn(`Site '${name}' targets a bucket — skipping (handled by the static-site path, not compute).`);
|
|
81936
82517
|
return false;
|
|
81937
82518
|
}
|
|
81938
82519
|
return true;
|
|
@@ -81984,6 +82565,7 @@ export {
|
|
|
81984
82565
|
validateTemplateSize,
|
|
81985
82566
|
validateTemplate,
|
|
81986
82567
|
validateResourceLimits,
|
|
82568
|
+
validateDeploymentConfig,
|
|
81987
82569
|
validateCredentials,
|
|
81988
82570
|
validateConfiguration,
|
|
81989
82571
|
validateCommand,
|
|
@@ -81999,6 +82581,7 @@ export {
|
|
|
81999
82581
|
suggestFlags,
|
|
82000
82582
|
suggestCommand,
|
|
82001
82583
|
storageAdvancedManager,
|
|
82584
|
+
staticSiteServerRoot,
|
|
82002
82585
|
staticSiteManager,
|
|
82003
82586
|
stackDependencyManager,
|
|
82004
82587
|
signRequestAsync,
|
|
@@ -82019,6 +82602,8 @@ export {
|
|
|
82019
82602
|
resolveStorageBucketName,
|
|
82020
82603
|
resolveSiteStackName,
|
|
82021
82604
|
resolveSiteResourceName,
|
|
82605
|
+
resolveSiteKind,
|
|
82606
|
+
resolveSiteDeployTarget,
|
|
82022
82607
|
resolveSiteBucketName,
|
|
82023
82608
|
resolveRegion,
|
|
82024
82609
|
resolveProjectStackName,
|
|
@@ -82028,11 +82613,14 @@ export {
|
|
|
82028
82613
|
resolveDeployBucketName,
|
|
82029
82614
|
resolveCredentials,
|
|
82030
82615
|
resolveCloudProvider,
|
|
82616
|
+
resolveCaddyfile,
|
|
82031
82617
|
requiresReplacement,
|
|
82032
82618
|
replicaManager,
|
|
82619
|
+
remapKey,
|
|
82033
82620
|
regionPairManager,
|
|
82034
82621
|
quickHash,
|
|
82035
82622
|
queueManagementManager,
|
|
82623
|
+
proxyConfigFromSites,
|
|
82036
82624
|
providerEndpoint,
|
|
82037
82625
|
progressiveDeploymentManager,
|
|
82038
82626
|
processInChunks,
|
|
@@ -82050,6 +82638,7 @@ export {
|
|
|
82050
82638
|
multiRegionManager,
|
|
82051
82639
|
multiAccountManager,
|
|
82052
82640
|
migrationManager,
|
|
82641
|
+
migrateObjectStorage,
|
|
82053
82642
|
metricsManager,
|
|
82054
82643
|
mergeInfrastructure,
|
|
82055
82644
|
makeAWSRequestOnce,
|
|
@@ -82063,8 +82652,10 @@ export {
|
|
|
82063
82652
|
lambdaDestinationsManager,
|
|
82064
82653
|
lambdaDLQManager,
|
|
82065
82654
|
lambdaConcurrencyManager,
|
|
82655
|
+
keyMatchesFilters,
|
|
82066
82656
|
isWebCryptoAvailable,
|
|
82067
82657
|
isValidRegion,
|
|
82658
|
+
isOnDemandDomain,
|
|
82068
82659
|
isNodeCryptoAvailable,
|
|
82069
82660
|
isLocalDevelopment,
|
|
82070
82661
|
isLikelyTypo,
|
|
@@ -82213,9 +82804,11 @@ export {
|
|
|
82213
82804
|
certificateManager,
|
|
82214
82805
|
categorizeChanges,
|
|
82215
82806
|
canaryManager,
|
|
82807
|
+
buildStaticSiteDeployScript,
|
|
82216
82808
|
buildSiteDeployScript,
|
|
82217
82809
|
buildOptimizationManager,
|
|
82218
82810
|
buildCloudFormationTemplate,
|
|
82811
|
+
buildCaddyfileFromProxy,
|
|
82219
82812
|
buildCaddyfile,
|
|
82220
82813
|
bounceComplaintHandler,
|
|
82221
82814
|
blueGreenManager,
|