@stacksjs/buddy 0.75.388 → 0.75.390
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/commands/setup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{cpSync,existsSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import process from"node:process";import{IosToolchainError,loadIosDependencies,pantryExecutable,pantryInstallArgs,resolveXcodegen,runAction,XCODE_NOTE,xcodeDeveloperDir}from"@stacksjs/actions";import{log,onUnknownSubcommand,runCommand}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{handleError}from"@stacksjs/error-handling";import{path as p}from"@stacksjs/path";import{copyFile,storage}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";import{setupPrettyDevEnvironment}from"./dev";import{runInitialMigration}from"../initial-migration";import{resultFailed}from"../result";function getTimeoutMs(envVar,fallbackMs){const value=Number(process.env[envVar]);if(Number.isFinite(value)&&value>0)return value;return fallbackMs}const PANTRY_CHECK_TIMEOUT_MS=getTimeoutMs("PANTRY_CHECK_TIMEOUT_MS",15000),PANTRY_INSTALL_TIMEOUT_MS=getTimeoutMs("PANTRY_INSTALL_TIMEOUT_MS",600000),PANTRY_DEPENDENCIES_TIMEOUT_MS=getTimeoutMs("PANTRY_DEPENDENCIES_TIMEOUT_MS",1200000),KEYGEN_TIMEOUT_MS=getTimeoutMs("KEYGEN_TIMEOUT_MS",120000),AWS_CONFIG_TIMEOUT_MS=getTimeoutMs("AWS_CONFIG_TIMEOUT_MS",900000);export function setup(buddy){const descriptions={setup:"This command ensures your project is setup correctly",ssl:"Setup SSL certificates and hosts file for HTTPS development",ai:"Set the project up for an AI coding agent (Claude Code, Codex, Cursor, Copilot, Gemini)",copy:"Copy the agent files instead of symlinking them, so they can be edited per project",force:"Overwrite files that already exist",ohMyZsh:"Enable Oh My Zsh",aws:"Ensures AWS is connected to the project",project:"Target a specific project",verbose:"Enable verbose output",domain:"Custom domain to setup (defaults to APP_URL)",skipHosts:"Skip adding domain to hosts file",skipTrust:"Skip trusting the certificate",skipAws:"Skip AWS configuration during setup",skipKeygen:"Skip generating an application key during setup",ios:"Install the iOS build tools (`iosDependencies` in config/deps.ts) through Pantry and check for Xcode"};buddy.command("setup",descriptions.setup).alias("ensure").option("-p, --project [project]",descriptions.project,{default:!1}).option("--skip-aws",descriptions.skipAws,{default:!1}).option("--skip-keygen",descriptions.skipKeygen,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup` ...",options);await ensurePantryInstalled();await optimizePantryDeps();await initializeProject(options)});buddy.command("setup:ios",descriptions.ios).option("--verbose",descriptions.verbose,{default:!1}).action(async()=>{if(process.platform!=="darwin"){await log.error("The iOS toolchain installs on macOS only: Xcode and xcodegen do not run anywhere else.");process.exit(ExitCode.FatalError)}const root=p.projectPath(),dependencies=await loadIosDependencies(root),pantry=pantryExecutable(root);log.info(`Installing ${Object.entries(dependencies).map(([pkg,version])=>`${pkg}@${version}`).join(", ")} through Pantry`);const install=Bun.spawnSync([pantry,...pantryInstallArgs(dependencies)],{cwd:root,stdin:"inherit",stdout:"inherit",stderr:"inherit"});if(install.exitCode!==0){await log.error(`\`${pantry} install\` failed with exit code ${install.exitCode}. PANTRY_BIN can point at another Pantry.`);process.exit(ExitCode.FatalError)}try{const xcodegen=await resolveXcodegen(root);log.success(`xcodegen ${xcodegen.version}: ${xcodegen.path}`)}catch(error){await log.error(error instanceof IosToolchainError?error.message:String(error));process.exit(ExitCode.FatalError)}const xcode=xcodeDeveloperDir();if(xcode)log.success(`Xcode: ${xcode}`);else log.warn(`No Xcode is selected, so the project can be generated but not built or archived. ${XCODE_NOTE}`)});buddy.command("setup:ssl",descriptions.ssl).alias("ssl:setup").option("-d, --domain [domain]",descriptions.domain).option("--skip-hosts",descriptions.skipHosts,{default:!1}).option("--skip-trust",descriptions.skipTrust,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup:ssl` ...",options);if(!await setupPrettyDevEnvironment({domain:options.domain,skipHosts:options.skipHosts,skipTrust:options.skipTrust,verbose:options.verbose})){log.warn("SSL setup completed with warnings");log.info("You may need to manually trust certificates or update hosts file")}});buddy.command("setup:ai [provider]",descriptions.ai).alias("ai:setup").option("--copy",descriptions.copy,{default:!1}).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(provider,options)=>{log.debug("Running `buddy setup:ai` ...",options);const{AI_PROVIDERS,isAiProvider,reportAiSetup,setupAiProvider}=await import("./setup-ai");let id=provider;if(!id){if(!process.stdin.isTTY){await log.error(`\`setup:ai\` needs a provider when stdin is not a terminal. Pass one: ${AI_PROVIDERS.map((entry)=>`buddy setup:ai ${entry.id}`).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const{select}=await import("@stacksjs/cli");id=await select({message:"Which AI coding agent do you use?",choices:AI_PROVIDERS.map((entry)=>({value:entry.id,label:entry.label})),initial:0})}if(!id||!isAiProvider(id)){await log.error(`Unknown AI provider: ${id}. Expected one of: ${AI_PROVIDERS.map((entry)=>entry.id).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const definition=AI_PROVIDERS.find((entry)=>entry.id===id),{generateAutoImportFiles}=await import("@stacksjs/server/imports");await generateAutoImportFiles();reportAiSetup(definition,setupAiProvider(id,{copy:options.copy,force:options.force}))});buddy.command("setup:oh-my-zsh",descriptions.ohMyZsh).alias("upgrade:oh-my-zsh").option("--verbose",descriptions.verbose,{default:!1}).action(async(_options)=>{log.debug("Running `buddy setup:oh-my-zsh` ...",_options);const result=await runAction(Action.UpgradeShell);if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}});onUnknownSubcommand(buddy,"setup")}async function isPantryInstalled(){try{return(await runCommand("pantry --version",{silent:!0,timeoutMs:PANTRY_CHECK_TIMEOUT_MS})).isOk}catch{return!1}}async function installPantry(){const bundledInstaller=p.frameworkPath("scripts/pantry-install"),command=existsSync(bundledInstaller)?[bundledInstaller]:["sh","-c","curl -fsSL https://pantry.dev | bash"],result=await runCommand(command,{timeoutMs:PANTRY_INSTALL_TIMEOUT_MS}),localBin=join(homedir(),".local","bin");if(!process.env.PATH?.split(":").includes(localBin))process.env.PATH=`${localBin}:${process.env.PATH||""}`;if(result.isOk&&await isPantryInstalled())return;if(resultFailed(result))handleError(result.error);else await log.error("Pantry installed but is not available on PATH. Open a new shell and run `buddy setup` again.");process.exit(ExitCode.FatalError)}export async function ensurePantryInstalled(){if(await isPantryInstalled())return;log.info("Pantry is required. Installing it from https://pantry.dev...");await installPantry()}export async function ensurePantryDependencies(cwd){await ensurePantryInstalled();log.info("Installing project dependencies with Pantry...");const result=await runCommand("pantry install",{cwd,timeoutMs:PANTRY_DEPENDENCIES_TIMEOUT_MS});if(resultFailed(result)){handleError(result.error);process.exit(ExitCode.FatalError)}if(existsSync(join(cwd,"package.json"))&&!existsSync(join(cwd,"node_modules"))){await log.error("Pantry completed without installing the project JavaScript dependencies.");process.exit(ExitCode.FatalError)}log.success("Installed project dependencies with Pantry")}function hasAppKey(cwd){const envPath=join(cwd,".env");if(!existsSync(envPath))return!1;return/^APP_KEY=.+$/m.test(readFileSync(envPath,"utf-8"))}export async function ensureAppKey(cwd){if(hasAppKey(cwd)||process.env.APP_KEY&&process.env.APP_KEY.length>0){log.success("APP_KEY existed");return}const keyResult=await runCommand("./buddy key:generate",{cwd,timeoutMs:KEYGEN_TIMEOUT_MS});if(resultFailed(keyResult)){handleError(keyResult.error);process.exit(ExitCode.FatalError)}log.success("Generated application key")}async function databaseIsReachable(){try{const{describeTarget,probeTargetDatabase,resolveConnectionTarget}=await import("@stacksjs/database"),target=resolveConnectionTarget();if(!target)return{ok:!0};const probe=await probeTargetDatabase(target);if(probe.ok)return{ok:!0};return{ok:!1,reason:`${describeTarget(target)} is not reachable yet (${probe.kind})`}}catch{return{ok:!0}}}async function initializeProject(options){const cwd=options.cwd||p.projectPath();await ensurePantryDependencies(cwd);await ensureEnvIsSet(options);if(!options.skipKeygen)await ensureAppKey(cwd);const migration=await runInitialMigration({appEnv:process.env.APP_ENV||process.env.NODE_ENV||"local",isReachable:databaseIsReachable,migrate:()=>runAction(Action.Migrate,{cwd}),failed:resultFailed,log});ensureIdeSettings(cwd);if(!options.skipAws){log.info("Ensuring AWS is connected...");try{const awsResult=await runCommand("./buddy configure:aws",{cwd,timeoutMs:AWS_CONFIG_TIMEOUT_MS});if(resultFailed(awsResult)){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(awsResult.error)}else log.success("Configured AWS")}catch(error){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(error)}}if(migration==="failed"){process.stderr.write(`
|
|
1
|
+
import{cpSync,existsSync,readFileSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";import process from"node:process";import{IosToolchainError,loadIosDependencies,pantryExecutable,pantryInstallArgs,resolveXcodegen,runAction,XCODE_NOTE,xcodeDeveloperDir}from"@stacksjs/actions";import{log,onUnknownSubcommand,runCommand}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{handleError}from"@stacksjs/error-handling";import{path as p}from"@stacksjs/path";import{copyFile,storage}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";import{setupPrettyDevEnvironment}from"./dev";import{runInitialMigration}from"../initial-migration";import{syncDatabasePortToEnv}from"../pantry-service-ports";import{resolveCreatePolicy}from"../database-preflight";import{resultFailed}from"../result";function getTimeoutMs(envVar,fallbackMs){const value=Number(process.env[envVar]);if(Number.isFinite(value)&&value>0)return value;return fallbackMs}const PANTRY_CHECK_TIMEOUT_MS=getTimeoutMs("PANTRY_CHECK_TIMEOUT_MS",15000),PANTRY_INSTALL_TIMEOUT_MS=getTimeoutMs("PANTRY_INSTALL_TIMEOUT_MS",600000),PANTRY_DEPENDENCIES_TIMEOUT_MS=getTimeoutMs("PANTRY_DEPENDENCIES_TIMEOUT_MS",1200000),KEYGEN_TIMEOUT_MS=getTimeoutMs("KEYGEN_TIMEOUT_MS",120000),AWS_CONFIG_TIMEOUT_MS=getTimeoutMs("AWS_CONFIG_TIMEOUT_MS",900000);export function setup(buddy){const descriptions={setup:"This command ensures your project is setup correctly",ssl:"Setup SSL certificates and hosts file for HTTPS development",ai:"Set the project up for an AI coding agent (Claude Code, Codex, Cursor, Copilot, Gemini)",copy:"Copy the agent files instead of symlinking them, so they can be edited per project",force:"Overwrite files that already exist",ohMyZsh:"Enable Oh My Zsh",aws:"Ensures AWS is connected to the project",project:"Target a specific project",verbose:"Enable verbose output",domain:"Custom domain to setup (defaults to APP_URL)",skipHosts:"Skip adding domain to hosts file",skipTrust:"Skip trusting the certificate",skipAws:"Skip AWS configuration during setup",skipKeygen:"Skip generating an application key during setup",ios:"Install the iOS build tools (`iosDependencies` in config/deps.ts) through Pantry and check for Xcode"};buddy.command("setup",descriptions.setup).alias("ensure").option("-p, --project [project]",descriptions.project,{default:!1}).option("--skip-aws",descriptions.skipAws,{default:!1}).option("--skip-keygen",descriptions.skipKeygen,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup` ...",options);await ensurePantryInstalled();await optimizePantryDeps();await initializeProject(options)});buddy.command("setup:ios",descriptions.ios).option("--verbose",descriptions.verbose,{default:!1}).action(async()=>{if(process.platform!=="darwin"){await log.error("The iOS toolchain installs on macOS only: Xcode and xcodegen do not run anywhere else.");process.exit(ExitCode.FatalError)}const root=p.projectPath(),dependencies=await loadIosDependencies(root),pantry=pantryExecutable(root);log.info(`Installing ${Object.entries(dependencies).map(([pkg,version])=>`${pkg}@${version}`).join(", ")} through Pantry`);const install=Bun.spawnSync([pantry,...pantryInstallArgs(dependencies)],{cwd:root,stdin:"inherit",stdout:"inherit",stderr:"inherit"});if(install.exitCode!==0){await log.error(`\`${pantry} install\` failed with exit code ${install.exitCode}. PANTRY_BIN can point at another Pantry.`);process.exit(ExitCode.FatalError)}try{const xcodegen=await resolveXcodegen(root);log.success(`xcodegen ${xcodegen.version}: ${xcodegen.path}`)}catch(error){await log.error(error instanceof IosToolchainError?error.message:String(error));process.exit(ExitCode.FatalError)}const xcode=xcodeDeveloperDir();if(xcode)log.success(`Xcode: ${xcode}`);else log.warn(`No Xcode is selected, so the project can be generated but not built or archived. ${XCODE_NOTE}`)});buddy.command("setup:ssl",descriptions.ssl).alias("ssl:setup").option("-d, --domain [domain]",descriptions.domain).option("--skip-hosts",descriptions.skipHosts,{default:!1}).option("--skip-trust",descriptions.skipTrust,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(options)=>{log.debug("Running `buddy setup:ssl` ...",options);if(!await setupPrettyDevEnvironment({domain:options.domain,skipHosts:options.skipHosts,skipTrust:options.skipTrust,verbose:options.verbose})){log.warn("SSL setup completed with warnings");log.info("You may need to manually trust certificates or update hosts file")}});buddy.command("setup:ai [provider]",descriptions.ai).alias("ai:setup").option("--copy",descriptions.copy,{default:!1}).option("--force",descriptions.force,{default:!1}).option("--verbose",descriptions.verbose,{default:!1}).action(async(provider,options)=>{log.debug("Running `buddy setup:ai` ...",options);const{AI_PROVIDERS,isAiProvider,reportAiSetup,setupAiProvider}=await import("./setup-ai");let id=provider;if(!id){if(!process.stdin.isTTY){await log.error(`\`setup:ai\` needs a provider when stdin is not a terminal. Pass one: ${AI_PROVIDERS.map((entry)=>`buddy setup:ai ${entry.id}`).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const{select}=await import("@stacksjs/cli");id=await select({message:"Which AI coding agent do you use?",choices:AI_PROVIDERS.map((entry)=>({value:entry.id,label:entry.label})),initial:0})}if(!id||!isAiProvider(id)){await log.error(`Unknown AI provider: ${id}. Expected one of: ${AI_PROVIDERS.map((entry)=>entry.id).join(", ")}`);process.exit(ExitCode.InvalidArgument)}const definition=AI_PROVIDERS.find((entry)=>entry.id===id),{generateAutoImportFiles}=await import("@stacksjs/server/imports");await generateAutoImportFiles();reportAiSetup(definition,setupAiProvider(id,{copy:options.copy,force:options.force}))});buddy.command("setup:oh-my-zsh",descriptions.ohMyZsh).alias("upgrade:oh-my-zsh").option("--verbose",descriptions.verbose,{default:!1}).action(async(_options)=>{log.debug("Running `buddy setup:oh-my-zsh` ...",_options);const result=await runAction(Action.UpgradeShell);if(resultFailed(result)){await log.error(result.error);process.exit(ExitCode.FatalError)}});onUnknownSubcommand(buddy,"setup")}async function isPantryInstalled(){try{return(await runCommand("pantry --version",{silent:!0,timeoutMs:PANTRY_CHECK_TIMEOUT_MS})).isOk}catch{return!1}}async function installPantry(){const bundledInstaller=p.frameworkPath("scripts/pantry-install"),command=existsSync(bundledInstaller)?[bundledInstaller]:["sh","-c","curl -fsSL https://pantry.dev | bash"],result=await runCommand(command,{timeoutMs:PANTRY_INSTALL_TIMEOUT_MS}),localBin=join(homedir(),".local","bin");if(!process.env.PATH?.split(":").includes(localBin))process.env.PATH=`${localBin}:${process.env.PATH||""}`;if(result.isOk&&await isPantryInstalled())return;if(resultFailed(result))handleError(result.error);else await log.error("Pantry installed but is not available on PATH. Open a new shell and run `buddy setup` again.");process.exit(ExitCode.FatalError)}export async function ensurePantryInstalled(){if(await isPantryInstalled())return;log.info("Pantry is required. Installing it from https://pantry.dev...");await installPantry()}export async function ensurePantryDependencies(cwd){await ensurePantryInstalled();log.info("Installing project dependencies with Pantry...");const result=await runCommand("pantry install",{cwd,timeoutMs:PANTRY_DEPENDENCIES_TIMEOUT_MS});if(resultFailed(result)){handleError(result.error);process.exit(ExitCode.FatalError)}if(existsSync(join(cwd,"package.json"))&&!existsSync(join(cwd,"node_modules"))){await log.error("Pantry completed without installing the project JavaScript dependencies.");process.exit(ExitCode.FatalError)}log.success("Installed project dependencies with Pantry")}function hasAppKey(cwd){const envPath=join(cwd,".env");if(!existsSync(envPath))return!1;return/^APP_KEY=.+$/m.test(readFileSync(envPath,"utf-8"))}export async function ensureAppKey(cwd){if(hasAppKey(cwd)||process.env.APP_KEY&&process.env.APP_KEY.length>0){log.success("APP_KEY existed");return}const keyResult=await runCommand("./buddy key:generate",{cwd,timeoutMs:KEYGEN_TIMEOUT_MS});if(resultFailed(keyResult)){handleError(keyResult.error);process.exit(ExitCode.FatalError)}log.success("Generated application key")}async function databaseIsReachable(){try{const{describeTarget,probeTargetDatabase,resolveConnectionTarget}=await import("@stacksjs/database"),target=resolveConnectionTarget();if(!target)return{ok:!0};const probe=await probeTargetDatabase(target);if(probe.ok)return{ok:!0};if(probe.kind==="missing-database"&&resolveCreatePolicy()!=="never"){process.env.STACKS_CREATE_DATABASE="1";return{ok:!0}}return{ok:!1,reason:`${describeTarget(target)} is not reachable yet (${probe.kind})`}}catch{return{ok:!0}}}async function initializeProject(options){const cwd=options.cwd||p.projectPath();await ensurePantryDependencies(cwd);await ensureEnvIsSet(options);const portSync=syncDatabasePortToEnv(cwd);if(portSync.port!==void 0)process.env.DB_PORT=String(portSync.port);if(portSync.changed)log.info(`Pointed DB_PORT at ${portSync.port}, the port Pantry assigned this project's ${portSync.service} service`);if(!options.skipKeygen)await ensureAppKey(cwd);const migration=await runInitialMigration({appEnv:process.env.APP_ENV||process.env.NODE_ENV||"local",isReachable:databaseIsReachable,migrate:()=>runAction(Action.Migrate,{cwd}),failed:resultFailed,log});ensureIdeSettings(cwd);if(!options.skipAws){log.info("Ensuring AWS is connected...");try{const awsResult=await runCommand("./buddy configure:aws",{cwd,timeoutMs:AWS_CONFIG_TIMEOUT_MS});if(resultFailed(awsResult)){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(awsResult.error)}else log.success("Configured AWS")}catch(error){log.warn("AWS not configured - you can do this later via ./buddy configure:aws");log.debug(error)}}if(migration==="failed"){process.stderr.write(`
|
|
2
2
|
Initial database migration FAILED, so this project is not set up.
|
|
3
3
|
`);process.stderr.write(`Its database did not receive the schema; the error is above.
|
|
4
4
|
`);process.stderr.write("Fix the migration, then run `./buddy migrate`.\n");await log.flush();process.exit(ExitCode.FatalError)}log.success("Project is setup");log.info("Run `./buddy doctor` anytime to check your setup. Happy coding! \uD83D\uDC99")}export function ensureIdeSettings(cwd){const source=p.frameworkPath("defaults/ide/vscode/.vscode"),destination=join(cwd,".vscode");if(existsSync(destination)){log.debug(".vscode already exists; keeping the project settings");return}if(!existsSync(source)){log.debug("No bundled VS Code settings found; skipping IDE setup");return}cpSync(source,destination,{recursive:!0});log.success("Installed project VS Code settings")}const DB_CONNECTION_PACKAGES={postgres:{name:"postgresql.org",version:"^17.10",service:"postgres"},mysql:{name:"mysql.com",version:"^9.2",service:"mysql"},sqlite:{name:"sqlite.org",version:"^3.47.2"}};function databaseAliases(connection,pkg){return[connection,pkg.name]}export function pantryDatabasePackage(connection){return DB_CONNECTION_PACKAGES[connection]}function detectDbPackage(cwd){const envPath=join(cwd,".env"),envExamplePath=join(cwd,".env.example"),filePath=existsSync(envPath)?envPath:existsSync(envExamplePath)?envExamplePath:void 0;if(!filePath)return;const match=readFileSync(filePath,"utf-8").match(/^DB_CONNECTION=(.+)$/m);if(!match)return;const value=match[1].trim().replace(/['"]/g,"");return pantryDatabasePackage(value)}export async function optimizePantryDeps(cwd=p.projectPath()){const depsConfigPath=join(cwd,"config","deps.ts");if(!existsSync(depsConfigPath)){log.debug("No config/deps.ts found, skipping dependency optimization");return!1}let configDeps={},configServices=[],configDefined={};try{const mod=await import(depsConfigPath),config=mod.config||mod.default;if(config?.dependencies)configDeps={...config.dependencies};if(Array.isArray(config?.services?.autoStart))configServices=config.services.autoStart.filter((name)=>typeof name==="string");if(config?.services?.define&&typeof config.services.define==="object")configDefined=config.services.define}catch(err){log.debug("Could not load config/deps.ts, skipping dependency optimization");return!1}const dbPackage=detectDbPackage(cwd),autoStart=[...configServices];if(dbPackage){const selected=new Set(Object.entries(DB_CONNECTION_PACKAGES).filter(([,pkg])=>pkg.name===dbPackage.name).flatMap(([connection,pkg])=>databaseAliases(connection,pkg))),unused=new Set(Object.entries(DB_CONNECTION_PACKAGES).flatMap(([connection,pkg])=>databaseAliases(connection,pkg)).filter((alias)=>!selected.has(alias)));for(const pkg of Object.keys(configDeps)){const domain=pkg.split("/")[0];if(unused.has(domain)){log.info(`DB_CONNECTION selects ${dbPackage.name}, dropping unused ${pkg}`);delete configDeps[pkg]}}if(!Object.keys(configDeps).some((key)=>{const domain=key.split("/")[0];return selected.has(domain)})){log.info(`Detected DB_CONNECTION requires ${dbPackage.name}, adding to dependencies`);configDeps[dbPackage.name]=dbPackage.version}if(dbPackage.service&&!autoStart.includes(dbPackage.service))autoStart.push(dbPackage.service)}const lines=["# Auto-generated from config/deps.ts and .env sniffing.","# This file is regenerated on each `buddy setup` run.","#","# To learn more, please visit:","# https://stacksjs.com/docs/dependency-management","","dependencies:"];for(const[pkg,version]of Object.entries(configDeps))lines.push(` ${pkg}: ${version}`);const defined=Object.entries(configDefined);if(autoStart.length>0||defined.length>0){lines.push("","services:"," enabled: true");if(autoStart.length>0){lines.push(" autoStart:");for(const service of autoStart)lines.push(` - ${service}`)}if(defined.length>0){lines.push(" define:");for(const[name,definition]of defined){if(!definition||typeof definition!=="object")continue;lines.push(` ${name}:`);for(const[key,value]of Object.entries(definition)){if(value===void 0||value===null)continue;lines.push(` ${key}: ${String(value)}`)}}}}const depsYamlPath=join(cwd,"deps.yaml");writeFileSync(depsYamlPath,`${lines.join(`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Where Pantry records its assignments; `home` is injectable for tests. */
|
|
2
|
+
export declare function pantryPortsFile(home?: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* The port Pantry assigned to `service` for the project rooted at `root`, or
|
|
5
|
+
* undefined when nothing was assigned (the service never started here).
|
|
6
|
+
*/
|
|
7
|
+
export declare function assignedServicePort(root: string, service: string, home?: string): number | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Write the assigned port into `.env` when it differs from what is there.
|
|
10
|
+
* Leaves the file alone for SQLite, for a remote `DB_HOST`, and when Pantry has
|
|
11
|
+
* assigned nothing: in each case `DB_PORT` is not Pantry's to decide.
|
|
12
|
+
*/
|
|
13
|
+
export declare function syncDatabasePortToEnv(cwd: string, home?: string): PortSyncResult;
|
|
14
|
+
export declare interface PortSyncResult {
|
|
15
|
+
service?: string
|
|
16
|
+
port?: number
|
|
17
|
+
changed: boolean
|
|
18
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{existsSync,readFileSync,realpathSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{join}from"node:path";const DATABASE_SERVICES={mysql:"mysql",postgres:"postgres"};function canonical(path){try{return realpathSync(path)}catch{return path}}export function pantryPortsFile(home=homedir()){return join(home,".local","share","pantry","ports.json")}export function assignedServicePort(root,service,home=homedir()){const file=pantryPortsFile(home);if(!existsSync(file))return;let assignments;try{assignments=JSON.parse(readFileSync(file,"utf8")).assignments??[]}catch{return}const wanted=canonical(root);return assignments.find((entry)=>entry.service===service&&typeof entry.port==="number"&&!!entry.root&&canonical(entry.root)===wanted)?.port}function envValue(contents,key){return contents.match(new RegExp(`^${key}=(.*)$`,"m"))?.[1]?.trim().replace(/^['"]|['"]$/g,"")}export function syncDatabasePortToEnv(cwd,home=homedir()){const envPath=join(cwd,".env");if(!existsSync(envPath))return{changed:!1};const contents=readFileSync(envPath,"utf8"),service=DATABASE_SERVICES[envValue(contents,"DB_CONNECTION")??""];if(!service)return{changed:!1};const host=envValue(contents,"DB_HOST")||"127.0.0.1";if(!["127.0.0.1","localhost","::1"].includes(host))return{service,changed:!1};const port=assignedServicePort(cwd,service,home);if(port===void 0||envValue(contents,"DB_PORT")===String(port))return{service,port,changed:!1};const next=/^DB_PORT=.*$/m.test(contents)?contents.replace(/^DB_PORT=.*$/m,`DB_PORT=${port}`):`${contents.replace(/\n*$/,`
|
|
2
|
+
`)}DB_PORT=${port}
|
|
3
|
+
`;writeFileSync(envPath,next);return{service,port,changed:!0}}
|
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.
|
|
5
|
+
"version": "0.75.390",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,69 +95,69 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "0.75.
|
|
99
|
-
"@stacksjs/ai": "0.75.
|
|
100
|
-
"@stacksjs/alias": "0.75.
|
|
101
|
-
"@stacksjs/analytics": "0.75.
|
|
102
|
-
"@stacksjs/api": "0.75.
|
|
103
|
-
"@stacksjs/arrays": "0.75.
|
|
104
|
-
"@stacksjs/auth": "0.75.
|
|
105
|
-
"@stacksjs/browser-extension": "0.75.
|
|
106
|
-
"@stacksjs/build": "0.75.
|
|
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",
|
|
107
107
|
"@stacksjs/bumpx": "^0.2.26",
|
|
108
|
-
"@stacksjs/cache": "0.75.
|
|
109
|
-
"@stacksjs/chat": "0.75.
|
|
108
|
+
"@stacksjs/cache": "0.75.390",
|
|
109
|
+
"@stacksjs/chat": "0.75.390",
|
|
110
110
|
"@stacksjs/clapp": "^0.2.16",
|
|
111
|
-
"@stacksjs/cli": "0.75.
|
|
112
|
-
"@stacksjs/cloud": "0.75.
|
|
113
|
-
"@stacksjs/cms": "0.75.
|
|
114
|
-
"@stacksjs/collections": "0.75.
|
|
115
|
-
"@stacksjs/commerce": "0.75.
|
|
116
|
-
"@stacksjs/config": "0.75.
|
|
117
|
-
"@stacksjs/database": "0.75.
|
|
118
|
-
"@stacksjs/desktop-build": "0.75.
|
|
119
|
-
"@stacksjs/dns": "0.75.
|
|
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",
|
|
120
120
|
"@stacksjs/dnsx": "^0.2.3",
|
|
121
|
-
"@stacksjs/email": "0.75.
|
|
122
|
-
"@stacksjs/enums": "0.75.
|
|
123
|
-
"@stacksjs/env": "0.75.
|
|
124
|
-
"@stacksjs/error-handling": "0.75.
|
|
125
|
-
"@stacksjs/events": "0.75.
|
|
126
|
-
"@stacksjs/features": "0.75.
|
|
127
|
-
"@stacksjs/git": "0.75.
|
|
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",
|
|
128
128
|
"@stacksjs/gitit": "^0.2.5",
|
|
129
|
-
"@stacksjs/health": "0.75.
|
|
129
|
+
"@stacksjs/health": "0.75.390",
|
|
130
130
|
"@stacksjs/httx": "^0.1.10",
|
|
131
|
-
"@stacksjs/image": "0.75.
|
|
132
|
-
"@stacksjs/lint": "0.75.
|
|
133
|
-
"@stacksjs/logging": "0.75.
|
|
134
|
-
"@stacksjs/notifications": "0.75.
|
|
135
|
-
"@stacksjs/objects": "0.75.
|
|
136
|
-
"@stacksjs/orm": "0.75.
|
|
137
|
-
"@stacksjs/path": "0.75.
|
|
138
|
-
"@stacksjs/payments": "0.75.
|
|
139
|
-
"@stacksjs/realtime": "0.75.
|
|
140
|
-
"@stacksjs/router": "0.75.
|
|
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",
|
|
141
141
|
"@stacksjs/rpx": "^0.11.54",
|
|
142
|
-
"@stacksjs/scheduler": "0.75.
|
|
143
|
-
"@stacksjs/search-engine": "0.75.
|
|
144
|
-
"@stacksjs/security": "0.75.
|
|
145
|
-
"@stacksjs/server": "0.75.
|
|
146
|
-
"@stacksjs/sites": "0.75.
|
|
147
|
-
"@stacksjs/skills": "0.75.
|
|
148
|
-
"@stacksjs/socials": "0.75.
|
|
149
|
-
"@stacksjs/storage": "0.75.
|
|
150
|
-
"@stacksjs/strings": "0.75.
|
|
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
151
|
"@stacksjs/stx": "0.2.752",
|
|
152
|
-
"@stacksjs/testing": "0.75.
|
|
153
|
-
"@stacksjs/tinker": "0.75.
|
|
152
|
+
"@stacksjs/testing": "0.75.390",
|
|
153
|
+
"@stacksjs/tinker": "0.75.390",
|
|
154
154
|
"@stacksjs/tlsx": "^0.13.19",
|
|
155
155
|
"@stacksjs/ts-cloud": "^0.16.89",
|
|
156
|
-
"@stacksjs/tunnel": "0.75.
|
|
157
|
-
"@stacksjs/types": "0.75.
|
|
158
|
-
"@stacksjs/ui": "0.75.
|
|
159
|
-
"@stacksjs/utils": "0.75.
|
|
160
|
-
"@stacksjs/validation": "0.75.
|
|
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",
|
|
161
161
|
"ajv": "^8.20.0",
|
|
162
162
|
"ajv-formats": "^3.0.1",
|
|
163
163
|
"bun-plugin-stx": "0.2.752",
|