@stacksjs/buddy 0.74.39 → 0.74.41
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/setup.js +1 -1
- package/dist/production-server.d.ts +28 -9
- package/dist/production-server.js +1 -1
- package/package.json +55 -55
package/dist/commands/setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cpSync,existsSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import process from"node:process";import{runAction}from"@stacksjs/actions";import{log,onUnknownSubcommand,runCommand}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{handleError}from"@stacksjs/error-handling";import{path as p}from"@stacksjs/path";import{copyFile,storage}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";import{setupPrettyDevEnvironment}from"./dev";import{runInitialMigration}from"../initial-migration";import{resultFailed}from"../result";function getTimeoutMs(envVar,fallbackMs){const value=Number(process.env[envVar]);if(Number.isFinite(value)&&value>0)return value;return fallbackMs}const PANTRY_CHECK_TIMEOUT_MS=getTimeoutMs("PANTRY_CHECK_TIMEOUT_MS",15000),PANTRY_INSTALL_TIMEOUT_MS=getTimeoutMs("PANTRY_INSTALL_TIMEOUT_MS",600000),PANTRY_DEPENDENCIES_TIMEOUT_MS=getTimeoutMs("PANTRY_DEPENDENCIES_TIMEOUT_MS",1200000),KEYGEN_TIMEOUT_MS=getTimeoutMs("KEYGEN_TIMEOUT_MS",120000),AWS_CONFIG_TIMEOUT_MS=getTimeoutMs("AWS_CONFIG_TIMEOUT_MS",900000);export function setup(buddy){const descriptions={setup:"This command ensures your project is setup correctly",ssl:"Setup SSL certificates and hosts file for HTTPS development",ai:"Set the project up for an AI coding agent (Claude Code, Codex, Cursor, Copilot, Gemini)",copy:"Copy the agent files instead of symlinking them, so they can be edited per project",force:"Overwrite files that already exist",ohMyZsh:"Enable Oh My Zsh",aws:"Ensures AWS is connected to the project",project:"Target a specific project",verbose:"Enable verbose output",domain:"Custom domain to setup (defaults to APP_URL)",skipHosts:"Skip adding domain to hosts file",skipTrust:"Skip trusting the certificate",skipAws:"Skip AWS configuration during setup",skipKeygen:"Skip generating an application key during setup"};buddy.command("setup",descriptions.setup).alias("ensure").option("-p, --project [project]",descriptions.project,{default:!1}).option("--skip-aws",descriptions.skipAws,{default:!1}).option("--skip-keygen",descriptions.skipKeygen,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup` ...",options);await ensurePantryInstalled();await optimizePantryDeps();await initializeProject(options)});buddy.command("setup:ssl",descriptions.ssl).alias("ssl:setup").option("-d, --domain [domain]",descriptions.domain).option("--skip-hosts",descriptions.skipHosts,{default:!1}).option("--skip-trust",descriptions.skipTrust,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup:ssl` ...",options);if(!await setupPrettyDevEnvironment({domain:options.domain,skipHosts:options.skipHosts,skipTrust:options.skipTrust,verbose:options.verbose})){log.warn("SSL setup completed with warnings");log.info("You may need to manually trust certificates or update hosts file")}});buddy.command("setup:ai [provider]",descriptions.ai).alias("ai:setup").option("--copy",descriptions.copy,{default:!1}).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(provider,options)=>{log.debug("Running `buddy setup:ai` ...",options);const{AI_PROVIDERS,isAiProvider,reportAiSetup,setupAiProvider}=await import("./setup-ai");let id=provider;if(!id){if(!process.stdin.isTTY){await log.error(`\`setup:ai\` needs a provider when stdin is not a terminal. Pass one: ${AI_PROVIDERS.map((entry)=>`buddy setup:ai ${entry.id}`).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const{select}=await import("@stacksjs/cli");id=await select({message:"Which AI coding agent do you use?",choices:AI_PROVIDERS.map((entry)=>({value:entry.id,label:entry.label})),initial:0})}if(!id||!isAiProvider(id)){await log.error(`Unknown AI provider: ${id}. Expected one of: ${AI_PROVIDERS.map((entry)=>entry.id).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const definition=AI_PROVIDERS.find((entry)=>entry.id===id);reportAiSetup(definition,setupAiProvider(id,{copy:options.copy,force:options.force}))});buddy.command("setup:oh-my-zsh",descriptions.ohMyZsh).alias("upgrade:oh-my-zsh").option("--verbose",descriptions.verbose,{default:!1}).action(async(_options)=>{log.debug("Running `buddy setup:oh-my-zsh` ...",_options);const result=await runAction(Action.UpgradeShell);if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}});onUnknownSubcommand(buddy,"setup")}async function isPantryInstalled(){try{return(await runCommand("pantry --version",{silent:!0,timeoutMs:PANTRY_CHECK_TIMEOUT_MS})).isOk}catch{return!1}}async function installPantry(){const bundledInstaller=p.frameworkPath("scripts/pantry-install"),command=existsSync(bundledInstaller)?[bundledInstaller]:["sh","-c","curl -fsSL https://pantry.dev | bash"],result=await runCommand(command,{timeoutMs:PANTRY_INSTALL_TIMEOUT_MS}),localBin=join(homedir(),".local","bin");if(!process.env.PATH?.split(":").includes(localBin))process.env.PATH=`${localBin}:${process.env.PATH||""}`;if(result.isOk&&await isPantryInstalled())return;if(resultFailed(result))handleError(result.error);else await log.error("Pantry installed but is not available on PATH. Open a new shell and run `buddy setup` again.");process.exit(ExitCode.FatalError)}export async function ensurePantryInstalled(){if(await isPantryInstalled())return;log.info("Pantry is required. Installing it from https://pantry.dev...");await installPantry()}export async function ensurePantryDependencies(cwd){await ensurePantryInstalled();log.info("Installing project dependencies with Pantry...");const result=await runCommand("pantry install",{cwd,timeoutMs:PANTRY_DEPENDENCIES_TIMEOUT_MS});if(resultFailed(result)){handleError(result.error);process.exit(ExitCode.FatalError)}if(existsSync(join(cwd,"package.json"))&&!existsSync(join(cwd,"node_modules"))){await log.error("Pantry completed without installing the project JavaScript dependencies.");process.exit(ExitCode.FatalError)}log.success("Installed project dependencies with Pantry")}function hasAppKey(cwd){const envPath=join(cwd,".env");if(!existsSync(envPath))return!1;return/^APP_KEY=.+$/m.test(readFileSync(envPath,"utf-8"))}export async function ensureAppKey(cwd){if(hasAppKey(cwd)||process.env.APP_KEY&&process.env.APP_KEY.length>0){log.success("APP_KEY existed");return}const keyResult=await runCommand("./buddy key:generate",{cwd,timeoutMs:KEYGEN_TIMEOUT_MS});if(resultFailed(keyResult)){handleError(keyResult.error);process.exit(ExitCode.FatalError)}log.success("Generated application key")}async function databaseIsReachable(){try{const{describeTarget,probeTargetDatabase,resolveConnectionTarget}=await import("@stacksjs/database"),target=resolveConnectionTarget();if(!target)return{ok:!0};const probe=await probeTargetDatabase(target);if(probe.ok)return{ok:!0};return{ok:!1,reason:`${describeTarget(target)} is not reachable yet (${probe.kind})`}}catch{return{ok:!0}}}async function initializeProject(options){const cwd=options.cwd||p.projectPath();await ensurePantryDependencies(cwd);await ensureEnvIsSet(options);if(!options.skipKeygen)await ensureAppKey(cwd);const migration=await runInitialMigration({appEnv:process.env.APP_ENV||process.env.NODE_ENV||"local",isReachable:databaseIsReachable,migrate:()=>runAction(Action.Migrate,{cwd}),failed:resultFailed,log});ensureIdeSettings(cwd);if(!options.skipAws){log.info("Ensuring AWS is connected...");try{const awsResult=await runCommand("./buddy configure:aws",{cwd,timeoutMs:AWS_CONFIG_TIMEOUT_MS});if(resultFailed(awsResult)){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(awsResult.error)}else log.success("Configured AWS")}catch(error){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(error)}}if(migration==="failed"){process.stderr.write(`
|
|
1
|
+
import{cpSync,existsSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import process from"node:process";import{runAction}from"@stacksjs/actions";import{log,onUnknownSubcommand,runCommand}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{handleError}from"@stacksjs/error-handling";import{path as p}from"@stacksjs/path";import{copyFile,storage}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";import{setupPrettyDevEnvironment}from"./dev";import{runInitialMigration}from"../initial-migration";import{resultFailed}from"../result";function getTimeoutMs(envVar,fallbackMs){const value=Number(process.env[envVar]);if(Number.isFinite(value)&&value>0)return value;return fallbackMs}const PANTRY_CHECK_TIMEOUT_MS=getTimeoutMs("PANTRY_CHECK_TIMEOUT_MS",15000),PANTRY_INSTALL_TIMEOUT_MS=getTimeoutMs("PANTRY_INSTALL_TIMEOUT_MS",600000),PANTRY_DEPENDENCIES_TIMEOUT_MS=getTimeoutMs("PANTRY_DEPENDENCIES_TIMEOUT_MS",1200000),KEYGEN_TIMEOUT_MS=getTimeoutMs("KEYGEN_TIMEOUT_MS",120000),AWS_CONFIG_TIMEOUT_MS=getTimeoutMs("AWS_CONFIG_TIMEOUT_MS",900000);export function setup(buddy){const descriptions={setup:"This command ensures your project is setup correctly",ssl:"Setup SSL certificates and hosts file for HTTPS development",ai:"Set the project up for an AI coding agent (Claude Code, Codex, Cursor, Copilot, Gemini)",copy:"Copy the agent files instead of symlinking them, so they can be edited per project",force:"Overwrite files that already exist",ohMyZsh:"Enable Oh My Zsh",aws:"Ensures AWS is connected to the project",project:"Target a specific project",verbose:"Enable verbose output",domain:"Custom domain to setup (defaults to APP_URL)",skipHosts:"Skip adding domain to hosts file",skipTrust:"Skip trusting the certificate",skipAws:"Skip AWS configuration during setup",skipKeygen:"Skip generating an application key during setup"};buddy.command("setup",descriptions.setup).alias("ensure").option("-p, --project [project]",descriptions.project,{default:!1}).option("--skip-aws",descriptions.skipAws,{default:!1}).option("--skip-keygen",descriptions.skipKeygen,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup` ...",options);if(process.argv.includes("--dry-run")){await log.error("`buddy setup` does not support --dry-run. It is a process-wide flag, so it appears in every command's help, but setup makes changes it cannot preview: it migrates the database and provisions the toolchain. Run it without the flag when you mean to.");process.exit(ExitCode.InvalidArgument)}await ensurePantryInstalled();await optimizePantryDeps();await initializeProject(options)});buddy.command("setup:ssl",descriptions.ssl).alias("ssl:setup").option("-d, --domain [domain]",descriptions.domain).option("--skip-hosts",descriptions.skipHosts,{default:!1}).option("--skip-trust",descriptions.skipTrust,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup:ssl` ...",options);if(!await setupPrettyDevEnvironment({domain:options.domain,skipHosts:options.skipHosts,skipTrust:options.skipTrust,verbose:options.verbose})){log.warn("SSL setup completed with warnings");log.info("You may need to manually trust certificates or update hosts file")}});buddy.command("setup:ai [provider]",descriptions.ai).alias("ai:setup").option("--copy",descriptions.copy,{default:!1}).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(provider,options)=>{log.debug("Running `buddy setup:ai` ...",options);const{AI_PROVIDERS,isAiProvider,reportAiSetup,setupAiProvider}=await import("./setup-ai");let id=provider;if(!id){if(!process.stdin.isTTY){await log.error(`\`setup:ai\` needs a provider when stdin is not a terminal. Pass one: ${AI_PROVIDERS.map((entry)=>`buddy setup:ai ${entry.id}`).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const{select}=await import("@stacksjs/cli");id=await select({message:"Which AI coding agent do you use?",choices:AI_PROVIDERS.map((entry)=>({value:entry.id,label:entry.label})),initial:0})}if(!id||!isAiProvider(id)){await log.error(`Unknown AI provider: ${id}. Expected one of: ${AI_PROVIDERS.map((entry)=>entry.id).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const definition=AI_PROVIDERS.find((entry)=>entry.id===id);reportAiSetup(definition,setupAiProvider(id,{copy:options.copy,force:options.force}))});buddy.command("setup:oh-my-zsh",descriptions.ohMyZsh).alias("upgrade:oh-my-zsh").option("--verbose",descriptions.verbose,{default:!1}).action(async(_options)=>{log.debug("Running `buddy setup:oh-my-zsh` ...",_options);const result=await runAction(Action.UpgradeShell);if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}});onUnknownSubcommand(buddy,"setup")}async function isPantryInstalled(){try{return(await runCommand("pantry --version",{silent:!0,timeoutMs:PANTRY_CHECK_TIMEOUT_MS})).isOk}catch{return!1}}async function installPantry(){const bundledInstaller=p.frameworkPath("scripts/pantry-install"),command=existsSync(bundledInstaller)?[bundledInstaller]:["sh","-c","curl -fsSL https://pantry.dev | bash"],result=await runCommand(command,{timeoutMs:PANTRY_INSTALL_TIMEOUT_MS}),localBin=join(homedir(),".local","bin");if(!process.env.PATH?.split(":").includes(localBin))process.env.PATH=`${localBin}:${process.env.PATH||""}`;if(result.isOk&&await isPantryInstalled())return;if(resultFailed(result))handleError(result.error);else await log.error("Pantry installed but is not available on PATH. Open a new shell and run `buddy setup` again.");process.exit(ExitCode.FatalError)}export async function ensurePantryInstalled(){if(await isPantryInstalled())return;log.info("Pantry is required. Installing it from https://pantry.dev...");await installPantry()}export async function ensurePantryDependencies(cwd){await ensurePantryInstalled();log.info("Installing project dependencies with Pantry...");const result=await runCommand("pantry install",{cwd,timeoutMs:PANTRY_DEPENDENCIES_TIMEOUT_MS});if(resultFailed(result)){handleError(result.error);process.exit(ExitCode.FatalError)}if(existsSync(join(cwd,"package.json"))&&!existsSync(join(cwd,"node_modules"))){await log.error("Pantry completed without installing the project JavaScript dependencies.");process.exit(ExitCode.FatalError)}log.success("Installed project dependencies with Pantry")}function hasAppKey(cwd){const envPath=join(cwd,".env");if(!existsSync(envPath))return!1;return/^APP_KEY=.+$/m.test(readFileSync(envPath,"utf-8"))}export async function ensureAppKey(cwd){if(hasAppKey(cwd)||process.env.APP_KEY&&process.env.APP_KEY.length>0){log.success("APP_KEY existed");return}const keyResult=await runCommand("./buddy key:generate",{cwd,timeoutMs:KEYGEN_TIMEOUT_MS});if(resultFailed(keyResult)){handleError(keyResult.error);process.exit(ExitCode.FatalError)}log.success("Generated application key")}async function databaseIsReachable(){try{const{describeTarget,probeTargetDatabase,resolveConnectionTarget}=await import("@stacksjs/database"),target=resolveConnectionTarget();if(!target)return{ok:!0};const probe=await probeTargetDatabase(target);if(probe.ok)return{ok:!0};return{ok:!1,reason:`${describeTarget(target)} is not reachable yet (${probe.kind})`}}catch{return{ok:!0}}}async function initializeProject(options){const cwd=options.cwd||p.projectPath();await ensurePantryDependencies(cwd);await ensureEnvIsSet(options);if(!options.skipKeygen)await ensureAppKey(cwd);const migration=await runInitialMigration({appEnv:process.env.APP_ENV||process.env.NODE_ENV||"local",isReachable:databaseIsReachable,migrate:()=>runAction(Action.Migrate,{cwd}),failed:resultFailed,log});ensureIdeSettings(cwd);if(!options.skipAws){log.info("Ensuring AWS is connected...");try{const awsResult=await runCommand("./buddy configure:aws",{cwd,timeoutMs:AWS_CONFIG_TIMEOUT_MS});if(resultFailed(awsResult)){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(awsResult.error)}else log.success("Configured AWS")}catch(error){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(error)}}if(migration==="failed"){process.stderr.write(`
|
|
2
2
|
Initial database migration FAILED, so this project is not set up.
|
|
3
3
|
`);process.stderr.write(`Its database did not receive the schema; the error is above.
|
|
4
4
|
`);process.stderr.write("Fix the migration, then run `./buddy migrate`.\n");await log.flush();process.exit(ExitCode.FatalError)}log.success("Project is setup");log.info("Run `./buddy doctor` anytime to check your setup. Happy coding! \uD83D\uDC99")}export function ensureIdeSettings(cwd){const source=p.frameworkPath("defaults/ide/vscode/.vscode"),destination=join(cwd,".vscode");if(existsSync(destination)){log.debug(".vscode already exists; keeping the project settings");return}if(!existsSync(source)){log.debug("No bundled VS Code settings found; skipping IDE setup");return}cpSync(source,destination,{recursive:!0});log.success("Installed project VS Code settings")}const DB_CONNECTION_PACKAGES={postgres:{name:"postgresql.org",version:"^17.10",service:"postgres"},mysql:{name:"mysql.com",version:"^9.2",service:"mysql"},sqlite:{name:"sqlite.org",version:"^3.47.2"}};function databaseAliases(connection,pkg){return[connection,pkg.name]}export function pantryDatabasePackage(connection){return DB_CONNECTION_PACKAGES[connection]}function detectDbPackage(cwd){const envPath=join(cwd,".env"),envExamplePath=join(cwd,".env.example"),filePath=existsSync(envPath)?envPath:existsSync(envExamplePath)?envExamplePath:void 0;if(!filePath)return;const match=readFileSync(filePath,"utf-8").match(/^DB_CONNECTION=(.+)$/m);if(!match)return;const value=match[1].trim().replace(/['"]/g,"");return pantryDatabasePackage(value)}export async function optimizePantryDeps(){const cwd=p.projectPath(),depsConfigPath=join(cwd,"config","deps.ts");if(!existsSync(depsConfigPath)){log.debug("No config/deps.ts found, skipping dependency optimization");return}let configDeps={},configServices=[],configDefined={};try{const mod=await import(depsConfigPath),config=mod.config||mod.default;if(config?.dependencies)configDeps={...config.dependencies};if(Array.isArray(config?.services?.autoStart))configServices=config.services.autoStart.filter((name)=>typeof name==="string");if(config?.services?.define&&typeof config.services.define==="object")configDefined=config.services.define}catch(err){log.debug("Could not load config/deps.ts, skipping dependency optimization");return}const dbPackage=detectDbPackage(cwd),autoStart=[...configServices];if(dbPackage){const selected=new Set(Object.entries(DB_CONNECTION_PACKAGES).filter(([,pkg])=>pkg.name===dbPackage.name).flatMap(([connection,pkg])=>databaseAliases(connection,pkg))),unused=new Set(Object.entries(DB_CONNECTION_PACKAGES).flatMap(([connection,pkg])=>databaseAliases(connection,pkg)).filter((alias)=>!selected.has(alias)));for(const pkg of Object.keys(configDeps)){const domain=pkg.split("/")[0];if(unused.has(domain)){log.info(`DB_CONNECTION selects ${dbPackage.name}, dropping unused ${pkg}`);delete configDeps[pkg]}}if(!Object.keys(configDeps).some((key)=>{const domain=key.split("/")[0];return selected.has(domain)})){log.info(`Detected DB_CONNECTION requires ${dbPackage.name}, adding to dependencies`);configDeps[dbPackage.name]=dbPackage.version}if(dbPackage.service&&!autoStart.includes(dbPackage.service))autoStart.push(dbPackage.service)}const lines=["# Auto-generated from config/deps.ts and .env sniffing.","# This file is regenerated on each `buddy setup` run.","#","# To learn more, please visit:","# https://stacksjs.com/docs/dependency-management","","dependencies:"];for(const[pkg,version]of Object.entries(configDeps))lines.push(` ${pkg}: ${version}`);const defined=Object.entries(configDefined);if(autoStart.length>0||defined.length>0){lines.push("","services:"," enabled: true");if(autoStart.length>0){lines.push(" autoStart:");for(const service of autoStart)lines.push(` - ${service}`)}if(defined.length>0){lines.push(" define:");for(const[name,definition]of defined){if(!definition||typeof definition!=="object")continue;lines.push(` ${name}:`);for(const[key,value]of Object.entries(definition)){if(value===void 0||value===null)continue;lines.push(` ${key}: ${String(value)}`)}}}}const depsYamlPath=join(cwd,"deps.yaml");writeFileSync(depsYamlPath,`${lines.join(`
|
|
@@ -40,17 +40,36 @@ export declare function startProductionServer(options?: { port?: string | number
|
|
|
40
40
|
* {@link applyDocumentCacheControl} is what keeps that safe.
|
|
41
41
|
*/
|
|
42
42
|
export declare function buildDocumentCacheControl(documents?: { maxAge?: number, staleWhileRevalidate?: number }): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Does this request belong to a signed-in visitor?
|
|
45
|
+
*
|
|
46
|
+
* A page rendered for someone in particular must never be handed to a shared
|
|
47
|
+
* cache, and the response alone cannot always say so — an app that keeps its
|
|
48
|
+
* session in `localStorage` sends no cookie back, and one that reuses an
|
|
49
|
+
* existing session sets none either. So the REQUEST is what decides.
|
|
50
|
+
*
|
|
51
|
+
* The CSRF cookie is excluded deliberately: it is a double-submit token every
|
|
52
|
+
* visitor gets, signed in or not, and treating it as a session would make
|
|
53
|
+
* every page uncacheable for everybody — which is exactly the state this is
|
|
54
|
+
* here to fix.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isAuthenticatedRequest(request: Request): boolean;
|
|
43
57
|
/**
|
|
44
58
|
* Mark a document cacheable, unless it is about one visitor.
|
|
45
59
|
*
|
|
46
|
-
*
|
|
47
|
-
* to remember:
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
60
|
+
* Two guards, and both are the framework's to enforce rather than each app's
|
|
61
|
+
* to remember:
|
|
62
|
+
*
|
|
63
|
+
* - the response sets a cookie — it is per-visitor by definition, and a
|
|
64
|
+
* shared cache told to reuse it serves that cookie to whoever asks next;
|
|
65
|
+
* - the request was authenticated — the page may hold someone's data even
|
|
66
|
+
* when the response sets nothing.
|
|
52
67
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
68
|
+
* Past those, the declared header REPLACES what is already there. That is the
|
|
69
|
+
* point: the page pipeline marks renders `no-store` by default, so a version
|
|
70
|
+
* that only filled in a missing header could never do anything, and an app
|
|
71
|
+
* that declared `documents` would get silence instead of caching. Declaring it
|
|
72
|
+
* is the app asserting these pages are shareable; the guards above are what
|
|
73
|
+
* keep that assertion from being taken on faith.
|
|
55
74
|
*/
|
|
56
|
-
export declare function applyDocumentCacheControl(response: Response, cacheControl?: string): Response;
|
|
75
|
+
export declare function applyDocumentCacheControl(request: Request, response: Response, cacheControl?: string): Response;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{join}from"node:path";import process from"node:process";import{installRequestContext,parseCookieHeader}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{siteConfigPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{resolveStxSource}from"./stx-source";function productionRequestSnapshot(){const{__stxServeContext:snapshot,__stxServeCookies:legacyCookies,__stxServeSearch:legacySearch,__stxServeSite:site}=globalThis;if(!snapshot&&!legacyCookies&&legacySearch===void 0)return;return{...snapshot,cookies:snapshot?.cookies??legacyCookies??{},url:snapshot?.url||snapshot?.search||legacySearch||"",search:snapshot?.search??legacySearch??"",site:snapshot?.site??site??null}}installRequestContext(productionRequestSnapshot);function parseCookies(req){return parseCookieHeader(req.headers.get("cookie"))}export function resolveUserPartialsPath(cwd=process.cwd(),configuredDir){if(configuredDir){const configured=[join(cwd,"resources",configuredDir),join(cwd,configuredDir)].find((candidate)=>existsSync(candidate));if(configured)return configured}const existing=["resources/partials","resources/views/partials","partials","resources/components"].filter((candidate)=>existsSync(join(cwd,candidate)));if(existing.length===0)return;const populated=existing.find((candidate)=>containsTemplates(join(cwd,candidate)));return join(cwd,populated??existing[0])}function containsTemplates(dir){try{return[...new Bun.Glob("**/*.stx").scanSync({cwd:dir,onlyFiles:!0})].length>0}catch{return!1}}export async function loadStxPartialsDir(cwd=process.cwd()){for(const name of["stx","ui"]){const configPath=join(cwd,`config/${name}.ts`);if(!existsSync(configPath))continue;try{const dir=(await import(configPath)).default?.partialsDir;if(typeof dir==="string"&&dir.length>0)return dir}catch{}}return}function resolveCsrfMiddlewarePath(){const rel="app/Middleware/Csrf.ts",vendored=join(process.cwd(),"storage/framework/defaults",rel);if(existsSync(vendored))return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return`${pkgJson.slice(0,pkgJson.lastIndexOf("/"))}/${rel}`}catch{return vendored}}export async function startProductionServer(options){if(options?.port)process.env.PORT=String(options.port);process.env.APP_ENV=process.env.APP_ENV||"production";const port=Number(process.env.PORT)||3000,{config,overridesReady,resolveViewPatterns}=await import("@stacksjs/config");await overridesReady;const{ensureDiscoveredPackages}=await import("@stacksjs/actions");await ensureDiscoveredPackages();const{applyViewSecurityHeaders,describeApiProxyRules,describeRedirectRules,describeRewriteRules,injectGlobalAutoImports,resolveApiBase,resolveApiProxyRules,resolveEmbeddableRules,resolveRedirectRules,resolveRewriteRules}=await import("@stacksjs/server"),{resolveDefaultsResources}=await import("@stacksjs/actions/dev/defaults-resources"),{stxPageAuthMiddleware}=await import("@stacksjs/auth"),{enhanceRequest,loadMiddlewareHandlers}=await import("@stacksjs/router"),pageMiddleware=await loadMiddlewareHandlers();try{const{autoImportsAreStale,generateAutoImportFiles}=await import("@stacksjs/server");if(autoImportsAreStale()){log.info("[server] Installed packages moved since the auto-import barrel was written; rebuilding it.");await generateAutoImportFiles({declarations:!1})}}catch(error){log.warn(`[server] Could not refresh the auto-import barrel: ${error instanceof Error?error.message:String(error)}`)}await injectGlobalAutoImports();let stxServe;const serveSource=resolveStxSource({value:process.env.BUN_PLUGIN_STX_SRC});if(serveSource.kind==="missing")await log.exit(`BUN_PLUGIN_STX_SRC points at ${serveSource.path}, which does not exist. Unset it to use the installed bun-plugin-stx.`,ExitCode.FatalError);if(serveSource.kind==="override"){({serve:stxServe}=await import(serveSource.path));log.warn(`Serving views through ${serveSource.path} instead of the installed bun-plugin-stx.`)}else({serve:stxServe}=await import("bun-plugin-stx/serve"));log.debug(`stx serve implementation: ${serveSource.kind==="override"?serveSource.path:"bun-plugin-stx/serve"}`);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",userComponentsPath=existsSync("resources/views/components")?"resources/views/components":"resources/components",userPartialsPath=resolveUserPartialsPath(process.cwd(),await loadStxPartialsDir()),apiBase=resolveApiBase(config.ports?.api),viewPatterns=resolveViewPatterns(userViewsPath,defaultViewsPath,config?.ui?.defaultViews);for(const name of viewPatterns.missing)log.warn(`ui.defaultViews lists "${name}", which does not exist under ${defaultViewsPath} - ignoring.`);const apiProxyRules=resolveApiProxyRules(config.server?.proxy);if(apiProxyRules.paths.length>0||apiProxyRules.prefixes.length>1)log.info(`API proxy: ${describeApiProxyRules(apiProxyRules)}`);const redirectRules=resolveRedirectRules(config.server?.redirects);if(redirectRules.size>0)log.info(`Redirects: ${describeRedirectRules(redirectRules)}`);const rewriteRules=resolveRewriteRules(config.server?.rewrites);if(rewriteRules.size>0)log.info(`Rewrites: ${describeRewriteRules(rewriteRules)}`);const cacheConfig=config.server?.cache,documentCacheControl=buildDocumentCacheControl(cacheConfig?.documents);if(cacheConfig?.renders)log.info(`Render cache: on, keyed by ${cacheConfig.renderVary??"request"}${cacheConfig.prewarm?", prewarmed":""}`);if(documentCacheControl)log.info(`Document cache: ${documentCacheControl}`);const embeddableRules=resolveEmbeddableRules(config.server?.security?.embeddable);if(embeddableRules.paths.length>0||embeddableRules.prefixes.length>0)log.info(`Frameable by other origins: ${[...embeddableRules.paths,...embeddableRules.prefixes].join(" ")}`);log.info(`Starting production server on port ${port}...`);await stxServe({patterns:viewPatterns.patterns,port,autoIncrementPort:!1,reusePort:["production","staging","development"].includes((process.env.APP_ENV||"").toLowerCase()),componentsDir:userComponentsPath,fallbackComponentsDir:join(defaultsResources,"components"),layoutsDir:userLayoutsPath,...userPartialsPath&&{partialsDir:userPartialsPath},fallbackLayoutsDir:join(defaultsResources,"layouts"),fallbackPartialsDir:defaultViewsPath,quiet:options?.verbose!==!0,...cacheConfig?.renders&&{renderCache:!0,renderCacheVary:cacheConfig.renderVary??"request",...cacheConfig.prewarm&&{prewarmRenderCache:!0}},...stxModule&&{stxModule},...i18nConfig&&{i18n:i18nConfig},...siteConfig?.url&&{site:siteConfig},middleware:{...pageMiddleware,...stxPageAuthMiddleware()},prepareMiddlewareRequest:(request)=>enhanceRequest(request),onRequest:async(req)=>{const{maintenanceGate,isApiBoundRequest:isApiBound,proxyToBackend,resolveRedirect,resolveRewrite}=await import("@stacksjs/server"),gated=await maintenanceGate(req);if(gated)return gated;const url=new URL(req.url),redirected=resolveRedirect(url,redirectRules);if(redirected)return redirected;const rewritten=resolveRewrite(url.pathname,rewriteRules);if(rewritten){if(!apiBase){log.error(`No API target configured for ${url.pathname} (rewritten to ${rewritten}); refusing to proxy.`);return new Response("Bad Gateway",{status:502})}try{const target=new URL(`${rewritten}${url.search}`,url.origin);return await proxyToBackend(new Request(target,req),apiBase)}catch(error){log.error(`Rewrite of ${url.pathname} to ${rewritten} failed: ${error.message}`);return new Response("Bad Gateway",{status:502})}}if(isApiBound(req,url.pathname,apiProxyRules)){if(!apiBase){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 - refusing to proxy. Set PORT_API (or API_URL) for this site, and deploy an \`api\` site on its own port.`);return new Response("Bad Gateway",{status:502})}try{return await proxyToBackend(req,apiBase)}catch(error){log.error(`API proxy to ${apiBase} failed: ${error.message}`);return new Response("Bad Gateway",{status:502})}}if(existsSync(join(process.cwd(),"resources/views/blog.stx"))){const{renderBlogFeed}=await import("@stacksjs/actions/blog"),feed=await renderBlogFeed(req);if(feed)return feed}globalThis.__stxServeSearch=url.search;globalThis.__stxServeCookies=parseCookies(req);if(config.analytics?.capturePageviews)import("@stacksjs/analytics").then(({recordPageview})=>recordPageview(req)).catch(()=>{});if(config.sites?.enabled){const sites=await import("@stacksjs/sites"),resolved=await sites.resolveSiteByHost(sites.requestHost(req.headers,sites.sitesOptions()));sites.setCurrentSite(resolved);globalThis.__stxServeSite=sites.toSiteSnapshot(resolved)}else globalThis.__stxServeSite=null;return},onResponse:async(req,response)=>{const secured=applyViewSecurityHeaders(req,response,embeddableRules),method=req.method.toUpperCase();if(method!=="GET"&&method!=="HEAD")return secured;const baseline=secured??response;let current=baseline;if(current.status===404&&config.sites?.enabled)try{const{cmsNotFoundFallback}=await import("@stacksjs/cms"),cmsResponse=await cmsNotFoundFallback(req);if(cmsResponse)current=cmsResponse}catch(error){log.debug(`CMS fallback skipped: ${error.message}`)}let finished;try{const{seedCsrfCookieIfMissing}=await import(resolveCsrfMiddlewarePath());finished=await seedCsrfCookieIfMissing(req,current)??(current===baseline?secured:current)}catch(error){log.debug(`CSRF cookie seeding skipped: ${error.message}`);finished=current===baseline?secured:current}if(!documentCacheControl)return finished;return applyDocumentCacheControl(finished??response,documentCacheControl)}});log.success(`Production server listening on http://0.0.0.0:${port}`)}export function buildDocumentCacheControl(documents){if(!documents)return;const maxAge=Number(documents.maxAge??0);if(!Number.isFinite(maxAge)||maxAge<=0)return;const stale=Number(documents.staleWhileRevalidate??0),parts=["public",`max-age=${Math.floor(maxAge)}`];if(Number.isFinite(stale)&&stale>0)parts.push(`stale-while-revalidate=${Math.floor(stale)}`);return parts.join(", ")}export function applyDocumentCacheControl(response,cacheControl){if(!cacheControl)return response;const contentType=response.headers.get("content-type")||"";if(response.status!==200||!contentType.startsWith("text/html"))return response;if(response.headers.has("cache-control"))return response;if(response.headers.getSetCookie().length>0)return response;const headers=new Headers(response.headers);headers.set("cache-control",cacheControl);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}async function resolveVendoredStxModule(){const source=resolveStxSource({value:process.env.STACKS_STX_SRC});if(source.kind==="missing")await log.exit(`STACKS_STX_SRC points at ${source.path}, which does not exist. Unset it to use the installed @stacksjs/stx.`,ExitCode.FatalError);if(source.kind==="override"){log.warn(`Rendering through ${source.path} instead of the installed @stacksjs/stx.`);return await import(source.path)}try{return await import("@stacksjs/stx")}catch{}return}function fallbackI18nFromSite(site){const locales=site.i18n.locales,defaultLocale=site.i18n.defaultLocale??locales[0];return{locales,defaultLocale,labels:site.i18n.labels??Object.fromEntries(locales.map((c)=>[c,c.toUpperCase()])),translations:{},pickerSelector:site.i18n.pickerSelector??"#lang-picker"}}async function resolveSiteI18n(site){try{const resolved=await resolveVendoredStxModule();if(typeof resolved?.resolveI18n==="function"){const i18n=resolved.resolveI18n(site,process.cwd());if(i18n)return i18n}}catch{}return fallbackI18nFromSite(site)}async function loadStxSiteConfig(){const sitePath=siteConfigPath();if(!existsSync(sitePath))return{};try{const mod=await import(sitePath),site=mod.default??mod.site??mod.config;if(!site)return{};if(!site.i18n)return{site};const i18n=await resolveSiteI18n(site);return{site,i18n}}catch{}return{}}
|
|
1
|
+
import{existsSync}from"node:fs";import{join}from"node:path";import process from"node:process";import{installRequestContext,parseCookieHeader}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{siteConfigPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{resolveStxSource}from"./stx-source";function productionRequestSnapshot(){const{__stxServeContext:snapshot,__stxServeCookies:legacyCookies,__stxServeSearch:legacySearch,__stxServeSite:site}=globalThis;if(!snapshot&&!legacyCookies&&legacySearch===void 0)return;return{...snapshot,cookies:snapshot?.cookies??legacyCookies??{},url:snapshot?.url||snapshot?.search||legacySearch||"",search:snapshot?.search??legacySearch??"",site:snapshot?.site??site??null}}installRequestContext(productionRequestSnapshot);function parseCookies(req){return parseCookieHeader(req.headers.get("cookie"))}export function resolveUserPartialsPath(cwd=process.cwd(),configuredDir){if(configuredDir){const configured=[join(cwd,"resources",configuredDir),join(cwd,configuredDir)].find((candidate)=>existsSync(candidate));if(configured)return configured}const existing=["resources/partials","resources/views/partials","partials","resources/components"].filter((candidate)=>existsSync(join(cwd,candidate)));if(existing.length===0)return;const populated=existing.find((candidate)=>containsTemplates(join(cwd,candidate)));return join(cwd,populated??existing[0])}function containsTemplates(dir){try{return[...new Bun.Glob("**/*.stx").scanSync({cwd:dir,onlyFiles:!0})].length>0}catch{return!1}}export async function loadStxPartialsDir(cwd=process.cwd()){for(const name of["stx","ui"]){const configPath=join(cwd,`config/${name}.ts`);if(!existsSync(configPath))continue;try{const dir=(await import(configPath)).default?.partialsDir;if(typeof dir==="string"&&dir.length>0)return dir}catch{}}return}function resolveCsrfMiddlewarePath(){const rel="app/Middleware/Csrf.ts",vendored=join(process.cwd(),"storage/framework/defaults",rel);if(existsSync(vendored))return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return`${pkgJson.slice(0,pkgJson.lastIndexOf("/"))}/${rel}`}catch{return vendored}}export async function startProductionServer(options){if(options?.port)process.env.PORT=String(options.port);process.env.APP_ENV=process.env.APP_ENV||"production";const port=Number(process.env.PORT)||3000,{config,overridesReady,resolveViewPatterns}=await import("@stacksjs/config");await overridesReady;const{ensureDiscoveredPackages}=await import("@stacksjs/actions");await ensureDiscoveredPackages();const{applyViewSecurityHeaders,describeApiProxyRules,describeRedirectRules,describeRewriteRules,injectGlobalAutoImports,resolveApiBase,resolveApiProxyRules,resolveEmbeddableRules,resolveRedirectRules,resolveRewriteRules}=await import("@stacksjs/server"),{resolveDefaultsResources}=await import("@stacksjs/actions/dev/defaults-resources"),{stxPageAuthMiddleware}=await import("@stacksjs/auth"),{enhanceRequest,loadMiddlewareHandlers}=await import("@stacksjs/router"),pageMiddleware=await loadMiddlewareHandlers();try{const{autoImportsAreStale,generateAutoImportFiles}=await import("@stacksjs/server");if(autoImportsAreStale()){log.info("[server] Installed packages moved since the auto-import barrel was written; rebuilding it.");await generateAutoImportFiles({declarations:!1})}}catch(error){log.warn(`[server] Could not refresh the auto-import barrel: ${error instanceof Error?error.message:String(error)}`)}await injectGlobalAutoImports();let stxServe;const serveSource=resolveStxSource({value:process.env.BUN_PLUGIN_STX_SRC});if(serveSource.kind==="missing")await log.exit(`BUN_PLUGIN_STX_SRC points at ${serveSource.path}, which does not exist. Unset it to use the installed bun-plugin-stx.`,ExitCode.FatalError);if(serveSource.kind==="override"){({serve:stxServe}=await import(serveSource.path));log.warn(`Serving views through ${serveSource.path} instead of the installed bun-plugin-stx.`)}else({serve:stxServe}=await import("bun-plugin-stx/serve"));log.debug(`stx serve implementation: ${serveSource.kind==="override"?serveSource.path:"bun-plugin-stx/serve"}`);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",userComponentsPath=existsSync("resources/views/components")?"resources/views/components":"resources/components",userPartialsPath=resolveUserPartialsPath(process.cwd(),await loadStxPartialsDir()),apiBase=resolveApiBase(config.ports?.api),viewPatterns=resolveViewPatterns(userViewsPath,defaultViewsPath,config?.ui?.defaultViews);for(const name of viewPatterns.missing)log.warn(`ui.defaultViews lists "${name}", which does not exist under ${defaultViewsPath} - ignoring.`);const apiProxyRules=resolveApiProxyRules(config.server?.proxy);if(apiProxyRules.paths.length>0||apiProxyRules.prefixes.length>1)log.info(`API proxy: ${describeApiProxyRules(apiProxyRules)}`);const redirectRules=resolveRedirectRules(config.server?.redirects);if(redirectRules.size>0)log.info(`Redirects: ${describeRedirectRules(redirectRules)}`);const rewriteRules=resolveRewriteRules(config.server?.rewrites);if(rewriteRules.size>0)log.info(`Rewrites: ${describeRewriteRules(rewriteRules)}`);const cacheConfig=config.server?.cache,documentCacheControl=buildDocumentCacheControl(cacheConfig?.documents);if(cacheConfig?.renders)log.info(`Render cache: on, keyed by ${cacheConfig.renderVary??"request"}${cacheConfig.prewarm?", prewarmed":""}`);if(documentCacheControl)log.info(`Document cache: ${documentCacheControl}`);const embeddableRules=resolveEmbeddableRules(config.server?.security?.embeddable);if(embeddableRules.paths.length>0||embeddableRules.prefixes.length>0)log.info(`Frameable by other origins: ${[...embeddableRules.paths,...embeddableRules.prefixes].join(" ")}`);log.info(`Starting production server on port ${port}...`);await stxServe({patterns:viewPatterns.patterns,port,autoIncrementPort:!1,reusePort:["production","staging","development"].includes((process.env.APP_ENV||"").toLowerCase()),componentsDir:userComponentsPath,fallbackComponentsDir:join(defaultsResources,"components"),layoutsDir:userLayoutsPath,...userPartialsPath&&{partialsDir:userPartialsPath},fallbackLayoutsDir:join(defaultsResources,"layouts"),fallbackPartialsDir:defaultViewsPath,quiet:options?.verbose!==!0,...cacheConfig?.renders&&{renderCache:!0,renderCacheVary:cacheConfig.renderVary??"request",...cacheConfig.prewarm&&{prewarmRenderCache:!0}},...stxModule&&{stxModule},...i18nConfig&&{i18n:i18nConfig},...siteConfig?.url&&{site:siteConfig},middleware:{...pageMiddleware,...stxPageAuthMiddleware()},prepareMiddlewareRequest:(request)=>enhanceRequest(request),onRequest:async(req)=>{const{maintenanceGate,isApiBoundRequest:isApiBound,proxyToBackend,resolveRedirect,resolveRewrite}=await import("@stacksjs/server"),gated=await maintenanceGate(req);if(gated)return gated;const url=new URL(req.url),redirected=resolveRedirect(url,redirectRules);if(redirected)return redirected;const rewritten=resolveRewrite(url.pathname,rewriteRules);if(rewritten){if(!apiBase){log.error(`No API target configured for ${url.pathname} (rewritten to ${rewritten}); refusing to proxy.`);return new Response("Bad Gateway",{status:502})}try{const target=new URL(`${rewritten}${url.search}`,url.origin);return await proxyToBackend(new Request(target,req),apiBase)}catch(error){log.error(`Rewrite of ${url.pathname} to ${rewritten} failed: ${error.message}`);return new Response("Bad Gateway",{status:502})}}if(isApiBound(req,url.pathname,apiProxyRules)){if(!apiBase){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 - refusing to proxy. Set PORT_API (or API_URL) for this site, and deploy an \`api\` site on its own port.`);return new Response("Bad Gateway",{status:502})}try{return await proxyToBackend(req,apiBase)}catch(error){log.error(`API proxy to ${apiBase} failed: ${error.message}`);return new Response("Bad Gateway",{status:502})}}if(existsSync(join(process.cwd(),"resources/views/blog.stx"))){const{renderBlogFeed}=await import("@stacksjs/actions/blog"),feed=await renderBlogFeed(req);if(feed)return feed}globalThis.__stxServeSearch=url.search;globalThis.__stxServeCookies=parseCookies(req);if(config.analytics?.capturePageviews)import("@stacksjs/analytics").then(({recordPageview})=>recordPageview(req)).catch(()=>{});if(config.sites?.enabled){const sites=await import("@stacksjs/sites"),resolved=await sites.resolveSiteByHost(sites.requestHost(req.headers,sites.sitesOptions()));sites.setCurrentSite(resolved);globalThis.__stxServeSite=sites.toSiteSnapshot(resolved)}else globalThis.__stxServeSite=null;return},onResponse:async(req,response)=>{const secured=applyViewSecurityHeaders(req,response,embeddableRules),method=req.method.toUpperCase();if(method!=="GET"&&method!=="HEAD")return secured;const baseline=secured??response;let current=baseline;if(current.status===404&&config.sites?.enabled)try{const{cmsNotFoundFallback}=await import("@stacksjs/cms"),cmsResponse=await cmsNotFoundFallback(req);if(cmsResponse)current=cmsResponse}catch(error){log.debug(`CMS fallback skipped: ${error.message}`)}let finished;try{const{seedCsrfCookieIfMissing}=await import(resolveCsrfMiddlewarePath());finished=await seedCsrfCookieIfMissing(req,current)??(current===baseline?secured:current)}catch(error){log.debug(`CSRF cookie seeding skipped: ${error.message}`);finished=current===baseline?secured:current}if(!documentCacheControl)return finished;return applyDocumentCacheControl(req,finished??response,documentCacheControl)}});log.success(`Production server listening on http://0.0.0.0:${port}`)}export function buildDocumentCacheControl(documents){if(!documents)return;const maxAge=Number(documents.maxAge??0);if(!Number.isFinite(maxAge)||maxAge<=0)return;const stale=Number(documents.staleWhileRevalidate??0),parts=["public",`max-age=${Math.floor(maxAge)}`];if(Number.isFinite(stale)&&stale>0)parts.push(`stale-while-revalidate=${Math.floor(stale)}`);return parts.join(", ")}export function isAuthenticatedRequest(request){if(request.headers.get("authorization"))return!0;const cookie=request.headers.get("cookie")||"";if(!cookie)return!1;return cookie.split(";").map((part)=>part.trim().split("=")[0]?.toLowerCase()??"").some((name)=>name!==""&&!name.includes("csrf")&&/session|auth|token|remember/.test(name))}export function applyDocumentCacheControl(request,response,cacheControl){if(!cacheControl)return response;const contentType=response.headers.get("content-type")||"";if(response.status!==200||!contentType.startsWith("text/html"))return response;if(response.headers.getSetCookie().length>0)return response;if(isAuthenticatedRequest(request))return response;const headers=new Headers(response.headers);headers.set("cache-control",cacheControl);return new Response(response.body,{status:response.status,statusText:response.statusText,headers})}async function resolveVendoredStxModule(){const source=resolveStxSource({value:process.env.STACKS_STX_SRC});if(source.kind==="missing")await log.exit(`STACKS_STX_SRC points at ${source.path}, which does not exist. Unset it to use the installed @stacksjs/stx.`,ExitCode.FatalError);if(source.kind==="override"){log.warn(`Rendering through ${source.path} instead of the installed @stacksjs/stx.`);return await import(source.path)}try{return await import("@stacksjs/stx")}catch{}return}function fallbackI18nFromSite(site){const locales=site.i18n.locales,defaultLocale=site.i18n.defaultLocale??locales[0];return{locales,defaultLocale,labels:site.i18n.labels??Object.fromEntries(locales.map((c)=>[c,c.toUpperCase()])),translations:{},pickerSelector:site.i18n.pickerSelector??"#lang-picker"}}async function resolveSiteI18n(site){try{const resolved=await resolveVendoredStxModule();if(typeof resolved?.resolveI18n==="function"){const i18n=resolved.resolveI18n(site,process.cwd());if(i18n)return i18n}}catch{}return fallbackI18nFromSite(site)}async function loadStxSiteConfig(){const sitePath=siteConfigPath();if(!existsSync(sitePath))return{};try{const mod=await import(sitePath),site=mod.default??mod.site??mod.config;if(!site)return{};if(!site.i18n)return{site};const i18n=await resolveSiteI18n(site);return{site,i18n}}catch{}return{}}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.41",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,69 +95,69 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "^0.74.
|
|
99
|
-
"@stacksjs/ai": "^0.74.
|
|
100
|
-
"@stacksjs/alias": "^0.74.
|
|
101
|
-
"@stacksjs/analytics": "^0.74.
|
|
102
|
-
"@stacksjs/api": "^0.74.
|
|
103
|
-
"@stacksjs/arrays": "^0.74.
|
|
104
|
-
"@stacksjs/auth": "^0.74.
|
|
105
|
-
"@stacksjs/browser-extension": "^0.74.
|
|
106
|
-
"@stacksjs/build": "^0.74.
|
|
107
|
-
"@stacksjs/cache": "^0.74.
|
|
108
|
-
"@stacksjs/chat": "^0.74.
|
|
98
|
+
"@stacksjs/actions": "^0.74.41",
|
|
99
|
+
"@stacksjs/ai": "^0.74.41",
|
|
100
|
+
"@stacksjs/alias": "^0.74.41",
|
|
101
|
+
"@stacksjs/analytics": "^0.74.41",
|
|
102
|
+
"@stacksjs/api": "^0.74.41",
|
|
103
|
+
"@stacksjs/arrays": "^0.74.41",
|
|
104
|
+
"@stacksjs/auth": "^0.74.41",
|
|
105
|
+
"@stacksjs/browser-extension": "^0.74.41",
|
|
106
|
+
"@stacksjs/build": "^0.74.41",
|
|
107
|
+
"@stacksjs/cache": "^0.74.41",
|
|
108
|
+
"@stacksjs/chat": "^0.74.41",
|
|
109
109
|
"@stacksjs/clapp": "^0.2.12",
|
|
110
|
-
"@stacksjs/cli": "^0.74.
|
|
111
|
-
"@stacksjs/cloud": "^0.74.
|
|
112
|
-
"@stacksjs/cms": "^0.74.
|
|
113
|
-
"@stacksjs/collections": "^0.74.
|
|
114
|
-
"@stacksjs/config": "^0.74.
|
|
115
|
-
"@stacksjs/database": "^0.74.
|
|
116
|
-
"@stacksjs/desktop-build": "^0.74.
|
|
117
|
-
"@stacksjs/dns": "^0.74.
|
|
110
|
+
"@stacksjs/cli": "^0.74.41",
|
|
111
|
+
"@stacksjs/cloud": "^0.74.41",
|
|
112
|
+
"@stacksjs/cms": "^0.74.41",
|
|
113
|
+
"@stacksjs/collections": "^0.74.41",
|
|
114
|
+
"@stacksjs/config": "^0.74.41",
|
|
115
|
+
"@stacksjs/database": "^0.74.41",
|
|
116
|
+
"@stacksjs/desktop-build": "^0.74.41",
|
|
117
|
+
"@stacksjs/dns": "^0.74.41",
|
|
118
118
|
"@stacksjs/dnsx": "^0.2.3",
|
|
119
|
-
"@stacksjs/email": "^0.74.
|
|
120
|
-
"@stacksjs/enums": "^0.74.
|
|
121
|
-
"@stacksjs/env": "^0.74.
|
|
122
|
-
"@stacksjs/error-handling": "^0.74.
|
|
123
|
-
"@stacksjs/events": "^0.74.
|
|
124
|
-
"@stacksjs/features": "^0.74.
|
|
125
|
-
"@stacksjs/git": "^0.74.
|
|
119
|
+
"@stacksjs/email": "^0.74.41",
|
|
120
|
+
"@stacksjs/enums": "^0.74.41",
|
|
121
|
+
"@stacksjs/env": "^0.74.41",
|
|
122
|
+
"@stacksjs/error-handling": "^0.74.41",
|
|
123
|
+
"@stacksjs/events": "^0.74.41",
|
|
124
|
+
"@stacksjs/features": "^0.74.41",
|
|
125
|
+
"@stacksjs/git": "^0.74.41",
|
|
126
126
|
"@stacksjs/gitit": "^0.2.5",
|
|
127
|
-
"@stacksjs/health": "^0.74.
|
|
127
|
+
"@stacksjs/health": "^0.74.41",
|
|
128
128
|
"@stacksjs/httx": "^0.1.10",
|
|
129
|
-
"@stacksjs/image": "^0.74.
|
|
130
|
-
"@stacksjs/lint": "^0.74.
|
|
131
|
-
"@stacksjs/logging": "^0.74.
|
|
132
|
-
"@stacksjs/notifications": "^0.74.
|
|
133
|
-
"@stacksjs/objects": "^0.74.
|
|
134
|
-
"@stacksjs/orm": "^0.74.
|
|
135
|
-
"@stacksjs/path": "^0.74.
|
|
136
|
-
"@stacksjs/payments": "^0.74.
|
|
137
|
-
"@stacksjs/realtime": "^0.74.
|
|
138
|
-
"@stacksjs/router": "^0.74.
|
|
129
|
+
"@stacksjs/image": "^0.74.41",
|
|
130
|
+
"@stacksjs/lint": "^0.74.41",
|
|
131
|
+
"@stacksjs/logging": "^0.74.41",
|
|
132
|
+
"@stacksjs/notifications": "^0.74.41",
|
|
133
|
+
"@stacksjs/objects": "^0.74.41",
|
|
134
|
+
"@stacksjs/orm": "^0.74.41",
|
|
135
|
+
"@stacksjs/path": "^0.74.41",
|
|
136
|
+
"@stacksjs/payments": "^0.74.41",
|
|
137
|
+
"@stacksjs/realtime": "^0.74.41",
|
|
138
|
+
"@stacksjs/router": "^0.74.41",
|
|
139
139
|
"@stacksjs/rpx": "^0.11.53",
|
|
140
|
-
"@stacksjs/scheduler": "^0.74.
|
|
141
|
-
"@stacksjs/search-engine": "^0.74.
|
|
142
|
-
"@stacksjs/security": "^0.74.
|
|
143
|
-
"@stacksjs/server": "^0.74.
|
|
144
|
-
"@stacksjs/sites": "^0.74.
|
|
145
|
-
"@stacksjs/skills": "^0.74.
|
|
146
|
-
"@stacksjs/storage": "^0.74.
|
|
147
|
-
"@stacksjs/strings": "^0.74.
|
|
148
|
-
"@stacksjs/stx": "^0.2.
|
|
149
|
-
"@stacksjs/testing": "^0.74.
|
|
150
|
-
"@stacksjs/tinker": "^0.74.
|
|
140
|
+
"@stacksjs/scheduler": "^0.74.41",
|
|
141
|
+
"@stacksjs/search-engine": "^0.74.41",
|
|
142
|
+
"@stacksjs/security": "^0.74.41",
|
|
143
|
+
"@stacksjs/server": "^0.74.41",
|
|
144
|
+
"@stacksjs/sites": "^0.74.41",
|
|
145
|
+
"@stacksjs/skills": "^0.74.41",
|
|
146
|
+
"@stacksjs/storage": "^0.74.41",
|
|
147
|
+
"@stacksjs/strings": "^0.74.41",
|
|
148
|
+
"@stacksjs/stx": "^0.2.285",
|
|
149
|
+
"@stacksjs/testing": "^0.74.41",
|
|
150
|
+
"@stacksjs/tinker": "^0.74.41",
|
|
151
151
|
"@stacksjs/tlsx": "^0.13.19",
|
|
152
152
|
"@stacksjs/ts-cloud": "^0.16.0",
|
|
153
|
-
"@stacksjs/tunnel": "^0.74.
|
|
154
|
-
"@stacksjs/types": "^0.74.
|
|
155
|
-
"@stacksjs/ui": "^0.74.
|
|
156
|
-
"@stacksjs/utils": "^0.74.
|
|
157
|
-
"@stacksjs/validation": "^0.74.
|
|
153
|
+
"@stacksjs/tunnel": "^0.74.41",
|
|
154
|
+
"@stacksjs/types": "^0.74.41",
|
|
155
|
+
"@stacksjs/ui": "^0.74.41",
|
|
156
|
+
"@stacksjs/utils": "^0.74.41",
|
|
157
|
+
"@stacksjs/validation": "^0.74.41",
|
|
158
158
|
"ajv": "^8.20.0",
|
|
159
159
|
"ajv-formats": "^3.0.1",
|
|
160
|
-
"bun-plugin-stx": "^0.2.
|
|
160
|
+
"bun-plugin-stx": "^0.2.285",
|
|
161
161
|
"ts-pantry": "^0.11.35"
|
|
162
162
|
},
|
|
163
163
|
"devDependencies": {
|