@stacksjs/buddy 0.72.7 → 0.72.9
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.
|
@@ -117,6 +117,12 @@ export declare const FEATURE_NAMES: readonly ['dashboard', 'commerce', 'cms', 'f
|
|
|
117
117
|
* ending in `/` are directory trees (recursive remove on uninstall); bare
|
|
118
118
|
* paths are single files.
|
|
119
119
|
*
|
|
120
|
+
* A feature must also claim the shared files its own actions IMPORT.
|
|
121
|
+
* `app/Actions/Dashboard/dashboard-response.ts` is the case that bit: five
|
|
122
|
+
* features publish a subdirectory of `app/Actions/Dashboard/`, and the actions
|
|
123
|
+
* in each import `../dashboard-response`. Without it, `<feature>:install`
|
|
124
|
+
* copied 22 actions whose very first import did not resolve.
|
|
125
|
+
*
|
|
120
126
|
* Manifests intentionally overlap where features share scaffolding —
|
|
121
127
|
* `dashboard` claims the umbrella `app/Actions/Dashboard/` even though
|
|
122
128
|
* `app/Actions/Dashboard/Content/` is also claimed by `cms`. Both the
|
|
@@ -131,13 +137,13 @@ export declare const FEATURE_NAMES: readonly ['dashboard', 'commerce', 'cms', 'f
|
|
|
131
137
|
* ```ts
|
|
132
138
|
* {
|
|
133
139
|
* forms: [ 'app/Models/Forms/', ],
|
|
134
|
-
* cms: [ 'app/Actions/Cms/', 'app/Actions/Dashboard/Content/', 'app/Models/Content/', 'app/Models/Tag.ts', 'app/Models/Comment.ts', 'resources/views/dashboard/content/', ],
|
|
135
|
-
* commerce: [ 'app/Actions/Commerce/', 'app/Actions/Dashboard/Commerce/', 'app/Models/commerce/', 'resources/components/Dashboard/Commerce/', 'resources/views/dashboard/commerce/', ],
|
|
140
|
+
* cms: [ 'app/Actions/Cms/', 'app/Actions/Dashboard/Content/', 'app/Actions/Dashboard/dashboard-response.ts', 'app/Models/Content/', 'app/Models/Tag.ts', 'app/Models/Comment.ts', 'resources/views/dashboard/content/', ],
|
|
141
|
+
* commerce: [ 'app/Actions/Commerce/', 'app/Actions/Dashboard/Commerce/', 'app/Actions/Dashboard/dashboard-response.ts', 'app/Models/commerce/', 'resources/components/Dashboard/Commerce/', 'resources/views/dashboard/commerce/', ],
|
|
136
142
|
* dashboard: [ 'app/Actions/Dashboard/', 'resources/components/Dashboard/', 'resources/views/dashboard/', 'routes/dashboard.ts', 'routes/dashboard-api.ts', ],
|
|
137
|
-
* marketing: [ 'app/Actions/Dashboard/Marketing/', 'app/Models/Campaign.ts', 'app/Models/CampaignSend.ts', 'app/Models/EmailList.ts', 'app/Models/EmailListSubscriber.ts', 'app/Models/SocialPost.ts', 'resources/components/Marketing/', 'resources/views/dashboard/marketing/', ],
|
|
143
|
+
* marketing: [ 'app/Actions/Dashboard/Marketing/', 'app/Actions/Dashboard/dashboard-response.ts', 'app/Models/Campaign.ts', 'app/Models/CampaignSend.ts', 'app/Models/EmailList.ts', 'app/Models/EmailListSubscriber.ts', 'app/Models/SocialPost.ts', 'resources/components/Marketing/', 'resources/views/dashboard/marketing/', ],
|
|
138
144
|
* monitoring: [ 'app/Actions/Monitoring/', 'app/Actions/TestErrorAction.ts', 'app/Models/Error.ts', 'functions/monitoring/', 'resources/views/dashboard/monitoring/', 'resources/views/dashboard/errors/', ],
|
|
139
|
-
* realtime: [ 'app/Actions/Realtime/', 'app/Actions/Dashboard/Realtime/', 'app/Models/realtime/', 'app/Broadcasts/', 'functions/realtime/', 'resources/views/dashboard/realtime/', ],
|
|
140
|
-
* queue: [ 'app/Actions/Queue/', 'app/Actions/Dashboard/Jobs/', 'app/Jobs/', 'app/Models/Job.ts', 'app/Models/FailedJob.ts', 'functions/jobs.ts', 'resources/views/dashboard/queue/', 'resources/views/dashboard/jobs/', ]
|
|
145
|
+
* realtime: [ 'app/Actions/Realtime/', 'app/Actions/Dashboard/Realtime/', 'app/Actions/Dashboard/dashboard-response.ts', 'app/Models/realtime/', 'app/Broadcasts/', 'functions/realtime/', 'resources/views/dashboard/realtime/', ],
|
|
146
|
+
* queue: [ 'app/Actions/Queue/', 'app/Actions/Dashboard/Jobs/', 'app/Actions/Dashboard/dashboard-response.ts', 'app/Jobs/', 'app/Models/Job.ts', 'app/Models/FailedJob.ts', 'functions/jobs.ts', 'resources/views/dashboard/queue/', 'resources/views/dashboard/jobs/', ]
|
|
141
147
|
* }
|
|
142
148
|
* ```
|
|
143
149
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync,readdirSync,readFileSync}from"node:fs";import{cp,rm}from"node:fs/promises";import{join}from"node:path";import process from"node:process";import{frameworkPath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";export const FEATURE_NAMES=["dashboard","commerce","cms","forms","marketing","monitoring","realtime","queue"],FEATURE_FILES={forms:["app/Models/Forms/"],cms:["app/Actions/Cms/","app/Actions/Dashboard/Content/","app/Models/Content/","app/Models/Tag.ts","app/Models/Comment.ts","resources/views/dashboard/content/"],commerce:["app/Actions/Commerce/","app/Actions/Dashboard/Commerce/","app/Models/commerce/","resources/components/Dashboard/Commerce/","resources/views/dashboard/commerce/"],dashboard:["app/Actions/Dashboard/","resources/components/Dashboard/","resources/views/dashboard/","routes/dashboard.ts","routes/dashboard-api.ts"],marketing:["app/Actions/Dashboard/Marketing/","app/Models/Campaign.ts","app/Models/CampaignSend.ts","app/Models/EmailList.ts","app/Models/EmailListSubscriber.ts","app/Models/SocialPost.ts","resources/components/Marketing/","resources/views/dashboard/marketing/"],monitoring:["app/Actions/Monitoring/","app/Actions/TestErrorAction.ts","app/Models/Error.ts","functions/monitoring/","resources/views/dashboard/monitoring/","resources/views/dashboard/errors/"],realtime:["app/Actions/Realtime/","app/Actions/Dashboard/Realtime/","app/Models/realtime/","app/Broadcasts/","functions/realtime/","resources/views/dashboard/realtime/"],queue:["app/Actions/Queue/","app/Actions/Dashboard/Jobs/","app/Jobs/","app/Models/Job.ts","app/Models/FailedJob.ts","functions/jobs.ts","resources/views/dashboard/queue/","resources/views/dashboard/jobs/"]},FEATURE_TABLES={forms:["forms","form_fields","form_submissions"],cms:["posts","pages","comments","tags","authors","categories","taggable_models","categorizable_models","commentables","page_revisions","redirects","menus","menu_items"],commerce:["products","product_variants","product_units","manufacturers","orders","order_items","order_idempotency","carts","cart_items","payments","payment_methods","payment_products","payment_transactions","customers","subscribers","subscriber_emails","subscriptions","gift_cards","coupons","transactions","reviews","drivers","driver_pings","delivery_routes","delivery_stops","digital_deliveries","shipping_methods","shipping_rates","shipping_zones","license_keys","loyalty_points","loyalty_rewards","print_devices","receipts","tax_rates","waitlist_products","waitlist_restaurants","auctions","auction_items","bids","pledges"],dashboard:["boards","board_columns","cards","card_labels","card_assignees","card_comments","labels","ci_run_states","ci_runner_samples","ci_runner_alert_states","requests","logs"],marketing:["campaigns","campaign_sends","email_lists","email_list_subscribers","social_posts","mail_preferences"],monitoring:["errors"],realtime:["websockets"],queue:["jobs","failed_jobs"]};export function migrationTable(filename){const inMatch=filename.match(/-in-([a-z0-9_]+)\.sql$/i);if(inMatch)return inMatch[1]??null;const createMatch=filename.match(/-create-([a-z0-9_]+)-table\.sql$/i);if(createMatch)return createMatch[1]??null;const alterMatch=filename.match(/-alter-([a-z0-9_]+)-/i);if(alterMatch)return alterMatch[1]??null;return null}export function migrationFeature(filename){const table=migrationTable(filename);if(!table)return null;for(const f of FEATURE_NAMES)if(FEATURE_TABLES[f].includes(table))return f;return null}export function appModelClaimsTable(table,root=projectPath()){const modelsDir=join(root,"app/Models");if(!existsSync(modelsDir))return!1;const featureModelFiles=new Set(FEATURE_NAMES.flatMap((feature)=>FEATURE_FILES[feature]).filter((path)=>path.startsWith("app/Models/")&&!path.endsWith("/"))),escaped=table.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),declaration=new RegExp(`\\btable\\s*:\\s*['"]${escaped}['"]`);for(const entry of readdirSync(modelsDir,{withFileTypes:!0})){if(!entry.isFile()||!/\.[cm]?[jt]s$/.test(entry.name))continue;if(featureModelFiles.has(`app/Models/${entry.name}`))continue;if(declaration.test(readFileSync(join(modelsDir,entry.name),"utf8")))return!0}return!1}export function featurePathsPresent(feature,root=projectPath()){return FEATURE_FILES[feature].filter((rel)=>existsSync(`${root}/${rel}`))}export async function deleteFeatureFiles(feature,root=projectPath()){const removed=[];for(const rel of FEATURE_FILES[feature]){const full=`${root}/${rel}`;if(!existsSync(full))continue;await rm(full,{recursive:!0,force:!0});removed.push(rel)}return removed}export async function copyFeatureFiles(feature,options={}){const source=options.source??frameworkPath("defaults"),target=options.target??projectPath(),force=options.force===!0,copied=[],skipped=[];for(const rel of FEATURE_FILES[feature]){const sourceFull=join(source,rel);if(!existsSync(sourceFull)){skipped.push(rel);continue}const targetFull=join(target,rel);if(existsSync(targetFull)&&!force){skipped.push(rel);continue}await cp(sourceFull,targetFull,{recursive:!0,force});copied.push(rel)}return{copied,skipped}}const FEATURE_DESCRIPTIONS={dashboard:"Admin SPA shell + Activity/Log/Request/Deployment/Notification dashboards.",commerce:"Order/Cart/Product/Customer/Coupon/GiftCard/Shipping + storefront API.",cms:"Post/Page/Author/Comment/Tag models + content edit dashboards.",forms:"User-defined forms: builder models, conditional fields, public submit + CSV export.",marketing:"/api/email/subscribe, /api/contact, Campaign/EmailList/SocialPost.",monitoring:"Error model + error-tracking views and actions.",realtime:"WebSocket broadcaster + Websocket model + realtime-stats actions.",queue:"Job + FailedJob models + queue dashboard pages."},STARTER_TEMPLATES={dashboard:`import type { DashboardConfig } from '@stacksjs/types'
|
|
1
|
+
import{existsSync,readdirSync,readFileSync}from"node:fs";import{cp,rm}from"node:fs/promises";import{join}from"node:path";import process from"node:process";import{frameworkPath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";export const FEATURE_NAMES=["dashboard","commerce","cms","forms","marketing","monitoring","realtime","queue"],FEATURE_FILES={forms:["app/Models/Forms/"],cms:["app/Actions/Cms/","app/Actions/Dashboard/Content/","app/Actions/Dashboard/dashboard-response.ts","app/Models/Content/","app/Models/Tag.ts","app/Models/Comment.ts","resources/views/dashboard/content/"],commerce:["app/Actions/Commerce/","app/Actions/Dashboard/Commerce/","app/Actions/Dashboard/dashboard-response.ts","app/Models/commerce/","resources/components/Dashboard/Commerce/","resources/views/dashboard/commerce/"],dashboard:["app/Actions/Dashboard/","resources/components/Dashboard/","resources/views/dashboard/","routes/dashboard.ts","routes/dashboard-api.ts"],marketing:["app/Actions/Dashboard/Marketing/","app/Actions/Dashboard/dashboard-response.ts","app/Models/Campaign.ts","app/Models/CampaignSend.ts","app/Models/EmailList.ts","app/Models/EmailListSubscriber.ts","app/Models/SocialPost.ts","resources/components/Marketing/","resources/views/dashboard/marketing/"],monitoring:["app/Actions/Monitoring/","app/Actions/TestErrorAction.ts","app/Models/Error.ts","functions/monitoring/","resources/views/dashboard/monitoring/","resources/views/dashboard/errors/"],realtime:["app/Actions/Realtime/","app/Actions/Dashboard/Realtime/","app/Actions/Dashboard/dashboard-response.ts","app/Models/realtime/","app/Broadcasts/","functions/realtime/","resources/views/dashboard/realtime/"],queue:["app/Actions/Queue/","app/Actions/Dashboard/Jobs/","app/Actions/Dashboard/dashboard-response.ts","app/Jobs/","app/Models/Job.ts","app/Models/FailedJob.ts","functions/jobs.ts","resources/views/dashboard/queue/","resources/views/dashboard/jobs/"]},FEATURE_TABLES={forms:["forms","form_fields","form_submissions"],cms:["posts","pages","comments","tags","authors","categories","taggable_models","categorizable_models","commentables","page_revisions","redirects","menus","menu_items"],commerce:["products","product_variants","product_units","manufacturers","orders","order_items","order_idempotency","carts","cart_items","payments","payment_methods","payment_products","payment_transactions","customers","subscribers","subscriber_emails","subscriptions","gift_cards","coupons","transactions","reviews","drivers","driver_pings","delivery_routes","delivery_stops","digital_deliveries","shipping_methods","shipping_rates","shipping_zones","license_keys","loyalty_points","loyalty_rewards","print_devices","receipts","tax_rates","waitlist_products","waitlist_restaurants","auctions","auction_items","bids","pledges"],dashboard:["boards","board_columns","cards","card_labels","card_assignees","card_comments","labels","ci_run_states","ci_runner_samples","ci_runner_alert_states","requests","logs"],marketing:["campaigns","campaign_sends","email_lists","email_list_subscribers","social_posts","mail_preferences"],monitoring:["errors"],realtime:["websockets"],queue:["jobs","failed_jobs"]};export function migrationTable(filename){const inMatch=filename.match(/-in-([a-z0-9_]+)\.sql$/i);if(inMatch)return inMatch[1]??null;const createMatch=filename.match(/-create-([a-z0-9_]+)-table\.sql$/i);if(createMatch)return createMatch[1]??null;const alterMatch=filename.match(/-alter-([a-z0-9_]+)-/i);if(alterMatch)return alterMatch[1]??null;return null}export function migrationFeature(filename){const table=migrationTable(filename);if(!table)return null;for(const f of FEATURE_NAMES)if(FEATURE_TABLES[f].includes(table))return f;return null}export function appModelClaimsTable(table,root=projectPath()){const modelsDir=join(root,"app/Models");if(!existsSync(modelsDir))return!1;const featureModelFiles=new Set(FEATURE_NAMES.flatMap((feature)=>FEATURE_FILES[feature]).filter((path)=>path.startsWith("app/Models/")&&!path.endsWith("/"))),escaped=table.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),declaration=new RegExp(`\\btable\\s*:\\s*['"]${escaped}['"]`);for(const entry of readdirSync(modelsDir,{withFileTypes:!0})){if(!entry.isFile()||!/\.[cm]?[jt]s$/.test(entry.name))continue;if(featureModelFiles.has(`app/Models/${entry.name}`))continue;if(declaration.test(readFileSync(join(modelsDir,entry.name),"utf8")))return!0}return!1}export function featurePathsPresent(feature,root=projectPath()){return FEATURE_FILES[feature].filter((rel)=>existsSync(`${root}/${rel}`))}export async function deleteFeatureFiles(feature,root=projectPath()){const removed=[];for(const rel of FEATURE_FILES[feature]){const full=`${root}/${rel}`;if(!existsSync(full))continue;await rm(full,{recursive:!0,force:!0});removed.push(rel)}return removed}export async function copyFeatureFiles(feature,options={}){const source=options.source??frameworkPath("defaults"),target=options.target??projectPath(),force=options.force===!0,copied=[],skipped=[];for(const rel of FEATURE_FILES[feature]){const sourceFull=join(source,rel);if(!existsSync(sourceFull)){skipped.push(rel);continue}const targetFull=join(target,rel);if(existsSync(targetFull)&&!force){skipped.push(rel);continue}await cp(sourceFull,targetFull,{recursive:!0,force});copied.push(rel)}return{copied,skipped}}const FEATURE_DESCRIPTIONS={dashboard:"Admin SPA shell + Activity/Log/Request/Deployment/Notification dashboards.",commerce:"Order/Cart/Product/Customer/Coupon/GiftCard/Shipping + storefront API.",cms:"Post/Page/Author/Comment/Tag models + content edit dashboards.",forms:"User-defined forms: builder models, conditional fields, public submit + CSV export.",marketing:"/api/email/subscribe, /api/contact, Campaign/EmailList/SocialPost.",monitoring:"Error model + error-tracking views and actions.",realtime:"WebSocket broadcaster + Websocket model + realtime-stats actions.",queue:"Job + FailedJob models + queue dashboard pages."},STARTER_TEMPLATES={dashboard:`import type { DashboardConfig } from '@stacksjs/types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* **Dashboard Configuration**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";import process from"node:process";import{installRequestContext,parseCookieHeader}from"@stacksjs/config";import{log}from"@stacksjs/logging";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()){const configPath=join(cwd,"config/stx.ts");if(!existsSync(configPath))return;try{const dir=(await import(configPath)).default?.partialsDir;return typeof dir==="string"&&dir.length>0?dir:void 0}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 function resolveApiBase(configuredPort,env=process.env){if(env.API_URL)return env.API_URL;const explicitPort=Number(env.PORT_API);if(explicitPort)return`http://127.0.0.1:${explicitPort}`;if(["production","staging","development"].includes((env.APP_ENV||"").toLowerCase()))return null;return`http://127.0.0.1:${configuredPort||3008}`}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{describeApiProxyRules,describeRedirectRules,injectGlobalAutoImports,resolveApiProxyRules,resolveRedirectRules}=await import("@stacksjs/server"),{resolveDefaultsResources}=await import("@stacksjs/actions/dev/defaults-resources"),{stxPageAuthMiddleware}=await import("@stacksjs/auth");await injectGlobalAutoImports();let stxServe;const serveCandidates=[join(homedir(),"Code/Tools/stx/packages/bun-plugin/dist/serve.js"),join(process.cwd(),"pantry/bun-plugin-stx/dist/serve.js")];for(const entry of serveCandidates)try{if(existsSync(entry)){({serve:stxServe}=await import(entry));break}}catch{}if(!stxServe)({serve:stxServe}=await import("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",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} \u2014 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)}`);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:join(defaultsResources,"components"),layoutsDir:userLayoutsPath,...userPartialsPath&&{partialsDir:userPartialsPath},fallbackLayoutsDir:join(defaultsResources,"layouts"),fallbackPartialsDir:defaultViewsPath,quiet:options?.verbose!==!0,...stxModule&&{stxModule},...i18nConfig&&{i18n:i18nConfig},...siteConfig?.url&&{site:siteConfig},middleware:stxPageAuthMiddleware(),onRequest:async(req)=>{const{maintenanceGate,isApiBoundRequest:isApiBound,proxyToBackend,resolveRedirect}=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;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 \u2014 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 method=req.method.toUpperCase();if(method!=="GET"&&method!=="HEAD")return;let current=
|
|
1
|
+
import{existsSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";import process from"node:process";import{installRequestContext,parseCookieHeader}from"@stacksjs/config";import{log}from"@stacksjs/logging";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()){const configPath=join(cwd,"config/stx.ts");if(!existsSync(configPath))return;try{const dir=(await import(configPath)).default?.partialsDir;return typeof dir==="string"&&dir.length>0?dir:void 0}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 function resolveApiBase(configuredPort,env=process.env){if(env.API_URL)return env.API_URL;const explicitPort=Number(env.PORT_API);if(explicitPort)return`http://127.0.0.1:${explicitPort}`;if(["production","staging","development"].includes((env.APP_ENV||"").toLowerCase()))return null;return`http://127.0.0.1:${configuredPort||3008}`}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{applyViewSecurityHeaders,describeApiProxyRules,describeRedirectRules,injectGlobalAutoImports,resolveApiProxyRules,resolveEmbeddableRules,resolveRedirectRules}=await import("@stacksjs/server"),{resolveDefaultsResources}=await import("@stacksjs/actions/dev/defaults-resources"),{stxPageAuthMiddleware}=await import("@stacksjs/auth");await injectGlobalAutoImports();let stxServe;const serveCandidates=[join(homedir(),"Code/Tools/stx/packages/bun-plugin/dist/serve.js"),join(process.cwd(),"pantry/bun-plugin-stx/dist/serve.js")];for(const entry of serveCandidates)try{if(existsSync(entry)){({serve:stxServe}=await import(entry));break}}catch{}if(!stxServe)({serve:stxServe}=await import("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",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} \u2014 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 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:join(defaultsResources,"components"),layoutsDir:userLayoutsPath,...userPartialsPath&&{partialsDir:userPartialsPath},fallbackLayoutsDir:join(defaultsResources,"layouts"),fallbackPartialsDir:defaultViewsPath,quiet:options?.verbose!==!0,...stxModule&&{stxModule},...i18nConfig&&{i18n:i18nConfig},...siteConfig?.url&&{site:siteConfig},middleware:stxPageAuthMiddleware(),onRequest:async(req)=>{const{maintenanceGate,isApiBoundRequest:isApiBound,proxyToBackend,resolveRedirect}=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;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 \u2014 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}`)}try{const{seedCsrfCookieIfMissing}=await import(resolveCsrfMiddlewarePath());return await seedCsrfCookieIfMissing(req,current)??(current===baseline?secured:current)}catch(error){log.debug(`CSRF cookie seeding skipped: ${error.message}`);return current===baseline?secured:current}}});log.success(`Production server listening on http://0.0.0.0:${port}`)}async function resolveVendoredStxModule(){const candidates=[join(homedir(),"Code/Tools/stx/packages/stx/dist/index.js"),join(process.cwd(),"pantry/@stacksjs/stx/dist/index.js")];for(const entry of candidates)try{if(existsSync(entry))return await import(entry)}catch{}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){const resolverPaths=[join(homedir(),"Code/Tools/stx/packages/stx/src/site-builder/i18n.ts"),join(homedir(),"Code/Tools/stx/packages/stx/dist/index.js"),join(process.cwd(),"pantry/@stacksjs/stx/dist/index.js")];for(const resolverPath of resolverPaths)try{if(!existsSync(resolverPath))continue;const resolved=await import(resolverPath);if(typeof resolved.resolveI18n!=="function")continue;const i18n=resolved.resolveI18n(site,process.cwd());if(i18n)return i18n}catch{}try{const resolved=await import("@stacksjs/stx");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=join(process.cwd(),"site.config.ts");if(!existsSync(sitePath))return{};try{const site=(await import(sitePath)).default;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.72.
|
|
5
|
+
"version": "0.72.9",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,55 +95,55 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "^0.72.
|
|
99
|
-
"@stacksjs/ai": "^0.72.
|
|
100
|
-
"@stacksjs/alias": "^0.72.
|
|
101
|
-
"@stacksjs/arrays": "^0.72.
|
|
102
|
-
"@stacksjs/auth": "^0.72.
|
|
103
|
-
"@stacksjs/build": "^0.72.
|
|
104
|
-
"@stacksjs/cache": "^0.72.
|
|
105
|
-
"@stacksjs/cli": "^0.72.
|
|
98
|
+
"@stacksjs/actions": "^0.72.9",
|
|
99
|
+
"@stacksjs/ai": "^0.72.9",
|
|
100
|
+
"@stacksjs/alias": "^0.72.9",
|
|
101
|
+
"@stacksjs/arrays": "^0.72.9",
|
|
102
|
+
"@stacksjs/auth": "^0.72.9",
|
|
103
|
+
"@stacksjs/build": "^0.72.9",
|
|
104
|
+
"@stacksjs/cache": "^0.72.9",
|
|
105
|
+
"@stacksjs/cli": "^0.72.9",
|
|
106
106
|
"@stacksjs/clapp": "^0.2.12",
|
|
107
|
-
"@stacksjs/cloud": "^0.72.
|
|
108
|
-
"@stacksjs/collections": "^0.72.
|
|
109
|
-
"@stacksjs/config": "^0.72.
|
|
110
|
-
"@stacksjs/database": "^0.72.
|
|
111
|
-
"@stacksjs/desktop-build": "^0.72.
|
|
112
|
-
"@stacksjs/dns": "^0.72.
|
|
113
|
-
"@stacksjs/email": "^0.72.
|
|
114
|
-
"@stacksjs/enums": "^0.72.
|
|
115
|
-
"@stacksjs/error-handling": "^0.72.
|
|
116
|
-
"@stacksjs/events": "^0.72.
|
|
117
|
-
"@stacksjs/git": "^0.72.
|
|
107
|
+
"@stacksjs/cloud": "^0.72.9",
|
|
108
|
+
"@stacksjs/collections": "^0.72.9",
|
|
109
|
+
"@stacksjs/config": "^0.72.9",
|
|
110
|
+
"@stacksjs/database": "^0.72.9",
|
|
111
|
+
"@stacksjs/desktop-build": "^0.72.9",
|
|
112
|
+
"@stacksjs/dns": "^0.72.9",
|
|
113
|
+
"@stacksjs/email": "^0.72.9",
|
|
114
|
+
"@stacksjs/enums": "^0.72.9",
|
|
115
|
+
"@stacksjs/error-handling": "^0.72.9",
|
|
116
|
+
"@stacksjs/events": "^0.72.9",
|
|
117
|
+
"@stacksjs/git": "^0.72.9",
|
|
118
118
|
"@stacksjs/gitit": "^0.2.5",
|
|
119
|
-
"@stacksjs/health": "^0.72.
|
|
119
|
+
"@stacksjs/health": "^0.72.9",
|
|
120
120
|
"@stacksjs/dnsx": "^0.2.3",
|
|
121
121
|
"@stacksjs/httx": "^0.1.10",
|
|
122
|
-
"@stacksjs/image": "^0.72.
|
|
123
|
-
"@stacksjs/lint": "^0.72.
|
|
124
|
-
"@stacksjs/logging": "^0.72.
|
|
125
|
-
"@stacksjs/notifications": "^0.72.
|
|
126
|
-
"@stacksjs/objects": "^0.72.
|
|
127
|
-
"@stacksjs/orm": "^0.72.
|
|
128
|
-
"@stacksjs/path": "^0.72.
|
|
129
|
-
"@stacksjs/skills": "^0.72.
|
|
130
|
-
"@stacksjs/payments": "^0.72.
|
|
131
|
-
"@stacksjs/realtime": "^0.72.
|
|
132
|
-
"@stacksjs/router": "^0.72.
|
|
122
|
+
"@stacksjs/image": "^0.72.9",
|
|
123
|
+
"@stacksjs/lint": "^0.72.9",
|
|
124
|
+
"@stacksjs/logging": "^0.72.9",
|
|
125
|
+
"@stacksjs/notifications": "^0.72.9",
|
|
126
|
+
"@stacksjs/objects": "^0.72.9",
|
|
127
|
+
"@stacksjs/orm": "^0.72.9",
|
|
128
|
+
"@stacksjs/path": "^0.72.9",
|
|
129
|
+
"@stacksjs/skills": "^0.72.9",
|
|
130
|
+
"@stacksjs/payments": "^0.72.9",
|
|
131
|
+
"@stacksjs/realtime": "^0.72.9",
|
|
132
|
+
"@stacksjs/router": "^0.72.9",
|
|
133
133
|
"@stacksjs/rpx": "^0.11.42",
|
|
134
|
-
"@stacksjs/search-engine": "^0.72.
|
|
135
|
-
"@stacksjs/security": "^0.72.
|
|
136
|
-
"@stacksjs/server": "^0.72.
|
|
137
|
-
"@stacksjs/cms": "^0.72.
|
|
138
|
-
"@stacksjs/sites": "^0.72.
|
|
139
|
-
"@stacksjs/storage": "^0.72.
|
|
140
|
-
"@stacksjs/strings": "^0.72.
|
|
141
|
-
"@stacksjs/testing": "^0.72.
|
|
142
|
-
"@stacksjs/tunnel": "^0.72.
|
|
143
|
-
"@stacksjs/types": "^0.72.
|
|
144
|
-
"@stacksjs/ui": "^0.72.
|
|
145
|
-
"@stacksjs/utils": "^0.72.
|
|
146
|
-
"@stacksjs/validation": "^0.72.
|
|
134
|
+
"@stacksjs/search-engine": "^0.72.9",
|
|
135
|
+
"@stacksjs/security": "^0.72.9",
|
|
136
|
+
"@stacksjs/server": "^0.72.9",
|
|
137
|
+
"@stacksjs/cms": "^0.72.9",
|
|
138
|
+
"@stacksjs/sites": "^0.72.9",
|
|
139
|
+
"@stacksjs/storage": "^0.72.9",
|
|
140
|
+
"@stacksjs/strings": "^0.72.9",
|
|
141
|
+
"@stacksjs/testing": "^0.72.9",
|
|
142
|
+
"@stacksjs/tunnel": "^0.72.9",
|
|
143
|
+
"@stacksjs/types": "^0.72.9",
|
|
144
|
+
"@stacksjs/ui": "^0.72.9",
|
|
145
|
+
"@stacksjs/utils": "^0.72.9",
|
|
146
|
+
"@stacksjs/validation": "^0.72.9",
|
|
147
147
|
"@stacksjs/ts-cloud": "^0.8.3",
|
|
148
148
|
"ajv": "^8.20.0",
|
|
149
149
|
"ajv-formats": "^3.0.1",
|