@stacksjs/config 0.75.177 → 0.75.179

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/defaults.js CHANGED
@@ -1,4 +1,4 @@
1
- var {require}=import.meta;import process from"node:process";import{commandsPath,projectPath,userDatabasePath}from"@stacksjs/path";export const FRAMEWORK_DEFAULTS={awsRegion:"us-east-1",timezone:"UTC",noReplyEmail:"no-reply@stacksjs.com",fallbackDomain:"stacks.localhost"};function deriveAppDefaults(){try{const pkgPath=projectPath("package.json"),raw=(require(pkgPath).name??"").trim();if(!raw)return{name:"Stacks",url:"stacks.localhost"};const slug=raw.replace(/^@[^/]+\//,"").toLowerCase();return{name:slug.replace(/[-_]+/g," ").replace(/(^|\s)\w/g,(c)=>c.toUpperCase()),url:`${slug}.localhost`}}catch{return{name:"Stacks",url:"stacks.localhost"}}}const appDefaults=deriveAppDefaults();export const defaults={cms:{enabled:!1},commerce:{enabled:!1},marketing:{enabled:!1},monitoring:{enabled:!1},sites:{enabled:!1},forms:{enabled:!1},sms:{enabled:!1,provider:"twilio",drivers:{}},ai:{deploy:!1,models:["amazon.nova-lite-v1:0","amazon.titan-embed-text-v2:0","global.anthropic.claude-sonnet-5-5","global.anthropic.claude-haiku-4-5-20251001-v1:0"]},auth:{username:"email",password:"password",defaultTokenName:"auth-token",cookie:{name:"auth-token"},tokenExpiry:3600000,refreshTokenExpiry:2592000000,defaultAbilities:["*"]},realtime:{driver:"pusher"},analytics:{driver:"analyticshq"},socials:{},app:{name:appDefaults.name,description:"A Stacks application.",env:"local",url:appDefaults.url,debug:!0,key:"",timezone:FRAMEWORK_DEFAULTS.timezone,locale:"en",fallbackLocale:"en",cipher:"AES-256-CBC",docMode:!1,redirectUrls:[],maintenanceMode:!1,comingSoonMode:!1,comingSoonSecret:""},cli:{name:"My Custom CLI",command:"my-custom-cli",description:"Stacks is a full-stack framework for TypeScript.",source:commandsPath(),deploy:!1},cache:{driver:"memory",prefix:"stx",ttl:3600,maxKeys:-1,useClones:!0,drivers:{redis:{host:"localhost",port:6379,username:"",password:"",database:0,tls:!1},memory:{maxKeys:-1,checkPeriod:600,deleteOnExpire:!0}}},featureFlags:{default:"memory",missing:"false",drivers:{memory:{cloneValues:!0},database:{table:"feature_flags",autoCreate:!1}}},cloud:{infrastructure:{type:"serverless",driver:"aws",environments:["production","staging","development"],firewall:{enabled:!0,countryCodes:[],ipAddresses:[],queryString:[],httpHeaders:[],rateLimitPerMinute:1000,useIpReputationLists:!0,useKnownBadInputsRuleSet:!0},cdn:{allowedMethods:"GET_HEAD",cachedMethods:"GET_HEAD",minTtl:0,defaultTtl:86400,maxTtl:31536000,compress:!0,priceClass:"PriceClass_All",originShieldRegion:FRAMEWORK_DEFAULTS.awsRegion,cookieBehavior:"none",allowList:{cookies:[],headers:[],queryStrings:[]},realtimeLogs:{enabled:!0,samplingRate:2}},fileSystem:!1,storage:{}},sites:{root:"",path:""}},dashboard:{sections:{library:{enabled:!0},content:{enabled:!0},commerce:{enabled:!0},marketing:{enabled:!0},analytics:{enabled:!0},management:{enabled:!0},utilities:{enabled:!0}}},database:{default:"sqlite",logging:!1,connections:{sqlite:{database:process.env.DB_DATABASE_PATH||userDatabasePath("stacks.sqlite"),prefix:""}},migrations:"migrations",migrationLocks:"migration_locks"},dns:{driver:"aws",a:[],aaaa:[],cname:[],mx:[],txt:[]},docs:{lang:"en-US",lastUpdated:!0,deploy:!1,themeConfig:{editLink:{pattern:"https://github.com/stacksjs/stacks/edit/main/docs/docs/:path",text:"Edit this page on GitHub"},footer:{message:"Released under the MIT License.",copyright:"Copyright \xA9 2024-present Stacks.js, Inc."}}},email:{from:{name:"Stacks",address:FRAMEWORK_DEFAULTS.noReplyEmail},mailboxes:[],server:{enabled:!0,scan:!0}},errors:{messages:{string:"The {{ field }} field must be a string",email:"The {{ field }} field must be a valid email address",regex:"The {{ field }} field format is invalid",url:"The {{ field }} field must be a valid URL",activeUrl:"The {{ field }} field must be a valid URL",alpha:"The {{ field }} field must contain only letters",alphaNumeric:"The {{ field }} field must contain only letters and numbers",minLength:"The {{ field }} field must have at least {{ min }} characters",maxLength:"The {{ field }} field must not be greater than {{ max }} characters",fixedLength:"The {{ field }} field must be {{ size }} characters long",confirmed:"The {{ field }} field and {{ otherField }} field must be the same",endsWith:"The {{ field }} field must end with {{ substring }}",startsWith:"The {{ field }} field must start with {{ substring }}",sameAs:"The {{ field }} field and {{ otherField }} field must be the same",notSameAs:"The {{ field }} field and {{ otherField }} field must be different",in:"The selected {{ field }} is invalid",notIn:"The selected {{ field }} is invalid",ipAddress:"The {{ field }} field must be a valid IP address",uuid:"The {{ field }} field must be a valid UUID",ascii:"The {{ field }} field must only contain ASCII characters",creditCard:"The {{ field }} field must be a valid {{ providersList }} card number",hexCode:"The {{ field }} field must be a valid hex color code",iban:"The {{ field }} field must be a valid IBAN number",jwt:"The {{ field }} field must be a valid JWT token",coordinates:"The {{ field }} field must contain latitude and longitude coordinates",mobile:"The {{ field }} field must be a valid mobile phone number",passport:"The {{ field }} field must be a valid passport number",postalCode:"The {{ field }} field must be a valid postal code",boolean:"The value must be a boolean",number:"The {{ field }} field must be a number",min:"The {{ field }} field must be at least {{ min }}",max:"The {{ field }} field must not be greater than {{ max }}",range:"The {{ field }} field must be between {{ min }} and {{ max }}",positive:"The {{ field }} field must be positive",negative:"The {{ field }} field must be negative",decimal:"The {{ field }} field must have {{ digits }} decimal places",withoutDecimals:"The {{ field }} field must not have decimal places",date:"The {{ field }} field must be a datetime value","date.equals":"The {{ field }} field must be a date equal to {{ expectedValue }}","date.after":"The {{ field }} field must be a date after {{ expectedValue }}","date.before":"The {{ field }} field must be a date before {{ expectedValue }}","date.afterOrEqual":"The {{ field }} field must be a date after or equal to {{ expectedValue }}","date.beforeOrEqual":"The {{ field }} field must be a date before or equal to {{ expectedValue }}","date.sameAs":"The {{ field }} field and {{ otherField }} field must be the same","date.notSameAs":"The {{ field }} field and {{ otherField }} field must be different","date.afterField":"The {{ field }} field must be a date after {{ otherField }}",accepted:"The {{ field }} field must be accepted",enum:"The selected {{ field }} is invalid",literal:"The {{ field }} field must be {{ expectedValue }}",object:"The {{ field }} field must be an object",record:"The {{ field }} field must be an object","record.min":"The {{ field }} field must have at least {{ min }} items","record.maxLength":"The {{ field }} field must not have more than {{ max }} items","record.fixedLength":"The {{ field }} field must contain {{ size }} items",array:"The {{ field }} field must be an array","array.min":"The {{ field }} field must have at least {{ min }} items","array.maxLength":"The {{ field }} field must not have more than {{ max }} items","array.fixedLength":"The {{ field }} field must contain {{ size }} items",notEmpty:"The {{ field }} field must not be empty",distinct:"The {{ field }} field has duplicate values",tuple:"The {{ field }} field must be an array",union:"Invalid value provided for {{ field }} field",unionGroup:"Invalid value provided for {{ field }} field",unionOfTypes:"Invalid value provided for {{ field }} field"}},git:{hooks:{"pre-commit":"lint-staged"},scopes:["","ci","deps","dx","release","docs","test","core","actions","arrays","auth","build","cache","cli","cloud","collections","config","database","datetime","docs","errors","git","lint","modules","notifications","objects","path","realtime","router","buddy","security","server","storage","strings","tests","types","ui","utils"],messages:{type:"Select the type of change that you're committing:",scope:"Select the SCOPE of this change (optional):",customScope:"Select the SCOPE of this change:",subject:`Write a SHORT, IMPERATIVE tense description of the change:
1
+ var {require}=import.meta;import{commandsPath,projectPath,userDatabasePath}from"@stacksjs/path";import{plainEnv}from"./plain-env";export const FRAMEWORK_DEFAULTS={awsRegion:"us-east-1",timezone:"UTC",noReplyEmail:"no-reply@stacksjs.com",fallbackDomain:"stacks.localhost"};function deriveAppDefaults(){try{const pkgPath=projectPath("package.json"),raw=(require(pkgPath).name??"").trim();if(!raw)return{name:"Stacks",url:"stacks.localhost"};const slug=raw.replace(/^@[^/]+\//,"").toLowerCase();return{name:slug.replace(/[-_]+/g," ").replace(/(^|\s)\w/g,(c)=>c.toUpperCase()),url:`${slug}.localhost`}}catch{return{name:"Stacks",url:"stacks.localhost"}}}const appDefaults=deriveAppDefaults();export const defaults={cms:{enabled:!1},commerce:{enabled:!1},marketing:{enabled:!1},monitoring:{enabled:!1},sites:{enabled:!1},forms:{enabled:!1},sms:{enabled:!1,provider:"twilio",drivers:{}},ai:{deploy:!1,models:["amazon.nova-lite-v1:0","amazon.titan-embed-text-v2:0","global.anthropic.claude-sonnet-5-5","global.anthropic.claude-haiku-4-5-20251001-v1:0"]},auth:{username:"email",password:"password",defaultTokenName:"auth-token",cookie:{name:"auth-token"},tokenExpiry:3600000,refreshTokenExpiry:2592000000,defaultAbilities:["*"]},realtime:{driver:"pusher"},analytics:{driver:"analyticshq"},socials:{},app:{name:appDefaults.name,description:"A Stacks application.",env:"local",url:appDefaults.url,debug:!0,key:"",timezone:FRAMEWORK_DEFAULTS.timezone,locale:"en",fallbackLocale:"en",cipher:"AES-256-CBC",docMode:!1,redirectUrls:[],maintenanceMode:!1,comingSoonMode:!1,comingSoonSecret:""},cli:{name:"My Custom CLI",command:"my-custom-cli",description:"Stacks is a full-stack framework for TypeScript.",source:commandsPath(),deploy:!1},cache:{driver:"memory",prefix:"stx",ttl:3600,maxKeys:-1,useClones:!0,drivers:{redis:{host:"localhost",port:6379,username:"",password:"",database:0,tls:!1},memory:{maxKeys:-1,checkPeriod:600,deleteOnExpire:!0}}},featureFlags:{default:"memory",missing:"false",drivers:{memory:{cloneValues:!0},database:{table:"feature_flags",autoCreate:!1}}},cloud:{infrastructure:{type:"serverless",driver:"aws",environments:["production","staging","development"],firewall:{enabled:!0,countryCodes:[],ipAddresses:[],queryString:[],httpHeaders:[],rateLimitPerMinute:1000,useIpReputationLists:!0,useKnownBadInputsRuleSet:!0},cdn:{allowedMethods:"GET_HEAD",cachedMethods:"GET_HEAD",minTtl:0,defaultTtl:86400,maxTtl:31536000,compress:!0,priceClass:"PriceClass_All",originShieldRegion:FRAMEWORK_DEFAULTS.awsRegion,cookieBehavior:"none",allowList:{cookies:[],headers:[],queryStrings:[]},realtimeLogs:{enabled:!0,samplingRate:2}},fileSystem:!1,storage:{}},sites:{root:"",path:""}},dashboard:{sections:{library:{enabled:!0},content:{enabled:!0},commerce:{enabled:!0},marketing:{enabled:!0},analytics:{enabled:!0},management:{enabled:!0},utilities:{enabled:!0}}},database:{default:"sqlite",logging:!1,connections:{sqlite:{database:plainEnv("DB_DATABASE_PATH")||userDatabasePath("stacks.sqlite"),prefix:""}},migrations:"migrations",migrationLocks:"migration_locks"},dns:{driver:"aws",a:[],aaaa:[],cname:[],mx:[],txt:[]},docs:{lang:"en-US",lastUpdated:!0,deploy:!1,themeConfig:{editLink:{pattern:"https://github.com/stacksjs/stacks/edit/main/docs/docs/:path",text:"Edit this page on GitHub"},footer:{message:"Released under the MIT License.",copyright:"Copyright \xA9 2024-present Stacks.js, Inc."}}},email:{from:{name:"Stacks",address:FRAMEWORK_DEFAULTS.noReplyEmail},mailboxes:[],server:{enabled:!0,scan:!0}},errors:{messages:{string:"The {{ field }} field must be a string",email:"The {{ field }} field must be a valid email address",regex:"The {{ field }} field format is invalid",url:"The {{ field }} field must be a valid URL",activeUrl:"The {{ field }} field must be a valid URL",alpha:"The {{ field }} field must contain only letters",alphaNumeric:"The {{ field }} field must contain only letters and numbers",minLength:"The {{ field }} field must have at least {{ min }} characters",maxLength:"The {{ field }} field must not be greater than {{ max }} characters",fixedLength:"The {{ field }} field must be {{ size }} characters long",confirmed:"The {{ field }} field and {{ otherField }} field must be the same",endsWith:"The {{ field }} field must end with {{ substring }}",startsWith:"The {{ field }} field must start with {{ substring }}",sameAs:"The {{ field }} field and {{ otherField }} field must be the same",notSameAs:"The {{ field }} field and {{ otherField }} field must be different",in:"The selected {{ field }} is invalid",notIn:"The selected {{ field }} is invalid",ipAddress:"The {{ field }} field must be a valid IP address",uuid:"The {{ field }} field must be a valid UUID",ascii:"The {{ field }} field must only contain ASCII characters",creditCard:"The {{ field }} field must be a valid {{ providersList }} card number",hexCode:"The {{ field }} field must be a valid hex color code",iban:"The {{ field }} field must be a valid IBAN number",jwt:"The {{ field }} field must be a valid JWT token",coordinates:"The {{ field }} field must contain latitude and longitude coordinates",mobile:"The {{ field }} field must be a valid mobile phone number",passport:"The {{ field }} field must be a valid passport number",postalCode:"The {{ field }} field must be a valid postal code",boolean:"The value must be a boolean",number:"The {{ field }} field must be a number",min:"The {{ field }} field must be at least {{ min }}",max:"The {{ field }} field must not be greater than {{ max }}",range:"The {{ field }} field must be between {{ min }} and {{ max }}",positive:"The {{ field }} field must be positive",negative:"The {{ field }} field must be negative",decimal:"The {{ field }} field must have {{ digits }} decimal places",withoutDecimals:"The {{ field }} field must not have decimal places",date:"The {{ field }} field must be a datetime value","date.equals":"The {{ field }} field must be a date equal to {{ expectedValue }}","date.after":"The {{ field }} field must be a date after {{ expectedValue }}","date.before":"The {{ field }} field must be a date before {{ expectedValue }}","date.afterOrEqual":"The {{ field }} field must be a date after or equal to {{ expectedValue }}","date.beforeOrEqual":"The {{ field }} field must be a date before or equal to {{ expectedValue }}","date.sameAs":"The {{ field }} field and {{ otherField }} field must be the same","date.notSameAs":"The {{ field }} field and {{ otherField }} field must be different","date.afterField":"The {{ field }} field must be a date after {{ otherField }}",accepted:"The {{ field }} field must be accepted",enum:"The selected {{ field }} is invalid",literal:"The {{ field }} field must be {{ expectedValue }}",object:"The {{ field }} field must be an object",record:"The {{ field }} field must be an object","record.min":"The {{ field }} field must have at least {{ min }} items","record.maxLength":"The {{ field }} field must not have more than {{ max }} items","record.fixedLength":"The {{ field }} field must contain {{ size }} items",array:"The {{ field }} field must be an array","array.min":"The {{ field }} field must have at least {{ min }} items","array.maxLength":"The {{ field }} field must not have more than {{ max }} items","array.fixedLength":"The {{ field }} field must contain {{ size }} items",notEmpty:"The {{ field }} field must not be empty",distinct:"The {{ field }} field has duplicate values",tuple:"The {{ field }} field must be an array",union:"Invalid value provided for {{ field }} field",unionGroup:"Invalid value provided for {{ field }} field",unionOfTypes:"Invalid value provided for {{ field }} field"}},git:{hooks:{"pre-commit":"lint-staged"},scopes:["","ci","deps","dx","release","docs","test","core","actions","arrays","auth","build","cache","cli","cloud","collections","config","database","datetime","docs","errors","git","lint","modules","notifications","objects","path","realtime","router","buddy","security","server","storage","strings","tests","types","ui","utils"],messages:{type:"Select the type of change that you're committing:",scope:"Select the SCOPE of this change (optional):",customScope:"Select the SCOPE of this change:",subject:`Write a SHORT, IMPERATIVE tense description of the change:
2
2
  `,body:`Provide a LONGER description of the change (optional). Use "|" to break new line:
3
3
  `,breaking:`List any BREAKING CHANGES (optional). Use "|" to break new line:
4
4
  `,footerPrefixesSelect:"Select the ISSUES type of the change list by this change (optional):",customFooterPrefixes:"Input ISSUES prefix:",footer:`List any ISSUES by this change. E.g.: #31, #34:
package/dist/overrides.js CHANGED
@@ -1,4 +1,4 @@
1
- import{existsSync}from"node:fs";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{defaults}from"./defaults";import{validateConfig}from"./validators";const skipConfigLoading=process.env.SKIP_CONFIG_LOADING==="true",skipConfigValidation=process.env.SKIP_CONFIG_VALIDATION==="true",OVERRIDES_KEY=Symbol.for("@stacksjs/config:overrides"),READY_KEY=Symbol.for("@stacksjs/config:overridesReady");export function defaultsForOverrides(){return{ai:{},analytics:{},app:{...defaults.app,name:process.env.APP_NAME||defaults.app?.name||"Stacks",env:process.env.APP_ENV||"production"},auth:{},cache:{},cli:{},cloud:{},cms:{},commerce:{},dashboard:{},database:{},dns:{},realtime:{},email:{},errors:{},featureFlags:{},git:{},hashing:{},images:{},library:{},logging:{},marketing:{},monitoring:{},notification:{},queue:{},payment:{},ports:{},saas:{},searchEngine:{},security:{},server:{},sites:{},forms:{},services:{},sms:{},socials:{},filesystems:{},team:{},ui:{}}}const globalScope=globalThis,sharedOverrides=globalScope[OVERRIDES_KEY];export const overrides=sharedOverrides??(()=>{const created=defaultsForOverrides();globalScope[OVERRIDES_KEY]=created;return created})();const userConfigs=[["ai","ai"],["analytics","analytics"],["app","app"],["auth","auth"],["cache","cache"],["cli","cli"],["cloud","cloud"],["cms","cms"],["commerce","commerce"],["cors","cors"],["dashboard","dashboard"],["database","database"],["dns","dns"],["docs","docs"],["email","email"],["errors","errors"],["featureFlags","feature-flags"],["git","git"],["hashing","hashing"],["images","images"],["library","library"],["logging","logging"],["marketing","marketing"],["monitoring","monitoring"],["notification","notification"],["payment","payment"],["ports","ports"],["queue","queue"],["realtime","realtime"],["saas","saas"],["searchEngine","search-engine"],["security","security"],["sites","sites"],["forms","forms"],["server","server"],["services","services"],["sms","sms"],["socials","socials"],["filesystems","filesystems"],["team","team"],["ui","ui","stx"]];export function resolveUserConfigName(names,cwd=process.cwd()){const present=names.filter((candidate)=>existsSync(resolve(cwd,"config",`${candidate}.ts`)));if(present.length>1)console.warn(`[config] config/${present.join(".ts and config/")}.ts both exist and configure the same thing. Using config/${present[0]}.ts; delete the other so every consumer agrees on one.`);return present[0]??names[0]}export function userConfigUrl(name,cwd=process.cwd()){return pathToFileURL(resolve(cwd,"config",`${name}.ts`)).href}const sharedReady=globalScope[READY_KEY];export const overridesReady=sharedReady??(()=>{const promise=skipConfigLoading?Promise.resolve(overrides):Promise.all(userConfigs.map(async([key,...names])=>{const name=resolveUserConfigName(names),modulePath=userConfigUrl(name);try{const mod=await import(modulePath);if(mod?.default!==void 0)overrides[key]=mod.default}catch(err){const code=err?.code,msg=err?.message??String(err);if(!(code==="ERR_MODULE_NOT_FOUND"||code==="MODULE_NOT_FOUND"||/Cannot find module/i.test(msg)))console.warn(`[config] Failed to load ${String(key)} config from ${modulePath}: ${msg}`)}})).then(()=>{if(!skipConfigValidation){const issues=validateConfig(overrides);if(issues.length>0){console.error("[config] Configuration issues detected:");for(const issue of issues)console.error(` \u2022 ${issue.path}: ${issue.message}`);const summary=issues.map((i)=>` \u2022 ${i.path}: ${i.message}`).join(`
1
+ import{existsSync}from"node:fs";import{resolve}from"node:path";import{pathToFileURL}from"node:url";import{defaults}from"./defaults";import{plainEnv}from"./plain-env";import{validateConfig}from"./validators";const skipConfigLoading=process.env.SKIP_CONFIG_LOADING==="true",skipConfigValidation=process.env.SKIP_CONFIG_VALIDATION==="true",OVERRIDES_KEY=Symbol.for("@stacksjs/config:overrides"),READY_KEY=Symbol.for("@stacksjs/config:overridesReady");export function defaultsForOverrides(){return{ai:{},analytics:{},app:{...defaults.app,name:plainEnv("APP_NAME")||defaults.app?.name||"Stacks",env:plainEnv("APP_ENV")||"production"},auth:{},cache:{},cli:{},cloud:{},cms:{},commerce:{},dashboard:{},database:{},dns:{},realtime:{},email:{},errors:{},featureFlags:{},git:{},hashing:{},images:{},library:{},logging:{},marketing:{},monitoring:{},notification:{},queue:{},payment:{},ports:{},saas:{},searchEngine:{},security:{},server:{},sites:{},forms:{},services:{},sms:{},socials:{},filesystems:{},team:{},ui:{}}}const globalScope=globalThis,sharedOverrides=globalScope[OVERRIDES_KEY];export const overrides=sharedOverrides??(()=>{const created=defaultsForOverrides();globalScope[OVERRIDES_KEY]=created;return created})();const userConfigs=[["ai","ai"],["analytics","analytics"],["app","app"],["auth","auth"],["cache","cache"],["cli","cli"],["cloud","cloud"],["cms","cms"],["commerce","commerce"],["cors","cors"],["dashboard","dashboard"],["database","database"],["dns","dns"],["docs","docs"],["email","email"],["errors","errors"],["featureFlags","feature-flags"],["git","git"],["hashing","hashing"],["images","images"],["library","library"],["logging","logging"],["marketing","marketing"],["monitoring","monitoring"],["notification","notification"],["payment","payment"],["ports","ports"],["queue","queue"],["realtime","realtime"],["saas","saas"],["searchEngine","search-engine"],["security","security"],["sites","sites"],["forms","forms"],["server","server"],["services","services"],["sms","sms"],["socials","socials"],["filesystems","filesystems"],["team","team"],["ui","ui","stx"]];export function resolveUserConfigName(names,cwd=process.cwd()){const present=names.filter((candidate)=>existsSync(resolve(cwd,"config",`${candidate}.ts`)));if(present.length>1)console.warn(`[config] config/${present.join(".ts and config/")}.ts both exist and configure the same thing. Using config/${present[0]}.ts; delete the other so every consumer agrees on one.`);return present[0]??names[0]}export function userConfigUrl(name,cwd=process.cwd()){return pathToFileURL(resolve(cwd,"config",`${name}.ts`)).href}const sharedReady=globalScope[READY_KEY];export const overridesReady=sharedReady??(()=>{const promise=skipConfigLoading?Promise.resolve(overrides):Promise.all(userConfigs.map(async([key,...names])=>{const name=resolveUserConfigName(names),modulePath=userConfigUrl(name);try{const mod=await import(modulePath);if(mod?.default!==void 0)overrides[key]=mod.default}catch(err){const code=err?.code,msg=err?.message??String(err);if(!(code==="ERR_MODULE_NOT_FOUND"||code==="MODULE_NOT_FOUND"||/Cannot find module/i.test(msg)))console.warn(`[config] Failed to load ${String(key)} config from ${modulePath}: ${msg}`)}})).then(()=>{if(!skipConfigValidation){const issues=validateConfig(overrides);if(issues.length>0){console.error("[config] Configuration issues detected:");for(const issue of issues)console.error(` \u2022 ${issue.path}: ${issue.message}`);const summary=issues.map((i)=>` \u2022 ${i.path}: ${i.message}`).join(`
2
2
  `);throw Error(`[config] ${issues.length} configuration issue(s) detected at boot:
3
3
  ${summary}
4
4
  Set SKIP_CONFIG_VALIDATION=true to bypass (e.g. when running migrations against partial config).`)}}return overrides});globalScope[READY_KEY]=promise;return promise})();export default overrides;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `process.env` reads that treat dotenvx-style ciphertext as absent.
3
+ *
4
+ * `@stacksjs/env` already holds this rule twice. Its proxy decrypts on read
5
+ * and scrubs a value no key can open, so `env.PORT ?? 3000` resolves to the
6
+ * default; its preloader scrubs ciphertext Bun left in `process.env`, because
7
+ * Bun auto-loads `.env` and knows nothing about encryption. Both say the same
8
+ * thing: ciphertext is unusable to every consumer, so it never wins.
9
+ *
10
+ * This package reads a handful of variables straight from `process.env` and so
11
+ * honoured neither. It cannot use the proxy. These reads run at module init,
12
+ * and importing `@stacksjs/env` for them would put its loader, CLI and plugin
13
+ * surface into the init path that `overridesReady` exists to keep untangled.
14
+ *
15
+ * So the rule is restated here, and `tests/plain-env.test.ts` fails if this
16
+ * copy and `isEncryptedValue` ever disagree.
17
+ *
18
+ * Without it, any command that skips the env preloader boots with `app.env`
19
+ * set to 300 characters of base64. Validation then rejects a config that is
20
+ * correct and throws, naming the ciphertext rather than the missing key
21
+ * (stacksjs/stacks#2903).
22
+ */
23
+ /**
24
+ * Whether a value is ciphertext rather than something a consumer can use.
25
+ *
26
+ * Both prefixes dotenvx writes, kept in step with `isEncryptedValue` in
27
+ * `@stacksjs/env`.
28
+ */
29
+ export declare function isCiphertext(value: unknown): boolean;
30
+ /**
31
+ * Read `key` from the environment, or `undefined` when it holds ciphertext.
32
+ *
33
+ * "Absent" is the honest reading of a value nothing can open, and it lets the
34
+ * `|| default` beside each call resolve exactly as it does when the variable
35
+ * was never set.
36
+ */
37
+ export declare function plainEnv(key: string): string | undefined;
@@ -0,0 +1 @@
1
+ export function isCiphertext(value){return typeof value==="string"&&(value.startsWith("encrypted:")||value.startsWith("enc:"))}export function plainEnv(key){const value=process.env[key];return isCiphertext(value)?void 0:value}
@@ -1 +1 @@
1
- import{capabilityDrivers,findCapability}from"./capabilities";function isPlainObject(v){return typeof v==="object"&&v!==null&&!Array.isArray(v)}function checkInteger(min,max){return(value,path)=>{if(value==null)return[];let num=null;if(typeof value==="number"&&Number.isInteger(value))num=value;else if(typeof value==="string"&&/^-?\d+$/.test(value))num=Number.parseInt(value,10);if(num===null)return[{path,message:`expected integer, got ${typeof value} (${JSON.stringify(value)})`}];if(num<min||num>max)return[{path,message:`expected integer in [${min}, ${max}], got ${num}`}];return[]}}function checkOneOf(values){return(value,path)=>{if(value==null)return[];if(typeof value!=="string"||!values.includes(value))return[{path,message:`expected one of [${values.join(", ")}], got ${JSON.stringify(value)}`}];return[]}}function checkCapability(category){return(value,path)=>{if(value==null)return[];const drivers=capabilityDrivers(category);if(typeof value!=="string")return[{path,message:`expected ${category} driver name, got ${typeof value}`}];const capability=findCapability(category,value);if(!capability)return[{path,message:`unknown ${category} driver ${JSON.stringify(value)}; known drivers: [${drivers.map((driver)=>driver.name).join(", ")}]`}];if(capability.status==="unsupported")return[{path,message:`${category} driver ${JSON.stringify(value)} is unsupported: ${capability.limitations.join(" ")}`}];return[]}}function checkString(){return(value,path)=>{if(value==null)return[];return typeof value==="string"?[]:[{path,message:`expected string, got ${typeof value}`}]}}function checkBoolean(){return(value,path)=>{if(value==null)return[];return typeof value==="boolean"?[]:[{path,message:`expected boolean, got ${typeof value}`}]}}const PORT_CHECK=checkInteger(1,65535),SCHEMA={app:{rules:{name:checkString(),env:checkOneOf(["local","development","staging","production","test"]),debug:checkBoolean(),url:checkString()}},ports:{rules:{frontend:PORT_CHECK,api:PORT_CHECK,admin:PORT_CHECK,docs:PORT_CHECK,systemTray:PORT_CHECK,desktop:PORT_CHECK}},database:{rules:{default:checkCapability("database")}},cache:{rules:{driver:checkCapability("cache")}},featureFlags:{rules:{default:checkOneOf(["memory","database"]),missing:checkOneOf(["false","throw"])}},queue:{rules:{default:checkCapability("queue")}},logging:{rules:{level:checkOneOf(["trace","debug","info","warn","error","fatal"])}},email:{rules:{default:checkCapability("mail")}}};export function validateConfig(config){const issues=[];for(const[section,schema]of Object.entries(SCHEMA)){const sectionValue=config[section];if(sectionValue==null)continue;if(!isPlainObject(sectionValue)){issues.push({path:section,message:`expected object, got ${typeof sectionValue}`});continue}for(const[field,check]of Object.entries(schema.rules)){const v=sectionValue[field];issues.push(...check(v,`${section}.${field}`))}}return issues}export function reportConfigIssues(config){const issues=validateConfig(config);if(issues.length===0)return!0;console.warn("[config] Configuration issues detected:");for(const issue of issues)console.warn(` \u2022 ${issue.path}: ${issue.message}`);return!1}
1
+ import{capabilityDrivers,findCapability}from"./capabilities";import{isCiphertext}from"./plain-env";function isPlainObject(v){return typeof v==="object"&&v!==null&&!Array.isArray(v)}function checkInteger(min,max){return(value,path)=>{if(value==null)return[];let num=null;if(typeof value==="number"&&Number.isInteger(value))num=value;else if(typeof value==="string"&&/^-?\d+$/.test(value))num=Number.parseInt(value,10);if(num===null)return[{path,message:`expected integer, got ${typeof value} (${JSON.stringify(value)})`}];if(num<min||num>max)return[{path,message:`expected integer in [${min}, ${max}], got ${num}`}];return[]}}function checkOneOf(values){return(value,path)=>{if(value==null)return[];if(typeof value!=="string"||!values.includes(value))return[{path,message:`expected one of [${values.join(", ")}], got ${JSON.stringify(value)}`}];return[]}}function checkCapability(category){return(value,path)=>{if(value==null)return[];const drivers=capabilityDrivers(category);if(typeof value!=="string")return[{path,message:`expected ${category} driver name, got ${typeof value}`}];const capability=findCapability(category,value);if(!capability)return[{path,message:`unknown ${category} driver ${JSON.stringify(value)}; known drivers: [${drivers.map((driver)=>driver.name).join(", ")}]`}];if(capability.status==="unsupported")return[{path,message:`${category} driver ${JSON.stringify(value)} is unsupported: ${capability.limitations.join(" ")}`}];return[]}}function checkString(){return(value,path)=>{if(value==null)return[];return typeof value==="string"?[]:[{path,message:`expected string, got ${typeof value}`}]}}function checkBoolean(){return(value,path)=>{if(value==null)return[];return typeof value==="boolean"?[]:[{path,message:`expected boolean, got ${typeof value}`}]}}const PORT_CHECK=checkInteger(1,65535),SCHEMA={app:{rules:{name:checkString(),env:checkOneOf(["local","development","staging","production","test"]),debug:checkBoolean(),url:checkString()}},ports:{rules:{frontend:PORT_CHECK,api:PORT_CHECK,admin:PORT_CHECK,docs:PORT_CHECK,systemTray:PORT_CHECK,desktop:PORT_CHECK}},database:{rules:{default:checkCapability("database")}},cache:{rules:{driver:checkCapability("cache")}},featureFlags:{rules:{default:checkOneOf(["memory","database"]),missing:checkOneOf(["false","throw"])}},queue:{rules:{default:checkCapability("queue")}},logging:{rules:{level:checkOneOf(["trace","debug","info","warn","error","fatal"])}},email:{rules:{default:checkCapability("mail")}}};export function validateConfig(config){const issues=[];for(const[section,schema]of Object.entries(SCHEMA)){const sectionValue=config[section];if(sectionValue==null)continue;if(!isPlainObject(sectionValue)){issues.push({path:section,message:`expected object, got ${typeof sectionValue}`});continue}for(const[field,check]of Object.entries(schema.rules)){const v=sectionValue[field],path=`${section}.${field}`;if(isCiphertext(v)){issues.push({path,message:"value is still encrypted; no usable private key opened it, so config read the ciphertext. Set the matching DOTENV_PRIVATE_KEY_<ENV> or ship .env.keys, and read it through `env` from @stacksjs/env rather than process.env"});continue}issues.push(...check(v,path))}}return issues}export function reportConfigIssues(config){const issues=validateConfig(config);if(issues.length===0)return!0;console.warn("[config] Configuration issues detected:");for(const issue of issues)console.warn(` \u2022 ${issue.path}: ${issue.message}`);return!1}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/config",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.177",
5
+ "version": "0.75.179",
6
6
  "description": "The Stacks config helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -55,15 +55,16 @@
55
55
  "prepublishOnly": "bun run build"
56
56
  },
57
57
  "dependencies": {
58
- "@stacksjs/events": "0.75.177",
59
- "@stacksjs/features": "0.75.177",
60
- "@stacksjs/path": "0.75.177",
61
- "@stacksjs/tunnel": "0.75.177",
58
+ "@stacksjs/events": "0.75.179",
59
+ "@stacksjs/features": "0.75.179",
60
+ "@stacksjs/path": "0.75.179",
61
+ "@stacksjs/tunnel": "0.75.179",
62
62
  "ts-pantry": "^0.11.69"
63
63
  },
64
64
  "devDependencies": {
65
- "@stacksjs/alias": "0.75.177",
66
- "@stacksjs/types": "0.75.177",
65
+ "@stacksjs/alias": "0.75.179",
66
+ "@stacksjs/env": "0.75.179",
67
+ "@stacksjs/types": "0.75.179",
67
68
  "better-dx": "^0.2.24"
68
69
  }
69
70
  }