@stacksjs/actions 0.74.6 → 0.74.8

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.
@@ -107,7 +107,7 @@ import process from"node:process";import{randomBytes}from"node:crypto";import{db
107
107
  )
108
108
  `);await db.unsafe(`
109
109
  CREATE INDEX IF NOT EXISTS idx_oauth_refresh_tokens_token ON oauth_refresh_tokens(token)
110
- `);log.success("OAuth tables ready")}catch(error){log.error("Failed to create OAuth tables",error);process.exit(1)}log.info("Ensuring password_resets table exists...");try{if(isPostgres)await db.unsafe(`
110
+ `);log.success("OAuth tables ready")}catch(error){await log.error("Failed to create OAuth tables",error);process.exit(1)}log.info("Ensuring password_resets table exists...");try{if(isPostgres)await db.unsafe(`
111
111
  CREATE TABLE IF NOT EXISTS password_resets (
112
112
  id SERIAL PRIMARY KEY,
113
113
  email VARCHAR(255) NOT NULL,
@@ -133,7 +133,7 @@ import process from"node:process";import{randomBytes}from"node:crypto";import{db
133
133
  )
134
134
  `);try{if(isPostgres)await db.unsafe("ALTER TABLE password_resets ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");else await db.unsafe("ALTER TABLE password_resets ADD COLUMN expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP")}catch{}await db.unsafe(`
135
135
  CREATE UNIQUE INDEX IF NOT EXISTS idx_password_resets_email_unique ON password_resets(email)
136
- `);log.success("Password resets table ready")}catch(error){log.error("Failed to create password_resets table",error);process.exit(1)}log.info("Ensuring email_verifications table exists...");try{if(isPostgres)await db.unsafe(`
136
+ `);log.success("Password resets table ready")}catch(error){await log.error("Failed to create password_resets table",error);process.exit(1)}log.info("Ensuring email_verifications table exists...");try{if(isPostgres)await db.unsafe(`
137
137
  CREATE TABLE IF NOT EXISTS email_verifications (
138
138
  id SERIAL PRIMARY KEY,
139
139
  user_id INTEGER NOT NULL,
@@ -159,7 +159,7 @@ import process from"node:process";import{randomBytes}from"node:crypto";import{db
159
159
  )
160
160
  `);await db.unsafe(`
161
161
  CREATE UNIQUE INDEX IF NOT EXISTS idx_email_verifications_user_id_unique ON email_verifications(user_id)
162
- `);try{await db.unsafe(usersEmailVerifiedAtSql(sql))}catch{}try{await db.unsafe(usersPasswordChangedAtSql(sql))}catch{}log.success("Email verifications table ready")}catch(error){log.error("Failed to create email_verifications table",error);process.exit(1)}log.info("Ensuring webauthn_challenges table exists...");try{if(isPostgres)await db.unsafe(`
162
+ `);try{await db.unsafe(usersEmailVerifiedAtSql(sql))}catch{}try{await db.unsafe(usersPasswordChangedAtSql(sql))}catch{}log.success("Email verifications table ready")}catch(error){await log.error("Failed to create email_verifications table",error);process.exit(1)}log.info("Ensuring webauthn_challenges table exists...");try{if(isPostgres)await db.unsafe(`
163
163
  CREATE TABLE IF NOT EXISTS webauthn_challenges (
164
164
  id SERIAL PRIMARY KEY,
165
165
  user_id INTEGER NOT NULL,
@@ -188,7 +188,7 @@ import process from"node:process";import{randomBytes}from"node:crypto";import{db
188
188
  )
189
189
  `);await db.unsafe(`
190
190
  CREATE UNIQUE INDEX IF NOT EXISTS idx_webauthn_challenges_user_purpose ON webauthn_challenges(user_id, purpose)
191
- `);log.success("WebAuthn challenges table ready")}catch(error){log.error("Failed to create webauthn_challenges table",error);process.exit(1)}log.info("Creating personal access client...");try{if((await db.unsafe(`
191
+ `);log.success("WebAuthn challenges table ready")}catch(error){await log.error("Failed to create webauthn_challenges table",error);process.exit(1)}log.info("Creating personal access client...");try{if((await db.unsafe(`
192
192
  SELECT id FROM oauth_clients WHERE personal_access_client = ${boolTrue} LIMIT 1
193
193
  `))?.length>0)console.log(`
194
194
  \u2713 Personal access client already exists`);else{const secret=randomBytes(40).toString("hex"),hashedSecret=await makeHash(secret,{algorithm:"bcrypt"});if(isPostgres)await db.unsafe(`
@@ -1 +1 @@
1
- import process from"node:process";import{createPersonalAccessClient}from"@stacksjs/auth";import{log}from"@stacksjs/logging";const result=await createPersonalAccessClient();if(result?.isErr){console.error(result.error);log.error("createPersonalAccessClient failed",result.error);process.exit(1)}process.exit(0);
1
+ import process from"node:process";import{createPersonalAccessClient}from"@stacksjs/auth";import{log}from"@stacksjs/logging";const result=await createPersonalAccessClient();if(result?.isErr){console.error(result.error);await log.error("createPersonalAccessClient failed",result.error);process.exit(1)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/cli";import{seed}from"@stacksjs/database";const argv=process.argv.slice(2),allowProtected=argv.includes("--allow-protected")||argv.includes("--allowProtected"),fresh=argv.includes("--fresh"),startTime=Date.now();try{const result=await seed({verbose:!0,fresh,allowProtected}),duration=Date.now()-startTime;if(result.failed>0){log.warn("");log.warn(`Seeding completed with ${result.failed} failure(s):`);for(const r of result.results)if(!r.success)log.error(` - ${r.model}: ${r.error}`);log.info("");log.info("Note: Failed models may have schema mismatches or invalid factory functions.");log.info("Tip: Run `./buddy migrate:fresh` to ensure schema is up to date.")}log.info("");log.success(`Seeding completed in ${duration}ms`);log.info(` Total models: ${result.total}`);log.info(` Successful: ${result.successful}`);log.info(` Failed: ${result.failed}`);process.exit(0)}catch(err){log.error("Seeding failed catastrophically:",err);process.exit(1)}
1
+ import process from"node:process";import{log}from"@stacksjs/cli";import{seed}from"@stacksjs/database";const argv=process.argv.slice(2),allowProtected=argv.includes("--allow-protected")||argv.includes("--allowProtected"),fresh=argv.includes("--fresh"),startTime=Date.now();try{const result=await seed({verbose:!0,fresh,allowProtected}),duration=Date.now()-startTime;if(result.failed>0){log.warn("");log.warn(`Seeding completed with ${result.failed} failure(s):`);for(const r of result.results)if(!r.success)log.error(` - ${r.model}: ${r.error}`);log.info("");log.info("Note: Failed models may have schema mismatches or invalid factory functions.");log.info("Tip: Run `./buddy migrate:fresh` to ensure schema is up to date.")}log.info("");log.success(`Seeding completed in ${duration}ms`);log.info(` Total models: ${result.total}`);log.info(` Successful: ${result.successful}`);log.info(` Failed: ${result.failed}`);process.exit(0)}catch(err){await log.error("Seeding failed catastrophically:",err);process.exit(1)}
@@ -296,4 +296,4 @@ ${children}${indent}</${name}>
296
296
  `}return""},xml=`<?xml version="1.0" encoding="UTF-8"?>
297
297
  <DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
298
298
  ${Object.entries(currentConfig).filter(([key])=>!key.startsWith("@_")).map(([key,val])=>buildXmlElement(key,val," ")).join("")}</DistributionConfig>
299
- `;await awsClient.request({service:"cloudfront",region:"us-east-1",method:"PUT",path:`/2020-05-31/distribution/${distributionId}/config`,body:xml,headers:{"Content-Type":"application/xml","If-Match":etag}});if(isVerbose)log.debug(" CloudFront custom error responses configured")}else if(isVerbose)log.debug(" CloudFront custom error responses already configured")}}errorPageSpinner.succeed("404 error page configured")}catch(errorPageError){errorPageSpinner.stop();console.log(`\u26A0 404 page configuration skipped: ${errorPageError.message}`);if(isVerbose)log.debug(`Error page config error: ${errorPageError.stack}`)}try{const tunnelCloudConfig=await import(p.projectPath("config/cloud")),tunnelConfig=tunnelCloudConfig?.tsCloud?.infrastructure?.tunnel;if(tunnelConfig?.enabled){const tunnelDomain=tunnelConfig.domain||"";if(!tunnelDomain||tunnelDomain==="localtunnel.dev"||tunnelDomain==="api.localtunnel.dev")console.log("\u2139 Tunnel: Using shared localtunnel.dev (no custom tunnel deployment needed)");else{const tunnelSpinner=spinner(`Deploying tunnel server to ${tunnelDomain}...`);tunnelSpinner.start();try{const{deployTunnelServer}=await import("@stacksjs/tunnel");await deployTunnelServer({domain:tunnelDomain,region:tunnelConfig.region||tunnelCloudConfig?.tsCloud?.project?.region||"us-east-1",instanceType:tunnelConfig.instanceType,prefix:tunnelConfig.prefix,enableSsl:tunnelConfig.ssl?.enabled,porkbunApiKey:tunnelConfig.ssl?.porkbunApiKey,porkbunSecretKey:tunnelConfig.ssl?.porkbunSecretKey,verbose:isVerbose});tunnelSpinner.succeed(`Tunnel server deployed to ${tunnelDomain}`)}catch(tunnelError){tunnelSpinner.stop();console.log(`\u26A0 Tunnel deployment skipped: ${tunnelError.message}`);if(isVerbose)log.debug(`Tunnel deploy error: ${tunnelError.stack}`)}}}}catch{}const dnsSpinner=spinner("Reconciling DNS records...");dnsSpinner.start();try{const{Route53Client,CloudFormationClient:DnsCfClient}=await import("@stacksjs/ts-cloud"),dnsCf=new DnsCfClient(region),dnsStackName=`${projectName}-cloud`,dnsOutputs=await dnsCf.getStackOutputs(dnsStackName),dnsCloudConfig=await import(p.projectPath("config/cloud")),hostedZoneId=dnsCloudConfig?.tsCloud?.infrastructure?.dns?.hostedZoneId,siteDomain=dnsCloudConfig?.tsCloud?.infrastructure?.dns?.domain||"stacksjs.com";if(hostedZoneId){const route53=new Route53Client(region),cfHostedZoneId="Z2FDTNDATAQYW2",domainMappings=[{domain:siteDomain,cfDomain:dnsOutputs.publicCloudFrontDomain},{domain:`www.${siteDomain}`,cfDomain:dnsOutputs.publicCloudFrontDomain},{domain:`docs.${siteDomain}`,cfDomain:dnsOutputs.docsCloudFrontDomain},{domain:`blog.${siteDomain}`,cfDomain:dnsOutputs.blogCloudFrontDomain}];let recordsCreated=0;for(const{domain,cfDomain}of domainMappings){if(!cfDomain)continue;try{if(!((await route53.listResourceRecordSets({HostedZoneId:hostedZoneId,StartRecordName:domain,StartRecordType:"A",MaxItems:"1"}))?.ResourceRecordSets||[]).some((r)=>r.Name===`${domain}.`&&r.Type==="A")){await route53.changeResourceRecordSets({HostedZoneId:hostedZoneId,ChangeBatch:{Changes:[{Action:"UPSERT",ResourceRecordSet:{Name:domain,Type:"A",AliasTarget:{HostedZoneId:cfHostedZoneId,DNSName:cfDomain,EvaluateTargetHealth:!1}}},{Action:"UPSERT",ResourceRecordSet:{Name:domain,Type:"AAAA",AliasTarget:{HostedZoneId:cfHostedZoneId,DNSName:cfDomain,EvaluateTargetHealth:!1}}}]}});recordsCreated++;if(isVerbose)log.debug(` Created DNS records: ${domain} -> ${cfDomain}`)}else if(isVerbose)log.debug(` DNS record exists: ${domain}`)}catch(dnsErr){if(isVerbose)log.debug(` DNS check/create failed for ${domain}: ${dnsErr.message}`)}}if(recordsCreated>0)dnsSpinner.succeed(`DNS records reconciled (${recordsCreated} created)`);else dnsSpinner.succeed("DNS records verified")}else{dnsSpinner.stop();if(isVerbose)log.debug("No hosted zone ID configured, skipping DNS reconciliation")}}catch(dnsError){dnsSpinner.stop();console.log(`\u26A0 DNS reconciliation skipped: ${dnsError.message}`);if(isVerbose)log.debug(`DNS error: ${dnsError.stack}`)}console.log("");log.success("Deployment completed successfully!")}catch(error){log.error("Deployment failed:",error);process.exit(ExitCode.FatalError)}
299
+ `;await awsClient.request({service:"cloudfront",region:"us-east-1",method:"PUT",path:`/2020-05-31/distribution/${distributionId}/config`,body:xml,headers:{"Content-Type":"application/xml","If-Match":etag}});if(isVerbose)log.debug(" CloudFront custom error responses configured")}else if(isVerbose)log.debug(" CloudFront custom error responses already configured")}}errorPageSpinner.succeed("404 error page configured")}catch(errorPageError){errorPageSpinner.stop();console.log(`\u26A0 404 page configuration skipped: ${errorPageError.message}`);if(isVerbose)log.debug(`Error page config error: ${errorPageError.stack}`)}try{const tunnelCloudConfig=await import(p.projectPath("config/cloud")),tunnelConfig=tunnelCloudConfig?.tsCloud?.infrastructure?.tunnel;if(tunnelConfig?.enabled){const tunnelDomain=tunnelConfig.domain||"";if(!tunnelDomain||tunnelDomain==="localtunnel.dev"||tunnelDomain==="api.localtunnel.dev")console.log("\u2139 Tunnel: Using shared localtunnel.dev (no custom tunnel deployment needed)");else{const tunnelSpinner=spinner(`Deploying tunnel server to ${tunnelDomain}...`);tunnelSpinner.start();try{const{deployTunnelServer}=await import("@stacksjs/tunnel");await deployTunnelServer({domain:tunnelDomain,region:tunnelConfig.region||tunnelCloudConfig?.tsCloud?.project?.region||"us-east-1",instanceType:tunnelConfig.instanceType,prefix:tunnelConfig.prefix,enableSsl:tunnelConfig.ssl?.enabled,porkbunApiKey:tunnelConfig.ssl?.porkbunApiKey,porkbunSecretKey:tunnelConfig.ssl?.porkbunSecretKey,verbose:isVerbose});tunnelSpinner.succeed(`Tunnel server deployed to ${tunnelDomain}`)}catch(tunnelError){tunnelSpinner.stop();console.log(`\u26A0 Tunnel deployment skipped: ${tunnelError.message}`);if(isVerbose)log.debug(`Tunnel deploy error: ${tunnelError.stack}`)}}}}catch{}const dnsSpinner=spinner("Reconciling DNS records...");dnsSpinner.start();try{const{Route53Client,CloudFormationClient:DnsCfClient}=await import("@stacksjs/ts-cloud"),dnsCf=new DnsCfClient(region),dnsStackName=`${projectName}-cloud`,dnsOutputs=await dnsCf.getStackOutputs(dnsStackName),dnsCloudConfig=await import(p.projectPath("config/cloud")),hostedZoneId=dnsCloudConfig?.tsCloud?.infrastructure?.dns?.hostedZoneId,siteDomain=dnsCloudConfig?.tsCloud?.infrastructure?.dns?.domain||"stacksjs.com";if(hostedZoneId){const route53=new Route53Client(region),cfHostedZoneId="Z2FDTNDATAQYW2",domainMappings=[{domain:siteDomain,cfDomain:dnsOutputs.publicCloudFrontDomain},{domain:`www.${siteDomain}`,cfDomain:dnsOutputs.publicCloudFrontDomain},{domain:`docs.${siteDomain}`,cfDomain:dnsOutputs.docsCloudFrontDomain},{domain:`blog.${siteDomain}`,cfDomain:dnsOutputs.blogCloudFrontDomain}];let recordsCreated=0;for(const{domain,cfDomain}of domainMappings){if(!cfDomain)continue;try{if(!((await route53.listResourceRecordSets({HostedZoneId:hostedZoneId,StartRecordName:domain,StartRecordType:"A",MaxItems:"1"}))?.ResourceRecordSets||[]).some((r)=>r.Name===`${domain}.`&&r.Type==="A")){await route53.changeResourceRecordSets({HostedZoneId:hostedZoneId,ChangeBatch:{Changes:[{Action:"UPSERT",ResourceRecordSet:{Name:domain,Type:"A",AliasTarget:{HostedZoneId:cfHostedZoneId,DNSName:cfDomain,EvaluateTargetHealth:!1}}},{Action:"UPSERT",ResourceRecordSet:{Name:domain,Type:"AAAA",AliasTarget:{HostedZoneId:cfHostedZoneId,DNSName:cfDomain,EvaluateTargetHealth:!1}}}]}});recordsCreated++;if(isVerbose)log.debug(` Created DNS records: ${domain} -> ${cfDomain}`)}else if(isVerbose)log.debug(` DNS record exists: ${domain}`)}catch(dnsErr){if(isVerbose)log.debug(` DNS check/create failed for ${domain}: ${dnsErr.message}`)}}if(recordsCreated>0)dnsSpinner.succeed(`DNS records reconciled (${recordsCreated} created)`);else dnsSpinner.succeed("DNS records verified")}else{dnsSpinner.stop();if(isVerbose)log.debug("No hosted zone ID configured, skipping DNS reconciliation")}}catch(dnsError){dnsSpinner.stop();console.log(`\u26A0 DNS reconciliation skipped: ${dnsError.message}`);if(isVerbose)log.debug(`DNS error: ${dnsError.stack}`)}console.log("");log.success("Deployment completed successfully!")}catch(error){await log.error("Deployment failed:",error);process.exit(ExitCode.FatalError)}
@@ -1 +1 @@
1
- import process from"node:process";import{italic,log,parseOptions,runCommand}from"@stacksjs/cli";import{createHostedZone,getHostedZoneNameservers,updateNameservers}from"@stacksjs/dns";import{loadEnv}from"@stacksjs/env";import{handleError}from"@stacksjs/error-handling";import{projectConfigPath}from"@stacksjs/path";import{whois}from"@stacksjs/whois";loadEnv({path:[".env",".env.production"],overload:!0});const options=parseOptions();function normalizeDomain(url){if(!url)return;return url.replace(/^https?:\/\//,"").replace(/\/$/,"")}const domain=normalizeDomain(options.domain??process.env.APP_URL);if(!domain){log.error("There was no Domain or App URL provided. Please provide a domain using the --domain flag or add a url to your app config.");process.exit(1)}const result=await createHostedZone(domain);if(result.isErr){handleError(result.error);process.exit(1)}const nameServers=await getHostedZoneNameservers(domain);if(!nameServers||nameServers.length===0){handleError(`No nameservers found for domain: ${domain}. Please ensure the Hosted Zone exists in Route53.`);process.exit(1)}await updateNameservers(nameServers);const registrar=(await whois(domain,!0)).parsedData.Registrar;if(registrar.includes("Amazon")){if(options.deploy)await runCommand("buddy deploy");else{log.info("");log.info("You can now continue your deployment process by re-running:");log.info("");log.info(` \u27A1\uFE0F ${italic("buddy deploy")}`);log.info("")}process.exit(0)}log.info("");log.info("\u2139\uFE0F Please note, before continuing your deployment process,");log.info(` update your ${registrar} nameservers to the following:`);const emojis=["1\uFE0F\u20E3","2\uFE0F\u20E3","3\uFE0F\u20E3","4\uFE0F\u20E3"];log.info("");nameServers.forEach((nameserver,index)=>{log.info(` ${emojis[index]} Nameserver: ${nameserver}`)});log.info("");log.info(italic(`stored in ${projectConfigPath("dns.ts")}`));log.info("");log.info("Once the nameservers have been updated, re-run the following command:");log.info("");log.info(` \u27A1\uFE0F ${italic("buddy deploy")}`);log.info("");
1
+ import process from"node:process";import{italic,log,parseOptions,runCommand}from"@stacksjs/cli";import{createHostedZone,getHostedZoneNameservers,updateNameservers}from"@stacksjs/dns";import{loadEnv}from"@stacksjs/env";import{handleError}from"@stacksjs/error-handling";import{projectConfigPath}from"@stacksjs/path";import{whois}from"@stacksjs/whois";loadEnv({path:[".env",".env.production"],overload:!0});const options=parseOptions();function normalizeDomain(url){if(!url)return;return url.replace(/^https?:\/\//,"").replace(/\/$/,"")}const domain=normalizeDomain(options.domain??process.env.APP_URL);if(!domain){await log.error("There was no Domain or App URL provided. Please provide a domain using the --domain flag or add a url to your app config.");process.exit(1)}const result=await createHostedZone(domain);if(result.isErr){handleError(result.error);process.exit(1)}const nameServers=await getHostedZoneNameservers(domain);if(!nameServers||nameServers.length===0){handleError(`No nameservers found for domain: ${domain}. Please ensure the Hosted Zone exists in Route53.`);process.exit(1)}await updateNameservers(nameServers);const registrar=(await whois(domain,!0)).parsedData.Registrar;if(registrar.includes("Amazon")){if(options.deploy)await runCommand("buddy deploy");else{log.info("");log.info("You can now continue your deployment process by re-running:");log.info("");log.info(` \u27A1\uFE0F ${italic("buddy deploy")}`);log.info("")}process.exit(0)}log.info("");log.info("\u2139\uFE0F Please note, before continuing your deployment process,");log.info(` update your ${registrar} nameservers to the following:`);const emojis=["1\uFE0F\u20E3","2\uFE0F\u20E3","3\uFE0F\u20E3","4\uFE0F\u20E3"];log.info("");nameServers.forEach((nameserver,index)=>{log.info(` ${emojis[index]} Nameserver: ${nameserver}`)});log.info("");log.info(italic(`stored in ${projectConfigPath("dns.ts")}`));log.info("");log.info("Once the nameservers have been updated, re-run the following command:");log.info("");log.info(` \u27A1\uFE0F ${italic("buddy deploy")}`);log.info("");
@@ -1,3 +1,3 @@
1
- import process from"node:process";import{cli}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath}from"@stacksjs/path";import{writeTextFile}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";log.info("Ensuring Component Library Entry Point...");if(!cli.command){log.error("No command defined in the CLI configuration");process.exit(ExitCode.FatalError)}await writeTextFile({path:projectPath(cli.command),data:`#!/usr/bin/env bun
1
+ import process from"node:process";import{cli}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{projectPath}from"@stacksjs/path";import{writeTextFile}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";log.info("Ensuring Component Library Entry Point...");if(!cli.command){await log.error("No command defined in the CLI configuration");process.exit(ExitCode.FatalError)}await writeTextFile({path:projectPath(cli.command),data:`#!/usr/bin/env bun
2
2
  import('./storage/framework/core/buddy/src/custom-cli')
3
- `}).catch((err)=>{log.error("There was an error generating your custom CLI file.",err);process.exit(ExitCode.FatalError)});log.success("Generated custom CLI file");
3
+ `}).catch((err)=>{console.error("There was an error generating your custom CLI file.",err);process.exit(ExitCode.FatalError)});log.success("Generated custom CLI file");
@@ -1 +1 @@
1
- import*as fs from"node:fs";import process from"node:process";import{runCommand}from"@stacksjs/cli";import{Action,NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{frameworkPath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";import{runAction}from"../helpers";import{generateVsCodeCustomData as genVsCodeCustomData,generateWebTypes as genWebTypes}from"../helpers/vscode-custom-data";import{generateProjectImages}from"./images";import{generateRouteNames}from"./action-types";export{generateProjectImages}from"./images";export{generateVSchema}from"./vschema";export async function invoke(options){if(options?.types)await generateTypes(options);else if(options?.entries)await generateLibEntries(options);else if(options?.webTypes)await generateWebTypes();else if(options?.customData)await generateVsCodeCustomData();else if(options?.ideHelpers)await generateIdeHelpers();else if(options?.componentMeta)await generateComponentMeta();else if(options?.coreSymlink)await generateCoreSymlink();else if(options?.openApiSpec)await generateOpenApiSpec();else if(options?.images)await generateProjectImages({verbose:options?.verbose});else await generateEverything(options)}async function generateEverything(options){log.info("Generating types, entry points, component meta and the OpenAPI spec...");await generateTypes({...options,types:!0});await generateLibEntries({...options,entries:!0});await generateOpenApiSpec();log.success("Generated")}export function generate(options){return invoke(options)}export async function generateLibEntries(options){const result=await runAction(Action.GenerateLibraryEntries,{...options,cwd:projectPath()});if(result.isErr){log.error("There was an error generating your library entry points",result.error);process.exit(ExitCode.FatalError)}log.success("Library entry points generated successfully")}export async function generateWebTypes(){await genWebTypes()}export async function generateVsCodeCustomData(){const result=await genVsCodeCustomData();if(result.isErr){log.error("There was an error generating the custom-elements.json file.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated the custom-elements.json file")}export async function generateIdeHelpers(){await genWebTypes();await genVsCodeCustomData();await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated IDE helpers")}export async function generateComponentMeta(){const result=await genVsCodeCustomData();if(result.isErr){log.error("There was an error generating your component meta information.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated component meta information")}export async function generateTypes(options){try{const{generateAutoImportFiles}=await import("@stacksjs/server");await generateAutoImportFiles()}catch(error){log.debug("[generate:types] Could not refresh the auto-import registries",{error})}await generateRouteNames();try{const{generateServerAutoImportTypes}=await import("@stacksjs/server");await generateServerAutoImportTypes()}catch(error){log.debug("[generate:types] Could not refresh server auto-import declarations",{error})}const entry=frameworkPath("core/actions/src/generate/types.ts");if(!fs.existsSync(entry)){await import(`./types?t=${Date.now()}`);return}const result=await runNpmScript(NpmScript.GenerateTypes,{cwd:frameworkPath(),...options});if(result.isErr){log.error("There was an error generating your types.",result.error);throw result.error instanceof Error?result.error:Error(String(result.error))}log.success("Types were generated successfully")}export async function watchTypes(options){try{const manifestPath=`${frameworkPath()}/package.json`,text=fs.existsSync(manifestPath)?fs.readFileSync(manifestPath,"utf-8"):"";if(!(text?JSON.parse(text):{})?.scripts?.["generate:types"]){log.warn("[generate:types --watch] no `generate:types` script in framework package.json - type regeneration disabled. dev:api will keep running, but type definitions won't auto-refresh on model/config changes.");return}}catch{}const watched=[...await(async()=>{try{const{autoImportSourceDirs}=await import("@stacksjs/server");return autoImportSourceDirs()}catch{return[]}})(),projectPath("config"),projectPath("database/migrations"),projectPath("app"),projectPath("routes")].filter((dir,index,all)=>all.indexOf(dir)===index);log.info(`[generate:types --watch] watching ${watched.length} directories`);let pending=null,inflight=!1;const trigger=()=>{if(pending)clearTimeout(pending);pending=setTimeout(async()=>{if(inflight)return;inflight=!0;try{log.info("[generate:types --watch] change detected, regenerating\u2026");await generateTypes(options);log.success("[generate:types --watch] types up to date")}catch(err){log.error("[generate:types --watch] regeneration failed:",err)}finally{inflight=!1}},200)},watchers=[];for(const dir of watched)try{if(!fs.existsSync(dir))continue;const w=fs.watch(dir,{recursive:!0},()=>trigger());watchers.push(w)}catch(err){log.warn(`[generate:types --watch] cannot watch ${dir}: ${err}`)}if(watchers.length===0){log.warn("[generate:types --watch] no directories to watch - exiting");return}await new Promise((resolve)=>{process.once("SIGINT",()=>{log.info("[generate:types --watch] stopping");for(const w of watchers)try{w.close()}catch{}resolve()})})}export function generatePantryConfig(){log.success("Successfully generated `./pantry.yaml` based on your config")}export async function generateSeeder(){}export async function generateCoreSymlink(){const link=projectPath(".framework");if(fs.existsSync(link))await runCommand(`rm -f ${link}`);await runCommand(`ln -s ${frameworkPath()} ${link}`)}export async function generateOpenApiSpec(){const{generateOpenApi}=await import("@stacksjs/api");await generateOpenApi();log.success("Successfully generated Open API Spec")}
1
+ import*as fs from"node:fs";import process from"node:process";import{runCommand}from"@stacksjs/cli";import{Action,NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{frameworkPath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";import{runAction}from"../helpers";import{generateVsCodeCustomData as genVsCodeCustomData,generateWebTypes as genWebTypes}from"../helpers/vscode-custom-data";import{generateProjectImages}from"./images";import{generateRouteNames}from"./action-types";export{generateProjectImages}from"./images";export{generateVSchema}from"./vschema";export async function invoke(options){if(options?.types)await generateTypes(options);else if(options?.entries)await generateLibEntries(options);else if(options?.webTypes)await generateWebTypes();else if(options?.customData)await generateVsCodeCustomData();else if(options?.ideHelpers)await generateIdeHelpers();else if(options?.componentMeta)await generateComponentMeta();else if(options?.coreSymlink)await generateCoreSymlink();else if(options?.openApiSpec)await generateOpenApiSpec();else if(options?.images)await generateProjectImages({verbose:options?.verbose});else await generateEverything(options)}async function generateEverything(options){log.info("Generating types, entry points, component meta and the OpenAPI spec...");await generateTypes({...options,types:!0});await generateLibEntries({...options,entries:!0});await generateOpenApiSpec();log.success("Generated")}export function generate(options){return invoke(options)}export async function generateLibEntries(options){const result=await runAction(Action.GenerateLibraryEntries,{...options,cwd:projectPath()});if(result.isErr){await log.error("There was an error generating your library entry points",result.error);process.exit(ExitCode.FatalError)}log.success("Library entry points generated successfully")}export async function generateWebTypes(){await genWebTypes()}export async function generateVsCodeCustomData(){const result=await genVsCodeCustomData();if(result.isErr){await log.error("There was an error generating the custom-elements.json file.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated the custom-elements.json file")}export async function generateIdeHelpers(){await genWebTypes();await genVsCodeCustomData();await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated IDE helpers")}export async function generateComponentMeta(){const result=await genVsCodeCustomData();if(result.isErr){await log.error("There was an error generating your component meta information.",result.error);process.exit(ExitCode.FatalError)}await runAction(Action.LintFix,{verbose:!0,cwd:projectPath()});log.success("Successfully generated component meta information")}export async function generateTypes(options){try{const{generateAutoImportFiles}=await import("@stacksjs/server");await generateAutoImportFiles()}catch(error){log.debug("[generate:types] Could not refresh the auto-import registries",{error})}await generateRouteNames();try{const{generateServerAutoImportTypes}=await import("@stacksjs/server");await generateServerAutoImportTypes()}catch(error){log.debug("[generate:types] Could not refresh server auto-import declarations",{error})}const entry=frameworkPath("core/actions/src/generate/types.ts");if(!fs.existsSync(entry)){await import(`./types?t=${Date.now()}`);return}const result=await runNpmScript(NpmScript.GenerateTypes,{cwd:frameworkPath(),...options});if(result.isErr){log.error("There was an error generating your types.",result.error);throw result.error instanceof Error?result.error:Error(String(result.error))}log.success("Types were generated successfully")}export async function watchTypes(options){try{const manifestPath=`${frameworkPath()}/package.json`,text=fs.existsSync(manifestPath)?fs.readFileSync(manifestPath,"utf-8"):"";if(!(text?JSON.parse(text):{})?.scripts?.["generate:types"]){log.warn("[generate:types --watch] no `generate:types` script in framework package.json - type regeneration disabled. dev:api will keep running, but type definitions won't auto-refresh on model/config changes.");return}}catch{}const watched=[...await(async()=>{try{const{autoImportSourceDirs}=await import("@stacksjs/server");return autoImportSourceDirs()}catch{return[]}})(),projectPath("config"),projectPath("database/migrations"),projectPath("app"),projectPath("routes")].filter((dir,index,all)=>all.indexOf(dir)===index);log.info(`[generate:types --watch] watching ${watched.length} directories`);let pending=null,inflight=!1;const trigger=()=>{if(pending)clearTimeout(pending);pending=setTimeout(async()=>{if(inflight)return;inflight=!0;try{log.info("[generate:types --watch] change detected, regenerating\u2026");await generateTypes(options);log.success("[generate:types --watch] types up to date")}catch(err){log.error("[generate:types --watch] regeneration failed:",err)}finally{inflight=!1}},200)},watchers=[];for(const dir of watched)try{if(!fs.existsSync(dir))continue;const w=fs.watch(dir,{recursive:!0},()=>trigger());watchers.push(w)}catch(err){log.warn(`[generate:types --watch] cannot watch ${dir}: ${err}`)}if(watchers.length===0){log.warn("[generate:types --watch] no directories to watch - exiting");return}await new Promise((resolve)=>{process.once("SIGINT",()=>{log.info("[generate:types --watch] stopping");for(const w of watchers)try{w.close()}catch{}resolve()})})}export function generatePantryConfig(){log.success("Successfully generated `./pantry.yaml` based on your config")}export async function generateSeeder(){}export async function generateCoreSymlink(){const link=projectPath(".framework");if(fs.existsSync(link))await runCommand(`rm -f ${link}`);await runCommand(`ln -s ${frameworkPath()} ${link}`)}export async function generateOpenApiSpec(){const{generateOpenApi}=await import("@stacksjs/api");await generateOpenApi();log.success("Successfully generated Open API Spec")}
package/dist/make.js CHANGED
@@ -3,7 +3,7 @@ import process from"node:process";import{existsSync}from"node:fs";import{italic,
3
3
  `),truncated=lines.length>60?`
4
4
  ${italic(`\u2026 ${lines.length-60} more lines elided`)}`:"";log.info(`[dry-run] would write ${path} (${lines.length} lines)`);process.stdout.write(`${preview}${truncated}
5
5
 
6
- `);return}await writeTextFile({path,data})}async function createFileWithTemplate(path,templateKey,...args){await writeOrPreview(path,generateCode(templateKey,...args))}export async function invoke(options){if(options.component)await makeComponent(options);if(options.database)makeDatabase(options);if(options.function)await makeFunction(options);if(options.language)await makeLanguage(options);if(options.middleware)await createMiddleware(options);if(options.notification)await makeNotification(options);if(options.page)await makePage(options);if(options.stack)await makeStack(options)}export async function make(options){return await invoke(options)}function requireName(options,what){const name=options.name?.trim();if(!name){log.error(`A name is required to create a ${what}. Pass one with --name.`);process.exit(ExitCode.InvalidArgument)}return name}export async function makeAction(options){try{const name=requireName(options,"action");log.info("Creating your action...");await createAction(options);log.success(`Created ${italic(name)} action`)}catch(error){log.error("There was an error creating your action",error);process.exit(ExitCode.FatalError)}}export async function makeComponent(options){try{const name=requireName(options,"component");log.info("Creating your component...");await createComponent(options);log.success(`Created ${italic(name)} component`)}catch(error){log.error("There was an error creating your component",error);process.exit(ExitCode.FatalError)}}export async function createAction(options){const name=requireName(options,"component"),opts=options,wantsValidation=Boolean(opts.withValidation||options["with-validation"]),wantsAuth=Boolean(opts.withAuth||options["with-auth"]),templateKey=wantsValidation&&wantsAuth?"actionWithBoth":wantsValidation?"actionWithValidation":wantsAuth?"actionWithAuth":"action";await createFileWithTemplate(p.userActionsPath(name),templateKey,name)}export async function createComponent(options){const name=requireName(options,"component");await createFileWithTemplate(p.userComponentsPath(componentFileName(name)),"component",name)}export function componentFileName(name){return`${name}.stx`}export function makeDatabase(options){try{createDatabase(options)}catch(error){log.error("There was an error creating your database",error);process.exit(ExitCode.FatalError)}}export function createDatabase(options){const name=pascalCase(options.name||"MyModel");console.log(`
6
+ `);return}await writeTextFile({path,data})}async function createFileWithTemplate(path,templateKey,...args){await writeOrPreview(path,generateCode(templateKey,...args))}export async function invoke(options){if(options.component)await makeComponent(options);if(options.database)makeDatabase(options);if(options.function)await makeFunction(options);if(options.language)await makeLanguage(options);if(options.middleware)await createMiddleware(options);if(options.notification)await makeNotification(options);if(options.page)await makePage(options);if(options.stack)await makeStack(options)}export async function make(options){return await invoke(options)}function requireName(options,what){const name=options.name?.trim();if(!name){console.error(`A name is required to create a ${what}. Pass one with --name.`);process.exit(ExitCode.InvalidArgument)}return name}export async function makeAction(options){try{const name=requireName(options,"action");log.info("Creating your action...");await createAction(options);log.success(`Created ${italic(name)} action`)}catch(error){await log.error("There was an error creating your action",error);process.exit(ExitCode.FatalError)}}export async function makeComponent(options){try{const name=requireName(options,"component");log.info("Creating your component...");await createComponent(options);log.success(`Created ${italic(name)} component`)}catch(error){await log.error("There was an error creating your component",error);process.exit(ExitCode.FatalError)}}export async function createAction(options){const name=requireName(options,"component"),opts=options,wantsValidation=Boolean(opts.withValidation||options["with-validation"]),wantsAuth=Boolean(opts.withAuth||options["with-auth"]),templateKey=wantsValidation&&wantsAuth?"actionWithBoth":wantsValidation?"actionWithValidation":wantsAuth?"actionWithAuth":"action";await createFileWithTemplate(p.userActionsPath(name),templateKey,name)}export async function createComponent(options){const name=requireName(options,"component");await createFileWithTemplate(p.userComponentsPath(componentFileName(name)),"component",name)}export function componentFileName(name){return`${name}.stx`}export function makeDatabase(options){try{createDatabase(options)}catch(error){console.error("There was an error creating your database",error);process.exit(ExitCode.FatalError)}}export function createDatabase(options){const name=pascalCase(options.name||"MyModel");console.log(`
7
7
  Stacks manages the database through models and migrations, so there is no separate database to create.
8
8
 
9
9
  To add a "${name}" table:
@@ -12,7 +12,7 @@ import process from"node:process";import{existsSync}from"node:fs";import{italic,
12
12
  3. buddy migrate
13
13
 
14
14
  To use a different database engine or file, edit the connections in \`config/database.ts\`.
15
- `)}export async function factory(options){try{const name=requireName(options,"component");log.info(`Creating your ${italic(name)} factory...`);await createFactory(options)}catch(error){log.error("There was an error creating your factory",error);process.exit(ExitCode.FatalError)}}export async function createFactory(options){const name=options.name||"MyFactory",factoryName=name.endsWith("Factory")?name:`${name}Factory`;try{const{path:p}=await import("@stacksjs/path"),factoryPath=`${p.userDatabasePath("factories")}/${factoryName}.ts`;if(await Bun.file(factoryPath).exists()){log.warn(`Factory ${factoryName} already exists at ${factoryPath}`);return}const modelName=name.replace(/Factory$/,""),content=`import type { ${modelName}Model } from '@stacksjs/orm'
15
+ `)}export async function factory(options){try{const name=requireName(options,"component");log.info(`Creating your ${italic(name)} factory...`);await createFactory(options)}catch(error){await log.error("There was an error creating your factory",error);process.exit(ExitCode.FatalError)}}export async function createFactory(options){const name=options.name||"MyFactory",factoryName=name.endsWith("Factory")?name:`${name}Factory`;try{const{path:p}=await import("@stacksjs/path"),factoryPath=`${p.userDatabasePath("factories")}/${factoryName}.ts`;if(await Bun.file(factoryPath).exists()){log.warn(`Factory ${factoryName} already exists at ${factoryPath}`);return}const modelName=name.replace(/Factory$/,""),content=`import type { ${modelName}Model } from '@stacksjs/orm'
16
16
 
17
17
  export function ${factoryName}(): Partial<${modelName}Model> {
18
18
  return {
@@ -21,4 +21,4 @@ export function ${factoryName}(): Partial<${modelName}Model> {
21
21
  }
22
22
 
23
23
  export default ${factoryName}
24
- `;await writeOrPreview(factoryPath,content);log.success(`Created factory: ${factoryPath}`)}catch(error){log.error(`Failed to create factory ${factoryName}`,error);throw error}}export async function makeNotification(options){try{const name=requireName(options,"notification");log.info(`Creating your ${italic(name)} notification...`);await createNotification(options);log.success(`Created ${italic(name)} notification`)}catch(error){log.error("There was an error creating your notification",error);process.exit(ExitCode.FatalError)}}export async function createMail(options){const rawName=String(options.name||"").trim();if(!rawName){log.error("A mail name is required (e.g. `buddy make:mail OrderShipped`).");return!1}const className=pascalCase(rawName),kebabName=kebabCase(rawName),mailPath=p.userMailPath(`${className}.ts`),templatePath=p.userEmailsPath(`${kebabName}.stx`);if(!options.force){if(existsSync(mailPath)){console.error(`${italic(mailPath)} already exists - re-run with --force to overwrite.`);return!1}if(existsSync(templatePath)){console.error(`${italic(templatePath)} already exists - re-run with --force to overwrite.`);return!1}}if(!doesFolderExist(p.userMailPath()))await createFolder(p.userMailPath());if(!doesFolderExist(p.userEmailsPath()))await createFolder(p.userEmailsPath());try{await createFileWithTemplate(mailPath,"mail",className,kebabName);await createFileWithTemplate(templatePath,"mailTemplate",className,kebabName);return!0}catch(error){handleError("Error creating mailable",error);return!1}}export async function makeMail(options){try{const name=requireName(options,"mail");log.info(`Creating your ${italic(name)} mailable...`);if(!await createMail(options))process.exit(ExitCode.FatalError);log.success(`Created ${italic(pascalCase(name))} mailable + ${italic(kebabCase(name))}.stx template`)}catch(error){log.error("There was an error creating your mailable",error);process.exit(ExitCode.FatalError)}}export async function makePage(options){try{const name=requireName(options,"page");log.info("Creating your page...");await createPage(options);log.success(`Created ${name} page`)}catch(error){log.error("There was an error creating your page",error);process.exit(ExitCode.FatalError)}}export async function createPage(options){const name=requireName(options,"page");await createFileWithTemplate(p.userViewsPath(pageFileName(name)),"page",name)}export function pageFileName(name){return`${name}.stx`}export async function makeFunction(options){try{const name=requireName(options,"function");log.info("Creating your function...");await createFunction(options);log.success(`Created ${name} function`)}catch(error){log.error("There was an error creating your function",error);process.exit(ExitCode.FatalError)}}export async function createFunction(options){const name=requireName(options,"function");await createFileWithTemplate(p.userFunctionsPath(`${name}.ts`),"function",camelCase(name))}export async function makeLanguage(options){try{const name=requireName(options,"language");log.info("Creating your translation file...");await createLanguage(options);log.success(`Created ${name} translation file`)}catch(error){log.error("There was an error creating your language.",error);process.exit(ExitCode.FatalError)}}export async function createLanguage(options){const name=requireName(options,"language");await createFileWithTemplate(p.resourcesPath(`lang/${name}.yml`),"language",name)}export async function makeStack(options){try{const name=requireName(options,"stack");log.info(`Creating your ${name} stack...`);const stackDir=resolve(process.cwd(),name);if(doesFolderExist(stackDir)){log.error(`Directory "${name}" already exists`);process.exit(ExitCode.FatalError)}await createFolder(stackDir);const dirs=["app/Actions","app/Models","config","database/migrations","resources/views","resources/components","resources/functions","resources/partials","routes","public"];for(const dir of dirs)await createFolder(resolve(stackDir,dir));const shortName=name.replace(/^@[^/]+\//,"").replace(/-stack$/,"");await createFileWithTemplate(resolve(stackDir,"package.json"),"stackPackageJson",name,shortName);log.success(`Successfully scaffolded your "${name}" stack`);log.info("");log.info(` cd ${name}`);log.info(" # Add your models, actions, views, etc.");log.info(" # Push the repository, then submit its GitHub source to the Stacks registry.");log.info("");log.info(" Users install it with:");log.info(` buddy add ${shortName}`)}catch(error){log.error("There was an error creating your stack",error);process.exit(ExitCode.FatalError)}}export async function createNotification(options){const name=requireName(options,"stack");try{let importOption="EmailOptions";if(!doesFolderExist("notifications"))await createFolder("./notifications");if(options.chat)importOption="ChatOptions";if(options.sms)importOption="SMSOptions";await createFileWithTemplate(p.userNotificationsPath(`${name}.ts`),"notification",importOption);return!0}catch(error){handleError("Error creating notification",error);return!1}}export async function createMigration(options){const optionName=options.name;if(!optionName)throw Error("options.name is required and cannot be empty");const fileName=`${Date.now()}-${kebabCase(optionName)}.sql`,path=p.userMigrationsPath(fileName),snake=kebabCase(optionName).replace(/-/g,"_"),table=/^create_(.+)_table$/.exec(snake)?.[1]??"table_name",driver=(process.env.DB_CONNECTION||"sqlite").toLowerCase(),mysqlLike=driver==="mysql"||driver==="singlestore",tableIdentifier=mysqlLike?`\`${table}\``:`"${table}"`,primaryKey=driver==="postgres"?'"id" BIGSERIAL PRIMARY KEY':mysqlLike?"`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY":'"id" INTEGER PRIMARY KEY AUTOINCREMENT';try{await createFileWithTemplate(path,"migration",tableIdentifier,primaryKey);log.success(`Migration created: ${italic(`database/migrations/${fileName}`)}`)}catch(error){log.error(error)}}export async function makeQueueTable(){await runAction(Action.QueueTable)}export async function makeCertificate(){const domain=await localUrl();log.info("Creating SSL certificate...");await runCommand(`tlsx ${domain}`,{cwd:p.storagePath("keys")});log.success("Certificate created");log.info("Installing SSL certificate...");await runCommand("tlsx -install",{cwd:p.storagePath("keys")});log.success("Certificate installed")}export async function createModel(options){const optionName=options.name;if(!optionName)throw Error("options.name is required and cannot be empty");const name=optionName.charAt(0).toUpperCase()+optionName.slice(1),tableName=name.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"").replace(/([^s])$/,"$1s"),path=p.userModelsPath(`${name}.ts`);try{await createFileWithTemplate(path,"model",name,tableName);log.success(`Model created: ${italic(`app/Models/${name}.ts`)}`)}catch(error){log.error(error)}}export async function createMiddleware(options){const name=requireName(options,"certificate");await createFileWithTemplate(p.userMiddlewarePath(`${name}.ts`),"middleware",name)}
24
+ `;await writeOrPreview(factoryPath,content);log.success(`Created factory: ${factoryPath}`)}catch(error){log.error(`Failed to create factory ${factoryName}`,error);throw error}}export async function makeNotification(options){try{const name=requireName(options,"notification");log.info(`Creating your ${italic(name)} notification...`);await createNotification(options);log.success(`Created ${italic(name)} notification`)}catch(error){await log.error("There was an error creating your notification",error);process.exit(ExitCode.FatalError)}}export async function createMail(options){const rawName=String(options.name||"").trim();if(!rawName){log.error("A mail name is required (e.g. `buddy make:mail OrderShipped`).");return!1}const className=pascalCase(rawName),kebabName=kebabCase(rawName),mailPath=p.userMailPath(`${className}.ts`),templatePath=p.userEmailsPath(`${kebabName}.stx`);if(!options.force){if(existsSync(mailPath)){console.error(`${italic(mailPath)} already exists - re-run with --force to overwrite.`);return!1}if(existsSync(templatePath)){console.error(`${italic(templatePath)} already exists - re-run with --force to overwrite.`);return!1}}if(!doesFolderExist(p.userMailPath()))await createFolder(p.userMailPath());if(!doesFolderExist(p.userEmailsPath()))await createFolder(p.userEmailsPath());try{await createFileWithTemplate(mailPath,"mail",className,kebabName);await createFileWithTemplate(templatePath,"mailTemplate",className,kebabName);return!0}catch(error){handleError("Error creating mailable",error);return!1}}export async function makeMail(options){try{const name=requireName(options,"mail");log.info(`Creating your ${italic(name)} mailable...`);if(!await createMail(options))process.exit(ExitCode.FatalError);log.success(`Created ${italic(pascalCase(name))} mailable + ${italic(kebabCase(name))}.stx template`)}catch(error){await log.error("There was an error creating your mailable",error);process.exit(ExitCode.FatalError)}}export async function makePage(options){try{const name=requireName(options,"page");log.info("Creating your page...");await createPage(options);log.success(`Created ${name} page`)}catch(error){await log.error("There was an error creating your page",error);process.exit(ExitCode.FatalError)}}export async function createPage(options){const name=requireName(options,"page");await createFileWithTemplate(p.userViewsPath(pageFileName(name)),"page",name)}export function pageFileName(name){return`${name}.stx`}export async function makeFunction(options){try{const name=requireName(options,"function");log.info("Creating your function...");await createFunction(options);log.success(`Created ${name} function`)}catch(error){await log.error("There was an error creating your function",error);process.exit(ExitCode.FatalError)}}export async function createFunction(options){const name=requireName(options,"function");await createFileWithTemplate(p.userFunctionsPath(`${name}.ts`),"function",camelCase(name))}export async function makeLanguage(options){try{const name=requireName(options,"language");log.info("Creating your translation file...");await createLanguage(options);log.success(`Created ${name} translation file`)}catch(error){await log.error("There was an error creating your language.",error);process.exit(ExitCode.FatalError)}}export async function createLanguage(options){const name=requireName(options,"language");await createFileWithTemplate(p.resourcesPath(`lang/${name}.yml`),"language",name)}export async function makeStack(options){try{const name=requireName(options,"stack");log.info(`Creating your ${name} stack...`);const stackDir=resolve(process.cwd(),name);if(doesFolderExist(stackDir)){await log.error(`Directory "${name}" already exists`);process.exit(ExitCode.FatalError)}await createFolder(stackDir);const dirs=["app/Actions","app/Models","config","database/migrations","resources/views","resources/components","resources/functions","resources/partials","routes","public"];for(const dir of dirs)await createFolder(resolve(stackDir,dir));const shortName=name.replace(/^@[^/]+\//,"").replace(/-stack$/,"");await createFileWithTemplate(resolve(stackDir,"package.json"),"stackPackageJson",name,shortName);log.success(`Successfully scaffolded your "${name}" stack`);log.info("");log.info(` cd ${name}`);log.info(" # Add your models, actions, views, etc.");log.info(" # Push the repository, then submit its GitHub source to the Stacks registry.");log.info("");log.info(" Users install it with:");log.info(` buddy add ${shortName}`)}catch(error){await log.error("There was an error creating your stack",error);process.exit(ExitCode.FatalError)}}export async function createNotification(options){const name=requireName(options,"stack");try{let importOption="EmailOptions";if(!doesFolderExist("notifications"))await createFolder("./notifications");if(options.chat)importOption="ChatOptions";if(options.sms)importOption="SMSOptions";await createFileWithTemplate(p.userNotificationsPath(`${name}.ts`),"notification",importOption);return!0}catch(error){handleError("Error creating notification",error);return!1}}export async function createMigration(options){const optionName=options.name;if(!optionName)throw Error("options.name is required and cannot be empty");const fileName=`${Date.now()}-${kebabCase(optionName)}.sql`,path=p.userMigrationsPath(fileName),snake=kebabCase(optionName).replace(/-/g,"_"),table=/^create_(.+)_table$/.exec(snake)?.[1]??"table_name",driver=(process.env.DB_CONNECTION||"sqlite").toLowerCase(),mysqlLike=driver==="mysql"||driver==="singlestore",tableIdentifier=mysqlLike?`\`${table}\``:`"${table}"`,primaryKey=driver==="postgres"?'"id" BIGSERIAL PRIMARY KEY':mysqlLike?"`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY":'"id" INTEGER PRIMARY KEY AUTOINCREMENT';try{await createFileWithTemplate(path,"migration",tableIdentifier,primaryKey);log.success(`Migration created: ${italic(`database/migrations/${fileName}`)}`)}catch(error){log.error(error)}}export async function makeQueueTable(){await runAction(Action.QueueTable)}export async function makeCertificate(){const domain=await localUrl();log.info("Creating SSL certificate...");await runCommand(`tlsx ${domain}`,{cwd:p.storagePath("keys")});log.success("Certificate created");log.info("Installing SSL certificate...");await runCommand("tlsx -install",{cwd:p.storagePath("keys")});log.success("Certificate installed")}export async function createModel(options){const optionName=options.name;if(!optionName)throw Error("options.name is required and cannot be empty");const name=optionName.charAt(0).toUpperCase()+optionName.slice(1),tableName=name.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"").replace(/([^s])$/,"$1s"),path=p.userModelsPath(`${name}.ts`);try{await createFileWithTemplate(path,"model",name,tableName);log.success(`Model created: ${italic(`app/Models/${name}.ts`)}`)}catch(error){log.error(error)}}export async function createMiddleware(options){const name=requireName(options,"certificate");await createFileWithTemplate(p.userMiddlewarePath(`${name}.ts`),"middleware",name)}
@@ -1 +1 @@
1
- import process from"node:process";import{generateMigrations,runDatabaseMigration}from"@stacksjs/database";import{log}from"@stacksjs/logging";const skipGeneration=process.env.STACKS_MIGRATE_NO_GENERATE==="1",generated=skipGeneration?void 0:await generateMigrations();if(skipGeneration)log.debug("[stacks] migration generation skipped (--no-generate); applying committed files only");if(generated?.isErr){console.error("Generating migrations failed. The model diff could not be turned into SQL.");console.error(String(generated.error?.message??generated.error));log.error("[stacks] generateMigrations failed",generated.error);process.exit(1)}const migrated=await runDatabaseMigration();if(migrated.isErr){console.error("Running migrations failed. The files were written; applying them to the database did not succeed.");console.error(String(migrated.error?.message??migrated.error));log.error("[stacks] runDatabaseMigration failed",migrated.error);process.exit(1)}process.exit(0);
1
+ import process from"node:process";import{generateMigrations,runDatabaseMigration}from"@stacksjs/database";import{log}from"@stacksjs/logging";const skipGeneration=process.env.STACKS_MIGRATE_NO_GENERATE==="1",generated=skipGeneration?void 0:await generateMigrations();if(skipGeneration)log.debug("[stacks] migration generation skipped (--no-generate); applying committed files only");if(generated?.isErr){console.error("Generating migrations failed. The model diff could not be turned into SQL.");console.error(String(generated.error?.message??generated.error));await log.error("[stacks] generateMigrations failed",generated.error);process.exit(1)}const migrated=await runDatabaseMigration();if(migrated.isErr){console.error("Running migrations failed. The files were written; applying them to the database did not succeed.");console.error(String(migrated.error?.message??migrated.error));await log.error("[stacks] runDatabaseMigration failed",migrated.error);process.exit(1)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{ensureUtcDatetimeColumns,generateMigrations,migrateAuthTables,migrateNotificationTables,migrateRbacTables,migrateTraitTables,resetDatabase,runDatabaseMigration}from"@stacksjs/database";import{log}from"@stacksjs/logging";const resetResult=await resetDatabase();if(resetResult?.isErr){console.error(resetResult.error);log.error("resetDatabase failed",resetResult.error);process.exit(1)}const genResult=await generateMigrations();if(genResult?.isErr){console.error(genResult.error);log.error("generateMigrations failed",genResult.error);process.exit(1)}const authResult=await migrateAuthTables();if(!authResult.success)log.error(`Failed to migrate auth tables: ${authResult.error}`);const notificationBootstrap=await migrateNotificationTables({tables:["notifications","notification_deliveries"]});if(!notificationBootstrap.success)log.error(`Failed to bootstrap notification tables: ${notificationBootstrap.error}`);const migrateResult=await runDatabaseMigration(),notifResult=await migrateNotificationTables();if(!notifResult.success)log.error(`Failed to migrate notification tables: ${notifResult.error}`);const rbacResult=await migrateRbacTables();if(!rbacResult.success)log.error(`Failed to migrate RBAC tables: ${rbacResult.error}`);const traitResult=await migrateTraitTables();if(!traitResult.success)log.error(`Failed to migrate polymorphic trait tables: ${traitResult.error}`);const datetimeResult=await ensureUtcDatetimeColumns();if(!datetimeResult.success)log.error(`Failed to convert TIMESTAMP columns to DATETIME: ${datetimeResult.error}`);if(migrateResult.isErr){log.error("runDatabaseMigration failed");log.error(migrateResult.error);process.exit(1)}process.exit(0);
1
+ import process from"node:process";import{ensureUtcDatetimeColumns,generateMigrations,migrateAuthTables,migrateNotificationTables,migrateRbacTables,migrateTraitTables,resetDatabase,runDatabaseMigration}from"@stacksjs/database";import{log}from"@stacksjs/logging";const resetResult=await resetDatabase();if(resetResult?.isErr){console.error(resetResult.error);await log.error("resetDatabase failed",resetResult.error);process.exit(1)}const genResult=await generateMigrations();if(genResult?.isErr){console.error(genResult.error);await log.error("generateMigrations failed",genResult.error);process.exit(1)}const authResult=await migrateAuthTables();if(!authResult.success)log.error(`Failed to migrate auth tables: ${authResult.error}`);const notificationBootstrap=await migrateNotificationTables({tables:["notifications","notification_deliveries"]});if(!notificationBootstrap.success)log.error(`Failed to bootstrap notification tables: ${notificationBootstrap.error}`);const migrateResult=await runDatabaseMigration(),notifResult=await migrateNotificationTables();if(!notifResult.success)log.error(`Failed to migrate notification tables: ${notifResult.error}`);const rbacResult=await migrateRbacTables();if(!rbacResult.success)log.error(`Failed to migrate RBAC tables: ${rbacResult.error}`);const traitResult=await migrateTraitTables();if(!traitResult.success)log.error(`Failed to migrate polymorphic trait tables: ${traitResult.error}`);const datetimeResult=await ensureUtcDatetimeColumns();if(!datetimeResult.success)log.error(`Failed to convert TIMESTAMP columns to DATETIME: ${datetimeResult.error}`);if(migrateResult.isErr){await log.error("runDatabaseMigration failed");await log.error(migrateResult.error);process.exit(1)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{runtimePath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";log.info("Running prepublish command...");const result=await runNpmScript(NpmScript.BuildStacks,{verbose:!0,cwd:runtimePath()});if(result.isErr){log.error(Error("There was an error while prepublishing your stack"),result.error);process.exit(ExitCode.FatalError)}log.success("Prepublishing completed");
1
+ import process from"node:process";import{NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{runtimePath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";log.info("Running prepublish command...");const result=await runNpmScript(NpmScript.BuildStacks,{verbose:!0,cwd:runtimePath()});if(result.isErr){await log.error(Error("There was an error while prepublishing your stack"),result.error);process.exit(ExitCode.FatalError)}log.success("Prepublishing completed");
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{Job}from"@stacksjs/orm";const options=parseArgs(),queueName=options.queue,force=options.force==="true";try{if(!force){log.warn("This will delete all jobs from the queue. Use --force to confirm.");process.exit(1)}let deletedCount=0;if(queueName){log.info(`Clearing all jobs from queue "${queueName}"...`);const jobs=await Job.where("queue",queueName).get();deletedCount=jobs.length;for(const job of jobs)await job.delete()}else{log.info("Clearing all jobs from all queues...");const jobs=await Job.all();deletedCount=jobs.length;for(const job of jobs)await job.delete()}log.success(`Cleared ${deletedCount} job(s)`);process.exit(0)}catch(error){log.error("Failed to clear jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{Job}from"@stacksjs/orm";const options=parseArgs(),queueName=options.queue,force=options.force==="true";try{if(!force){log.warn("This will delete all jobs from the queue. Use --force to confirm.");process.exit(1)}let deletedCount=0;if(queueName){log.info(`Clearing all jobs from queue "${queueName}"...`);const jobs=await Job.where("queue",queueName).get();deletedCount=jobs.length;for(const job of jobs)await job.delete()}else{log.info("Clearing all jobs from all queues...");const jobs=await Job.all();deletedCount=jobs.length;for(const job of jobs)await job.delete()}log.success(`Cleared ${deletedCount} job(s)`);process.exit(0)}catch(error){await log.error("Failed to clear jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{purgeDeadLetterJobs}from"@stacksjs/queue";const options=parseArgs(),days=options.olderThanDays?Number.parseInt(options.olderThanDays,10):options["older-than-days"]?Number.parseInt(options["older-than-days"],10):30;if(!Number.isFinite(days)||days<1){log.error(`Invalid --older-than-days value '${options.olderThanDays??options["older-than-days"]}'`);process.exit(1)}try{log.info(`Pruning dead-letter rows older than ${days} day(s)...`);const deleted=await purgeDeadLetterJobs(days);log.success(`Deleted ${deleted} dead-letter row(s)`);process.exit(0)}catch(error){log.error("Failed to purge dead-letter jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{purgeDeadLetterJobs}from"@stacksjs/queue";const options=parseArgs(),days=options.olderThanDays?Number.parseInt(options.olderThanDays,10):options["older-than-days"]?Number.parseInt(options["older-than-days"],10):30;if(!Number.isFinite(days)||days<1){await log.error(`Invalid --older-than-days value '${options.olderThanDays??options["older-than-days"]}'`);process.exit(1)}try{log.info(`Pruning dead-letter rows older than ${days} day(s)...`);const deleted=await purgeDeadLetterJobs(days);log.success(`Deleted ${deleted} dead-letter row(s)`);process.exit(0)}catch(error){await log.error("Failed to purge dead-letter jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{retryDeadLetterJob}from"@stacksjs/queue";const options=parseArgs(),idArg=options.id;if(!idArg){log.error("Usage: buddy queue:dlq:retry --id=<id>");process.exit(1)}const id=Number.parseInt(idArg,10);if(Number.isNaN(id)){log.error("Invalid id provided");process.exit(1)}try{if(!await retryDeadLetterJob(id)){log.error(`Dead-letter job ${id} not found (or DLQ table missing)`);process.exit(1)}log.success(`Re-enqueued dead-letter job ${id}`);process.exit(0)}catch(error){log.error("Failed to re-enqueue dead-letter job",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{retryDeadLetterJob}from"@stacksjs/queue";const options=parseArgs(),idArg=options.id;if(!idArg){await log.error("Usage: buddy queue:dlq:retry --id=<id>");process.exit(1)}const id=Number.parseInt(idArg,10);if(Number.isNaN(id)){await log.error("Invalid id provided");process.exit(1)}try{if(!await retryDeadLetterJob(id)){await log.error(`Dead-letter job ${id} not found (or DLQ table missing)`);process.exit(1)}log.success(`Re-enqueued dead-letter job ${id}`);process.exit(0)}catch(error){await log.error("Failed to re-enqueue dead-letter job",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
package/dist/queue/dlq.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{listDeadLetterJobs}from"@stacksjs/queue";const options=parseArgs(),queueName=options.queue||void 0,reason=options.reason||void 0,sinceArg=options.since,limit=options.limit?Number.parseInt(options.limit,10):100;try{log.info(`Fetching dead-letter jobs...
2
2
  `);const sinceCutoffMs=sinceArg?parseSinceDuration(sinceArg)??void 0:void 0,rows=await listDeadLetterJobs({queue:queueName,reason,sinceCutoffMs,limit});if(rows.length===0){console.log("\u2713 No dead-letter jobs found");process.exit(0)}console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 ID \u2502 Queue \u2502 Reason \u2502 Failures \u2502 Dead-Lettered At \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");for(const row of rows){const id=String(row.id).padEnd(3),q=(row.queue||"default").substring(0,32).padEnd(32),r=(row.reason||"unknown").substring(0,16).padEnd(16),f=String(row.total_failures??1).padEnd(8),dt=(row.dead_lettered_at||"N/A").substring(0,24).padEnd(24);console.log(`\u2502 ${id} \u2502 ${q} \u2502 ${r} \u2502 ${f} \u2502 ${dt} \u2502`)}console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");console.log(`
3
3
  Showing ${rows.length} dead-letter job(s)`);process.exit(0)}catch(error){const message=error instanceof Error?error.message:String(error);if(/no such table:\s*dead_letter_jobs\b/i.test(message)){process.stdout.write(`Dead-letter queue table is not set up yet.
4
- `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}log.error("Failed to fetch dead-letter jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}function parseSinceDuration(input){const m=/^(\d+)([smhd])$/.exec(input.trim());if(!m)return null;const n=Number(m[1]);if(!Number.isFinite(n)||n<=0)return null;const mult=m[2]==="s"?1000:m[2]==="m"?60000:m[2]==="h"?3600000:86400000;return Date.now()-n*mult}
4
+ `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}await log.error("Failed to fetch dead-letter jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}function parseSinceDuration(input){const m=/^(\d+)([smhd])$/.exec(input.trim());if(!m)return null;const n=Number(m[1]);if(!Number.isFinite(n)||n<=0)return null;const mult=m[2]==="s"?1000:m[2]==="m"?60000:m[2]==="h"?3600000:86400000;return Date.now()-n*mult}
@@ -1,4 +1,4 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{FailedJob}from"@stacksjs/orm";const options=parseArgs(),queueName=options.queue,sinceArg=options.since,sinceCutoff=sinceArg?parseSinceDuration(sinceArg):null;try{log.info(`Fetching failed jobs...
2
2
  `);let failedJobs;if(queueName)failedJobs=await FailedJob.where("queue",queueName).get();else failedJobs=await FailedJob.all();if(sinceCutoff)failedJobs=failedJobs.filter((j)=>{if(!j.failed_at)return!1;const ts=Date.parse(j.failed_at);return Number.isFinite(ts)&&ts>=sinceCutoff});if(failedJobs.length===0){if(sinceCutoff)console.log(`\u2713 No failed jobs found since ${sinceArg}`);else console.log("\u2713 No failed jobs found");process.exit(0)}console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 ID \u2502 Queue \u2502 Failed At \u2502 Exception \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");for(const job of failedJobs){const id=String(job.id).padEnd(3),queue=(job.queue||"default").substring(0,48).padEnd(48),failedAt=(job.failed_at||"N/A").substring(0,21).padEnd(21),exception=(job.exception||"Unknown error").substring(0,31).padEnd(31);console.log(`\u2502 ${id} \u2502 ${queue} \u2502 ${failedAt} \u2502 ${exception} \u2502`)}console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");console.log(`
3
3
  Total: ${failedJobs.length} failed job(s)`);process.exit(0)}catch(error){const message=error instanceof Error?error.message:String(error);if(/no such table:\s*(?:jobs|failed_jobs)\b/i.test(message)){process.stdout.write(`Queue tables are not set up yet.
4
- `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}log.error("Failed to fetch failed jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}function parseSinceDuration(input){const m=/^(\d+)([smhd])$/.exec(input.trim());if(!m)return null;const n=Number(m[1]);if(!Number.isFinite(n)||n<=0)return null;const mult=m[2]==="s"?1000:m[2]==="m"?60000:m[2]==="h"?3600000:86400000;return Date.now()-n*mult}
4
+ `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}await log.error("Failed to fetch failed jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}function parseSinceDuration(input){const m=/^(\d+)([smhd])$/.exec(input.trim());if(!m)return null;const n=Number(m[1]);if(!Number.isFinite(n)||n<=0)return null;const mult=m[2]==="s"?1000:m[2]==="m"?60000:m[2]==="h"?3600000:86400000;return Date.now()-n*mult}
@@ -1,2 +1,2 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{FailedJob}from"@stacksjs/orm";const options=parseArgs(),queueName=options.queue,force=options.force==="true";try{if(!force){log.warn("This will permanently delete all failed jobs. Use --force to confirm.");process.exit(1)}let deletedCount=0;if(queueName){log.info(`Flushing all failed jobs from queue "${queueName}"...`);const failedJobs=await FailedJob.where("queue",queueName).get();deletedCount=failedJobs.length;for(const job of failedJobs)await job.delete()}else{log.info("Flushing all failed jobs from all queues...");const failedJobs=await FailedJob.all();deletedCount=failedJobs.length;for(const job of failedJobs)await job.delete()}log.success(`Flushed ${deletedCount} failed job(s)`);process.exit(0)}catch(error){const message=error instanceof Error?error.message:String(error);if(/no such table:\s*(?:jobs|failed_jobs)\b/i.test(message)){process.stdout.write(`Queue tables are not set up yet.
2
- `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}log.error("Failed to flush failed jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
2
+ `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}await log.error("Failed to flush failed jobs",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
@@ -1,7 +1,7 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{Job,FailedJob}from"@stacksjs/orm";const options=parseArgs(),jobId=options.id,failedFlag=options.failed==="true";try{if(!jobId){log.error("Please provide a job ID");log.info("Usage: buddy queue:inspect --id=<jobId> [--failed]");process.exit(1)}const id=Number.parseInt(jobId,10);if(Number.isNaN(id)){log.error("Invalid job ID provided");process.exit(1)}if(failedFlag){const failedResult=await FailedJob.find(id);if(!failedResult){log.error(`Failed job with ID ${id} not found`);process.exit(1)}const job=failedResult;console.log(`
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{Job,FailedJob}from"@stacksjs/orm";const options=parseArgs(),jobId=options.id,failedFlag=options.failed==="true";try{if(!jobId){await log.error("Please provide a job ID");log.info("Usage: buddy queue:inspect --id=<jobId> [--failed]");process.exit(1)}const id=Number.parseInt(jobId,10);if(Number.isNaN(id)){await log.error("Invalid job ID provided");process.exit(1)}if(failedFlag){const failedResult=await FailedJob.find(id);if(!failedResult){await log.error(`Failed job with ID ${id} not found`);process.exit(1)}const job=failedResult;console.log(`
2
2
  \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`);console.log("\u2502 Failed Job Details \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log(`\u2502 ID: ${String(job.id).padEnd(52)} \u2502`);console.log(`\u2502 Queue: ${(job.queue||"default").padEnd(52)} \u2502`);console.log(`\u2502 Connection: ${(job.connection||"database").padEnd(52)} \u2502`);console.log(`\u2502 Failed At: ${(job.failed_at||"N/A").padEnd(52)} \u2502`);console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log("\u2502 Exception: \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");const exception=job.exception||"Unknown error",lines=exception.match(/.{1,62}/g)||[exception];for(const line of lines.slice(0,20))console.log(`\u2502 ${line.padEnd(62)} \u2502`);if(lines.length>20)console.log(`\u2502 ... (${lines.length-20} more lines)`.padEnd(64)+" \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log("\u2502 Payload: \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");if(job.payload)try{const payload=JSON.parse(job.payload);console.log(`\u2502 Name: ${payload.name.padEnd(52)} \u2502`);console.log(`\u2502 Path: ${payload.path.padEnd(52)} \u2502`);console.log(`\u2502 Max Tries: ${String(payload.maxTries).padEnd(52)} \u2502`);console.log("\u2502 Parameters: \u2502");const paramLines=JSON.stringify(payload.params,null,2).split(`
3
3
  `);for(const line of paramLines.slice(0,15))console.log(`\u2502 ${line.substring(0,60).padEnd(60)} \u2502`);if(paramLines.length>15)console.log(`\u2502 ... (${paramLines.length-15} more lines)`.padEnd(64)+" \u2502")}catch{console.log(`\u2502 Raw: ${job.payload.substring(0,58).padEnd(58)} \u2502`)}else console.log("\u2502 (No payload) \u2502");console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");console.log(`
4
- Actions:`);console.log(` Retry this job: buddy queue:retry --id=${id}`);console.log(` Delete this job: buddy queue:flush --id=${id}`)}else{const jobResult=await Job.find(id);if(!jobResult){log.error(`Job with ID ${id} not found`);log.info("Tip: Use --failed flag to inspect failed jobs");process.exit(1)}const job=jobResult,now=Math.floor(Date.now()/1000);let status="Pending";if(job.reserved_at)status="Processing";else if(job.available_at&&job.available_at>now)status="Delayed";console.log(`
4
+ Actions:`);console.log(` Retry this job: buddy queue:retry --id=${id}`);console.log(` Delete this job: buddy queue:flush --id=${id}`)}else{const jobResult=await Job.find(id);if(!jobResult){await log.error(`Job with ID ${id} not found`);log.info("Tip: Use --failed flag to inspect failed jobs");process.exit(1)}const job=jobResult,now=Math.floor(Date.now()/1000);let status="Pending";if(job.reserved_at)status="Processing";else if(job.available_at&&job.available_at>now)status="Delayed";console.log(`
5
5
  \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`);console.log("\u2502 Job Details \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log(`\u2502 ID: ${String(job.id).padEnd(50)} \u2502`);console.log(`\u2502 Queue: ${(job.queue||"default").padEnd(50)} \u2502`);console.log(`\u2502 Status: ${status.padEnd(50)} \u2502`);console.log(`\u2502 Attempts: ${String(job.attempts||0).padEnd(50)} \u2502`);console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");if(job.available_at){const availableDate=new Date(job.available_at*1000);console.log(`\u2502 Available At: ${availableDate.toISOString().padEnd(50)} \u2502`)}if(job.reserved_at){const reservedDate=new Date(job.reserved_at*1000);console.log(`\u2502 Reserved At: ${reservedDate.toISOString().padEnd(50)} \u2502`)}if(job.created_at)console.log(`\u2502 Created At: ${String(job.created_at).padEnd(50)} \u2502`);console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log("\u2502 Payload: \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");if(job.payload)try{const payload=JSON.parse(job.payload);console.log(`\u2502 Name: ${payload.name.padEnd(52)} \u2502`);console.log(`\u2502 Path: ${payload.path.padEnd(52)} \u2502`);console.log(`\u2502 Max Tries: ${String(payload.maxTries).padEnd(52)} \u2502`);console.log(`\u2502 Timeout: ${String(payload.timeOut||"N/A").padEnd(52)} \u2502`);console.log("\u2502 Parameters: \u2502");const paramLines=JSON.stringify(payload.params,null,2).split(`
6
6
  `);for(const line of paramLines.slice(0,15))console.log(`\u2502 ${line.substring(0,60).padEnd(60)} \u2502`);if(paramLines.length>15)console.log(`\u2502 ... (${paramLines.length-15} more lines)`.padEnd(64)+" \u2502");if(payload.classPayload)try{const classConfig=JSON.parse(payload.classPayload);console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log("\u2502 Job Configuration: \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log(`\u2502 Tries: ${String(classConfig.tries||3).padEnd(52)} \u2502`);console.log(`\u2502 Timeout: ${String(classConfig.timeout||60).padEnd(52)} \u2502`);if(classConfig.backoff)console.log(`\u2502 Backoff: ${String(JSON.stringify(classConfig.backoff)).substring(0,52).padEnd(52)} \u2502`)}catch{}}catch{console.log(`\u2502 Raw: ${job.payload.substring(0,58).padEnd(58)} \u2502`)}else console.log("\u2502 (No payload) \u2502");console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");if(status==="Delayed"&&job.available_at){const waitTime=job.available_at-now;console.log(`
7
- This job will be available in ${waitTime} second(s)`)}}process.exit(0)}catch(error){log.error("Failed to inspect job",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
7
+ This job will be available in ${waitTime} second(s)`)}}process.exit(0)}catch(error){await log.error("Failed to inspect job",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
@@ -1,4 +1,4 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{Job}from"@stacksjs/orm";const options=parseArgs(),queueName=options.queue,statusFilter=options.status,limit=options.limit?Number.parseInt(options.limit,10):50;try{log.info(`Fetching queued jobs...
2
2
  `);let jobs=queueName?await Job.where("queue",queueName).get():await Job.all();const nowSec=Math.floor(Date.now()/1000);if(statusFilter==="pending")jobs=jobs.filter((j)=>!j.reserved_at&&(j.available_at??0)<=nowSec);else if(statusFilter==="reserved")jobs=jobs.filter((j)=>Boolean(j.reserved_at));else if(statusFilter==="delayed")jobs=jobs.filter((j)=>!j.reserved_at&&(j.available_at??0)>nowSec);if(Number.isFinite(limit)&&limit>0)jobs=jobs.slice(0,limit);if(jobs.length===0){console.log("\u2713 No queued jobs found");process.exit(0)}console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 ID \u2502 Queue \u2502 Job \u2502 Attempts \u2502 Status \u2502 Available At \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");for(const j of jobs){const jobAny=j,id=String(jobAny.id??"").padEnd(3),queue=String(jobAny.queue??"default").substring(0,18).padEnd(18),jobName=extractJobName(jobAny.payload).substring(0,32).padEnd(32),attempts=String(jobAny.attempts??0).padEnd(8),status=(jobAny.reserved_at?"reserved":(jobAny.available_at??0)>nowSec?"delayed":"pending").padEnd(8),avail=formatUnixTime(jobAny.available_at).padEnd(19);console.log(`\u2502 ${id} \u2502 ${queue} \u2502 ${jobName} \u2502 ${attempts} \u2502 ${status} \u2502 ${avail} \u2502`)}console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");console.log(`
3
3
  Showing ${jobs.length} job(s)${queueName?` on queue '${queueName}'`:""}${statusFilter?` (status=${statusFilter})`:""}`);process.exit(0)}catch(error){const message=error instanceof Error?error.message:String(error);if(/no such table:\s*jobs\b/i.test(message)){process.stdout.write(`Queue tables are not set up yet.
4
- `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}log.error("Failed to list queued jobs",error);process.exit(1)}function extractJobName(payload){if(typeof payload!=="string")return"unknown";try{const parsed=JSON.parse(payload);return parsed.jobName??parsed.name??"unknown"}catch{return"unknown"}}function formatUnixTime(seconds){if(typeof seconds!=="number"||!Number.isFinite(seconds))return"N/A";return new Date(seconds*1000).toISOString().slice(0,19).replace("T"," ")}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
4
+ `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}await log.error("Failed to list queued jobs",error);process.exit(1)}function extractJobName(payload){if(typeof payload!=="string")return"unknown";try{const parsed=JSON.parse(payload);return parsed.jobName??parsed.name??"unknown"}catch{return"unknown"}}function formatUnixTime(seconds){if(typeof seconds!=="number"||!Number.isFinite(seconds))return"N/A";return new Date(seconds*1000).toISOString().slice(0,19).replace("T"," ")}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -3,4 +3,4 @@ import process from"node:process";import{log}from"@stacksjs/logging";import{Job,
3
3
  Monitoring stopped.`);process.exit(0)});process.on("SIGTERM",()=>{isRunning=!1;process.exit(0)});async function getStats(){const allJobs=queueName?await Job.where("queue",queueName).get():await Job.all(),failedJobs=queueName?await FailedJob.where("queue",queueName).get():await FailedJob.all(),now=Math.floor(Date.now()/1000);let pending=0,processing=0,delayed=0;for(const job of allJobs)if(job.reserved_at)processing++;else if(job.available_at&&Number(job.available_at)>now)delayed++;else pending++;return{pending,processing,delayed,failed:failedJobs.length,total:allJobs.length,workerRunning:isWorkerRunning(),activeJobs:getActiveJobCount()}}async function displayStats(){const stats=await getStats(),timestamp=new Date().toLocaleTimeString();console.clear();console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 Queue Monitor \u2502");console.log("\u2502 Press Ctrl+C to exit \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log(`\u2502 Last Updated: ${timestamp.padEnd(48)} \u2502`);console.log(`\u2502 Queue Filter: ${(queueName||"All Queues").padEnd(48)} \u2502`);console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log("\u2502 \u2502");console.log(`\u2502 Pending Jobs: ${String(stats.pending).padStart(8)} \u2502`);console.log(`\u2502 Processing Jobs: ${String(stats.processing).padStart(8)} \u2502`);console.log(`\u2502 Delayed Jobs: ${String(stats.delayed).padStart(8)} \u2502`);console.log(`\u2502 Failed Jobs: ${String(stats.failed).padStart(8)} \u2502`);console.log("\u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502");console.log(`\u2502 Total Jobs: ${String(stats.total).padStart(8)} \u2502`);console.log("\u2502 \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log("\u2502 Worker Status \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");console.log(`\u2502 Worker Running: ${(stats.workerRunning?"Yes":"No").padEnd(8)} \u2502`);console.log(`\u2502 Active Jobs: ${String(stats.activeJobs).padStart(8)} \u2502`);console.log("\u2502 \u2502");console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");if(stats.failed>0)console.log(`
4
4
  \u26A0\uFE0F Warning: ${stats.failed} failed job(s) - run 'buddy queue:failed' for details`);if(!stats.workerRunning&&stats.pending>0)console.log(`
5
5
  \u26A0\uFE0F Warning: Worker not running but ${stats.pending} job(s) pending - run 'buddy queue:work'`)}try{log.info(`Starting queue monitor...
6
- `);await displayStats();while(isRunning){await new Promise((resolve)=>setTimeout(resolve,refreshInterval));if(isRunning)await displayStats()}}catch(error){log.error("Monitor error:",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
6
+ `);await displayStats();while(isRunning){await new Promise((resolve)=>setTimeout(resolve,refreshInterval));if(isRunning)await displayStats()}}catch(error){await log.error("Monitor error:",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{pauseQueue}from"@stacksjs/queue";const options=parseArgs(),queue=options.queue;if(!queue){log.error("Usage: buddy queue:pause --queue=<name> [--for=300]");process.exit(1)}const pauseSeconds=options.for?Number.parseInt(options.for,10):300;try{await pauseQueue(queue,pauseSeconds);log.success(`Paused queue '${queue}' for ${pauseSeconds}s`);process.exit(0)}catch(error){log.error("Failed to pause queue",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{pauseQueue}from"@stacksjs/queue";const options=parseArgs(),queue=options.queue;if(!queue){await log.error("Usage: buddy queue:pause --queue=<name> [--for=300]");process.exit(1)}const pauseSeconds=options.for?Number.parseInt(options.for,10):300;try{await pauseQueue(queue,pauseSeconds);log.success(`Paused queue '${queue}' for ${pauseSeconds}s`);process.exit(0)}catch(error){await log.error("Failed to pause queue",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -1,2 +1,2 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{listQuarantined,quarantineJob}from"@stacksjs/queue";const options=parseArgs();try{if(options.add){const jobName=options.add;await quarantineJob(jobName);log.success(`Quarantined ${jobName}`);process.exit(0)}const rows=await listQuarantined();if(rows.length===0){console.log("\u2713 No jobs currently quarantined");process.exit(0)}console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 ID \u2502 Job Name \u2502 Payload Hash \u2502 Failures \u2502 Quarantined At \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");for(const row of rows){const id=String(row.id).padEnd(3),jn=(row.job_name||"?").substring(0,32).padEnd(32),ph=(row.payload_hash||"?").substring(0,16).padEnd(16),fc=String(row.failure_count??0).padEnd(8),qa=(row.quarantined_at||"N/A").substring(0,24).padEnd(24);console.log(`\u2502 ${id} \u2502 ${jn} \u2502 ${ph} \u2502 ${fc} \u2502 ${qa} \u2502`)}console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");console.log(`
2
- ${rows.length} quarantined entrie(s). Use \`queue:unquarantine <JobName>\` to lift.`);process.exit(0)}catch(error){log.error("Failed to handle queue:quarantine",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
2
+ ${rows.length} quarantined entrie(s). Use \`queue:unquarantine <JobName>\` to lift.`);process.exit(0)}catch(error){await log.error("Failed to handle queue:quarantine",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{resumeQueue}from"@stacksjs/queue";const options=parseArgs(),queue=options.queue;if(!queue){log.error("Usage: buddy queue:resume --queue=<name>");process.exit(1)}try{await resumeQueue(queue);log.success(`Resumed queue '${queue}'`);process.exit(0)}catch(error){log.error("Failed to resume queue",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{resumeQueue}from"@stacksjs/queue";const options=parseArgs(),queue=options.queue;if(!queue){await log.error("Usage: buddy queue:resume --queue=<name>");process.exit(1)}try{await resumeQueue(queue);log.success(`Resumed queue '${queue}'`);process.exit(0)}catch(error){await log.error("Failed to resume queue",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{executeFailedJobs,retryFailedJob}from"@stacksjs/queue";const options=parseArgs(),jobId=options.id,all=options.all==="true";try{if(all){log.info("Retrying all failed jobs...");await executeFailedJobs();log.success("All failed jobs have been queued for retry")}else if(jobId){const id=Number.parseInt(jobId,10);if(Number.isNaN(id)){log.error("Invalid job ID provided");process.exit(1)}log.info(`Retrying failed job ${id}...`);await retryFailedJob(id);log.success(`Failed job ${id} has been queued for retry`)}else{log.warn("Please provide a job ID or use --all to retry all failed jobs");log.info("Usage: buddy queue:retry <id> or buddy queue:retry --all");process.exit(1)}process.exit(0)}catch(error){log.error("Failed to retry job(s)",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{executeFailedJobs,retryFailedJob}from"@stacksjs/queue";const options=parseArgs(),jobId=options.id,all=options.all==="true";try{if(all){log.info("Retrying all failed jobs...");await executeFailedJobs();log.success("All failed jobs have been queued for retry")}else if(jobId){const id=Number.parseInt(jobId,10);if(Number.isNaN(id)){await log.error("Invalid job ID provided");process.exit(1)}log.info(`Retrying failed job ${id}...`);await retryFailedJob(id);log.success(`Failed job ${id} has been queued for retry`)}else{log.warn("Please provide a job ID or use --all to retry all failed jobs");log.info("Usage: buddy queue:retry <id> or buddy queue:retry --all");process.exit(1)}process.exit(0)}catch(error){await log.error("Failed to retry job(s)",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value;else if(key)args[key]="true"}});return args}
@@ -1,4 +1,4 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{discoverJobs,getScheduledJobs}from"@stacksjs/queue";try{log.info(`Discovering scheduled jobs...
2
2
  `);await discoverJobs();const scheduledJobs=getScheduledJobs();if(scheduledJobs.length===0){log.info("No scheduled jobs found.");log.info('Tip: Add a "rate" or "schedule" property to your job configuration.');process.exit(0)}console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 Job Name \u2502 Schedule \u2502 Queue \u2502 Tries \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");for(const job of scheduledJobs){const name=job.name.substring(0,48).padEnd(48),schedule=(job.config.rate||job.config.schedule||"N/A").substring(0,21).padEnd(21),queue=(job.config.queue||"default").substring(0,9).padEnd(9),tries=String(job.config.tries||3).padStart(7);console.log(`\u2502 ${name} \u2502 ${schedule} \u2502 ${queue} \u2502${tries} \u2502`)}console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");console.log(`
3
3
  Total: ${scheduledJobs.length} scheduled job(s)`);console.log(`
4
- Schedule Formats:`);console.log(' \u2022 Cron: "* * * * *" (minute hour day month dayOfWeek)');console.log(" \u2022 Presets: @hourly, @daily, @weekly, @monthly");console.log(" \u2022 Every: Every.Minute, Every.Hour, Every.Day");process.exit(0)}catch(error){log.error("Failed to list scheduled jobs:",error);process.exit(1)}
4
+ Schedule Formats:`);console.log(' \u2022 Cron: "* * * * *" (minute hour day month dayOfWeek)');console.log(" \u2022 Presets: @hourly, @daily, @weekly, @monthly");console.log(" \u2022 Every: Every.Minute, Every.Hour, Every.Day");process.exit(0)}catch(error){await log.error("Failed to list scheduled jobs:",error);process.exit(1)}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{startScheduler}from"@stacksjs/queue";log.info("Starting job scheduler...");try{await startScheduler();log.success("Scheduler is running. Press Ctrl+C to stop.");await new Promise(()=>{})}catch(error){log.error("Failed to start scheduler:",error);process.exit(1)}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{startScheduler}from"@stacksjs/queue";log.info("Starting job scheduler...");try{await startScheduler();log.success("Scheduler is running. Press Ctrl+C to stop.");await new Promise(()=>{})}catch(error){await log.error("Failed to start scheduler:",error);process.exit(1)}
@@ -1,4 +1,4 @@
1
1
  import process from"node:process";import{log}from"@stacksjs/logging";import{Job,FailedJob}from"@stacksjs/orm";const options=parseArgs(),queueName=options.queue;try{log.info(`Fetching queue status...
2
2
  `);const allJobs=queueName?await Job.where("queue",queueName).get():await Job.all(),failedJobs=queueName?await FailedJob.where("queue",queueName).get():await FailedJob.all(),queueStats=new Map;for(const job of allJobs){const queue=job.queue||"default";if(!queueStats.has(queue))queueStats.set(queue,{pending:0,processing:0,delayed:0});const stats=queueStats.get(queue),now=Math.floor(Date.now()/1000);if(job.reserved_at)stats.processing++;else if(job.available_at&&Number(job.available_at)>now)stats.delayed++;else stats.pending++}const failedStats=new Map;for(const job of failedJobs){const queue=job.queue||"default";failedStats.set(queue,(failedStats.get(queue)||0)+1)}console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");console.log("\u2502 Queue \u2502 Pending \u2502 Processing \u2502 Delayed \u2502 Failed \u2502");console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");const allQueues=new Set([...queueStats.keys(),...failedStats.keys()]);if(allQueues.size===0)console.log("\u2502 No queues found \u2502");else for(const queue of allQueues){const stats=queueStats.get(queue)||{pending:0,processing:0,delayed:0},failed=failedStats.get(queue)||0,queueDisplay=queue.substring(0,48).padEnd(48),pendingDisplay=String(stats.pending).padStart(8),processingDisplay=String(stats.processing).padStart(10),delayedDisplay=String(stats.delayed).padStart(8),failedDisplay=String(failed).padStart(6);console.log(`\u2502 ${queueDisplay} \u2502${pendingDisplay} \u2502${processingDisplay} \u2502${delayedDisplay} \u2502${failedDisplay} \u2502`)}console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");const totalPending=Array.from(queueStats.values()).reduce((sum,s)=>sum+s.pending,0),totalProcessing=Array.from(queueStats.values()).reduce((sum,s)=>sum+s.processing,0),totalDelayed=Array.from(queueStats.values()).reduce((sum,s)=>sum+s.delayed,0),totalFailed=Array.from(failedStats.values()).reduce((sum,n)=>sum+n,0);console.log(`
3
3
  Summary:`);console.log(` Pending: ${totalPending}`);console.log(` Processing: ${totalProcessing}`);console.log(` Delayed: ${totalDelayed}`);console.log(` Failed: ${totalFailed}`);console.log(` Total: ${totalPending+totalProcessing+totalDelayed+totalFailed}`);process.exit(0)}catch(error){const message=error instanceof Error?error.message:String(error);if(/no such table:\s*(?:jobs|failed_jobs)\b/i.test(message)){process.stdout.write(`Queue tables are not set up yet.
4
- `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}log.error("Failed to get queue status",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
4
+ `);process.stdout.write("Run `buddy queue:table` to create the migrations, then `buddy migrate` to apply them.\n");process.exit(0)}await log.error("Failed to get queue status",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{createJobsMigration}from"@stacksjs/database";import{log}from"@stacksjs/logging";const result=await createJobsMigration();if(result?.isErr){console.error(result.error);log.error("generateMigrations failed",result.error);process.exit(1)}process.exit(0);
1
+ import process from"node:process";import{createJobsMigration}from"@stacksjs/database";import{log}from"@stacksjs/logging";const result=await createJobsMigration();if(result?.isErr){console.error(result.error);await log.error("generateMigrations failed",result.error);process.exit(1)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{unquarantineJob}from"@stacksjs/queue";const options=parseArgs(),jobName=options.name;if(!jobName){log.error("Usage: buddy queue:unquarantine --name=<JobName>");process.exit(1)}try{await unquarantineJob(jobName);log.success(`Lifted quarantine for ${jobName}`);process.exit(0)}catch(error){log.error("Failed to unquarantine",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{unquarantineJob}from"@stacksjs/queue";const options=parseArgs(),jobName=options.name;if(!jobName){await log.error("Usage: buddy queue:unquarantine --name=<JobName>");process.exit(1)}try{await unquarantineJob(jobName);log.success(`Lifted quarantine for ${jobName}`);process.exit(0)}catch(error){await log.error("Failed to unquarantine",error);process.exit(1)}function parseArgs(){const args={};process.argv.forEach((arg)=>{if(arg.startsWith("--")){const[key,value]=arg.slice(2).split("=");if(key&&value)args[key]=value}});return args}
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{startProcessor,stopProcessor}from"@stacksjs/queue";process.on("unhandledRejection",(reason)=>{log.error(`Unhandled Rejection in queue worker: ${reason}`)});process.on("uncaughtException",(error)=>{log.error(`Uncaught Exception in queue worker: ${error.message}`)});const SHUTDOWN_GRACE_MS=Number(process.env.STACKS_QUEUE_SHUTDOWN_GRACE_MS)||1e4;let shuttingDown=!1;async function gracefulShutdown(signal){if(shuttingDown)return;shuttingDown=!0;log.info(`[queue] Worker received ${signal}, draining in-flight jobs (max ${SHUTDOWN_GRACE_MS/1000}s)\u2026`);setTimeout(()=>{log.warn("[queue] Drain window exceeded - forcing worker exit.");process.exit(1)},SHUTDOWN_GRACE_MS+2000).unref();try{await stopProcessor({graceMs:SHUTDOWN_GRACE_MS})}catch(error){log.error("[queue] Error while draining worker on shutdown:",error)}process.exit(0)}process.on("SIGTERM",()=>{gracefulShutdown("SIGTERM")});process.on("SIGINT",()=>{gracefulShutdown("SIGINT")});const options=parseArgs(),queue=options.queue,concurrency=Number(options.concurrency)||1;log.info(`Starting queue worker with ${concurrency} concurrent worker(s)...`);const result=await startProcessor(queue,{concurrency});if(result.isErr){log.error("Failed to start queue worker:",result.error);process.exit(1)}log.success("Queue worker started successfully!");await new Promise(()=>{});function parseArgs(){const args={},argv=process.argv;for(let index=0;index<argv.length;index++){const arg=argv[index];if(!arg?.startsWith("--"))continue;const[key,inline]=arg.slice(2).split("=");if(!key)continue;if(inline!==void 0){args[key]=inline;continue}const next=argv[index+1];if(next!==void 0&&!next.startsWith("-")){args[key]=next;index++;continue}args[key]="true"}return args}
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{startProcessor,stopProcessor}from"@stacksjs/queue";process.on("unhandledRejection",(reason)=>{log.error(`Unhandled Rejection in queue worker: ${reason}`)});process.on("uncaughtException",(error)=>{log.error(`Uncaught Exception in queue worker: ${error.message}`)});const SHUTDOWN_GRACE_MS=Number(process.env.STACKS_QUEUE_SHUTDOWN_GRACE_MS)||1e4;let shuttingDown=!1;async function gracefulShutdown(signal){if(shuttingDown)return;shuttingDown=!0;log.info(`[queue] Worker received ${signal}, draining in-flight jobs (max ${SHUTDOWN_GRACE_MS/1000}s)\u2026`);setTimeout(()=>{log.warn("[queue] Drain window exceeded - forcing worker exit.");process.exit(1)},SHUTDOWN_GRACE_MS+2000).unref();try{await stopProcessor({graceMs:SHUTDOWN_GRACE_MS})}catch(error){await log.error("[queue] Error while draining worker on shutdown:",error)}process.exit(0)}process.on("SIGTERM",()=>{gracefulShutdown("SIGTERM")});process.on("SIGINT",()=>{gracefulShutdown("SIGINT")});const options=parseArgs(),queue=options.queue,concurrency=Number(options.concurrency)||1;log.info(`Starting queue worker with ${concurrency} concurrent worker(s)...`);const result=await startProcessor(queue,{concurrency});if(result.isErr){await log.error("Failed to start queue worker:",result.error);process.exit(1)}log.success("Queue worker started successfully!");await new Promise(()=>{});function parseArgs(){const args={},argv=process.argv;for(let index=0;index<argv.length;index++){const arg=argv[index];if(!arg?.startsWith("--"))continue;const[key,inline]=arg.slice(2).split("=");if(!key)continue;if(inline!==void 0){args[key]=inline;continue}const next=argv[index+1];if(next!==void 0&&!next.startsWith("-")){args[key]=next;index++;continue}args[key]="true"}return args}
@@ -1,2 +1,2 @@
1
1
  import process from"node:process";import{italic,log}from"@stacksjs/cli";import{route}from"@stacksjs/router";const METHOD_ORDER=["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"];function formatHandler(handler){if(typeof handler==="string")return handler;if(typeof handler==="function")return handler.name?`[fn ${handler.name}]`:"[fn]";return"[handler]"}function pad(value,width){if(value.length>=width)return value;return value+" ".repeat(width-value.length)}function write(line){process.stdout.write(`${line}
2
- `)}async function listRoutes(){try{await route.importRoutes()}catch(error){return{isErr:!0,error}}const registered=route.routes??[];if(registered.length===0){write(italic("No routes registered."));return{}}const rows=registered.map((r)=>({method:String(r.method||"GET").toUpperCase(),uri:String(r.path||r.uri||""),handler:formatHandler(r.handler??r.action??""),name:String(r.name||""),middleware:Array.isArray(r.middleware)?r.middleware.join(","):String(r.middleware||"")}));rows.sort((a,b)=>{const ai=METHOD_ORDER.indexOf(a.method),bi=METHOD_ORDER.indexOf(b.method);if(ai!==bi)return(ai===-1?99:ai)-(bi===-1?99:bi);return a.uri.localeCompare(b.uri)});const widths={method:Math.max(6,...rows.map((r)=>r.method.length)),uri:Math.max(3,...rows.map((r)=>r.uri.length)),handler:Math.max(7,...rows.map((r)=>r.handler.length)),name:Math.max(4,...rows.map((r)=>r.name.length))},header=[pad("METHOD",widths.method),pad("URI",widths.uri),pad("HANDLER",widths.handler),pad("NAME",widths.name),"MIDDLEWARE"].join(" ");write("");write(header);write("-".repeat(header.length));for(const r of rows)write([pad(r.method,widths.method),pad(r.uri,widths.uri),pad(r.handler,widths.handler),pad(r.name||"-",widths.name),r.middleware||"-"].join(" "));write("");write(italic(`${rows.length} route${rows.length===1?"":"s"} registered.`));return{}}const result=await listRoutes();if(result?.isErr){console.error(result.error);log.error("Route lists failed",result.error);process.exit(1)}process.exit(0);
2
+ `)}async function listRoutes(){try{await route.importRoutes()}catch(error){return{isErr:!0,error}}const registered=route.routes??[];if(registered.length===0){write(italic("No routes registered."));return{}}const rows=registered.map((r)=>({method:String(r.method||"GET").toUpperCase(),uri:String(r.path||r.uri||""),handler:formatHandler(r.handler??r.action??""),name:String(r.name||""),middleware:Array.isArray(r.middleware)?r.middleware.join(","):String(r.middleware||"")}));rows.sort((a,b)=>{const ai=METHOD_ORDER.indexOf(a.method),bi=METHOD_ORDER.indexOf(b.method);if(ai!==bi)return(ai===-1?99:ai)-(bi===-1?99:bi);return a.uri.localeCompare(b.uri)});const widths={method:Math.max(6,...rows.map((r)=>r.method.length)),uri:Math.max(3,...rows.map((r)=>r.uri.length)),handler:Math.max(7,...rows.map((r)=>r.handler.length)),name:Math.max(4,...rows.map((r)=>r.name.length))},header=[pad("METHOD",widths.method),pad("URI",widths.uri),pad("HANDLER",widths.handler),pad("NAME",widths.name),"MIDDLEWARE"].join(" ");write("");write(header);write("-".repeat(header.length));for(const r of rows)write([pad(r.method,widths.method),pad(r.uri,widths.uri),pad(r.handler,widths.handler),pad(r.name||"-",widths.name),r.middleware||"-"].join(" "));write("");write(italic(`${rows.length} route${rows.length===1?"":"s"} registered.`));return{}}const result=await listRoutes();if(result?.isErr){console.error(result.error);await log.error("Route lists failed",result.error);process.exit(1)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{createStripeProduct,formatSetupReport}from"@stacksjs/payments";const argv=process.argv.slice(2),dryRun=argv.includes("--dry-run")||argv.includes("--dryRun"),result=await createStripeProduct({dryRun});if(result?.isErr){console.error(result.error);log.error("stripe:setup failed",result.error);process.exit(1)}const report=result.value;log.info(dryRun?"Dry run. Nothing was written to Stripe. This is what would be applied:":"Applied:");for(const line of formatSetupReport(report))log.info(line);
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{createStripeProduct,formatSetupReport}from"@stacksjs/payments";const argv=process.argv.slice(2),dryRun=argv.includes("--dry-run")||argv.includes("--dryRun"),result=await createStripeProduct({dryRun});if(result?.isErr){console.error(result.error);await log.error("stripe:setup failed",result.error);process.exit(1)}const report=result.value;log.info(dryRun?"Dry run. Nothing was written to Stripe. This is what would be applied:":"Applied:");for(const line of formatSetupReport(report))log.info(line);
@@ -1 +1 @@
1
- import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{flushModelDocuments}from"@stacksjs/search-engine";const options=parseOptions(),modelOption=options.model,result=await flushModelDocuments(modelOption);if(result?.isErr){console.error(result.error);log.error("Flushing search engine failed",result.error)}process.exit(0);
1
+ import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{flushModelDocuments}from"@stacksjs/search-engine";const options=parseOptions(),modelOption=options.model,result=await flushModelDocuments(modelOption);if(result?.isErr){console.error(result.error);await log.error("Flushing search engine failed",result.error)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{importModelDocuments}from"@stacksjs/search-engine";const options=parseOptions(),modelOption=options.model,result=await importModelDocuments(modelOption);if(result?.isErr){console.error(result.error);log.error("Import to search engine failed",result.error)}process.exit(0);
1
+ import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{importModelDocuments}from"@stacksjs/search-engine";const options=parseOptions(),modelOption=options.model,result=await importModelDocuments(modelOption);if(result?.isErr){console.error(result.error);await log.error("Import to search engine failed",result.error)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{listIndexSettings}from"@stacksjs/search-engine";const options=parseOptions(),modelOption=options.model,result=await listIndexSettings(modelOption);if(result?.isErr){console.error(result.error);log.error("Listing index settings failed",result.error)}process.exit(0);
1
+ import process from"node:process";import{parseOptions}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{listIndexSettings}from"@stacksjs/search-engine";const options=parseOptions(),modelOption=options.model,result=await listIndexSettings(modelOption);if(result?.isErr){console.error(result.error);await log.error("Listing index settings failed",result.error)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/logging";import{updateIndexSettings}from"@stacksjs/search-engine";const result=await updateIndexSettings();if(result?.isErr){console.error(result.error);log.error("generateMigrations failed",result.error)}process.exit(0);
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{updateIndexSettings}from"@stacksjs/search-engine";const result=await updateIndexSettings();if(result?.isErr){console.error(result.error);await log.error("generateMigrations failed",result.error)}process.exit(0);
@@ -1 +1 @@
1
- import process from"node:process";import{existsSync,realpathSync}from"node:fs";import{join}from"node:path";import{log}from"@stacksjs/cli";import{projectPath}from"@stacksjs/path";const testFilePattern="**/*.{test,spec}.{js,jsx,ts,tsx,mjs,mts,cjs,cts}";export function resolveTestSuiteFilters(project,suites){const visitedDirectories=new Set;return suites.flatMap((suite)=>{const suiteDirectory=join(project,"tests",suite);if(!existsSync(suiteDirectory))return[];const canonicalDirectory=realpathSync(suiteDirectory);if(visitedDirectories.has(canonicalDirectory))return[];visitedDirectories.add(canonicalDirectory);return[...new Bun.Glob(testFilePattern).scanSync({cwd:suiteDirectory,onlyFiles:!0})].map((file)=>suite?`./tests/${suite}/${file}`:`./tests/${file}`)})}export async function runTestSuites(suites,timeout){const project=projectPath(),filters=resolveTestSuiteFilters(project,suites);if(filters.length===0){log.info("No matching tests found.");return}const args=["bun","test"];if(timeout)args.push("--timeout",String(timeout));args.push(...filters);const exitCode=await Bun.spawn(args,{cwd:project,stdio:["inherit","inherit","inherit"]}).exited;if(exitCode!==0){log.error("Tests failed");process.exit(exitCode??1)}}
1
+ import process from"node:process";import{existsSync,realpathSync}from"node:fs";import{join}from"node:path";import{log}from"@stacksjs/cli";import{projectPath}from"@stacksjs/path";const testFilePattern="**/*.{test,spec}.{js,jsx,ts,tsx,mjs,mts,cjs,cts}";export function resolveTestSuiteFilters(project,suites){const visitedDirectories=new Set;return suites.flatMap((suite)=>{const suiteDirectory=join(project,"tests",suite);if(!existsSync(suiteDirectory))return[];const canonicalDirectory=realpathSync(suiteDirectory);if(visitedDirectories.has(canonicalDirectory))return[];visitedDirectories.add(canonicalDirectory);return[...new Bun.Glob(testFilePattern).scanSync({cwd:suiteDirectory,onlyFiles:!0})].map((file)=>suite?`./tests/${suite}/${file}`:`./tests/${file}`)})}export async function runTestSuites(suites,timeout){const project=projectPath(),filters=resolveTestSuiteFilters(project,suites);if(filters.length===0){log.info("No matching tests found.");return}const args=["bun","test"];if(timeout)args.push("--timeout",String(timeout));args.push(...filters);const exitCode=await Bun.spawn(args,{cwd:project,stdio:["inherit","inherit","inherit"]}).exited;if(exitCode!==0){await log.error("Tests failed");process.exit(exitCode??1)}}
package/dist/types.js CHANGED
@@ -1 +1 @@
1
- import process from"node:process";import{NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";export async function invoke(options){const results=await runNpmScript(NpmScript.TypesFix,options);if(results.isErr){log.error("There was an error fixing your types.",results.error);process.exit(ExitCode.FatalError)}log.success("Types are set")}export async function types(options){return await invoke(options)}
1
+ import process from"node:process";import{NpmScript}from"@stacksjs/enums";import{log}from"@stacksjs/logging";import{ExitCode}from"@stacksjs/types";import{runNpmScript}from"@stacksjs/utils";export async function invoke(options){const results=await runNpmScript(NpmScript.TypesFix,options);if(results.isErr){await log.error("There was an error fixing your types.",results.error);process.exit(ExitCode.FatalError)}log.success("Types are set")}export async function types(options){return await invoke(options)}
@@ -1 +1 @@
1
- import process from"node:process";import{runCommand}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{path}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{fs,storage}from"@stacksjs/storage";const source=path.buddyPath("dist/stacks"),destination=path.homeDir(".stacks/bin/stacks"),destinationDir=path.homeDir(".stacks/bin/");log.info("Upgrading `stacks`...");const result=await runCommand("bun compile.ts",{cwd:path.buddyPath()});if(result.isErr){log.error("There was an error compiling the binary",result.error);process.exit(ExitCode.FatalError)}if(await storage.exists(source))try{log.info("Updating Binary...");log.info(`Source: ${source}`);log.info(`Destination: ${destination}`);await fs.promises.mkdir(destinationDir,{recursive:!0});await fs.promises.copyFile(source,destination);await fs.promises.unlink(source);log.success("Binary Latest Version Is Used")}catch(err){log.error(err);process.exit(ExitCode.FatalError)}else{log.error(`Binary source not found: ${source}`);process.exit(ExitCode.FatalError)}
1
+ import process from"node:process";import{runCommand}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{path}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{fs,storage}from"@stacksjs/storage";const source=path.buddyPath("dist/stacks"),destination=path.homeDir(".stacks/bin/stacks"),destinationDir=path.homeDir(".stacks/bin/");log.info("Upgrading `stacks`...");const result=await runCommand("bun compile.ts",{cwd:path.buddyPath()});if(result.isErr){await log.error("There was an error compiling the binary",result.error);process.exit(ExitCode.FatalError)}if(await storage.exists(source))try{log.info("Updating Binary...");log.info(`Source: ${source}`);log.info(`Destination: ${destination}`);await fs.promises.mkdir(destinationDir,{recursive:!0});await fs.promises.copyFile(source,destination);await fs.promises.unlink(source);log.success("Binary Latest Version Is Used")}catch(err){await log.error(err);process.exit(ExitCode.FatalError)}else{await log.error(`Binary source not found: ${source}`);process.exit(ExitCode.FatalError)}
@@ -1,4 +1,4 @@
1
- import{$}from"bun";import process from"node:process";import{italic,runCommand}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{path as p}from"@stacksjs/path";import{writeFile}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";const homePath=(await $`echo $HOME`.text()).trim(),zshrcPath=p.join(homePath,".zshrc"),pluginPath=p.shellPath("src/buddy.plugin.zsh"),customPath=p.join(homePath,".oh-my-zsh/custom/plugins/buddy/");log.info(`Setting up Oh My Zsh via ${zshrcPath}...`);$.cwd(p.dirname(zshrcPath));let data=await $`cat ${p.basename(zshrcPath)}`.text();const pluginLineRegex=/^(?!#).*plugins=\(([^)]+)\)/m,match=data.match(pluginLineRegex);if(match){const plugins=match[1]?.split(/\s+/).filter(Boolean);if(!plugins){log.error("Maybe the plugins line in your .zshrc file could not be found? If this continues being an issue, please reach out to us on Discord.");process.exit(ExitCode.FatalError)}if(!plugins.includes("buddy")){plugins.push("buddy");const newPluginLine=`plugins=(
1
+ import{$}from"bun";import process from"node:process";import{italic,runCommand}from"@stacksjs/cli";import{log}from"@stacksjs/logging";import{path as p}from"@stacksjs/path";import{writeFile}from"@stacksjs/storage";import{ExitCode}from"@stacksjs/types";const homePath=(await $`echo $HOME`.text()).trim(),zshrcPath=p.join(homePath,".zshrc"),pluginPath=p.shellPath("src/buddy.plugin.zsh"),customPath=p.join(homePath,".oh-my-zsh/custom/plugins/buddy/");log.info(`Setting up Oh My Zsh via ${zshrcPath}...`);$.cwd(p.dirname(zshrcPath));let data=await $`cat ${p.basename(zshrcPath)}`.text();const pluginLineRegex=/^(?!#).*plugins=\(([^)]+)\)/m,match=data.match(pluginLineRegex);if(match){const plugins=match[1]?.split(/\s+/).filter(Boolean);if(!plugins){await log.error("Maybe the plugins line in your .zshrc file could not be found? If this continues being an issue, please reach out to us on Discord.");process.exit(ExitCode.FatalError)}if(!plugins.includes("buddy")){plugins.push("buddy");const newPluginLine=`plugins=(
2
2
  ${plugins.map((plugin)=>plugin.trim()).join(`
3
3
  `)}
4
4
  )`;data=data.replace(pluginLineRegex,newPluginLine);await writeFile(zshrcPath,data);log.info(`Copying buddy zsh plugin ${pluginPath} to ${customPath}...`);await runCommand(`mkdir -p ${customPath}`);await runCommand(`cp -rf ${pluginPath} ${customPath}`);log.success("Copied buddy zsh plugin")}else{log.info("Buddy is already set up");log.info("Ensuring `buddy` is updated...");await runCommand(`cp -rf ${pluginPath} ${customPath}`);log.success("Updated buddy zsh plugin to latest version")}}log.success("Oh My Zsh Setup Complete");log.info(italic("To see changes reflect, you may need to open a new terminal window"));if(process.env.TERM_PROGRAM==="vscode")log.info("\u2318\u21E7P terminal.create.new.terminal");
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/actions",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.6",
5
+ "version": "0.74.8",
6
6
  "description": "The Stacks actions.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -60,51 +60,51 @@
60
60
  "prepublishOnly": "bun run build"
61
61
  },
62
62
  "dependencies": {
63
- "@stacksjs/action-runner": "0.74.6",
64
- "@stacksjs/ai": "0.74.6",
65
- "@stacksjs/analytics": "0.74.6",
66
- "@stacksjs/api": "0.74.6",
67
- "@stacksjs/auth": "0.74.6",
68
- "@stacksjs/build": "0.74.6",
63
+ "@stacksjs/action-runner": "0.74.8",
64
+ "@stacksjs/ai": "0.74.8",
65
+ "@stacksjs/analytics": "0.74.8",
66
+ "@stacksjs/api": "0.74.8",
67
+ "@stacksjs/auth": "0.74.8",
68
+ "@stacksjs/build": "0.74.8",
69
69
  "@stacksjs/bumpx": "^0.2.6",
70
70
  "@stacksjs/bunpress": "^0.2.8",
71
- "@stacksjs/cli": "0.74.6",
72
- "@stacksjs/cloud": "0.74.6",
73
- "@stacksjs/cms": "0.74.6",
74
- "@stacksjs/config": "0.74.6",
75
- "@stacksjs/database": "0.74.6",
76
- "@stacksjs/desktop-build": "0.74.6",
77
- "@stacksjs/dns": "0.74.6",
78
- "@stacksjs/enums": "0.74.6",
79
- "@stacksjs/env": "0.74.6",
80
- "@stacksjs/error-handling": "0.74.6",
71
+ "@stacksjs/cli": "0.74.8",
72
+ "@stacksjs/cloud": "0.74.8",
73
+ "@stacksjs/cms": "0.74.8",
74
+ "@stacksjs/config": "0.74.8",
75
+ "@stacksjs/database": "0.74.8",
76
+ "@stacksjs/desktop-build": "0.74.8",
77
+ "@stacksjs/dns": "0.74.8",
78
+ "@stacksjs/enums": "0.74.8",
79
+ "@stacksjs/env": "0.74.8",
80
+ "@stacksjs/error-handling": "0.74.8",
81
81
  "@stacksjs/gitit": "^0.2.5",
82
- "@stacksjs/i18n": "0.74.6",
83
- "@stacksjs/image": "0.74.6",
84
- "@stacksjs/logging": "0.74.6",
82
+ "@stacksjs/i18n": "0.74.8",
83
+ "@stacksjs/image": "0.74.8",
84
+ "@stacksjs/logging": "0.74.8",
85
85
  "@stacksjs/logsmith": "^0.2.3",
86
- "@stacksjs/orm": "0.74.6",
87
- "@stacksjs/path": "0.74.6",
88
- "@stacksjs/payments": "0.74.6",
89
- "@stacksjs/queue": "0.74.6",
90
- "@stacksjs/registry": "0.74.6",
91
- "@stacksjs/router": "0.74.6",
86
+ "@stacksjs/orm": "0.74.8",
87
+ "@stacksjs/path": "0.74.8",
88
+ "@stacksjs/payments": "0.74.8",
89
+ "@stacksjs/queue": "0.74.8",
90
+ "@stacksjs/registry": "0.74.8",
91
+ "@stacksjs/router": "0.74.8",
92
92
  "@stacksjs/rpx": "^0.11.42",
93
- "@stacksjs/scheduler": "0.74.6",
94
- "@stacksjs/search-engine": "0.74.6",
95
- "@stacksjs/security": "0.74.6",
96
- "@stacksjs/server": "0.74.6",
97
- "@stacksjs/sites": "0.74.6",
98
- "@stacksjs/storage": "0.74.6",
99
- "@stacksjs/strings": "0.74.6",
93
+ "@stacksjs/scheduler": "0.74.8",
94
+ "@stacksjs/search-engine": "0.74.8",
95
+ "@stacksjs/security": "0.74.8",
96
+ "@stacksjs/server": "0.74.8",
97
+ "@stacksjs/sites": "0.74.8",
98
+ "@stacksjs/storage": "0.74.8",
99
+ "@stacksjs/strings": "0.74.8",
100
100
  "@stacksjs/stx": "^0.2.253",
101
101
  "@stacksjs/tlsx": "^0.13.19",
102
102
  "@stacksjs/ts-cloud": "^0.12.10",
103
103
  "@stacksjs/ts-md": "^0.1.1",
104
- "@stacksjs/tunnel": "0.74.6",
105
- "@stacksjs/types": "0.74.6",
106
- "@stacksjs/utils": "0.74.6",
107
- "@stacksjs/whois": "0.74.6",
104
+ "@stacksjs/tunnel": "0.74.8",
105
+ "@stacksjs/types": "0.74.8",
106
+ "@stacksjs/utils": "0.74.8",
107
+ "@stacksjs/whois": "0.74.8",
108
108
  "bun-plugin-stx": "^0.2.246"
109
109
  },
110
110
  "peerDependencies": {
@@ -120,7 +120,7 @@
120
120
  }
121
121
  },
122
122
  "devDependencies": {
123
- "@stacksjs/validation": "0.74.6",
123
+ "@stacksjs/validation": "0.74.8",
124
124
  "better-dx": "^0.2.24"
125
125
  }
126
126
  }