@stacksjs/actions 0.70.365 → 0.70.367
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/build/desktop.js +1 -1
- package/dist/dev/api.js +1 -1
- package/dist/dev/dashboard-native.d.ts +8 -0
- package/dist/dev/dashboard-native.js +1 -0
- package/dist/dev/dashboard-utils.d.ts +0 -7
- package/dist/dev/dashboard-utils.js +1 -1
- package/dist/dev/dashboard.js +3 -4
- package/dist/serve/api.js +1 -1
- package/package.json +20 -19
package/dist/build/desktop.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createHash}from"node:crypto";import{chmodSync,copyFileSync,existsSync,mkdirSync,readFileSync,rmSync,writeFileSync}from"node:fs";import{
|
|
1
|
+
import{createHash}from"node:crypto";import{chmodSync,copyFileSync,existsSync,mkdirSync,readFileSync,rmSync,writeFileSync}from"node:fs";import{join}from"node:path";import process from"node:process";import{log,runCommand}from"@stacksjs/cli";import{corePath,projectPath,storagePath}from"@stacksjs/path";import{assertDesktopReleaseChannel,resolveCraftExecutable,resolveDesktopLauncher}from"@stacksjs/desktop-build";const outputDir=storagePath("framework/desktop-dist"),launcherName=process.platform==="win32"?"stacks-desktop.exe":"stacks-desktop",runtimeName=process.platform==="win32"?"craft-runtime.exe":"craft-runtime",appUrl=process.env.DESKTOP_URL||process.env.APP_URL,releaseChannel=process.env.DESKTOP_RELEASE_CHANNEL==="stable"?"stable":"experimental",support=assertDesktopReleaseChannel(releaseChannel);if(!appUrl)throw Error("Desktop builds require APP_URL or DESKTOP_URL so the native app knows which Stacks application to open");const url=new URL(/^https?:\/\//.test(appUrl)?appUrl:`https://${appUrl}`),craftBinary=resolveCraftExecutable();if(readFileSync(craftBinary).subarray(0,2).toString()==="#!")throw Error(`CRAFT_BIN points at a script, not a native binary: ${craftBinary}. Point it at the compiled Craft executable (packages/zig/zig-out/bin/craft).`);if(existsSync(outputDir))rmSync(outputDir,{recursive:!0});mkdirSync(outputDir,{recursive:!0});const desktopSource=corePath("desktop");if(existsSync(join(desktopSource,"package.json"))&&existsSync(join(desktopSource,"src")))await runCommand("bun run build",{cwd:desktopSource});const launcherEntry=resolveDesktopLauncher();await runCommand(["bun","build","--compile",launcherEntry,"--outfile",join(outputDir,launcherName)],{cwd:projectPath()});copyFileSync(craftBinary,join(outputDir,runtimeName));if(process.platform!=="win32"){chmodSync(join(outputDir,launcherName),493);chmodSync(join(outputDir,runtimeName),493)}writeFileSync(join(outputDir,"desktop.json"),`${JSON.stringify({url:url.toString().replace(/\/$/,""),title:process.env.APP_NAME||"Stacks",width:1400,height:900,darkMode:!1,systemTray:!0,hideDockIcon:!1,releaseChannel,platform:process.platform,architecture:process.arch},null,2)}
|
|
2
2
|
`);const gitResult=Bun.spawnSync(["git","rev-parse","HEAD"],{cwd:projectPath()});if(gitResult.exitCode!==0)throw Error("Desktop builds require an exact Git source revision");const sourceRevision=gitResult.stdout.toString().trim(),remoteResult=Bun.spawnSync(["git","remote","get-url","origin"],{cwd:projectPath()}),sourceRepository=remoteResult.exitCode===0?remoteResult.stdout.toString().trim().replace(/\.git$/,""):"unknown",artifacts=[launcherName,runtimeName,"desktop.json"].map((name)=>{const contents=readFileSync(join(outputDir,name));return{name,bytes:contents.byteLength,sha256:createHash("sha256").update(contents).digest("hex")}});writeFileSync(join(outputDir,"provenance.json"),`${JSON.stringify({schemaVersion:"1.0.0",sourceRepository,sourceRevision,builtWith:{bun:Bun.version,craftSha256:artifacts.find((artifact)=>artifact.name===runtimeName)?.sha256},target:{platform:process.platform,architecture:process.arch,status:support.status,osVersions:support.osVersions},releaseChannel,artifacts},null,2)}
|
|
3
3
|
`);writeFileSync(join(outputDir,"checksums.sha256"),`${artifacts.map((artifact)=>`${artifact.sha256} ${artifact.name}`).join(`
|
|
4
4
|
`)}
|
package/dist/dev/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{config,overridesReady}from"@stacksjs/config";import{path}from"@stacksjs/path";import{route}from"@stacksjs/router";import{autoImportsAreStale,generateAutoImportFiles,generateServerAutoImportTypes,injectGlobalAutoImports}from"@stacksjs/server";import{resolveApiHost}from"../helpers/api-host";import{exitWithParent}from"./exit-with-parent";const _options=parseOptions();exitWithParent();await generateServerAutoImportTypes();await overridesReady;const port=Number(process.env.PORT_API)||config.ports?.api||3008,hostname=resolveApiHost(),modelsIndex=path.storagePath("framework/auto-imports/models.ts");if(!existsSync(modelsIndex)||autoImportsAreStale())await generateAutoImportFiles();await injectGlobalAutoImports();try{const typesenseDev=path.appPath("Lib/typesense-dev.ts");if(existsSync(typesenseDev)){const mod=await import(typesenseDev);if(typeof mod.ensureTypesenseRunning==="function")await mod.ensureTypesenseRunning()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] search engine bootstrap skipped: ${err.message}`)}try{const listenerPath=path.appPath("Listener.ts");if(existsSync(listenerPath)){const listener=await import(listenerPath);if(typeof listener.handleEvents==="function")await listener.handleEvents()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] failed to bootstrap event listeners \u2014 dispatched events will be ignored: ${err.message}`)}try{const{startModelMigrationWatcher}=await import("./migrate-watcher");startModelMigrationWatcher()}catch(err){const{log}=await import("@stacksjs/cli");log.debug(`[api:dev] auto-migrate watcher not started: ${err.message}`)}function resolveDefaultsCorsPath(){const vendored=path.frameworkPath("defaults/app/Middleware/Cors.ts");if(existsSync(vendored))return vendored;try{return`${Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd()).replace(/package\.json$/,"")}app/Middleware/Cors.ts`}catch{return vendored}}const corsMod=await import(resolveDefaultsCorsPath()),corsMiddleware=corsMod.default;route.use(corsMiddleware.toRouterHandler());const SAFE_REQUEST_ID=/^[a-zA-Z0-9._-]{8,128}$/;route.use(async(request,next)=>{const inbound=request?.headers?.get?.("x-request-id")||request?.headers?.get?.("X-Request-ID")||"",id=typeof inbound==="string"&&SAFE_REQUEST_ID.test(inbound)?inbound:crypto.randomUUID();request._requestId=id;request._startNs=process.hrtime.bigint();const response=await next();if(response&&response.status===404&&(response.headers.get("content-type")||"").includes("json"))try{const body=await response.clone().json();if(body&&(body.message==="Not Found"||body.error==="Not Found")){const url=new URL(request.url),enriched={...body,path:url.pathname,method:request.method,request_id:id},headers=new Headers(response.headers);headers.set("X-Request-ID",id);return new Response(JSON.stringify(enriched),{status:404,headers})}}catch{}return response});await route.importRoutes();try{await route.serve({port,hostname});console.log(`
|
|
1
|
+
import{existsSync}from"node:fs";import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{config,overridesReady}from"@stacksjs/config";import{path}from"@stacksjs/path";import{disableViewRouting,route}from"@stacksjs/router";import{autoImportsAreStale,generateAutoImportFiles,generateServerAutoImportTypes,injectGlobalAutoImports}from"@stacksjs/server";import{resolveApiHost}from"../helpers/api-host";import{exitWithParent}from"./exit-with-parent";const _options=parseOptions();exitWithParent();await generateServerAutoImportTypes();await overridesReady;const port=Number(process.env.PORT_API)||config.ports?.api||3008,hostname=resolveApiHost(),modelsIndex=path.storagePath("framework/auto-imports/models.ts");if(!existsSync(modelsIndex)||autoImportsAreStale())await generateAutoImportFiles();await injectGlobalAutoImports();try{const typesenseDev=path.appPath("Lib/typesense-dev.ts");if(existsSync(typesenseDev)){const mod=await import(typesenseDev);if(typeof mod.ensureTypesenseRunning==="function")await mod.ensureTypesenseRunning()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] search engine bootstrap skipped: ${err.message}`)}try{const listenerPath=path.appPath("Listener.ts");if(existsSync(listenerPath)){const listener=await import(listenerPath);if(typeof listener.handleEvents==="function")await listener.handleEvents()}}catch(err){const{log}=await import("@stacksjs/cli");log.warn(`[api:dev] failed to bootstrap event listeners \u2014 dispatched events will be ignored: ${err.message}`)}try{const{startModelMigrationWatcher}=await import("./migrate-watcher");startModelMigrationWatcher()}catch(err){const{log}=await import("@stacksjs/cli");log.debug(`[api:dev] auto-migrate watcher not started: ${err.message}`)}function resolveDefaultsCorsPath(){const vendored=path.frameworkPath("defaults/app/Middleware/Cors.ts");if(existsSync(vendored))return vendored;try{return`${Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd()).replace(/package\.json$/,"")}app/Middleware/Cors.ts`}catch{return vendored}}const corsMod=await import(resolveDefaultsCorsPath()),corsMiddleware=corsMod.default;route.use(corsMiddleware.toRouterHandler());const SAFE_REQUEST_ID=/^[a-zA-Z0-9._-]{8,128}$/;route.use(async(request,next)=>{const inbound=request?.headers?.get?.("x-request-id")||request?.headers?.get?.("X-Request-ID")||"",id=typeof inbound==="string"&&SAFE_REQUEST_ID.test(inbound)?inbound:crypto.randomUUID();request._requestId=id;request._startNs=process.hrtime.bigint();const response=await next();if(response&&response.status===404&&(response.headers.get("content-type")||"").includes("json"))try{const body=await response.clone().json();if(body&&(body.message==="Not Found"||body.error==="Not Found")){const url=new URL(request.url),enriched={...body,path:url.pathname,method:request.method,request_id:id},headers=new Headers(response.headers);headers.set("X-Request-ID",id);return new Response(JSON.stringify(enriched),{status:404,headers})}}catch{}return response});await route.importRoutes();disableViewRouting(route.bunRouter);try{await route.serve({port,hostname});console.log(`
|
|
2
2
|
\u279C API server ready: http://${hostname}:${port}
|
|
3
3
|
`)}catch(err){if((err?.code||err?.errno)==="EADDRINUSE"||String(err?.message||"").includes("EADDRINUSE")){console.error(`
|
|
4
4
|
[dev/api] Port ${port} is already in use. Kill the other process and re-run \`./buddy dev\`, or set PORT_API to another port.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function resolveDashboardCraftExecutable(resolveCraftBinary: CraftBinaryResolver, options?: DashboardCraftResolutionOptions): string | undefined;
|
|
2
|
+
export declare interface DashboardCraftResolutionOptions {
|
|
3
|
+
disabled?: boolean
|
|
4
|
+
explicit?: string
|
|
5
|
+
findOnPath?: CraftPathLocator
|
|
6
|
+
}
|
|
7
|
+
export type CraftBinaryResolver = (explicit?: string) => string;
|
|
8
|
+
export type CraftPathLocator = (binary: string) => string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import process from"node:process";export function resolveDashboardCraftExecutable(resolveCraftBinary,options={}){if(options.disabled??process.env.STACKS_NO_NATIVE==="1")return;const explicit=options.explicit??process.env.CRAFT_BIN,resolved=resolveCraftBinary(explicit);if(resolved!=="craft")return resolved;return(options.findOnPath??((binary)=>Bun.which(binary)))(resolved)??void 0}
|
|
@@ -17,13 +17,6 @@ export declare function findAvailablePort(preferred: number, maxAttempts?: numbe
|
|
|
17
17
|
export declare function waitForServer(port: number, maxWait?: number): Promise<boolean>;
|
|
18
18
|
/** Build sidebar config for native sidebar navigation */
|
|
19
19
|
export declare function buildSidebarConfig(baseRoute: string, discoveredModels: DiscoveredModel[], toggles?: DashboardSectionToggles): void;
|
|
20
|
-
/**
|
|
21
|
-
* Build the URL the Craft window opens to. The `native-sidebar=1` query
|
|
22
|
-
* param signals the layout that Craft is rendering the sidebar natively,
|
|
23
|
-
* so the layout can suppress its own HTML sidebar and let the macOS
|
|
24
|
-
* NSOutlineView own that 240px column.
|
|
25
|
-
*/
|
|
26
|
-
export declare function buildDashboardUrl(port: number): string;
|
|
27
20
|
/**
|
|
28
21
|
* Model name to icon mapping (SF Symbols)
|
|
29
22
|
* @defaultValue
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Glob}from"bun";import{existsSync}from"node:fs";import path from"node:path";import{pathToFileURL}from"node:url";export const iconMap={user:"person.fill",team:"person.2.fill",subscriber:"envelope.open.fill",subscriberemail:"envelope.fill",post:"doc.text.fill",page:"doc.fill",product:"cube.box.fill",productvariant:"cube.fill",productunit:"scalemass.fill",order:"list.clipboard.fill",orderitem:"list.bullet.clipboard.fill",customer:"person.crop.circle.fill",payment:"creditcard.fill",paymentmethod:"creditcard.and.123",paymentproduct:"creditcard.fill",paymenttransaction:"creditcard.fill",category:"tag.fill",tag:"tag",comment:"bubble.left.fill",author:"person.text.rectangle.fill",notification:"bell.fill",job:"briefcase.fill",log:"list.bullet.rectangle.portrait.fill",campaign:"megaphone.fill",review:"star.fill",coupon:"ticket.fill",giftcard:"giftcard.fill",cart:"cart.fill",cartitem:"cart.badge.plus",taxrate:"percent",transaction:"arrow.left.arrow.right.circle.fill",subscription:"repeat.circle.fill",emaillist:"envelope.badge.person.crop",socialpost:"bubble.left.and.bubble.right.fill",manufacturer:"building.2.fill",shippingmethod:"shippingbox.fill",shippingzone:"map.fill",shippingrate:"dollarsign.circle.fill",deliveryroute:"map.fill",driver:"car.fill",digitaldelivery:"arrow.down.doc.fill",licensekey:"key.fill",loyaltypoint:"star.circle.fill",loyaltyreward:"gift.fill",printdevice:"printer.fill",receipt:"doc.text.fill",waitlistproduct:"clock.fill",waitlistrestaurant:"clock.badge.checkmark.fill",websocket:"antenna.radiowaves.left.and.right",default:"tablecells.fill"},excludeModels=new Set(["activity","request","error","failedjob"]),dedicatedPages=new Set(["user","team","subscriber","post","page","product","order","customer","payment","coupon","category","tag","comment","author","notification","job","campaign","review","gift-card"]);const ROOT_MODEL_CATEGORY={user:"data",team:"data",subscriber:"data",activity:"data",campaign:"marketing",emaillist:"marketing",socialpost:"marketing",subscriberemail:"marketing",comment:"content",tag:"content",paymentmethod:"commerce",paymentproduct:"commerce",paymenttransaction:"commerce",subscription:"commerce",deployment:"system",error:"system",failedjob:"system",job:"system",log:"system",notification:"system",release:"system",request:"system"};export function modelNameToId(name){return name.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"")}export function getModelIcon(name){return iconMap[name.toLowerCase()]||iconMap.default||"database"}function categorizeModel(relativePath,modelId,defaultCategory){if(defaultCategory)return defaultCategory;const firstSeg=relativePath.includes("/")?(relativePath.split("/")[0]??"").toLowerCase():"";if(firstSeg==="commerce")return"commerce";if(firstSeg==="content")return"content";if(firstSeg==="realtime")return"system";return ROOT_MODEL_CATEGORY[modelId.replace(/-/g,"")]??"data"}async function readModelDashboardConfig(absolutePath){try{const mod=await import(pathToFileURL(absolutePath).href);return(mod.default??mod).dashboard}catch(error){throw Error(`Could not discover dashboard model ${absolutePath}: ${error instanceof Error?error.message:String(error)}`)}}export async function scanModelsDir(dir,seenNames,models,defaultCategory){if(!existsSync(dir))return;const glob=new Glob("**/*.ts");for await(const file of glob.scan({cwd:dir})){if(file.includes("README")||file.includes(".d.ts"))continue;const name=file.replace(/\.ts$/,"").split("/").pop()||"",nameLower=name.toLowerCase();if(name&&!seenNames.has(nameLower)&&!excludeModels.has(nameLower)){seenNames.add(nameLower);const id=modelNameToId(name),absolutePath=path.resolve(dir,file),dashboard=await readModelDashboardConfig(absolutePath);models.push({name,icon:dashboard?.icon??getModelIcon(name),id,category:categorizeModel(file,id,defaultCategory),dashboard})}}}export async function discoverModels(userModelsPath,defaultModelsPath){const models=[],seenNames=new Set;await scanModelsDir(userModelsPath,seenNames,models,"userland");await scanModelsDir(defaultModelsPath,seenNames,models);return models.sort((a,b)=>a.name.localeCompare(b.name))}export function buildManifest(models){return models.map((m)=>{const row={id:m.id,name:m.name,icon:m.icon,hasDedicatedPage:dedicatedPages.has(m.id),category:m.category??"data"};if(m.dashboard)row.dashboard=m.dashboard;return row})}export async function isPortAvailable(port){try{Bun.serve({port,fetch:()=>new Response}).stop(!0);return!0}catch{return!1}}export async function findAvailablePort(preferred,maxAttempts=20){for(let i=0;i<maxAttempts;i++){const port=preferred+i;if(await isPortAvailable(port))return port}return preferred}export async function waitForServer(port,maxWait=500){const start=Date.now();while(Date.now()-start<maxWait){try{const res=await fetch(`http://localhost:${port}/`,{method:"HEAD"});if(res.ok||res.status===404)return!0}catch{}await new Promise((resolve)=>setTimeout(resolve,25))}return!1}function effectiveCategory(m){if(m.dashboard?.enabled===!1)return;if(m.dashboard?.section)return m.dashboard.section;return m.category??"data"}function categoryRows(baseRoute,models,category,dedicated){return models.filter((m)=>effectiveCategory(m)===category).filter((m)=>!dedicatedPages.has(m.id)&&!dedicated.has(m.id)).map((m)=>({id:`model-${m.id}`,label:m.dashboard?.label??m.name,icon:m.icon,url:`${baseRoute}/models/${m.id}`,...m.dashboard?.roles&&m.dashboard.roles.length>0?{roles:m.dashboard.roles}:{}}))}export function buildSidebarConfig(baseRoute,discoveredModels,toggles={}){const enabled={library:toggles.library??!0,content:toggles.content??!0,commerce:toggles.commerce??!0,marketing:toggles.marketing??!0,analytics:toggles.analytics??!0,management:toggles.management??!0,utilities:toggles.utilities??!0},dataRow={dashboard:toggles.data?.dashboard??!0,activity:toggles.data?.activity??!0,users:toggles.data?.users??!0,teams:toggles.data?.teams??!0,subscribers:toggles.data?.subscribers??!0,allModels:toggles.data?.allModels??!0},sections=[],homeItems=[{id:"home",label:"Dashboard",icon:"house.fill",url:`${baseRoute}/`},{id:"dependencies",label:"Dependencies",icon:"shippingbox.fill",url:`${baseRoute}/dependencies`},{id:"health",label:"Health",icon:"heart.text.square.fill",url:`${baseRoute}/health`}];if(toggles.ci)homeItems.push({id:"ci",label:"CI",icon:"checkmark.seal.fill",url:`${baseRoute}/ci`});sections.push({id:"home",title:"Home",items:homeItems});if(enabled.library)sections.push({id:"library",title:"Library",items:[{id:"functions",label:"Functions",icon:"function",url:`${baseRoute}/functions`},{id:"packages",label:"Packages",icon:"shippingbox.fill",url:`${baseRoute}/packages`}]});if(enabled.content)sections.push({id:"content",title:"Content",items:[{id:"content-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/content/dashboard`},{id:"files",label:"Files",icon:"folder.fill",url:`${baseRoute}/content/files`},{id:"pages",label:"Pages",icon:"doc.fill",url:`${baseRoute}/content/pages`},{id:"posts",label:"Posts",icon:"text.bubble.fill",url:`${baseRoute}/content/posts`},{id:"categories",label:"Categories",icon:"tag.fill",url:`${baseRoute}/content/categories`},{id:"tags",label:"Tags",icon:"tag",url:`${baseRoute}/content/tags`},{id:"comments",label:"Comments",icon:"bubble.left.fill",url:`${baseRoute}/content/comments`},{id:"authors",label:"Authors",icon:"person.text.rectangle.fill",url:`${baseRoute}/content/authors`},{id:"seo",label:"SEO",icon:"magnifyingglass",url:`${baseRoute}/content/seo`},...categoryRows(baseRoute,discoveredModels,"content",new Set(["page","post","category","tag","comment","author"]))]});sections.push({id:"app",title:"App",items:[{id:"deployments",label:"Deployments",icon:"rocket.fill",url:`${baseRoute}/deployments`},{id:"requests",label:"Requests",icon:"arrow.left.arrow.right",url:`${baseRoute}/requests`},{id:"realtime",label:"Realtime",icon:"link",url:`${baseRoute}/realtime`},{id:"actions",label:"Actions",icon:"bolt.fill",url:`${baseRoute}/actions`},{id:"commands",label:"Commands",icon:"terminal.fill",url:`${baseRoute}/commands`},{id:"queue",label:"Queue",icon:"list.bullet.rectangle.fill",url:`${baseRoute}/queue`},{id:"jobs",label:"Jobs",icon:"briefcase.fill",url:`${baseRoute}/jobs`},{id:"inbox",label:"Inbox",icon:"tray.full.fill",url:`${baseRoute}/inbox`},{id:"queries",label:"Queries",icon:"magnifyingglass.circle.fill",url:`${baseRoute}/queries`},{id:"notifications",label:"Notifications",icon:"bell.fill",url:`${baseRoute}/notifications/dashboard`},...categoryRows(baseRoute,discoveredModels,"system",new Set)]});const dataItems=[];if(dataRow.dashboard)dataItems.push({id:"data-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/data/dashboard`});if(dataRow.activity)dataItems.push({id:"activity",label:"Activity",icon:"waveform.path.ecg",url:`${baseRoute}/data/activity`});if(dataRow.users)dataItems.push({id:"users",label:"Users",icon:"person.crop.circle.fill",url:`${baseRoute}/data/users`});if(dataRow.teams)dataItems.push({id:"teams",label:"Teams",icon:"person.3.fill",url:`${baseRoute}/data/teams`});if(dataRow.subscribers)dataItems.push({id:"subscribers",label:"Subscribers",icon:"envelope.fill",url:`${baseRoute}/data/subscribers`});if(dataRow.allModels)dataItems.push({id:"data-models",label:"All Models",icon:"square.grid.2x2.fill",url:`${baseRoute}/models`});const dynamicRows=discoveredModels.filter((m)=>{const eff=effectiveCategory(m);return eff==="userland"||eff==="data"}).filter((m)=>!dedicatedPages.has(m.id)).map((model)=>({id:`model-${model.id}`,label:model.dashboard?.label??model.name,icon:model.icon,url:`${baseRoute}/models/${model.id}`,...model.dashboard?.roles&&model.dashboard.roles.length>0?{roles:model.dashboard.roles}:{}}));sections.push({id:"data",title:"Data",items:[...dataItems,...dynamicRows]});if(enabled.commerce)sections.push({id:"commerce",title:"Commerce",items:[{id:"commerce-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/commerce/dashboard`},{id:"customers",label:"Customers",icon:"person.crop.circle.fill",url:`${baseRoute}/commerce/customers`},{id:"orders",label:"Orders",icon:"list.clipboard.fill",url:`${baseRoute}/commerce/orders`},{id:"products",label:"Products",icon:"cube.box.fill",url:`${baseRoute}/commerce/products`},{id:"coupons",label:"Coupons",icon:"ticket.fill",url:`${baseRoute}/commerce/coupons`},{id:"gift-cards",label:"Gift Cards",icon:"giftcard.fill",url:`${baseRoute}/commerce/gift-cards`},{id:"payments",label:"Payments",icon:"creditcard.fill",url:`${baseRoute}/commerce/payments`},{id:"delivery",label:"Delivery",icon:"truck.box.fill",url:`${baseRoute}/commerce/delivery`},{id:"taxes",label:"Taxes",icon:"percent",url:`${baseRoute}/commerce/taxes`},...categoryRows(baseRoute,discoveredModels,"commerce",new Set(["customer","order","product","coupon","gift-card","payment","review","category"]))]});if(enabled.marketing)sections.push({id:"marketing",title:"Marketing",items:[{id:"lists",label:"Lists",icon:"list.bullet",url:`${baseRoute}/marketing/lists`},{id:"social-posts",label:"Social Posts",icon:"clock.fill",url:`${baseRoute}/marketing/social-posts`},{id:"campaigns",label:"Campaigns",icon:"megaphone.fill",url:`${baseRoute}/marketing/campaigns`},{id:"marketing-reviews",label:"Reviews",icon:"star.fill",url:`${baseRoute}/marketing/reviews`},...categoryRows(baseRoute,discoveredModels,"marketing",new Set(["campaign"]))]});if(enabled.analytics)sections.push({id:"analytics",title:"Analytics",items:[{id:"analytics-web",label:"Web",icon:"globe",url:`${baseRoute}/analytics/web`},{id:"analytics-blog",label:"Blog",icon:"doc.text.fill",url:`${baseRoute}/analytics/blog`},{id:"analytics-commerce",label:"Commerce",icon:"cart.fill",url:`${baseRoute}/analytics/commerce/web`},{id:"analytics-marketing",label:"Marketing",icon:"megaphone.fill",url:`${baseRoute}/analytics/marketing`}]});if(enabled.management)sections.push({id:"management",title:"Management",items:[{id:"cloud",label:"Cloud",icon:"cloud.fill",url:`${baseRoute}/cloud`},{id:"servers",label:"Servers",icon:"server.rack",url:`${baseRoute}/servers`},{id:"serverless",label:"Serverless",icon:"bolt.horizontal.circle.fill",url:`${baseRoute}/serverless`},{id:"dns",label:"DNS",icon:"network",url:`${baseRoute}/dns`},{id:"mailboxes",label:"Mailboxes",icon:"tray.full.fill",url:`${baseRoute}/mailboxes`},{id:"logs",label:"Logs",icon:"list.bullet.rectangle.portrait.fill",url:`${baseRoute}/logs`},{id:"kanban",label:"Kanban",icon:"rectangle.stack.fill",url:`${baseRoute}/kanban`,roles:["admin","dev"]}]});if(enabled.utilities)sections.push({id:"utilities",title:"Utilities",items:[{id:"buddy",label:"AI Buddy",icon:"bubble.left.and.bubble.right.fill",url:`${baseRoute}/buddy`},{id:"environment",label:"Environment",icon:"key.fill",url:`${baseRoute}/environment`},{id:"access-tokens",label:"Access Tokens",icon:"key.horizontal.fill",url:`${baseRoute}/access-tokens`},{id:"settings",label:"Settings",icon:"gear",url:`${baseRoute}/settings/billing`}]});return{sections,minWidth:200,maxWidth:280}}export function buildDashboardUrl(port){return`http://localhost:${port}/?native-sidebar=1`}
|
|
1
|
+
import{Glob}from"bun";import{existsSync}from"node:fs";import path from"node:path";import{pathToFileURL}from"node:url";export const iconMap={user:"person.fill",team:"person.2.fill",subscriber:"envelope.open.fill",subscriberemail:"envelope.fill",post:"doc.text.fill",page:"doc.fill",product:"cube.box.fill",productvariant:"cube.fill",productunit:"scalemass.fill",order:"list.clipboard.fill",orderitem:"list.bullet.clipboard.fill",customer:"person.crop.circle.fill",payment:"creditcard.fill",paymentmethod:"creditcard.and.123",paymentproduct:"creditcard.fill",paymenttransaction:"creditcard.fill",category:"tag.fill",tag:"tag",comment:"bubble.left.fill",author:"person.text.rectangle.fill",notification:"bell.fill",job:"briefcase.fill",log:"list.bullet.rectangle.portrait.fill",campaign:"megaphone.fill",review:"star.fill",coupon:"ticket.fill",giftcard:"giftcard.fill",cart:"cart.fill",cartitem:"cart.badge.plus",taxrate:"percent",transaction:"arrow.left.arrow.right.circle.fill",subscription:"repeat.circle.fill",emaillist:"envelope.badge.person.crop",socialpost:"bubble.left.and.bubble.right.fill",manufacturer:"building.2.fill",shippingmethod:"shippingbox.fill",shippingzone:"map.fill",shippingrate:"dollarsign.circle.fill",deliveryroute:"map.fill",driver:"car.fill",digitaldelivery:"arrow.down.doc.fill",licensekey:"key.fill",loyaltypoint:"star.circle.fill",loyaltyreward:"gift.fill",printdevice:"printer.fill",receipt:"doc.text.fill",waitlistproduct:"clock.fill",waitlistrestaurant:"clock.badge.checkmark.fill",websocket:"antenna.radiowaves.left.and.right",default:"tablecells.fill"},excludeModels=new Set(["activity","request","error","failedjob"]),dedicatedPages=new Set(["user","team","subscriber","post","page","product","order","customer","payment","coupon","category","tag","comment","author","notification","job","campaign","review","gift-card"]);const ROOT_MODEL_CATEGORY={user:"data",team:"data",subscriber:"data",activity:"data",campaign:"marketing",emaillist:"marketing",socialpost:"marketing",subscriberemail:"marketing",comment:"content",tag:"content",paymentmethod:"commerce",paymentproduct:"commerce",paymenttransaction:"commerce",subscription:"commerce",deployment:"system",error:"system",failedjob:"system",job:"system",log:"system",notification:"system",release:"system",request:"system"};export function modelNameToId(name){return name.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"")}export function getModelIcon(name){return iconMap[name.toLowerCase()]||iconMap.default||"database"}function categorizeModel(relativePath,modelId,defaultCategory){if(defaultCategory)return defaultCategory;const firstSeg=relativePath.includes("/")?(relativePath.split("/")[0]??"").toLowerCase():"";if(firstSeg==="commerce")return"commerce";if(firstSeg==="content")return"content";if(firstSeg==="realtime")return"system";return ROOT_MODEL_CATEGORY[modelId.replace(/-/g,"")]??"data"}async function readModelDashboardConfig(absolutePath){try{const mod=await import(pathToFileURL(absolutePath).href);return(mod.default??mod).dashboard}catch(error){throw Error(`Could not discover dashboard model ${absolutePath}: ${error instanceof Error?error.message:String(error)}`)}}export async function scanModelsDir(dir,seenNames,models,defaultCategory){if(!existsSync(dir))return;const glob=new Glob("**/*.ts");for await(const file of glob.scan({cwd:dir})){if(file.includes("README")||file.includes(".d.ts"))continue;const name=file.replace(/\.ts$/,"").split("/").pop()||"",nameLower=name.toLowerCase();if(name&&!seenNames.has(nameLower)&&!excludeModels.has(nameLower)){seenNames.add(nameLower);const id=modelNameToId(name),absolutePath=path.resolve(dir,file),dashboard=await readModelDashboardConfig(absolutePath);models.push({name,icon:dashboard?.icon??getModelIcon(name),id,category:categorizeModel(file,id,defaultCategory),dashboard})}}}export async function discoverModels(userModelsPath,defaultModelsPath){const models=[],seenNames=new Set;await scanModelsDir(userModelsPath,seenNames,models,"userland");await scanModelsDir(defaultModelsPath,seenNames,models);return models.sort((a,b)=>a.name.localeCompare(b.name))}export function buildManifest(models){return models.map((m)=>{const row={id:m.id,name:m.name,icon:m.icon,hasDedicatedPage:dedicatedPages.has(m.id),category:m.category??"data"};if(m.dashboard)row.dashboard=m.dashboard;return row})}export async function isPortAvailable(port){try{await Bun.serve({port,fetch:()=>new Response}).stop(!0);return!0}catch{return!1}}export async function findAvailablePort(preferred,maxAttempts=20){for(let i=0;i<maxAttempts;i++){const port=preferred+i;if(await isPortAvailable(port))return port}return preferred}export async function waitForServer(port,maxWait=500){const start=Date.now();while(Date.now()-start<maxWait){try{const res=await fetch(`http://localhost:${port}/`,{method:"HEAD"});if(res.ok||res.status===404)return!0}catch{}await new Promise((resolve)=>setTimeout(resolve,25))}return!1}function effectiveCategory(m){if(m.dashboard?.enabled===!1)return;if(m.dashboard?.section)return m.dashboard.section;return m.category??"data"}function categoryRows(baseRoute,models,category,dedicated){return models.filter((m)=>effectiveCategory(m)===category).filter((m)=>!dedicatedPages.has(m.id)&&!dedicated.has(m.id)).map((m)=>({id:`model-${m.id}`,label:m.dashboard?.label??m.name,icon:m.icon,url:`${baseRoute}/models/${m.id}`,...m.dashboard?.roles&&m.dashboard.roles.length>0?{roles:m.dashboard.roles}:{}}))}export function buildSidebarConfig(baseRoute,discoveredModels,toggles={}){const enabled={library:toggles.library??!0,content:toggles.content??!0,commerce:toggles.commerce??!0,marketing:toggles.marketing??!0,analytics:toggles.analytics??!0,management:toggles.management??!0,utilities:toggles.utilities??!0},dataRow={dashboard:toggles.data?.dashboard??!0,activity:toggles.data?.activity??!0,users:toggles.data?.users??!0,teams:toggles.data?.teams??!0,subscribers:toggles.data?.subscribers??!0,allModels:toggles.data?.allModels??!0},sections=[],homeItems=[{id:"home",label:"Dashboard",icon:"house.fill",url:`${baseRoute}/`},{id:"dependencies",label:"Dependencies",icon:"shippingbox.fill",url:`${baseRoute}/dependencies`},{id:"health",label:"Health",icon:"heart.text.square.fill",url:`${baseRoute}/health`}];if(toggles.ci)homeItems.push({id:"ci",label:"CI",icon:"checkmark.seal.fill",url:`${baseRoute}/ci`});sections.push({id:"home",title:"Home",items:homeItems});if(enabled.library)sections.push({id:"library",title:"Library",items:[{id:"functions",label:"Functions",icon:"function",url:`${baseRoute}/functions`},{id:"packages",label:"Packages",icon:"shippingbox.fill",url:`${baseRoute}/packages`}]});if(enabled.content)sections.push({id:"content",title:"Content",items:[{id:"content-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/content/dashboard`},{id:"files",label:"Files",icon:"folder.fill",url:`${baseRoute}/content/files`},{id:"pages",label:"Pages",icon:"doc.fill",url:`${baseRoute}/content/pages`},{id:"posts",label:"Posts",icon:"text.bubble.fill",url:`${baseRoute}/content/posts`},{id:"categories",label:"Categories",icon:"tag.fill",url:`${baseRoute}/content/categories`},{id:"tags",label:"Tags",icon:"tag",url:`${baseRoute}/content/tags`},{id:"comments",label:"Comments",icon:"bubble.left.fill",url:`${baseRoute}/content/comments`},{id:"authors",label:"Authors",icon:"person.text.rectangle.fill",url:`${baseRoute}/content/authors`},{id:"seo",label:"SEO",icon:"magnifyingglass",url:`${baseRoute}/content/seo`},...categoryRows(baseRoute,discoveredModels,"content",new Set(["page","post","category","tag","comment","author"]))]});sections.push({id:"app",title:"App",items:[{id:"deployments",label:"Deployments",icon:"rocket.fill",url:`${baseRoute}/deployments`},{id:"requests",label:"Requests",icon:"arrow.left.arrow.right",url:`${baseRoute}/requests`},{id:"realtime",label:"Realtime",icon:"link",url:`${baseRoute}/realtime`},{id:"actions",label:"Actions",icon:"bolt.fill",url:`${baseRoute}/actions`},{id:"commands",label:"Commands",icon:"terminal.fill",url:`${baseRoute}/commands`},{id:"queue",label:"Queue",icon:"list.bullet.rectangle.fill",url:`${baseRoute}/queue`},{id:"jobs",label:"Jobs",icon:"briefcase.fill",url:`${baseRoute}/jobs`},{id:"inbox",label:"Inbox",icon:"tray.full.fill",url:`${baseRoute}/inbox`},{id:"queries",label:"Queries",icon:"magnifyingglass.circle.fill",url:`${baseRoute}/queries`},{id:"notifications",label:"Notifications",icon:"bell.fill",url:`${baseRoute}/notifications/dashboard`},...categoryRows(baseRoute,discoveredModels,"system",new Set)]});const dataItems=[];if(dataRow.dashboard)dataItems.push({id:"data-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/data/dashboard`});if(dataRow.activity)dataItems.push({id:"activity",label:"Activity",icon:"waveform.path.ecg",url:`${baseRoute}/data/activity`});if(dataRow.users)dataItems.push({id:"users",label:"Users",icon:"person.crop.circle.fill",url:`${baseRoute}/data/users`});if(dataRow.teams)dataItems.push({id:"teams",label:"Teams",icon:"person.3.fill",url:`${baseRoute}/data/teams`});if(dataRow.subscribers)dataItems.push({id:"subscribers",label:"Subscribers",icon:"envelope.fill",url:`${baseRoute}/data/subscribers`});if(dataRow.allModels)dataItems.push({id:"data-models",label:"All Models",icon:"square.grid.2x2.fill",url:`${baseRoute}/models`});const dynamicRows=discoveredModels.filter((m)=>{const eff=effectiveCategory(m);return eff==="userland"||eff==="data"}).filter((m)=>!dedicatedPages.has(m.id)).map((model)=>({id:`model-${model.id}`,label:model.dashboard?.label??model.name,icon:model.icon,url:`${baseRoute}/models/${model.id}`,...model.dashboard?.roles&&model.dashboard.roles.length>0?{roles:model.dashboard.roles}:{}}));sections.push({id:"data",title:"Data",items:[...dataItems,...dynamicRows]});if(enabled.commerce)sections.push({id:"commerce",title:"Commerce",items:[{id:"commerce-dashboard",label:"Dashboard",icon:"gauge.with.dots.needle.33percent",url:`${baseRoute}/commerce/dashboard`},{id:"customers",label:"Customers",icon:"person.crop.circle.fill",url:`${baseRoute}/commerce/customers`},{id:"orders",label:"Orders",icon:"list.clipboard.fill",url:`${baseRoute}/commerce/orders`},{id:"products",label:"Products",icon:"cube.box.fill",url:`${baseRoute}/commerce/products`},{id:"coupons",label:"Coupons",icon:"ticket.fill",url:`${baseRoute}/commerce/coupons`},{id:"gift-cards",label:"Gift Cards",icon:"giftcard.fill",url:`${baseRoute}/commerce/gift-cards`},{id:"payments",label:"Payments",icon:"creditcard.fill",url:`${baseRoute}/commerce/payments`},{id:"delivery",label:"Delivery",icon:"truck.box.fill",url:`${baseRoute}/commerce/delivery`},{id:"taxes",label:"Taxes",icon:"percent",url:`${baseRoute}/commerce/taxes`},...categoryRows(baseRoute,discoveredModels,"commerce",new Set(["customer","order","product","coupon","gift-card","payment","review","category"]))]});if(enabled.marketing)sections.push({id:"marketing",title:"Marketing",items:[{id:"lists",label:"Lists",icon:"list.bullet",url:`${baseRoute}/marketing/lists`},{id:"social-posts",label:"Social Posts",icon:"clock.fill",url:`${baseRoute}/marketing/social-posts`},{id:"campaigns",label:"Campaigns",icon:"megaphone.fill",url:`${baseRoute}/marketing/campaigns`},{id:"marketing-reviews",label:"Reviews",icon:"star.fill",url:`${baseRoute}/marketing/reviews`},...categoryRows(baseRoute,discoveredModels,"marketing",new Set(["campaign"]))]});if(enabled.analytics)sections.push({id:"analytics",title:"Analytics",items:[{id:"analytics-web",label:"Web",icon:"globe",url:`${baseRoute}/analytics/web`},{id:"analytics-blog",label:"Blog",icon:"doc.text.fill",url:`${baseRoute}/analytics/blog`},{id:"analytics-commerce",label:"Commerce",icon:"cart.fill",url:`${baseRoute}/analytics/commerce/web`},{id:"analytics-marketing",label:"Marketing",icon:"megaphone.fill",url:`${baseRoute}/analytics/marketing`}]});if(enabled.management)sections.push({id:"management",title:"Management",items:[{id:"cloud",label:"Cloud",icon:"cloud.fill",url:`${baseRoute}/cloud`},{id:"servers",label:"Servers",icon:"server.rack",url:`${baseRoute}/servers`},{id:"serverless",label:"Serverless",icon:"bolt.horizontal.circle.fill",url:`${baseRoute}/serverless`},{id:"dns",label:"DNS",icon:"network",url:`${baseRoute}/dns`},{id:"mailboxes",label:"Mailboxes",icon:"tray.full.fill",url:`${baseRoute}/mailboxes`},{id:"logs",label:"Logs",icon:"list.bullet.rectangle.portrait.fill",url:`${baseRoute}/logs`},{id:"kanban",label:"Kanban",icon:"rectangle.stack.fill",url:`${baseRoute}/kanban`,roles:["admin","dev"]}]});if(enabled.utilities)sections.push({id:"utilities",title:"Utilities",items:[{id:"buddy",label:"AI Buddy",icon:"bubble.left.and.bubble.right.fill",url:`${baseRoute}/buddy`},{id:"environment",label:"Environment",icon:"key.fill",url:`${baseRoute}/environment`},{id:"access-tokens",label:"Access Tokens",icon:"key.horizontal.fill",url:`${baseRoute}/access-tokens`},{id:"settings",label:"Settings",icon:"gear",url:`${baseRoute}/settings/billing`}]});return{sections,minWidth:200,maxWidth:280}}
|
package/dist/dev/dashboard.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import process from"node:process";import{randomUUID}from"node:crypto";import{existsSync,renameSync,unlinkSync,writeFileSync}from"node:fs";import{bold,cyan,dim,green}from"@stacksjs/cli";import{projectPath,publicPath,storagePath}from"@stacksjs/path";import{seedCsrfPageResponse,validateDevCsrfRequest}from"./csrf";import{shouldDelegateDashboardRequest}from"./dashboard-request-routing";import{
|
|
1
|
+
import process from"node:process";import{randomUUID}from"node:crypto";import{existsSync,renameSync,unlinkSync,writeFileSync}from"node:fs";import{bold,cyan,dim,green}from"@stacksjs/cli";import{projectPath,publicPath,storagePath}from"@stacksjs/path";import{seedCsrfPageResponse,validateDevCsrfRequest}from"./csrf";import{shouldDelegateDashboardRequest}from"./dashboard-request-routing";import{resolveDashboardCraftExecutable}from"./dashboard-native";import{buildManifest,discoverModels,findAvailablePort,waitForServer}from"./dashboard-utils";import{runDashboardSupervisor}from"./dashboard-supervisor";if(process.env.STACKS_DASHBOARD_WORKER!=="1"){const exitCode=await runDashboardSupervisor(import.meta.path,process.argv.slice(2));process.exit(exitCode)}const verboseIdx=process.argv.indexOf("--verbose"),verbose=verboseIdx!==-1&&process.argv[verboseIdx+1]!=="false",startTime=Bun.nanoseconds(),originalConsoleLog=console.log,originalConsoleWarn=console.warn,bufferedLogs=[];console.log=(...args)=>{bufferedLogs.push(args.map(String).join(" "))};console.warn=(...args)=>{bufferedLogs.push(args.map(String).join(" "))};const dashboardPath=storagePath("framework/defaults/views/dashboard"),userDashboardPath=projectPath("resources/views/dashboard"),preferredPort=Number(process.env.PORT_ADMIN)||3002,dashboardPort=await findAvailablePort(preferredPort),appUrl=process.env.APP_URL||"",hasCustomDomain=appUrl!==""&&appUrl!=="localhost"&&!appUrl.includes("localhost:"),domain=hasCustomDomain?appUrl.replace(/^https?:\/\//,""):null,dashboardDomain=domain?`dashboard.${domain}`:null,sslBasePath=`${process.env.HOME}/.stacks/ssl`;function restoreConsole(){console.log=originalConsoleLog;console.warn=(...args)=>{const msg=args.map(String).join(" ");if(msg.includes("[STX] DOM API violation")||msg.includes("unsafe expression"))return;originalConsoleWarn(...args)}}async function startStxServer(){await import("@stacksjs/orm");let serve;try{const localServe=process.env.BUN_PLUGIN_STX_SRC||`${process.env.HOME}/Code/Tools/stx/packages/bun-plugin/src/serve.ts`;if(!await Bun.file(localServe).exists())throw Error("local bun-plugin-stx source not found");serve=(await import(localServe)).serve}catch{try{serve=(await import("bun-plugin-stx/serve")).serve}catch{serve=(await import(projectPath("pantry/bun-plugin-stx/dist/serve.js"))).serve}}let stxModule;try{const localStx=process.env.STX_SRC||`${process.env.HOME}/Code/Tools/stx/packages/stx/src/index.ts`;if(await Bun.file(localStx).exists()){const localCrosswind=`${process.env.HOME}/Code/Tools/crosswind/packages/crosswind/src/index.ts`;if(!process.env.CROSSWIND_SRC&&await Bun.file(localCrosswind).exists())process.env.CROSSWIND_SRC=localCrosswind;stxModule=await import(localStx)}}catch{}if(stxModule&&verbose)console.log("[Dashboard] Using local stx checkout");if(!stxModule)try{const vendoredStx=projectPath("pantry/@stacksjs/stx/dist/index.js");if(await Bun.file(vendoredStx).exists())stxModule=await import(vendoredStx)}catch{}const{listConfigFiles,readConfig,updateConfigKeys}=await import(storagePath("framework/defaults/resources/functions/dashboard/config-io.ts")),localTsChartsRoot=process.env.TS_CHARTS_SRC||`${process.env.HOME}/Code/Libraries/ts-charts`,dependencyBundles=new Map;function resolveTsChartsSource(packageName){return[`${localTsChartsRoot}/packages/${packageName}/src/index.ts`,projectPath(`node_modules/@ts-charts/${packageName}/src/index.ts`),projectPath(`pantry/@ts-charts/${packageName}/src/index.ts`)].find(existsSync)||null}const tsChartsEntry=[`${localTsChartsRoot}/packages/ts-charts/src/index.ts`,projectPath("node_modules/ts-charts/src/index.ts"),projectPath("pantry/ts-charts/src/index.ts")].find(existsSync);async function buildDashboardDependency(entrypoint){const cached=dependencyBundles.get(entrypoint);if(cached)return cached;const build=(async()=>{const result=await Bun.build({entrypoints:[entrypoint],target:"browser",format:"esm",minify:!0,plugins:[{name:"dashboard-ts-charts-source",setup(builder){builder.onResolve({filter:/^@ts-charts\/[a-z0-9-]+$/},(args)=>{const packageName=args.path.slice(11),source=resolveTsChartsSource(packageName);if(source)return{path:source};return null})}}]});if(!result.success){const errors=result.logs.filter((log)=>log.level==="error").map((log)=>log.message).join(`
|
|
2
2
|
`);throw Error(errors||`Failed to bundle ${entrypoint}`)}const output=result.outputs.find((file)=>file.path.endsWith(".js"));if(!output)throw Error(`No JavaScript output produced for ${entrypoint}`);return output.text()})();dependencyBundles.set(entrypoint,build);return build}function browserDependencyRoute(entrypoint){return async()=>{try{return new Response(await buildDashboardDependency(entrypoint),{headers:{"content-type":"text/javascript; charset=utf-8","cache-control":"no-cache"}})}catch(error){dependencyBundles.delete(entrypoint);return new Response(`throw new Error(${JSON.stringify(error.message)})`,{status:500,headers:{"content-type":"text/javascript; charset=utf-8"}})}}}let dashboardIconCss=null;async function buildDashboardIconCss(){const{generateCrosswindCSS}=await import("@stacksjs/stx"),dirs=[dashboardPath,userDashboardPath,storagePath("framework/defaults/resources/components/Dashboard")],icons=new Set;for(const dir of dirs){if(!existsSync(dir))continue;const glob=new Bun.Glob("**/*.{stx,ts}");for await(const file of glob.scan({cwd:dir,absolute:!0})){const text=await Bun.file(file).text();for(const match of text.matchAll(/\bi-[a-z][a-z0-9]*(?:-[a-z0-9]+)+/g))icons.add(match[0])}}if(icons.size===0)return"";return generateCrosswindCSS(`<div class="${[...icons].join(" ")}"></div>`,process.cwd())}const configRoutes={"/__deps/charts.js":browserDependencyRoute(storagePath("framework/core/charts/src/index.ts")),"/__deps/ts-charts.js":browserDependencyRoute(tsChartsEntry||`${localTsChartsRoot}/packages/ts-charts/src/index.ts`),"/__deps/dashboard-icons.css":async()=>{if(!dashboardIconCss)dashboardIconCss=buildDashboardIconCss();return new Response(await dashboardIconCss,{headers:{"content-type":"text/css; charset=utf-8","cache-control":"no-cache"}})},"/api/config/list":async()=>{try{return Response.json({ok:!0,files:listConfigFiles()})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/read":async(req)=>{try{const name=new URL(req.url).searchParams.get("name")||"";if(!/^[\w-]+$/.test(name))return Response.json({ok:!1,error:"Invalid config name"},{status:400});const result=await readConfig(name);if(!result)return Response.json({ok:!1,error:"Not found"},{status:404});return Response.json({ok:!0,name,fields:result.fields})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/source":async(req)=>{try{const name=new URL(req.url).searchParams.get("name")||"";if(!/^[\w-]+$/.test(name))return Response.json({ok:!1,error:"Invalid config name"},{status:400});const result=await readConfig(name);if(!result)return Response.json({ok:!1,error:"Not found"},{status:404});return new Response(result.source,{headers:{"content-type":"text/plain; charset=utf-8"}})}catch(e){return Response.json({ok:!1,error:e?.message},{status:500})}},"/api/config/update":async(req)=>{if(req.method!=="POST")return Response.json({ok:!1,error:"Method not allowed"},{status:405});try{await validateDevCsrfRequest(req);const body=await req.json(),file=body.file?.replace(/\.ts$/,"");if(!file||!/^[\w-]+$/.test(file))return Response.json({ok:!1,error:"Invalid file"},{status:400});const updates=[];if(Array.isArray(body.updates))for(const u of body.updates){const k=u.key??u.path;if(typeof k==="string")updates.push({key:k,value:u.value})}else if(body.key)updates.push({key:body.key,value:body.value});if(updates.length===0)return Response.json({ok:!1,error:"No updates supplied"},{status:400});const invalid=updates.find((update)=>!/^[A-Za-z_$][\w$]*$/.test(update.key)||typeof update.value!=="string"&&typeof update.value!=="number"&&typeof update.value!=="boolean"||typeof update.value==="number"&&!Number.isFinite(update.value));if(invalid)return Response.json({ok:!1,error:`Invalid value for configuration key "${invalid.key}"`},{status:400});const typedUpdates=updates;try{await updateConfigKeys(file,typedUpdates)}catch(err){const error=err?.message||"Configuration values could not be updated.",results=typedUpdates.map((update)=>({key:update.key,ok:!1,error}));return Response.json({ok:!1,file,results,error},{status:422})}const results=typedUpdates.map((update)=>({key:update.key,ok:!0,newValue:update.value}));return Response.json({ok:!0,file,results})}catch(e){if(e?.status===403)return Response.json({ok:!1,error:"Forbidden",message:"CSRF token mismatch"},{status:403});return Response.json({ok:!1,error:e?.message},{status:500})}}},router=await import("@stacksjs/router"),routeRegistry=(await import(projectPath("app/Routes.ts"))).default;await router.loadRoutes(routeRegistry);const stacksRoute=router.route;serve({root:projectPath("resources"),patterns:[userDashboardPath,dashboardPath],port:dashboardPort,componentsDir:storagePath("framework/defaults/resources/components/Dashboard"),layoutsDir:dashboardPath,partialsDir:dashboardPath,publicDir:publicPath(),renderCache:!0,renderCacheVary:"source",prewarmRenderCache:4,watchDirs:[projectPath("resources/functions"),storagePath("framework/defaults/resources/functions")],quiet:!0,routes:configRoutes,onRequest:async(req)=>{const pathname=new URL(req.url).pathname;if(shouldDelegateDashboardRequest(pathname,req.method))return stacksRoute.handleRequest(req);return null},onResponse:seedCsrfPageResponse,auth:!1,...stxModule&&{stxModule}}).catch((err)=>{restoreConsole();console.error(`
|
|
3
3
|
Failed to start dashboard server: ${err?.message||err}
|
|
4
|
-
`);process.exit(1)})}async function startReverseProxy(){if(!dashboardDomain)return!1;if(process.env.STACKS_PROXY_MANAGED)return!1;try{const{startProxies}=await import("@stacksjs/rpx");await startProxies({proxies:[{from:`localhost:${dashboardPort}`,to:dashboardDomain,cleanUrls:!1}],https:{basePath:sslBasePath,validityDays:825},regenerateUntrustedCerts:!1,verbose});return!0}catch(error){if(verbose)originalConsoleLog(` ${dim(`Proxy: ${error}`)}`);return!1}}const[,discoveredModels]=await Promise.all([startStxServer(),discoverModels(projectPath("app/Models"),storagePath("framework/defaults/app/Models"))]),{loadDashboardToggles}=await import(storagePath("framework/defaults/resources/functions/dashboard/toggles.ts")),dashboardToggles=await loadDashboardToggles(projectPath("config/dashboard.ts")),manifestPath=storagePath("framework/defaults/views/dashboard/.discovered-models.json"),manifestPayload={models:buildManifest(discoveredModels),sections:dashboardToggles},manifestTempPath=`${manifestPath}.${process.pid}.${randomUUID()}.tmp`;try{writeFileSync(manifestTempPath,JSON.stringify(manifestPayload,null,2),{flag:"wx"});renameSync(manifestTempPath,manifestPath)}finally{if(existsSync(manifestTempPath))unlinkSync(manifestTempPath)}const serverReady=await waitForServer(dashboardPort);restoreConsole();let proxyStarted=Boolean(process.env.STACKS_PROXY_MANAGED);if(!proxyStarted)proxyStarted=await startReverseProxy().catch((err)=>{if(verbose)console.warn("[Dashboard] Reverse proxy failed:",err);return!1});const dashboardHttpsUrl=dashboardDomain?`https://${dashboardDomain}`:null,dashboardLocalUrl=`http://localhost:${dashboardPort}`,initialUrl=dashboardHttpsUrl&&proxyStarted?`${dashboardHttpsUrl}/`:`${dashboardLocalUrl}/`,elapsedMs=(Bun.nanoseconds()-startTime)/1e6;console.log();console.log(` ${bold(cyan("stacks dashboard"))}`);console.log();if(dashboardHttpsUrl){console.log(` ${green("\u279C")} ${bold("Local")}: ${cyan(dashboardHttpsUrl)}`);console.log(` ${dim("\u279C")} ${dim("Origin")}: ${dim(dashboardLocalUrl)}`)}else console.log(` ${green("\u279C")} ${bold("Local")}: ${cyan(dashboardLocalUrl)}`);console.log(` ${green("\u279C")} ${bold("Window")}: ${dim("Stacks Dashboard")} ${dim("1400\xD7900")}`);console.log(` ${green("\u279C")} ${bold("Models")}: ${dim(`${discoveredModels.length} discovered`)}`);if(dashboardPort!==preferredPort)console.log(` ${dim("\u279C")} ${dim(`Port ${preferredPort} in use, using ${dashboardPort}`)}`);if(!serverReady)console.log(` ${dim("\u26A0")} ${dim("Dev server may not be ready yet")}`);console.log();console.log(` ${dim(`ready in ${elapsedMs.toFixed(0)} ms`)}`);if(verbose){console.log();console.log(` ${dim("\u279C")} ${dim("URL")}: ${dim(initialUrl)}`);if(dashboardDomain){console.log(` ${dim("\u279C")} ${dim("SSL")}: ${dim(sslBasePath)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${dashboardPort} \u2192 ${dashboardDomain}`)}`)}if(bufferedLogs.length>0){console.log();for(const line of bufferedLogs)console.log(` ${dim(line)}`)}}console.log();let createApp;const
|
|
5
|
-
`);await new Promise(()=>{})}
|
|
6
|
-
`);await new Promise(()=>{})}}else{console.log(` ${dim("Native window unavailable. Set CRAFT_BIN to a craft binary, or open the URL above in a browser.")}
|
|
4
|
+
`);process.exit(1)})}async function startReverseProxy(){if(!dashboardDomain)return!1;if(process.env.STACKS_PROXY_MANAGED)return!1;try{const{startProxies}=await import("@stacksjs/rpx");await startProxies({proxies:[{from:`localhost:${dashboardPort}`,to:dashboardDomain,cleanUrls:!1}],https:{basePath:sslBasePath,validityDays:825},regenerateUntrustedCerts:!1,verbose});return!0}catch(error){if(verbose)originalConsoleLog(` ${dim(`Proxy: ${error}`)}`);return!1}}const[,discoveredModels]=await Promise.all([startStxServer(),discoverModels(projectPath("app/Models"),storagePath("framework/defaults/app/Models"))]),{loadDashboardToggles}=await import(storagePath("framework/defaults/resources/functions/dashboard/toggles.ts")),dashboardToggles=await loadDashboardToggles(projectPath("config/dashboard.ts")),manifestPath=storagePath("framework/defaults/views/dashboard/.discovered-models.json"),manifestPayload={models:buildManifest(discoveredModels),sections:dashboardToggles},manifestTempPath=`${manifestPath}.${process.pid}.${randomUUID()}.tmp`;try{writeFileSync(manifestTempPath,JSON.stringify(manifestPayload,null,2),{flag:"wx"});renameSync(manifestTempPath,manifestPath)}finally{if(existsSync(manifestTempPath))unlinkSync(manifestTempPath)}const serverReady=await waitForServer(dashboardPort);restoreConsole();let proxyStarted=Boolean(process.env.STACKS_PROXY_MANAGED);if(!proxyStarted)proxyStarted=await startReverseProxy().catch((err)=>{if(verbose)console.warn("[Dashboard] Reverse proxy failed:",err);return!1});const dashboardHttpsUrl=dashboardDomain?`https://${dashboardDomain}`:null,dashboardLocalUrl=`http://localhost:${dashboardPort}`,initialUrl=dashboardHttpsUrl&&proxyStarted?`${dashboardHttpsUrl}/`:`${dashboardLocalUrl}/`,elapsedMs=(Bun.nanoseconds()-startTime)/1e6;console.log();console.log(` ${bold(cyan("stacks dashboard"))}`);console.log();if(dashboardHttpsUrl){console.log(` ${green("\u279C")} ${bold("Local")}: ${cyan(dashboardHttpsUrl)}`);console.log(` ${dim("\u279C")} ${dim("Origin")}: ${dim(dashboardLocalUrl)}`)}else console.log(` ${green("\u279C")} ${bold("Local")}: ${cyan(dashboardLocalUrl)}`);console.log(` ${green("\u279C")} ${bold("Window")}: ${dim("Stacks Dashboard")} ${dim("1400\xD7900")}`);console.log(` ${green("\u279C")} ${bold("Models")}: ${dim(`${discoveredModels.length} discovered`)}`);if(dashboardPort!==preferredPort)console.log(` ${dim("\u279C")} ${dim(`Port ${preferredPort} in use, using ${dashboardPort}`)}`);if(!serverReady)console.log(` ${dim("\u26A0")} ${dim("Dev server may not be ready yet")}`);console.log();console.log(` ${dim(`ready in ${elapsedMs.toFixed(0)} ms`)}`);if(verbose){console.log();console.log(` ${dim("\u279C")} ${dim("URL")}: ${dim(initialUrl)}`);if(dashboardDomain){console.log(` ${dim("\u279C")} ${dim("SSL")}: ${dim(sslBasePath)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${dashboardPort} \u2192 ${dashboardDomain}`)}`)}if(bufferedLogs.length>0){console.log();for(const line of bufferedLogs)console.log(` ${dim(line)}`)}}console.log();let createApp,resolveCraftBinary;const nativeDisabled=process.env.STACKS_NO_NATIVE==="1",explicitCraftSdk=process.env.CRAFT_SDK_SRC;if(!nativeDisabled&&explicitCraftSdk){if(!existsSync(explicitCraftSdk))throw Error(`CRAFT_SDK_SRC not found: ${explicitCraftSdk}`);try{({createApp,resolveCraftBinary}=await import(explicitCraftSdk));if(!createApp||!resolveCraftBinary)throw Error("CRAFT_SDK_SRC must export createApp and resolveCraftBinary")}catch(error){throw Error(`Could not load CRAFT_SDK_SRC ${explicitCraftSdk}: ${error instanceof Error?error.message:String(error)}`)}}if(!nativeDisabled&&!explicitCraftSdk)try{({createApp,resolveCraftBinary}=await import("craft-native"))}catch{}const craftBinaryPath=createApp&&resolveCraftBinary?resolveDashboardCraftExecutable(resolveCraftBinary):void 0;if(createApp&&craftBinaryPath){const userIconPath=projectPath("resources/assets/images/app-icon.png"),defaultIconPath=storagePath("framework/defaults/resources/assets/images/app-icon.png"),appIconPath=await Bun.file(userIconPath).exists()?userIconPath:await Bun.file(defaultIconPath).exists()?defaultIconPath:void 0,app=createApp({url:dashboardLocalUrl,quiet:!verbose,craftPath:craftBinaryPath,window:{title:"Stacks Dashboard",width:1400,height:900,titlebarHidden:!0,webSidebarMaterial:!0,webSidebarWidth:250,...appIconPath&&{icon:appIconPath}}});process.on("SIGINT",()=>{app.close();process.exit(0)});process.on("SIGTERM",()=>{app.close();process.exit(0)});try{await app.show();process.exit(0)}catch(error){const fallbackUrl=dashboardHttpsUrl||dashboardLocalUrl;console.log(` ${dim("Native Craft window failed:")} ${dim(error instanceof Error?error.message:String(error))}`);console.log(` ${dim("Dashboard available at:")} ${cyan(fallbackUrl)}
|
|
5
|
+
`);await new Promise(()=>{})}}else{console.log(` ${dim(nativeDisabled?"Native window disabled by STACKS_NO_NATIVE=1. Open the URL above in a browser.":"Native window unavailable. Run pantry install craft, set CRAFT_BIN, or open the URL above in a browser.")}
|
|
7
6
|
`);process.on("SIGINT",()=>process.exit(0));process.on("SIGTERM",()=>process.exit(0));await new Promise(()=>{})}
|
package/dist/serve/api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync}from"node:fs";import{dirname,join}from"node:path";import process from"node:process";import{config,overridesReady}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{frameworkPath}from"@stacksjs/path";import{route}from"@stacksjs/router";import{resolveApiHost}from"../helpers/api-host";function resolveDefaultsFile(rel){const vendored=frameworkPath(`defaults/${rel}`);if(existsSync(vendored))return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return join(dirname(pkgJson),rel)}catch{return vendored}}await overridesReady;const port=Number(process.env.PORT)||config.ports?.api||3008,hostname=resolveApiHost();log.info("[Stacks API] Starting server...");log.info(`[Stacks API] Environment: ${process.env.APP_ENV||"development"}`);const corsMod=await import(resolveDefaultsFile("app/Middleware/Cors.ts")),corsMiddleware=corsMod.default;route.use(corsMiddleware.toRouterHandler());await route.importRoutes();const isDeployed=["production","staging","development"].includes((process.env.APP_ENV||"").toLowerCase()),server=await route.serve({port,hostname,reusePort:isDeployed});process.on("SIGTERM",()=>{const graceMs=Number(process.env.SHUTDOWN_GRACE_MS)||15000;setTimeout(()=>process.exit(0),graceMs).unref();Promise.resolve(server?.stop()).then(()=>process.exit(0))});log.info(`[Stacks API] Server running at http://${hostname}:${port}`);
|
|
1
|
+
import{existsSync}from"node:fs";import{dirname,join}from"node:path";import process from"node:process";import{config,overridesReady}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{frameworkPath}from"@stacksjs/path";import{disableViewRouting,route}from"@stacksjs/router";import{resolveApiHost}from"../helpers/api-host";function resolveDefaultsFile(rel){const vendored=frameworkPath(`defaults/${rel}`);if(existsSync(vendored))return vendored;try{const pkgJson=Bun.resolveSync("@stacksjs/defaults/package.json",process.cwd());return join(dirname(pkgJson),rel)}catch{return vendored}}await overridesReady;const port=Number(process.env.PORT)||config.ports?.api||3008,hostname=resolveApiHost();log.info("[Stacks API] Starting server...");log.info(`[Stacks API] Environment: ${process.env.APP_ENV||"development"}`);const corsMod=await import(resolveDefaultsFile("app/Middleware/Cors.ts")),corsMiddleware=corsMod.default;route.use(corsMiddleware.toRouterHandler());await route.importRoutes();disableViewRouting(route.bunRouter);const isDeployed=["production","staging","development"].includes((process.env.APP_ENV||"").toLowerCase()),server=await route.serve({port,hostname,reusePort:isDeployed});process.on("SIGTERM",()=>{const graceMs=Number(process.env.SHUTDOWN_GRACE_MS)||15000;setTimeout(()=>process.exit(0),graceMs).unref();Promise.resolve(server?.stop()).then(()=>process.exit(0))});log.info(`[Stacks API] Server running at http://${hostname}:${port}`);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/actions",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.367",
|
|
6
6
|
"description": "The Stacks actions.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -59,33 +59,34 @@
|
|
|
59
59
|
"prepublishOnly": "bun run build"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@stacksjs/config": "0.70.
|
|
62
|
+
"@stacksjs/config": "0.70.367",
|
|
63
63
|
"@stacksjs/bumpx": "^0.2.6",
|
|
64
64
|
"@stacksjs/bunpress": "^0.1.18",
|
|
65
65
|
"@stacksjs/logsmith": "^0.2.3",
|
|
66
|
-
"@stacksjs/registry": "0.70.
|
|
66
|
+
"@stacksjs/registry": "0.70.367",
|
|
67
67
|
"@stacksjs/stx": "^0.2.176",
|
|
68
68
|
"@stacksjs/ts-cloud": "^0.7.103",
|
|
69
|
-
"@stacksjs/ts-md": "^0.1.1"
|
|
69
|
+
"@stacksjs/ts-md": "^0.1.1",
|
|
70
|
+
"craft-native": ">=0.0.55"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
|
-
"@stacksjs/api": "0.70.
|
|
73
|
-
"@stacksjs/cli": "0.70.
|
|
74
|
-
"@stacksjs/database": "0.70.
|
|
73
|
+
"@stacksjs/api": "0.70.367",
|
|
74
|
+
"@stacksjs/cli": "0.70.367",
|
|
75
|
+
"@stacksjs/database": "0.70.367",
|
|
75
76
|
"@stacksjs/tlsx": "^0.13.2",
|
|
76
77
|
"better-dx": "^0.2.17",
|
|
77
|
-
"@stacksjs/dns": "0.70.
|
|
78
|
-
"@stacksjs/enums": "0.70.
|
|
79
|
-
"@stacksjs/env": "0.70.
|
|
80
|
-
"@stacksjs/error-handling": "0.70.
|
|
81
|
-
"@stacksjs/image": "0.70.
|
|
82
|
-
"@stacksjs/logging": "0.70.
|
|
83
|
-
"@stacksjs/path": "0.70.
|
|
84
|
-
"@stacksjs/security": "0.70.
|
|
85
|
-
"@stacksjs/storage": "0.70.
|
|
86
|
-
"@stacksjs/strings": "0.70.
|
|
87
|
-
"@stacksjs/utils": "0.70.
|
|
88
|
-
"@stacksjs/validation": "0.70.
|
|
78
|
+
"@stacksjs/dns": "0.70.367",
|
|
79
|
+
"@stacksjs/enums": "0.70.367",
|
|
80
|
+
"@stacksjs/env": "0.70.367",
|
|
81
|
+
"@stacksjs/error-handling": "0.70.367",
|
|
82
|
+
"@stacksjs/image": "0.70.367",
|
|
83
|
+
"@stacksjs/logging": "0.70.367",
|
|
84
|
+
"@stacksjs/path": "0.70.367",
|
|
85
|
+
"@stacksjs/security": "0.70.367",
|
|
86
|
+
"@stacksjs/storage": "0.70.367",
|
|
87
|
+
"@stacksjs/strings": "0.70.367",
|
|
88
|
+
"@stacksjs/utils": "0.70.367",
|
|
89
|
+
"@stacksjs/validation": "0.70.367"
|
|
89
90
|
},
|
|
90
91
|
"peerDependencies": {
|
|
91
92
|
"pickier": "^0.1.35"
|