@stacksjs/server 0.74.50 → 0.74.52
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/config.js +1 -1
- package/dist/controllers/base.js +1 -1
- package/dist/imports.js +1 -1
- package/dist/maintenance.d.ts +1 -1
- package/dist/maintenance.js +2 -2
- package/dist/start.js +1 -1
- package/package.json +11 -11
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ports}from"@stacksjs/config";export function config(options){const serversMap={frontend:{host:"localhost",port:ports.frontend},backend:{host:"localhost",port:ports.backend},api:{host:"localhost",port:ports.api},admin:{host:"localhost",port:ports.admin},library:{host:"localhost",port:ports.library},desktop:{host:"localhost",port:ports.desktop},docs:{host:"localhost",port:ports.docs},email:{host:"localhost",port:ports.email},inspect:{host:"localhost",port:ports.inspect},"system-tray":{host:"localhost",port:ports.systemTray},database:{host:"localhost",port:ports.database}};if(options.type&&options.type in serversMap)return{host:serversMap[options.type].host,port:serversMap[options.type].port,open:!1};return{host:options.host||"stacks.localhost",port:options.port||3000,open:!1}}
|
|
1
|
+
import{ports}from"@stacksjs/config/runtime";export function config(options){const serversMap={frontend:{host:"localhost",port:ports.frontend},backend:{host:"localhost",port:ports.backend},api:{host:"localhost",port:ports.api},admin:{host:"localhost",port:ports.admin},library:{host:"localhost",port:ports.library},desktop:{host:"localhost",port:ports.desktop},docs:{host:"localhost",port:ports.docs},email:{host:"localhost",port:ports.email},inspect:{host:"localhost",port:ports.inspect},"system-tray":{host:"localhost",port:ports.systemTray},database:{host:"localhost",port:ports.database}};if(options.type&&options.type in serversMap)return{host:serversMap[options.type].host,port:serversMap[options.type].port,open:!1};return{host:options.host||"stacks.localhost",port:options.port||3000,open:!1}}
|
package/dist/controllers/base.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{log}from"@stacksjs/logging/runtime";import{response}from"@stacksjs/router/response";export class Controller{json(data,status=200){return response.json(data,status)}success(data){return this.json(data,200)}created(data){return this.json(data,201)}noContent(){return response.noContent()}error(message,status=500){return this.json({error:message},status)}notFound(message="Resource not found"){return this.error(message,404)}unauthorized(message="Unauthorized"){return this.error(message,401)}forbidden(message="Forbidden"){return this.error(message,403)}async validate(request,rules){if(typeof request.validate!=="function")throw TypeError("This request does not carry the validation macro.");const result=await request.validate(rules);log.info("Validation result:",result)}}
|
package/dist/imports.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var {require}=import.meta;import{existsSync,readFileSync,renameSync,statSync,unlinkSync}from"node:fs";import{dirname,relative,resolve as resolvePath}from"node:path";import{fileURLToPath}from"node:url";import{plugin}from"bun";import{log}from"@stacksjs/logging";import{path}from"@stacksjs/path";import{autoImports,generateRuntimeIndex,generateGlobalsScript}from"bun-plugin-auto-imports";import{globSync}from"@stacksjs/storage";import{primitiveAutoImportEntries,primitiveModules}from"./primitive-imports";const OPTIONAL_MODEL_MODULES={commerce:["config/commerce.ts"],Content:["config/cms.ts","config/blog.ts"],Forms:["config/forms.ts"],realtime:["config/realtime.ts"]};function configEnabled(configRelPaths){if(process.env.STACKS_CANONICAL_FEATURES==="1")return!0;return configRelPaths.some((rel)=>existsSync(path.projectPath(rel)))}export function frameworkDefaultsDir(sub){const vendored=path.storagePath(`framework/defaults/${sub}`);return resolveDefaultsDir(existsSync(vendored)?vendored:void 0,packagedDefaultsDir(sub),vendoredDefaultsAreStale())}export function resolveDefaultsDir(vendored,packaged,vendoredIsStale){if(packaged&&vendoredIsStale)return packaged;return vendored??packaged}function packagedDefaultsDir(sub){try{const packaged=`${dirname(fileURLToPath(import.meta.resolve("@stacksjs/defaults/package.json")))}/${sub}`;return existsSync(packaged)?packaged:void 0}catch{return}}let defaultsAreStale;function vendoredDefaultsAreStale(){if(defaultsAreStale===void 0)defaultsAreStale=path.inspectDefaultsProvenance().status==="stale";return defaultsAreStale}function existingDirs(dirs){return dirs.filter((dir)=>Boolean(dir)&&existsSync(dir))}function packageModelDirs(){try{const{packageModelRoots}=require("@stacksjs/config");return packageModelRoots().map((root)=>root.dir)}catch{return[]}}async function writeGeneratedFile(outputPath,contents){const temp=`${outputPath}.${process.pid}.tmp`;try{await Bun.write(temp,contents);renameSync(temp,outputPath)}catch(error){try{unlinkSync(temp)}catch{}throw error}}function packageJobDirs(){try{const{packageJobRoots}=require("@stacksjs/config");return packageJobRoots().map((root)=>root.dir)}catch{return[]}}function resolveDefaultModelDirs(){const root=frameworkDefaultsDir("app/Models");if(!root)return[];const dirs=[root];for(const[subdir,configPaths]of Object.entries(OPTIONAL_MODEL_MODULES))if(configEnabled(configPaths))dirs.push(`${root}/${subdir}`);return dirs}function scanDirTopLevel(dir){try{return globSync(`${dir}/*.ts`,{ignore:["**/*.d.ts","**/index.ts","**/README*"]}).sort()}catch{return[]}}function scanDefineModelExports(dir,opts={}){const{recursive=!0}=opts;let files=[];try{const pattern=recursive?`${dir}/**/*.ts`:`${dir}/*.ts`;files=globSync(pattern,{ignore:["**/*.d.ts","**/index.ts","**/README*"]}).sort()}catch{return[]}const exports=[],seen=new Set;for(const file of files){const basename=file.split("/").pop()?.replace(".ts","")||"";if(basename&&!seen.has(basename)){seen.add(basename);exports.push({name:basename,file,isDefault:!0})}}return exports}const GLOBAL_SHADOW_BLOCKLIST=new Set(["Error","Request","Response","URL","Map","Set","Object","Array","Number","String","Date","Promise","Symbol"]);async function generateDefineModelIndex(entries,outputPath){const lines=["// Generated by bun-plugin-auto-imports"],seen=new Set;for(const entry of entries){const dir=typeof entry==="string"?entry:entry.dir,recursive=typeof entry==="string"?!0:entry.recursive;let files=[];try{const pattern=recursive?`${dir}/**/*.ts`:`${dir}/*.ts`;files=globSync(pattern,{ignore:["**/*.d.ts","**/index.ts","**/README*"]}).sort()}catch{continue}for(const file of files){const basename=file.split("/").pop()?.replace(".ts","")||"";if(!basename||seen.has(basename))continue;seen.add(basename);const relativePath=relative(dirname(outputPath),file).replace(/\.ts$/,"");if(GLOBAL_SHADOW_BLOCKLIST.has(basename)){lines.push(`// Skipped '${basename}' - would shadow a built-in global. Import directly if needed.`);lines.push(`// export { default as ${basename} } from '${relativePath}'`);continue}lines.push(`export { default as ${basename} } from '${relativePath}'`)}}await writeGeneratedFile(outputPath,`${lines.join(`
|
|
1
|
+
var {require}=import.meta;import{existsSync,readFileSync,renameSync,statSync,unlinkSync}from"node:fs";import{dirname,relative,resolve as resolvePath}from"node:path";import{fileURLToPath}from"node:url";import{plugin}from"bun";import{log}from"@stacksjs/logging/runtime";import{path}from"@stacksjs/path";import{autoImports,generateRuntimeIndex,generateGlobalsScript}from"bun-plugin-auto-imports";import{globSync}from"@stacksjs/storage/glob";import{primitiveAutoImportEntries,primitiveModules}from"./primitive-imports";const OPTIONAL_MODEL_MODULES={commerce:["config/commerce.ts"],Content:["config/cms.ts","config/blog.ts"],Forms:["config/forms.ts"],realtime:["config/realtime.ts"]};function configEnabled(configRelPaths){if(process.env.STACKS_CANONICAL_FEATURES==="1")return!0;return configRelPaths.some((rel)=>existsSync(path.projectPath(rel)))}export function frameworkDefaultsDir(sub){const vendored=path.storagePath(`framework/defaults/${sub}`);return resolveDefaultsDir(existsSync(vendored)?vendored:void 0,packagedDefaultsDir(sub),vendoredDefaultsAreStale())}export function resolveDefaultsDir(vendored,packaged,vendoredIsStale){if(packaged&&vendoredIsStale)return packaged;return vendored??packaged}function packagedDefaultsDir(sub){try{const packaged=`${dirname(fileURLToPath(import.meta.resolve("@stacksjs/defaults/package.json")))}/${sub}`;return existsSync(packaged)?packaged:void 0}catch{return}}let defaultsAreStale;function vendoredDefaultsAreStale(){if(defaultsAreStale===void 0)defaultsAreStale=path.inspectDefaultsProvenance().status==="stale";return defaultsAreStale}function existingDirs(dirs){return dirs.filter((dir)=>Boolean(dir)&&existsSync(dir))}function packageModelDirs(){try{const{packageModelRoots}=require("@stacksjs/config");return packageModelRoots().map((root)=>root.dir)}catch{return[]}}async function writeGeneratedFile(outputPath,contents){const temp=`${outputPath}.${process.pid}.tmp`;try{await Bun.write(temp,contents);renameSync(temp,outputPath)}catch(error){try{unlinkSync(temp)}catch{}throw error}}function packageJobDirs(){try{const{packageJobRoots}=require("@stacksjs/config");return packageJobRoots().map((root)=>root.dir)}catch{return[]}}function resolveDefaultModelDirs(){const root=frameworkDefaultsDir("app/Models");if(!root)return[];const dirs=[root];for(const[subdir,configPaths]of Object.entries(OPTIONAL_MODEL_MODULES))if(configEnabled(configPaths))dirs.push(`${root}/${subdir}`);return dirs}function scanDirTopLevel(dir){try{return globSync(`${dir}/*.ts`,{ignore:["**/*.d.ts","**/index.ts","**/README*"]}).sort()}catch{return[]}}function scanDefineModelExports(dir,opts={}){const{recursive=!0}=opts;let files=[];try{const pattern=recursive?`${dir}/**/*.ts`:`${dir}/*.ts`;files=globSync(pattern,{ignore:["**/*.d.ts","**/index.ts","**/README*"]}).sort()}catch{return[]}const exports=[],seen=new Set;for(const file of files){const basename=file.split("/").pop()?.replace(".ts","")||"";if(basename&&!seen.has(basename)){seen.add(basename);exports.push({name:basename,file,isDefault:!0})}}return exports}const GLOBAL_SHADOW_BLOCKLIST=new Set(["Error","Request","Response","URL","Map","Set","Object","Array","Number","String","Date","Promise","Symbol"]);async function generateDefineModelIndex(entries,outputPath){const lines=["// Generated by bun-plugin-auto-imports"],seen=new Set;for(const entry of entries){const dir=typeof entry==="string"?entry:entry.dir,recursive=typeof entry==="string"?!0:entry.recursive;let files=[];try{const pattern=recursive?`${dir}/**/*.ts`:`${dir}/*.ts`;files=globSync(pattern,{ignore:["**/*.d.ts","**/index.ts","**/README*"]}).sort()}catch{continue}for(const file of files){const basename=file.split("/").pop()?.replace(".ts","")||"";if(!basename||seen.has(basename))continue;seen.add(basename);const relativePath=relative(dirname(outputPath),file).replace(/\.ts$/,"");if(GLOBAL_SHADOW_BLOCKLIST.has(basename)){lines.push(`// Skipped '${basename}' - would shadow a built-in global. Import directly if needed.`);lines.push(`// export { default as ${basename} } from '${relativePath}'`);continue}lines.push(`export { default as ${basename} } from '${relativePath}'`)}}await writeGeneratedFile(outputPath,`${lines.join(`
|
|
2
2
|
`)}
|
|
3
3
|
`)}async function generatePathIndex(entries,outputPath,options){const prefix=options.prefix??"",extensions=options.extensions??[".ts"],found=new Map;for(const entry of entries){const dir=typeof entry==="string"?entry:entry.dir,recursive=typeof entry==="string"?!0:entry.recursive;let files=[];try{for(const extension of extensions){const pattern=recursive?`${dir}/**/*${extension}`:`${dir}/*${extension}`;files.push(...globSync(pattern).sort())}}catch{continue}const rank=(file)=>extensions.findIndex((extension)=>file.endsWith(extension));files=files.sort((left,right)=>{const byRank=rank(left)-rank(right);if(byRank!==0)return byRank;return left<right?-1:left>right?1:0});for(const file of files){const basename=file.split("/").pop()??"";if(basename.endsWith(".d.ts")||basename.endsWith(".test.ts")||basename.endsWith(".spec.ts")||basename==="index.ts")continue;const extension=extensions.find((candidate)=>file.endsWith(candidate))??"",key=prefix+relative(dir,file).slice(0,extension.length?-extension.length:void 0);if(found.has(key))continue;found.set(key,relative(dirname(outputPath),file))}}const lines=["// Generated by bun-plugin-auto-imports","//","// Name to file, relative to this file. The resolvers read it, and the name","// types are `keyof` over it - so a name that type-checks is a name that","// resolves. Values are paths rather than import thunks on purpose: thunks","// would make every compilation that touches a name resolve every module.",`export const ${options.exportName} = {`,...[...found.entries()].map(([key,file])=>` '${key}': '${file}',`),"} as const",""];await writeGeneratedFile(outputPath,lines.join(`
|
|
4
4
|
`))}export function autoImportSourceDirs(){return existingDirs([path.resourcesPath("functions"),frameworkDefaultsDir("functions"),path.userModelsPath(),...resolveDefaultModelDirs(),path.userJobsPath(),frameworkDefaultsDir("app/Jobs"),...packageJobDirs(),path.userControllersPath(),frameworkDefaultsDir("app/Controllers"),path.appPath("Actions"),frameworkDefaultsDir("app/Actions"),path.appPath("Listeners"),frameworkDefaultsDir("app/Listeners"),path.appPath("Policies"),frameworkDefaultsDir("app/Policies"),path.appPath("Middleware"),frameworkDefaultsDir("app/Middleware"),path.resourcesPath("emails"),frameworkDefaultsDir("resources/emails")])}export function autoImportsAreStale(){const manifest=path.storagePath("framework/auto-imports/functions.ts");if(!existsSync(manifest))return!0;let manifestTime;try{manifestTime=statSync(manifest).mtimeMs}catch{return!0}const discovered=path.storagePath("framework/discovered-packages.json");try{if(existsSync(discovered)&&statSync(discovered).mtimeMs>manifestTime)return!0}catch{}for(const dir of autoImportSourceDirs()){if(!existsSync(dir))continue;try{if(statSync(dir).mtimeMs>manifestTime)return!0;for(const file of globSync(`${dir}/**/*.ts`,{ignore:["**/*.d.ts"]}))if(statSync(file).mtimeMs>manifestTime)return!0}catch{continue}}return!1}export async function generateAutoImportFiles(options={}){const userFunctionsPath=path.resourcesPath("functions"),defaultFunctionsPath=frameworkDefaultsDir("functions"),functionsPath=userFunctionsPath,outputDir=path.storagePath("framework/auto-imports"),userModelsPath=path.userModelsPath(),defaultModelDirs=resolveDefaultModelDirs(),[defaultsRoot,...enabledSubdirs]=defaultModelDirs,userJobsPath=path.userJobsPath(),userControllersPath=path.userControllersPath(),defaultControllersPath=frameworkDefaultsDir("app/Controllers");await Bun.write(`${outputDir}/.gitkeep`,"");const functionsIndexPath=`${outputDir}/functions.ts`;await generateRuntimeIndex(existingDirs([userFunctionsPath,defaultFunctionsPath]),functionsIndexPath);const modelsIndexPath=`${outputDir}/models.ts`,modelScan=[userModelsPath,...packageModelDirs().map((dir)=>({dir,recursive:!0})),...defaultsRoot?[{dir:defaultsRoot,recursive:!1}]:[],...defaultModelDirs.slice(1).map((d)=>({dir:d,recursive:!0}))];await generateDefineModelIndex(modelScan,modelsIndexPath);const jobsIndexPath=`${outputDir}/jobs.ts`;await generateDefineModelIndex(existingDirs([userJobsPath,frameworkDefaultsDir("app/Jobs"),...packageJobDirs()]),jobsIndexPath);await generatePathIndex(existingDirs([path.appPath("Actions"),frameworkDefaultsDir("app/Actions")]),`${outputDir}/actions.ts`,{exportName:"actions",prefix:"Actions/"});await generatePathIndex(existingDirs([path.appPath("Listeners"),frameworkDefaultsDir("app/Listeners")]),`${outputDir}/listeners.ts`,{exportName:"listeners"});await generatePathIndex(existingDirs([path.appPath("Policies"),frameworkDefaultsDir("app/Policies")]),`${outputDir}/policies.ts`,{exportName:"policies"});await generatePathIndex(existingDirs([path.appPath("Middleware"),frameworkDefaultsDir("app/Middleware")]),`${outputDir}/middleware.ts`,{exportName:"middleware"});await generatePathIndex(existingDirs([path.resourcesPath("emails"),frameworkDefaultsDir("resources/emails")]),`${outputDir}/emails.ts`,{exportName:"emails",extensions:[".stx",".html"]});const controllersIndexPath=`${outputDir}/controllers.ts`;await generateDefineModelIndex(existingDirs([userControllersPath,defaultControllersPath]),controllersIndexPath);const combinedContent=`// Generated by bun-plugin-auto-imports
|
package/dist/maintenance.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export declare function isAlwaysAllowed(path: string): boolean;
|
|
|
77
77
|
* coming-soon when a redirect URL is configured; HTML page for
|
|
78
78
|
* maintenance).
|
|
79
79
|
*/
|
|
80
|
-
export declare function maintenanceGate(req: Request): Promise<Response | null>;
|
|
80
|
+
export declare function maintenanceGate(req: Request): Response | null | Promise<Response | null>;
|
|
81
81
|
export declare interface MaintenancePayload {
|
|
82
82
|
mode?: SiteMode
|
|
83
83
|
time: number
|
package/dist/maintenance.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{log}from"@stacksjs/logging";import*as p from"@stacksjs/path";import{existsSync}from"node:fs";const DEFAULT_MAINTENANCE_PAYLOAD={mode:"maintenance",status:503,message:"We are currently performing maintenance. Please check back soon."},DEFAULT_COMING_SOON_PAYLOAD={mode:"coming-soon",status:200,message:"Stacks is setting up camp. Check back soon for the public launch.",redirect:"/coming-soon"};function defaultsForMode(mode){return mode==="coming-soon"?DEFAULT_COMING_SOON_PAYLOAD:DEFAULT_MAINTENANCE_PAYLOAD}let siteModePaths;function currentSiteModePaths(){const cwd=process.cwd();if(siteModePaths?.cwd!==cwd)siteModePaths={cwd};return siteModePaths}export function maintenanceFilePath(){return currentSiteModePaths().maintenance??=p.storagePath("framework/down")}export function comingSoonFilePath(){return currentSiteModePaths().comingSoon??=p.storagePath("framework/coming-soon")}export function siteModeFilePath(mode){return mode==="coming-soon"?comingSoonFilePath():maintenanceFilePath()}export async function isDownForMaintenance(){try{return existsSync(maintenanceFilePath())}catch{return!1}}export async function isComingSoon(){try{return existsSync(comingSoonFilePath())}catch{return!1}}export async function maintenancePayload(){return siteModePayload("maintenance")}export async function comingSoonPayload(){return siteModePayload("coming-soon")}function readSiteModePayload(mode){try{const filePath=siteModeFilePath(mode);if(!existsSync(filePath))return null;return Bun.file(filePath).text().then((content)=>({...defaultsForMode(mode),...JSON.parse(content),mode})).catch(()=>null)}catch{return null}}export async function siteModePayload(mode){return readSiteModePayload(mode)}function comingSoonPayloadResult(){const payload=readSiteModePayload("coming-soon");return payload?payload.then((value)=>value??envSiteModePayload()):envSiteModePayload()}function activeSiteModePayloadResult(){const payload=readSiteModePayload("maintenance");return payload?payload.then((value)=>value??comingSoonPayloadResult()):comingSoonPayloadResult()}export async function activeSiteModePayload(){return activeSiteModePayloadResult()}function envSiteModePayload(){if(isTruthy(process.env.APP_MAINTENANCE))return{...DEFAULT_MAINTENANCE_PAYLOAD,mode:"maintenance",time:Date.now(),secret:process.env.APP_MAINTENANCE_SECRET||void 0};if(isTruthy(process.env.APP_COMING_SOON))return{...DEFAULT_COMING_SOON_PAYLOAD,mode:"coming-soon",time:Date.now(),secret:process.env.APP_COMING_SOON_SECRET||void 0};return null}function isTruthy(value){const normalized=value?.toLowerCase();return normalized==="1"||normalized==="true"||normalized==="yes"||normalized==="on"}export async function down(options={}){const payload={...DEFAULT_MAINTENANCE_PAYLOAD,...options,mode:"maintenance",time:Date.now()},frameworkDir=p.storagePath("framework"),{mkdirSync,existsSync}=await import("@stacksjs/storage");if(!existsSync(frameworkDir))mkdirSync(frameworkDir,{recursive:!0});await Bun.write(maintenanceFilePath(),JSON.stringify(payload,null,2));log.info("Application is now in maintenance mode.");if(payload.secret)log.info("Maintenance bypass secret has been configured")}export async function comingSoon(options={}){const payload={...DEFAULT_COMING_SOON_PAYLOAD,...options,mode:"coming-soon",time:Date.now()},frameworkDir=p.storagePath("framework"),{mkdirSync,existsSync}=await import("@stacksjs/storage");if(!existsSync(frameworkDir))mkdirSync(frameworkDir,{recursive:!0});await Bun.write(comingSoonFilePath(),JSON.stringify(payload,null,2));log.info("Application is now in coming soon mode.");if(payload.secret)log.info("Coming soon bypass secret has been configured")}export async function up(){const{unlinkSync,existsSync}=await import("node:fs"),filePath=maintenanceFilePath();if(existsSync(filePath)){unlinkSync(filePath);log.info("Application is now live.")}else log.info("Application is already live.")}export async function launch(){const{unlinkSync,existsSync}=await import("node:fs"),filePath=comingSoonFilePath();if(existsSync(filePath)){unlinkSync(filePath);log.info("Application is out of coming soon mode.")}else log.info("Application is not in coming soon mode.")}export function isAllowedIp(ip,allowed=[],trustLocalhost=!0){if(trustLocalhost&&["127.0.0.1","::1","localhost"].includes(ip))return!0;if(allowed.length===0)return!1;return allowed.includes(ip)}export function bypassCookieName(mode="maintenance"){return mode==="coming-soon"?"stacks_coming_soon_bypass":"stacks_maintenance_bypass"}export function hasValidBypassCookie(cookies,secret,mode="maintenance"){return cookies[bypassCookieName(mode)]===secret}export function isSecretPath(path,secret){return path===`/${secret}`||path.startsWith(`/${secret}/`)}export function maintenanceHtml(payload){const mode=payload.mode??"maintenance",defaults=defaultsForMode(mode),message=escapeHtml(payload.message||defaults.message||""),title=escapeHtml(payload.title||(mode==="coming-soon"?"Opening Soon":"Trail Maintenance"));return`<!DOCTYPE html>
|
|
1
|
+
import{log}from"@stacksjs/logging/runtime";import*as p from"@stacksjs/path";import{existsSync}from"node:fs";const DEFAULT_MAINTENANCE_PAYLOAD={mode:"maintenance",status:503,message:"We are currently performing maintenance. Please check back soon."},DEFAULT_COMING_SOON_PAYLOAD={mode:"coming-soon",status:200,message:"Stacks is setting up camp. Check back soon for the public launch.",redirect:"/coming-soon"};function defaultsForMode(mode){return mode==="coming-soon"?DEFAULT_COMING_SOON_PAYLOAD:DEFAULT_MAINTENANCE_PAYLOAD}let siteModePaths;function currentSiteModePaths(){const cwd=process.cwd();if(siteModePaths?.cwd!==cwd)siteModePaths={cwd};return siteModePaths}export function maintenanceFilePath(){return currentSiteModePaths().maintenance??=p.storagePath("framework/down")}export function comingSoonFilePath(){return currentSiteModePaths().comingSoon??=p.storagePath("framework/coming-soon")}export function siteModeFilePath(mode){return mode==="coming-soon"?comingSoonFilePath():maintenanceFilePath()}export async function isDownForMaintenance(){try{return existsSync(maintenanceFilePath())}catch{return!1}}export async function isComingSoon(){try{return existsSync(comingSoonFilePath())}catch{return!1}}export async function maintenancePayload(){return siteModePayload("maintenance")}export async function comingSoonPayload(){return siteModePayload("coming-soon")}function readSiteModePayload(mode){try{const filePath=siteModeFilePath(mode);if(!existsSync(filePath))return null;return Bun.file(filePath).text().then((content)=>({...defaultsForMode(mode),...JSON.parse(content),mode})).catch(()=>null)}catch{return null}}export async function siteModePayload(mode){return readSiteModePayload(mode)}function comingSoonPayloadResult(){const payload=readSiteModePayload("coming-soon");return payload?payload.then((value)=>value??envSiteModePayload()):envSiteModePayload()}function activeSiteModePayloadResult(){const payload=readSiteModePayload("maintenance");return payload?payload.then((value)=>value??comingSoonPayloadResult()):comingSoonPayloadResult()}export async function activeSiteModePayload(){return activeSiteModePayloadResult()}function envSiteModePayload(){if(isTruthy(process.env.APP_MAINTENANCE))return{...DEFAULT_MAINTENANCE_PAYLOAD,mode:"maintenance",time:Date.now(),secret:process.env.APP_MAINTENANCE_SECRET||void 0};if(isTruthy(process.env.APP_COMING_SOON))return{...DEFAULT_COMING_SOON_PAYLOAD,mode:"coming-soon",time:Date.now(),secret:process.env.APP_COMING_SOON_SECRET||void 0};return null}function isTruthy(value){const normalized=value?.toLowerCase();return normalized==="1"||normalized==="true"||normalized==="yes"||normalized==="on"}export async function down(options={}){const payload={...DEFAULT_MAINTENANCE_PAYLOAD,...options,mode:"maintenance",time:Date.now()},frameworkDir=p.storagePath("framework"),{mkdirSync,existsSync}=await import("@stacksjs/storage");if(!existsSync(frameworkDir))mkdirSync(frameworkDir,{recursive:!0});await Bun.write(maintenanceFilePath(),JSON.stringify(payload,null,2));log.info("Application is now in maintenance mode.");if(payload.secret)log.info("Maintenance bypass secret has been configured")}export async function comingSoon(options={}){const payload={...DEFAULT_COMING_SOON_PAYLOAD,...options,mode:"coming-soon",time:Date.now()},frameworkDir=p.storagePath("framework"),{mkdirSync,existsSync}=await import("@stacksjs/storage");if(!existsSync(frameworkDir))mkdirSync(frameworkDir,{recursive:!0});await Bun.write(comingSoonFilePath(),JSON.stringify(payload,null,2));log.info("Application is now in coming soon mode.");if(payload.secret)log.info("Coming soon bypass secret has been configured")}export async function up(){const{unlinkSync,existsSync}=await import("node:fs"),filePath=maintenanceFilePath();if(existsSync(filePath)){unlinkSync(filePath);log.info("Application is now live.")}else log.info("Application is already live.")}export async function launch(){const{unlinkSync,existsSync}=await import("node:fs"),filePath=comingSoonFilePath();if(existsSync(filePath)){unlinkSync(filePath);log.info("Application is out of coming soon mode.")}else log.info("Application is not in coming soon mode.")}export function isAllowedIp(ip,allowed=[],trustLocalhost=!0){if(trustLocalhost&&["127.0.0.1","::1","localhost"].includes(ip))return!0;if(allowed.length===0)return!1;return allowed.includes(ip)}export function bypassCookieName(mode="maintenance"){return mode==="coming-soon"?"stacks_coming_soon_bypass":"stacks_maintenance_bypass"}export function hasValidBypassCookie(cookies,secret,mode="maintenance"){return cookies[bypassCookieName(mode)]===secret}export function isSecretPath(path,secret){return path===`/${secret}`||path.startsWith(`/${secret}/`)}export function maintenanceHtml(payload){const mode=payload.mode??"maintenance",defaults=defaultsForMode(mode),message=escapeHtml(payload.message||defaults.message||""),title=escapeHtml(payload.title||(mode==="coming-soon"?"Opening Soon":"Trail Maintenance"));return`<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
@@ -129,4 +129,4 @@ import{log}from"@stacksjs/logging";import*as p from"@stacksjs/path";import{exist
|
|
|
129
129
|
${payload.retry?`<p class="retry">Estimated reopening: ${Math.ceil(payload.retry/60)} minutes.</p>`:""}
|
|
130
130
|
</div>
|
|
131
131
|
</body>
|
|
132
|
-
</html>`}function escapeHtml(value){return value.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}export function maintenanceResponse(payload){return siteModeResponse(payload)}export function siteModeResponse(payload){const headers={"Content-Type":"text/html; charset=utf-8"};if(payload.retry)headers["Retry-After"]=String(payload.retry);if(payload.redirect)return new Response(null,{status:302,headers:{Location:payload.redirect}});return new Response(maintenanceHtml(payload),{status:payload.status||(payload.mode==="coming-soon"?200:503),headers})}export function bypassCookieValue(secret,mode="maintenance"){return`${bypassCookieName(mode)}=${secret}; Path=/; HttpOnly; SameSite=Lax`}const ALWAYS_ALLOWED_PATHS=new Set(["/coming-soon","/api/email/subscribe","/favicon.ico"]),ALWAYS_ALLOWED_PREFIXES=["/css/","/js/","/images/","/fonts/","/assets/","/_stx/","/_modules/","/@vite/","/@fs/","/__deps/"],ALWAYS_ALLOWED_EXTENSIONS=[".css",".js",".mjs",".map",".woff",".woff2",".ttf",".otf",".eot",".svg",".png",".jpg",".jpeg",".gif",".webp",".avif",".ico"];function configuredAllowList(){return(process.env.APP_MAINTENANCE_ALLOW??process.env.APP_COMING_SOON_ALLOW??"").split(",").map((entry)=>entry.trim()).filter(Boolean)}function isConfiguredAllowed(path){for(const entry of configuredAllowList()){if(entry.endsWith("*")){if(path.startsWith(entry.slice(0,-1)))return!0;continue}if(entry.endsWith("/")){if(path.startsWith(entry))return!0;continue}if(path===entry)return!0}return!1}export function isAlwaysAllowed(path){if(ALWAYS_ALLOWED_PATHS.has(path))return!0;if(isConfiguredAllowed(path))return!0;if(ALWAYS_ALLOWED_PREFIXES.some((p)=>path.startsWith(p)))return!0;const lower=path.toLowerCase();return ALWAYS_ALLOWED_EXTENSIONS.some((ext)=>lower.endsWith(ext))}function parseCookieHeader(header){const out={};if(!header)return out;for(const part of header.split(";")){const trimmed=part.trim(),eq=trimmed.indexOf("=");if(eq===-1)continue;const k=trimmed.slice(0,eq).trim(),v=trimmed.slice(eq+1).trim();if(k)out[k]=v}return out}function clientIp(req){const fwd=req.headers.get("x-forwarded-for");if(fwd)return fwd.split(",")[0]?.trim()??"127.0.0.1";const real=req.headers.get("x-real-ip");if(real)return real;return"127.0.0.1"}export
|
|
132
|
+
</html>`}function escapeHtml(value){return value.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}export function maintenanceResponse(payload){return siteModeResponse(payload)}export function siteModeResponse(payload){const headers={"Content-Type":"text/html; charset=utf-8"};if(payload.retry)headers["Retry-After"]=String(payload.retry);if(payload.redirect)return new Response(null,{status:302,headers:{Location:payload.redirect}});return new Response(maintenanceHtml(payload),{status:payload.status||(payload.mode==="coming-soon"?200:503),headers})}export function bypassCookieValue(secret,mode="maintenance"){return`${bypassCookieName(mode)}=${secret}; Path=/; HttpOnly; SameSite=Lax`}const ALWAYS_ALLOWED_PATHS=new Set(["/coming-soon","/api/email/subscribe","/favicon.ico"]),ALWAYS_ALLOWED_PREFIXES=["/css/","/js/","/images/","/fonts/","/assets/","/_stx/","/_modules/","/@vite/","/@fs/","/__deps/"],ALWAYS_ALLOWED_EXTENSIONS=[".css",".js",".mjs",".map",".woff",".woff2",".ttf",".otf",".eot",".svg",".png",".jpg",".jpeg",".gif",".webp",".avif",".ico"];function configuredAllowList(){return(process.env.APP_MAINTENANCE_ALLOW??process.env.APP_COMING_SOON_ALLOW??"").split(",").map((entry)=>entry.trim()).filter(Boolean)}function isConfiguredAllowed(path){for(const entry of configuredAllowList()){if(entry.endsWith("*")){if(path.startsWith(entry.slice(0,-1)))return!0;continue}if(entry.endsWith("/")){if(path.startsWith(entry))return!0;continue}if(path===entry)return!0}return!1}export function isAlwaysAllowed(path){if(ALWAYS_ALLOWED_PATHS.has(path))return!0;if(isConfiguredAllowed(path))return!0;if(ALWAYS_ALLOWED_PREFIXES.some((p)=>path.startsWith(p)))return!0;const lower=path.toLowerCase();return ALWAYS_ALLOWED_EXTENSIONS.some((ext)=>lower.endsWith(ext))}function parseCookieHeader(header){const out={};if(!header)return out;for(const part of header.split(";")){const trimmed=part.trim(),eq=trimmed.indexOf("=");if(eq===-1)continue;const k=trimmed.slice(0,eq).trim(),v=trimmed.slice(eq+1).trim();if(k)out[k]=v}return out}function clientIp(req){const fwd=req.headers.get("x-forwarded-for");if(fwd)return fwd.split(",")[0]?.trim()??"127.0.0.1";const real=req.headers.get("x-real-ip");if(real)return real;return"127.0.0.1"}export function maintenanceGate(req){const payloadResult=activeSiteModePayloadResult();return payloadResult instanceof Promise?payloadResult.then((payload)=>applyMaintenanceGate(req,payload)):applyMaintenanceGate(req,payloadResult)}function applyMaintenanceGate(req,payload){if(!payload)return null;const mode=payload.mode??"maintenance",path=new URL(req.url).pathname;if(isAlwaysAllowed(path))return null;if(payload.secret&&isSecretPath(path,payload.secret)){const location=mode==="coming-soon"?`/?preview=${encodeURIComponent(payload.secret)}`:"/";return new Response(null,{status:302,headers:{Location:location,"Set-Cookie":bypassCookieValue(payload.secret,mode)}})}const cookies=parseCookieHeader(req.headers.get("cookie")),hasCookie=!!payload.secret&&hasValidBypassCookie(cookies,payload.secret,mode),appEnv=(process.env.APP_ENV||"development").toLowerCase(),trustLocalhost=!["production","staging"].includes(appEnv),ipAllowed=isAllowedIp(clientIp(req),payload.allowed,trustLocalhost);if(hasCookie||ipAllowed)return null;return siteModeResponse(payload)}
|
package/dist/start.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
globalThis.__STACKS_BINARY_MODE__=!0;import{assertRouteMiddlewareResolvable,loadRoutes,serve}from"@stacksjs/router";import{log,report}from"@stacksjs/logging";import config from"./config-production";import routeRegistry from"../../../../../app/Routes";process.on("unhandledRejection",(reason)=>{report(reason,{label:"[server] unhandledRejection"})});process.on("uncaughtException",(error)=>{report(error,{label:"[server] uncaughtException"});log.flush().finally(()=>process.exit(1))});console.log("[START] Application starting...");console.log("[START] Node version:",process.version);console.log("[START] Working directory:",process.cwd());console.log("[START] Environment:",process.env.APP_ENV||"not set");process.env.SKIP_CONFIG_LOADING="true";console.log("[START] Config loaded:",{port:config.server.port,host:config.server.host,appName:config.app.name,appUrl:config.app.url});console.log("[START] Loading routes from registry...");loadRoutes(routeRegistry).then(async()=>{console.log("[START] Routes loaded successfully");try{const ormRoutesPackage="@stacksjs/orm/routes";try{await import(ormRoutesPackage)}catch{await import("../../orm/routes")}console.log("[START] ORM routes loaded successfully")}catch(ormError){console.warn("[START] ORM routes skipped:",ormError instanceof Error?ormError.message:String(ormError))}try{await assertRouteMiddlewareResolvable();console.log("[START] Route middleware validated")}catch(middlewareError){console.error("[START] FATAL: unresolvable route middleware - refusing to serve unprotected routes:",middlewareError instanceof Error?middlewareError.message:String(middlewareError));process.exit(1)}console.log("[START] Calling serve()...");try{serve({port:config.server.port,hostname:config.server.host});console.log("[START] serve() called successfully")}catch(error){console.error("[START] ERROR calling serve():",error);process.exit(1)}}).catch((error)=>{console.error("[START] ERROR loading routes:",error);process.exit(1)});
|
|
1
|
+
globalThis.__STACKS_BINARY_MODE__=!0;import{assertRouteMiddlewareResolvable,loadRoutes,serve}from"@stacksjs/router";import{log,report}from"@stacksjs/logging/runtime";import config from"./config-production";import routeRegistry from"../../../../../app/Routes";process.on("unhandledRejection",(reason)=>{report(reason,{label:"[server] unhandledRejection"})});process.on("uncaughtException",(error)=>{report(error,{label:"[server] uncaughtException"});log.flush().finally(()=>process.exit(1))});console.log("[START] Application starting...");console.log("[START] Node version:",process.version);console.log("[START] Working directory:",process.cwd());console.log("[START] Environment:",process.env.APP_ENV||"not set");process.env.SKIP_CONFIG_LOADING="true";console.log("[START] Config loaded:",{port:config.server.port,host:config.server.host,appName:config.app.name,appUrl:config.app.url});console.log("[START] Loading routes from registry...");loadRoutes(routeRegistry).then(async()=>{console.log("[START] Routes loaded successfully");try{const ormRoutesPackage="@stacksjs/orm/routes";try{await import(ormRoutesPackage)}catch{await import("../../orm/routes")}console.log("[START] ORM routes loaded successfully")}catch(ormError){console.warn("[START] ORM routes skipped:",ormError instanceof Error?ormError.message:String(ormError))}try{await assertRouteMiddlewareResolvable();console.log("[START] Route middleware validated")}catch(middlewareError){console.error("[START] FATAL: unresolvable route middleware - refusing to serve unprotected routes:",middlewareError instanceof Error?middlewareError.message:String(middlewareError));process.exit(1)}console.log("[START] Calling serve()...");try{serve({port:config.server.port,hostname:config.server.host});console.log("[START] serve() called successfully")}catch(error){console.error("[START] ERROR calling serve():",error);process.exit(1)}}).catch((error)=>{console.error("[START] ERROR loading routes:",error);process.exit(1)});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/server",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.52",
|
|
6
6
|
"description": "Local development and production-ready.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -58,19 +58,19 @@
|
|
|
58
58
|
"prepublishOnly": "bun run build"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@stacksjs/auth": "0.74.
|
|
62
|
-
"@stacksjs/config": "0.74.
|
|
63
|
-
"@stacksjs/events": "0.74.
|
|
64
|
-
"@stacksjs/i18n": "0.74.
|
|
65
|
-
"@stacksjs/logging": "0.74.
|
|
66
|
-
"@stacksjs/orm": "0.74.
|
|
67
|
-
"@stacksjs/path": "0.74.
|
|
68
|
-
"@stacksjs/router": "0.74.
|
|
69
|
-
"@stacksjs/storage": "0.74.
|
|
61
|
+
"@stacksjs/auth": "0.74.52",
|
|
62
|
+
"@stacksjs/config": "0.74.52",
|
|
63
|
+
"@stacksjs/events": "0.74.52",
|
|
64
|
+
"@stacksjs/i18n": "0.74.52",
|
|
65
|
+
"@stacksjs/logging": "0.74.52",
|
|
66
|
+
"@stacksjs/orm": "0.74.52",
|
|
67
|
+
"@stacksjs/path": "0.74.52",
|
|
68
|
+
"@stacksjs/router": "0.74.52",
|
|
69
|
+
"@stacksjs/storage": "0.74.52",
|
|
70
70
|
"bun-plugin-auto-imports": "^0.4.2"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@stacksjs/validation": "0.74.
|
|
73
|
+
"@stacksjs/validation": "0.74.52",
|
|
74
74
|
"better-dx": "^0.2.24"
|
|
75
75
|
}
|
|
76
76
|
}
|