@stacksjs/buddy 0.70.232 → 0.70.235
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/commands/build.js +12 -0
- package/dist/commands/deploy.js +41 -9
- package/dist/commands/docs/buddy-commands.js +2 -0
- package/dist/commands/docs/framework-repo.d.ts +29 -0
- package/dist/commands/docs/framework-repo.js +14 -0
- package/dist/commands/docs/framework-repo.test.d.ts +1 -0
- package/dist/commands/docs/framework-repo.test.js +68 -0
- package/dist/commands/docs/generated-artifacts.js +2 -0
- package/dist/commands/docs/links.js +2 -0
- package/dist/commands/extension.js +7 -2
- package/dist/commands/generate.js +36 -1
- package/dist/production-server.d.ts +14 -0
- package/dist/production-server.js +17 -2
- package/package.json +43 -45
package/dist/commands/build.js
CHANGED
|
@@ -18,6 +18,7 @@ export function build(buddy) {
|
|
|
18
18
|
buddy: "Build the Buddy binary",
|
|
19
19
|
functions: "Build your function library",
|
|
20
20
|
desktop: "Build the Desktop Application",
|
|
21
|
+
dmg: "Package the desktop build as a macOS .app inside a .dmg",
|
|
21
22
|
pages: "Build your frontend",
|
|
22
23
|
docs: "Build your documentation",
|
|
23
24
|
framework: "Build Stacks framework",
|
|
@@ -153,6 +154,17 @@ export function build(buddy) {
|
|
|
153
154
|
await outro("Exited", { startTime: perf, useSeconds: !0 });
|
|
154
155
|
process.exit(ExitCode.Success);
|
|
155
156
|
});
|
|
157
|
+
buddy.command("build:dmg", descriptions.dmg).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
158
|
+
log.debug("Running `buddy build:dmg` ...", options);
|
|
159
|
+
const perf = await intro("buddy build:dmg"), result = await runAction(Action.BuildDmg, options);
|
|
160
|
+
if (result.isErr) {
|
|
161
|
+
await outro("While running the build:dmg command, there was an issue", { startTime: perf, useSeconds: !0 }, result.error);
|
|
162
|
+
process.exit(ExitCode.FatalError);
|
|
163
|
+
}
|
|
164
|
+
console.log("");
|
|
165
|
+
await outro("Exited", { startTime: perf, useSeconds: !0 });
|
|
166
|
+
process.exit(ExitCode.Success);
|
|
167
|
+
});
|
|
156
168
|
buddy.command("build:stacks", "Build the Stacks framework.").option("-s, --stacks", descriptions.framework, { default: !0 }).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
157
169
|
log.debug("Running `buddy build:stacks` ...", options);
|
|
158
170
|
const startTime = await intro("buddy build:stacks"), result = await runAction(Action.BuildStacks, options);
|
package/dist/commands/deploy.js
CHANGED
|
@@ -831,7 +831,7 @@ async function runHetznerDeploy(args) {
|
|
|
831
831
|
".env.keys",
|
|
832
832
|
".env.production.bak",
|
|
833
833
|
".env.production.plain"
|
|
834
|
-
]
|
|
834
|
+
];
|
|
835
835
|
if (onlySite && !sites[onlySite]) {
|
|
836
836
|
log.error(`--site '${onlySite}' is not a configured site. Available: ${Object.keys(sites).join(", ")}`);
|
|
837
837
|
process.exit(ExitCode.FatalError);
|
|
@@ -851,9 +851,11 @@ async function runHetznerDeploy(args) {
|
|
|
851
851
|
log.info(`Building static site '${siteName}': ${site.build}`);
|
|
852
852
|
execSync(site.build, { stdio: verbose ? "inherit" : "pipe" });
|
|
853
853
|
}
|
|
854
|
-
const root = site.root || ".", tarballPath = join(tmpdir(), `${slug}-${siteName}-${sha}.tar.gz`);
|
|
854
|
+
const root = site.root || ".", tarballPath = join(tmpdir(), `${slug}-${siteName}-${sha}.tar.gz`), siteExcludes = Array.isArray(site.exclude) ? site.exclude.filter((entry) => typeof entry === "string" && entry.length > 0) : [], excludeArgs = [...tarExcludes, ...siteExcludes].flatMap((pattern) => [`--exclude='${pattern}'`, `--exclude='*/${pattern}'`]);
|
|
855
|
+
if (siteExcludes.length > 0)
|
|
856
|
+
log.info(`Excluding server-owned paths: ${siteExcludes.join(", ")}`);
|
|
855
857
|
log.info(`Packaging ${root} \u2192 ${tarballPath}...`);
|
|
856
|
-
execSync(`tar czf "${tarballPath}" ${
|
|
858
|
+
execSync(`tar czf "${tarballPath}" ${excludeArgs.join(" ")} -C "${root}" .`, { stdio: verbose ? "inherit" : "pipe", env: { ...process.env, COPYFILE_DISABLE: "1" } });
|
|
857
859
|
const sizeMb = Math.max(1, Math.round(statSync(tarballPath).size / 1048576));
|
|
858
860
|
log.info(`Release tarball: ~${sizeMb} MB`);
|
|
859
861
|
tarballs.set(siteName, tarballPath);
|
|
@@ -888,8 +890,32 @@ async function runHetznerDeploy(args) {
|
|
|
888
890
|
success: (m) => log.success(m)
|
|
889
891
|
}
|
|
890
892
|
});
|
|
893
|
+
let publishedDns = [];
|
|
891
894
|
if (ok)
|
|
892
|
-
await reconcileHetznerDns(onlySite ? { [onlySite]: sites[onlySite] } : sites, ip, log, ipv6);
|
|
895
|
+
publishedDns = await reconcileHetznerDns(onlySite ? { [onlySite]: sites[onlySite] } : sites, ip, log, ipv6);
|
|
896
|
+
if (ok && publishedDns.length > 0) {
|
|
897
|
+
log.info(`Issuing TLS for ${publishedDns.length} newly published record(s)...`);
|
|
898
|
+
try {
|
|
899
|
+
const { renewRpxCertificates, reloadRpxGateway } = await import("@stacksjs/ts-cloud"), gatewayOptions = {
|
|
900
|
+
rpxConfig: { ...tsCloudConfig, sites: sitesWithResolvedEnv },
|
|
901
|
+
environment,
|
|
902
|
+
driver,
|
|
903
|
+
logger: {
|
|
904
|
+
info: (m) => log.info(m),
|
|
905
|
+
warn: (m) => log.warn(m),
|
|
906
|
+
error: (m) => log.error(m),
|
|
907
|
+
step: (m) => log.info(m),
|
|
908
|
+
success: (m) => log.success(m)
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
await renewRpxCertificates(gatewayOptions);
|
|
912
|
+
await reloadRpxGateway(gatewayOptions);
|
|
913
|
+
log.success("TLS issued and gateway reloaded for the new record(s)");
|
|
914
|
+
} catch (err) {
|
|
915
|
+
log.warn(`TLS issuance after DNS failed: ${err?.message || err}`);
|
|
916
|
+
log.warn(` Until it succeeds, ${publishedDns.join(", ")} serves a fallback certificate.`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
893
919
|
if (ok)
|
|
894
920
|
await reconcileConfigDns(onlySite ? { [onlySite]: sites[onlySite] } : sites, log);
|
|
895
921
|
if (ok) {
|
|
@@ -1379,12 +1405,12 @@ async function reconcileConfigDns(sites, logger) {
|
|
|
1379
1405
|
}
|
|
1380
1406
|
}
|
|
1381
1407
|
async function reconcileHetznerDns(sites, ip, logger, ipv6) {
|
|
1382
|
-
const domains = new Set;
|
|
1408
|
+
const published = [], domains = new Set;
|
|
1383
1409
|
for (const site of Object.values(sites))
|
|
1384
1410
|
if (site?.domain && typeof site.domain === "string")
|
|
1385
1411
|
domains.add(site.domain.replace(/^www\./, ""));
|
|
1386
1412
|
if (domains.size === 0)
|
|
1387
|
-
return;
|
|
1413
|
+
return published;
|
|
1388
1414
|
const providerConfigs = [];
|
|
1389
1415
|
if (process.env.PORKBUN_API_KEY && process.env.PORKBUN_SECRET_KEY)
|
|
1390
1416
|
providerConfigs.push({ provider: "porkbun", apiKey: process.env.PORKBUN_API_KEY, secretKey: process.env.PORKBUN_SECRET_KEY });
|
|
@@ -1398,7 +1424,7 @@ async function reconcileHetznerDns(sites, ip, logger, ipv6) {
|
|
|
1398
1424
|
logger.warn("DNS: no DNS provider credentials found (PORKBUN_API_KEY/\u2026); skipping DNS reconciliation.");
|
|
1399
1425
|
for (const d of domains)
|
|
1400
1426
|
logger.info(` Point manually: A ${d} \u2192 ${ip} and A www.${d} \u2192 ${ip}`);
|
|
1401
|
-
return;
|
|
1427
|
+
return published;
|
|
1402
1428
|
}
|
|
1403
1429
|
const {
|
|
1404
1430
|
createDnsProvider,
|
|
@@ -1423,7 +1449,10 @@ async function reconcileHetznerDns(sites, ip, logger, ipv6) {
|
|
|
1423
1449
|
};
|
|
1424
1450
|
for (const domain of domains)
|
|
1425
1451
|
try {
|
|
1426
|
-
let provider = await detectDnsProvider(domain, providerConfigs)
|
|
1452
|
+
let provider = await detectDnsProvider(domain, providerConfigs).catch((err) => {
|
|
1453
|
+
logger.warn(` DNS: ignoring a configured provider for ${domain} - its credentials were rejected (${err?.message || err})`);
|
|
1454
|
+
return;
|
|
1455
|
+
});
|
|
1427
1456
|
if (!provider) {
|
|
1428
1457
|
const providerName = dnsProviderNameFromNameservers(await resolveAuthoritativeNameservers(domain)), providerConfig = providerConfigs.find((config) => config.provider === providerName);
|
|
1429
1458
|
if (providerConfig)
|
|
@@ -1443,14 +1472,17 @@ async function reconcileHetznerDns(sites, ip, logger, ipv6) {
|
|
|
1443
1472
|
}
|
|
1444
1473
|
for (const sub of ["", "www"]) {
|
|
1445
1474
|
const fqdn = sub ? `${sub}.${domain}` : domain, report = await reconcileAddressRecords({ provider, zone: domain, fqdn, ipv4: ip, ipv6 });
|
|
1446
|
-
for (const record of report.published)
|
|
1475
|
+
for (const record of report.published) {
|
|
1447
1476
|
logger.success(` DNS: ${record.fqdn} \u2192 ${record.content} (${provider.name})`);
|
|
1477
|
+
published.push(record.fqdn);
|
|
1478
|
+
}
|
|
1448
1479
|
for (const warning of report.warnings)
|
|
1449
1480
|
logger.warn(` DNS: ${warning}`);
|
|
1450
1481
|
}
|
|
1451
1482
|
} catch (err) {
|
|
1452
1483
|
logger.warn(` DNS: ${domain} reconciliation failed: ${err?.message || err}`);
|
|
1453
1484
|
}
|
|
1485
|
+
return published;
|
|
1454
1486
|
}
|
|
1455
1487
|
async function buildContainerImageWithPantry(args) {
|
|
1456
1488
|
const { slug, sites, verbose } = args, { execSync } = await import("node:child_process");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertFrameworkRepo } from "./framework-repo";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, resolve } from "node:path";
|
|
3
4
|
const root = resolve(import.meta.dir, "../../../../../../.."), outputPath = resolve(root, "docs/guide/buddy/commands.md"), cliPath = resolve(root, "storage/framework/core/buddy/src/cli.ts");
|
|
@@ -99,6 +100,7 @@ export function loadBuddyInventory() {
|
|
|
99
100
|
return inventory;
|
|
100
101
|
}
|
|
101
102
|
export async function run() {
|
|
103
|
+
assertFrameworkRepo(root, "docs:buddy");
|
|
102
104
|
const mode = process.argv.includes("--write") ? "write" : process.argv.includes("--check") ? "check" : null;
|
|
103
105
|
if (!mode) {
|
|
104
106
|
console.error("usage: bun storage/framework/core/buddy/src/commands/docs/buddy-commands.ts --write | --check");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These tools operate on the framework repository, not on an app.
|
|
3
|
+
*
|
|
4
|
+
* Each one locates its files by counting `../` up from its own source, so the
|
|
5
|
+
* root it edits is wherever the module happens to live. In a consumer app that
|
|
6
|
+
* resolves buddy from a linked framework checkout, that root is the framework
|
|
7
|
+
* checkout - so `buddy docs:artifacts` run from an application regenerated and
|
|
8
|
+
* overwrote the FRAMEWORK's `openapi.json`, from routes belonging to a
|
|
9
|
+
* different project entirely.
|
|
10
|
+
*
|
|
11
|
+
* The failure that surfaced first was milder and more confusing: the generator
|
|
12
|
+
* read the app's routes but compared them against the framework's artifact and
|
|
13
|
+
* reported "no routes were registered". Nobody would guess from that message
|
|
14
|
+
* that the command was editing another repository.
|
|
15
|
+
*
|
|
16
|
+
* So they refuse to run unless the working directory is inside the root they
|
|
17
|
+
* would write to.
|
|
18
|
+
*/
|
|
19
|
+
/** Whether `cwd` sits inside `root`. */
|
|
20
|
+
export declare function isInsideRoot(root: string, cwd: string): boolean;
|
|
21
|
+
/** Whether `root` looks like the Stacks framework repository. */
|
|
22
|
+
export declare function looksLikeFrameworkRepo(root: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Stop unless this is the framework repository and we are standing in it.
|
|
25
|
+
*
|
|
26
|
+
* Throws rather than warns: the alternative is silently rewriting a file in a
|
|
27
|
+
* repository the caller is not looking at.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertFrameworkRepo(root: string, command: string, cwd?: string): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
export function isInsideRoot(root, cwd) {
|
|
4
|
+
const rel = relative(resolve(root), resolve(cwd));
|
|
5
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
6
|
+
}
|
|
7
|
+
export function looksLikeFrameworkRepo(root) {
|
|
8
|
+
return existsSync(resolve(root, "storage/framework/core/buddy"));
|
|
9
|
+
}
|
|
10
|
+
export function assertFrameworkRepo(root, command, cwd = process.cwd()) {
|
|
11
|
+
if (looksLikeFrameworkRepo(root) && isInsideRoot(root, cwd))
|
|
12
|
+
return;
|
|
13
|
+
throw Error(`${command} operates on the Stacks framework repository and would write to ${root}, which is not where you are (${cwd}). Run it from a framework checkout; an application has no generated framework artifacts to check.`);
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { assertFrameworkRepo, isInsideRoot, looksLikeFrameworkRepo } from "./framework-repo";
|
|
6
|
+
describe("isInsideRoot", () => {
|
|
7
|
+
it("accepts the root itself and anything under it", () => {
|
|
8
|
+
expect(isInsideRoot("/a/b", "/a/b")).toBe(!0);
|
|
9
|
+
expect(isInsideRoot("/a/b", "/a/b/c/d")).toBe(!0);
|
|
10
|
+
});
|
|
11
|
+
it("rejects a sibling and a parent", () => {
|
|
12
|
+
expect(isInsideRoot("/a/b", "/a/c")).toBe(!1);
|
|
13
|
+
expect(isInsideRoot("/a/b", "/a")).toBe(!1);
|
|
14
|
+
});
|
|
15
|
+
it("rejects another project entirely", () => {
|
|
16
|
+
expect(isInsideRoot("/Users/x/Code/stacks", "/Users/x/Code/Apps/reviewos.org")).toBe(!1);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("looksLikeFrameworkRepo", () => {
|
|
20
|
+
it("recognises a tree carrying the framework core", () => {
|
|
21
|
+
const dir = mkdtempSync(join(tmpdir(), "stacks-fw-"));
|
|
22
|
+
try {
|
|
23
|
+
mkdirSync(resolve(dir, "storage/framework/core/buddy"), { recursive: !0 });
|
|
24
|
+
expect(looksLikeFrameworkRepo(dir)).toBe(!0);
|
|
25
|
+
} finally {
|
|
26
|
+
rmSync(dir, { recursive: !0, force: !0 });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
it("does not mistake an ordinary application for it", () => {
|
|
30
|
+
const dir = mkdtempSync(join(tmpdir(), "stacks-app-"));
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(resolve(dir, "app"), { recursive: !0 });
|
|
33
|
+
expect(looksLikeFrameworkRepo(dir)).toBe(!1);
|
|
34
|
+
} finally {
|
|
35
|
+
rmSync(dir, { recursive: !0, force: !0 });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("assertFrameworkRepo", () => {
|
|
40
|
+
it("passes inside the framework repo", () => {
|
|
41
|
+
const dir = mkdtempSync(join(tmpdir(), "stacks-fw-"));
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(resolve(dir, "storage/framework/core/buddy"), { recursive: !0 });
|
|
44
|
+
expect(() => assertFrameworkRepo(dir, "docs:artifacts", dir)).not.toThrow();
|
|
45
|
+
} finally {
|
|
46
|
+
rmSync(dir, { recursive: !0, force: !0 });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
it("refuses when run from somewhere else, and says where it would have written", () => {
|
|
50
|
+
const framework = mkdtempSync(join(tmpdir(), "stacks-fw-")), app = mkdtempSync(join(tmpdir(), "stacks-app-"));
|
|
51
|
+
try {
|
|
52
|
+
mkdirSync(resolve(framework, "storage/framework/core/buddy"), { recursive: !0 });
|
|
53
|
+
expect(() => assertFrameworkRepo(framework, "docs:artifacts", app)).toThrow(/docs:artifacts/);
|
|
54
|
+
expect(() => assertFrameworkRepo(framework, "docs:artifacts", app)).toThrow(new RegExp(framework));
|
|
55
|
+
} finally {
|
|
56
|
+
rmSync(framework, { recursive: !0, force: !0 });
|
|
57
|
+
rmSync(app, { recursive: !0, force: !0 });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
it("refuses when the root is not a framework tree at all", () => {
|
|
61
|
+
const dir = mkdtempSync(join(tmpdir(), "stacks-app-"));
|
|
62
|
+
try {
|
|
63
|
+
expect(() => assertFrameworkRepo(dir, "docs:links", dir)).toThrow();
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(dir, { recursive: !0, force: !0 });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { assertFrameworkRepo } from "./framework-repo";
|
|
3
4
|
import { generateOpenApi } from "../../../../api/src/generate-openapi";
|
|
4
5
|
import { renderOpenApiTypes } from "../../../../api/src/generate-types";
|
|
5
6
|
const root = resolve(import.meta.dir, "../../../../../../.."), openApiPath = resolve(root, "storage/framework/api/openapi.json"), apiTypesPath = resolve(root, "storage/framework/api/api-types.ts");
|
|
@@ -51,6 +52,7 @@ Run bun run docs:artifacts and review the generated diff.`);
|
|
|
51
52
|
console.log(`Generated API artifacts are current (${Object.keys(JSON.parse(expected.openApi).paths).length} paths)`);
|
|
52
53
|
}
|
|
53
54
|
export async function run() {
|
|
55
|
+
assertFrameworkRepo(root, "docs:artifacts");
|
|
54
56
|
try {
|
|
55
57
|
if (process.argv.includes("--write"))
|
|
56
58
|
await write();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertFrameworkRepo } from "./framework-repo";
|
|
1
2
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
3
4
|
const root = resolve(import.meta.dir, "../../../../../../.."), docsDir = resolve(root, "docs"), INLINE_LINK = /\[[^\]]*\]\(([^)]+)\)/g;
|
|
@@ -68,6 +69,7 @@ export function checkDocsLinks(docsRoot = docsDir) {
|
|
|
68
69
|
return broken;
|
|
69
70
|
}
|
|
70
71
|
export async function run() {
|
|
72
|
+
assertFrameworkRepo(root, "docs:links");
|
|
71
73
|
const broken = checkDocsLinks();
|
|
72
74
|
if (broken.length === 0)
|
|
73
75
|
console.log("\u2713 All internal documentation links resolve.");
|
|
@@ -172,8 +172,13 @@ export function extension(buddy) {
|
|
|
172
172
|
} else
|
|
173
173
|
log.success(`Extension payload synced \u2192 ${resources}`);
|
|
174
174
|
});
|
|
175
|
-
buddy.command("extension:safari:publish", "Archive and validate or upload the Safari app to App Store Connect").option("--version <version>", "Override the marketing version (defaults to package.json)").option("--build-number <number>", "CFBundleVersion (defaults to GITHUB_RUN_NUMBER or Unix time)").option("--team-id <id>", "Apple Developer team (defaults to config safariTeamId)").option("--api-key-id <id>", "App Store Connect API key ID").option("--api-issuer-id <id>", "App Store Connect API issuer ID").option("--api-key-path <path>", "Path to the App Store Connect AuthKey_*.p8 file").option("--validate-only", "Create and validate the archive without uploading it").option("--platform <platform>", "Publish macos, ios, or all (defaults to config safariPlatforms)").action(async (options) => {
|
|
176
|
-
const { publishSafariApp } = await import("@stacksjs/browser-extension"), { config, version } = await load()
|
|
175
|
+
buddy.command("extension:safari:publish", "Archive and validate or upload the Safari app to App Store Connect").option("--version <version>", "Override the marketing version (defaults to package.json)").option("--build-number <number>", "CFBundleVersion (defaults to GITHUB_RUN_NUMBER or Unix time)").option("--team-id <id>", "Apple Developer team (defaults to config safariTeamId)").option("--api-key-id <id>", "App Store Connect API key ID").option("--api-issuer-id <id>", "App Store Connect API issuer ID").option("--api-key-path <path>", "Path to the App Store Connect AuthKey_*.p8 file").option("--validate-only", "Create and validate the archive without uploading it").option("--platform <platform>", "Publish macos, ios, or all (defaults to config safariPlatforms)").option("--skip-screenshots", "Do not regenerate the App Store screenshot set before publishing").action(async (options) => {
|
|
176
|
+
const { publishSafariApp } = await import("@stacksjs/browser-extension"), { config, version } = await load();
|
|
177
|
+
if (!options.skipScreenshots) {
|
|
178
|
+
const { generateProjectImages } = await import("@stacksjs/actions");
|
|
179
|
+
await generateProjectImages({ only: ["app-store"] });
|
|
180
|
+
}
|
|
181
|
+
const result = await publishSafariApp(config, {
|
|
177
182
|
version: options.version ?? version,
|
|
178
183
|
buildNumber: options.buildNumber,
|
|
179
184
|
teamId: options.teamId,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
generateLibEntries,
|
|
7
7
|
generateOpenApiSpec,
|
|
8
8
|
generatePantryConfig,
|
|
9
|
+
generateProjectImages,
|
|
9
10
|
generateTypes,
|
|
10
11
|
generateVsCodeCustomData,
|
|
11
12
|
generateWebTypes,
|
|
@@ -26,11 +27,15 @@ export function generate(buddy) {
|
|
|
26
27
|
coreSymlink: "Generate symlink of the core framework to the project root",
|
|
27
28
|
pantry: "Generate the pantry configuration file",
|
|
28
29
|
openApi: "Generate the OpenAPI specification",
|
|
30
|
+
images: "Generate every image declared in config/images.ts",
|
|
31
|
+
og: "Generate the social cards used by link previews",
|
|
32
|
+
appStore: "Generate the App Store screenshot set",
|
|
33
|
+
appIcons: "Generate the app icon and favicon sets",
|
|
29
34
|
select: "What are you trying to generate?",
|
|
30
35
|
project: "Target a specific project",
|
|
31
36
|
verbose: "Enable verbose output"
|
|
32
37
|
};
|
|
33
|
-
buddy.command("generate", descriptions.command).option("-t, --types", descriptions.types).option("-e, --entries", descriptions.entries).option("-w, --web-types", descriptions.webTypes).option("-c, --custom-data", descriptions.customData).option("-i, --ide-helpers", descriptions.ideHelpers).option("-c, --component-meta", descriptions.componentMeta).option("-p, --pantry", descriptions.pantry).option("-o, --openapi", descriptions.openApi).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--core-symlink", descriptions.coreSymlink).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
38
|
+
buddy.command("generate", descriptions.command).option("-t, --types", descriptions.types).option("-e, --entries", descriptions.entries).option("-w, --web-types", descriptions.webTypes).option("-c, --custom-data", descriptions.customData).option("-i, --ide-helpers", descriptions.ideHelpers).option("-c, --component-meta", descriptions.componentMeta).option("-p, --pantry", descriptions.pantry).option("-o, --openapi", descriptions.openApi).option("--images", descriptions.images).option("-p, --project [project]", descriptions.project, { default: !1 }).option("--core-symlink", descriptions.coreSymlink).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
34
39
|
log.debug("Running `buddy generate` ...", options);
|
|
35
40
|
await startGenerationProcess(options);
|
|
36
41
|
process.exit(ExitCode.Success);
|
|
@@ -100,5 +105,35 @@ export function generate(buddy) {
|
|
|
100
105
|
log.debug("Running `buddy core-symlink` ...", options);
|
|
101
106
|
await generateCoreSymlink();
|
|
102
107
|
});
|
|
108
|
+
buddy.command("generate:images", descriptions.images).alias("images:generate").option("--social", "Only build the social cards").option("--app-store", "Only build the App Store screenshots").option("--app-icons", "Only build the app icons and favicons").option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
109
|
+
log.debug("Running `buddy generate:images` ...", options);
|
|
110
|
+
const perf = await intro("buddy generate:images"), only = [];
|
|
111
|
+
if (options.social)
|
|
112
|
+
only.push("social");
|
|
113
|
+
if (options.appStore)
|
|
114
|
+
only.push("app-store");
|
|
115
|
+
if (options.appIcons)
|
|
116
|
+
only.push("app-icons");
|
|
117
|
+
await generateProjectImages({ only, verbose: options.verbose });
|
|
118
|
+
await outro("Generated images", { startTime: perf, useSeconds: !0 });
|
|
119
|
+
});
|
|
120
|
+
buddy.command("generate:og", descriptions.og).alias("generate:social").option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
121
|
+
log.debug("Running `buddy generate:og` ...", options);
|
|
122
|
+
const perf = await intro("buddy generate:og");
|
|
123
|
+
await generateProjectImages({ only: ["social"], verbose: options.verbose });
|
|
124
|
+
await outro("Generated social cards", { startTime: perf, useSeconds: !0 });
|
|
125
|
+
});
|
|
126
|
+
buddy.command("generate:app-store", descriptions.appStore).alias("generate:screenshots").option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
127
|
+
log.debug("Running `buddy generate:app-store` ...", options);
|
|
128
|
+
const perf = await intro("buddy generate:app-store");
|
|
129
|
+
await generateProjectImages({ only: ["app-store"], verbose: options.verbose });
|
|
130
|
+
await outro("Generated App Store screenshots", { startTime: perf, useSeconds: !0 });
|
|
131
|
+
});
|
|
132
|
+
buddy.command("generate:app-icons", descriptions.appIcons).option("--verbose", descriptions.verbose, { default: !1 }).action(async (options) => {
|
|
133
|
+
log.debug("Running `buddy generate:app-icons` ...", options);
|
|
134
|
+
const perf = await intro("buddy generate:app-icons");
|
|
135
|
+
await generateProjectImages({ only: ["app-icons"], verbose: options.verbose });
|
|
136
|
+
await outro("Generated app icons", { startTime: perf, useSeconds: !0 });
|
|
137
|
+
});
|
|
103
138
|
onUnknownSubcommand(buddy, "generate");
|
|
104
139
|
}
|
|
@@ -18,4 +18,18 @@ export declare function resolveUserPartialsPath(cwd?: unknown, configuredDir?: s
|
|
|
18
18
|
* it {@link resolveUserPartialsPath} falls back to the conventions.
|
|
19
19
|
*/
|
|
20
20
|
export declare function loadStxPartialsDir(cwd?: unknown): Promise<string | undefined>;
|
|
21
|
+
/**` proxy target, or `null` when it cannot be
|
|
22
|
+
* known safely.
|
|
23
|
+
*
|
|
24
|
+
* `API_URL` and `PORT_API` are explicit operator intent and always win. The
|
|
25
|
+
* framework-wide `ports.api` default is only trusted *outside* deployed
|
|
26
|
+
* environments: locally one app owns the machine, so `127.0.0.1:3008` really is
|
|
27
|
+
* its own API. On a deployed box that assumption does not hold — ts-cloud runs
|
|
28
|
+
* many SSR sites side by side, each on its own port, and an unconfigured
|
|
29
|
+
* default resolves to whichever tenant happens to own it.
|
|
30
|
+
*
|
|
31
|
+
* Returning `null` makes the caller answer 502. That is the safe failure: the
|
|
32
|
+
* alternative is proxying authenticated requests into another app's process.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveApiBase(configuredPort?: number, env?: NodeJS.ProcessEnv): string | null;
|
|
21
35
|
export declare function startProductionServer(options?: { port?: string | number, verbose?: boolean }): Promise<void>;
|
|
@@ -89,6 +89,16 @@ function resolveCsrfMiddlewarePath() {
|
|
|
89
89
|
return vendored;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
export function resolveApiBase(configuredPort, env = process.env) {
|
|
93
|
+
if (env.API_URL)
|
|
94
|
+
return env.API_URL;
|
|
95
|
+
const explicitPort = Number(env.PORT_API);
|
|
96
|
+
if (explicitPort)
|
|
97
|
+
return `http://127.0.0.1:${explicitPort}`;
|
|
98
|
+
if (["production", "staging", "development"].includes((env.APP_ENV || "").toLowerCase()))
|
|
99
|
+
return null;
|
|
100
|
+
return `http://127.0.0.1:${configuredPort || 3008}`;
|
|
101
|
+
}
|
|
92
102
|
export async function startProductionServer(options) {
|
|
93
103
|
if (options?.port)
|
|
94
104
|
process.env.PORT = String(options.port);
|
|
@@ -111,7 +121,7 @@ export async function startProductionServer(options) {
|
|
|
111
121
|
} catch {}
|
|
112
122
|
if (!stxServe)
|
|
113
123
|
({ serve: stxServe } = await import("bun-plugin-stx/serve"));
|
|
114
|
-
const stxModule = await resolveVendoredStxModule(), { site: siteConfig, i18n: i18nConfig } = await loadStxSiteConfig(), userViewsPath = "resources/views", defaultsResources = resolveDefaultsResources(), defaultViewsPath = join(defaultsResources, "views"), userLayoutsPath = existsSync("resources/views/layouts") ? "resources/views/layouts" : "resources/layouts", userPartialsPath = resolveUserPartialsPath(process.cwd(), await loadStxPartialsDir()), apiBase =
|
|
124
|
+
const stxModule = await resolveVendoredStxModule(), { site: siteConfig, i18n: i18nConfig } = await loadStxSiteConfig(), userViewsPath = "resources/views", defaultsResources = resolveDefaultsResources(), defaultViewsPath = join(defaultsResources, "views"), userLayoutsPath = existsSync("resources/views/layouts") ? "resources/views/layouts" : "resources/layouts", userPartialsPath = resolveUserPartialsPath(process.cwd(), await loadStxPartialsDir()), apiBase = resolveApiBase(config.ports?.api);
|
|
115
125
|
log.info(`Starting production server on port ${port}...`);
|
|
116
126
|
await stxServe({
|
|
117
127
|
patterns: [userViewsPath, defaultViewsPath],
|
|
@@ -132,13 +142,18 @@ export async function startProductionServer(options) {
|
|
|
132
142
|
if (gated)
|
|
133
143
|
return gated;
|
|
134
144
|
const url = new URL(req.url);
|
|
135
|
-
if (isApiBoundRequest(req, url.pathname))
|
|
145
|
+
if (isApiBoundRequest(req, url.pathname)) {
|
|
146
|
+
if (!apiBase) {
|
|
147
|
+
log.error(`No API target configured for ${url.pathname}. This app shares its host with other ` + "deployments, so there is no safe default port to guess \u2014 refusing to proxy. Set " + "PORT_API (or API_URL) for this site, and deploy an `api` site on its own port.");
|
|
148
|
+
return new Response("Bad Gateway", { status: 502 });
|
|
149
|
+
}
|
|
136
150
|
try {
|
|
137
151
|
return await proxyToBackend(req, apiBase);
|
|
138
152
|
} catch (error) {
|
|
139
153
|
log.error(`API proxy to ${apiBase} failed: ${error.message}`);
|
|
140
154
|
return new Response("Bad Gateway", { status: 502 });
|
|
141
155
|
}
|
|
156
|
+
}
|
|
142
157
|
if (existsSync(join(process.cwd(), "resources/views/blog.stx"))) {
|
|
143
158
|
const { renderBlogFeed } = await import("@stacksjs/actions/blog"), feed = await renderBlogFeed(req);
|
|
144
159
|
if (feed)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.235",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -41,20 +41,17 @@
|
|
|
41
41
|
"exports": {
|
|
42
42
|
".": {
|
|
43
43
|
"types": "./dist/index.d.ts",
|
|
44
|
-
"development": "./src/index.ts",
|
|
45
44
|
"bun": "./dist/index.js",
|
|
46
45
|
"import": "./dist/index.js",
|
|
47
46
|
"default": "./dist/index.js"
|
|
48
47
|
},
|
|
49
48
|
"./cli": {
|
|
50
49
|
"types": "./dist/cli.d.ts",
|
|
51
|
-
"development": "./src/cli.ts",
|
|
52
50
|
"bun": "./dist/cli.js",
|
|
53
51
|
"import": "./dist/cli.js",
|
|
54
52
|
"default": "./dist/cli.js"
|
|
55
53
|
},
|
|
56
54
|
"./*": {
|
|
57
|
-
"development": "./src/*",
|
|
58
55
|
"bun": "./dist/*",
|
|
59
56
|
"import": "./dist/*",
|
|
60
57
|
"default": "./dist/*"
|
|
@@ -91,52 +88,53 @@
|
|
|
91
88
|
"prepublishOnly": "bun run build"
|
|
92
89
|
},
|
|
93
90
|
"dependencies": {
|
|
94
|
-
"@stacksjs/actions": "^0.70.
|
|
95
|
-
"@stacksjs/ai": "^0.70.
|
|
96
|
-
"@stacksjs/alias": "^0.70.
|
|
97
|
-
"@stacksjs/arrays": "^0.70.
|
|
98
|
-
"@stacksjs/auth": "^0.70.
|
|
99
|
-
"@stacksjs/build": "^0.70.
|
|
100
|
-
"@stacksjs/cache": "^0.70.
|
|
101
|
-
"@stacksjs/cli": "^0.70.
|
|
91
|
+
"@stacksjs/actions": "^0.70.235",
|
|
92
|
+
"@stacksjs/ai": "^0.70.235",
|
|
93
|
+
"@stacksjs/alias": "^0.70.235",
|
|
94
|
+
"@stacksjs/arrays": "^0.70.235",
|
|
95
|
+
"@stacksjs/auth": "^0.70.235",
|
|
96
|
+
"@stacksjs/build": "^0.70.235",
|
|
97
|
+
"@stacksjs/cache": "^0.70.235",
|
|
98
|
+
"@stacksjs/cli": "^0.70.235",
|
|
102
99
|
"@stacksjs/clapp": "^0.2.12",
|
|
103
|
-
"@stacksjs/cloud": "^0.70.
|
|
104
|
-
"@stacksjs/collections": "^0.70.
|
|
105
|
-
"@stacksjs/config": "^0.70.
|
|
106
|
-
"@stacksjs/database": "^0.70.
|
|
107
|
-
"@stacksjs/desktop": "^0.70.
|
|
108
|
-
"@stacksjs/dns": "^0.70.
|
|
109
|
-
"@stacksjs/email": "^0.70.
|
|
110
|
-
"@stacksjs/enums": "^0.70.
|
|
111
|
-
"@stacksjs/error-handling": "^0.70.
|
|
112
|
-
"@stacksjs/events": "^0.70.
|
|
113
|
-
"@stacksjs/git": "^0.70.
|
|
100
|
+
"@stacksjs/cloud": "^0.70.235",
|
|
101
|
+
"@stacksjs/collections": "^0.70.235",
|
|
102
|
+
"@stacksjs/config": "^0.70.235",
|
|
103
|
+
"@stacksjs/database": "^0.70.235",
|
|
104
|
+
"@stacksjs/desktop-build": "^0.70.235",
|
|
105
|
+
"@stacksjs/dns": "^0.70.235",
|
|
106
|
+
"@stacksjs/email": "^0.70.235",
|
|
107
|
+
"@stacksjs/enums": "^0.70.235",
|
|
108
|
+
"@stacksjs/error-handling": "^0.70.235",
|
|
109
|
+
"@stacksjs/events": "^0.70.235",
|
|
110
|
+
"@stacksjs/git": "^0.70.235",
|
|
114
111
|
"@stacksjs/gitit": "^0.2.5",
|
|
115
|
-
"@stacksjs/health": "^0.70.
|
|
112
|
+
"@stacksjs/health": "^0.70.235",
|
|
116
113
|
"@stacksjs/dnsx": "^0.2.3",
|
|
117
114
|
"@stacksjs/httx": "^0.1.10",
|
|
118
|
-
"@stacksjs/
|
|
119
|
-
"@stacksjs/
|
|
120
|
-
"@stacksjs/
|
|
121
|
-
"@stacksjs/
|
|
122
|
-
"@stacksjs/
|
|
123
|
-
"@stacksjs/
|
|
124
|
-
"@stacksjs/
|
|
125
|
-
"@stacksjs/
|
|
126
|
-
"@stacksjs/
|
|
127
|
-
"@stacksjs/
|
|
115
|
+
"@stacksjs/image": "^0.70.235",
|
|
116
|
+
"@stacksjs/lint": "^0.70.235",
|
|
117
|
+
"@stacksjs/logging": "^0.70.235",
|
|
118
|
+
"@stacksjs/notifications": "^0.70.235",
|
|
119
|
+
"@stacksjs/objects": "^0.70.235",
|
|
120
|
+
"@stacksjs/orm": "^0.70.235",
|
|
121
|
+
"@stacksjs/path": "^0.70.235",
|
|
122
|
+
"@stacksjs/skills": "^0.70.235",
|
|
123
|
+
"@stacksjs/payments": "^0.70.235",
|
|
124
|
+
"@stacksjs/realtime": "^0.70.235",
|
|
125
|
+
"@stacksjs/router": "^0.70.235",
|
|
128
126
|
"@stacksjs/rpx": "^0.11.42",
|
|
129
|
-
"@stacksjs/search-engine": "^0.70.
|
|
130
|
-
"@stacksjs/security": "^0.70.
|
|
131
|
-
"@stacksjs/server": "^0.70.
|
|
132
|
-
"@stacksjs/storage": "^0.70.
|
|
133
|
-
"@stacksjs/strings": "^0.70.
|
|
134
|
-
"@stacksjs/testing": "^0.70.
|
|
135
|
-
"@stacksjs/tunnel": "^0.70.
|
|
136
|
-
"@stacksjs/types": "^0.70.
|
|
137
|
-
"@stacksjs/ui": "^0.70.
|
|
138
|
-
"@stacksjs/utils": "^0.70.
|
|
139
|
-
"@stacksjs/validation": "^0.70.
|
|
127
|
+
"@stacksjs/search-engine": "^0.70.235",
|
|
128
|
+
"@stacksjs/security": "^0.70.235",
|
|
129
|
+
"@stacksjs/server": "^0.70.235",
|
|
130
|
+
"@stacksjs/storage": "^0.70.235",
|
|
131
|
+
"@stacksjs/strings": "^0.70.235",
|
|
132
|
+
"@stacksjs/testing": "^0.70.235",
|
|
133
|
+
"@stacksjs/tunnel": "^0.70.235",
|
|
134
|
+
"@stacksjs/types": "^0.70.235",
|
|
135
|
+
"@stacksjs/ui": "^0.70.235",
|
|
136
|
+
"@stacksjs/utils": "^0.70.235",
|
|
137
|
+
"@stacksjs/validation": "^0.70.235",
|
|
140
138
|
"@stacksjs/ts-cloud": "^0.7.92",
|
|
141
139
|
"ajv": "^8.20.0",
|
|
142
140
|
"ajv-formats": "^3.0.1",
|