@wraps.dev/cli 2.26.1 → 2.27.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/api-lambda.zip +0 -0
- package/dist/cli.js +227 -18
- package/dist/cli.js.map +1 -1
- package/dist/lambda/agent-enforcer/.bundled +1 -1
- package/dist/lambda/event-processor/.bundled +1 -1
- package/dist/lambda/inbound-processor/.bundled +1 -1
- package/dist/lambda/sms-event-processor/.bundled +1 -1
- package/package.json +2 -1
package/dist/api-lambda.zip
CHANGED
|
Binary file
|
package/dist/cli.js
CHANGED
|
@@ -2679,10 +2679,9 @@ var init_costs = __esm({
|
|
|
2679
2679
|
// $0.20 per million API Destination invocations
|
|
2680
2680
|
};
|
|
2681
2681
|
FREE_TIER = {
|
|
2682
|
-
// SES: 3,000
|
|
2683
|
-
//
|
|
2682
|
+
// SES: no free sending tier (the 3,000/mo-for-12-months offer was retired
|
|
2683
|
+
// 2026-07-21; new accounts get generic $200 AWS credits instead)
|
|
2684
2684
|
SES_EMAILS: 0,
|
|
2685
|
-
// Conservative: assume no free tier (most users are past 12 months)
|
|
2686
2685
|
// Lambda: Permanently free tier
|
|
2687
2686
|
LAMBDA_REQUESTS: 1e6,
|
|
2688
2687
|
// 1M requests per month (always free)
|
|
@@ -6084,6 +6083,9 @@ async function findConnectionsWithService(accountId, service) {
|
|
|
6084
6083
|
const accountConnections = await findConnectionsForAccount(accountId);
|
|
6085
6084
|
return accountConnections.filter((conn) => hasService(conn, service));
|
|
6086
6085
|
}
|
|
6086
|
+
function normalizeWebhookBaseUrl(webhookUrl) {
|
|
6087
|
+
return webhookUrl.replace(/\/v1\/ses-events\/?$/, "").replace(/\/+$/, "");
|
|
6088
|
+
}
|
|
6087
6089
|
function buildEmailStackConfig(metadata, region, overrides) {
|
|
6088
6090
|
const emailService = metadata.services.email;
|
|
6089
6091
|
let webhook;
|
|
@@ -6092,7 +6094,10 @@ function buildEmailStackConfig(metadata, region, overrides) {
|
|
|
6092
6094
|
} else if (emailService?.webhookSecret) {
|
|
6093
6095
|
webhook = {
|
|
6094
6096
|
awsAccountNumber: metadata.accountId,
|
|
6095
|
-
webhookSecret: emailService.webhookSecret
|
|
6097
|
+
webhookSecret: emailService.webhookSecret,
|
|
6098
|
+
...emailService.webhookUrl && {
|
|
6099
|
+
webhookUrl: normalizeWebhookBaseUrl(emailService.webhookUrl)
|
|
6100
|
+
}
|
|
6096
6101
|
};
|
|
6097
6102
|
}
|
|
6098
6103
|
const emailConfig = overrides?.emailConfig ?? emailService?.config;
|
|
@@ -11450,7 +11455,7 @@ async function runFullSetup() {
|
|
|
11450
11455
|
console.log(` ${pc9.cyan("https://aws.amazon.com/free")}`);
|
|
11451
11456
|
console.log();
|
|
11452
11457
|
console.log(
|
|
11453
|
-
pc9.dim("
|
|
11458
|
+
pc9.dim(" New AWS accounts get $200 in Free Tier credits (6 months)")
|
|
11454
11459
|
);
|
|
11455
11460
|
console.log();
|
|
11456
11461
|
const hasAccount = await clack7.confirm({
|
|
@@ -35326,6 +35331,128 @@ import { dirname as dirname5, join as join20 } from "path";
|
|
|
35326
35331
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
35327
35332
|
import * as clack45 from "@clack/prompts";
|
|
35328
35333
|
import * as pulumi28 from "@pulumi/pulumi";
|
|
35334
|
+
|
|
35335
|
+
// ../db/dist/connection-url.js
|
|
35336
|
+
init_esm_shims();
|
|
35337
|
+
|
|
35338
|
+
// ../db/dist/connection-url-CJ0MYp2V.js
|
|
35339
|
+
init_esm_shims();
|
|
35340
|
+
var POSTGRES_SCHEMES = ["postgres:", "postgresql:"];
|
|
35341
|
+
var TRUST_STORE_SENTINELS = /* @__PURE__ */ new Set(["system", "os"]);
|
|
35342
|
+
var TLS_OPT_OUT = /* @__PURE__ */ new Set(["disable", "no-verify"]);
|
|
35343
|
+
function pinVerification(parsed) {
|
|
35344
|
+
const modes = parsed.searchParams.getAll("sslmode");
|
|
35345
|
+
const effective = modes.at(-1)?.toLowerCase();
|
|
35346
|
+
const hadCompat = parsed.searchParams.has("uselibpqcompat");
|
|
35347
|
+
if (effective && TLS_OPT_OUT.has(effective)) {
|
|
35348
|
+
parsed.searchParams.set("sslmode", effective);
|
|
35349
|
+
parsed.searchParams.delete("uselibpqcompat");
|
|
35350
|
+
return;
|
|
35351
|
+
}
|
|
35352
|
+
const alreadyVerifying = modes.length === 1 && effective === "verify-full" && !hadCompat;
|
|
35353
|
+
parsed.searchParams.set("sslmode", "verify-full");
|
|
35354
|
+
parsed.searchParams.delete("uselibpqcompat");
|
|
35355
|
+
return alreadyVerifying ? void 0 : "Pinned sslmode=verify-full \u2014 without the CA reference the original mode would have connected unverified (or in cleartext).";
|
|
35356
|
+
}
|
|
35357
|
+
function normalizeDatabaseUrl(raw) {
|
|
35358
|
+
const url = raw.trim();
|
|
35359
|
+
const notes = [];
|
|
35360
|
+
let parsed;
|
|
35361
|
+
try {
|
|
35362
|
+
parsed = new URL(url);
|
|
35363
|
+
} catch {
|
|
35364
|
+
return {
|
|
35365
|
+
url,
|
|
35366
|
+
notes
|
|
35367
|
+
};
|
|
35368
|
+
}
|
|
35369
|
+
const certKey = [...parsed.searchParams.keys()].find((key) => key.toLowerCase() === "sslrootcert");
|
|
35370
|
+
const sslRootCert = certKey ? parsed.searchParams.get(certKey) : void 0;
|
|
35371
|
+
if (!(sslRootCert && TRUST_STORE_SENTINELS.has(sslRootCert.toLowerCase()))) return {
|
|
35372
|
+
url,
|
|
35373
|
+
notes
|
|
35374
|
+
};
|
|
35375
|
+
parsed.searchParams.delete(certKey);
|
|
35376
|
+
notes.push(`Dropped sslrootcert=${sslRootCert} \u2014 node-postgres would read it as a filename. TLS stays on and the certificate is verified against Node's bundled CA set (set NODE_EXTRA_CA_CERTS for a private CA).`);
|
|
35377
|
+
const pinNote = pinVerification(parsed);
|
|
35378
|
+
if (pinNote) notes.push(pinNote);
|
|
35379
|
+
return {
|
|
35380
|
+
url: parsed.toString(),
|
|
35381
|
+
notes
|
|
35382
|
+
};
|
|
35383
|
+
}
|
|
35384
|
+
function describeMigrationError(error) {
|
|
35385
|
+
const parts = [];
|
|
35386
|
+
let current = error;
|
|
35387
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35388
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
35389
|
+
seen.add(current);
|
|
35390
|
+
const code = current.code;
|
|
35391
|
+
const message = current.message.startsWith("Failed query:") ? current.message.split("\n")[0] : current.message;
|
|
35392
|
+
const described = code ? `${message} (${code})` : message;
|
|
35393
|
+
if (!parts.includes(described)) parts.push(described);
|
|
35394
|
+
current = current.cause;
|
|
35395
|
+
}
|
|
35396
|
+
return parts.length > 0 ? parts.join(" \u2014 ") : String(error);
|
|
35397
|
+
}
|
|
35398
|
+
var HINTS = [
|
|
35399
|
+
{
|
|
35400
|
+
match: /\b(ENOENT|EACCES)\b/,
|
|
35401
|
+
hint: "A file named in DATABASE_URL (usually sslrootcert) is missing or unreadable. Point it at a readable CA file, or set NODE_EXTRA_CA_CERTS if the certificate chains to a private CA."
|
|
35402
|
+
},
|
|
35403
|
+
{
|
|
35404
|
+
match: /password authentication failed|28P01|28000/i,
|
|
35405
|
+
hint: "The credentials in DATABASE_URL were rejected. Check the user and password."
|
|
35406
|
+
},
|
|
35407
|
+
{
|
|
35408
|
+
match: /CREATE SCHEMA|permission denied for database|must be owner of database/i,
|
|
35409
|
+
hint: "The database user cannot create schemas. Grant it CREATE on the database (GRANT CREATE ON DATABASE <db> TO <user>), or use an owner role."
|
|
35410
|
+
},
|
|
35411
|
+
{
|
|
35412
|
+
match: /permission denied|42501/i,
|
|
35413
|
+
hint: "The database user lacks a privilege this migration needs. The failing statement above names the object \u2014 grant on that, or run migrations as the database owner."
|
|
35414
|
+
},
|
|
35415
|
+
{
|
|
35416
|
+
match: /database .* does not exist|3D000/i,
|
|
35417
|
+
hint: "The database named in DATABASE_URL does not exist. Create it first."
|
|
35418
|
+
},
|
|
35419
|
+
{
|
|
35420
|
+
match: /no pg_hba\.conf entry|SSL|self[- ]signed|certificate/i,
|
|
35421
|
+
hint: "TLS negotiation failed. Most managed Postgres requires ?sslmode=verify-full on DATABASE_URL."
|
|
35422
|
+
},
|
|
35423
|
+
{
|
|
35424
|
+
match: /ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN/i,
|
|
35425
|
+
hint: "Could not reach the database host. Check the host and port, and that this machine is allowed through the firewall or IP allowlist."
|
|
35426
|
+
}
|
|
35427
|
+
];
|
|
35428
|
+
function migrationHint(description) {
|
|
35429
|
+
return HINTS.find(({ match }) => match.test(description))?.hint;
|
|
35430
|
+
}
|
|
35431
|
+
function describeMigrationFailure(error) {
|
|
35432
|
+
const description = describeMigrationError(error);
|
|
35433
|
+
const hint = migrationHint(description);
|
|
35434
|
+
return hint ? `Database migrations failed: ${description}
|
|
35435
|
+
${hint}` : `Database migrations failed: ${description}`;
|
|
35436
|
+
}
|
|
35437
|
+
function assertPostgresUrl(raw) {
|
|
35438
|
+
const url = raw.trim();
|
|
35439
|
+
if (!url) throw new Error("DATABASE_URL is empty.");
|
|
35440
|
+
if (!url.includes("://")) {
|
|
35441
|
+
if (/(^|\s)(host|dbname|user)=/.test(url)) throw new Error("DATABASE_URL is a libpq keyword/value connection string, which node-postgres cannot parse. Use the URI form instead: postgresql://USER:PASSWORD@HOST:5432/DATABASE?sslmode=verify-full");
|
|
35442
|
+
throw new Error("DATABASE_URL is not a connection URI. Expected postgresql://USER:PASSWORD@HOST:5432/DATABASE");
|
|
35443
|
+
}
|
|
35444
|
+
let parsed;
|
|
35445
|
+
try {
|
|
35446
|
+
parsed = new URL(url);
|
|
35447
|
+
} catch {
|
|
35448
|
+
throw new Error("DATABASE_URL could not be parsed as a URI. If the password contains @ / : or ?, percent-encode it.");
|
|
35449
|
+
}
|
|
35450
|
+
if (POSTGRES_SCHEMES.includes(parsed.protocol)) return;
|
|
35451
|
+
if (parsed.protocol === "mysql:" || parsed.protocol === "mysql2:") throw new Error(`DATABASE_URL points at MySQL (${parsed.protocol}//). Wraps requires PostgreSQL \u2014 its schema uses Postgres types, enums and partial indexes that have no MySQL equivalent.`);
|
|
35452
|
+
throw new Error(`DATABASE_URL has an unsupported scheme (${parsed.protocol}//). Wraps requires PostgreSQL: postgresql://USER:PASSWORD@HOST:5432/DATABASE`);
|
|
35453
|
+
}
|
|
35454
|
+
|
|
35455
|
+
// src/commands/selfhost/deploy.ts
|
|
35329
35456
|
import pc48 from "picocolors";
|
|
35330
35457
|
|
|
35331
35458
|
// src/infrastructure/selfhost-stack.ts
|
|
@@ -35582,6 +35709,29 @@ function buildNeonProjectName(accountId, region) {
|
|
|
35582
35709
|
return `wraps-selfhost-${accountId}-${region}`;
|
|
35583
35710
|
}
|
|
35584
35711
|
|
|
35712
|
+
// src/utils/selfhost/variant.ts
|
|
35713
|
+
init_esm_shims();
|
|
35714
|
+
var SHARED_SCHEDULER_ROLE = "wraps-selfhost-scheduler-role";
|
|
35715
|
+
async function detectSelfhostVariant(region) {
|
|
35716
|
+
try {
|
|
35717
|
+
const { LambdaClient: LambdaClient3, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
35718
|
+
const lambda7 = new LambdaClient3({ region });
|
|
35719
|
+
await lambda7.send(
|
|
35720
|
+
new GetFunctionCommand({ FunctionName: SELFHOST_API_FUNCTION_NAME })
|
|
35721
|
+
);
|
|
35722
|
+
return "pulumi";
|
|
35723
|
+
} catch {
|
|
35724
|
+
}
|
|
35725
|
+
try {
|
|
35726
|
+
const { IAMClient: IAMClient5, GetRoleCommand: GetRoleCommand3 } = await import("@aws-sdk/client-iam");
|
|
35727
|
+
const iam13 = new IAMClient5({ region });
|
|
35728
|
+
await iam13.send(new GetRoleCommand3({ RoleName: SHARED_SCHEDULER_ROLE }));
|
|
35729
|
+
return "sst";
|
|
35730
|
+
} catch {
|
|
35731
|
+
return null;
|
|
35732
|
+
}
|
|
35733
|
+
}
|
|
35734
|
+
|
|
35585
35735
|
// src/commands/selfhost/deploy.ts
|
|
35586
35736
|
init_aws();
|
|
35587
35737
|
init_errors();
|
|
@@ -35639,17 +35789,42 @@ async function selfhostDeploy(options) {
|
|
|
35639
35789
|
region
|
|
35640
35790
|
);
|
|
35641
35791
|
if (existingMetadata?.services?.selfhost) {
|
|
35792
|
+
const existing = existingMetadata.services.selfhost;
|
|
35642
35793
|
clack45.log.warn(
|
|
35643
35794
|
`Self-hosted deployment already exists for account ${pc48.cyan(identity.accountId)} in region ${pc48.cyan(region)}`
|
|
35644
35795
|
);
|
|
35796
|
+
clack45.log.info(`API URL: ${pc48.cyan(existing.apiUrl)}`);
|
|
35797
|
+
clack45.log.info(`Deployed: ${existing.deployedAt}`);
|
|
35645
35798
|
clack45.log.info(
|
|
35646
|
-
`
|
|
35799
|
+
existing.variant === "sst" ? `This is a full-platform (SST) deployment. To update: run ${pc48.cyan("pnpm selfhost:upgrade")} from your fork.` : `To update: run ${pc48.cyan("wraps selfhost upgrade")}`
|
|
35800
|
+
);
|
|
35801
|
+
process.exit(0);
|
|
35802
|
+
}
|
|
35803
|
+
const deployedVariant = await progress.execute(
|
|
35804
|
+
"Checking for existing selfhost deployments",
|
|
35805
|
+
async () => detectSelfhostVariant(region)
|
|
35806
|
+
);
|
|
35807
|
+
if (deployedVariant === "sst") {
|
|
35808
|
+
progress.stop();
|
|
35809
|
+
clack45.log.error(
|
|
35810
|
+
"A full-platform (SST) selfhost deployment already exists in this AWS account."
|
|
35647
35811
|
);
|
|
35648
35812
|
clack45.log.info(
|
|
35649
|
-
`
|
|
35813
|
+
`The two selfhost variants share IAM resources and cannot coexist.
|
|
35814
|
+
To update it: run ${pc48.cyan("pnpm selfhost:upgrade")} from your fork.
|
|
35815
|
+
To replace it with the API-only control plane: run ${pc48.cyan("pnpm selfhost:destroy")} from your fork first.`
|
|
35650
35816
|
);
|
|
35651
|
-
|
|
35652
|
-
|
|
35817
|
+
process.exit(1);
|
|
35818
|
+
}
|
|
35819
|
+
if (deployedVariant === "pulumi") {
|
|
35820
|
+
progress.stop();
|
|
35821
|
+
clack45.log.error(
|
|
35822
|
+
"An API-only selfhost control plane already exists in this AWS account, but no local metadata was found."
|
|
35823
|
+
);
|
|
35824
|
+
clack45.log.info(
|
|
35825
|
+
`It was deployed from another machine. Run ${pc48.cyan("wraps selfhost upgrade")} there, or remove the existing stack before redeploying from this one.`
|
|
35826
|
+
);
|
|
35827
|
+
process.exit(1);
|
|
35653
35828
|
}
|
|
35654
35829
|
let resolvedDatabaseUrl = options.databaseUrl;
|
|
35655
35830
|
let resolvedNeonProjectId;
|
|
@@ -35777,6 +35952,7 @@ async function selfhostDeploy(options) {
|
|
|
35777
35952
|
);
|
|
35778
35953
|
}
|
|
35779
35954
|
const databaseUrl = resolvedDatabaseUrl;
|
|
35955
|
+
assertPostgresUrl(databaseUrl);
|
|
35780
35956
|
const unsubscribeSecret = randomBytes6(32).toString("hex");
|
|
35781
35957
|
const betterAuthSecret = randomBytes6(32).toString("hex");
|
|
35782
35958
|
const selfhostConfig = {
|
|
@@ -35802,19 +35978,26 @@ async function selfhostDeploy(options) {
|
|
|
35802
35978
|
deployedAt,
|
|
35803
35979
|
pulumiStackName: stackName,
|
|
35804
35980
|
config: selfhostConfig,
|
|
35805
|
-
apiUrl: priorApiUrl
|
|
35981
|
+
apiUrl: priorApiUrl,
|
|
35806
35982
|
// Refreshed from Pulumi outputs after deploy succeeds
|
|
35983
|
+
variant: "pulumi"
|
|
35807
35984
|
};
|
|
35808
35985
|
savedMetadata.timestamp = deployedAt;
|
|
35809
35986
|
await saveConnectionMetadata(savedMetadata);
|
|
35987
|
+
const { url: migrationUrl, notes: connectionNotes } = normalizeDatabaseUrl(databaseUrl);
|
|
35988
|
+
for (const note22 of connectionNotes) {
|
|
35989
|
+
progress.info(note22);
|
|
35990
|
+
}
|
|
35810
35991
|
await progress.execute("Running database migrations", async () => {
|
|
35811
35992
|
const { Pool } = await import("pg");
|
|
35812
35993
|
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
35813
35994
|
const { migrate } = await import("drizzle-orm/node-postgres/migrator");
|
|
35814
|
-
const pool = new Pool({ connectionString:
|
|
35995
|
+
const pool = new Pool({ connectionString: migrationUrl });
|
|
35815
35996
|
const db = drizzle(pool);
|
|
35816
35997
|
try {
|
|
35817
35998
|
await migrate(db, { migrationsFolder: bundledMigrationsDir });
|
|
35999
|
+
} catch (error) {
|
|
36000
|
+
throw new Error(describeMigrationFailure(error));
|
|
35818
36001
|
} finally {
|
|
35819
36002
|
await pool.end();
|
|
35820
36003
|
}
|
|
@@ -36086,7 +36269,7 @@ Run ${pc50.cyan("wraps selfhost deploy")} to deploy the self-hosted control plan
|
|
|
36086
36269
|
);
|
|
36087
36270
|
console.log(
|
|
36088
36271
|
`
|
|
36089
|
-
The deployment may have failed partway through.
|
|
36272
|
+
The deployment may have failed partway through. Run ${pc50.cyan("pnpm selfhost:upgrade")} from your fork (SST deployments) or re-run ${pc50.cyan("wraps selfhost deploy")} (API-only deployments) to complete it.
|
|
36090
36273
|
`
|
|
36091
36274
|
);
|
|
36092
36275
|
process.exit(1);
|
|
@@ -36397,15 +36580,30 @@ init_output();
|
|
|
36397
36580
|
init_region_resolver();
|
|
36398
36581
|
function displaySelfhostStatus(options) {
|
|
36399
36582
|
const lines = [];
|
|
36400
|
-
|
|
36583
|
+
const incomplete = !(options.apiUrl && options.appUrl);
|
|
36584
|
+
lines.push(
|
|
36585
|
+
incomplete ? pc53.bold(pc53.yellow("Self-Hosted Control Plane Incomplete")) : pc53.bold(pc53.green("Self-Hosted Control Plane Active"))
|
|
36586
|
+
);
|
|
36587
|
+
if (incomplete) {
|
|
36588
|
+
lines.push(
|
|
36589
|
+
pc53.yellow(
|
|
36590
|
+
`A previous deploy did not finish. Run ${pc53.cyan("pnpm selfhost:upgrade")} from your fork to complete it.`
|
|
36591
|
+
)
|
|
36592
|
+
);
|
|
36593
|
+
}
|
|
36401
36594
|
lines.push("");
|
|
36402
36595
|
lines.push(pc53.bold("API"));
|
|
36403
|
-
lines.push(` URL: ${pc53.cyan(options.apiUrl)}`);
|
|
36596
|
+
lines.push(` URL: ${pc53.cyan(options.apiUrl || "(not deployed)")}`);
|
|
36404
36597
|
lines.push(` Region: ${pc53.cyan(options.region)}`);
|
|
36405
36598
|
lines.push(` Deployed: ${pc53.dim(options.deployedAt)}`);
|
|
36599
|
+
if (options.variant) {
|
|
36600
|
+
lines.push(
|
|
36601
|
+
` Type: ${pc53.cyan(options.variant === "sst" ? "Full platform (SST, deployed from fork)" : "API-only control plane (CLI)")}`
|
|
36602
|
+
);
|
|
36603
|
+
}
|
|
36406
36604
|
lines.push("");
|
|
36407
36605
|
lines.push(pc53.bold("Configuration"));
|
|
36408
|
-
lines.push(` App URL: ${pc53.cyan(options.appUrl)}`);
|
|
36606
|
+
lines.push(` App URL: ${pc53.cyan(options.appUrl || "(not deployed)")}`);
|
|
36409
36607
|
lines.push(` License Key: ${pc53.dim(`${options.licenseKeyPrefix}...`)}`);
|
|
36410
36608
|
if (options.neonProjectId) {
|
|
36411
36609
|
lines.push(` Neon Project: ${pc53.dim(options.neonProjectId)}`);
|
|
@@ -36434,7 +36632,8 @@ async function selfhostStatus(options) {
|
|
|
36434
36632
|
clack50.log.error("No self-hosted deployment found");
|
|
36435
36633
|
console.log(
|
|
36436
36634
|
`
|
|
36437
|
-
Run ${pc53.cyan("
|
|
36635
|
+
Run ${pc53.cyan("pnpm selfhost:deploy")} from your fork to deploy the full platform (API + dashboard),
|
|
36636
|
+
or ${pc53.cyan("wraps selfhost deploy")} for the API-only control plane.
|
|
36438
36637
|
`
|
|
36439
36638
|
);
|
|
36440
36639
|
process.exit(1);
|
|
@@ -36443,13 +36642,23 @@ Run ${pc53.cyan("wraps selfhost deploy")} to deploy the self-hosted control plan
|
|
|
36443
36642
|
const selfhostService = metadata.services.selfhost;
|
|
36444
36643
|
const config2 = selfhostService.config;
|
|
36445
36644
|
const apiUrl = await reconcileSelfhostApiUrl(metadata, region) ?? selfhostService.apiUrl;
|
|
36645
|
+
if (!selfhostService.variant) {
|
|
36646
|
+
const inferred = await detectSelfhostVariant(region);
|
|
36647
|
+
if (inferred) {
|
|
36648
|
+
selfhostService.variant = inferred;
|
|
36649
|
+
metadata.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
36650
|
+
await saveConnectionMetadata(metadata);
|
|
36651
|
+
}
|
|
36652
|
+
}
|
|
36653
|
+
const variant = selfhostService.variant;
|
|
36446
36654
|
const statusData = {
|
|
36447
36655
|
region,
|
|
36448
36656
|
apiUrl,
|
|
36449
36657
|
deployedAt: selfhostService.deployedAt,
|
|
36450
36658
|
neonProjectId: config2.neonProjectId,
|
|
36451
36659
|
appUrl: config2.appUrl,
|
|
36452
|
-
licenseKeyPrefix: config2.licenseKey.slice(0, 12)
|
|
36660
|
+
licenseKeyPrefix: config2.licenseKey.slice(0, 12),
|
|
36661
|
+
variant
|
|
36453
36662
|
};
|
|
36454
36663
|
if (isJsonMode()) {
|
|
36455
36664
|
jsonSuccess("selfhost.status", statusData);
|
|
@@ -36459,7 +36668,7 @@ Run ${pc53.cyan("wraps selfhost deploy")} to deploy the self-hosted control plan
|
|
|
36459
36668
|
console.log("");
|
|
36460
36669
|
clack50.log.info(pc53.bold("Commands:"));
|
|
36461
36670
|
console.log(
|
|
36462
|
-
` ${pc53.cyan("wraps selfhost upgrade")} - Rebuild and redeploy the API Lambda`
|
|
36671
|
+
variant === "sst" ? ` ${pc53.cyan("pnpm selfhost:upgrade")} (from your fork) - Redeploy the platform and run migrations` : ` ${pc53.cyan("wraps selfhost upgrade")} - Rebuild and redeploy the API Lambda`
|
|
36463
36672
|
);
|
|
36464
36673
|
trackCommand("selfhost:status", {
|
|
36465
36674
|
success: true,
|