@stacksjs/buddy 0.72.50 → 0.72.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/migrate.js +1 -1
- package/package.json +51 -46
package/dist/commands/doctor.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{lstatSync,readlinkSync}from"node:fs";import{resolve}from"node:path";import process from"node:process";import{bold,dim,green,intro,log,onUnknownSubcommand,red,yellow}from"@stacksjs/cli";import{feature}from"@stacksjs/config";import{inspectDefaultsProvenance}from"@stacksjs/path";import{storage}from"@stacksjs/storage";import{isSupportedBunVersion,isVersionGreaterThanOrEqual,minimumBunVersion}from"@stacksjs/utils";import{FEATURE_NAMES,featurePathsPresent}from"./features";const minimumSqliteVersion="3.47.2";class ProbeWarning extends Error{}async function probe(checks,name,fn,timeoutMs=2000){const start=Date.now(),ac=new AbortController,timer=setTimeout(()=>ac.abort(),timeoutMs);try{const message=await Promise.race([fn(),new Promise((_,rej)=>ac.signal.addEventListener("abort",()=>rej(Error(`timed out (>${Math.round(timeoutMs/1000)}s)`))))]);checks.push({name,status:"pass",message:`${message} (${Date.now()-start}ms)`})}catch(err){checks.push({name,status:err instanceof ProbeWarning?"warn":"fail",message:err instanceof Error?err.message:String(err)})}finally{clearTimeout(timer)}}export function doctor(buddy){buddy.command("doctor","Run health checks on your Stacks installation").option("--no-fail","Print results but always exit 0 (CI ramp-up)").action(async(options)=>{log.debug("Running `buddy doctor` ...");await intro("buddy doctor");const checks=[],modulesPath=resolve(process.cwd(),"node_modules");try{if(lstatSync(modulesPath).isSymbolicLink()){const target=resolve(process.cwd(),readlinkSync(modulesPath)),insideProject=target.startsWith(`${resolve(process.cwd())}/`);checks.push({name:"Dependency tree",status:insideProject?"warn":"fail",message:insideProject?`node_modules is a symlink to ${target}`:`node_modules is a symlink to ${target}, which belongs to another project - installs here mutate it, and what runs locally is not what the lockfile resolves. Remove the link and run \`bun install\`.`})}else checks.push({name:"Dependency tree",status:"pass",message:"node_modules is this project's own"})}catch{checks.push({name:"Dependency tree",status:"warn",message:"node_modules is missing - run `bun install`"})}await probe(checks,"Framework defaults",async()=>{const{measureDefaultsDrift,summarizeStructureChanges}=await import("@stacksjs/actions"),root=resolve(process.cwd()),skew=inspectDefaultsProvenance(root);if(skew.status==="not-applicable")return"Not a package-managed project (nothing to compare)";const drift=measureDefaultsDrift(root)??[],synced=skew.syncedAt?`, synced ${skew.syncedAt.slice(0,10)}`:"";if(drift.length===0)return`Vendored tree matches @stacksjs/defaults ${skew.installed}${synced}`;const counts=summarizeStructureChanges(drift);if(skew.status==="stale")throw new ProbeWarning(`storage/framework/defaults is from ${skew.vendored} while @stacksjs/defaults ${skew.installed} is installed (${counts}). Boot resolves the package, so the tree on disk is not what runs. Run \`buddy upgrade\` to sync it.`);throw new ProbeWarning(`storage/framework/defaults differs from the installed @stacksjs/defaults ${skew.installed} (${counts}), and carries no record of what it was synced from. The app runs the vendored copy. Run \`buddy upgrade\` to sync it.`)},8000);const bunVersion=process.versions.bun;if(bunVersion&&isSupportedBunVersion(bunVersion))checks.push({name:"Bun Runtime",status:"pass",message:`v${bunVersion}`});else if(bunVersion)checks.push({name:"Bun Runtime",status:"fail",message:`v${bunVersion} (requires v${minimumBunVersion} or later, run: bun upgrade)`});else checks.push({name:"Bun Runtime",status:"fail",message:"Not found"});const nodeVersion=process.versions.node;if(nodeVersion)if(Number.parseInt(nodeVersion.split(".")[0]||"0",10)>=18)checks.push({name:"Node.js",status:"pass",message:`v${nodeVersion}`});else checks.push({name:"Node.js",status:"warn",message:`v${nodeVersion} (v18+ recommended)`});try{const pkg=await storage.readPackageJson("./package.json");if(pkg.name)checks.push({name:"package.json",status:"pass",message:`Found: ${pkg.name}`})}catch{checks.push({name:"package.json",status:"fail",message:"Not found in current directory"})}try{await storage.readTextFile(".env");checks.push({name:".env file",status:"pass",message:"Found"})}catch{checks.push({name:".env file",status:"warn",message:"Not found (optional)"})}const appKey=process.env.APP_KEY;if(appKey&&appKey.length>0)checks.push({name:"APP_KEY",status:appKey.length>=32?"pass":"warn",message:appKey.length>=32?"Set (\u226532 chars)":`Set but short (${appKey.length} chars; \u226532 recommended)`});else checks.push({name:"APP_KEY",status:"fail",message:"Not set - features that depend on it (encrypted columns, signed URLs, env decryption) will refuse to run. Run `buddy key:generate`."});await probe(checks,"SQLite Engine",async()=>{const{Database}=await import("bun:sqlite"),memory=new Database(":memory:");let version;try{const row=memory.query("SELECT sqlite_version() AS version").get();if(typeof row?.version==="string")version=row.version}finally{memory.close()}if(!version)throw new ProbeWarning("could not read sqlite_version() (skipped)");if(!isVersionGreaterThanOrEqual(version,minimumSqliteVersion))throw Error(`v${version} is below the required v${minimumSqliteVersion} (system.sqlite in package.json); upgrade Bun for a newer bundled SQLite`);return`v${version}`});await probe(checks,"Database",async()=>{const{db}=await import("@stacksjs/database"),unsafe=db.unsafe;if(typeof unsafe!=="function")throw new ProbeWarning("driver exposes no raw query method (skipped)");const statement=unsafe("SELECT 1");if(!statement||typeof statement.then!=="function"&&typeof statement.execute!=="function")throw new ProbeWarning("driver returned a non-executable statement (skipped)");const result=typeof statement.execute==="function"?await statement.execute():await statement;if(result===void 0||result===null)throw Error("probe query returned no result");if(Array.isArray(result)&&result.length===0)throw Error("probe query returned no rows");return"Reachable"});await probe(checks,"Database FKs",async()=>{const{auditForeignKeys}=await import("@stacksjs/database"),result=await auditForeignKeys();if(result.declared.length===0)return"No belongsTo declarations";if(result.missing.length===0)return`${result.declared.length} declared FKs all present`;const sample=result.missing.slice(0,5).map((fk)=>`${fk.fromTable}.${fk.fromColumn} \u2192 ${fk.toTable}.${fk.toColumn}`).join(", "),more=result.missing.length>5?` (+${result.missing.length-5} more)`:"";throw Error(`${result.missing.length}/${result.declared.length} declared FKs missing from live schema: ${sample}${more}. Run \`buddy migrate:fresh\` (will reset data) or \`buddy migrate\` against a clean DB.`)});await probe(checks,"Unique indexes",async()=>{const{auditUniqueIndexes}=await import("@stacksjs/database"),result=await auditUniqueIndexes();if(!result.supported)return"Dialect not audited (skipped)";const skipped=result.skippedTables.length>0?`, ${result.skippedTables.length} tables skipped (not migrated)`:"";if(result.missing.length===0)return`${result.declared.length} declared unique constraints all indexed${skipped}`;const sample=result.missing.slice(0,5).map((u)=>`${u.table}.${u.columns.join("+")}`).join(", "),more=result.missing.length>5?` (+${result.missing.length-5} more)`:"",first=result.missing[0];if(!first)throw Error("Unique-index audit reported missing entries without details");const example=`CREATE UNIQUE INDEX IF NOT EXISTS "${first.table}_${first.columns.join("_")}_unique" ON "${first.table}" ("${first.columns.join('", "')}")`;throw Error(`${result.missing.length}/${result.declared.length} declared unique constraints have no UNIQUE index: ${sample}${more}. Run \`buddy migrate\` (re-queues missing unique-index migrations, #1952) - dedupe duplicate rows first or migrate hard-fails; if no migration file exists run \`buddy generate:migrations\`, or create manually: ${example}`)},1e4);await probe(checks,"Migration ledger",async()=>{const{auditMigrationLedger}=await import("@stacksjs/database"),result=await auditMigrationLedger();if(!result.supported)return"Dialect not audited (skipped)";if(result.entries.length===0)return"No migration files";const{counts,orphans}=result;if(!result.drift)return`${result.entries.length} migrations, ledger consistent with the schema`;const parts=[];if(counts.stranded>0)parts.push(`${counts.stranded} applied but unrecorded (would re-run)`);if(counts.partial>0)parts.push(`${counts.partial} half-applied`);if(counts.reverted>0)parts.push(`${counts.reverted} recorded but missing from the schema`);if(orphans.length>0)parts.push(`${orphans.length} ledger row(s) with no file on disk`);throw Error(`Migration ledger has drifted: ${parts.join(", ")}. Inspect with \`buddy migrate:status\`, repair with \`buddy migrate:status --reconcile\`.`)},1e4);await probe(checks,"FK orphans",async()=>{const{findFkOrphans}=await import("@stacksjs/database"),result=await findFkOrphans();if(!result.supported)return"Dialect not audited (skipped)";if(result.total===0)return"No orphan rows (PRAGMA foreign_key_check clean)";const sample=result.orphans.slice(0,5).map((o)=>`${o.table}.${o.column} \u2192 ${o.parent} (${o.count})`).join(", "),more=result.orphans.length>5?` (+${result.orphans.length-5} more)`:"",first=result.orphans[0];if(!first)throw Error("Foreign-key audit reported orphan rows without details");throw Error(`${result.total} orphan rows violate FKs: ${sample}${more}. Legacy rows written under foreign_keys=OFF (#1951). Review and clean manually, e.g. DELETE FROM ${first.table} WHERE ${first.column} IS NOT NULL AND ${first.column} NOT IN (SELECT id FROM ${first.parent}) - doctor never deletes data.`)},1e4);await probe(checks,"Cache",async()=>{const{cache}=await import("@stacksjs/cache"),k=`__doctor_${Date.now()}`;await cache.set(k,1,5);const v=await cache.get(k);await cache.del(k);if(v!==1)throw Error("round-trip failed");return"Round-trip ok"});await probe(checks,"Queue driver",async()=>{const{config}=await import("@stacksjs/config");return`Driver: ${config.queue?.default??"sync"}`});await probe(checks,"Mail driver",async()=>{const{config}=await import("@stacksjs/config");return`Driver: ${config.email?.default??"log"}`});{const{ensureRuntimeDirectories,projectPath}=await import("@stacksjs/path"),leftover=ensureRuntimeDirectories().filter((entry)=>!entry.cleared);checks.push(leftover.length===0?{name:"Runtime directories",status:"pass",message:"All under storage/"}:{name:"Runtime directories",status:"warn",message:`Still in the project root: ${leftover.map((entry)=>entry.legacy.slice(projectPath().length+1)).join(", ")} - remove them so state stays under storage/`})}const hasAwsCreds=Boolean(process.env.AWS_ACCESS_KEY_ID&&process.env.AWS_SECRET_ACCESS_KEY),hasAwsRole=Boolean(process.env.AWS_PROFILE)||Boolean(process.env.AWS_ROLE_ARN);if(hasAwsCreds||hasAwsRole)checks.push({name:"AWS credentials",status:"pass",message:hasAwsCreds?"Static keys in env":"IAM role configured"});else checks.push({name:"AWS credentials",status:"warn",message:"Not configured (cloud / SES / S3 commands will fail)"});await probe(checks,"Database backups",async()=>{const fs=await import("node:fs"),cloudConfig=resolve(process.cwd(),"config/cloud.ts");if(!fs.existsSync(cloudConfig))return"No config/cloud.ts (skipped)";const{findUnbackedManagedServices,unbackedDataMessage}=await import("../unbacked-data");let tsCloud;try{tsCloud=(await import(cloudConfig)).tsCloud}catch{throw new ProbeWarning("could not read config/cloud.ts (skipped)")}const unbacked=findUnbackedManagedServices(tsCloud);if(unbacked.length===0)return"No unbacked managed data services";throw new ProbeWarning(unbackedDataMessage(unbacked))});await probe(checks,".env decryption",async()=>{const fs=await import("node:fs");if(!fs.existsSync(".env"))return"No .env (skipped)";const content=fs.readFileSync(".env","utf8");if(!/(?:^|=)(?:enc|encrypted):/m.test(content))return"No encrypted values";const privateKey=process.env.DOTENV_PRIVATE_KEY;if(!privateKey)throw Error("Encrypted values present but DOTENV_PRIVATE_KEY is unset");const{parse}=await import("@stacksjs/env"),{errors}=parse(content,{privateKey});if(errors.length>0)throw Error(errors.join("; "));return"All encrypted values decrypt cleanly"});try{const{filesystems}=await import("@stacksjs/config"),driver=filesystems.driver??"local";if(driver==="s3"){const bucket=process.env.S3_BUCKET??filesystems.s3?.bucket,accessKeyId=process.env.AWS_ACCESS_KEY_ID,secretAccessKey=process.env.AWS_SECRET_ACCESS_KEY,missing=[];if(!bucket)missing.push("S3_BUCKET");if(!accessKeyId)missing.push("AWS_ACCESS_KEY_ID");if(!secretAccessKey)missing.push("AWS_SECRET_ACCESS_KEY");if(missing.length>0)checks.push({name:"Storage credentials",status:"warn",message:`Default disk is 's3' but missing: ${missing.join(", ")}. Uploads will throw on first use.`});else checks.push({name:"Storage credentials",status:"pass",message:"s3 default disk has bucket + AWS credentials"})}else checks.push({name:"Storage credentials",status:"pass",message:`Default disk is '${driver}' (no remote credentials required)`})}catch(err){checks.push({name:"Storage credentials",status:"warn",message:`Could not audit storage config: ${err instanceof Error?err.message:String(err)}`})}try{if(process.platform!=="darwin")checks.push({name:"Dev domains (rpx)",status:"pass",message:"Not macOS (skipped)"});else{const fs=await import("node:fs"),os=await import("node:os"),path=await import("node:path"),isAlive=(pid)=>{try{process.kill(pid,0);return!0}catch(err){return err.code==="EPERM"}},registryDir=path.join(os.homedir(),".stacks","rpx","registry.d"),registered=new Set,deadRegistryFiles=[];if(fs.existsSync(registryDir))for(const file of fs.readdirSync(registryDir)){if(!file.endsWith(".json"))continue;try{const entry=JSON.parse(fs.readFileSync(path.join(registryDir,file),"utf8"));if(entry.to&&(entry.pid===void 0||isAlive(entry.pid)))registered.add(entry.to.toLowerCase());if(typeof entry.pid==="number"&&!isAlive(entry.pid))deadRegistryFiles.push(file)}catch{}}const staleHosts=new Set,staleResolvers=[],hostsPath="/etc/hosts",hostsLines=fs.existsSync(hostsPath)?fs.readFileSync(hostsPath,"utf8").split(`
|
|
1
|
+
import{lstatSync,readlinkSync}from"node:fs";import{resolve}from"node:path";import process from"node:process";import{bold,dim,green,intro,log,onUnknownSubcommand,red,yellow}from"@stacksjs/cli";import{feature}from"@stacksjs/config";import{inspectDefaultsProvenance}from"@stacksjs/path";import{storage}from"@stacksjs/storage";import{isSupportedBunVersion,isVersionGreaterThanOrEqual,minimumBunVersion}from"@stacksjs/utils";import{FEATURE_NAMES,featurePathsPresent}from"./features";const minimumSqliteVersion="3.47.2";class ProbeWarning extends Error{}async function probe(checks,name,fn,timeoutMs=2000){const start=Date.now(),ac=new AbortController,timer=setTimeout(()=>ac.abort(),timeoutMs);try{const message=await Promise.race([fn(),new Promise((_,rej)=>ac.signal.addEventListener("abort",()=>rej(Error(`timed out (>${Math.round(timeoutMs/1000)}s)`))))]);checks.push({name,status:"pass",message:`${message} (${Date.now()-start}ms)`})}catch(err){checks.push({name,status:err instanceof ProbeWarning?"warn":"fail",message:err instanceof Error?err.message:String(err)})}finally{clearTimeout(timer)}}export function doctor(buddy){buddy.command("doctor","Run health checks on your Stacks installation").option("--no-fail","Print results but always exit 0 (CI ramp-up)").action(async(options)=>{log.debug("Running `buddy doctor` ...");await intro("buddy doctor");const checks=[],modulesPath=resolve(process.cwd(),"node_modules");try{if(lstatSync(modulesPath).isSymbolicLink()){const target=resolve(process.cwd(),readlinkSync(modulesPath)),insideProject=target.startsWith(`${resolve(process.cwd())}/`);checks.push({name:"Dependency tree",status:insideProject?"warn":"fail",message:insideProject?`node_modules is a symlink to ${target}`:`node_modules is a symlink to ${target}, which belongs to another project - installs here mutate it, and what runs locally is not what the lockfile resolves. Remove the link and run \`bun install\`.`})}else checks.push({name:"Dependency tree",status:"pass",message:"node_modules is this project's own"})}catch{checks.push({name:"Dependency tree",status:"warn",message:"node_modules is missing - run `bun install`"})}await probe(checks,"Framework defaults",async()=>{const{measureDefaultsDrift,summarizeStructureChanges}=await import("@stacksjs/actions"),root=resolve(process.cwd()),skew=inspectDefaultsProvenance(root);if(skew.status==="not-applicable")return"Not a package-managed project (nothing to compare)";const drift=measureDefaultsDrift(root)??[],synced=skew.syncedAt?`, synced ${skew.syncedAt.slice(0,10)}`:"";if(drift.length===0)return`Vendored tree matches @stacksjs/defaults ${skew.installed}${synced}`;const counts=summarizeStructureChanges(drift);if(skew.status==="stale")throw new ProbeWarning(`storage/framework/defaults is from ${skew.vendored} while @stacksjs/defaults ${skew.installed} is installed (${counts}). Boot resolves the package, so the tree on disk is not what runs. Run \`buddy upgrade\` to sync it.`);throw new ProbeWarning(`storage/framework/defaults differs from the installed @stacksjs/defaults ${skew.installed} (${counts}), and carries no record of what it was synced from. The app runs the vendored copy. Run \`buddy upgrade\` to sync it.`)},8000);const bunVersion=process.versions.bun;if(bunVersion&&isSupportedBunVersion(bunVersion))checks.push({name:"Bun Runtime",status:"pass",message:`v${bunVersion}`});else if(bunVersion)checks.push({name:"Bun Runtime",status:"fail",message:`v${bunVersion} (requires v${minimumBunVersion} or later, run: bun upgrade)`});else checks.push({name:"Bun Runtime",status:"fail",message:"Not found"});const nodeVersion=process.versions.node;if(nodeVersion)if(Number.parseInt(nodeVersion.split(".")[0]||"0",10)>=18)checks.push({name:"Node.js",status:"pass",message:`v${nodeVersion}`});else checks.push({name:"Node.js",status:"warn",message:`v${nodeVersion} (v18+ recommended)`});try{const pkg=await storage.readPackageJson("./package.json");if(pkg.name)checks.push({name:"package.json",status:"pass",message:`Found: ${pkg.name}`})}catch{checks.push({name:"package.json",status:"fail",message:"Not found in current directory"})}try{await storage.readTextFile(".env");checks.push({name:".env file",status:"pass",message:"Found"})}catch{checks.push({name:".env file",status:"warn",message:"Not found (optional)"})}const appKey=process.env.APP_KEY;if(appKey&&appKey.length>0)checks.push({name:"APP_KEY",status:appKey.length>=32?"pass":"warn",message:appKey.length>=32?"Set (\u226532 chars)":`Set but short (${appKey.length} chars; \u226532 recommended)`});else checks.push({name:"APP_KEY",status:"fail",message:"Not set - features that depend on it (encrypted columns, signed URLs, env decryption) will refuse to run. Run `buddy key:generate`."});await probe(checks,"SQLite Engine",async()=>{const{Database}=await import("bun:sqlite"),memory=new Database(":memory:");let version;try{const row=memory.query("SELECT sqlite_version() AS version").get();if(typeof row?.version==="string")version=row.version}finally{memory.close()}if(!version)throw new ProbeWarning("could not read sqlite_version() (skipped)");if(!isVersionGreaterThanOrEqual(version,minimumSqliteVersion))throw Error(`v${version} is below the required v${minimumSqliteVersion} (system.sqlite in package.json); upgrade Bun for a newer bundled SQLite`);return`v${version}`});await probe(checks,"Database",async()=>{const{db}=await import("@stacksjs/database"),unsafe=db.unsafe;if(typeof unsafe!=="function")throw new ProbeWarning("driver exposes no raw query method (skipped)");const statement=unsafe("SELECT 1");if(!statement||typeof statement.then!=="function"&&typeof statement.execute!=="function")throw new ProbeWarning("driver returned a non-executable statement (skipped)");const result=typeof statement.execute==="function"?await statement.execute():await statement;if(result===void 0||result===null)throw Error("probe query returned no result");if(Array.isArray(result)&&result.length===0)throw Error("probe query returned no rows");return"Reachable"});await probe(checks,"Database FKs",async()=>{const{auditForeignKeys}=await import("@stacksjs/database"),result=await auditForeignKeys();if(result.declared.length===0)return"No belongsTo declarations";const skipped=result.absentTable.length>0?`, ${result.absentTable.length} on tables not migrated`:"";if(result.missing.length===0)return`${result.declared.length} declared FKs all present${skipped}`;const sample=result.missing.slice(0,5).map((fk)=>`${fk.fromTable}.${fk.fromColumn} \u2192 ${fk.toTable}.${fk.toColumn}`).join(", "),more=result.missing.length>5?` (+${result.missing.length-5} more)`:"";throw Error(`${result.missing.length}/${result.declared.length} declared FKs missing from live schema: ${sample}${more}${skipped}. Run \`buddy migrate:fresh\` (will reset data) or \`buddy migrate\` against a clean DB.`)});await probe(checks,"Unique indexes",async()=>{const{auditUniqueIndexes}=await import("@stacksjs/database"),result=await auditUniqueIndexes();if(!result.supported)return"Dialect not audited (skipped)";const skipped=result.skippedTables.length>0?`, ${result.skippedTables.length} tables skipped (not migrated)`:"";if(result.missing.length===0)return`${result.declared.length} declared unique constraints all indexed${skipped}`;const sample=result.missing.slice(0,5).map((u)=>`${u.table}.${u.columns.join("+")}`).join(", "),more=result.missing.length>5?` (+${result.missing.length-5} more)`:"",first=result.missing[0];if(!first)throw Error("Unique-index audit reported missing entries without details");const example=`CREATE UNIQUE INDEX IF NOT EXISTS "${first.table}_${first.columns.join("_")}_unique" ON "${first.table}" ("${first.columns.join('", "')}")`;throw Error(`${result.missing.length}/${result.declared.length} declared unique constraints have no UNIQUE index: ${sample}${more}. Run \`buddy migrate\` (re-queues missing unique-index migrations, #1952) - dedupe duplicate rows first or migrate hard-fails; if no migration file exists run \`buddy generate:migrations\`, or create manually: ${example}`)},1e4);await probe(checks,"Migration ledger",async()=>{const{auditMigrationLedger}=await import("@stacksjs/database"),result=await auditMigrationLedger();if(!result.supported)return"Dialect not audited (skipped)";if(result.entries.length===0)return"No migration files";const{counts,orphans}=result;if(!result.drift)return`${result.entries.length} migrations, ledger consistent with the schema`;const parts=[];if(counts.stranded>0)parts.push(`${counts.stranded} applied but unrecorded (would re-run)`);if(counts.partial>0)parts.push(`${counts.partial} half-applied`);if(counts.reverted>0)parts.push(`${counts.reverted} recorded but missing from the schema`);if(orphans.length>0)parts.push(`${orphans.length} ledger row(s) with no file on disk`);throw Error(`Migration ledger has drifted: ${parts.join(", ")}. Inspect with \`buddy migrate:status\`, repair with \`buddy migrate:status --reconcile\`.`)},1e4);await probe(checks,"FK orphans",async()=>{const{findFkOrphans}=await import("@stacksjs/database"),result=await findFkOrphans();if(!result.supported)return"Dialect not audited (skipped)";if(result.total===0)return"No orphan rows (PRAGMA foreign_key_check clean)";const sample=result.orphans.slice(0,5).map((o)=>`${o.table}.${o.column} \u2192 ${o.parent} (${o.count})`).join(", "),more=result.orphans.length>5?` (+${result.orphans.length-5} more)`:"",first=result.orphans[0];if(!first)throw Error("Foreign-key audit reported orphan rows without details");throw Error(`${result.total} orphan rows violate FKs: ${sample}${more}. Legacy rows written under foreign_keys=OFF (#1951). Review and clean manually, e.g. DELETE FROM ${first.table} WHERE ${first.column} IS NOT NULL AND ${first.column} NOT IN (SELECT id FROM ${first.parent}) - doctor never deletes data.`)},1e4);await probe(checks,"Cache",async()=>{const{cache}=await import("@stacksjs/cache"),k=`__doctor_${Date.now()}`;await cache.set(k,1,5);const v=await cache.get(k);await cache.del(k);if(v!==1)throw Error("round-trip failed");return"Round-trip ok"});await probe(checks,"Queue driver",async()=>{const{config}=await import("@stacksjs/config");return`Driver: ${config.queue?.default??"sync"}`});await probe(checks,"Mail driver",async()=>{const{config}=await import("@stacksjs/config");return`Driver: ${config.email?.default??"log"}`});{const{ensureRuntimeDirectories,projectPath}=await import("@stacksjs/path"),leftover=ensureRuntimeDirectories().filter((entry)=>!entry.cleared);checks.push(leftover.length===0?{name:"Runtime directories",status:"pass",message:"All under storage/"}:{name:"Runtime directories",status:"warn",message:`Still in the project root: ${leftover.map((entry)=>entry.legacy.slice(projectPath().length+1)).join(", ")} - remove them so state stays under storage/`})}const hasAwsCreds=Boolean(process.env.AWS_ACCESS_KEY_ID&&process.env.AWS_SECRET_ACCESS_KEY),hasAwsRole=Boolean(process.env.AWS_PROFILE)||Boolean(process.env.AWS_ROLE_ARN);if(hasAwsCreds||hasAwsRole)checks.push({name:"AWS credentials",status:"pass",message:hasAwsCreds?"Static keys in env":"IAM role configured"});else checks.push({name:"AWS credentials",status:"warn",message:"Not configured (cloud / SES / S3 commands will fail)"});await probe(checks,"Database backups",async()=>{const fs=await import("node:fs"),cloudConfig=resolve(process.cwd(),"config/cloud.ts");if(!fs.existsSync(cloudConfig))return"No config/cloud.ts (skipped)";const{findUnbackedManagedServices,unbackedDataMessage}=await import("../unbacked-data");let tsCloud;try{tsCloud=(await import(cloudConfig)).tsCloud}catch{throw new ProbeWarning("could not read config/cloud.ts (skipped)")}const unbacked=findUnbackedManagedServices(tsCloud);if(unbacked.length===0)return"No unbacked managed data services";throw new ProbeWarning(unbackedDataMessage(unbacked))});await probe(checks,".env decryption",async()=>{const fs=await import("node:fs");if(!fs.existsSync(".env"))return"No .env (skipped)";const content=fs.readFileSync(".env","utf8");if(!/(?:^|=)(?:enc|encrypted):/m.test(content))return"No encrypted values";const privateKey=process.env.DOTENV_PRIVATE_KEY;if(!privateKey)throw Error("Encrypted values present but DOTENV_PRIVATE_KEY is unset");const{parse}=await import("@stacksjs/env"),{errors}=parse(content,{privateKey});if(errors.length>0)throw Error(errors.join("; "));return"All encrypted values decrypt cleanly"});try{const{filesystems}=await import("@stacksjs/config"),driver=filesystems.driver??"local";if(driver==="s3"){const bucket=process.env.S3_BUCKET??filesystems.s3?.bucket,accessKeyId=process.env.AWS_ACCESS_KEY_ID,secretAccessKey=process.env.AWS_SECRET_ACCESS_KEY,missing=[];if(!bucket)missing.push("S3_BUCKET");if(!accessKeyId)missing.push("AWS_ACCESS_KEY_ID");if(!secretAccessKey)missing.push("AWS_SECRET_ACCESS_KEY");if(missing.length>0)checks.push({name:"Storage credentials",status:"warn",message:`Default disk is 's3' but missing: ${missing.join(", ")}. Uploads will throw on first use.`});else checks.push({name:"Storage credentials",status:"pass",message:"s3 default disk has bucket + AWS credentials"})}else checks.push({name:"Storage credentials",status:"pass",message:`Default disk is '${driver}' (no remote credentials required)`})}catch(err){checks.push({name:"Storage credentials",status:"warn",message:`Could not audit storage config: ${err instanceof Error?err.message:String(err)}`})}try{if(process.platform!=="darwin")checks.push({name:"Dev domains (rpx)",status:"pass",message:"Not macOS (skipped)"});else{const fs=await import("node:fs"),os=await import("node:os"),path=await import("node:path"),isAlive=(pid)=>{try{process.kill(pid,0);return!0}catch(err){return err.code==="EPERM"}},registryDir=path.join(os.homedir(),".stacks","rpx","registry.d"),registered=new Set,deadRegistryFiles=[];if(fs.existsSync(registryDir))for(const file of fs.readdirSync(registryDir)){if(!file.endsWith(".json"))continue;try{const entry=JSON.parse(fs.readFileSync(path.join(registryDir,file),"utf8"));if(entry.to&&(entry.pid===void 0||isAlive(entry.pid)))registered.add(entry.to.toLowerCase());if(typeof entry.pid==="number"&&!isAlive(entry.pid))deadRegistryFiles.push(file)}catch{}}const staleHosts=new Set,staleResolvers=[],hostsPath="/etc/hosts",hostsLines=fs.existsSync(hostsPath)?fs.readFileSync(hostsPath,"utf8").split(`
|
|
2
2
|
`):[];for(let i=0;i<hostsLines.length;i++){const line=hostsLines[i];if(line.trim()==="# Added by rpx"){for(let j=i+1;j<hostsLines.length;j++){const blockLine=hostsLines[j].trim();if(blockLine===""||blockLine.startsWith("#"))break;const names=blockLine.split("#")[0]?.trim().split(/\s+/).slice(1)??[];for(const name of names)if(!registered.has(name.toLowerCase()))staleHosts.add(name)}continue}const hash=line.indexOf("#");if(hash===-1)continue;const marker=/^rpx(?::pid=(\d+))?$/.exec(line.slice(hash+1).trim());if(!marker)continue;const names=line.slice(0,hash).trim().split(/\s+/).slice(1),pid=marker[1]?Number.parseInt(marker[1],10):null;if(pid!==null?!isAlive(pid):names.every((n)=>!registered.has(n.toLowerCase())))for(const name of names)staleHosts.add(name)}const resolverDir="/etc/resolver";if(fs.existsSync(resolverDir))for(const file of fs.readdirSync(resolverDir))try{const content=fs.readFileSync(path.join(resolverDir,file),"utf8");if(!content.includes("127.0.0.1")||!content.includes("15353"))continue;const domain=file.toLowerCase();if(![...registered].some((host)=>host===domain||host.endsWith(`.${domain}`)))staleResolvers.push(file)}catch{}if(staleHosts.size>0||staleResolvers.length>0||deadRegistryFiles.length>0){const parts=[];if(staleHosts.size>0)parts.push(`hosts(${[...staleHosts].join(", ")})`);if(staleResolvers.length>0)parts.push(`resolver(${staleResolvers.join(", ")})`);if(deadRegistryFiles.length>0)parts.push(`registry(${deadRegistryFiles.join(", ")})`);checks.push({name:"Dev domains (rpx)",status:"warn",message:`Stale loopback overrides from dead dev sessions: ${parts.join(" ")}. These keep pointing the domain at 127.0.0.1. Remove with: sudo nano /etc/hosts; sudo rm /etc/resolver/<name>; rm ~/.stacks/rpx/registry.d/<file>. Updating @stacksjs/rpx lets the daemon sweep pid-stamped entries automatically.`})}else checks.push({name:"Dev domains (rpx)",status:"pass",message:"No stale dev-domain overrides"})}}catch(err){checks.push({name:"Dev domains (rpx)",status:"warn",message:`Could not audit dev-domain overrides: ${err instanceof Error?err.message:String(err)}`})}await probe(checks,"Dev ports",async()=>{const net=await import("node:net"),{config}=await import("@stacksjs/config"),configured=config.ports??{},targets=[{name:"frontend",key:"frontend",envVar:"PORT",fallback:3000},{name:"api",key:"api",envVar:"PORT_API",fallback:3008},{name:"docs",key:"docs",envVar:"PORT_DOCS",fallback:3006},{name:"dashboard",key:"admin",envVar:"PORT_ADMIN",fallback:3002}].map((t)=>({...t,port:Number(configured[t.key])||t.fallback})),canConnect=(port,host)=>new Promise((resolve)=>{const socket=net.createConnection({port,host});socket.setTimeout(400);const done=(occupied)=>{socket.destroy();resolve(occupied)};socket.once("connect",()=>done(!0));socket.once("timeout",()=>done(!1));socket.once("error",()=>done(!1))}),occupied=new Set;await Promise.all([...new Set(targets.map((t)=>t.port))].map(async(port)=>{if(await canConnect(port,"127.0.0.1")||await canConnect(port,"::1"))occupied.add(port)}));const busy=targets.filter((t)=>occupied.has(t.port));if(busy.length>0){const list=busy.map((t)=>`${t.name} :${t.port} (${t.envVar})`).join(", ");throw new ProbeWarning(`in use: ${list}. buddy dev will fail to bind; stop the process holding the port or set the override env var`)}return`All free: ${targets.map((t)=>`${t.name} :${t.port}`).join(", ")}`});try{const orphans=[];for(const name of FEATURE_NAMES){if(feature(name))continue;const present=featurePathsPresent(name);if(present.length>0)orphans.push({feature:name,count:present.length})}if(orphans.length>0){const summary=orphans.map((o)=>`${o.feature} (${o.count} path${o.count===1?"":"s"})`).join(", ");checks.push({name:"Feature scaffolding",status:"warn",message:`Stamped files remain for disabled features: ${summary}. Run \`./buddy <feature>:uninstall\` to remove or \`<feature>:install\` to re-enable.`})}else checks.push({name:"Feature scaffolding",status:"pass",message:"No orphan files for disabled features"})}catch(err){checks.push({name:"Feature scaffolding",status:"warn",message:`Could not audit feature scaffolding: ${err instanceof Error?err.message:String(err)}`})}log.info("");log.info(bold("Health Check Results:"));log.info(dim("\u2500".repeat(60)));log.info("");let hasFailures=!1,hasWarnings=!1;for(const check of checks){let statusIcon="",statusColor=(text)=>text;if(check.status==="pass"){statusIcon="\u2713";statusColor=green}else if(check.status==="warn"){statusIcon="\u26A0";statusColor=yellow;hasWarnings=!0}else{statusIcon="\u2717";statusColor=red;hasFailures=!0}log.info(`${statusColor(statusIcon)} ${bold(check.name.padEnd(20))} ${dim(check.message)}`)}log.info("");log.info(dim("\u2500".repeat(60)));log.info("");if(hasFailures){log.error("Some critical checks failed. Please address the issues above.");if(options?.fail!==!1){await log.flush();process.exit(1)}}else if(hasWarnings)log.info(yellow("Some checks have warnings. Your system should work but may have issues."));else log.success(green("All checks passed! Your Stacks installation looks healthy."));log.info("")});onUnknownSubcommand(buddy,"doctor")}
|
package/dist/commands/migrate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{closeSync,existsSync,mkdirSync,openSync,readdirSync,readFileSync,rmSync}from"node:fs";import{relative}from"node:path";import process from"node:process";import{confirm,intro,log,onUnknownSubcommand,outro,text}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{hasTTY,isCI}from"@stacksjs/env";import{appPath,frameworkPath,frameworkRuntimePath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{preflightDatabase}from"../database-preflight";import{DDL_CONSTRAINT_OVERRIDE_ENV,DIALECT_OVERRIDE_ENV,auditDdlConstraints,auditMigrationCorpus,dialectCapabilities,formatDdlConstraintError,formatMigrationDialectError,relativeMigrationDirectory,resolveMigrationDirectory,stripSqlNoise}from"@stacksjs/database";import{resultFailed}from"../result";let _runAction;async function runAction(...args){if(!_runAction)_runAction=(await import("@stacksjs/actions")).runAction;return _runAction(...args)}function acquireMigrationLock(){const lockDir=frameworkRuntimePath(),lockFile=`${lockDir}/migrations.lock`;try{if(!existsSync(lockDir))mkdirSync(lockDir,{recursive:!0});const fd=openSync(lockFile,"wx");try{closeSync(fd)}catch{}return{acquired:!0,release:()=>{try{rmSync(lockFile,{force:!0})}catch{}}}}catch{return{acquired:!1,release:()=>{}}}}async function ensureDatabaseOrExit(){try{const{ensureDatabaseReady}=await import("@stacksjs/database");await ensureDatabaseReady()}catch(error){log.syncError(error instanceof Error?error.message:String(error));process.exit(ExitCode.FatalError)}}function readMigrateMarker(){const file=frameworkRuntimePath("last-migrate-result.json");if(!existsSync(file))return null;try{const raw=readFileSync(file,"utf8"),parsed=JSON.parse(raw),n=typeof parsed.appliedCount==="number"?parsed.appliedCount:null;if(n===null||!Number.isFinite(n))return null;return{appliedCount:Math.max(0,Math.floor(n))}}catch{return null}finally{try{rmSync(file,{force:!0})}catch{}}}function countModelFiles(dir){if(!existsSync(dir))return 0;let count=0;const entries=readdirSync(dir,{withFileTypes:!0});for(const entry of entries)if(entry.isDirectory())count+=countModelFiles(`${dir}/${entry.name}`);else if(entry.name.endsWith(".ts")&&!entry.name.startsWith(".")&&!entry.name.startsWith("index"))count++;return count}function validateModelsExist(){const userModelsPath=appPath("Models"),defaultModelsPath=frameworkPath("defaults/app/Models"),userModelCount=countModelFiles(userModelsPath),defaultModelCount=countModelFiles(defaultModelsPath);if(userModelCount===0&&defaultModelCount===0)return{valid:!1,error:"No models found. Please create models in app/Models or ensure framework defaults exist."};return{valid:!0}}export function validateMigrationDialect(cwd=process.cwd(),options={}){const driver=String(options.driver||process.env.DB_CONNECTION||"sqlite").toLowerCase(),dir=resolveMigrationDirectory(driver,{cwd}),relativeDir=relativeMigrationDirectory(dir,cwd);if(process.env[DIALECT_OVERRIDE_ENV]!=="1"){const caps=dialectCapabilities(driver),target=caps.wire==="mysql"?"mysql":caps.wire==="postgres"?"postgres":"sqlite";if(driver==="sqlite"||driver==="postgres"||caps.wire==="mysql"){const audit=auditMigrationCorpus({dir,target});if(!audit.empty&&audit.incompatible.length>0)return{valid:!1,error:formatMigrationDialectError(audit,target,relativeDir)}}}if(process.env[DDL_CONSTRAINT_OVERRIDE_ENV]!=="1"){const constraints=auditDdlConstraints({dir,dialect:driver});if(!constraints.empty&&constraints.violations.length>0)return{valid:!1,error:formatDdlConstraintError(constraints,driver,relativeDir)}}return{valid:!0}}async function reportSchemaDrift(){try{const{auditSchemaDrift,formatSchemaDrift}=await import("@stacksjs/database"),drift=await auditSchemaDrift();if(drift.skipped||drift.clean)return;log.warn(await formatSchemaDrift(drift))}catch(err){log.debug(`[migrate] schema drift check skipped: ${err instanceof Error?err.message:String(err)}`)}}async function reportMissingForeignKeys(){try{const{auditForeignKeys}=await import("@stacksjs/database"),result=await auditForeignKeys();if(result.missing.length===0)return;const sample=result.missing.slice(0,5).map((fk)=>` \u2022 ${fk.fromTable}.${fk.fromColumn} \u2192 ${fk.toTable}.${fk.toColumn} (${fk.model})`).join(`
|
|
1
|
+
import{closeSync,existsSync,mkdirSync,openSync,readdirSync,readFileSync,rmSync}from"node:fs";import{relative}from"node:path";import process from"node:process";import{confirm,intro,log,onUnknownSubcommand,outro,text}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{hasTTY,isCI}from"@stacksjs/env";import{appPath,frameworkPath,frameworkRuntimePath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{preflightDatabase}from"../database-preflight";import{DDL_CONSTRAINT_OVERRIDE_ENV,DIALECT_OVERRIDE_ENV,auditDdlConstraints,auditMigrationCorpus,dialectCapabilities,formatDdlConstraintError,formatMigrationDialectError,relativeMigrationDirectory,resolveMigrationDirectory,stripSqlNoise}from"@stacksjs/database";import{resultFailed}from"../result";let _runAction;async function runAction(...args){if(!_runAction)_runAction=(await import("@stacksjs/actions")).runAction;return _runAction(...args)}function acquireMigrationLock(){const lockDir=frameworkRuntimePath(),lockFile=`${lockDir}/migrations.lock`;try{if(!existsSync(lockDir))mkdirSync(lockDir,{recursive:!0});const fd=openSync(lockFile,"wx");try{closeSync(fd)}catch{}return{acquired:!0,release:()=>{try{rmSync(lockFile,{force:!0})}catch{}}}}catch{return{acquired:!1,release:()=>{}}}}async function ensureDatabaseOrExit(){try{const{ensureDatabaseReady}=await import("@stacksjs/database");await ensureDatabaseReady()}catch(error){log.syncError(error instanceof Error?error.message:String(error));process.exit(ExitCode.FatalError)}}function readMigrateMarker(){const file=frameworkRuntimePath("last-migrate-result.json");if(!existsSync(file))return null;try{const raw=readFileSync(file,"utf8"),parsed=JSON.parse(raw),n=typeof parsed.appliedCount==="number"?parsed.appliedCount:null;if(n===null||!Number.isFinite(n))return null;return{appliedCount:Math.max(0,Math.floor(n))}}catch{return null}finally{try{rmSync(file,{force:!0})}catch{}}}function countModelFiles(dir){if(!existsSync(dir))return 0;let count=0;const entries=readdirSync(dir,{withFileTypes:!0});for(const entry of entries)if(entry.isDirectory())count+=countModelFiles(`${dir}/${entry.name}`);else if(entry.name.endsWith(".ts")&&!entry.name.startsWith(".")&&!entry.name.startsWith("index"))count++;return count}function validateModelsExist(){const userModelsPath=appPath("Models"),defaultModelsPath=frameworkPath("defaults/app/Models"),userModelCount=countModelFiles(userModelsPath),defaultModelCount=countModelFiles(defaultModelsPath);if(userModelCount===0&&defaultModelCount===0)return{valid:!1,error:"No models found. Please create models in app/Models or ensure framework defaults exist."};return{valid:!0}}export function validateMigrationDialect(cwd=process.cwd(),options={}){const driver=String(options.driver||process.env.DB_CONNECTION||"sqlite").toLowerCase(),dir=resolveMigrationDirectory(driver,{cwd}),relativeDir=relativeMigrationDirectory(dir,cwd);if(process.env[DIALECT_OVERRIDE_ENV]!=="1"){const caps=dialectCapabilities(driver),target=caps.wire==="mysql"?"mysql":caps.wire==="postgres"?"postgres":"sqlite";if(driver==="sqlite"||driver==="postgres"||caps.wire==="mysql"){const audit=auditMigrationCorpus({dir,target});if(!audit.empty&&audit.incompatible.length>0)return{valid:!1,error:formatMigrationDialectError(audit,target,relativeDir)}}}if(process.env[DDL_CONSTRAINT_OVERRIDE_ENV]!=="1"){const constraints=auditDdlConstraints({dir,dialect:driver});if(!constraints.empty&&constraints.violations.length>0)return{valid:!1,error:formatDdlConstraintError(constraints,driver,relativeDir)}}return{valid:!0}}async function reportSchemaDrift(){try{const{auditSchemaDrift,formatSchemaDrift}=await import("@stacksjs/database"),drift=await auditSchemaDrift();if(drift.skipped||drift.clean)return;log.warn(await formatSchemaDrift(drift))}catch(err){log.debug(`[migrate] schema drift check skipped: ${err instanceof Error?err.message:String(err)}`)}}async function reportMissingForeignKeys(){try{const{auditForeignKeys}=await import("@stacksjs/database"),result=await auditForeignKeys();if(result.absentTable.length>0)log.debug(`[migrate] ${result.absentTable.length} declared foreign key(s) belong to tables that are not in this database (${[...new Set(result.absentTable.map((fk)=>fk.fromTable))].join(", ")}); not reported as missing.`);if(result.missing.length===0)return;const sample=result.missing.slice(0,5).map((fk)=>` \u2022 ${fk.fromTable}.${fk.fromColumn} \u2192 ${fk.toTable}.${fk.toColumn} (${fk.model})`).join(`
|
|
2
2
|
`),more=result.missing.length>5?`
|
|
3
3
|
+ ${result.missing.length-5} more - run \`./buddy doctor\` for the full list.`:"";log.warn(`${result.missing.length} of ${result.declared.length} declared foreign keys are missing from the live schema:
|
|
4
4
|
${sample}${more}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.52",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,57 +95,62 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "^0.72.
|
|
99
|
-
"@stacksjs/ai": "^0.72.
|
|
100
|
-
"@stacksjs/alias": "^0.72.
|
|
101
|
-
"@stacksjs/
|
|
102
|
-
"@stacksjs/
|
|
103
|
-
"@stacksjs/
|
|
104
|
-
"@stacksjs/
|
|
105
|
-
"@stacksjs/
|
|
98
|
+
"@stacksjs/actions": "^0.72.52",
|
|
99
|
+
"@stacksjs/ai": "^0.72.52",
|
|
100
|
+
"@stacksjs/alias": "^0.72.52",
|
|
101
|
+
"@stacksjs/analytics": "^0.72.52",
|
|
102
|
+
"@stacksjs/arrays": "^0.72.52",
|
|
103
|
+
"@stacksjs/auth": "^0.72.52",
|
|
104
|
+
"@stacksjs/browser-extension": "^0.72.52",
|
|
105
|
+
"@stacksjs/build": "^0.72.52",
|
|
106
|
+
"@stacksjs/cache": "^0.72.52",
|
|
107
|
+
"@stacksjs/chat": "^0.72.52",
|
|
106
108
|
"@stacksjs/clapp": "^0.2.12",
|
|
107
|
-
"@stacksjs/cli": "^0.72.
|
|
108
|
-
"@stacksjs/cloud": "^0.72.
|
|
109
|
-
"@stacksjs/cms": "^0.72.
|
|
110
|
-
"@stacksjs/collections": "^0.72.
|
|
111
|
-
"@stacksjs/config": "^0.72.
|
|
112
|
-
"@stacksjs/database": "^0.72.
|
|
113
|
-
"@stacksjs/desktop-build": "^0.72.
|
|
114
|
-
"@stacksjs/dns": "^0.72.
|
|
109
|
+
"@stacksjs/cli": "^0.72.52",
|
|
110
|
+
"@stacksjs/cloud": "^0.72.52",
|
|
111
|
+
"@stacksjs/cms": "^0.72.52",
|
|
112
|
+
"@stacksjs/collections": "^0.72.52",
|
|
113
|
+
"@stacksjs/config": "^0.72.52",
|
|
114
|
+
"@stacksjs/database": "^0.72.52",
|
|
115
|
+
"@stacksjs/desktop-build": "^0.72.52",
|
|
116
|
+
"@stacksjs/dns": "^0.72.52",
|
|
115
117
|
"@stacksjs/dnsx": "^0.2.3",
|
|
116
|
-
"@stacksjs/email": "^0.72.
|
|
117
|
-
"@stacksjs/enums": "^0.72.
|
|
118
|
-
"@stacksjs/
|
|
119
|
-
"@stacksjs/
|
|
120
|
-
"@stacksjs/
|
|
118
|
+
"@stacksjs/email": "^0.72.52",
|
|
119
|
+
"@stacksjs/enums": "^0.72.52",
|
|
120
|
+
"@stacksjs/env": "^0.72.52",
|
|
121
|
+
"@stacksjs/error-handling": "^0.72.52",
|
|
122
|
+
"@stacksjs/events": "^0.72.52",
|
|
123
|
+
"@stacksjs/git": "^0.72.52",
|
|
121
124
|
"@stacksjs/gitit": "^0.2.5",
|
|
122
|
-
"@stacksjs/health": "^0.72.
|
|
125
|
+
"@stacksjs/health": "^0.72.52",
|
|
123
126
|
"@stacksjs/httx": "^0.1.10",
|
|
124
|
-
"@stacksjs/image": "^0.72.
|
|
125
|
-
"@stacksjs/lint": "^0.72.
|
|
126
|
-
"@stacksjs/logging": "^0.72.
|
|
127
|
-
"@stacksjs/notifications": "^0.72.
|
|
128
|
-
"@stacksjs/objects": "^0.72.
|
|
129
|
-
"@stacksjs/orm": "^0.72.
|
|
130
|
-
"@stacksjs/path": "^0.72.
|
|
131
|
-
"@stacksjs/payments": "^0.72.
|
|
132
|
-
"@stacksjs/realtime": "^0.72.
|
|
133
|
-
"@stacksjs/router": "^0.72.
|
|
127
|
+
"@stacksjs/image": "^0.72.52",
|
|
128
|
+
"@stacksjs/lint": "^0.72.52",
|
|
129
|
+
"@stacksjs/logging": "^0.72.52",
|
|
130
|
+
"@stacksjs/notifications": "^0.72.52",
|
|
131
|
+
"@stacksjs/objects": "^0.72.52",
|
|
132
|
+
"@stacksjs/orm": "^0.72.52",
|
|
133
|
+
"@stacksjs/path": "^0.72.52",
|
|
134
|
+
"@stacksjs/payments": "^0.72.52",
|
|
135
|
+
"@stacksjs/realtime": "^0.72.52",
|
|
136
|
+
"@stacksjs/router": "^0.72.52",
|
|
134
137
|
"@stacksjs/rpx": "^0.11.42",
|
|
135
|
-
"@stacksjs/
|
|
136
|
-
"@stacksjs/
|
|
137
|
-
"@stacksjs/
|
|
138
|
-
"@stacksjs/
|
|
139
|
-
"@stacksjs/
|
|
140
|
-
"@stacksjs/
|
|
141
|
-
"@stacksjs/
|
|
142
|
-
"@stacksjs/
|
|
138
|
+
"@stacksjs/scheduler": "^0.72.52",
|
|
139
|
+
"@stacksjs/search-engine": "^0.72.52",
|
|
140
|
+
"@stacksjs/security": "^0.72.52",
|
|
141
|
+
"@stacksjs/server": "^0.72.52",
|
|
142
|
+
"@stacksjs/sites": "^0.72.52",
|
|
143
|
+
"@stacksjs/skills": "^0.72.52",
|
|
144
|
+
"@stacksjs/storage": "^0.72.52",
|
|
145
|
+
"@stacksjs/strings": "^0.72.52",
|
|
146
|
+
"@stacksjs/testing": "^0.72.52",
|
|
147
|
+
"@stacksjs/tinker": "^0.72.52",
|
|
143
148
|
"@stacksjs/ts-cloud": "^0.11.4",
|
|
144
|
-
"@stacksjs/tunnel": "^0.72.
|
|
145
|
-
"@stacksjs/types": "^0.72.
|
|
146
|
-
"@stacksjs/ui": "^0.72.
|
|
147
|
-
"@stacksjs/utils": "^0.72.
|
|
148
|
-
"@stacksjs/validation": "^0.72.
|
|
149
|
+
"@stacksjs/tunnel": "^0.72.52",
|
|
150
|
+
"@stacksjs/types": "^0.72.52",
|
|
151
|
+
"@stacksjs/ui": "^0.72.52",
|
|
152
|
+
"@stacksjs/utils": "^0.72.52",
|
|
153
|
+
"@stacksjs/validation": "^0.72.52",
|
|
149
154
|
"ajv": "^8.20.0",
|
|
150
155
|
"ajv-formats": "^3.0.1",
|
|
151
156
|
"ts-pantry": "^0.11.35"
|