@stacksjs/buddy 0.70.329 → 0.70.332
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/features.d.ts +1 -1
- package/dist/commands/features.js +1 -1
- package/dist/production-server.js +1 -1
- package/package.json +43 -43
- package/dist/commands/docs/buddy-commands.test.d.ts +0 -1
- package/dist/commands/docs/buddy-commands.test.js +0 -1
- package/dist/commands/docs/framework-repo.test.d.ts +0 -1
- package/dist/commands/docs/framework-repo.test.js +0 -1
- package/dist/commands/docs/generated-artifacts.test.d.ts +0 -1
- package/dist/commands/docs/generated-artifacts.test.js +0 -1
- package/dist/commands/docs/links.test.d.ts +0 -1
- package/dist/commands/docs/links.test.js +0 -11
|
@@ -164,7 +164,7 @@ export declare const FEATURE_FILES: Record<FeatureName, readonly string[]>;
|
|
|
164
164
|
* ```ts
|
|
165
165
|
* {
|
|
166
166
|
* cms: [ 'posts', 'pages', 'comments', 'tags', 'authors', 'categories', 'taggable_models', 'categorizable_models', 'commentables', ],
|
|
167
|
-
* 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', 'delivery_routes', 'digital_deliveries', 'shipping_methods', 'shipping_rates', 'shipping_zones', 'license_keys', 'loyalty_points', 'loyalty_rewards', 'print_devices', 'receipts', 'tax_rates', 'waitlist_products', 'waitlist_restaurants', ],
|
|
167
|
+
* 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', ],
|
|
168
168
|
* dashboard: [ 'boards', 'board_columns', 'cards', 'card_labels', 'card_assignees', 'card_comments', 'labels', 'ci_run_states', 'ci_runner_samples', 'ci_runner_alert_states', 'requests', 'logs', ],
|
|
169
169
|
* marketing: [ 'campaigns', 'campaign_sends', 'email_lists', 'email_list_subscribers', 'social_posts', 'mail_preferences', ],
|
|
170
170
|
* monitoring: ['errors'],
|
|
@@ -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","marketing","monitoring","realtime","queue"],FEATURE_FILES={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={cms:["posts","pages","comments","tags","authors","categories","taggable_models","categorizable_models","commentables"],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","delivery_routes","digital_deliveries","shipping_methods","shipping_rates","shipping_zones","license_keys","loyalty_points","loyalty_rewards","print_devices","receipts","tax_rates","waitlist_products","waitlist_restaurants"],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.",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","marketing","monitoring","realtime","queue"],FEATURE_FILES={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={cms:["posts","pages","comments","tags","authors","categories","taggable_models","categorizable_models","commentables"],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"],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.",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}=globalThis;if(!snapshot&&!legacyCookies&&legacySearch===void 0)return;return{...snapshot,cookies:snapshot?.cookies??legacyCookies??{},url:snapshot?.url||snapshot?.search||legacySearch||"",search:snapshot?.search??legacySearch??""}}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"),{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);return},onResponse:async(req,response)=>{const method=req.method.toUpperCase();if(method!=="GET"&&method!=="HEAD")return;try{const{seedCsrfCookieIfMissing}=await import(resolveCsrfMiddlewarePath());return seedCsrfCookieIfMissing(req,response)}catch(error){log.debug(`CSRF cookie seeding skipped: ${error.message}`)}}});log.success(`Production server listening on http://0.0.0.0:${port}`)}
|
|
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}=globalThis;if(!snapshot&&!legacyCookies&&legacySearch===void 0)return;return{...snapshot,cookies:snapshot?.cookies??legacyCookies??{},url:snapshot?.url||snapshot?.search||legacySearch||"",search:snapshot?.search??legacySearch??""}}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);return},onResponse:async(req,response)=>{const method=req.method.toUpperCase();if(method!=="GET"&&method!=="HEAD")return;try{const{seedCsrfCookieIfMissing}=await import(resolveCsrfMiddlewarePath());return seedCsrfCookieIfMissing(req,response)}catch(error){log.debug(`CSRF cookie seeding skipped: ${error.message}`)}}});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.70.
|
|
5
|
+
"version": "0.70.332",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,53 +95,53 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "^0.70.
|
|
99
|
-
"@stacksjs/ai": "^0.70.
|
|
100
|
-
"@stacksjs/alias": "^0.70.
|
|
101
|
-
"@stacksjs/arrays": "^0.70.
|
|
102
|
-
"@stacksjs/auth": "^0.70.
|
|
103
|
-
"@stacksjs/build": "^0.70.
|
|
104
|
-
"@stacksjs/cache": "^0.70.
|
|
105
|
-
"@stacksjs/cli": "^0.70.
|
|
98
|
+
"@stacksjs/actions": "^0.70.332",
|
|
99
|
+
"@stacksjs/ai": "^0.70.332",
|
|
100
|
+
"@stacksjs/alias": "^0.70.332",
|
|
101
|
+
"@stacksjs/arrays": "^0.70.332",
|
|
102
|
+
"@stacksjs/auth": "^0.70.332",
|
|
103
|
+
"@stacksjs/build": "^0.70.332",
|
|
104
|
+
"@stacksjs/cache": "^0.70.332",
|
|
105
|
+
"@stacksjs/cli": "^0.70.332",
|
|
106
106
|
"@stacksjs/clapp": "^0.2.12",
|
|
107
|
-
"@stacksjs/cloud": "^0.70.
|
|
108
|
-
"@stacksjs/collections": "^0.70.
|
|
109
|
-
"@stacksjs/config": "^0.70.
|
|
110
|
-
"@stacksjs/database": "^0.70.
|
|
111
|
-
"@stacksjs/desktop-build": "^0.70.
|
|
112
|
-
"@stacksjs/dns": "^0.70.
|
|
113
|
-
"@stacksjs/email": "^0.70.
|
|
114
|
-
"@stacksjs/enums": "^0.70.
|
|
115
|
-
"@stacksjs/error-handling": "^0.70.
|
|
116
|
-
"@stacksjs/events": "^0.70.
|
|
117
|
-
"@stacksjs/git": "^0.70.
|
|
107
|
+
"@stacksjs/cloud": "^0.70.332",
|
|
108
|
+
"@stacksjs/collections": "^0.70.332",
|
|
109
|
+
"@stacksjs/config": "^0.70.332",
|
|
110
|
+
"@stacksjs/database": "^0.70.332",
|
|
111
|
+
"@stacksjs/desktop-build": "^0.70.332",
|
|
112
|
+
"@stacksjs/dns": "^0.70.332",
|
|
113
|
+
"@stacksjs/email": "^0.70.332",
|
|
114
|
+
"@stacksjs/enums": "^0.70.332",
|
|
115
|
+
"@stacksjs/error-handling": "^0.70.332",
|
|
116
|
+
"@stacksjs/events": "^0.70.332",
|
|
117
|
+
"@stacksjs/git": "^0.70.332",
|
|
118
118
|
"@stacksjs/gitit": "^0.2.5",
|
|
119
|
-
"@stacksjs/health": "^0.70.
|
|
119
|
+
"@stacksjs/health": "^0.70.332",
|
|
120
120
|
"@stacksjs/dnsx": "^0.2.3",
|
|
121
121
|
"@stacksjs/httx": "^0.1.10",
|
|
122
|
-
"@stacksjs/image": "^0.70.
|
|
123
|
-
"@stacksjs/lint": "^0.70.
|
|
124
|
-
"@stacksjs/logging": "^0.70.
|
|
125
|
-
"@stacksjs/notifications": "^0.70.
|
|
126
|
-
"@stacksjs/objects": "^0.70.
|
|
127
|
-
"@stacksjs/orm": "^0.70.
|
|
128
|
-
"@stacksjs/path": "^0.70.
|
|
129
|
-
"@stacksjs/skills": "^0.70.
|
|
130
|
-
"@stacksjs/payments": "^0.70.
|
|
131
|
-
"@stacksjs/realtime": "^0.70.
|
|
132
|
-
"@stacksjs/router": "^0.70.
|
|
122
|
+
"@stacksjs/image": "^0.70.332",
|
|
123
|
+
"@stacksjs/lint": "^0.70.332",
|
|
124
|
+
"@stacksjs/logging": "^0.70.332",
|
|
125
|
+
"@stacksjs/notifications": "^0.70.332",
|
|
126
|
+
"@stacksjs/objects": "^0.70.332",
|
|
127
|
+
"@stacksjs/orm": "^0.70.332",
|
|
128
|
+
"@stacksjs/path": "^0.70.332",
|
|
129
|
+
"@stacksjs/skills": "^0.70.332",
|
|
130
|
+
"@stacksjs/payments": "^0.70.332",
|
|
131
|
+
"@stacksjs/realtime": "^0.70.332",
|
|
132
|
+
"@stacksjs/router": "^0.70.332",
|
|
133
133
|
"@stacksjs/rpx": "^0.11.42",
|
|
134
|
-
"@stacksjs/search-engine": "^0.70.
|
|
135
|
-
"@stacksjs/security": "^0.70.
|
|
136
|
-
"@stacksjs/server": "^0.70.
|
|
137
|
-
"@stacksjs/storage": "^0.70.
|
|
138
|
-
"@stacksjs/strings": "^0.70.
|
|
139
|
-
"@stacksjs/testing": "^0.70.
|
|
140
|
-
"@stacksjs/tunnel": "^0.70.
|
|
141
|
-
"@stacksjs/types": "^0.70.
|
|
142
|
-
"@stacksjs/ui": "^0.70.
|
|
143
|
-
"@stacksjs/utils": "^0.70.
|
|
144
|
-
"@stacksjs/validation": "^0.70.
|
|
134
|
+
"@stacksjs/search-engine": "^0.70.332",
|
|
135
|
+
"@stacksjs/security": "^0.70.332",
|
|
136
|
+
"@stacksjs/server": "^0.70.332",
|
|
137
|
+
"@stacksjs/storage": "^0.70.332",
|
|
138
|
+
"@stacksjs/strings": "^0.70.332",
|
|
139
|
+
"@stacksjs/testing": "^0.70.332",
|
|
140
|
+
"@stacksjs/tunnel": "^0.70.332",
|
|
141
|
+
"@stacksjs/types": "^0.70.332",
|
|
142
|
+
"@stacksjs/ui": "^0.70.332",
|
|
143
|
+
"@stacksjs/utils": "^0.70.332",
|
|
144
|
+
"@stacksjs/validation": "^0.70.332",
|
|
145
145
|
"@stacksjs/ts-cloud": "^0.7.103",
|
|
146
146
|
"ajv": "^8.20.0",
|
|
147
147
|
"ajv-formats": "^3.0.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{describe,expect,it}from"bun:test";import{renderBuddyCommandReference}from"./buddy-commands";const command={name:"deploy",description:"Deploy the application",aliases:["ship"],usage:"$ buddy deploy [environment]",arguments:[{name:"environment",required:!1,variadic:!1}],options:[{name:"region",flags:["r","region"],description:"Cloud region",required:!0,boolean:!1,negated:!1,default:"us-east-1"}],examples:["buddy deploy production"]};describe("Buddy command reference",()=>{it("renders every command contract field",()=>{const result=renderBuddyCommandReference({commands:[command],total:1});expect(result).toContain("**1 commands**");expect(result).toContain("### `deploy`");expect(result).toContain("Aliases: `ship`");expect(result).toContain("Arguments: `[environment]`");expect(result).toContain('| `-r`, `--region` | Cloud region | value, required | `"us-east-1"` |');expect(result).toContain("buddy deploy production")});it("sorts commands and rejects incomplete inventories",()=>{const about={...command,name:"about"},result=renderBuddyCommandReference({commands:[command,about],total:2});expect(result.indexOf("### `about`")).toBeLessThan(result.indexOf("### `deploy`"));expect(()=>renderBuddyCommandReference({commands:[command],total:2})).toThrow("does not match")});it("rejects duplicate canonical names",()=>{expect(()=>renderBuddyCommandReference({commands:[command,command],total:2})).toThrow("duplicate")})});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{describe,expect,it}from"bun:test";import{mkdirSync,mkdtempSync,rmSync}from"node:fs";import{tmpdir}from"node:os";import{join,resolve}from"node:path";import{assertFrameworkRepo,isInsideRoot,looksLikeFrameworkRepo}from"./framework-repo";describe("isInsideRoot",()=>{it("accepts the root itself and anything under it",()=>{expect(isInsideRoot("/a/b","/a/b")).toBe(!0);expect(isInsideRoot("/a/b","/a/b/c/d")).toBe(!0)});it("rejects a sibling and a parent",()=>{expect(isInsideRoot("/a/b","/a/c")).toBe(!1);expect(isInsideRoot("/a/b","/a")).toBe(!1)});it("rejects another project entirely",()=>{expect(isInsideRoot("/Users/x/Code/stacks","/Users/x/Code/Apps/reviewos.org")).toBe(!1)})});describe("looksLikeFrameworkRepo",()=>{it("recognises a tree carrying the framework core",()=>{const dir=mkdtempSync(join(tmpdir(),"stacks-fw-"));try{mkdirSync(resolve(dir,"storage/framework/core/buddy"),{recursive:!0});expect(looksLikeFrameworkRepo(dir)).toBe(!0)}finally{rmSync(dir,{recursive:!0,force:!0})}});it("does not mistake an ordinary application for it",()=>{const dir=mkdtempSync(join(tmpdir(),"stacks-app-"));try{mkdirSync(resolve(dir,"app"),{recursive:!0});expect(looksLikeFrameworkRepo(dir)).toBe(!1)}finally{rmSync(dir,{recursive:!0,force:!0})}})});describe("assertFrameworkRepo",()=>{it("passes inside the framework repo",()=>{const dir=mkdtempSync(join(tmpdir(),"stacks-fw-"));try{mkdirSync(resolve(dir,"storage/framework/core/buddy"),{recursive:!0});expect(()=>assertFrameworkRepo(dir,"docs:artifacts",dir)).not.toThrow()}finally{rmSync(dir,{recursive:!0,force:!0})}});it("refuses when run from somewhere else, and says where it would have written",()=>{const framework=mkdtempSync(join(tmpdir(),"stacks-fw-")),app=mkdtempSync(join(tmpdir(),"stacks-app-"));try{mkdirSync(resolve(framework,"storage/framework/core/buddy"),{recursive:!0});expect(()=>assertFrameworkRepo(framework,"docs:artifacts",app)).toThrow(/docs:artifacts/);expect(()=>assertFrameworkRepo(framework,"docs:artifacts",app)).toThrow(new RegExp(framework))}finally{rmSync(framework,{recursive:!0,force:!0});rmSync(app,{recursive:!0,force:!0})}});it("refuses when the root is not a framework tree at all",()=>{const dir=mkdtempSync(join(tmpdir(),"stacks-app-"));try{expect(()=>assertFrameworkRepo(dir,"docs:links",dir)).toThrow()}finally{rmSync(dir,{recursive:!0,force:!0})}})});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{describe,expect,it}from"bun:test";import{validateOpenApi}from"./generated-artifacts";describe("generated API artifact validation",()=>{it("rejects empty, duplicate, and optional path evidence",()=>{expect(validateOpenApi({paths:{}})).toContain("OpenAPI document has fewer than 10 registered paths");const paths={};for(let index=0;index<10;index++)paths[`/items/${index}/{id}`]={get:{operationId:index<2?"duplicate":`get_${index}`,parameters:[{name:"id",in:"path",required:index!==2,schema:{type:"string"}}],responses:{}}};const errors=validateOpenApi({paths});expect(errors).toContain("GET /items/1/{id}: duplicate operationId duplicate");expect(errors).toContain("GET /items/2/{id}: path parameter id must be required")})});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import{describe,expect,it}from"bun:test";import{extractLinks,isSkippableLink,resolveCandidates}from"./links";describe("docs link checker (stacksjs/stacks#2056)",()=>{describe("isSkippableLink",()=>{it("skips external, mail/tel, anchors, protocol-relative, and templated links",()=>{for(const target of["https://x.com","http://x.com","mailto:a@b.c","tel:123","#section","//cdn.example.com/x","{{ url }}","data:image/png;base64,AAAA",""])expect(isSkippableLink(target)).toBe(!0)});it("does not skip internal relative or absolute links",()=>{for(const target of["./foo.md","../bar/baz.md","/guide/intro","sibling.md"])expect(isSkippableLink(target)).toBe(!1)})});describe("extractLinks",()=>{it("extracts inline links with 1-based line numbers",()=>{expect(extractLinks(`intro
|
|
2
|
-
|
|
3
|
-
see [a](./a.md) and [b](../b.md)
|
|
4
|
-
`)).toEqual([{target:"./a.md",line:3},{target:"../b.md",line:3}])});it("strips an optional link title",()=>{expect(extractLinks('[x](/guide/x "The X page")')).toEqual([{target:"/guide/x",line:1}])});it("ignores links inside fenced code blocks",()=>{expect(extractLinks("```md\n[x](./nope.md)\n```\n[y](./yes.md)\n")).toEqual([{target:"./yes.md",line:4}])});it("ignores links inside inline code spans",()=>{expect(extractLinks("use `[x](./nope.md)` but link [y](./yes.md)")).toEqual([{target:"./yes.md",line:1}])});it("ignores links inside HTML comments while preserving line numbers",()=>{expect(extractLinks(`line1
|
|
5
|
-
<!--  -->
|
|
6
|
-
real [y](./y.md)
|
|
7
|
-
`)).toEqual([{target:"./y.md",line:3}])});it("handles a multi-line HTML comment",()=>{expect(extractLinks(`<!--
|
|
8
|
-
[x](./a.md)
|
|
9
|
-
-->
|
|
10
|
-
[y](./b.md)
|
|
11
|
-
`)).toEqual([{target:"./b.md",line:4}])})});describe("resolveCandidates",()=>{const docsRoot="/docs",fileDir="/docs/guide/components";it("resolves a relative link against the file directory",()=>{expect(resolveCandidates("./tabs.md",fileDir,docsRoot)).toEqual(["/docs/guide/components/tabs.md"])});it("resolves an absolute link against the docs root",()=>{expect(resolveCandidates("/guide/intro.md",fileDir,docsRoot)).toEqual(["/docs/guide/intro.md"])});it("offers .md and index.md candidates for an extensionless (clean-URL) link",()=>{expect(resolveCandidates("../intro",fileDir,docsRoot)).toEqual(["/docs/guide/intro","/docs/guide/intro.md","/docs/guide/intro/index.md"])});it("offers the .md source for an .html link",()=>{const candidates=resolveCandidates("./api.html",fileDir,docsRoot);expect(candidates).toContain("/docs/guide/components/api.md")});it("strips the anchor before resolving",()=>{expect(resolveCandidates("./tabs.md#usage",fileDir,docsRoot)).toEqual(["/docs/guide/components/tabs.md"])});it("returns no candidates for a pure anchor",()=>{expect(resolveCandidates("#usage",fileDir,docsRoot)).toEqual([])})})});
|