@stacksjs/buddy 0.75.390 → 0.75.397

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.
@@ -1 +1 @@
1
- import process from"node:process";import{intro,log,multiselect,onUnknownSubcommand,outro}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{hasTTY,isCI}from"@stacksjs/env";import{ExitCode}from"@stacksjs/types";import{resultFailed}from"../result";let _runAction;async function runAction(...args){if(!_runAction)_runAction=(await import("@stacksjs/actions")).runAction;return _runAction(...args)}export function build(buddy){const descriptions={build:"Build any of your libraries (packages) for production use",components:"Build your component library",webComponents:"Build your framework agnostic web component library",elements:"An alias to the -w flag",buddy:"Build the Buddy binary",functions:"Build your function library",libs:"Build every package configured in config/library.ts",desktop:"Build the Desktop Application",mobile:"Build the native iOS and Android applications",android:"Build the native Android application",ios:"Build the native iOS application",dmg:"Package the desktop build as a macOS .app inside a .dmg",pages:"Build your frontend",docs:"Build your documentation",framework:"Build Stacks framework",cli:"Automagically build the CLI",server:"Build the Stacks cloud server (Docker image)",frontendStatic:"Build the prerendered marketing/public static site (storage/framework/frontend-dist)",select:"What are you trying to build?",project:"Target a specific project",verbose:"Enable verbose output"};buddy.command("build [type]",descriptions.build).option("-c, --components",descriptions.components).option("-w, --web-components",descriptions.webComponents).option("-e, --elements",descriptions.elements).option("-f, --functions",descriptions.functions).option("-l, --libs",descriptions.libs).option("-k, --desktop",descriptions.desktop).option("-m, --mobile",descriptions.mobile).option("--android",descriptions.android).option("--ios",descriptions.ios).option("-p, --views",descriptions.pages).option("--pages",descriptions.pages).option("-d, --docs",descriptions.docs).option("-b, --buddy",descriptions.buddy,{default:!1}).option("-s, --stacks",descriptions.framework,{default:!1}).option("--project [project]",descriptions.project,{default:!1}).option("--server",descriptions.server,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(server,options)=>{log.debug("Running `buddy build` ...",options);applyBuildTarget(server,options);if(options.mobile){options.android=!0;options.ios=!0}if(hasNoOptions(options)){if(!isCI&&hasTTY&&process.stdin.isTTY){const answers=await multiselect({message:descriptions.select,choices:[{label:"Frontend (views)",value:"views"},{label:"Components",value:"components"},{label:"Web Components",value:"webComponents"},{label:"Functions",value:"functions"},{label:"All library packages",value:"libs"},{label:"Desktop application",value:"desktop"},{label:"Mobile applications (iOS + Android)",value:"mobile"},{label:"Android application",value:"android"},{label:"iOS application",value:"ios"},{label:"Documentation",value:"docs"},{label:"Stacks framework",value:"stacks"},{label:"Buddy CLI",value:"buddy"},{label:"Server (Docker image)",value:"server"}]}),selected=new Set(answers);if(selected.has("views"))options.views=!0;if(selected.has("components"))options.components=!0;if(selected.has("webComponents"))options.webComponents=!0;if(selected.has("functions"))options.functions=!0;if(selected.has("libs"))options.libs=!0;if(selected.has("desktop"))options.desktop=!0;if(selected.has("mobile")){options.mobile=!0;options.android=!0;options.ios=!0}if(selected.has("android"))options.android=!0;if(selected.has("ios"))options.ios=!0;if(selected.has("docs"))options.docs=!0;if(selected.has("stacks"))options.stacks=!0;if(selected.has("buddy"))options.buddy=!0;if(selected.has("server"))options.server=!0}if(hasNoOptions(options)){options.views=!0;log.info("No build target specified, defaulting to the frontend (views). See `buddy build --help` for all targets.")}}let succeeded=!0;if(options.docs)succeeded=await runBuildAction(Action.BuildDocs,"documentation",options)&&succeeded;if(options.components)succeeded=await runBuildAction(Action.BuildComponentLibs,"component libraries",options)&&succeeded;if(options.webComponents)succeeded=await runBuildAction(Action.BuildWebComponentLib,"web component library",options)&&succeeded;if(options.functions)succeeded=await runBuildAction(Action.BuildFunctionLib,"function library",options)&&succeeded;if(options.libs)succeeded=await runBuildAction(Action.BuildLibs,"library packages",options)&&succeeded;if(options.desktop)succeeded=await runBuildAction(Action.BuildDesktop,"desktop application",options)&&succeeded;if(options.android)succeeded=await runBuildAction(Action.BuildAndroid,"Android application",options)&&succeeded;if(options.ios)succeeded=await runBuildAction(Action.BuildIos,"iOS application",options)&&succeeded;if(options.views)succeeded=await runBuildAction(Action.BuildViews,"frontend",options)&&succeeded;if(options.stacks)succeeded=await runBuildAction(Action.BuildStacks,"Stacks framework",options)&&succeeded;if(options.buddy)succeeded=await runBuildAction(Action.BuildCli,"Buddy CLI",options)&&succeeded;if(options.server)succeeded=await runBuildAction(Action.BuildServer,"server",options)&&succeeded;if(!succeeded)process.exit(ExitCode.FatalError);process.exit(ExitCode.Success)});buddy.command("build:components","Automagically build component libraries for production use & npm/CDN distribution").alias("prod:components").option("-c, --components",descriptions.components,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:components` ...",options);if(!await runBuildAction(Action.BuildComponentLibs,"component libraries",options))process.exit(ExitCode.FatalError)});buddy.command("build:cli",descriptions.cli).alias("prod:cli").option("-b, --buddy",descriptions.buddy,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:cli` ...",options);await runAction(Action.BuildCli,options)});buddy.command("build:server",descriptions.server).alias("prod:server").alias("build:docker").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:server` ...",options);await runAction(Action.BuildServer,options)});buddy.command("build:functions","Automagically build function library for npm/CDN distribution").option("-f, --functions",descriptions.functions,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:functions` ...",options);if(!await runBuildAction(Action.BuildFunctionLib,"function library",options))process.exit(ExitCode.FatalError)});buddy.command("build:libs",descriptions.libs).alias("build:libraries").alias("prod:libs").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:libs` ...",options);if(!await runBuildAction(Action.BuildLibs,"library packages",options))process.exit(ExitCode.FatalError)});buddy.command("build:web-components","Automagically build Web Component library for npm/CDN distribution").alias("build:wc").alias("prod:web-components").alias("prod:wc").option("-w, --web-components",descriptions.webComponents,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:web-components` ...",options);if(!await runBuildAction(Action.BuildWebComponentLib,"web component library",options))process.exit(ExitCode.FatalError)});buddy.command("build:frontend",descriptions.pages).alias("build:pages").alias("build:views").alias("prod:frontend").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:frontend` ...",options);await runAction(Action.BuildViews,options)});buddy.command("build:docs","Automagically build your documentation site.").alias("prod:docs").alias("build:documentation").alias("prod:documentation").option("-d, --docs",descriptions.docs,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:docs` ...",options);await runAction(Action.BuildDocs,options)});buddy.command("build:frontend-static",descriptions.frontendStatic).alias("build:public").alias("prod:frontend-static").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:frontend-static` ...",options);await runAction(Action.BuildFrontendStatic,options)});buddy.command("build:core","Automagically build the Stacks core.").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:core` ...",options);const startTime=await intro("buddy build:core"),result=await runAction(Action.BuildCore,options);if(resultFailed(result)){await log.error("Failed to build the Stacks core.",result.error);process.exit(ExitCode.FatalError)}await outro("Core packages built successfully",{startTime,useSeconds:!0})});buddy.command("build:desktop",descriptions.desktop).alias("prod:desktop").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:desktop` ...",options);const perf=await intro("buddy build:desktop"),result=await runAction(Action.BuildDesktop,options);if(resultFailed(result)){await outro("While running the build:desktop command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("build:mobile",descriptions.mobile).alias("prod:mobile").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:mobile` ...",options);const perf=await intro("buddy build:mobile"),androidSucceeded=await runBuildAction(Action.BuildAndroid,"Android application",options),iosSucceeded=await runBuildAction(Action.BuildIos,"iOS application",options);if(!androidSucceeded||!iosSucceeded){await outro("One or more mobile application builds failed",{startTime:perf,useSeconds:!0,type:"error"});process.exit(ExitCode.FatalError)}await outro("iOS and Android applications built",{startTime:perf,useSeconds:!0})});buddy.command("build:android",descriptions.android).alias("prod:android").option("-p, --project [project]",descriptions.project,{default:!1}).option("--bundle","Build a signed Android App Bundle (.aab) for Google Play (CRAFT_ANDROID_KEYSTORE and friends)",{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:android` ...",options);if(options.bundle)process.env.STACKS_ANDROID_BUNDLE="1";const perf=await intro("buddy build:android"),result=await runAction(Action.BuildAndroid,options);if(resultFailed(result)){await outro("While building the Android application, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}await outro("Android application built",{startTime:perf,useSeconds:!0})});buddy.command("build:ios",descriptions.ios).alias("prod:ios").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:ios` ...",options);const perf=await intro("buddy build:ios"),result=await runAction(Action.BuildIos,options);if(resultFailed(result)){await outro("While building the iOS application, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}await outro("iOS application built",{startTime:perf,useSeconds:!0})});buddy.command("build:dmg",descriptions.dmg).option("--app-name <name>","Bundle display name (or DESKTOP_APP_NAME / APP_NAME)").option("--app-version <version>","Bundle version (or DESKTOP_APP_VERSION; defaults to the project's package.json)").option("--bundle-id <id>","Reverse-DNS bundle identifier (or DESKTOP_BUNDLE_ID)").option("--signing-identity <identity>","Developer ID identity to sign with (or DESKTOP_SIGNING_IDENTITY)").option("--notary-profile <profile>","notarytool keychain profile to notarize and staple with (or DESKTOP_NOTARY_PROFILE)").option("--volume-name <name>","Name the mounted image shows (or DESKTOP_VOLUME_NAME)").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:dmg` ...",options);const perf=await intro("buddy build:dmg"),result=await runAction(Action.BuildDmg,options);if(resultFailed(result)){await outro("While running the build:dmg command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("build:stacks","Build the Stacks framework.").option("-s, --stacks",descriptions.framework,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:stacks` ...",options);const startTime=await intro("buddy build:stacks"),result=await runAction(Action.BuildStacks,options);if(resultFailed(result)){await log.error("Failed to build Stacks.",result.error);process.exit(ExitCode.FatalError)}await outro("Stacks built successfully",{startTime,useSeconds:!0})});onUnknownSubcommand(buddy,"build")}function hasNoOptions(options){return!options.components&&!options.webComponents&&!options.elements&&!options.functions&&!options.libs&&!options.desktop&&!options.mobile&&!options.android&&!options.ios&&!options.views&&!options.docs&&!options.stacks&&!options.buddy&&!options.server}export function applyBuildTarget(target,options){switch(target){case"components":options.components=!0;break;case"web-components":options.webComponents=!0;break;case"functions":options.functions=!0;break;case"libs":case"libraries":options.libs=!0;break;case"desktop":options.desktop=!0;break;case"mobile":options.mobile=!0;options.android=!0;options.ios=!0;break;case"android":options.android=!0;break;case"ios":options.ios=!0;break;case"views":options.views=!0;break;case"docs":options.docs=!0;break;case"buddy":case"cli":options.buddy=!0;break;case"stacks":options.stacks=!0;break;case"server":options.server=!0;break}}async function runBuildAction(action,target,options){const result=await runAction(action,options);if(resultFailed(result)){log.error(`Failed to build ${target}.`,result.error);return!1}return!0}
1
+ import process from"node:process";import{intro,log,multiselect,onUnknownSubcommand,outro}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{hasTTY,isCI}from"@stacksjs/env";import{projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{resultFailed}from"../result";let _runAction;async function runAction(...args){if(!_runAction)_runAction=(await import("@stacksjs/actions")).runAction;return _runAction(...args)}export function build(buddy){const descriptions={build:"Build any of your libraries (packages) for production use",components:"Build your component library",webComponents:"Build your framework agnostic web component library",elements:"An alias to the -w flag",buddy:"Build the Buddy binary",functions:"Build your function library",libs:"Build every package configured in config/library.ts",desktop:"Build the Desktop Application",mobile:"Build the native iOS and Android applications",android:"Build the native Android application",ios:"Build the native iOS application",dmg:"Package the desktop build as a macOS .app inside a .dmg",pages:"Build your frontend",docs:"Build your documentation",framework:"Build Stacks framework",cli:"Automagically build the CLI",server:"Build the Stacks cloud server (Docker image)",frontendStatic:"Build the prerendered marketing/public static site (storage/framework/frontend-dist)",select:"What are you trying to build?",project:"Target a specific project",verbose:"Enable verbose output"};buddy.command("build [type]",descriptions.build).option("-c, --components",descriptions.components).option("-w, --web-components",descriptions.webComponents).option("-e, --elements",descriptions.elements).option("-f, --functions",descriptions.functions).option("-l, --libs",descriptions.libs).option("-k, --desktop",descriptions.desktop).option("-m, --mobile",descriptions.mobile).option("--android",descriptions.android).option("--ios",descriptions.ios).option("-p, --views",descriptions.pages).option("--pages",descriptions.pages).option("-d, --docs",descriptions.docs).option("-b, --buddy",descriptions.buddy,{default:!1}).option("-s, --stacks",descriptions.framework,{default:!1}).option("--project [project]",descriptions.project,{default:!1}).option("--server",descriptions.server,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(server,options)=>{log.debug("Running `buddy build` ...",options);applyBuildTarget(server,options);if(options.mobile){options.android=!0;options.ios=!0}if(hasNoOptions(options)){if(!isCI&&hasTTY&&process.stdin.isTTY){const answers=await multiselect({message:descriptions.select,choices:[{label:"Frontend (views)",value:"views"},{label:"Components",value:"components"},{label:"Web Components",value:"webComponents"},{label:"Functions",value:"functions"},{label:"All library packages",value:"libs"},{label:"Desktop application",value:"desktop"},{label:"Mobile applications (iOS + Android)",value:"mobile"},{label:"Android application",value:"android"},{label:"iOS application",value:"ios"},{label:"Documentation",value:"docs"},{label:"Stacks framework",value:"stacks"},{label:"Buddy CLI",value:"buddy"},{label:"Server (Docker image)",value:"server"}]}),selected=new Set(answers);if(selected.has("views"))options.views=!0;if(selected.has("components"))options.components=!0;if(selected.has("webComponents"))options.webComponents=!0;if(selected.has("functions"))options.functions=!0;if(selected.has("libs"))options.libs=!0;if(selected.has("desktop"))options.desktop=!0;if(selected.has("mobile")){options.mobile=!0;options.android=!0;options.ios=!0}if(selected.has("android"))options.android=!0;if(selected.has("ios"))options.ios=!0;if(selected.has("docs"))options.docs=!0;if(selected.has("stacks"))options.stacks=!0;if(selected.has("buddy"))options.buddy=!0;if(selected.has("server"))options.server=!0}if(hasNoOptions(options)){options.views=!0;log.info("No build target specified, defaulting to the frontend (views). See `buddy build --help` for all targets.")}}let succeeded=!0;if(options.docs)succeeded=await runBuildAction(Action.BuildDocs,"documentation",options)&&succeeded;if(options.components)succeeded=await runBuildAction(Action.BuildComponentLibs,"component libraries",options)&&succeeded;if(options.webComponents)succeeded=await runBuildAction(Action.BuildWebComponentLib,"web component library",options)&&succeeded;if(options.functions)succeeded=await runBuildAction(Action.BuildFunctionLib,"function library",options)&&succeeded;if(options.libs)succeeded=await runBuildAction(Action.BuildLibs,"library packages",options)&&succeeded;if(options.desktop)succeeded=await runBuildAction(Action.BuildDesktop,"desktop application",options)&&succeeded;if(options.android)succeeded=await runBuildAction(Action.BuildAndroid,"Android application",options)&&succeeded;if(options.ios)succeeded=await runBuildAction(Action.BuildIos,"iOS application",options)&&succeeded;if(options.views)succeeded=await runBuildAction(Action.BuildViews,"frontend",options)&&succeeded;if(options.stacks)succeeded=await runBuildAction(Action.BuildStacks,"Stacks framework",options)&&succeeded;if(options.buddy)succeeded=await runBuildAction(Action.BuildCli,"Buddy CLI",options)&&succeeded;if(options.server)succeeded=await runBuildAction(Action.BuildServer,"server",options)&&succeeded;if(!succeeded)process.exit(ExitCode.FatalError);process.exit(ExitCode.Success)});buddy.command("build:components","Automagically build component libraries for production use & npm/CDN distribution").alias("prod:components").option("-c, --components",descriptions.components,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:components` ...",options);if(!await runBuildAction(Action.BuildComponentLibs,"component libraries",options))process.exit(ExitCode.FatalError)});buddy.command("build:cli",descriptions.cli).alias("prod:cli").option("-b, --buddy",descriptions.buddy,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:cli` ...",options);await runAction(Action.BuildCli,options)});buddy.command("build:server",descriptions.server).alias("prod:server").alias("build:docker").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:server` ...",options);await runAction(Action.BuildServer,options)});buddy.command("build:functions","Automagically build function library for npm/CDN distribution").option("-f, --functions",descriptions.functions,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:functions` ...",options);if(!await runBuildAction(Action.BuildFunctionLib,"function library",options))process.exit(ExitCode.FatalError)});buddy.command("build:libs",descriptions.libs).alias("build:libraries").alias("prod:libs").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:libs` ...",options);if(!await runBuildAction(Action.BuildLibs,"library packages",options))process.exit(ExitCode.FatalError)});buddy.command("build:web-components","Automagically build Web Component library for npm/CDN distribution").alias("build:wc").alias("prod:web-components").alias("prod:wc").option("-w, --web-components",descriptions.webComponents,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:web-components` ...",options);if(!await runBuildAction(Action.BuildWebComponentLib,"web component library",options))process.exit(ExitCode.FatalError)});buddy.command("build:frontend",descriptions.pages).alias("build:pages").alias("build:views").alias("prod:frontend").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:frontend` ...",options);await runAction(Action.BuildViews,options)});buddy.command("build:docs","Automagically build your documentation site.").alias("prod:docs").alias("build:documentation").alias("prod:documentation").option("-d, --docs",descriptions.docs,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:docs` ...",options);await runAction(Action.BuildDocs,options)});buddy.command("build:frontend-static",descriptions.frontendStatic).alias("build:public").alias("prod:frontend-static").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:frontend-static` ...",options);await runAction(Action.BuildFrontendStatic,options)});buddy.command("build:core","Automagically build the Stacks core.").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:core` ...",options);const startTime=await intro("buddy build:core"),result=await runAction(Action.BuildCore,options);if(resultFailed(result)){await log.error("Failed to build the Stacks core.",result.error);process.exit(ExitCode.FatalError)}await outro("Core packages built successfully",{startTime,useSeconds:!0})});buddy.command("build:desktop",descriptions.desktop).alias("prod:desktop").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:desktop` ...",options);const perf=await intro("buddy build:desktop"),{hasMacosDesktop}=await import("@stacksjs/desktop-build"),result=await runAction(await hasMacosDesktop(projectPath())?Action.BuildDesktopMacos:Action.BuildDesktop,options);if(resultFailed(result)){await outro("While running the build:desktop command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("build:mobile",descriptions.mobile).alias("prod:mobile").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:mobile` ...",options);const perf=await intro("buddy build:mobile"),androidSucceeded=await runBuildAction(Action.BuildAndroid,"Android application",options),iosSucceeded=await runBuildAction(Action.BuildIos,"iOS application",options);if(!androidSucceeded||!iosSucceeded){await outro("One or more mobile application builds failed",{startTime:perf,useSeconds:!0,type:"error"});process.exit(ExitCode.FatalError)}await outro("iOS and Android applications built",{startTime:perf,useSeconds:!0})});buddy.command("build:android",descriptions.android).alias("prod:android").option("-p, --project [project]",descriptions.project,{default:!1}).option("--bundle","Build a signed Android App Bundle (.aab) for Google Play (CRAFT_ANDROID_KEYSTORE and friends)",{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:android` ...",options);if(options.bundle)process.env.STACKS_ANDROID_BUNDLE="1";const perf=await intro("buddy build:android"),result=await runAction(Action.BuildAndroid,options);if(resultFailed(result)){await outro("While building the Android application, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}await outro("Android application built",{startTime:perf,useSeconds:!0})});buddy.command("build:ios",descriptions.ios).alias("prod:ios").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:ios` ...",options);const perf=await intro("buddy build:ios"),result=await runAction(Action.BuildIos,options);if(resultFailed(result)){await outro("While building the iOS application, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}await outro("iOS application built",{startTime:perf,useSeconds:!0})});buddy.command("build:dmg",descriptions.dmg).option("--app-name <name>","Bundle display name (or DESKTOP_APP_NAME / APP_NAME)").option("--app-version <version>","Bundle version (or DESKTOP_APP_VERSION; defaults to the project's package.json)").option("--bundle-id <id>","Reverse-DNS bundle identifier (or DESKTOP_BUNDLE_ID)").option("--signing-identity <identity>","Developer ID identity to sign with (or DESKTOP_SIGNING_IDENTITY)").option("--notary-profile <profile>","notarytool keychain profile to notarize and staple with (or DESKTOP_NOTARY_PROFILE)").option("--volume-name <name>","Name the mounted image shows (or DESKTOP_VOLUME_NAME)").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:dmg` ...",options);const perf=await intro("buddy build:dmg"),result=await runAction(Action.BuildDmg,options);if(resultFailed(result)){await outro("While running the build:dmg command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("build:stacks","Build the Stacks framework.").option("-s, --stacks",descriptions.framework,{default:!0}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy build:stacks` ...",options);const startTime=await intro("buddy build:stacks"),result=await runAction(Action.BuildStacks,options);if(resultFailed(result)){await log.error("Failed to build Stacks.",result.error);process.exit(ExitCode.FatalError)}await outro("Stacks built successfully",{startTime,useSeconds:!0})});onUnknownSubcommand(buddy,"build")}function hasNoOptions(options){return!options.components&&!options.webComponents&&!options.elements&&!options.functions&&!options.libs&&!options.desktop&&!options.mobile&&!options.android&&!options.ios&&!options.views&&!options.docs&&!options.stacks&&!options.buddy&&!options.server}export function applyBuildTarget(target,options){switch(target){case"components":options.components=!0;break;case"web-components":options.webComponents=!0;break;case"functions":options.functions=!0;break;case"libs":case"libraries":options.libs=!0;break;case"desktop":options.desktop=!0;break;case"mobile":options.mobile=!0;options.android=!0;options.ios=!0;break;case"android":options.android=!0;break;case"ios":options.ios=!0;break;case"views":options.views=!0;break;case"docs":options.docs=!0;break;case"buddy":case"cli":options.buddy=!0;break;case"stacks":options.stacks=!0;break;case"server":options.server=!0;break}}async function runBuildAction(action,target,options){const result=await runAction(action,options);if(resultFailed(result)){log.error(`Failed to build ${target}.`,result.error);return!1}return!0}
@@ -1,6 +1,16 @@
1
1
  import { fetchPublishedVersions } from '../registry';
2
2
  import type { CLI } from '@stacksjs/types';
3
3
  export declare function create(buddy: CLI): void;
4
+ /**
5
+ * An empty directory, or one holding nothing but `.git`, is a valid target:
6
+ * creating the repository on GitHub first and cloning it before scaffolding is
7
+ * the common way to start a project, and refusing that forced people to
8
+ * scaffold under a throwaway name and shuffle files (and `.git`) by hand.
9
+ *
10
+ * Anything else still hard-fails — overwriting a real project is never what
11
+ * `buddy new` was asked to do.
12
+ */
13
+ export declare function scaffoldTargetOccupants(path: string): string[];
4
14
  /**
5
15
  * Uses `@stacksjs/gitit`'s library API directly rather than shelling out to
6
16
  * `bunx --bun @stacksjs/gitit`. `bunx` always resolves the published npm
@@ -1 +1 @@
1
- import{chmodSync,cpSync,existsSync,readFileSync,readdirSync,rmSync,writeFileSync}from"node:fs";import process from"node:process";import{runAction}from"@stacksjs/actions";import{bold,cyan,dim,intro,log,onUnknownSubcommand,runCommand}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{relative,resolve}from"@stacksjs/path";import{isFolder}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";import{uninstallAllFeatures}from"./features";import{ensurePantryDependencies,ensurePantryInstalled,optimizePantryDeps}from"./setup";import{resultFailed}from"../result";import{appIdentity,applyAppAgentsTemplate,applyAppChangelogTemplate,applyAppEnvTemplate,applyAppPackageTemplate,removeFrameworkLicense,renderTemplate}from"../scaffold-app";import{applyAppSiteTemplate}from"../scaffold-site";import{adoptPackagedDefaults,applyAppPackageScripts,applyPackageAppTsconfig,pruneFrameworkOnlyFiles,rewriteCoreContentPaths,stripFrameworkManifestCredits}from"../scaffold-package-app";import{fetchPublishedVersions}from"../registry";export function create(buddy){const descriptions={name:"The name of the project",command:"Create a new Stacks project",ui:"Are you building a UI?",components:"Are you building UI components?",webComponents:"Automagically built optimized custom elements/web components?",views:"How about views?",functions:"Are you developing functions/composables?",api:"Are you building an API?",database:"Do you need a database?",notifications:"Do you need notifications? e.g. email, SMS, push or chat notifications",cache:"Do you need caching?",email:"Do you need email?",project:"Target a specific project",minimal:"Skip optional feature bundles (cms, commerce, dashboard, marketing, monitoring, realtime, queue) - bare-bones API/SPA starter that can re-add them later via `./buddy <feature>:install`.",withCore:"Keep the framework vendored in `storage/framework/core` as a Bun workspace, for working ON Stacks. Apps that only work WITH Stacks want the default, which resolves every @stacksjs/* package from npm.",vendorDefaults:"Commit a copy of the framework defaults in `storage/framework/defaults` instead of linking it to node_modules/@stacksjs/defaults. Implied by --with-core.",verbose:"Enable verbose output"};buddy.command("new [name]",descriptions.command).alias("create").option("-n, --name [name]",descriptions.name,{default:!1}).option("-u, --ui",descriptions.ui,{default:!0}).option("-c, --components",descriptions.components,{default:!0}).option("-w, --web-components",descriptions.webComponents,{default:!0}).option("-p, --views",descriptions.views,{default:!0}).option("-f, --functions",descriptions.functions,{default:!0}).option("-a, --api",descriptions.api,{default:!0}).option("-d, --database",descriptions.database,{default:!0}).option("-ca, --cache",descriptions.cache,{default:!1}).option("-e, --email",descriptions.email,{default:!1}).option("-P, --project [project]",descriptions.project,{default:!1}).option("-m, --minimal",descriptions.minimal,{default:!1}).option("--native","Create a minimal offline Craft desktop app",{default:!1}).option("--adopt","Integrate an existing native app without replacing its source (requires --native)",{default:!1}).option("--with-core",descriptions.withCore,{default:!1}).option("--vendor-defaults",descriptions.vendorDefaults,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(name,options)=>{log.debug("Running `buddy new <name>` ...",options);const startTime=await intro("buddy new");name=name??options.name;const path=resolve(process.cwd(),name??".");if(options.adopt&&!options.native)throw Error("--adopt requires --native");if(options.native){const{scaffoldNativeProject}=await import("../native-project"),{version}=await import("../../package.json"),{latest}=await fetchPublishedVersions("@stacksjs/stx");if(!latest)throw Error("The registry has no published stx version.");scaffoldNativeProject(path,{adopt:options.adopt,buddyVersion:version,stxVersion:latest});const installed=await runCommand(["bun","install"],{cwd:path});if(resultFailed(installed))throw installed.error;log.success(`Native app ready at ${path}. Run ./buddy dev to launch.`);await log.flush();return}isFolderCheck(path);await onlineCheck();const result=await download(name,path,options);if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}ensureExecutableScripts(path);applyAppVcsTemplate(path);applyAppConfigTemplate(path);applyAppEnv(path);applyAppPackage(path);applyAppReadme(path);applyAppAgents(path);replaceFrameworkSite(path);removeFrameworkTests(path);await generatePantryDeps(path);await ensureEnv(path,options);if(options.minimal)await stripFeatures(path);await install(path,options);if(!options.withCore){trimFrameworkOnlyFiles(path);await unvendorCore(path,options);applyPackageAppTsconfig(path);if(!options.vendorDefaults)linkFrameworkDefaults(path)}if(options.minimal||!options.withCore&&!options.vendorDefaults)await regenerateAutoImports(path,options);if(startTime){const time=performance.now()-startTime;log.success(dim(`[${time.toFixed(2)}ms] Completed`))}log.info(bold("Welcome to the Stacks Framework! \u269B\uFE0F"));log.info(`Get started: ${cyan(`cd ${name}`)} and then ${cyan("./buddy dev")}`);log.info(`Run ${cyan("./buddy doctor")} anytime to check your setup`);log.info("To learn more, visit https://stacksjs.com");await log.flush();process.exit(ExitCode.Success)});onUnknownSubcommand(buddy,"new")}function isFolderCheck(path){if(!isFolder(path))return;if(readdirSync(path).filter((entry)=>entry!==".git").length===0)return;console.error(`Path ${path} already exists`);process.exit(ExitCode.FatalError)}async function onlineCheck(){if(await isOnline())return;log.info("It appears you are disconnected from the internet.");log.info("Creating a new project requires a brief internet connection to download the template and install dependencies.");await log.flush();process.exit(ExitCode.FatalError)}async function isOnline(){try{return(await fetch("https://github.com",{method:"HEAD",signal:AbortSignal.timeout(3000)})).ok}catch{return!1}}export async function templateRef(fetchVersions=fetchPublishedVersions){try{const{latest}=await fetchVersions("stacks");return latest?`v${latest}`:null}catch{return null}}export function templateSpec(ref){return ref?`gh:stacksjs/stacks#${ref}`:"gh:stacksjs/stacks"}async function download(name,path,_options){log.info("Setting up your stack.");const ref=await templateRef();try{const{downloadTemplate}=await import("@stacksjs/gitit");try{await downloadTemplate(templateSpec(ref),{dir:name,force:!0})}catch(error){if(!ref)throw error;log.warn(`No ${ref} tag to scaffold from (${error instanceof Error?error.message:String(error)}).`);log.info("Falling back to the default branch. If this app does not typecheck, that gap is why.");await downloadTemplate(templateSpec(null),{dir:name,force:!0})}log.success(`Successfully scaffolded your project at ${cyan(path)}`);return{isErr:!1}}catch(error){return{isErr:!0,error:error instanceof Error?error.message:String(error)}}}function applyAppVcsTemplate(path){const source=resolve(path,"storage/framework/defaults/vcs/github"),destination=resolve(path,".github");if(!existsSync(source)){log.warn("No app CI template found at storage/framework/defaults/vcs/github - leaving .github as downloaded.");return}log.info("Installing app-shaped GitHub workflows...");try{rmSync(destination,{recursive:!0,force:!0});cpSync(source,destination,{recursive:!0});log.success("App CI installed")}catch(error){log.warn(`Could not install the app CI template: ${error instanceof Error?error.message:String(error)}`)}}function applyAppConfigTemplate(path){const source=resolve(path,"storage/framework/defaults/scaffold/config"),destination=resolve(path,"config");if(!existsSync(source)){log.warn("No app config template found at storage/framework/defaults/scaffold/config - leaving config as downloaded.");return}const identity=appIdentity(path);log.info("Installing app-safe infrastructure configuration...");try{for(const file of readdirSync(source)){if(!file.endsWith(".ts"))continue;const template=readFileSync(resolve(source,file),"utf8");writeFileSync(resolve(destination,file),renderTemplate(template,identity))}log.success("App-safe infrastructure configuration installed")}catch(error){log.warn(`Could not install the app config template: ${error instanceof Error?error.message:String(error)}`)}}function applyAppEnv(path){try{const changed=applyAppEnvTemplate(path,appIdentity(path));if(changed.length>0)log.success(`Named the app in .env.example (${changed.join(", ")})`)}catch(error){log.warn(`Could not name the app in .env.example: ${error instanceof Error?error.message:String(error)}`)}}function applyAppPackage(path){try{const identity=appIdentity(path),changed=applyAppPackageTemplate(path,identity);if(changed.length>0)log.success(`Named the app in package.json (${changed.join(", ")})`);if(applyAppChangelogTemplate(path,identity))log.success("Started the app its own CHANGELOG.md");if(removeFrameworkLicense(path))log.success("Removed the framework's LICENSE.md - choose the app's own licence")}catch(error){log.warn(`Could not name the app in package.json: ${error instanceof Error?error.message:String(error)}`)}}function applyAppAgents(path){try{if(applyAppAgentsTemplate(path))log.success("AGENTS.md no longer tells agents to publish as the framework's author")}catch(error){log.warn(`Could not update AGENTS.md: ${error instanceof Error?error.message:String(error)}`)}}function applyAppReadme(path){const source=resolve(path,"storage/framework/defaults/scaffold/README.md");if(!existsSync(source))return;try{writeFileSync(resolve(path,"README.md"),renderTemplate(readFileSync(source,"utf8"),appIdentity(path)))}catch(error){log.warn(`Could not write the app README: ${error instanceof Error?error.message:String(error)}`)}}function replaceFrameworkSite(path){log.info("Replacing the stacksjs.com site with a starter page...");try{const removed=applyAppSiteTemplate(path);log.success(`Starter page installed (${removed.length} stacksjs.com path${removed.length===1?"":"s"} removed)`)}catch(error){log.warn(`Could not install the starter page: ${error instanceof Error?error.message:String(error)}`)}}function removeFrameworkTests(path){const tests=resolve(path,"tests");if(!existsSync(tests))return;log.info("Removing the framework's own test suite...");for(const entry of readdirSync(tests)){if(entry==="setup.ts")continue;rmSync(resolve(tests,entry),{recursive:!0,force:!0})}log.success("App starts with a clean test suite")}function ensureExecutableScripts(path){for(const script of["buddy","bootstrap"])try{chmodSync(resolve(path,script),493)}catch{}}async function ensureEnv(path,_options){log.info("Ensuring your environment is ready...");await ensurePantryInstalled();await ensurePantryDependencies(path);log.success("Environment is ready")}async function install(path,options){log.info("Installing & setting up Stacks");log.info("Copying .env.example \u2192 .env");let result=await runCommand("cp .env.example .env",{...options,cwd:path});if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}log.info("Removing template-encrypted env files...");const{rm}=await import("node:fs/promises");for(const stale of[".env.development",".env.staging",".env.production",".env.keys"])await rm(`${path}/${stale}`,{force:!0});log.info("Generating application key...");const keyResult=await runAction(Action.KeyGenerate,{...options,cwd:path});if(resultFailed(keyResult)){await log.error(keyResult.error);process.exit(ExitCode.FatalError)}if(existsSync(resolve(path,".git")))log.info("Existing git repository detected, skipping git init");else{log.info("Initializing git repository...");result=await runCommand("git init",{...options,cwd:path});if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}}log.success("Installed & set-up \uD83D\uDE80")}async function unvendorCore(path,options){log.info("Resolving the framework from npm (pass --with-core to keep it vendored)...");const result=await runCommand("./buddy unpublish:core --all --force",{...options,cwd:path});if(resultFailed(result)){const reason=result.error instanceof Error?result.error.stack??result.error.message:String(result.error??"").trim();log.error("Could not resolve the framework from npm.");log.error(reason.length>0?reason:"The step failed without reporting a reason.");log.error("");log.error(`The project at ${path} is half converted: the vendored framework has been`);log.error("removed and the published packages are not in place yet, so ./buddy cannot");log.error("boot there. Finish it by hand with:");log.error("");await log.error(` cd ${path} && rm -f node_modules/stacks && bun install`);await log.error("");await log.error("Or start over with `--with-core` to keep the framework vendored.");process.exit(ExitCode.FatalError)}log.success("Framework resolved from npm")}async function generatePantryDeps(path){try{if(await optimizePantryDeps(path))rmSync(resolve(path,"deps.yml"),{force:!0})}catch(error){log.warn(`Could not generate deps.yaml from config/deps.ts, keeping the template's deps.yml: ${error instanceof Error?error.message:String(error)}`)}}function trimFrameworkOnlyFiles(path){try{const removed=pruneFrameworkOnlyFiles(path),scripts=applyAppPackageScripts(path);rewriteCoreContentPaths(path);stripFrameworkManifestCredits(path);log.success(`Removed ${removed.length} framework-only path${removed.length===1?"":"s"} and ${scripts.removed.length} framework-only script${scripts.removed.length===1?"":"s"}`)}catch(error){log.warn(`Could not remove the framework-only files: ${error instanceof Error?error.message:String(error)}`)}}function linkFrameworkDefaults(path){log.info("Linking the framework defaults from node_modules (pass --vendor-defaults to commit a copy)...");try{const adopted=adoptPackagedDefaults(path);log.success(`storage/framework/defaults now links to ${relative(path,adopted.packageRoot)}`)}catch(error){log.warn(`Could not move the framework defaults to node_modules: ${error instanceof Error?error.message:String(error)}`)}}async function regenerateAutoImports(path,options){const result=await runCommand("./buddy generate --types",{...options,cwd:path});if(resultFailed(result))log.warn("Could not regenerate the auto-import barrels. Run `./buddy generate --types` in the project before `./buddy dev`.")}async function stripFeatures(path){log.info("Stripping optional feature bundles (--minimal)...");const results=await uninstallAllFeatures({root:path});let strippedAny=!1;for(const{feature,configOutcome,filesRemoved}of results)if(configOutcome==="flipped"||filesRemoved.length>0){strippedAny=!0;const fileSummary=filesRemoved.length>0?` (${filesRemoved.length} path${filesRemoved.length===1?"":"s"})`:"";log.info(` - ${feature}${fileSummary}`)}if(!strippedAny)log.info(" \u2192 no feature scaffolding present; nothing to strip.");else log.success("Minimal skeleton ready - run `./buddy <feature>:install` to add features back.")}
1
+ import{chmodSync,cpSync,existsSync,readFileSync,readdirSync,rmSync,writeFileSync}from"node:fs";import process from"node:process";import{runAction}from"@stacksjs/actions";import{bold,cyan,dim,intro,log,onUnknownSubcommand,runCommand}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{relative,resolve}from"@stacksjs/path";import{isFolder}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";import{uninstallAllFeatures}from"./features";import{ensurePantryDependencies,ensurePantryInstalled,optimizePantryDeps}from"./setup";import{resultFailed}from"../result";import{appIdentity,applyAppAgentsTemplate,applyAppChangelogTemplate,applyAppEnvTemplate,applyAppPackageTemplate,removeFrameworkLicense,renderTemplate}from"../scaffold-app";import{applyAppSiteTemplate}from"../scaffold-site";import{adoptPackagedDefaults,applyAppPackageScripts,applyPackageAppTsconfig,pruneFrameworkOnlyFiles,rewriteCoreContentPaths,stripFrameworkManifestCredits}from"../scaffold-package-app";import{fetchPublishedVersions}from"../registry";import{ensureAuthRoutesEnv}from"../minimal-routes";import{scaffoldDesktopApp}from"../scaffold-desktop";export function create(buddy){const descriptions={name:"The name of the project",command:"Create a new Stacks project",ui:"Are you building a UI?",components:"Are you building UI components?",webComponents:"Automagically built optimized custom elements/web components?",views:"How about views?",functions:"Are you developing functions/composables?",api:"Are you building an API?",database:"Do you need a database?",notifications:"Do you need notifications? e.g. email, SMS, push or chat notifications",cache:"Do you need caching?",email:"Do you need email?",project:"Target a specific project",minimal:"Skip optional feature bundles (cms, commerce, dashboard, marketing, monitoring, realtime, queue) - bare-bones API/SPA starter that can re-add them later via `./buddy <feature>:install`.",withCore:"Keep the framework vendored in `storage/framework/core` as a Bun workspace, for working ON Stacks. Apps that only work WITH Stacks want the default, which resolves every @stacksjs/* package from npm.",vendorDefaults:"Commit a copy of the framework defaults in `storage/framework/defaults` instead of linking it to node_modules/@stacksjs/defaults. Implied by --with-core.",verbose:"Enable verbose output"};buddy.command("new [name]",descriptions.command).alias("create").option("-n, --name [name]",descriptions.name,{default:!1}).option("-u, --ui",descriptions.ui,{default:!0}).option("-c, --components",descriptions.components,{default:!0}).option("-w, --web-components",descriptions.webComponents,{default:!0}).option("-p, --views",descriptions.views,{default:!0}).option("-f, --functions",descriptions.functions,{default:!0}).option("-a, --api",descriptions.api,{default:!0}).option("-d, --database",descriptions.database,{default:!0}).option("-ca, --cache",descriptions.cache,{default:!1}).option("-e, --email",descriptions.email,{default:!1}).option("-P, --project [project]",descriptions.project,{default:!1}).option("-m, --minimal",descriptions.minimal,{default:!1}).option("--native","Create a minimal offline Craft desktop app",{default:!1}).option("--desktop","Also make it a native Mac app: stx screens drawn by AppKit, with the API running inside the app (config/desktop.ts, macos.agent)",{default:!1}).option("--adopt","Integrate an existing native app without replacing its source (requires --native)",{default:!1}).option("--with-core",descriptions.withCore,{default:!1}).option("--vendor-defaults",descriptions.vendorDefaults,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(name,options)=>{log.debug("Running `buddy new <name>` ...",options);const startTime=await intro("buddy new");name=name??options.name;const path=resolve(process.cwd(),name??".");if(options.adopt&&!options.native)throw Error("--adopt requires --native");if(options.desktop&&options.native)throw Error("--desktop makes a Stacks app with a native Mac app; --native makes a standalone Craft app. Choose one.");if(options.native){const{scaffoldNativeProject}=await import("../native-project"),{version}=await import("../../package.json"),{latest}=await fetchPublishedVersions("@stacksjs/stx");if(!latest)throw Error("The registry has no published stx version.");scaffoldNativeProject(path,{adopt:options.adopt,buddyVersion:version,stxVersion:latest});const installed=await runCommand(["bun","install"],{cwd:path});if(resultFailed(installed))throw installed.error;log.success(`Native app ready at ${path}. Run ./buddy dev to launch.`);await log.flush();return}isFolderCheck(path);await onlineCheck();const result=await download(name,path,options);if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}ensureExecutableScripts(path);applyAppVcsTemplate(path);applyAppConfigTemplate(path);applyAppEnv(path);applyAppPackage(path);applyAppReadme(path);applyAppAgents(path);replaceFrameworkSite(path);removeFrameworkTests(path);await generatePantryDeps(path);await ensureEnv(path,options);if(options.minimal)await stripFeatures(path);await install(path,options);if(!options.withCore){trimFrameworkOnlyFiles(path);await unvendorCore(path,options);applyPackageAppTsconfig(path);if(!options.vendorDefaults)linkFrameworkDefaults(path)}if(options.minimal||!options.withCore&&!options.vendorDefaults)await regenerateAutoImports(path,options);if(options.desktop){const written=scaffoldDesktopApp(path,appIdentity(path));log.success(`Added the Mac app (${written.join(", ")}). Run ${cyan("bun run dev:desktop")} to open it.`)}if(startTime){const time=performance.now()-startTime;log.success(dim(`[${time.toFixed(2)}ms] Completed`))}log.info(bold("Welcome to the Stacks Framework! \u269B\uFE0F"));log.info(`Get started: ${cyan(`cd ${name}`)} and then ${cyan("./buddy dev")}`);log.info(`Run ${cyan("./buddy doctor")} anytime to check your setup`);log.info("To learn more, visit https://stacksjs.com");await log.flush();process.exit(ExitCode.Success)});onUnknownSubcommand(buddy,"new")}export function scaffoldTargetOccupants(path){if(!isFolder(path))return[];return readdirSync(path).filter((entry)=>entry!==".git")}function isFolderCheck(path){if(scaffoldTargetOccupants(path).length===0)return;console.error(`Path ${path} already exists`);process.exit(ExitCode.FatalError)}async function onlineCheck(){if(await isOnline())return;log.info("It appears you are disconnected from the internet.");log.info("Creating a new project requires a brief internet connection to download the template and install dependencies.");await log.flush();process.exit(ExitCode.FatalError)}async function isOnline(){try{return(await fetch("https://github.com",{method:"HEAD",signal:AbortSignal.timeout(3000)})).ok}catch{return!1}}export async function templateRef(fetchVersions=fetchPublishedVersions){try{const{latest}=await fetchVersions("stacks");return latest?`v${latest}`:null}catch{return null}}export function templateSpec(ref){return ref?`gh:stacksjs/stacks#${ref}`:"gh:stacksjs/stacks"}async function download(name,path,_options){log.info("Setting up your stack.");const ref=await templateRef();try{const{downloadTemplate}=await import("@stacksjs/gitit");try{await downloadTemplate(templateSpec(ref),{dir:name,force:!0})}catch(error){if(!ref)throw error;log.warn(`No ${ref} tag to scaffold from (${error instanceof Error?error.message:String(error)}).`);log.info("Falling back to the default branch. If this app does not typecheck, that gap is why.");await downloadTemplate(templateSpec(null),{dir:name,force:!0})}log.success(`Successfully scaffolded your project at ${cyan(path)}`);return{isErr:!1}}catch(error){return{isErr:!0,error:error instanceof Error?error.message:String(error)}}}function applyAppVcsTemplate(path){const source=resolve(path,"storage/framework/defaults/vcs/github"),destination=resolve(path,".github");if(!existsSync(source)){log.warn("No app CI template found at storage/framework/defaults/vcs/github - leaving .github as downloaded.");return}log.info("Installing app-shaped GitHub workflows...");try{rmSync(destination,{recursive:!0,force:!0});cpSync(source,destination,{recursive:!0});log.success("App CI installed")}catch(error){log.warn(`Could not install the app CI template: ${error instanceof Error?error.message:String(error)}`)}}function applyAppConfigTemplate(path){const source=resolve(path,"storage/framework/defaults/scaffold/config"),destination=resolve(path,"config");if(!existsSync(source)){log.warn("No app config template found at storage/framework/defaults/scaffold/config - leaving config as downloaded.");return}const identity=appIdentity(path);log.info("Installing app-safe infrastructure configuration...");try{for(const file of readdirSync(source)){if(!file.endsWith(".ts"))continue;const template=readFileSync(resolve(source,file),"utf8");writeFileSync(resolve(destination,file),renderTemplate(template,identity))}log.success("App-safe infrastructure configuration installed")}catch(error){log.warn(`Could not install the app config template: ${error instanceof Error?error.message:String(error)}`)}}function applyAppEnv(path){try{const changed=applyAppEnvTemplate(path,appIdentity(path));if(changed.length>0)log.success(`Named the app in .env.example (${changed.join(", ")})`)}catch(error){log.warn(`Could not name the app in .env.example: ${error instanceof Error?error.message:String(error)}`)}}function applyAppPackage(path){try{const identity=appIdentity(path),changed=applyAppPackageTemplate(path,identity);if(changed.length>0)log.success(`Named the app in package.json (${changed.join(", ")})`);if(applyAppChangelogTemplate(path,identity))log.success("Started the app its own CHANGELOG.md");if(removeFrameworkLicense(path))log.success("Removed the framework's LICENSE.md - choose the app's own licence")}catch(error){log.warn(`Could not name the app in package.json: ${error instanceof Error?error.message:String(error)}`)}}function applyAppAgents(path){try{if(applyAppAgentsTemplate(path))log.success("AGENTS.md no longer tells agents to publish as the framework's author")}catch(error){log.warn(`Could not update AGENTS.md: ${error instanceof Error?error.message:String(error)}`)}}function applyAppReadme(path){const source=resolve(path,"storage/framework/defaults/scaffold/README.md");if(!existsSync(source))return;try{writeFileSync(resolve(path,"README.md"),renderTemplate(readFileSync(source,"utf8"),appIdentity(path)))}catch(error){log.warn(`Could not write the app README: ${error instanceof Error?error.message:String(error)}`)}}function replaceFrameworkSite(path){log.info("Replacing the stacksjs.com site with a starter page...");try{const removed=applyAppSiteTemplate(path);log.success(`Starter page installed (${removed.length} stacksjs.com path${removed.length===1?"":"s"} removed)`)}catch(error){log.warn(`Could not install the starter page: ${error instanceof Error?error.message:String(error)}`)}}function removeFrameworkTests(path){const tests=resolve(path,"tests");if(!existsSync(tests))return;log.info("Removing the framework's own test suite...");for(const entry of readdirSync(tests)){if(entry==="setup.ts")continue;rmSync(resolve(tests,entry),{recursive:!0,force:!0})}log.success("App starts with a clean test suite")}function ensureExecutableScripts(path){for(const script of["buddy","bootstrap"])try{chmodSync(resolve(path,script),493)}catch{}}async function ensureEnv(path,_options){log.info("Ensuring your environment is ready...");await ensurePantryInstalled();await ensurePantryDependencies(path);log.success("Environment is ready")}async function install(path,options){log.info("Installing & setting up Stacks");log.info("Copying .env.example \u2192 .env");let result=await runCommand("cp .env.example .env",{...options,cwd:path});if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}log.info("Removing template-encrypted env files...");const{rm}=await import("node:fs/promises");for(const stale of[".env.development",".env.staging",".env.production",".env.keys"])await rm(`${path}/${stale}`,{force:!0});log.info("Generating application key...");const keyResult=await runAction(Action.KeyGenerate,{...options,cwd:path});if(resultFailed(keyResult)){await log.error(keyResult.error);process.exit(ExitCode.FatalError)}if(existsSync(resolve(path,".git")))log.info("Existing git repository detected, skipping git init");else{log.info("Initializing git repository...");result=await runCommand("git init",{...options,cwd:path});if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}}log.success("Installed & set-up \uD83D\uDE80")}async function unvendorCore(path,options){log.info("Resolving the framework from npm (pass --with-core to keep it vendored)...");const result=await runCommand("./buddy unpublish:core --all --force",{...options,cwd:path});if(resultFailed(result)){const reason=result.error instanceof Error?result.error.stack??result.error.message:String(result.error??"").trim();log.error("Could not resolve the framework from npm.");log.error(reason.length>0?reason:"The step failed without reporting a reason.");log.error("");log.error(`The project at ${path} is half converted: the vendored framework has been`);log.error("removed and the published packages are not in place yet, so ./buddy cannot");log.error("boot there. Finish it by hand with:");log.error("");await log.error(` cd ${path} && rm -f node_modules/stacks && bun install`);await log.error("");await log.error("Or start over with `--with-core` to keep the framework vendored.");process.exit(ExitCode.FatalError)}log.success("Framework resolved from npm")}async function generatePantryDeps(path){try{if(await optimizePantryDeps(path))rmSync(resolve(path,"deps.yml"),{force:!0})}catch(error){log.warn(`Could not generate deps.yaml from config/deps.ts, keeping the template's deps.yml: ${error instanceof Error?error.message:String(error)}`)}}function trimFrameworkOnlyFiles(path){try{const removed=pruneFrameworkOnlyFiles(path),scripts=applyAppPackageScripts(path);rewriteCoreContentPaths(path);stripFrameworkManifestCredits(path);log.success(`Removed ${removed.length} framework-only path${removed.length===1?"":"s"} and ${scripts.removed.length} framework-only script${scripts.removed.length===1?"":"s"}`)}catch(error){log.warn(`Could not remove the framework-only files: ${error instanceof Error?error.message:String(error)}`)}}function linkFrameworkDefaults(path){log.info("Linking the framework defaults from node_modules (pass --vendor-defaults to commit a copy)...");try{const adopted=adoptPackagedDefaults(path);log.success(`storage/framework/defaults now links to ${relative(path,adopted.packageRoot)}`)}catch(error){log.warn(`Could not move the framework defaults to node_modules: ${error instanceof Error?error.message:String(error)}`)}}async function regenerateAutoImports(path,options){const result=await runCommand("./buddy generate --types",{...options,cwd:path});if(resultFailed(result))log.warn("Could not regenerate the auto-import barrels. Run `./buddy generate --types` in the project before `./buddy dev`.")}async function stripFeatures(path){log.info("Stripping optional feature bundles (--minimal)...");const results=await uninstallAllFeatures({root:path}),authEnv=ensureAuthRoutesEnv(path);if(authEnv.length>0)log.info(` - kept the auth routes mounted (STACKS_DEFAULT_ROUTES=auth in ${authEnv.join(", ")})`);let strippedAny=!1;for(const{feature,configOutcome,filesRemoved}of results)if(configOutcome==="flipped"||filesRemoved.length>0){strippedAny=!0;const fileSummary=filesRemoved.length>0?` (${filesRemoved.length} path${filesRemoved.length===1?"":"s"})`:"";log.info(` - ${feature}${fileSummary}`)}if(!strippedAny)log.info(" \u2192 no feature scaffolding present; nothing to strip.");else log.success("Minimal skeleton ready - run `./buddy <feature>:install` to add features back.")}
@@ -1,5 +1,12 @@
1
1
  import type { CLI } from '@stacksjs/types';
2
- export declare function resolveAppleDesktopConfig(options?: AppleDesktopOptions): AppleDesktopConfig;
2
+ import type { ResolvedMacosDesktop } from '@stacksjs/desktop-build';
3
+ /**
4
+ * Flags, then the environment, then `config/desktop.ts` (`macos`), then the
5
+ * framework's defaults. A project that declares its native app there never
6
+ * needs the Apple variables for what is not secret: the name, bundle id, team,
7
+ * version, category and icon.
8
+ */
9
+ export declare function resolveAppleDesktopConfig(options?: AppleDesktopOptions, native?: ResolvedMacosDesktop | null): AppleDesktopConfig;
3
10
  export declare function validateAppleDesktopConfig(config: AppleDesktopConfig, includeApi?: boolean): string[];
4
11
  /**
5
12
  * Whether a string is shaped like an `LSApplicationCategoryType`.
@@ -1,4 +1,4 @@
1
- import{createHash}from"node:crypto";import{chmodSync,copyFileSync,existsSync,mkdirSync,readdirSync,readFileSync,rmSync,writeFileSync}from"node:fs";import{basename,join,resolve}from"node:path";import process from"node:process";import{log}from"@stacksjs/cli";import{projectPath,storagePath}from"@stacksjs/path";import{resultFailed}from"../result";const REQUIRED_TOOLS=["codesign","security","productbuild","pkgutil","xcrun"];function env(name){return process.env[name]?.trim()||""}function xml(value){return value.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}function packageMetadata(){const packageJson=JSON.parse(readFileSync(projectPath("package.json"),"utf8"));return{name:packageJson.name||"Stacks",version:packageJson.version||"0.1.0"}}export function resolveAppleDesktopConfig(options={}){const metadata=packageMetadata(),provisioningProfile=options.provisioningProfile||env("APPLE_PROVISIONING_PROFILE"),apiKeyPath=options.apiKeyPath||env("APP_STORE_CONNECT_API_KEY_PATH"),craftArm64=options.craftArm64||env("CRAFT_BIN_ARM64"),craftX64=options.craftX64||env("CRAFT_BIN_X64"),keychain=options.keychain||env("APPLE_KEYCHAIN");return{appName:options.appName||env("APPLE_APP_NAME")||env("APP_NAME")||metadata.name,bundleId:options.bundleId||env("APPLE_BUNDLE_ID"),teamId:options.teamId||env("APPLE_TEAM_ID"),version:options.appVersion||env("APPLE_APP_VERSION")||metadata.version,buildNumber:options.buildNumber||env("APPLE_BUILD_NUMBER")||env("GITHUB_RUN_NUMBER")||String(Math.floor(Date.now()/1000)),minimumMacos:options.minimumMacos||env("APPLE_MINIMUM_MACOS")||"13.0",category:options.category||env("APPLE_APP_CATEGORY")||"public.app-category.productivity",appSigningIdentity:options.appSigningIdentity||env("APPLE_APP_SIGNING_IDENTITY"),installerSigningIdentity:options.installerSigningIdentity||env("APPLE_INSTALLER_SIGNING_IDENTITY"),provisioningProfile:provisioningProfile?resolve(provisioningProfile):"",apiKeyId:options.apiKeyId||env("APP_STORE_CONNECT_API_KEY_ID"),apiIssuerId:options.apiIssuerId||env("APP_STORE_CONNECT_API_ISSUER_ID"),apiKeyPath:apiKeyPath?resolve(apiKeyPath):"",icon:options.icon||env("APPLE_APP_ICON")||void 0,universal:Boolean(options.universal)||env("APPLE_UNIVERSAL")==="true",craftArm64:craftArm64?resolve(craftArm64):void 0,craftX64:craftX64?resolve(craftX64):void 0,keychain:keychain?resolve(keychain):void 0}}export function validateAppleDesktopConfig(config,includeApi=!0){const errors=[];if(process.platform!=="darwin")errors.push("Mac App Store packaging must run on macOS");if(!config.appName)errors.push("APPLE_APP_NAME or --app-name is required");if(!/^[A-Za-z0-9][A-Za-z0-9.-]+$/.test(config.bundleId))errors.push("APPLE_BUNDLE_ID must be a reverse-DNS bundle identifier");if(!/^[A-Z0-9]{10}$/.test(config.teamId))errors.push("APPLE_TEAM_ID must be the 10-character Apple Developer team ID");if(!/^\d+(?:\.\d+){0,2}$/.test(config.version))errors.push("The marketing version must contain one to three numeric components");if(!/^[A-Za-z0-9.-]+$/.test(config.buildNumber))errors.push("The build number must contain only letters, numbers, periods, and hyphens");if(!config.appSigningIdentity)errors.push("APPLE_APP_SIGNING_IDENTITY is required");if(!config.installerSigningIdentity)errors.push("APPLE_INSTALLER_SIGNING_IDENTITY is required");if(!config.provisioningProfile||!existsSync(config.provisioningProfile))errors.push("APPLE_PROVISIONING_PROFILE must point to an existing .provisionprofile file");if(config.icon&&!existsSync(resolve(config.icon)))errors.push(`Apple app icon does not exist: ${config.icon}`);if(!/^\d+(?:\.\d+){0,2}$/.test(config.minimumMacos))errors.push("APPLE_MINIMUM_MACOS must be a macOS version such as 13.0");if(!isAppleCategory(config.category))errors.push("APPLE_APP_CATEGORY must be an Apple LSApplicationCategoryType such as public.app-category.productivity");if(includeApi){if(!config.apiKeyId)errors.push("APP_STORE_CONNECT_API_KEY_ID is required");if(!config.apiIssuerId)errors.push("APP_STORE_CONNECT_API_ISSUER_ID is required");if(!config.apiKeyPath||!existsSync(config.apiKeyPath))errors.push("APP_STORE_CONNECT_API_KEY_PATH must point to an existing .p8 file")}if(config.universal){for(const[variable,path]of[["CRAFT_BIN_ARM64",config.craftArm64],["CRAFT_BIN_X64",config.craftX64]])if(!path||!existsSync(path))errors.push(`${variable} must point to a native Craft runtime for a universal build`);else if(readFileSync(path).subarray(0,2).toString()==="#!")errors.push(`${variable} points at a script, not a native Craft runtime: ${path}`)}for(const tool of config.universal?[...REQUIRED_TOOLS,"lipo"]:REQUIRED_TOOLS)if(!Bun.which(tool))errors.push(`Required Apple command is unavailable: ${tool}`);return errors}function bundleFiles(appPath){const walk=(dir,prefix)=>readdirSync(dir,{withFileTypes:!0}).flatMap((entry)=>{const relative=prefix?`${prefix}/${entry.name}`:entry.name;return entry.isDirectory()?walk(join(dir,entry.name),relative):[relative]});return walk(appPath,"")}export function isAppleCategory(value){return/^public\.app-category\.[a-z][a-z0-9-]*$/.test(value)}export function storeRejectionFindings(paths,executable="stacks-desktop"){const findings=[],has=(path)=>paths.includes(path);for(const required of["Contents/Info.plist",`Contents/MacOS/${executable}`,"Contents/embedded.provisionprofile"])if(!has(required))findings.push(`missing required bundle file: ${required}`);for(const path of paths){const base=path.split("/").pop()??"";if(base===".DS_Store"||path.startsWith("__MACOSX/")||base.startsWith("._"))findings.push(`forbidden file in the bundle: ${path}`);if(/\.(?:dylib|so|framework|app|xpc)$/.test(path)){if(!(path.startsWith("Contents/Frameworks/")||path.startsWith("Contents/MacOS/")||path.startsWith("Contents/XPCServices/")||path.startsWith("Contents/PlugIns/")))findings.push(`nested code outside a signed location: ${path}`)}if(path.startsWith("Contents/Resources/")&&/\.(?:sh|command|dylib|so)$/.test(path))findings.push(`executable content under Resources: ${path}`)}return findings}export function renderInfoPlist(config,executable="stacks-desktop"){const iconEntry=config.icon?`
1
+ import{createHash}from"node:crypto";import{chmodSync,copyFileSync,existsSync,mkdirSync,readdirSync,readFileSync,rmSync,writeFileSync}from"node:fs";import{basename,join,resolve}from"node:path";import process from"node:process";import{log}from"@stacksjs/cli";import{projectPath,storagePath}from"@stacksjs/path";import{resultFailed}from"../result";const REQUIRED_TOOLS=["codesign","security","productbuild","pkgutil","xcrun"];function env(name){return process.env[name]?.trim()||""}function xml(value){return value.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&apos;")}async function nativeAppleConfig(){const{loadMacosDesktop}=await import("@stacksjs/desktop-build");return loadMacosDesktop(projectPath(),packageMetadata().version)}function packageMetadata(){const packageJson=JSON.parse(readFileSync(projectPath("package.json"),"utf8"));return{name:packageJson.name||"Stacks",version:packageJson.version||"0.1.0"}}export function resolveAppleDesktopConfig(options={},native){const metadata=packageMetadata(),provisioningProfile=options.provisioningProfile||env("APPLE_PROVISIONING_PROFILE"),apiKeyPath=options.apiKeyPath||env("APP_STORE_CONNECT_API_KEY_PATH"),craftArm64=options.craftArm64||env("CRAFT_BIN_ARM64"),craftX64=options.craftX64||env("CRAFT_BIN_X64"),keychain=options.keychain||env("APPLE_KEYCHAIN");return{appName:options.appName||env("APPLE_APP_NAME")||native?.appName||env("APP_NAME")||metadata.name,bundleId:options.bundleId||env("APPLE_BUNDLE_ID")||native?.bundleId||"",teamId:options.teamId||env("APPLE_TEAM_ID")||native?.teamId||"",version:options.appVersion||env("APPLE_APP_VERSION")||native?.version||metadata.version,buildNumber:options.buildNumber||env("APPLE_BUILD_NUMBER")||(native?native.buildNumber:"")||env("GITHUB_RUN_NUMBER")||String(Math.floor(Date.now()/1000)),minimumMacos:options.minimumMacos||env("APPLE_MINIMUM_MACOS")||native?.minimumSystemVersion||"13.0",category:options.category||env("APPLE_APP_CATEGORY")||native?.category||"public.app-category.productivity",appSigningIdentity:options.appSigningIdentity||env("APPLE_APP_SIGNING_IDENTITY"),installerSigningIdentity:options.installerSigningIdentity||env("APPLE_INSTALLER_SIGNING_IDENTITY"),provisioningProfile:provisioningProfile?resolve(provisioningProfile):"",apiKeyId:options.apiKeyId||env("APP_STORE_CONNECT_API_KEY_ID"),apiIssuerId:options.apiIssuerId||env("APP_STORE_CONNECT_API_ISSUER_ID"),apiKeyPath:apiKeyPath?resolve(apiKeyPath):"",icon:options.icon||env("APPLE_APP_ICON")||(native?.icon?resolve(projectPath(),native.icon):void 0),universal:Boolean(options.universal)||env("APPLE_UNIVERSAL")==="true",craftArm64:craftArm64?resolve(craftArm64):void 0,craftX64:craftX64?resolve(craftX64):void 0,keychain:keychain?resolve(keychain):void 0}}export function validateAppleDesktopConfig(config,includeApi=!0){const errors=[];if(process.platform!=="darwin")errors.push("Mac App Store packaging must run on macOS");if(!config.appName)errors.push("APPLE_APP_NAME or --app-name is required");if(!/^[A-Za-z0-9][A-Za-z0-9.-]+$/.test(config.bundleId))errors.push("APPLE_BUNDLE_ID must be a reverse-DNS bundle identifier");if(!/^[A-Z0-9]{10}$/.test(config.teamId))errors.push("APPLE_TEAM_ID must be the 10-character Apple Developer team ID");if(!/^\d+(?:\.\d+){0,2}$/.test(config.version))errors.push("The marketing version must contain one to three numeric components");if(!/^[A-Za-z0-9.-]+$/.test(config.buildNumber))errors.push("The build number must contain only letters, numbers, periods, and hyphens");if(!config.appSigningIdentity)errors.push("APPLE_APP_SIGNING_IDENTITY is required");if(!config.installerSigningIdentity)errors.push("APPLE_INSTALLER_SIGNING_IDENTITY is required");if(!config.provisioningProfile||!existsSync(config.provisioningProfile))errors.push("APPLE_PROVISIONING_PROFILE must point to an existing .provisionprofile file");if(config.icon&&!existsSync(resolve(config.icon)))errors.push(`Apple app icon does not exist: ${config.icon}`);if(!/^\d+(?:\.\d+){0,2}$/.test(config.minimumMacos))errors.push("APPLE_MINIMUM_MACOS must be a macOS version such as 13.0");if(!isAppleCategory(config.category))errors.push("APPLE_APP_CATEGORY must be an Apple LSApplicationCategoryType such as public.app-category.productivity");if(includeApi){if(!config.apiKeyId)errors.push("APP_STORE_CONNECT_API_KEY_ID is required");if(!config.apiIssuerId)errors.push("APP_STORE_CONNECT_API_ISSUER_ID is required");if(!config.apiKeyPath||!existsSync(config.apiKeyPath))errors.push("APP_STORE_CONNECT_API_KEY_PATH must point to an existing .p8 file")}if(config.universal){for(const[variable,path]of[["CRAFT_BIN_ARM64",config.craftArm64],["CRAFT_BIN_X64",config.craftX64]])if(!path||!existsSync(path))errors.push(`${variable} must point to a native Craft runtime for a universal build`);else if(readFileSync(path).subarray(0,2).toString()==="#!")errors.push(`${variable} points at a script, not a native Craft runtime: ${path}`)}for(const tool of config.universal?[...REQUIRED_TOOLS,"lipo"]:REQUIRED_TOOLS)if(!Bun.which(tool))errors.push(`Required Apple command is unavailable: ${tool}`);return errors}function bundleFiles(appPath){const walk=(dir,prefix)=>readdirSync(dir,{withFileTypes:!0}).flatMap((entry)=>{const relative=prefix?`${prefix}/${entry.name}`:entry.name;return entry.isDirectory()?walk(join(dir,entry.name),relative):[relative]});return walk(appPath,"")}export function isAppleCategory(value){return/^public\.app-category\.[a-z][a-z0-9-]*$/.test(value)}export function storeRejectionFindings(paths,executable="stacks-desktop"){const findings=[],has=(path)=>paths.includes(path);for(const required of["Contents/Info.plist",`Contents/MacOS/${executable}`,"Contents/embedded.provisionprofile"])if(!has(required))findings.push(`missing required bundle file: ${required}`);for(const path of paths){const base=path.split("/").pop()??"";if(base===".DS_Store"||path.startsWith("__MACOSX/")||base.startsWith("._"))findings.push(`forbidden file in the bundle: ${path}`);if(/\.(?:dylib|so|framework|app|xpc)$/.test(path)){if(!(path.startsWith("Contents/Frameworks/")||path.startsWith("Contents/MacOS/")||path.startsWith("Contents/XPCServices/")||path.startsWith("Contents/PlugIns/")))findings.push(`nested code outside a signed location: ${path}`)}if(path.startsWith("Contents/Resources/")&&/\.(?:sh|command|dylib|so)$/.test(path))findings.push(`executable content under Resources: ${path}`)}return findings}export function renderInfoPlist(config,executable="stacks-desktop"){const iconEntry=config.icon?`
2
2
  <key>CFBundleIconFile</key>
3
3
  <string>AppIcon</string>`:"";return`<?xml version="1.0" encoding="UTF-8"?>
4
4
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -94,14 +94,16 @@ jobs:
94
94
  s3-prefix: \${{ vars.RELEASE_S3_PREFIX }}
95
95
  s3-public-url: \${{ vars.RELEASE_S3_PUBLIC_URL }}
96
96
  secrets: inherit
97
- `}function command(args,cwd=projectPath()){const result=Bun.spawnSync(args,{cwd,stdin:"inherit",stdout:"inherit",stderr:"inherit"});if(result.exitCode!==0)throw Error(`${args[0]} ${args.slice(1).join(" ")} exited with code ${result.exitCode}`)}export function signingPlan(input){const sign=(entitlements,target)=>["codesign","--force","--timestamp","--options","runtime","--entitlements",entitlements,...input.keychain?["--keychain",input.keychain]:[],"--sign",input.identity,target];return[sign(input.helperEntitlements,input.helperPath),sign(input.appEntitlements,input.appPath)]}export const captureCommand=(args)=>{const result=Bun.spawnSync(args,{cwd:projectPath(),stdin:"inherit",stdout:"pipe",stderr:"inherit"});if(result.exitCode!==0)throw Error(`${args[0]} ${args.slice(1).join(" ")} exited with code ${result.exitCode}`);return result.stdout.toString()},MACH_O_ARCHITECTURE={arm64:"arm64",x64:"x86_64"};export function parseLipoArchs(output){return output.trim().split(/\s+/).filter(Boolean).sort()}export function mergeUniversalBinary(input,run=captureCommand){for(const architecture of["arm64","x64"]){const expected=MACH_O_ARCHITECTURE[architecture],found=parseLipoArchs(run(["lipo","-archs",input[architecture]]));if(found.length!==1||found[0]!==expected)throw Error(`${input.name}: the ${architecture} input ${input[architecture]} contains ${found.join(" + ")||"no architectures"}, expected only ${expected}`)}run(["lipo","-create","-output",input.output,input.arm64,input.x64]);const merged=parseLipoArchs(run(["lipo","-archs",input.output])),expected=Object.values(MACH_O_ARCHITECTURE).sort();if(merged.join(" ")!==expected.join(" "))throw Error(`${input.name}: the universal binary ${input.output} contains ${merged.join(" + ")||"no architectures"}, expected ${expected.join(" + ")}`);return merged}export function buildUniversalPayload(input,run=captureCommand){const slice=(architecture)=>join(input.directory,architecture,"stacks-desktop");for(const architecture of["arm64","x64"]){mkdirSync(join(input.directory,architecture),{recursive:!0});run(input.launcherCompileArgs(architecture,slice(architecture)))}const launcher=join(input.directory,"stacks-desktop"),runtime=join(input.directory,"craft-runtime");mergeUniversalBinary({name:"stacks-desktop",arm64:slice("arm64"),x64:slice("x64"),output:launcher},run);const architectures=mergeUniversalBinary({name:"craft-runtime",arm64:input.craft.arm64,x64:input.craft.x64,output:runtime},run);return{launcher,runtime,architectures}}export function launcherSliceCompileArgs(base,architecture){return["bun",...base,`--target=bun-darwin-${architecture}`]}function signingIdentityExists(identity,keychain){if(!identity)return!1;const result=Bun.spawnSync(["security","find-identity","-v",...keychain?[keychain]:[]],{stdout:"pipe",stderr:"pipe"});return result.exitCode===0&&result.stdout.toString().includes(identity)}function provisioningProfileMatches(config){if(!config.provisioningProfile||!existsSync(config.provisioningProfile))return!1;const result=Bun.spawnSync(["security","cms","-D","-i",config.provisioningProfile],{stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)return!1;const profile=result.stdout.toString();return profile.includes(config.bundleId)&&profile.includes(config.teamId)}async function buildDesktop(){const{runAction}=await import("@stacksjs/actions"),{Action}=await import("@stacksjs/enums"),result=await runAction(Action.BuildDesktop);if(resultFailed(result))throw result.error}function sha256(path){return createHash("sha256").update(readFileSync(path)).digest("hex")}async function packageAppleDesktop(config,skipBuild=!1){const errors=validateAppleDesktopConfig(config,!1);if(config.keychain&&!existsSync(config.keychain))errors.push(`APPLE_KEYCHAIN does not exist: ${config.keychain}`);const where=config.keychain?` in ${config.keychain}`:"";if(!signingIdentityExists(config.appSigningIdentity,config.keychain))errors.push(`App signing identity is not installed${where}: ${config.appSigningIdentity}`);if(!signingIdentityExists(config.installerSigningIdentity,config.keychain))errors.push(`Installer signing identity is not installed${where}: ${config.installerSigningIdentity}`);if(!provisioningProfileMatches(config))errors.push(`Provisioning profile does not match ${config.teamId}.${config.bundleId}`);if(errors.length)throw Error(errors.join(`
98
- `));if(!skipBuild)await buildDesktop();const desktopDist=storagePath("framework/desktop-dist");let launcher=join(desktopDist,"stacks-desktop"),runtime=join(desktopDist,"craft-runtime");const manifest=join(desktopDist,"desktop.json");for(const path of[launcher,runtime,manifest])if(!existsSync(path))throw Error(`Desktop build artifact is missing: ${path}`);let architectures=[process.arch==="x64"?"x86_64":process.arch],craftSlices;if(config.universal){const universalDir=join(desktopDist,"universal");if(existsSync(universalDir))rmSync(universalDir,{recursive:!0});const{desktopLauncherCompileArgs,resolveDesktopLauncher}=await import("@stacksjs/desktop-build"),entry=resolveDesktopLauncher(projectPath()),minify=process.env.DESKTOP_MINIFY!=="false",payload=buildUniversalPayload({directory:universalDir,launcherCompileArgs:(architecture,outfile)=>launcherSliceCompileArgs(desktopLauncherCompileArgs({entry,outfile,minify}),architecture),craft:{arm64:config.craftArm64,x64:config.craftX64}});launcher=payload.launcher;runtime=payload.runtime;architectures=payload.architectures;craftSlices={arm64:config.craftArm64,x64:config.craftX64}}const appleDir=join(desktopDist,"apple"),appPath=join(appleDir,`${config.appName}.app`),contents=join(appPath,"Contents"),macosDir=join(contents,"MacOS"),resourcesDir=join(contents,"Resources");if(existsSync(appleDir))rmSync(appleDir,{recursive:!0});mkdirSync(macosDir,{recursive:!0});mkdirSync(resourcesDir,{recursive:!0});copyFileSync(launcher,join(macosDir,"stacks-desktop"));copyFileSync(runtime,join(macosDir,"craft-runtime"));if(config.universal){const parsed=JSON.parse(readFileSync(manifest,"utf8"));writeFileSync(join(resourcesDir,"desktop.json"),`${JSON.stringify({...parsed,architecture:"universal"},null,2)}
97
+ `}function command(args,cwd=projectPath()){const result=Bun.spawnSync(args,{cwd,stdin:"inherit",stdout:"inherit",stderr:"inherit"});if(result.exitCode!==0)throw Error(`${args[0]} ${args.slice(1).join(" ")} exited with code ${result.exitCode}`)}export function signingPlan(input){const sign=(entitlements,target)=>["codesign","--force","--timestamp","--options","runtime","--entitlements",entitlements,...input.keychain?["--keychain",input.keychain]:[],"--sign",input.identity,target];return[sign(input.helperEntitlements,input.helperPath),sign(input.appEntitlements,input.appPath)]}export const captureCommand=(args)=>{const result=Bun.spawnSync(args,{cwd:projectPath(),stdin:"inherit",stdout:"pipe",stderr:"inherit"});if(result.exitCode!==0)throw Error(`${args[0]} ${args.slice(1).join(" ")} exited with code ${result.exitCode}`);return result.stdout.toString()},MACH_O_ARCHITECTURE={arm64:"arm64",x64:"x86_64"};export function parseLipoArchs(output){return output.trim().split(/\s+/).filter(Boolean).sort()}export function mergeUniversalBinary(input,run=captureCommand){for(const architecture of["arm64","x64"]){const expected=MACH_O_ARCHITECTURE[architecture],found=parseLipoArchs(run(["lipo","-archs",input[architecture]]));if(found.length!==1||found[0]!==expected)throw Error(`${input.name}: the ${architecture} input ${input[architecture]} contains ${found.join(" + ")||"no architectures"}, expected only ${expected}`)}run(["lipo","-create","-output",input.output,input.arm64,input.x64]);const merged=parseLipoArchs(run(["lipo","-archs",input.output])),expected=Object.values(MACH_O_ARCHITECTURE).sort();if(merged.join(" ")!==expected.join(" "))throw Error(`${input.name}: the universal binary ${input.output} contains ${merged.join(" + ")||"no architectures"}, expected ${expected.join(" + ")}`);return merged}export function buildUniversalPayload(input,run=captureCommand){const slice=(architecture)=>join(input.directory,architecture,"stacks-desktop");for(const architecture of["arm64","x64"]){mkdirSync(join(input.directory,architecture),{recursive:!0});run(input.launcherCompileArgs(architecture,slice(architecture)))}const launcher=join(input.directory,"stacks-desktop"),runtime=join(input.directory,"craft-runtime");mergeUniversalBinary({name:"stacks-desktop",arm64:slice("arm64"),x64:slice("x64"),output:launcher},run);const architectures=mergeUniversalBinary({name:"craft-runtime",arm64:input.craft.arm64,x64:input.craft.x64,output:runtime},run);return{launcher,runtime,architectures}}export function launcherSliceCompileArgs(base,architecture){return["bun",...base,`--target=bun-darwin-${architecture}`]}function signingIdentityExists(identity,keychain){if(!identity)return!1;const result=Bun.spawnSync(["security","find-identity","-v",...keychain?[keychain]:[]],{stdout:"pipe",stderr:"pipe"});return result.exitCode===0&&result.stdout.toString().includes(identity)}function provisioningProfileMatches(config){if(!config.provisioningProfile||!existsSync(config.provisioningProfile))return!1;const result=Bun.spawnSync(["security","cms","-D","-i",config.provisioningProfile],{stdout:"pipe",stderr:"pipe"});if(result.exitCode!==0)return!1;const profile=result.stdout.toString();return profile.includes(config.bundleId)&&profile.includes(config.teamId)}async function buildDesktop(){const{runAction}=await import("@stacksjs/actions"),{Action}=await import("@stacksjs/enums"),result=await runAction(Action.BuildDesktop);if(resultFailed(result))throw result.error}function sha256(path){return createHash("sha256").update(readFileSync(path)).digest("hex")}async function packageAppleDesktop(config,skipBuild=!1,native=null){const errors=validateAppleDesktopConfig(config,!1);if(config.keychain&&!existsSync(config.keychain))errors.push(`APPLE_KEYCHAIN does not exist: ${config.keychain}`);const where=config.keychain?` in ${config.keychain}`:"";if(!signingIdentityExists(config.appSigningIdentity,config.keychain))errors.push(`App signing identity is not installed${where}: ${config.appSigningIdentity}`);if(!signingIdentityExists(config.installerSigningIdentity,config.keychain))errors.push(`Installer signing identity is not installed${where}: ${config.installerSigningIdentity}`);if(!provisioningProfileMatches(config))errors.push(`Provisioning profile does not match ${config.teamId}.${config.bundleId}`);if(native&&config.universal)errors.push("--universal is not supported for a native macOS app yet: the AppKit host is built for this machine's architecture");if(errors.length)throw Error(errors.join(`
98
+ `));if(native)return packageNativeApple(config,native);if(!skipBuild)await buildDesktop();const desktopDist=storagePath("framework/desktop-dist");let launcher=join(desktopDist,"stacks-desktop"),runtime=join(desktopDist,"craft-runtime");const manifest=join(desktopDist,"desktop.json");for(const path of[launcher,runtime,manifest])if(!existsSync(path))throw Error(`Desktop build artifact is missing: ${path}`);let architectures=[process.arch==="x64"?"x86_64":process.arch],craftSlices;if(config.universal){const universalDir=join(desktopDist,"universal");if(existsSync(universalDir))rmSync(universalDir,{recursive:!0});const{desktopLauncherCompileArgs,resolveDesktopLauncher}=await import("@stacksjs/desktop-build"),entry=resolveDesktopLauncher(projectPath()),minify=process.env.DESKTOP_MINIFY!=="false",payload=buildUniversalPayload({directory:universalDir,launcherCompileArgs:(architecture,outfile)=>launcherSliceCompileArgs(desktopLauncherCompileArgs({entry,outfile,minify}),architecture),craft:{arm64:config.craftArm64,x64:config.craftX64}});launcher=payload.launcher;runtime=payload.runtime;architectures=payload.architectures;craftSlices={arm64:config.craftArm64,x64:config.craftX64}}const appleDir=join(desktopDist,"apple"),appPath=join(appleDir,`${config.appName}.app`),contents=join(appPath,"Contents"),macosDir=join(contents,"MacOS"),resourcesDir=join(contents,"Resources");if(existsSync(appleDir))rmSync(appleDir,{recursive:!0});mkdirSync(macosDir,{recursive:!0});mkdirSync(resourcesDir,{recursive:!0});copyFileSync(launcher,join(macosDir,"stacks-desktop"));copyFileSync(runtime,join(macosDir,"craft-runtime"));if(config.universal){const parsed=JSON.parse(readFileSync(manifest,"utf8"));writeFileSync(join(resourcesDir,"desktop.json"),`${JSON.stringify({...parsed,architecture:"universal"},null,2)}
99
99
  `)}else copyFileSync(manifest,join(resourcesDir,"desktop.json"));copyFileSync(config.provisioningProfile,join(contents,"embedded.provisionprofile"));chmodSync(join(macosDir,"stacks-desktop"),493);chmodSync(join(macosDir,"craft-runtime"),493);if(config.icon)copyFileSync(resolve(config.icon),join(resourcesDir,"AppIcon.icns"));const infoPlist=join(contents,"Info.plist"),appEntitlements=join(appleDir,"app.entitlements"),helperEntitlements=join(appleDir,"helper.entitlements");writeFileSync(infoPlist,renderInfoPlist(config));writeFileSync(appEntitlements,renderAppEntitlements(config));writeFileSync(helperEntitlements,renderHelperEntitlements());command(["plutil","-lint",infoPlist,appEntitlements,helperEntitlements]);for(const args of signingPlan({identity:config.appSigningIdentity,appPath,helperPath:join(macosDir,"craft-runtime"),appEntitlements,helperEntitlements,keychain:config.keychain}))command(args);command(["codesign","--verify","--deep","--strict","--verbose=2",appPath]);command(["codesign","-d","--entitlements","-","--xml",appPath]);const findings=storeRejectionFindings(bundleFiles(appPath));if(findings.length>0)throw Error(`The app bundle would be rejected by App Review:
100
100
  ${findings.map((finding)=>` - ${finding}`).join(`
101
- `)}`);const packagePath=join(appleDir,`${config.appName}-${config.version}-${config.buildNumber}.pkg`);command(["productbuild","--component",appPath,"/Applications",...config.keychain?["--keychain",config.keychain]:[],"--sign",config.installerSigningIdentity,packagePath]);command(["pkgutil","--check-signature",packagePath]);const packageHash=sha256(packagePath);writeFileSync(join(appleDir,"checksums.sha256"),`${packageHash} ${basename(packagePath)}
102
- `);writeFileSync(join(appleDir,"apple-provenance.json"),`${JSON.stringify({schemaVersion:"1.0.0",appName:config.appName,bundleId:config.bundleId,teamId:config.teamId,version:config.version,buildNumber:config.buildNumber,minimumMacos:config.minimumMacos,architectures,package:{name:basename(packagePath),sha256:packageHash},sourceRevision:Bun.spawnSync(["git","rev-parse","HEAD"],{cwd:projectPath()}).stdout.toString().trim(),craft:{sha256:sha256(join(macosDir,"craft-runtime")),...craftSlices&&{slices:{arm64:sha256(craftSlices.arm64),x64:sha256(craftSlices.x64)}}}},null,2)}
101
+ `)}`);return finishPackage(config,appPath,appleDir,{architectures,craft:{sha256:sha256(join(macosDir,"craft-runtime")),...craftSlices&&{slices:{arm64:sha256(craftSlices.arm64),x64:sha256(craftSlices.x64)}}}})}async function packageNativeApple(config,native){const{prepareMacosAgent,prepareMacosNative}=await import("@stacksjs/actions"),{assembleMacosApp}=await import("@stacksjs/desktop-build"),store={...native,appName:config.appName,executable:config.appName,bundleId:config.bundleId,teamId:config.teamId,version:config.version,buildNumber:config.buildNumber,minimumSystemVersion:config.minimumMacos,category:config.category,...config.icon?{icon:config.icon}:{},sandbox:!0},prepared=await prepareMacosNative(store),agent=store.agent?await prepareMacosAgent(store,prepared,"app-store"):void 0,appleDir=join(storagePath("framework/desktop/macos"),"apple");if(existsSync(appleDir))rmSync(appleDir,{recursive:!0});mkdirSync(appleDir,{recursive:!0});const assembled=assembleMacosApp({config:store,root:projectPath(),host:prepared.host,nativeBundle:prepared.nativeBundle,output:appleDir,distribution:"app-store",provisioningProfile:config.provisioningProfile,signingIdentity:config.appSigningIdentity,keychain:config.keychain,agent});for(const note of assembled.notes)await log.warn(note);command(["codesign","-d","--entitlements","-","--xml",assembled.appPath]);const findings=storeRejectionFindings(bundleFiles(assembled.appPath),store.executable);if(findings.length>0)throw Error(`The app bundle would be rejected by App Review:
102
+ ${findings.map((finding)=>` - ${finding}`).join(`
103
+ `)}`);return finishPackage(config,assembled.appPath,appleDir,{architectures:[process.arch==="x64"?"x86_64":process.arch],craft:{kind:"native-appkit",hostKey:prepared.hostKey,sha256:sha256(prepared.host)}})}function finishPackage(config,appPath,appleDir,provenance){const packagePath=join(appleDir,`${config.appName}-${config.version}-${config.buildNumber}.pkg`);command(["productbuild","--component",appPath,"/Applications",...config.keychain?["--keychain",config.keychain]:[],"--sign",config.installerSigningIdentity,packagePath]);command(["pkgutil","--check-signature",packagePath]);const packageHash=sha256(packagePath);writeFileSync(join(appleDir,"checksums.sha256"),`${packageHash} ${basename(packagePath)}
104
+ `);writeFileSync(join(appleDir,"apple-provenance.json"),`${JSON.stringify({schemaVersion:"1.0.0",appName:config.appName,bundleId:config.bundleId,teamId:config.teamId,version:config.version,buildNumber:config.buildNumber,minimumMacos:config.minimumMacos,architectures:provenance.architectures,package:{name:basename(packagePath),sha256:packageHash},sourceRevision:Bun.spawnSync(["git","rev-parse","HEAD"],{cwd:projectPath()}).stdout.toString().trim(),craft:provenance.craft},null,2)}
103
105
  `);return packagePath}function validateOrUpload(packagePath,config,validateOnly){const errors=validateAppleDesktopConfig(config,!0);if(errors.length)throw Error(errors.join(`
104
106
  `));const common=["--type","macos","--file",packagePath,"--apiKey",config.apiKeyId,"--apiIssuer",config.apiIssuerId];process.env.API_PRIVATE_KEYS_DIR=resolve(config.apiKeyPath,"..");command(["xcrun","altool","--validate-app",...common]);if(!validateOnly)command(["xcrun","altool","--upload-app",...common])}export function renderFailure(error){return`${error instanceof Error?error.message:String(error)}
105
- `}function fail(error){process.stderr.write(renderFailure(error));process.exit(1)}export function desktopApple(buddy){buddy.command("desktop:apple:csr","Generate local private keys and CSRs for Mac App Store distribution certificates").option("--common-name <name>","Certificate request common name").option("--output <path>","Directory for private keys and certificate requests").action(async(options)=>{try{const metadata=packageMetadata(),outputDirectory=resolve(options.output||storagePath("framework/desktop-dist/apple/provisioning")),{generateMacCertificateRequests}=await import("ts-pantry");generateMacCertificateRequests({outputDirectory,commonName:options.commonName||metadata.name});log.success(`Generated Apple certificate requests and private keys in ${outputDirectory}`)}catch(error){fail(error)}});buddy.command("desktop:apple:provision","Plan or reconcile Apple Bundle ID, capabilities, certificates, and profile").option("--app-name <name>","Mac App Store display name").option("--bundle-id <id>","Reverse-DNS bundle identifier").option("--api-key-id <id>","App Store Connect API key ID").option("--api-issuer-id <id>","App Store Connect API issuer ID").option("--api-key-path <path>","App Store Connect AuthKey .p8 file").option("--capabilities <types>","Comma-separated Apple capability types").option("--app-certificate-csr <path>","CSR for a missing Mac App Distribution certificate").option("--installer-certificate-csr <path>","CSR for a missing Mac Installer Distribution certificate").option("--profile-name <name>","Provisioning profile name").option("--output <path>","Directory for the plan and downloaded Apple assets").option("--plan","Report the idempotent Apple resource diff without mutating it").option("--apply","Apply the Apple resource diff without revoking existing certificates").action(async(options)=>{try{if(options.plan&&options.apply)throw Error("Choose either --plan or --apply");const config=resolveAppleDesktopConfig(options);if(!config.bundleId)throw Error("APPLE_BUNDLE_ID or --bundle-id is required");if(!config.apiKeyId||!config.apiIssuerId||!config.apiKeyPath||!existsSync(config.apiKeyPath))throw Error("App Store Connect API key ID, issuer ID, and existing .p8 key path are required");const readCsr=(file)=>{if(!file)return;const resolved=resolve(file);if(!existsSync(resolved))throw Error(`Apple certificate CSR does not exist: ${resolved}`);return readFileSync(resolved,"utf8")},{exportAppleCertificateP12,provisionMacApp}=await import("ts-pantry"),result=await provisionMacApp({identifier:config.bundleId,name:config.appName,capabilities:options.capabilities?.split(",").map((value)=>value.trim()).filter(Boolean),appCertificateCsr:readCsr(options.appCertificateCsr),installerCertificateCsr:readCsr(options.installerCertificateCsr),profileName:options.profileName,keyId:config.apiKeyId,issuerId:config.apiIssuerId,keyPath:config.apiKeyPath,checkOnly:!options.apply}),outputDirectory=resolve(options.output||storagePath("framework/desktop-dist/apple/provisioning"));mkdirSync(outputDirectory,{recursive:!0});for(const certificate of result.certificates){if(!certificate.certificateContent)continue;const fileName=certificate.type==="MAC_APP_DISTRIBUTION"?"mac-app-distribution.cer":"mac-installer-distribution.cer";writeFileSync(join(outputDirectory,fileName),Buffer.from(certificate.certificateContent,"base64"),{mode:384})}if(result.profile.profileContent)writeFileSync(join(outputDirectory,"mac-app-store.provisionprofile"),Buffer.from(result.profile.profileContent,"base64"),{mode:384});const certificatePassword=env("APPLE_CERTIFICATE_PASSWORD"),certificateExports=[{type:"MAC_APP_DISTRIBUTION",csr:options.appCertificateCsr,certificate:"mac-app-distribution.cer",output:"mac-app-distribution.p12",name:`${config.appName} Mac App Distribution`},{type:"MAC_INSTALLER_DISTRIBUTION",csr:options.installerCertificateCsr,certificate:"mac-installer-distribution.cer",output:"mac-installer-distribution.p12",name:`${config.appName} Mac Installer Distribution`}];for(const certificateExport of certificateExports){if(!result.certificates.find((item)=>item.type===certificateExport.type)?.certificateContent||!certificateExport.csr||!certificatePassword)continue;exportAppleCertificateP12({certificatePath:join(outputDirectory,certificateExport.certificate),privateKeyPath:resolve(certificateExport.csr.replace(/\.csr$/i,".key")),outputPath:join(outputDirectory,certificateExport.output),password:certificatePassword,name:certificateExport.name})}const certificates=result.certificates.map((item)=>{const certificate={...item};delete certificate.certificateContent;return certificate}),profile={...result.profile};delete profile.profileContent;const report={...result,certificates,profile},reportPath=join(outputDirectory,"provisioning-plan.json");writeFileSync(reportPath,`${JSON.stringify(report,null,2)}
106
- `,{mode:384});log.success(`${options.apply?"Applied":"Planned"} Apple provisioning: ${reportPath}`);if(!result.appRecord.exists&&result.appRecord.manualAction)log.warn(result.appRecord.manualAction)}catch(error){fail(error)}});buddy.command("desktop:apple:doctor","Validate Mac App Store tooling, credentials, certificates, and project metadata").option("--app-name <name>","Mac App Store display name").option("--bundle-id <id>","Reverse-DNS bundle identifier").option("--team-id <id>","Apple Developer team ID").option("--app-signing-identity <identity>","Mac App Distribution signing identity").option("--installer-signing-identity <identity>","Mac Installer Distribution signing identity").option("--provisioning-profile <path>","Mac App Store provisioning profile").option("--api-key-id <id>","App Store Connect API key ID").option("--api-issuer-id <id>","App Store Connect API issuer ID").option("--api-key-path <path>","App Store Connect AuthKey .p8 file").option("--keychain <path>","Look for the signing identities in this keychain file only (or APPLE_KEYCHAIN)").action((options)=>{try{const config=resolveAppleDesktopConfig(options),errors=validateAppleDesktopConfig(config,!0);if(!signingIdentityExists(config.appSigningIdentity,config.keychain))errors.push(`App signing identity is not installed: ${config.appSigningIdentity}`);if(!signingIdentityExists(config.installerSigningIdentity,config.keychain))errors.push(`Installer signing identity is not installed: ${config.installerSigningIdentity}`);if(!provisioningProfileMatches(config))errors.push(`Provisioning profile does not match ${config.teamId}.${config.bundleId}`);if(errors.length)throw Error(errors.join(`
107
- `));log.success(`Mac App Store prerequisites are ready for ${config.bundleId}`)}catch(error){fail(error)}});buddy.command("desktop:apple:init","Create a GitHub Actions caller for the reusable Stacks Mac App Store workflow").option("--force","Replace an existing workflow").action((options)=>{try{const workflowPath=projectPath(".github/workflows/apple-app-store.yml");if(existsSync(workflowPath)&&!options.force)throw Error(`${workflowPath} already exists. Use --force to replace it.`);mkdirSync(resolve(workflowPath,".."),{recursive:!0});writeFileSync(workflowPath,renderAppleWorkflowCaller());log.success(`Created ${workflowPath}`)}catch(error){fail(error)}});const addSharedOptions=(commandBuilder)=>commandBuilder.option("--app-name <name>","Mac App Store display name").option("--bundle-id <id>","Reverse-DNS bundle identifier").option("--team-id <id>","Apple Developer team ID").option("--app-version <version>","Marketing version").option("--build-number <number>","Unique App Store build number").option("--minimum-macos <version>","Minimum supported macOS version").option("--category <category>","LSApplicationCategoryType value").option("--app-signing-identity <identity>","Mac App Distribution signing identity").option("--installer-signing-identity <identity>","Mac Installer Distribution signing identity").option("--provisioning-profile <path>","Mac App Store provisioning profile").option("--icon <path>","Optional .icns app icon").option("--skip-build","Package existing storage/framework/desktop-dist artifacts").option("--universal","Ship one arm64 + x86_64 binary, merged with lipo").option("--craft-arm64 <path>","Thin arm64 Craft runtime for --universal (or CRAFT_BIN_ARM64)").option("--craft-x64 <path>","Thin x86_64 Craft runtime for --universal (or CRAFT_BIN_X64)").option("--keychain <path>","Sign with identities from this keychain file only (or APPLE_KEYCHAIN)");addSharedOptions(buddy.command("desktop:apple:package","Build, sandbox, sign, and package a Mac App Store desktop app")).action(async(options)=>{try{const packagePath=await packageAppleDesktop(resolveAppleDesktopConfig(options),Boolean(options.skipBuild));log.success(`Created signed Mac App Store package ${packagePath}`)}catch(error){fail(error)}});addSharedOptions(buddy.command("desktop:apple:publish","Build and validate or upload a signed Mac App Store package")).option("--api-key-id <id>","App Store Connect API key ID").option("--api-issuer-id <id>","App Store Connect API issuer ID").option("--api-key-path <path>","App Store Connect AuthKey .p8 file").option("--validate-only","Validate with App Store Connect without uploading").option("--package-only","Create the signed package without contacting App Store Connect").action(async(options)=>{try{const config=resolveAppleDesktopConfig(options),packagePath=await packageAppleDesktop(config,Boolean(options.skipBuild));if(!options.packageOnly)validateOrUpload(packagePath,config,Boolean(options.validateOnly));log.success(options.packageOnly?`Created signed Mac App Store package ${packagePath}`:options.validateOnly?`Validated ${packagePath} with App Store Connect`:`Uploaded ${packagePath} to App Store Connect`)}catch(error){fail(error)}})}
107
+ `}function fail(error){process.stderr.write(renderFailure(error));process.exit(1)}export function desktopApple(buddy){buddy.command("desktop:apple:csr","Generate local private keys and CSRs for Mac App Store distribution certificates").option("--common-name <name>","Certificate request common name").option("--output <path>","Directory for private keys and certificate requests").action(async(options)=>{try{const metadata=packageMetadata(),outputDirectory=resolve(options.output||storagePath("framework/desktop-dist/apple/provisioning")),{generateMacCertificateRequests}=await import("ts-pantry");generateMacCertificateRequests({outputDirectory,commonName:options.commonName||metadata.name});log.success(`Generated Apple certificate requests and private keys in ${outputDirectory}`)}catch(error){fail(error)}});buddy.command("desktop:apple:provision","Plan or reconcile Apple Bundle ID, capabilities, certificates, and profile").option("--app-name <name>","Mac App Store display name").option("--bundle-id <id>","Reverse-DNS bundle identifier").option("--api-key-id <id>","App Store Connect API key ID").option("--api-issuer-id <id>","App Store Connect API issuer ID").option("--api-key-path <path>","App Store Connect AuthKey .p8 file").option("--capabilities <types>","Comma-separated Apple capability types").option("--app-certificate-csr <path>","CSR for a missing Mac App Distribution certificate").option("--installer-certificate-csr <path>","CSR for a missing Mac Installer Distribution certificate").option("--profile-name <name>","Provisioning profile name").option("--output <path>","Directory for the plan and downloaded Apple assets").option("--plan","Report the idempotent Apple resource diff without mutating it").option("--apply","Apply the Apple resource diff without revoking existing certificates").action(async(options)=>{try{if(options.plan&&options.apply)throw Error("Choose either --plan or --apply");const config=resolveAppleDesktopConfig(options,await nativeAppleConfig());if(!config.bundleId)throw Error("APPLE_BUNDLE_ID or --bundle-id is required");if(!config.apiKeyId||!config.apiIssuerId||!config.apiKeyPath||!existsSync(config.apiKeyPath))throw Error("App Store Connect API key ID, issuer ID, and existing .p8 key path are required");const readCsr=(file)=>{if(!file)return;const resolved=resolve(file);if(!existsSync(resolved))throw Error(`Apple certificate CSR does not exist: ${resolved}`);return readFileSync(resolved,"utf8")},{exportAppleCertificateP12,provisionMacApp}=await import("ts-pantry"),result=await provisionMacApp({identifier:config.bundleId,name:config.appName,capabilities:options.capabilities?.split(",").map((value)=>value.trim()).filter(Boolean),appCertificateCsr:readCsr(options.appCertificateCsr),installerCertificateCsr:readCsr(options.installerCertificateCsr),profileName:options.profileName,keyId:config.apiKeyId,issuerId:config.apiIssuerId,keyPath:config.apiKeyPath,checkOnly:!options.apply}),outputDirectory=resolve(options.output||storagePath("framework/desktop-dist/apple/provisioning"));mkdirSync(outputDirectory,{recursive:!0});for(const certificate of result.certificates){if(!certificate.certificateContent)continue;const fileName=certificate.type==="MAC_APP_DISTRIBUTION"?"mac-app-distribution.cer":"mac-installer-distribution.cer";writeFileSync(join(outputDirectory,fileName),Buffer.from(certificate.certificateContent,"base64"),{mode:384})}if(result.profile.profileContent)writeFileSync(join(outputDirectory,"mac-app-store.provisionprofile"),Buffer.from(result.profile.profileContent,"base64"),{mode:384});const certificatePassword=env("APPLE_CERTIFICATE_PASSWORD"),certificateExports=[{type:"MAC_APP_DISTRIBUTION",csr:options.appCertificateCsr,certificate:"mac-app-distribution.cer",output:"mac-app-distribution.p12",name:`${config.appName} Mac App Distribution`},{type:"MAC_INSTALLER_DISTRIBUTION",csr:options.installerCertificateCsr,certificate:"mac-installer-distribution.cer",output:"mac-installer-distribution.p12",name:`${config.appName} Mac Installer Distribution`}];for(const certificateExport of certificateExports){if(!result.certificates.find((item)=>item.type===certificateExport.type)?.certificateContent||!certificateExport.csr||!certificatePassword)continue;exportAppleCertificateP12({certificatePath:join(outputDirectory,certificateExport.certificate),privateKeyPath:resolve(certificateExport.csr.replace(/\.csr$/i,".key")),outputPath:join(outputDirectory,certificateExport.output),password:certificatePassword,name:certificateExport.name})}const certificates=result.certificates.map((item)=>{const certificate={...item};delete certificate.certificateContent;return certificate}),profile={...result.profile};delete profile.profileContent;const report={...result,certificates,profile},reportPath=join(outputDirectory,"provisioning-plan.json");writeFileSync(reportPath,`${JSON.stringify(report,null,2)}
108
+ `,{mode:384});log.success(`${options.apply?"Applied":"Planned"} Apple provisioning: ${reportPath}`);if(!result.appRecord.exists&&result.appRecord.manualAction)log.warn(result.appRecord.manualAction)}catch(error){fail(error)}});buddy.command("desktop:apple:doctor","Validate Mac App Store tooling, credentials, certificates, and project metadata").option("--app-name <name>","Mac App Store display name").option("--bundle-id <id>","Reverse-DNS bundle identifier").option("--team-id <id>","Apple Developer team ID").option("--app-signing-identity <identity>","Mac App Distribution signing identity").option("--installer-signing-identity <identity>","Mac Installer Distribution signing identity").option("--provisioning-profile <path>","Mac App Store provisioning profile").option("--api-key-id <id>","App Store Connect API key ID").option("--api-issuer-id <id>","App Store Connect API issuer ID").option("--api-key-path <path>","App Store Connect AuthKey .p8 file").option("--keychain <path>","Look for the signing identities in this keychain file only (or APPLE_KEYCHAIN)").action(async(options)=>{try{const config=resolveAppleDesktopConfig(options,await nativeAppleConfig()),errors=validateAppleDesktopConfig(config,!0);if(!signingIdentityExists(config.appSigningIdentity,config.keychain))errors.push(`App signing identity is not installed: ${config.appSigningIdentity}`);if(!signingIdentityExists(config.installerSigningIdentity,config.keychain))errors.push(`Installer signing identity is not installed: ${config.installerSigningIdentity}`);if(!provisioningProfileMatches(config))errors.push(`Provisioning profile does not match ${config.teamId}.${config.bundleId}`);if(errors.length)throw Error(errors.join(`
109
+ `));log.success(`Mac App Store prerequisites are ready for ${config.bundleId}`)}catch(error){fail(error)}});buddy.command("desktop:apple:init","Create a GitHub Actions caller for the reusable Stacks Mac App Store workflow").option("--force","Replace an existing workflow").action((options)=>{try{const workflowPath=projectPath(".github/workflows/apple-app-store.yml");if(existsSync(workflowPath)&&!options.force)throw Error(`${workflowPath} already exists. Use --force to replace it.`);mkdirSync(resolve(workflowPath,".."),{recursive:!0});writeFileSync(workflowPath,renderAppleWorkflowCaller());log.success(`Created ${workflowPath}`)}catch(error){fail(error)}});const addSharedOptions=(commandBuilder)=>commandBuilder.option("--app-name <name>","Mac App Store display name").option("--bundle-id <id>","Reverse-DNS bundle identifier").option("--team-id <id>","Apple Developer team ID").option("--app-version <version>","Marketing version").option("--build-number <number>","Unique App Store build number").option("--minimum-macos <version>","Minimum supported macOS version").option("--category <category>","LSApplicationCategoryType value").option("--app-signing-identity <identity>","Mac App Distribution signing identity").option("--installer-signing-identity <identity>","Mac Installer Distribution signing identity").option("--provisioning-profile <path>","Mac App Store provisioning profile").option("--icon <path>","Optional .icns app icon").option("--skip-build","Package existing storage/framework/desktop-dist artifacts").option("--universal","Ship one arm64 + x86_64 binary, merged with lipo").option("--craft-arm64 <path>","Thin arm64 Craft runtime for --universal (or CRAFT_BIN_ARM64)").option("--craft-x64 <path>","Thin x86_64 Craft runtime for --universal (or CRAFT_BIN_X64)").option("--keychain <path>","Sign with identities from this keychain file only (or APPLE_KEYCHAIN)");addSharedOptions(buddy.command("desktop:apple:package","Build, sandbox, sign, and package a Mac App Store desktop app")).action(async(options)=>{try{const native=await nativeAppleConfig(),packagePath=await packageAppleDesktop(resolveAppleDesktopConfig(options,native),Boolean(options.skipBuild),native);log.success(`Created signed Mac App Store package ${packagePath}`)}catch(error){fail(error)}});addSharedOptions(buddy.command("desktop:apple:publish","Build and validate or upload a signed Mac App Store package")).option("--api-key-id <id>","App Store Connect API key ID").option("--api-issuer-id <id>","App Store Connect API issuer ID").option("--api-key-path <path>","App Store Connect AuthKey .p8 file").option("--validate-only","Validate with App Store Connect without uploading").option("--package-only","Create the signed package without contacting App Store Connect").action(async(options)=>{try{const native=await nativeAppleConfig(),config=resolveAppleDesktopConfig(options,native),packagePath=await packageAppleDesktop(config,Boolean(options.skipBuild),native);if(!options.packageOnly)validateOrUpload(packagePath,config,Boolean(options.validateOnly));log.success(options.packageOnly?`Created signed Mac App Store package ${packagePath}`:options.validateOnly?`Validated ${packagePath} with App Store Connect`:`Uploaded ${packagePath} to App Store Connect`)}catch(error){fail(error)}})}
@@ -1,4 +1,4 @@
1
- import{execSync}from"node:child_process";import{existsSync,readdirSync,readFileSync}from"node:fs";import readline from"node:readline";import process from"node:process";import{bold,cyan,dim,green,intro,log,onUnknownSubcommand,outro,prompts,runCommand,yellow}from"@stacksjs/cli";import{homedir}from"node:os";import{dirname,join,relative}from"node:path";import{fileURLToPath}from"node:url";import{Action}from"@stacksjs/enums";import{inspectDefaultsProvenance,libsPath,projectPath,stxPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{version}from"../../package.json";import{resultFailed}from"../result";const DEV_BOOT_STARTING_LINE_COUNT=3;function eraseDevBootStartingLines(progressLines=0){if(!process.stdout.isTTY)return;for(let i=0;i<DEV_BOOT_STARTING_LINE_COUNT+progressLines;i++){readline.moveCursor(process.stdout,0,-1);readline.clearLine(process.stdout,0)}}export const DEV_BOOT_PROGRESS_EVERY_SECONDS=10;const DEV_BOOT_VERBOSE_HINT_SECONDS=30;export function devBootProgressLine(elapsedSeconds,waitingOn,verbose=!1){if(!waitingOn.length||elapsedSeconds<DEV_BOOT_PROGRESS_EVERY_SECONDS)return null;const servers=waitingOn.map((server)=>`the ${server.name} on :${server.port}`).join(", "),line=`still starting, ${Math.floor(elapsedSeconds)}s so far, waiting for ${servers}`;return!verbose&&elapsedSeconds>=DEV_BOOT_VERBOSE_HINT_SECONDS?`${line} (\`./buddy dev --verbose\` shows its output)`:line}let developmentRpx;function resolveRpxEntryPath(){return[projectPath("node_modules/@stacksjs/rpx/dist/index.js"),join(homedir(),"Code/Tools/rpx/packages/rpx/dist/index.js"),projectPath("pantry/@stacksjs/rpx/dist/src/index.js")].find((entry)=>existsSync(entry))??null}async function importDevelopmentRpx(){if(developmentRpx)return developmentRpx;const entry=resolveRpxEntryPath();developmentRpx=entry?await import(entry):await import("@stacksjs/rpx");return developmentRpx}const activeRpxRegistryIds=[];let _actions;async function actions(){if(!_actions)_actions=await import("@stacksjs/actions");return _actions}async function checkFrameworkDefaults(){let skew;try{skew=inspectDefaultsProvenance(projectPath())}catch{return}if(skew.status==="not-applicable"||skew.status==="current")return;if(skew.status==="stale")throw Error(`Framework defaults version mismatch: storage/framework/defaults is from ${skew.vendored}, but @stacksjs/defaults ${skew.installed} is installed. Run \`buddy upgrade\` to synchronize them before starting development servers.`);try{const{measureDefaultsDrift}=await actions(),drift=measureDefaultsDrift(projectPath(),{shallow:!0});if(!drift||drift.length===0)return;log.warn(`storage/framework/defaults does not match the installed @stacksjs/defaults ${skew.installed}.`);log.warn("The app runs the vendored copy. Run `buddy upgrade` to sync it, or `buddy doctor` for the file counts.")}catch{}}export const interactiveDevChoices=[{value:"all",title:"All"},{value:"frontend",title:"Frontend"},{value:"api",title:"Backend"},{value:"dashboard",title:"Dashboard"},{value:"desktop",title:"Desktop"},{value:"native",title:"Native App"},{value:"components",title:"Components"},{value:"docs",title:"Documentation"}];export async function dispatchInteractiveDevSelection(selection,runners){if(!Object.hasOwn(runners,selection))return!1;await runners[selection]();return!0}export function resolvePrettyDevDomain(appUrl,nativeMode=!1){if(!appUrl||nativeMode)return null;try{const hostname=new URL(/^https?:\/\//i.test(appUrl)?appUrl:`https://${appUrl}`).hostname.toLowerCase();if(hostname==="localhost"||hostname==="127.0.0.1"||hostname==="::1"||hostname==="0.0.0.0")return null;return hostname}catch{return null}}export function shouldUsePrettyDevUrls(input){return input.domain!==null&&!input.localhostOnly&&(input.proxyManagedExternally||input.systemAuthorized)}const APPLICATION_VIEW_CANDIDATES=["app/index","app","dashboard/index","dashboard","home/index","home","composer","workspace/index","workspace","feed","portal","admin","account/index","account","profile/index","profile"],SITE_LAYOUT_NAMES=new Set(["default","guest","marketing","site"]);export function normalizeDevelopmentEntryPath(path){const value=path?.trim();if(!value||value==="/")return"/";try{const parsed=new URL(value,"http://stacks.localhost");return`${parsed.pathname}${parsed.search}${parsed.hash}`}catch{return"/"}}function configuredApplicationPath(root){if(process.env.APP_PATH)return process.env.APP_PATH;try{return readFileSync(join(root,"config/app.ts"),"utf8").match(/^[ \t]*appPath\s*:\s*(['"`])([^'"`]+)\1/m)?.[2]}catch{return}}function configuredDevelopmentLaunch(root){const environmentValue=process.env.STACKS_DEV_LAUNCH?.trim().toLowerCase();if(environmentValue==="browser"||environmentValue==="native")return environmentValue;try{const value=readFileSync(join(root,"config/app.ts"),"utf8").match(/\bdevLaunch\s*:\s*(['"`])(browser|native)\1/)?.[2];return value==="browser"||value==="native"?value:void 0}catch{return}}export function resolveDevelopmentLaunch(input={}){if(input.browser||input.site)return"browser";if(input.native)return"native";const root=input.root??projectPath();return input.configuredLaunch??configuredDevelopmentLaunch(root)??"browser"}function viewSource(root,route){const base=join(root,"resources/views",route);for(const extension of["stx","vue","html"]){const path=`${base}.${extension}`;if(existsSync(path))try{return readFileSync(path,"utf8")}catch{return""}}return}function usesApplicationLayout(source){const layout=source.match(/@extends\(\s*['"]layouts\/([^'"]+)['"]\s*\)/)?.[1]??source.match(/@layout\(\s*['"]layouts\/([^'"]+)['"]\s*\)/)?.[1];if(!layout)return!1;return!SITE_LAYOUT_NAMES.has(layout.split("/").at(-1)??layout)}export function resolveDevelopmentEntryPath(input={}){if(input.site)return"/";const root=input.root??projectPath(),configuredPath=input.configuredPath??configuredApplicationPath(root);if(configuredPath)return normalizeDevelopmentEntryPath(configuredPath);const loginExists=viewSource(root,"login")!==void 0||viewSource(root,"auth/login")!==void 0,candidates=APPLICATION_VIEW_CANDIDATES.map((route)=>({route,source:viewSource(root,route)})).filter((candidate)=>candidate.source!==void 0),appCandidate=candidates.find((candidate)=>usesApplicationLayout(candidate.source))??(loginExists?candidates[0]:void 0);if(appCandidate)return normalizeDevelopmentEntryPath(`/${appCandidate.route.replace(/\/index$/,"")}`);return loginExists?"/login":"/"}export function developmentUrl(baseUrl,entryPath){const normalized=normalizeDevelopmentEntryPath(entryPath);if(normalized==="/")return baseUrl.replace(/\/$/,"");return new URL(normalized,`${baseUrl.replace(/\/$/,"")}/`).toString()}export function developmentBrowserCommand(url,platform=process.platform){if(platform==="darwin")return["open",url];if(platform==="win32")return["cmd","/c","start","",url];return["xdg-open",url]}function openDevelopmentBrowser(url){if(!process.stdout.isTTY||process.env.CI||process.env.STACKS_DEV_NO_OPEN==="1")return;try{Bun.spawn(developmentBrowserCommand(url),{stdin:"ignore",stdout:"ignore",stderr:"ignore"}).unref()}catch{}}async function canStartPrettyDevProxy(){if(await waitForHttpsProxy(443,150))return!0;try{const{authorizeSystemAccess}=await importDevelopmentRpx();return authorizeSystemAccess({interactive:!1})}catch{return!1}}export function dev(buddy){const descriptions={dev:"Start development server",frontend:"Start the frontend development server",components:"Start the Components development server",desktop:"Start the Desktop App development server",native:"Start the app in a native Craft window",dashboard:"Start the Dashboard development server",api:"Start the local API development server",docs:"Start the Documentation development server",systemTray:"Start the System Tray development server",interactive:"Get asked which development server to start",select:"Which development server are you trying to start?",withLocalhost:"Include the localhost URL in the output",browser:"Open the application in a browser instead of its configured native window",site:"Open the marketing site instead of the application",project:"Target a specific project",verbose:"Enable verbose output"};buddy.command("dev [server]",descriptions.dev).option("-f, --frontend",descriptions.frontend).option("-a, --api",descriptions.api).option("-c, --components",descriptions.components).option("-d, --dashboard",descriptions.dashboard).option("-k, --desktop",descriptions.desktop).option("-n, --native",descriptions.native).option("-o, --docs",descriptions.docs).option("-s, --system-tray",descriptions.systemTray).option("-i, --interactive",descriptions.interactive,{default:!1}).option("-l, --with-localhost",descriptions.withLocalhost,{default:!1}).option("--browser",descriptions.browser,{default:!1}).option("--site",descriptions.site,{default:!1}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(server,options)=>{const perf=Bun.nanoseconds();process.env.STACKS_DEV_ENTRY_PATH=resolveDevelopmentEntryPath({site:options.site});await checkFrameworkDefaults();const target=server||(options.frontend?"frontend":void 0)||(options.api?"api":void 0)||(options.components?"components":void 0)||(options.dashboard?"dashboard":void 0)||(options.desktop?"desktop":void 0)||(options.native?"native":void 0)||(options.systemTray||options["system-tray"]?"system-tray":void 0)||(options.docs?"docs":void 0);if(target){const serverOptions={...options},a=await actions();switch(target){case"native":await startDevelopmentServer({...serverOptions,native:!0},perf);break;case"frontend":await a.runFrontendDevServer(serverOptions);break;case"api":await a.runApiDevServer(serverOptions);break;case"components":await a.runComponentsDevServer(serverOptions);break;case"dashboard":await a.runDashboardDevServer(serverOptions);break;case"desktop":await startDevelopmentServer({...serverOptions,native:!0},perf);break;case"system-tray":await a.runSystemTrayDevServer(serverOptions);break;case"docs":await a.runDocsDevServer(serverOptions);break;default:await log.error(`Unknown server: ${target}`);process.exit(ExitCode.InvalidArgument)}}else if(wantsInteractive(options)){const selectedValue=await prompts.select({message:descriptions.select,choices:interactiveDevChoices.map((choice)=>({value:choice.value,label:choice.title}))}),a=await actions();if(!await dispatchInteractiveDevSelection(selectedValue,{all:()=>startDevelopmentServer(options,perf),frontend:()=>a.runFrontendDevServer(options),api:()=>a.runApiDevServer(options),dashboard:()=>a.runDashboardDevServer(options),desktop:()=>startDevelopmentServer({...options,native:!0},perf),native:()=>startDevelopmentServer({...options,native:!0},perf),components:()=>a.runComponentsDevServer(options),docs:()=>a.runDocsDevServer(options)})){await log.error("Invalid option during interactive mode");process.exit(ExitCode.InvalidArgument)}}else await startDevelopmentServer(options,perf);outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("dev:components",descriptions.components).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:components"),result=await runCommand("bun run dev",{cwd:libsPath("components/stx")});if(options.verbose)log.info("buddy dev:components result",result);if(resultFailed(result)){await outro("While running the dev:components command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("dev:docs",descriptions.docs).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:docs"),result=await(await actions()).runAction(Action.DevDocs,options);if(resultFailed(result)){await outro("While running the dev:docs command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("dev:native",descriptions.native).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:native");await startDevelopmentServer({...options,native:!0},perf)});buddy.command("dev:desktop",descriptions.desktop).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:desktop");await startDevelopmentServer({...options,native:!0},perf)});buddy.command("dev:api",descriptions.api).option("-p, --project [project]",descriptions.project,{default:!1}).option("--no-watch-types","Skip the model/config type-regeneration watcher").option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const a=await actions();if(options.watchTypes!==!1)a.watchTypes(options).catch((err)=>{log.warn("[dev:api] type watcher exited:",err)});await a.runApiDevServer(options)});buddy.command("dev:frontend",descriptions.frontend).alias("dev:pages").alias("dev:views").option("-p, --project [project]",descriptions.project,{default:!1}).option("--site",descriptions.site,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();process.env.STACKS_DEV_ENTRY_PATH=resolveDevelopmentEntryPath({site:options.site});await(await actions()).runFrontendDevServer(options)});buddy.command("dev:dashboard",descriptions.dashboard).alias("dev:admin").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();await(await actions()).runDashboardDevServer(options)});buddy.command("dev:system-tray",descriptions.systemTray).alias("dev:tray").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();await(await actions()).runSystemTrayDevServer(options)});onUnknownSubcommand(buddy,"dev")}export async function startDevelopmentServer(_options,_startTime){const options=_options,startedAt=_startTime,appUrl=process.env.APP_URL??"stacks.localhost",nativeMode=resolveDevelopmentLaunch({browser:options.browser,native:options.native,site:options.site})==="native",entryPath=resolveDevelopmentEntryPath({site:options.site});process.env.STACKS_DEV_ENTRY_PATH=entryPath;const proxyManagedExternally=process.env.STACKS_PROXY_MANAGED==="1",preferredFrontendPort=Number(process.env.PORT)||3000,preferredApiPort=Number(process.env.PORT_API)||3008,preferredDocsPort=Number(process.env.PORT_DOCS)||3006,preferredDashboardPort=Number(process.env.PORT_ADMIN)||3002;if(process.env.STACKS_DEV_NO_KILL!=="1")await cleanupStaleDevProcesses([preferredFrontendPort,preferredApiPort,preferredDocsPort,preferredDashboardPort]);const portShifts=[],claimPort=async(label,preferred)=>{if(proxyManagedExternally)return preferred;const port=await findAvailablePort(preferred);if(port!==preferred)portShifts.push(`${label} :${preferred} \u2192 :${port}`);return port},frontendPort=await claimPort("Frontend",preferredFrontendPort),apiPort=await claimPort("API",preferredApiPort),docsPort=await claimPort("Docs",preferredDocsPort),dashboardPort=await claimPort("Dashboard",preferredDashboardPort);process.env.PORT=String(frontendPort);process.env.PORT_API=String(apiPort);process.env.PORT_DOCS=String(docsPort);process.env.PORT_ADMIN=String(dashboardPort);const includeDashboard=process.env.STACKS_DEV_DASHBOARD==="1",domain=resolvePrettyDevDomain(appUrl,nativeMode),appLooksCustom=domain!==null,localhostOnly=process.env.STACKS_DEV_LOCALHOST==="1",prettyUrlsRequested=appLooksCustom&&!localhostOnly,systemAuthorized=!prettyUrlsRequested||proxyManagedExternally?!0:await canStartPrettyDevProxy(),usePrettyUrls=shouldUsePrettyDevUrls({domain,localhostOnly,proxyManagedExternally,systemAuthorized}),prettySetupRequired=prettyUrlsRequested&&!usePrettyUrls,hasCustomDomain=usePrettyUrls&&!proxyManagedExternally,displayedDomain=usePrettyUrls?domain:null,dashboardDomain=displayedDomain?`dashboard.${displayedDomain}`:null,frontendBaseUrl=displayedDomain?`https://${displayedDomain}`:`http://localhost:${frontendPort}`,frontendUrl=developmentUrl(frontendBaseUrl,entryPath),apiUrl=displayedDomain?`https://${displayedDomain}/api`:`http://localhost:${apiPort}`,docsUrl=displayedDomain?`https://${displayedDomain}/docs`:`http://localhost:${docsPort}`,dashboardUrl=dashboardDomain?`https://${dashboardDomain}`:`http://localhost:${dashboardPort}`;process.env.STACKS_PROXY_MANAGED="1";process.env.STACKS_DEV_QUIET="1";console.log();if(prettySetupRequired){console.log(` ${yellow("\u26A0")} ${yellow("Pretty URL setup required")} ${dim("- using localhost for this session")}`);console.log(` ${dim(" ")}${dim("Run `./buddy setup:ssl` once, then restart `./buddy dev`.")}`);console.log()}if(portShifts.length>0){console.log(` ${yellow("\u26A0")} ${yellow("Ports in use by another process")} ${dim(`- ${portShifts.join(", ")}`)}`);console.log()}console.log(` ${bold(cyan("stacks"))} ${dim(`v${version}`)} ${dim("starting\u2026")}`);console.log();const rpxTlsPreflight=hasCustomDomain&&domain?prepareRpxTlsForDev({domain,includeDashboard,options}):Promise.resolve();if(localhostOnly&&domain&&!proxyManagedExternally)removeStalePublicDomainOverrides(domain,includeDashboard,options.verbose??!1).catch(()=>{});rpxTlsPreflight.catch(()=>{});let isExiting=!1,closeNativeApp;const SHUTDOWN_GRACE_MS=1500,cleanup=(signal)=>{if(isExiting)return;isExiting=!0;console.log();console.log(` ${bold(cyan("stacks"))} ${dim(`received ${signal} - shutting down the dev server`)}`);if(signal==="SIGHUP")console.log(` ${dim(" ")}${dim("The launching shell went away (a closed terminal, or a CI step that ended). nohup does not prevent this.")}`);closeNativeApp?.();const rpxTeardown=(async()=>{try{await unregisterRpxProxies(activeRpxRegistryIds);activeRpxRegistryIds.length=0;if(hasCustomDomain&&domain)await removeStalePublicDomainOverrides(domain,includeDashboard,options.verbose??!1)}catch{}})(),teardownDeadline=new Promise((resolve)=>setTimeout(resolve,SHUTDOWN_GRACE_MS));Promise.race([rpxTeardown,teardownDeadline]).finally(()=>{try{process.kill(-process.pid,"SIGTERM")}catch{try{process.kill(0,"SIGTERM")}catch{}}setTimeout(()=>{try{process.kill(0,"SIGKILL")}catch{process.exit(1)}},SHUTDOWN_GRACE_MS).unref()})};process.on("SIGINT",cleanup);process.on("SIGTERM",cleanup);process.on("SIGHUP",cleanup);const quietOpts={...options,quiet:!0},a=await actions(),ports=[{name:"API",port:apiPort}],readinessTimeoutMs=30000;let readyAnnounced=!1;const nativeUrl=developmentUrl(`http://localhost:${frontendPort}`,entryPath),nativeWindowReady=nativeMode?waitForPort(frontendPort,readinessTimeoutMs).then(async(ready)=>{if(!ready){log.warn(`Native window skipped because the frontend did not answer within ${readinessTimeoutMs/1000}s`);return}closeNativeApp=await launchNativeAppWindow(nativeUrl,options)}):Promise.resolve(),waitingOn=new Set(ports),bootStartedMs=Date.now();let progressLines=0,nextProgressAt=DEV_BOOT_PROGRESS_EVERY_SECONDS;const progress=setInterval(()=>{const elapsed=(Date.now()-bootStartedMs)/1000;if(elapsed<nextProgressAt)return;nextProgressAt+=DEV_BOOT_PROGRESS_EVERY_SECONDS;const line=devBootProgressLine(elapsed,[...waitingOn],options.verbose);if(!line)return;console.log(` ${dim(line)}`);progressLines++},1000);progress.unref?.();Promise.all(ports.map((p)=>waitForPort(p.port,Number.POSITIVE_INFINITY).then(()=>waitingOn.delete(p)))).then(async()=>{clearInterval(progress);if(readyAnnounced)return;readyAnnounced=!0;let proxyReachable=!1;if(hasCustomDomain&&domain){(async()=>{try{await rpxTlsPreflight;await registerRpxProxiesForDomain({domain,frontendPort,apiPort,docsPort,dashboardPort,includeDashboard,options})}catch{}})();proxyReachable=await waitForHttpsProxy(443,16000);if(!proxyReachable){console.log(` ${yellow("\u26A0")} ${yellow("HTTPS proxy not reachable on :443")} - serving ${cyan(`http://localhost:${frontendPort}`)} instead`);console.log(` ${dim(" ")}${dim("rpx needs a valid SUDO_PASSWORD in .env to bind :443; trust the local CA, then restart `./buddy dev`.")}`);if(options.verbose)console.log(` ${dim(" ")}${dim(`Trust CA: sh ${join(RPX_SSL_DIR,"trust-rpx-cert.sh")}`)}`)}}eraseDevBootStartingLines(progressLines);printDevReadyBanner({options,nativeMode,hasCustomDomain:!!appLooksCustom&&!localhostOnly,proxyReachable:proxyManagedExternally?!0:proxyReachable,frontendUrl,frontendBaseUrl,entryPath,apiUrl,docsUrl,dashboardUrl,frontendPort,apiPort,docsPort,includeDashboard,domain,dashboardPort,dashboardDomain});if(!nativeMode){const browserUrl=hasCustomDomain&&(proxyManagedExternally||proxyReachable)?frontendUrl:developmentUrl(`http://localhost:${frontendPort}`,entryPath);openDevelopmentBrowser(browserUrl)}if(startedAt){const elapsedMs=(Bun.nanoseconds()-startedAt)/1e6;console.log(` ${dim(`ready in ${(elapsedMs/1000).toFixed(1)}s`)}`);console.log();printDevEngineNotes();console.log()}if(process.env.STACKS_PRINT_ROUTES==="1")await printRegisteredRoutes(apiPort).catch(()=>{})}).catch(()=>{});await Promise.all([a.runFrontendDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`Frontend: ${error}`)}),a.runApiDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`API: ${error}`)}),a.runDocsDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`Docs: ${error}`)}),includeDashboard?a.runDashboardDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`Dashboard: ${error}`)}):Promise.resolve(),nativeWindowReady.catch((error)=>{if(options.verbose)log.warn(`Native window: ${error}`)})])}function isComingSoonMode(){if(existsSync(projectPath("storage/framework/coming-soon")))return!0;try{const idx=projectPath("resources/views/index.stx");return existsSync(idx)&&/siteMode\s*=\s*['"]coming-soon['"]/.test(readFileSync(idx,"utf8"))}catch{return!1}}function blogIsConfigured(){try{const dir=projectPath("content/blog");return existsSync(dir)&&readdirSync(dir).some((f)=>f.endsWith(".md"))}catch{return!1}}function printDevReadyBanner(input){const{options,nativeMode,hasCustomDomain,proxyReachable,frontendUrl,frontendBaseUrl,entryPath,apiUrl,docsUrl,dashboardUrl,frontendPort,apiPort,docsPort,includeDashboard,domain,dashboardPort,dashboardDomain}=input,verbose=options.verbose??!1,useProxy=hasCustomDomain&&proxyReachable,feUrl=useProxy?frontendUrl:developmentUrl(`http://localhost:${frontendPort}`,entryPath),feBaseUrl=useProxy?frontendBaseUrl:`http://localhost:${frontendPort}`,apUrl=useProxy?apiUrl:`http://localhost:${apiPort}`,dcUrl=useProxy?docsUrl:`http://localhost:${docsPort}`,dbUrl=useProxy?dashboardUrl:`http://localhost:${dashboardPort}`,blogUrl=`${feBaseUrl}/blog`;console.log();const frontendLabel=entryPath==="/"?"Site":"App";console.log(` ${green("\u279C")} ${bold(frontendLabel.padEnd(8))}: ${cyan(feUrl)}`);if(entryPath!=="/")console.log(` ${dim("\u279C")} ${dim("Site")}: ${dim(feBaseUrl)}`);if(nativeMode)console.log(` ${green("\u279C")} ${bold("Native")}: ${cyan(`Craft \u2192 ${feUrl}`)}`);console.log(` ${green("\u279C")} ${bold("API")}: ${cyan(apUrl)}`);console.log(` ${green("\u279C")} ${bold("Docs")}: ${cyan(dcUrl)}`);if(blogIsConfigured())console.log(` ${green("\u279C")} ${bold("Blog")}: ${cyan(blogUrl)}`);if(includeDashboard)console.log(` ${green("\u279C")} ${bold("Dashboard")}: ${cyan(dbUrl)}`);if(useProxy){console.log(` ${dim("\u279C")} ${dim("Direct")}: ${dim(`${developmentUrl(`http://localhost:${frontendPort}`,entryPath)} (bypasses the proxy)`)}`);if(includeDashboard&&dashboardDomain)console.log(` ${dim("\u279C")} ${dim("Direct")}: ${dim(`http://localhost:${dashboardPort} (dashboard, bypasses the proxy)`)}`)}if(isComingSoonMode()){console.log();console.log(` ${yellow("\u25CF")} ${bold(yellow("Coming soon mode"))} ${dim("- visitors see the holding page; bypass with the coming-soon secret.")}`)}if(verbose&&domain){console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${frontendPort} \u2192 ${domain}`)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`${frontendBaseUrl}/api \u2192 localhost:${apiPort}`)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`${frontendBaseUrl}/docs \u2192 localhost:${docsPort}`)}`);if(includeDashboard)console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${dashboardPort} \u2192 ${dashboardDomain}`)}`)}console.log()}function printDevEngineNotes(){const routesFile=stxPath("routes.ts"),routesLabel=relative(projectPath(),routesFile);if(["config/css.ts","css.config.ts","config/crosswind.ts","crosswind.config.ts"].some((config)=>existsSync(join(projectPath(),config))))console.log(` ${green("[ts-css]")} ${dim("CSS engine loaded")}`);if(existsSync(routesFile))try{const routeCount=(readFileSync(routesFile,"utf8").match(/pattern:/g)??[]).length;if(routeCount>0)console.log(` ${green("[stx]")} ${dim(`Generated ${routeCount} routes \u2192 ${routesLabel}`)}`)}catch{console.log(` ${green("[stx]")} ${dim(`Routes manifest \u2192 ${routesLabel}`)}`)}}async function launchNativeAppWindow(url,options){let createApp;try{createApp=(await importCraftSdk()).createApp}catch(error){if(options.verbose)log.warn(`Native window unavailable: ${error}`);console.log(` ${dim("Native window unavailable. Install craft-native or set CRAFT_BIN to a Craft binary.")}
1
+ import{execSync}from"node:child_process";import{existsSync,readdirSync,readFileSync}from"node:fs";import readline from"node:readline";import process from"node:process";import{bold,cyan,dim,green,intro,log,onUnknownSubcommand,outro,prompts,runCommand,yellow}from"@stacksjs/cli";import{homedir}from"node:os";import{dirname,join,relative}from"node:path";import{fileURLToPath}from"node:url";import{Action}from"@stacksjs/enums";import{inspectDefaultsProvenance,libsPath,projectPath,stxPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{version}from"../../package.json";import{resultFailed}from"../result";const DEV_BOOT_STARTING_LINE_COUNT=3;function eraseDevBootStartingLines(progressLines=0){if(!process.stdout.isTTY)return;for(let i=0;i<DEV_BOOT_STARTING_LINE_COUNT+progressLines;i++){readline.moveCursor(process.stdout,0,-1);readline.clearLine(process.stdout,0)}}export const DEV_BOOT_PROGRESS_EVERY_SECONDS=10;const DEV_BOOT_VERBOSE_HINT_SECONDS=30;export function devBootProgressLine(elapsedSeconds,waitingOn,verbose=!1){if(!waitingOn.length||elapsedSeconds<DEV_BOOT_PROGRESS_EVERY_SECONDS)return null;const servers=waitingOn.map((server)=>`the ${server.name} on :${server.port}`).join(", "),line=`still starting, ${Math.floor(elapsedSeconds)}s so far, waiting for ${servers}`;return!verbose&&elapsedSeconds>=DEV_BOOT_VERBOSE_HINT_SECONDS?`${line} (\`./buddy dev --verbose\` shows its output)`:line}let developmentRpx;function resolveRpxEntryPath(){return[projectPath("node_modules/@stacksjs/rpx/dist/index.js"),join(homedir(),"Code/Tools/rpx/packages/rpx/dist/index.js"),projectPath("pantry/@stacksjs/rpx/dist/src/index.js")].find((entry)=>existsSync(entry))??null}async function importDevelopmentRpx(){if(developmentRpx)return developmentRpx;const entry=resolveRpxEntryPath();developmentRpx=entry?await import(entry):await import("@stacksjs/rpx");return developmentRpx}const activeRpxRegistryIds=[];let _actions;async function actions(){if(!_actions)_actions=await import("@stacksjs/actions");return _actions}async function checkFrameworkDefaults(){let skew;try{skew=inspectDefaultsProvenance(projectPath())}catch{return}if(skew.status==="not-applicable"||skew.status==="current")return;if(skew.status==="stale")throw Error(`Framework defaults version mismatch: storage/framework/defaults is from ${skew.vendored}, but @stacksjs/defaults ${skew.installed} is installed. Run \`buddy upgrade\` to synchronize them before starting development servers.`);try{const{measureDefaultsDrift}=await actions(),drift=measureDefaultsDrift(projectPath(),{shallow:!0});if(!drift||drift.length===0)return;log.warn(`storage/framework/defaults does not match the installed @stacksjs/defaults ${skew.installed}.`);log.warn("The app runs the vendored copy. Run `buddy upgrade` to sync it, or `buddy doctor` for the file counts.")}catch{}}export const interactiveDevChoices=[{value:"all",title:"All"},{value:"frontend",title:"Frontend"},{value:"api",title:"Backend"},{value:"dashboard",title:"Dashboard"},{value:"desktop",title:"Desktop"},{value:"native",title:"Native App"},{value:"components",title:"Components"},{value:"docs",title:"Documentation"}];export async function dispatchInteractiveDevSelection(selection,runners){if(!Object.hasOwn(runners,selection))return!1;await runners[selection]();return!0}export function resolvePrettyDevDomain(appUrl,nativeMode=!1){if(!appUrl||nativeMode)return null;try{const hostname=new URL(/^https?:\/\//i.test(appUrl)?appUrl:`https://${appUrl}`).hostname.toLowerCase();if(hostname==="localhost"||hostname==="127.0.0.1"||hostname==="::1"||hostname==="0.0.0.0")return null;return hostname}catch{return null}}export function shouldUsePrettyDevUrls(input){return input.domain!==null&&!input.localhostOnly&&(input.proxyManagedExternally||input.systemAuthorized)}const APPLICATION_VIEW_CANDIDATES=["app/index","app","dashboard/index","dashboard","home/index","home","composer","workspace/index","workspace","feed","portal","admin","account/index","account","profile/index","profile"],SITE_LAYOUT_NAMES=new Set(["default","guest","marketing","site"]);export function normalizeDevelopmentEntryPath(path){const value=path?.trim();if(!value||value==="/")return"/";try{const parsed=new URL(value,"http://stacks.localhost");return`${parsed.pathname}${parsed.search}${parsed.hash}`}catch{return"/"}}function configuredApplicationPath(root){if(process.env.APP_PATH)return process.env.APP_PATH;try{return readFileSync(join(root,"config/app.ts"),"utf8").match(/^[ \t]*appPath\s*:\s*(['"`])([^'"`]+)\1/m)?.[2]}catch{return}}function configuredDevelopmentLaunch(root){const environmentValue=process.env.STACKS_DEV_LAUNCH?.trim().toLowerCase();if(environmentValue==="browser"||environmentValue==="native")return environmentValue;try{const value=readFileSync(join(root,"config/app.ts"),"utf8").match(/\bdevLaunch\s*:\s*(['"`])(browser|native)\1/)?.[2];return value==="browser"||value==="native"?value:void 0}catch{return}}export function resolveDevelopmentLaunch(input={}){if(input.browser||input.site)return"browser";if(input.native)return"native";const root=input.root??projectPath();return input.configuredLaunch??configuredDevelopmentLaunch(root)??"browser"}function viewSource(root,route){const base=join(root,"resources/views",route);for(const extension of["stx","vue","html"]){const path=`${base}.${extension}`;if(existsSync(path))try{return readFileSync(path,"utf8")}catch{return""}}return}function usesApplicationLayout(source){const layout=source.match(/@extends\(\s*['"]layouts\/([^'"]+)['"]\s*\)/)?.[1]??source.match(/@layout\(\s*['"]layouts\/([^'"]+)['"]\s*\)/)?.[1];if(!layout)return!1;return!SITE_LAYOUT_NAMES.has(layout.split("/").at(-1)??layout)}export function resolveDevelopmentEntryPath(input={}){if(input.site)return"/";const root=input.root??projectPath(),configuredPath=input.configuredPath??configuredApplicationPath(root);if(configuredPath)return normalizeDevelopmentEntryPath(configuredPath);const loginExists=viewSource(root,"login")!==void 0||viewSource(root,"auth/login")!==void 0,candidates=APPLICATION_VIEW_CANDIDATES.map((route)=>({route,source:viewSource(root,route)})).filter((candidate)=>candidate.source!==void 0),appCandidate=candidates.find((candidate)=>usesApplicationLayout(candidate.source))??(loginExists?candidates[0]:void 0);if(appCandidate)return normalizeDevelopmentEntryPath(`/${appCandidate.route.replace(/\/index$/,"")}`);return loginExists?"/login":"/"}export function developmentUrl(baseUrl,entryPath){const normalized=normalizeDevelopmentEntryPath(entryPath);if(normalized==="/")return baseUrl.replace(/\/$/,"");return new URL(normalized,`${baseUrl.replace(/\/$/,"")}/`).toString()}export function developmentBrowserCommand(url,platform=process.platform){if(platform==="darwin")return["open",url];if(platform==="win32")return["cmd","/c","start","",url];return["xdg-open",url]}function openDevelopmentBrowser(url){if(!process.stdout.isTTY||process.env.CI||process.env.STACKS_DEV_NO_OPEN==="1")return;try{Bun.spawn(developmentBrowserCommand(url),{stdin:"ignore",stdout:"ignore",stderr:"ignore"}).unref()}catch{}}async function canStartPrettyDevProxy(){if(await waitForHttpsProxy(443,150))return!0;try{const{authorizeSystemAccess}=await importDevelopmentRpx();return authorizeSystemAccess({interactive:!1})}catch{return!1}}export function dev(buddy){const descriptions={dev:"Start development server",frontend:"Start the frontend development server",components:"Start the Components development server",desktop:"Start the Desktop App development server",native:"Start the app in a native Craft window",dashboard:"Start the Dashboard development server",api:"Start the local API development server",docs:"Start the Documentation development server",systemTray:"Start the System Tray development server",interactive:"Get asked which development server to start",select:"Which development server are you trying to start?",withLocalhost:"Include the localhost URL in the output",browser:"Open the application in a browser instead of its configured native window",site:"Open the marketing site instead of the application",project:"Target a specific project",verbose:"Enable verbose output",detach:"Keep the dev servers running after this process or its shell exits (for `nohup ./buddy dev --detach &`)"};buddy.command("dev [server]",descriptions.dev).option("-f, --frontend",descriptions.frontend).option("-a, --api",descriptions.api).option("-c, --components",descriptions.components).option("-d, --dashboard",descriptions.dashboard).option("-k, --desktop",descriptions.desktop).option("-n, --native",descriptions.native).option("-o, --docs",descriptions.docs).option("-s, --system-tray",descriptions.systemTray).option("-i, --interactive",descriptions.interactive,{default:!1}).option("-l, --with-localhost",descriptions.withLocalhost,{default:!1}).option("--browser",descriptions.browser,{default:!1}).option("--site",descriptions.site,{default:!1}).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).option("--detach",descriptions.detach,{default:!1}).action(async(server,options)=>{if(options.detach)process.env.STACKS_DEV_DETACH="1";const perf=Bun.nanoseconds();process.env.STACKS_DEV_ENTRY_PATH=resolveDevelopmentEntryPath({site:options.site});await checkFrameworkDefaults();const target=server||(options.frontend?"frontend":void 0)||(options.api?"api":void 0)||(options.components?"components":void 0)||(options.dashboard?"dashboard":void 0)||(options.desktop?"desktop":void 0)||(options.native?"native":void 0)||(options.systemTray||options["system-tray"]?"system-tray":void 0)||(options.docs?"docs":void 0);if(target){const serverOptions={...options},a=await actions();switch(target){case"native":await startDevelopmentServer({...serverOptions,native:!0},perf);break;case"frontend":await a.runFrontendDevServer(serverOptions);break;case"api":await a.runApiDevServer(serverOptions);break;case"components":await a.runComponentsDevServer(serverOptions);break;case"dashboard":await a.runDashboardDevServer(serverOptions);break;case"desktop":await startDevelopmentServer({...serverOptions,native:!0},perf);break;case"system-tray":await a.runSystemTrayDevServer(serverOptions);break;case"docs":await a.runDocsDevServer(serverOptions);break;default:await log.error(`Unknown server: ${target}`);process.exit(ExitCode.InvalidArgument)}}else if(wantsInteractive(options)){const selectedValue=await prompts.select({message:descriptions.select,choices:interactiveDevChoices.map((choice)=>({value:choice.value,label:choice.title}))}),a=await actions();if(!await dispatchInteractiveDevSelection(selectedValue,{all:()=>startDevelopmentServer(options,perf),frontend:()=>a.runFrontendDevServer(options),api:()=>a.runApiDevServer(options),dashboard:()=>a.runDashboardDevServer(options),desktop:()=>startDevelopmentServer({...options,native:!0},perf),native:()=>startDevelopmentServer({...options,native:!0},perf),components:()=>a.runComponentsDevServer(options),docs:()=>a.runDocsDevServer(options)})){await log.error("Invalid option during interactive mode");process.exit(ExitCode.InvalidArgument)}}else await startDevelopmentServer(options,perf);outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("dev:components",descriptions.components).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:components"),result=await runCommand("bun run dev",{cwd:libsPath("components/stx")});if(options.verbose)log.info("buddy dev:components result",result);if(resultFailed(result)){await outro("While running the dev:components command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("dev:docs",descriptions.docs).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:docs"),result=await(await actions()).runAction(Action.DevDocs,options);if(resultFailed(result)){await outro("While running the dev:docs command, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}console.log("");await outro("Exited",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)});buddy.command("dev:native",descriptions.native).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:native");await startDevelopmentServer({...options,native:!0},perf)});buddy.command("dev:desktop",descriptions.desktop).option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const perf=await intro("buddy dev:desktop"),{hasMacosDesktop}=await import("@stacksjs/desktop-build");if(await hasMacosDesktop(projectPath())){process.env.STACKS_DESKTOP_LAUNCH="1";const{runAction}=await import("@stacksjs/actions"),{Action}=await import("@stacksjs/enums"),result=await runAction(Action.BuildDesktopMacos,options);if(resultFailed(result)){await outro("While building the native macOS app, there was an issue",{startTime:perf,useSeconds:!0},result.error);process.exit(ExitCode.FatalError)}await outro("Opened the native macOS app",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)}await startDevelopmentServer({...options,native:!0},perf)});buddy.command("dev:api",descriptions.api).option("-p, --project [project]",descriptions.project,{default:!1}).option("--no-watch-types","Skip the model/config type-regeneration watcher").option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();const a=await actions();if(options.watchTypes!==!1)a.watchTypes(options).catch((err)=>{log.warn("[dev:api] type watcher exited:",err)});await a.runApiDevServer(options)});buddy.command("dev:frontend",descriptions.frontend).alias("dev:pages").alias("dev:views").option("-p, --project [project]",descriptions.project,{default:!1}).option("--site",descriptions.site,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();process.env.STACKS_DEV_ENTRY_PATH=resolveDevelopmentEntryPath({site:options.site});await(await actions()).runFrontendDevServer(options)});buddy.command("dev:dashboard",descriptions.dashboard).alias("dev:admin").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();await(await actions()).runDashboardDevServer(options)});buddy.command("dev:system-tray",descriptions.systemTray).alias("dev:tray").option("-p, --project [project]",descriptions.project,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{await checkFrameworkDefaults();await(await actions()).runSystemTrayDevServer(options)});onUnknownSubcommand(buddy,"dev")}export async function startDevelopmentServer(_options,_startTime){const options=_options,startedAt=_startTime,appUrl=process.env.APP_URL??"stacks.localhost",nativeMode=resolveDevelopmentLaunch({browser:options.browser,native:options.native,site:options.site})==="native",entryPath=resolveDevelopmentEntryPath({site:options.site});process.env.STACKS_DEV_ENTRY_PATH=entryPath;const proxyManagedExternally=process.env.STACKS_PROXY_MANAGED==="1",preferredFrontendPort=Number(process.env.PORT)||3000,preferredApiPort=Number(process.env.PORT_API)||3008,preferredDocsPort=Number(process.env.PORT_DOCS)||3006,preferredDashboardPort=Number(process.env.PORT_ADMIN)||3002;if(process.env.STACKS_DEV_NO_KILL!=="1")await cleanupStaleDevProcesses([preferredFrontendPort,preferredApiPort,preferredDocsPort,preferredDashboardPort]);const portShifts=[],claimPort=async(label,preferred)=>{if(proxyManagedExternally)return preferred;const port=await findAvailablePort(preferred);if(port!==preferred)portShifts.push(`${label} :${preferred} \u2192 :${port}`);return port},frontendPort=await claimPort("Frontend",preferredFrontendPort),apiPort=await claimPort("API",preferredApiPort),docsPort=await claimPort("Docs",preferredDocsPort),dashboardPort=await claimPort("Dashboard",preferredDashboardPort);process.env.PORT=String(frontendPort);process.env.PORT_API=String(apiPort);process.env.PORT_DOCS=String(docsPort);process.env.PORT_ADMIN=String(dashboardPort);const includeDashboard=process.env.STACKS_DEV_DASHBOARD==="1",domain=resolvePrettyDevDomain(appUrl,nativeMode),appLooksCustom=domain!==null,localhostOnly=process.env.STACKS_DEV_LOCALHOST==="1",prettyUrlsRequested=appLooksCustom&&!localhostOnly,systemAuthorized=!prettyUrlsRequested||proxyManagedExternally?!0:await canStartPrettyDevProxy(),usePrettyUrls=shouldUsePrettyDevUrls({domain,localhostOnly,proxyManagedExternally,systemAuthorized}),prettySetupRequired=prettyUrlsRequested&&!usePrettyUrls,hasCustomDomain=usePrettyUrls&&!proxyManagedExternally,displayedDomain=usePrettyUrls?domain:null,dashboardDomain=displayedDomain?`dashboard.${displayedDomain}`:null,frontendBaseUrl=displayedDomain?`https://${displayedDomain}`:`http://localhost:${frontendPort}`,frontendUrl=developmentUrl(frontendBaseUrl,entryPath),apiUrl=displayedDomain?`https://${displayedDomain}/api`:`http://localhost:${apiPort}`,docsUrl=displayedDomain?`https://${displayedDomain}/docs`:`http://localhost:${docsPort}`,dashboardUrl=dashboardDomain?`https://${dashboardDomain}`:`http://localhost:${dashboardPort}`;process.env.STACKS_PROXY_MANAGED="1";process.env.STACKS_DEV_QUIET="1";console.log();if(prettySetupRequired){console.log(` ${yellow("\u26A0")} ${yellow("Pretty URL setup required")} ${dim("- using localhost for this session")}`);console.log(` ${dim(" ")}${dim("Run `./buddy setup:ssl` once, then restart `./buddy dev`.")}`);console.log()}if(portShifts.length>0){console.log(` ${yellow("\u26A0")} ${yellow("Ports in use by another process")} ${dim(`- ${portShifts.join(", ")}`)}`);console.log()}console.log(` ${bold(cyan("stacks"))} ${dim(`v${version}`)} ${dim("starting\u2026")}`);console.log();const rpxTlsPreflight=hasCustomDomain&&domain?prepareRpxTlsForDev({domain,includeDashboard,options}):Promise.resolve();if(localhostOnly&&domain&&!proxyManagedExternally)removeStalePublicDomainOverrides(domain,includeDashboard,options.verbose??!1).catch(()=>{});rpxTlsPreflight.catch(()=>{});let isExiting=!1,closeNativeApp;const SHUTDOWN_GRACE_MS=1500,cleanup=(signal)=>{if(isExiting)return;isExiting=!0;console.log();console.log(` ${bold(cyan("stacks"))} ${dim(`received ${signal} - shutting down the dev server`)}`);if(signal==="SIGHUP")console.log(` ${dim(" ")}${dim("The launching shell went away (a closed terminal, or a CI step that ended). nohup does not prevent this.")}`);closeNativeApp?.();const rpxTeardown=(async()=>{try{await unregisterRpxProxies(activeRpxRegistryIds);activeRpxRegistryIds.length=0;if(hasCustomDomain&&domain)await removeStalePublicDomainOverrides(domain,includeDashboard,options.verbose??!1)}catch{}})(),teardownDeadline=new Promise((resolve)=>setTimeout(resolve,SHUTDOWN_GRACE_MS));Promise.race([rpxTeardown,teardownDeadline]).finally(()=>{try{process.kill(-process.pid,"SIGTERM")}catch{try{process.kill(0,"SIGTERM")}catch{}}setTimeout(()=>{try{process.kill(0,"SIGKILL")}catch{process.exit(1)}},SHUTDOWN_GRACE_MS).unref()})};process.on("SIGINT",cleanup);process.on("SIGTERM",cleanup);process.on("SIGHUP",cleanup);const quietOpts={...options,quiet:!0},a=await actions(),ports=[{name:"API",port:apiPort}],readinessTimeoutMs=30000;let readyAnnounced=!1;const nativeUrl=developmentUrl(`http://localhost:${frontendPort}`,entryPath),nativeWindowReady=nativeMode?waitForPort(frontendPort,readinessTimeoutMs).then(async(ready)=>{if(!ready){log.warn(`Native window skipped because the frontend did not answer within ${readinessTimeoutMs/1000}s`);return}closeNativeApp=await launchNativeAppWindow(nativeUrl,options)}):Promise.resolve(),waitingOn=new Set(ports),bootStartedMs=Date.now();let progressLines=0,nextProgressAt=DEV_BOOT_PROGRESS_EVERY_SECONDS;const progress=setInterval(()=>{const elapsed=(Date.now()-bootStartedMs)/1000;if(elapsed<nextProgressAt)return;nextProgressAt+=DEV_BOOT_PROGRESS_EVERY_SECONDS;const line=devBootProgressLine(elapsed,[...waitingOn],options.verbose);if(!line)return;console.log(` ${dim(line)}`);progressLines++},1000);progress.unref?.();Promise.all(ports.map((p)=>waitForPort(p.port,Number.POSITIVE_INFINITY).then(()=>waitingOn.delete(p)))).then(async()=>{clearInterval(progress);if(readyAnnounced)return;readyAnnounced=!0;let proxyReachable=!1;if(hasCustomDomain&&domain){(async()=>{try{await rpxTlsPreflight;await registerRpxProxiesForDomain({domain,frontendPort,apiPort,docsPort,dashboardPort,includeDashboard,options})}catch{}})();proxyReachable=await waitForHttpsProxy(443,16000);if(!proxyReachable){console.log(` ${yellow("\u26A0")} ${yellow("HTTPS proxy not reachable on :443")} - serving ${cyan(`http://localhost:${frontendPort}`)} instead`);console.log(` ${dim(" ")}${dim("rpx needs a valid SUDO_PASSWORD in .env to bind :443; trust the local CA, then restart `./buddy dev`.")}`);if(options.verbose)console.log(` ${dim(" ")}${dim(`Trust CA: sh ${join(RPX_SSL_DIR,"trust-rpx-cert.sh")}`)}`)}}eraseDevBootStartingLines(progressLines);printDevReadyBanner({options,nativeMode,hasCustomDomain:!!appLooksCustom&&!localhostOnly,proxyReachable:proxyManagedExternally?!0:proxyReachable,frontendUrl,frontendBaseUrl,entryPath,apiUrl,docsUrl,dashboardUrl,frontendPort,apiPort,docsPort,includeDashboard,domain,dashboardPort,dashboardDomain});if(!nativeMode){const browserUrl=hasCustomDomain&&(proxyManagedExternally||proxyReachable)?frontendUrl:developmentUrl(`http://localhost:${frontendPort}`,entryPath);openDevelopmentBrowser(browserUrl)}if(startedAt){const elapsedMs=(Bun.nanoseconds()-startedAt)/1e6;console.log(` ${dim(`ready in ${(elapsedMs/1000).toFixed(1)}s`)}`);console.log();printDevEngineNotes();console.log()}if(process.env.STACKS_PRINT_ROUTES==="1")await printRegisteredRoutes(apiPort).catch(()=>{})}).catch(()=>{});await Promise.all([a.runFrontendDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`Frontend: ${error}`)}),a.runApiDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`API: ${error}`)}),a.runDocsDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`Docs: ${error}`)}),includeDashboard?a.runDashboardDevServer(quietOpts).catch((error)=>{if(options.verbose)log.error(`Dashboard: ${error}`)}):Promise.resolve(),nativeWindowReady.catch((error)=>{if(options.verbose)log.warn(`Native window: ${error}`)})])}function isComingSoonMode(){if(existsSync(projectPath("storage/framework/coming-soon")))return!0;try{const idx=projectPath("resources/views/index.stx");return existsSync(idx)&&/siteMode\s*=\s*['"]coming-soon['"]/.test(readFileSync(idx,"utf8"))}catch{return!1}}function blogIsConfigured(){try{const dir=projectPath("content/blog");return existsSync(dir)&&readdirSync(dir).some((f)=>f.endsWith(".md"))}catch{return!1}}function printDevReadyBanner(input){const{options,nativeMode,hasCustomDomain,proxyReachable,frontendUrl,frontendBaseUrl,entryPath,apiUrl,docsUrl,dashboardUrl,frontendPort,apiPort,docsPort,includeDashboard,domain,dashboardPort,dashboardDomain}=input,verbose=options.verbose??!1,useProxy=hasCustomDomain&&proxyReachable,feUrl=useProxy?frontendUrl:developmentUrl(`http://localhost:${frontendPort}`,entryPath),feBaseUrl=useProxy?frontendBaseUrl:`http://localhost:${frontendPort}`,apUrl=useProxy?apiUrl:`http://localhost:${apiPort}`,dcUrl=useProxy?docsUrl:`http://localhost:${docsPort}`,dbUrl=useProxy?dashboardUrl:`http://localhost:${dashboardPort}`,blogUrl=`${feBaseUrl}/blog`;console.log();const frontendLabel=entryPath==="/"?"Site":"App";console.log(` ${green("\u279C")} ${bold(frontendLabel.padEnd(8))}: ${cyan(feUrl)}`);if(entryPath!=="/")console.log(` ${dim("\u279C")} ${dim("Site")}: ${dim(feBaseUrl)}`);if(nativeMode)console.log(` ${green("\u279C")} ${bold("Native")}: ${cyan(`Craft \u2192 ${feUrl}`)}`);console.log(` ${green("\u279C")} ${bold("API")}: ${cyan(apUrl)}`);console.log(` ${green("\u279C")} ${bold("Docs")}: ${cyan(dcUrl)}`);if(blogIsConfigured())console.log(` ${green("\u279C")} ${bold("Blog")}: ${cyan(blogUrl)}`);if(includeDashboard)console.log(` ${green("\u279C")} ${bold("Dashboard")}: ${cyan(dbUrl)}`);if(useProxy){console.log(` ${dim("\u279C")} ${dim("Direct")}: ${dim(`${developmentUrl(`http://localhost:${frontendPort}`,entryPath)} (bypasses the proxy)`)}`);if(includeDashboard&&dashboardDomain)console.log(` ${dim("\u279C")} ${dim("Direct")}: ${dim(`http://localhost:${dashboardPort} (dashboard, bypasses the proxy)`)}`)}if(isComingSoonMode()){console.log();console.log(` ${yellow("\u25CF")} ${bold(yellow("Coming soon mode"))} ${dim("- visitors see the holding page; bypass with the coming-soon secret.")}`)}if(verbose&&domain){console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${frontendPort} \u2192 ${domain}`)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`${frontendBaseUrl}/api \u2192 localhost:${apiPort}`)}`);console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`${frontendBaseUrl}/docs \u2192 localhost:${docsPort}`)}`);if(includeDashboard)console.log(` ${dim("\u279C")} ${dim("Proxy")}: ${dim(`localhost:${dashboardPort} \u2192 ${dashboardDomain}`)}`)}console.log()}function printDevEngineNotes(){const routesFile=stxPath("routes.ts"),routesLabel=relative(projectPath(),routesFile);if(["config/css.ts","css.config.ts","config/crosswind.ts","crosswind.config.ts"].some((config)=>existsSync(join(projectPath(),config))))console.log(` ${green("[ts-css]")} ${dim("CSS engine loaded")}`);if(existsSync(routesFile))try{const routeCount=(readFileSync(routesFile,"utf8").match(/pattern:/g)??[]).length;if(routeCount>0)console.log(` ${green("[stx]")} ${dim(`Generated ${routeCount} routes \u2192 ${routesLabel}`)}`)}catch{console.log(` ${green("[stx]")} ${dim(`Routes manifest \u2192 ${routesLabel}`)}`)}}async function launchNativeAppWindow(url,options){let createApp;try{createApp=(await importCraftSdk()).createApp}catch(error){if(options.verbose)log.warn(`Native window unavailable: ${error}`);console.log(` ${dim("Native window unavailable. Install craft-native or set CRAFT_BIN to a Craft binary.")}
2
2
  `);return}if(!createApp){console.log(` ${dim("Native window unavailable. The Craft SDK did not export createApp.")}
3
3
  `);return}const craftBinaryPath=resolveCraftBinaryPath();if(!craftBinaryPath){console.log(` ${dim("Native window unavailable. Set CRAFT_BIN to a Craft binary, or install Craft in ~/Code/Tools/craft.")}
4
4
  `);return}const appIconPath=resolveNativeAppIconPath(),app=createApp({url,quiet:!options.verbose,craftPath:craftBinaryPath,window:{title:await resolveNativeAppTitle(),width:1280,height:860,titlebarHidden:!0,webSidebarMaterial:!0,webSidebarWidth:286,webSidebarMaterialOpacity:0.78,...appIconPath&&{icon:appIconPath}}});app.show().catch((error)=>{if(options.verbose)log.warn(`Native window exited: ${error}`)});return()=>app.close()}async function importCraftSdk(){const localCraftSdk=process.env.HOME?`${process.env.HOME}/Code/Tools/craft/packages/typescript/src/index.ts`:void 0;if(localCraftSdk&&existsSync(localCraftSdk))return await import(localCraftSdk);const packageNames=["craft-native","@craft-native/craft","@stacksjs/ts-craft"];let primaryError;for(const packageName of packageNames)try{return await import(packageName)}catch(error){primaryError??=error}throw primaryError}async function cleanupStaleDevProcesses(ports){const projectRoot=projectPath(),actionDevPath=projectPath("storage/framework/core/actions/src/dev/"),pids=new Set;for(const port of ports)for(const pid of await listenerPids(port)){if(pid===process.pid)continue;const command=await commandForPid(pid);if(!command.includes(projectRoot))continue;if(command.includes("storage/framework/core/buddy/src/cli.ts dev")||command.includes("storage/framework/core/actions/src/dev/")||command.includes(actionDevPath))pids.add(pid)}if(pids.size===0)return;for(const pid of pids)try{process.kill(pid,"SIGTERM")}catch{}await new Promise((r)=>setTimeout(r,250));for(const pid of pids)try{process.kill(pid,"SIGKILL")}catch{}await new Promise((r)=>setTimeout(r,150))}async function listenerPids(port){try{const proc=Bun.spawn(["lsof","-nP",`-iTCP:${port}`,"-sTCP:LISTEN","-t"],{stdout:"pipe",stderr:"ignore"}),output=await new Response(proc.stdout).text();await proc.exited;return output.split(/\s+/).map((value)=>Number(value)).filter((pid)=>Number.isInteger(pid)&&pid>0)}catch{return[]}}async function commandForPid(pid){try{const proc=Bun.spawn(["ps","-p",String(pid),"-o","command="],{stdout:"pipe",stderr:"ignore"}),output=await new Response(proc.stdout).text();await proc.exited;return output.trim()}catch{return""}}function resolveCraftBinaryPath(){const home=process.env.HOME;return[process.env.CRAFT_BIN,home?`${home}/Code/Tools/craft/craft`:void 0,home?`${home}/Code/Tools/craft/bin/craft`:void 0,home?`${home}/Code/Tools/craft/packages/zig/zig-out/bin/craft`:void 0,home?`${home}/Documents/Projects/craft/packages/zig/zig-out/bin/craft`:void 0].filter((candidate)=>Boolean(candidate)).find((candidate)=>existsSync(candidate))}function resolveNativeAppIconPath(){return[projectPath("resources/assets/images/app-icon.png"),projectPath("resources/assets/images/icon.png"),projectPath("public/icon.png")].find((candidate)=>existsSync(candidate))}async function resolveNativeAppTitle(){try{const pkg=await Bun.file(projectPath("package.json")).json(),name=typeof pkg.productName==="string"?pkg.productName:pkg.name;if(typeof name==="string"&&name.length>0)return name.split(/[-_\s]+/).filter(Boolean).map((part)=>part.charAt(0).toUpperCase()+part.slice(1)).join(" ")}catch{}return"Stacks App"}const METHOD_COLORS={GET:cyan,POST:green,PUT:cyan,PATCH:cyan,DELETE:cyan,OPTIONS:dim};async function printRegisteredRoutes(apiPort){try{const ac=new AbortController,t=setTimeout(()=>ac.abort(),1500),res=await fetch(`http://127.0.0.1:${apiPort}/__routes`,{signal:ac.signal}).catch(()=>null);clearTimeout(t);if(!res||!res.ok)return;const routes=await res.json();if(!Array.isArray(routes)||routes.length===0)return;const sorted=[...routes].sort((a,b)=>a.path.localeCompare(b.path)||a.method.localeCompare(b.method)),longestPath=Math.max(...sorted.map((r)=>r.path.length));console.log(` ${dim("Registered routes")}
@@ -54,6 +54,7 @@ export * from './saas';
54
54
  export * from './schedule';
55
55
  export * from './search';
56
56
  export * from './seed';
57
+ export * from './security';
57
58
  export * from './serve';
58
59
  export * from './setup';
59
60
  export * from './sms';
@@ -1 +1 @@
1
- export*from"./about";export*from"./auth";export*from"./build";export*from"./cd";export*from"./changelog";export*from"./clean";export*from"./cloud";export*from"./comments";export*from"./commerce-import";export*from"./commit";export*from"./completion";export*from"./config-migrate";export*from"./configure";export*from"./create";export*from"./db";export*from"./storage-backup";export*from"./deploy";export*from"./deploy-preview";export*from"./dev";export*from"./dns";export*from"./docs";export*from"./doctor";export*from"./domains";export*from"./email";export*from"./env";export*from"./features";export*from"./feedback";export*from"./fresh";export*from"./gdpr";export*from"./generate";export*from"./http";export*from"./install";export*from"./key";export*from"./libs";export*from"./lint";export*from"./list";export*from"./mail";export*from"./make";export*from"./migrate";export*from"./migrate-project";export*from"./outdated";export*from"./phone";export*from"./ports";export*from"./server";export*from"./link";export*from"./user";export*from"./prepublish";export*from"./projects";export*from"./publish";export*from"./queue";export*from"./release";export*from"./route";export*from"./saas";export*from"./schedule";export*from"./search";export*from"./seed";export*from"./serve";export*from"./setup";export*from"./sms";export*from"./socials";export*from"./telemetry";export*from"./test";export*from"./tinker";export*from"./types";export*from"./upgrade";export*from"./version";
1
+ export*from"./about";export*from"./auth";export*from"./build";export*from"./cd";export*from"./changelog";export*from"./clean";export*from"./cloud";export*from"./comments";export*from"./commerce-import";export*from"./commit";export*from"./completion";export*from"./config-migrate";export*from"./configure";export*from"./create";export*from"./db";export*from"./storage-backup";export*from"./deploy";export*from"./deploy-preview";export*from"./dev";export*from"./dns";export*from"./docs";export*from"./doctor";export*from"./domains";export*from"./email";export*from"./env";export*from"./features";export*from"./feedback";export*from"./fresh";export*from"./gdpr";export*from"./generate";export*from"./http";export*from"./install";export*from"./key";export*from"./libs";export*from"./lint";export*from"./list";export*from"./mail";export*from"./make";export*from"./migrate";export*from"./migrate-project";export*from"./outdated";export*from"./phone";export*from"./ports";export*from"./server";export*from"./link";export*from"./user";export*from"./prepublish";export*from"./projects";export*from"./publish";export*from"./queue";export*from"./release";export*from"./route";export*from"./saas";export*from"./schedule";export*from"./search";export*from"./seed";export*from"./security";export*from"./serve";export*from"./setup";export*from"./sms";export*from"./socials";export*from"./telemetry";export*from"./test";export*from"./tinker";export*from"./types";export*from"./upgrade";export*from"./version";
@@ -0,0 +1,2 @@
1
+ import type { CLI } from '@stacksjs/types';
2
+ export declare function security(buddy: CLI): void;
@@ -0,0 +1,2 @@
1
+ import{resolve}from"node:path";import process from"node:process";import{intro,log,onUnknownSubcommand,outro}from"@stacksjs/cli";import{projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";const SEVERITY_NAMES=["critical","high","medium","low","none"];function parseChecks(value,flag,known){if(!value)return;const names=value.split(",").map((name)=>name.trim().toLowerCase()).filter(Boolean),unknown=names.filter((name)=>!known.includes(name));if(unknown.length)throw Error(`${flag}: unknown check ${unknown.map((name)=>`"${name}"`).join(", ")}. Choose from ${known.join(", ")}.`);return names}export function security(buddy){const descriptions={scan:"Scan the project for security vulnerabilities: vulnerable dependencies, committed secrets, dangerous code, risky GitHub workflows and repository hygiene",only:"Run only these checks, comma-separated: dependencies, secrets, code, workflows, hygiene",skip:"Skip these checks, comma-separated",json:"Print the report as JSON",failOn:"Exit non-zero for findings at or above this severity: critical, high, medium, low or none",offline:"Do not use the network (skips the dependency advisory check)",showIgnored:"List the findings that .security-ignore.json or a security-ignore comment set aside",path:"Scan this directory instead of the project"};buddy.command("security:scan",descriptions.scan).alias("audit").option("--only <checks>",descriptions.only).option("--skip <checks>",descriptions.skip).option("--json",descriptions.json,{default:!1}).option("--fail-on <severity>",descriptions.failOn,{default:"high"}).option("--offline",descriptions.offline,{default:!1}).option("--show-ignored",descriptions.showIgnored,{default:!1}).option("--path <dir>",descriptions.path).example("buddy security:scan").example("buddy security:scan --only dependencies,secrets").example("buddy security:scan --fail-on medium --json").action(async(options)=>{log.debug("Running `buddy security:scan` ...",options);const{failsScan,formatScan,runSecurityScan,SCAN_CHECKS}=await import("@stacksjs/security/scan"),failOn=String(options.failOn??"high").toLowerCase();if(!SEVERITY_NAMES.includes(failOn)){await log.error(`--fail-on: unknown severity "${failOn}". Choose from ${SEVERITY_NAMES.join(", ")}.`);process.exit(ExitCode.InvalidArgument)}let only,skip;try{only=parseChecks(options.only,"--only",SCAN_CHECKS);skip=parseChecks(options.skip,"--skip",SCAN_CHECKS)}catch(error){await log.error(error instanceof Error?error.message:String(error));process.exit(ExitCode.InvalidArgument)}const startTime=options.json?0:await intro("buddy security:scan"),report=await runSecurityScan({cwd:options.path?resolve(options.path):projectPath(),only,skip,offline:Boolean(options.offline)}),failed=failsScan(report,failOn);if(options.json){process.stdout.write(`${JSON.stringify({...report,failOn,passed:!failed},null,2)}
2
+ `);process.exit(failed?ExitCode.FatalError:ExitCode.Success)}console.log(formatScan(report,{color:process.stdout.isTTY,showIgnored:Boolean(options.showIgnored)}));if(failed){await outro(`Security scan failed (${failOn} or worse)`,{startTime,useSeconds:!0,type:"error"});process.exit(ExitCode.FatalError)}await outro("Security scan passed",{startTime,useSeconds:!0})});onUnknownSubcommand(buddy,"security")}
@@ -1 +1 @@
1
- export async function withTimeout(work,ms){let timeoutId;const deadline=new Promise((_,reject)=>{timeoutId=setTimeout(()=>reject(Error("timeout")),ms)});try{return await Promise.race([work,deadline])}finally{if(timeoutId!==void 0)clearTimeout(timeoutId)}}const commandRegistry={about:{path:"./commands/about.js",load:()=>import("./commands/about.js"),exportName:"about"},add:{path:"./commands/add.js",load:()=>import("./commands/add.js"),exportName:"add"},"ai:access":{path:"./commands/ai-access.js",load:()=>import("./commands/ai-access.js"),exportName:"aiAccess"},"ai:context":{path:"./commands/ai-context.js",load:()=>import("./commands/ai-context.js"),exportName:"aiContext"},auth:{path:"./commands/auth.js",load:()=>import("./commands/auth.js"),exportName:"auth"},build:{path:"./commands/build.js",load:()=>import("./commands/build.js"),exportName:"build"},cd:{path:"./commands/cd.js",load:()=>import("./commands/cd.js"),exportName:"cd"},changelog:{path:"./commands/changelog.js",load:()=>import("./commands/changelog.js"),exportName:"changelog"},clean:{path:"./commands/clean.js",load:()=>import("./commands/clean.js"),exportName:"clean"},cloud:{path:"./commands/cloud.js",load:()=>import("./commands/cloud.js"),exportName:"cloud"},commit:{path:"./commands/commit.js",load:()=>import("./commands/commit.js"),exportName:"commit"},completion:{path:"./commands/completion.js",load:()=>import("./commands/completion.js"),exportName:"completion"},"config:migrate":{path:"./commands/config-migrate.js",load:()=>import("./commands/config-migrate.js"),exportName:"configMigrate"},configure:{path:"./commands/configure.js",load:()=>import("./commands/configure.js"),exportName:"configure"},create:{path:"./commands/create.js",load:()=>import("./commands/create.js"),exportName:"create"},new:{path:"./commands/create.js",load:()=>import("./commands/create.js"),exportName:"create"},deploy:{path:"./commands/deploy.js",load:()=>import("./commands/deploy.js"),exportName:"deploy"},"deploy:rollback":{path:"./commands/deploy.js",load:()=>import("./commands/deploy.js"),exportName:"deploy"},dev:{path:"./commands/dev.js",load:()=>import("./commands/dev.js"),exportName:"dev"},"desktop:apple:doctor":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:csr":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:init":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:package":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:provision":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:publish":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:probe":{path:"./commands/desktop-probe.js",load:()=>import("./commands/desktop-probe.js"),exportName:"desktopProbe"},"integrations:probe":{path:"./commands/integrations-probe.js",load:()=>import("./commands/integrations-probe.js"),exportName:"integrationsProbe"},dns:{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},"dns:pull":{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},"dns:diff":{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},"dns:sync":{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},doctor:{path:"./commands/doctor.js",load:()=>import("./commands/doctor.js"),exportName:"doctor"},domains:{path:"./commands/domains.js",load:()=>import("./commands/domains.js"),exportName:"domains"},email:{path:"./commands/email.js",load:()=>import("./commands/email.js"),exportName:"email"},env:{path:"./commands/env.js",load:()=>import("./commands/env.js"),exportName:"env"},"extension:init":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:build":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:package":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:chrome:publish":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:chrome:status":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:firefox:publish":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:init":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:provision":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:app":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:publish":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"dashboard:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"dashboard:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"commerce:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"commerce:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"commerce:import":{path:"./commands/commerce-import.js",load:()=>import("./commands/commerce-import.js"),exportName:"commerceImport"},"cms:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"cms:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"marketing:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"marketing:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"monitoring:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"monitoring:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"realtime:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"realtime:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"queue:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"queue:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"db:backup":{path:"./commands/db.js",load:()=>import("./commands/db.js"),exportName:"db"},"db:backups":{path:"./commands/db.js",load:()=>import("./commands/db.js"),exportName:"db"},"db:restore":{path:"./commands/db.js",load:()=>import("./commands/db.js"),exportName:"db"},"storage:backup":{path:"./commands/storage-backup.js",load:()=>import("./commands/storage-backup.js"),exportName:"storageBackup"},"storage:backups":{path:"./commands/storage-backup.js",load:()=>import("./commands/storage-backup.js"),exportName:"storageBackup"},"storage:restore":{path:"./commands/storage-backup.js",load:()=>import("./commands/storage-backup.js"),exportName:"storageBackup"},"feedback:invite":{path:"./commands/feedback.js",load:()=>import("./commands/feedback.js"),exportName:"feedback"},"feedback:tokens":{path:"./commands/feedback.js",load:()=>import("./commands/feedback.js"),exportName:"feedback"},"feedback:revoke":{path:"./commands/feedback.js",load:()=>import("./commands/feedback.js"),exportName:"feedback"},"socials:identities":{path:"./commands/socials.js",load:()=>import("./commands/socials.js"),exportName:"socials"},"socials:authorize":{path:"./commands/socials.js",load:()=>import("./commands/socials.js"),exportName:"socials"},fresh:{path:"./commands/fresh.js",load:()=>import("./commands/fresh.js"),exportName:"fresh"},generate:{path:"./commands/generate.js",load:()=>import("./commands/generate.js"),exportName:"generate"},"gdpr:export":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:erase":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:prune":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:register":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:register:check":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},http:{path:"./commands/http.js",load:()=>import("./commands/http.js"),exportName:"http"},install:{path:"./commands/install.js",load:()=>import("./commands/install.js"),exportName:"install"},key:{path:"./commands/key.js",load:()=>import("./commands/key.js"),exportName:"key"},lint:{path:"./commands/lint.js",load:()=>import("./commands/lint.js"),exportName:"lint"},format:{path:"./commands/lint.js",load:()=>import("./commands/lint.js"),exportName:"lint"},"format:check":{path:"./commands/lint.js",load:()=>import("./commands/lint.js"),exportName:"lint"},libs:{path:"./commands/libs.js",load:()=>import("./commands/libs.js"),exportName:"libs"},libraries:{path:"./commands/libs.js",load:()=>import("./commands/libs.js"),exportName:"libs"},list:{path:"./commands/list.js",load:()=>import("./commands/list.js"),exportName:"list"},mail:{path:"./commands/mail.js",load:()=>import("./commands/mail.js"),exportName:"mailCommands"},"mail:preview":{path:"./commands/mail.js",load:()=>import("./commands/mail.js"),exportName:"mailCommands"},maintenance:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},down:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},up:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},status:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},"coming-soon":{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},"coming-soon:status":{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},launch:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},make:{path:"./commands/make.js",load:()=>import("./commands/make.js"),exportName:"make"},"scaffold:crud":{path:"./commands/make.js",load:()=>import("./commands/make.js"),exportName:"make"},migrate:{path:"./commands/migrate.js",load:()=>import("./commands/migrate.js"),exportName:"migrate"},"migrate:fresh":{path:"./commands/migrate.js",load:()=>import("./commands/migrate.js"),exportName:"migrate"},"migrate:switch":{path:"./commands/migrate.js",load:()=>import("./commands/migrate.js"),exportName:"migrate"},"migrate:project":{path:"./commands/migrate-project.js",load:()=>import("./commands/migrate-project.js"),exportName:"migrateProject"},outdated:{path:"./commands/outdated.js",load:()=>import("./commands/outdated.js"),exportName:"outdated"},package:{path:"./commands/package.js",load:()=>import("./commands/package.js"),exportName:"packageCommands"},phone:{path:"./commands/phone.js",load:()=>import("./commands/phone.js"),exportName:"phone"},ports:{path:"./commands/ports.js",load:()=>import("./commands/ports.js"),exportName:"ports"},"server:flash":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:first-boot":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:doctor":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:setup":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:trust":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"link:core":{path:"./commands/link.js",load:()=>import("./commands/link.js"),exportName:"link"},"comments:list":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"comments:approve":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"comments:reject":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"comments:delete":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"user:add":{path:"./commands/user.js",load:()=>import("./commands/user.js"),exportName:"user"},"user:list":{path:"./commands/user.js",load:()=>import("./commands/user.js"),exportName:"user"},"unlink:core":{path:"./commands/link.js",load:()=>import("./commands/link.js"),exportName:"link"},prepublish:{path:"./commands/prepublish.js",load:()=>import("./commands/prepublish.js"),exportName:"prepublish"},projects:{path:"./commands/projects.js",load:()=>import("./commands/projects.js"),exportName:"projects"},publish:{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:model":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:controller":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:middleware":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:action":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:component":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:view":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:core":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"core:status":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:defaults":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},unpublish:{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},queue:{path:"./commands/queue.js",load:()=>import("./commands/queue.js"),exportName:"queue"},release:{path:"./commands/release.js",load:()=>import("./commands/release.js"),exportName:"release"},route:{path:"./commands/route.js",load:()=>import("./commands/route.js"),exportName:"route"},saas:{path:"./commands/saas.js",load:()=>import("./commands/saas.js"),exportName:"saas"},"stripe:setup":{path:"./commands/saas.js",load:()=>import("./commands/saas.js"),exportName:"saas"},schedule:{path:"./commands/schedule.js",load:()=>import("./commands/schedule.js"),exportName:"schedule"},search:{path:"./commands/search.js",load:()=>import("./commands/search.js"),exportName:"search"},"search-engine:update":{path:"./commands/search.js",load:()=>import("./commands/search.js"),exportName:"search"},"search-engine:settings":{path:"./commands/search.js",load:()=>import("./commands/search.js"),exportName:"search"},seed:{path:"./commands/seed.js",load:()=>import("./commands/seed.js"),exportName:"seed"},"seed:roles":{path:"./commands/seed.js",load:()=>import("./commands/seed.js"),exportName:"seed"},"roles:seed":{path:"./commands/seed.js",load:()=>import("./commands/seed.js"),exportName:"seed"},preview:{path:"./commands/serve.js",load:()=>import("./commands/serve.js"),exportName:"preview"},serve:{path:"./commands/serve.js",load:()=>import("./commands/serve.js"),exportName:"serve"},"serve:api":{path:"./commands/serve.js",load:()=>import("./commands/serve.js"),exportName:"serveApi"},setup:{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},ensure:{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:ai":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:ssl":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:ios":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:oh-my-zsh":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"ai:setup":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},share:{path:"./commands/share.js",load:()=>import("./commands/share.js"),exportName:"share"},"simulator:doctor":{path:"./commands/simulator.js",load:()=>import("./commands/simulator.js"),exportName:"simulator"},"simulator:open":{path:"./commands/simulator.js",load:()=>import("./commands/simulator.js"),exportName:"simulator"},stack:{path:"./commands/stacks.js",load:()=>import("./commands/stacks.js"),exportName:"stacks"},sms:{path:"./commands/sms.js",load:()=>import("./commands/sms.js"),exportName:"sms"},telemetry:{path:"./commands/telemetry.js",load:()=>import("./commands/telemetry.js"),exportName:"telemetryCommand"},test:{path:"./commands/test.js",load:()=>import("./commands/test.js"),exportName:"test"},tinker:{path:"./commands/tinker.js",load:()=>import("./commands/tinker.js"),exportName:"tinker"},types:{path:"./commands/types.js",load:()=>import("./commands/types.js"),exportName:"types"},undeploy:{path:"./commands/cloud.js",load:()=>import("./commands/cloud.js"),exportName:"cloud"},upgrade:{path:"./commands/upgrade.js",load:()=>import("./commands/upgrade.js"),exportName:"upgrade"},version:{path:"./commands/version.js",load:()=>import("./commands/version.js"),exportName:"version"},"docs:buddy":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:native-skills":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:native-skills:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:buddy:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:artifacts":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:artifacts:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:links":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:links:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"}},commandGroups={minimal:["version","help"],development:["dev","build","test","lint"],database:["migrate","seed","fresh"],scaffolding:["make","generate"],deployment:["deploy","release","cloud","server"],info:["about","doctor","list"]},loadedRegistrars=new WeakMap;function loaderKey(loader){return`${loader.path}#${loader.exportName}`}export function markLoaded(buddy,commandName){const loader=commandRegistry[commandName];if(!loader)return;let set=loadedRegistrars.get(buddy);if(!set){set=new Set;loadedRegistrars.set(buddy,set)}set.add(loaderKey(loader))}export async function loadCommand(commandName,buddy){const loader=commandRegistry[commandName];if(!loader)return!1;let set=loadedRegistrars.get(buddy);if(!set){set=new Set;loadedRegistrars.set(buddy,set)}const key=loaderKey(loader);if(set.has(key))return!0;set.add(key);try{const commandFunction=(await loader.load())[loader.exportName];if(typeof commandFunction==="function"){commandFunction(buddy);return!0}else return!1}catch{return!1}}export async function loadCommands(commandNames,buddy){const seen=new Set,unique=[];for(const name of commandNames){const loader=commandRegistry[name],key=loader?`${loader.path}#${loader.exportName}`:`name:${name}`;if(seen.has(key))continue;seen.add(key);unique.push(name)}await Promise.all(unique.map(async(name)=>{try{await withTimeout(loadCommand(name,buddy),5000)}catch{}}))}export async function loadCommandGroup(groupName,buddy){const commands=commandGroups[groupName];if(commands)await loadCommands(commands,buddy)}export async function loadAllCommands(buddy){const allCommands=Object.keys(commandRegistry);await loadCommands(allCommands,buddy)}export function getCommandNames(){return Object.keys(commandRegistry)}export function getCommandsToLoad(args){const requestedCommand=args[0],isVersionFlag=requestedCommand==="--version"||requestedCommand==="-v",isHelpFlag=requestedCommand==="--help"||requestedCommand==="-h",isHelpWord=requestedCommand==="help";if(isVersionFlag)return["version"];if(!requestedCommand||isHelpFlag||isHelpWord)return Object.keys(commandRegistry);const baseCommand=requestedCommand.split(":")[0];if(baseCommand==="list")return["list",...Object.keys(commandRegistry).filter((k)=>k!=="list")];if(commandRegistry[requestedCommand])return[requestedCommand];if(commandRegistry[baseCommand])return[baseCommand];return Object.keys(commandRegistry)}
1
+ export async function withTimeout(work,ms){let timeoutId;const deadline=new Promise((_,reject)=>{timeoutId=setTimeout(()=>reject(Error("timeout")),ms)});try{return await Promise.race([work,deadline])}finally{if(timeoutId!==void 0)clearTimeout(timeoutId)}}const commandRegistry={about:{path:"./commands/about.js",load:()=>import("./commands/about.js"),exportName:"about"},add:{path:"./commands/add.js",load:()=>import("./commands/add.js"),exportName:"add"},"ai:access":{path:"./commands/ai-access.js",load:()=>import("./commands/ai-access.js"),exportName:"aiAccess"},"ai:context":{path:"./commands/ai-context.js",load:()=>import("./commands/ai-context.js"),exportName:"aiContext"},auth:{path:"./commands/auth.js",load:()=>import("./commands/auth.js"),exportName:"auth"},build:{path:"./commands/build.js",load:()=>import("./commands/build.js"),exportName:"build"},cd:{path:"./commands/cd.js",load:()=>import("./commands/cd.js"),exportName:"cd"},changelog:{path:"./commands/changelog.js",load:()=>import("./commands/changelog.js"),exportName:"changelog"},clean:{path:"./commands/clean.js",load:()=>import("./commands/clean.js"),exportName:"clean"},cloud:{path:"./commands/cloud.js",load:()=>import("./commands/cloud.js"),exportName:"cloud"},commit:{path:"./commands/commit.js",load:()=>import("./commands/commit.js"),exportName:"commit"},completion:{path:"./commands/completion.js",load:()=>import("./commands/completion.js"),exportName:"completion"},"config:migrate":{path:"./commands/config-migrate.js",load:()=>import("./commands/config-migrate.js"),exportName:"configMigrate"},configure:{path:"./commands/configure.js",load:()=>import("./commands/configure.js"),exportName:"configure"},create:{path:"./commands/create.js",load:()=>import("./commands/create.js"),exportName:"create"},new:{path:"./commands/create.js",load:()=>import("./commands/create.js"),exportName:"create"},deploy:{path:"./commands/deploy.js",load:()=>import("./commands/deploy.js"),exportName:"deploy"},"deploy:rollback":{path:"./commands/deploy.js",load:()=>import("./commands/deploy.js"),exportName:"deploy"},dev:{path:"./commands/dev.js",load:()=>import("./commands/dev.js"),exportName:"dev"},"desktop:apple:doctor":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:csr":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:init":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:package":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:provision":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:apple:publish":{path:"./commands/desktop-apple.js",load:()=>import("./commands/desktop-apple.js"),exportName:"desktopApple"},"desktop:probe":{path:"./commands/desktop-probe.js",load:()=>import("./commands/desktop-probe.js"),exportName:"desktopProbe"},"integrations:probe":{path:"./commands/integrations-probe.js",load:()=>import("./commands/integrations-probe.js"),exportName:"integrationsProbe"},dns:{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},"dns:pull":{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},"dns:diff":{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},"dns:sync":{path:"./commands/dns.js",load:()=>import("./commands/dns.js"),exportName:"dns"},doctor:{path:"./commands/doctor.js",load:()=>import("./commands/doctor.js"),exportName:"doctor"},domains:{path:"./commands/domains.js",load:()=>import("./commands/domains.js"),exportName:"domains"},email:{path:"./commands/email.js",load:()=>import("./commands/email.js"),exportName:"email"},env:{path:"./commands/env.js",load:()=>import("./commands/env.js"),exportName:"env"},"extension:init":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:build":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:package":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:chrome:publish":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:chrome:status":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:firefox:publish":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:init":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:provision":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:app":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"extension:safari:publish":{path:"./commands/extension.js",load:()=>import("./commands/extension.js"),exportName:"extension"},"dashboard:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"dashboard:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"commerce:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"commerce:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"commerce:import":{path:"./commands/commerce-import.js",load:()=>import("./commands/commerce-import.js"),exportName:"commerceImport"},"cms:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"cms:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"marketing:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"marketing:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"monitoring:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"monitoring:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"realtime:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"realtime:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"queue:install":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"queue:uninstall":{path:"./commands/features.js",load:()=>import("./commands/features.js"),exportName:"features"},"db:backup":{path:"./commands/db.js",load:()=>import("./commands/db.js"),exportName:"db"},"db:backups":{path:"./commands/db.js",load:()=>import("./commands/db.js"),exportName:"db"},"db:restore":{path:"./commands/db.js",load:()=>import("./commands/db.js"),exportName:"db"},"storage:backup":{path:"./commands/storage-backup.js",load:()=>import("./commands/storage-backup.js"),exportName:"storageBackup"},"storage:backups":{path:"./commands/storage-backup.js",load:()=>import("./commands/storage-backup.js"),exportName:"storageBackup"},"storage:restore":{path:"./commands/storage-backup.js",load:()=>import("./commands/storage-backup.js"),exportName:"storageBackup"},"feedback:invite":{path:"./commands/feedback.js",load:()=>import("./commands/feedback.js"),exportName:"feedback"},"feedback:tokens":{path:"./commands/feedback.js",load:()=>import("./commands/feedback.js"),exportName:"feedback"},"feedback:revoke":{path:"./commands/feedback.js",load:()=>import("./commands/feedback.js"),exportName:"feedback"},"socials:identities":{path:"./commands/socials.js",load:()=>import("./commands/socials.js"),exportName:"socials"},"socials:authorize":{path:"./commands/socials.js",load:()=>import("./commands/socials.js"),exportName:"socials"},fresh:{path:"./commands/fresh.js",load:()=>import("./commands/fresh.js"),exportName:"fresh"},generate:{path:"./commands/generate.js",load:()=>import("./commands/generate.js"),exportName:"generate"},"gdpr:export":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:erase":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:prune":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:register":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},"gdpr:register:check":{path:"./commands/gdpr.js",load:()=>import("./commands/gdpr.js"),exportName:"gdpr"},http:{path:"./commands/http.js",load:()=>import("./commands/http.js"),exportName:"http"},install:{path:"./commands/install.js",load:()=>import("./commands/install.js"),exportName:"install"},key:{path:"./commands/key.js",load:()=>import("./commands/key.js"),exportName:"key"},lint:{path:"./commands/lint.js",load:()=>import("./commands/lint.js"),exportName:"lint"},format:{path:"./commands/lint.js",load:()=>import("./commands/lint.js"),exportName:"lint"},"format:check":{path:"./commands/lint.js",load:()=>import("./commands/lint.js"),exportName:"lint"},libs:{path:"./commands/libs.js",load:()=>import("./commands/libs.js"),exportName:"libs"},libraries:{path:"./commands/libs.js",load:()=>import("./commands/libs.js"),exportName:"libs"},list:{path:"./commands/list.js",load:()=>import("./commands/list.js"),exportName:"list"},mail:{path:"./commands/mail.js",load:()=>import("./commands/mail.js"),exportName:"mailCommands"},"mail:preview":{path:"./commands/mail.js",load:()=>import("./commands/mail.js"),exportName:"mailCommands"},maintenance:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},down:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},up:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},status:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},"coming-soon":{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},"coming-soon:status":{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},launch:{path:"./commands/maintenance.js",load:()=>import("./commands/maintenance.js"),exportName:"maintenance"},make:{path:"./commands/make.js",load:()=>import("./commands/make.js"),exportName:"make"},"scaffold:crud":{path:"./commands/make.js",load:()=>import("./commands/make.js"),exportName:"make"},migrate:{path:"./commands/migrate.js",load:()=>import("./commands/migrate.js"),exportName:"migrate"},"migrate:fresh":{path:"./commands/migrate.js",load:()=>import("./commands/migrate.js"),exportName:"migrate"},"migrate:switch":{path:"./commands/migrate.js",load:()=>import("./commands/migrate.js"),exportName:"migrate"},"migrate:project":{path:"./commands/migrate-project.js",load:()=>import("./commands/migrate-project.js"),exportName:"migrateProject"},outdated:{path:"./commands/outdated.js",load:()=>import("./commands/outdated.js"),exportName:"outdated"},package:{path:"./commands/package.js",load:()=>import("./commands/package.js"),exportName:"packageCommands"},phone:{path:"./commands/phone.js",load:()=>import("./commands/phone.js"),exportName:"phone"},ports:{path:"./commands/ports.js",load:()=>import("./commands/ports.js"),exportName:"ports"},"security:scan":{path:"./commands/security.js",load:()=>import("./commands/security.js"),exportName:"security"},audit:{path:"./commands/security.js",load:()=>import("./commands/security.js"),exportName:"security"},"server:flash":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:first-boot":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:doctor":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:setup":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"server:trust":{path:"./commands/server.js",load:()=>import("./commands/server.js"),exportName:"server"},"link:core":{path:"./commands/link.js",load:()=>import("./commands/link.js"),exportName:"link"},"comments:list":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"comments:approve":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"comments:reject":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"comments:delete":{path:"./commands/comments.js",load:()=>import("./commands/comments.js"),exportName:"commentsCommands"},"user:add":{path:"./commands/user.js",load:()=>import("./commands/user.js"),exportName:"user"},"user:list":{path:"./commands/user.js",load:()=>import("./commands/user.js"),exportName:"user"},"unlink:core":{path:"./commands/link.js",load:()=>import("./commands/link.js"),exportName:"link"},prepublish:{path:"./commands/prepublish.js",load:()=>import("./commands/prepublish.js"),exportName:"prepublish"},projects:{path:"./commands/projects.js",load:()=>import("./commands/projects.js"),exportName:"projects"},publish:{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:model":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:controller":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:middleware":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:action":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:component":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:view":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:core":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"core:status":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},"publish:defaults":{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},unpublish:{path:"./commands/publish.js",load:()=>import("./commands/publish.js"),exportName:"publish"},queue:{path:"./commands/queue.js",load:()=>import("./commands/queue.js"),exportName:"queue"},release:{path:"./commands/release.js",load:()=>import("./commands/release.js"),exportName:"release"},route:{path:"./commands/route.js",load:()=>import("./commands/route.js"),exportName:"route"},saas:{path:"./commands/saas.js",load:()=>import("./commands/saas.js"),exportName:"saas"},"stripe:setup":{path:"./commands/saas.js",load:()=>import("./commands/saas.js"),exportName:"saas"},schedule:{path:"./commands/schedule.js",load:()=>import("./commands/schedule.js"),exportName:"schedule"},search:{path:"./commands/search.js",load:()=>import("./commands/search.js"),exportName:"search"},"search-engine:update":{path:"./commands/search.js",load:()=>import("./commands/search.js"),exportName:"search"},"search-engine:settings":{path:"./commands/search.js",load:()=>import("./commands/search.js"),exportName:"search"},seed:{path:"./commands/seed.js",load:()=>import("./commands/seed.js"),exportName:"seed"},"seed:roles":{path:"./commands/seed.js",load:()=>import("./commands/seed.js"),exportName:"seed"},"roles:seed":{path:"./commands/seed.js",load:()=>import("./commands/seed.js"),exportName:"seed"},preview:{path:"./commands/serve.js",load:()=>import("./commands/serve.js"),exportName:"preview"},serve:{path:"./commands/serve.js",load:()=>import("./commands/serve.js"),exportName:"serve"},"serve:api":{path:"./commands/serve.js",load:()=>import("./commands/serve.js"),exportName:"serveApi"},setup:{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},ensure:{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:ai":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:ssl":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:ios":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"setup:oh-my-zsh":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},"ai:setup":{path:"./commands/setup.js",load:()=>import("./commands/setup.js"),exportName:"setup"},share:{path:"./commands/share.js",load:()=>import("./commands/share.js"),exportName:"share"},"simulator:doctor":{path:"./commands/simulator.js",load:()=>import("./commands/simulator.js"),exportName:"simulator"},"simulator:open":{path:"./commands/simulator.js",load:()=>import("./commands/simulator.js"),exportName:"simulator"},stack:{path:"./commands/stacks.js",load:()=>import("./commands/stacks.js"),exportName:"stacks"},sms:{path:"./commands/sms.js",load:()=>import("./commands/sms.js"),exportName:"sms"},telemetry:{path:"./commands/telemetry.js",load:()=>import("./commands/telemetry.js"),exportName:"telemetryCommand"},test:{path:"./commands/test.js",load:()=>import("./commands/test.js"),exportName:"test"},tinker:{path:"./commands/tinker.js",load:()=>import("./commands/tinker.js"),exportName:"tinker"},types:{path:"./commands/types.js",load:()=>import("./commands/types.js"),exportName:"types"},undeploy:{path:"./commands/cloud.js",load:()=>import("./commands/cloud.js"),exportName:"cloud"},upgrade:{path:"./commands/upgrade.js",load:()=>import("./commands/upgrade.js"),exportName:"upgrade"},version:{path:"./commands/version.js",load:()=>import("./commands/version.js"),exportName:"version"},"docs:buddy":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:native-skills":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:native-skills:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:buddy:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:artifacts":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:artifacts:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:links":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"},"docs:links:check":{path:"./commands/docs.js",load:()=>import("./commands/docs.js"),exportName:"docs"}},commandGroups={minimal:["version","help"],development:["dev","build","test","lint"],database:["migrate","seed","fresh"],scaffolding:["make","generate"],deployment:["deploy","release","cloud","server"],info:["about","doctor","list"]},loadedRegistrars=new WeakMap;function loaderKey(loader){return`${loader.path}#${loader.exportName}`}export function markLoaded(buddy,commandName){const loader=commandRegistry[commandName];if(!loader)return;let set=loadedRegistrars.get(buddy);if(!set){set=new Set;loadedRegistrars.set(buddy,set)}set.add(loaderKey(loader))}export async function loadCommand(commandName,buddy){const loader=commandRegistry[commandName];if(!loader)return!1;let set=loadedRegistrars.get(buddy);if(!set){set=new Set;loadedRegistrars.set(buddy,set)}const key=loaderKey(loader);if(set.has(key))return!0;set.add(key);try{const commandFunction=(await loader.load())[loader.exportName];if(typeof commandFunction==="function"){commandFunction(buddy);return!0}else return!1}catch{return!1}}export async function loadCommands(commandNames,buddy){const seen=new Set,unique=[];for(const name of commandNames){const loader=commandRegistry[name],key=loader?`${loader.path}#${loader.exportName}`:`name:${name}`;if(seen.has(key))continue;seen.add(key);unique.push(name)}await Promise.all(unique.map(async(name)=>{try{await withTimeout(loadCommand(name,buddy),5000)}catch{}}))}export async function loadCommandGroup(groupName,buddy){const commands=commandGroups[groupName];if(commands)await loadCommands(commands,buddy)}export async function loadAllCommands(buddy){const allCommands=Object.keys(commandRegistry);await loadCommands(allCommands,buddy)}export function getCommandNames(){return Object.keys(commandRegistry)}export function getCommandsToLoad(args){const requestedCommand=args[0],isVersionFlag=requestedCommand==="--version"||requestedCommand==="-v",isHelpFlag=requestedCommand==="--help"||requestedCommand==="-h",isHelpWord=requestedCommand==="help";if(isVersionFlag)return["version"];if(!requestedCommand||isHelpFlag||isHelpWord)return Object.keys(commandRegistry);const baseCommand=requestedCommand.split(":")[0];if(baseCommand==="list")return["list",...Object.keys(commandRegistry).filter((k)=>k!=="list")];if(commandRegistry[requestedCommand])return[requestedCommand];if(commandRegistry[baseCommand])return[baseCommand];return Object.keys(commandRegistry)}
@@ -0,0 +1,2 @@
1
+ /** Returns the env files that were changed. An app that already names its bundles is left alone. */
2
+ export declare function ensureAuthRoutesEnv(root: string): string[];
@@ -0,0 +1,5 @@
1
+ import{existsSync,readFileSync,writeFileSync}from"node:fs";import{join}from"node:path";const COMMENT="# Mount the login/register/token routes without the admin dashboard.";export function ensureAuthRoutesEnv(root){const changed=[];for(const name of[".env.example",".env"]){const file=join(root,name);if(!existsSync(file))continue;const contents=readFileSync(file,"utf8");if(/^\s*STACKS_DEFAULT_ROUTES\s*=/m.test(contents))continue;writeFileSync(file,`${contents.replace(/\n*$/,`
2
+ `)}
3
+ ${COMMENT}
4
+ STACKS_DEFAULT_ROUTES=auth
5
+ `);changed.push(name)}return changed}
@@ -0,0 +1,22 @@
1
+ import type { AppIdentity } from './scaffold-app';
2
+ export declare function desktopScaffoldFiles(identity: AppIdentity): Record<string, string>;
3
+ /** Write the desktop files and scripts the app does not already have. Returns the paths written. */
4
+ export declare function scaffoldDesktopApp(root: string, identity: AppIdentity): string[];
5
+ /**
6
+ * `buddy new <name> --desktop`: a Stacks app that is also a native Mac app.
7
+ * Its stx screens are drawn by Craft's AppKit host, and its API runs inside
8
+ * the app (`macos.agent`), so the app needs no server to work. The files
9
+ * below are the smallest working version of that: one screen that calls one
10
+ * route the agent serves, and the scripts that build and open it.
11
+ *
12
+ * Writes only what is missing, so running it over an app that already has a
13
+ * `config/desktop.ts` (or a screen of its own) changes nothing it holds.
14
+ * @defaultValue
15
+ * ```ts
16
+ * {
17
+ * 'dev:desktop': './buddy dev:desktop',
18
+ * 'build:desktop': './buddy build:desktop'
19
+ * }
20
+ * ```
21
+ */
22
+ export declare const DESKTOP_SCRIPTS: Record<string, string>;
@@ -0,0 +1,55 @@
1
+ import{existsSync,mkdirSync,readFileSync,writeFileSync}from"node:fs";import{dirname,join}from"node:path";export const DESKTOP_SCRIPTS={"dev:desktop":"./buddy dev:desktop","build:desktop":"./buddy build:desktop"};export function desktopScaffoldFiles(identity){const name=JSON.stringify(identity.displayName);return{"config/desktop.ts":`import type { DesktopConfig } from '@stacksjs/types'
2
+
3
+ /**
4
+ * The Mac app: the stx screens in resources/native, drawn by Craft's AppKit
5
+ * host (no web view), with this project's API running inside the app.
6
+ *
7
+ * \`agent: true\` starts the routes on 127.0.0.1 when the app opens and points
8
+ * the window at them: \`routes/api.ts\` (under /api) and the agent's own routes
9
+ * in app/Desktop/Agent.ts. Only the app's own window can call them.
10
+ *
11
+ * \`bun run dev:desktop\` builds the screens and opens the app;
12
+ * \`bun run build:desktop\` builds a signed \`.app\` in storage/framework/desktop/macos.
13
+ */
14
+ export default {
15
+ macos: {
16
+ appName: ${name},
17
+ bundleId: ${JSON.stringify(identity.bundleId)},
18
+ agent: true,
19
+ nativeScreens: ['Main'],
20
+ window: { width: 1100, height: 720, minWidth: 720, minHeight: 480 },
21
+ },
22
+ } satisfies DesktopConfig
23
+ `,"resources/native/Main.stx":`<script client>
24
+ // A native screen: AppKit views, not HTML. \`fetch\` with a relative URL goes
25
+ // to the app's agent, which answers from app/Desktop/Agent.ts and routes/.
26
+ const status = state('Starting...')
27
+
28
+ onMount(async () => {
29
+ try {
30
+ const response = await fetch('/api/desktop/status')
31
+ const body = await response.json()
32
+ status.set(\`\${body.app} is running its own API on this Mac.\`)
33
+ }
34
+ catch {
35
+ status.set('The agent did not answer. Is the app running from buddy dev:desktop?')
36
+ }
37
+ })
38
+ </script>
39
+
40
+ <View class="flex-1 flex-col gap-3 items-center justify-center p-8" testID="main">
41
+ <Text class="font-semibold text-[22px]" :text="'${identity.displayName}'" />
42
+ <Text class="text-[13px]" style="color: secondaryLabel" :text="status()" testID="main-status" />
43
+ </View>
44
+ `,"app/Desktop/Agent.ts":`import { response, route } from '@stacksjs/router'
45
+
46
+ /**
47
+ * Routes only the desktop app serves. They run inside the app, on 127.0.0.1,
48
+ * next to everything in routes/ - which is mounted as \`app/Routes.ts\` says
49
+ * (\`routes/api.ts\` under /api). These are not prefixed: write the full path.
50
+ *
51
+ * Whatever this file imports is compiled into the built app.
52
+ */
53
+ route.get('/api/desktop/status', () => response.json({ app: ${name}, ok: true }))
54
+ `}}export function scaffoldDesktopApp(root,identity){const written=[];for(const[file,content]of Object.entries(desktopScaffoldFiles(identity))){const target=join(root,file);if(existsSync(target))continue;mkdirSync(dirname(target),{recursive:!0});writeFileSync(target,content);written.push(file)}const manifest=join(root,"package.json");if(existsSync(manifest)){const source=readFileSync(manifest,"utf8"),pkg=JSON.parse(source),missing=Object.entries(DESKTOP_SCRIPTS).filter(([script])=>!pkg.scripts?.[script]);if(missing.length){pkg.scripts={...pkg.scripts,...Object.fromEntries(missing)};const indent=/^\{\n(\s+)"/.exec(source)?.[1]??" ";writeFileSync(manifest,`${JSON.stringify(pkg,null,indent)}
55
+ `);written.push("package.json")}}return written}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.390",
5
+ "version": "0.75.397",
6
6
  "description": "Meet Buddy. The Stacks runtime.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -95,72 +95,72 @@
95
95
  "prepublishOnly": "bun run build"
96
96
  },
97
97
  "dependencies": {
98
- "@stacksjs/actions": "0.75.390",
99
- "@stacksjs/ai": "0.75.390",
100
- "@stacksjs/alias": "0.75.390",
101
- "@stacksjs/analytics": "0.75.390",
102
- "@stacksjs/api": "0.75.390",
103
- "@stacksjs/arrays": "0.75.390",
104
- "@stacksjs/auth": "0.75.390",
105
- "@stacksjs/browser-extension": "0.75.390",
106
- "@stacksjs/build": "0.75.390",
98
+ "@stacksjs/actions": "0.75.397",
99
+ "@stacksjs/ai": "0.75.397",
100
+ "@stacksjs/alias": "0.75.397",
101
+ "@stacksjs/analytics": "0.75.397",
102
+ "@stacksjs/api": "0.75.397",
103
+ "@stacksjs/arrays": "0.75.397",
104
+ "@stacksjs/auth": "0.75.397",
105
+ "@stacksjs/browser-extension": "0.75.397",
106
+ "@stacksjs/build": "0.75.397",
107
107
  "@stacksjs/bumpx": "^0.2.26",
108
- "@stacksjs/cache": "0.75.390",
109
- "@stacksjs/chat": "0.75.390",
108
+ "@stacksjs/cache": "0.75.397",
109
+ "@stacksjs/chat": "0.75.397",
110
110
  "@stacksjs/clapp": "^0.2.16",
111
- "@stacksjs/cli": "0.75.390",
112
- "@stacksjs/cloud": "0.75.390",
113
- "@stacksjs/cms": "0.75.390",
114
- "@stacksjs/collections": "0.75.390",
115
- "@stacksjs/commerce": "0.75.390",
116
- "@stacksjs/config": "0.75.390",
117
- "@stacksjs/database": "0.75.390",
118
- "@stacksjs/desktop-build": "0.75.390",
119
- "@stacksjs/dns": "0.75.390",
111
+ "@stacksjs/cli": "0.75.397",
112
+ "@stacksjs/cloud": "0.75.397",
113
+ "@stacksjs/cms": "0.75.397",
114
+ "@stacksjs/collections": "0.75.397",
115
+ "@stacksjs/commerce": "0.75.397",
116
+ "@stacksjs/config": "0.75.397",
117
+ "@stacksjs/database": "0.75.397",
118
+ "@stacksjs/desktop-build": "0.75.397",
119
+ "@stacksjs/dns": "0.75.397",
120
120
  "@stacksjs/dnsx": "^0.2.3",
121
- "@stacksjs/email": "0.75.390",
122
- "@stacksjs/enums": "0.75.390",
123
- "@stacksjs/env": "0.75.390",
124
- "@stacksjs/error-handling": "0.75.390",
125
- "@stacksjs/events": "0.75.390",
126
- "@stacksjs/features": "0.75.390",
127
- "@stacksjs/git": "0.75.390",
121
+ "@stacksjs/email": "0.75.397",
122
+ "@stacksjs/enums": "0.75.397",
123
+ "@stacksjs/env": "0.75.397",
124
+ "@stacksjs/error-handling": "0.75.397",
125
+ "@stacksjs/events": "0.75.397",
126
+ "@stacksjs/features": "0.75.397",
127
+ "@stacksjs/git": "0.75.397",
128
128
  "@stacksjs/gitit": "^0.2.5",
129
- "@stacksjs/health": "0.75.390",
129
+ "@stacksjs/health": "0.75.397",
130
130
  "@stacksjs/httx": "^0.1.10",
131
- "@stacksjs/image": "0.75.390",
132
- "@stacksjs/lint": "0.75.390",
133
- "@stacksjs/logging": "0.75.390",
134
- "@stacksjs/notifications": "0.75.390",
135
- "@stacksjs/objects": "0.75.390",
136
- "@stacksjs/orm": "0.75.390",
137
- "@stacksjs/path": "0.75.390",
138
- "@stacksjs/payments": "0.75.390",
139
- "@stacksjs/realtime": "0.75.390",
140
- "@stacksjs/router": "0.75.390",
131
+ "@stacksjs/image": "0.75.397",
132
+ "@stacksjs/lint": "0.75.397",
133
+ "@stacksjs/logging": "0.75.397",
134
+ "@stacksjs/notifications": "0.75.397",
135
+ "@stacksjs/objects": "0.75.397",
136
+ "@stacksjs/orm": "0.75.397",
137
+ "@stacksjs/path": "0.75.397",
138
+ "@stacksjs/payments": "0.75.397",
139
+ "@stacksjs/realtime": "0.75.397",
140
+ "@stacksjs/router": "0.75.397",
141
141
  "@stacksjs/rpx": "^0.11.54",
142
- "@stacksjs/scheduler": "0.75.390",
143
- "@stacksjs/search-engine": "0.75.390",
144
- "@stacksjs/security": "0.75.390",
145
- "@stacksjs/server": "0.75.390",
146
- "@stacksjs/sites": "0.75.390",
147
- "@stacksjs/skills": "0.75.390",
148
- "@stacksjs/socials": "0.75.390",
149
- "@stacksjs/storage": "0.75.390",
150
- "@stacksjs/strings": "0.75.390",
151
- "@stacksjs/stx": "0.2.752",
152
- "@stacksjs/testing": "0.75.390",
153
- "@stacksjs/tinker": "0.75.390",
142
+ "@stacksjs/scheduler": "0.75.397",
143
+ "@stacksjs/search-engine": "0.75.397",
144
+ "@stacksjs/security": "0.75.397",
145
+ "@stacksjs/server": "0.75.397",
146
+ "@stacksjs/sites": "0.75.397",
147
+ "@stacksjs/skills": "0.75.397",
148
+ "@stacksjs/socials": "0.75.397",
149
+ "@stacksjs/storage": "0.75.397",
150
+ "@stacksjs/strings": "0.75.397",
151
+ "@stacksjs/stx": "0.2.769",
152
+ "@stacksjs/testing": "0.75.397",
153
+ "@stacksjs/tinker": "0.75.397",
154
154
  "@stacksjs/tlsx": "^0.13.19",
155
- "@stacksjs/ts-cloud": "^0.16.89",
156
- "@stacksjs/tunnel": "0.75.390",
157
- "@stacksjs/types": "0.75.390",
158
- "@stacksjs/ui": "0.75.390",
159
- "@stacksjs/utils": "0.75.390",
160
- "@stacksjs/validation": "0.75.390",
155
+ "@stacksjs/ts-cloud": "^0.16.91",
156
+ "@stacksjs/tunnel": "0.75.397",
157
+ "@stacksjs/types": "0.75.397",
158
+ "@stacksjs/ui": "0.75.397",
159
+ "@stacksjs/utils": "0.75.397",
160
+ "@stacksjs/validation": "0.75.397",
161
161
  "ajv": "^8.20.0",
162
162
  "ajv-formats": "^3.0.1",
163
- "bun-plugin-stx": "0.2.752",
163
+ "bun-plugin-stx": "0.2.769",
164
164
  "ts-pantry": "^0.11.146"
165
165
  },
166
166
  "devDependencies": {