@fjall/util 28.1.0 → 28.2.0

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.
@@ -9,7 +9,7 @@
9
9
  "package.json",
10
10
  "tsconfig.json"
11
11
  ],
12
- "hash": "5a4f4b97aa5eb222156d7db2ebc5643068d5d8d465a215749a4970a730fff27a",
12
+ "hash": "a07238212f0e522feebb88afc1b3581bdb501b0f479775e7a2014c13d4a66cd4",
13
13
  "files": {
14
14
  "src/async/concurrency.ts": "3c7ebe0222a720f9",
15
15
  "src/async/singleton.ts": "7bcc21b0fb920561",
@@ -33,7 +33,7 @@
33
33
  "src/cdkToolchain.ts": "a6b996c60b87d644",
34
34
  "src/cfn/physicalNameProperties.ts": "35d3c6f28079c593",
35
35
  "src/clickhouse.ts": "655c74cdba292c29",
36
- "src/config.ts": "cea64a749e4cdaaf",
36
+ "src/config.ts": "77011bd9d88e0129",
37
37
  "src/configPaths.ts": "afcc2e4ceb6652a0",
38
38
  "src/constructMap.ts": "b81fc8e90015467f",
39
39
  "src/deploy/approvalTokenOutput.ts": "4be7b52825d88a37",
@@ -131,9 +131,9 @@
131
131
  "src/staticSiteOutput.ts": "cc81ed32acc597f1",
132
132
  "src/targets.ts": "126280f0a5762e21",
133
133
  "../scripts/minify-dist.mjs": "6b2e4b0df8aec601",
134
- "package.json": "aab09a7b1c11638a",
134
+ "package.json": "2905c8b5387c5578",
135
135
  "tsconfig.json": "db759831ad95f396"
136
136
  },
137
- "outputHash": "55a998461fb38cf4d45d0fc723646bf3ee7cdecbfff90e98af8ac7930e9e0c28",
137
+ "outputHash": "da4ec1b532eae3b07df1e167e1452d29879f0a6ea03cca47f9472b8e2229b7e4",
138
138
  "outputFileCount": 247
139
139
  }
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 123 files minified at 2026-09-01T09:01:12.427Z
1
+ 123 files minified at 2026-09-01T23:53:35.097Z
package/dist/config.d.ts CHANGED
@@ -282,6 +282,18 @@ export declare class Config {
282
282
  * sibling domain entries.
283
283
  */
284
284
  private loadedSnapshot;
285
+ /**
286
+ * The directory `loadConfig` was asked to walk up from, remembered so an
287
+ * UNBOUND instance stays coherent with its origin: later resolutions
288
+ * (`resolveProjectDirectory`'s fallback walk, `saveConfig`'s create-a-new-
289
+ * project default) anchor here instead of re-walking from process.cwd().
290
+ * Without this, `loadConfig(scaffoldRoot)` under `create app --into`
291
+ * followed by a save could materialise the config in a DIFFERENT project —
292
+ * whichever one happened to contain the shell's cwd. Undefined for
293
+ * programmatically-constructed instances and no-arg loads (cwd semantics
294
+ * unchanged).
295
+ */
296
+ private searchStartDir;
285
297
  constructor(rootConfig?: RootConfig, configPath?: string);
286
298
  /**
287
299
  * Find the config directory by walking up the directory tree.
package/dist/config.js CHANGED
@@ -1,2 +1,2 @@
1
- var I=Object.defineProperty;var p=(u,e)=>I(u,"name",{value:e,configurable:!0});import*as a from"fs";import*as d from"path";import{z as c}from"zod";import{CapacityIdentityConfigSchema as A}from"./capacityIdentity.js";import{failure as m,success as w}from"./docker/result.js";import{getErrorMessage as E}from"./errorUtils.js";import{logger as v}from"./logger.js";import{maskSensitiveOutput as C}from"./securityHelpers.js";import{ensureGitignoreEntries as D}from"./scaffold/ensureGitignore.js";import{FJALL_MANAGED_STATE_DIR_ENTRIES as R,FJALL_PROJECT_GITIGNORE_HEADER as b,LOCAL_CONFIG_GITIGNORE_ENTRY as x,LOCAL_STATE_GITIGNORE_MARKER as $}from"./scaffold/gitignore.js";import{LOCAL_CONFIG_DIRNAME as M,LOCAL_CONFIG_FILENAME as G,ROOT_CONFIG_FILENAME as T}from"./configPaths.js";import{LOCAL_CONFIG_DIRNAME as ge,LOCAL_CONFIG_FILENAME as ue,ROOT_CONFIG_FILENAME as me}from"./configPaths.js";const P=10,ee=["compliance","governance","none"],te=["enforced","off"],ne=["centralised","off"],oe=["account","draining","org"],ie="managementEvents",re="organisationManagementEvents",se=["active","draining","removed"],ae="FjallTrailBucketName",ce="FjallTrailKeyArn",le="OrganisationTrailBucketName",j=c.object({name:c.string(),type:c.enum(["apex","delegated"]),parentDomain:c.string().optional(),account:c.string().optional(),region:c.string().optional()}).strict(),L=c.object({id:c.string().min(1),name:c.string().min(1).optional()}).strict(),_=c.object({activeTarget:c.string().optional(),domains:c.array(j).optional(),capacityIdentity:A.optional(),organisation:L.optional()}).strict(),N=c.object({activeTarget:c.string().optional(),domains:c.array(j).optional(),capacityIdentity:A.optional(),organisation:L.optional()}),K=c.object({activeTarget:c.string().optional()});function J(u={}){return JSON.stringify(u,null,2)}p(J,"serialiseRootConfig");const k=_.keyof().options;function U(u,e,t){const n=e[t];n!==void 0&&(u[t]=n)}p(U,"copyDefinedKey");function F(u,e){if(e===void 0)return!1;const t=new Set([...Object.keys(u),...Object.keys(e)]);for(const n of t)if(u[n]!==e[n])return!1;return!0}p(F,"domainEntriesEqual");class f{static{p(this,"Config")}rootConfig;configPath=null;loadFailed=!1;parseDegraded=!1;loadedSnapshot={};constructor(e,t){this.rootConfig=e??{},this.configPath=t??null}static findConfigDirectory(e){let t=e!==void 0&&e!==""?e:process.cwd();for(let n=0;n<P;n++){const o=d.join(t,"fjall"),r=d.join(o,T);if(a.existsSync(r))return o;const i=d.join(t,T);if(a.existsSync(i))return t;const l=d.dirname(t);if(l===t)break;t=l}return null}static loadConfigFile(e){try{return a.accessSync(e,a.constants.R_OK),a.readFileSync(e,{encoding:"utf8"})}catch(t){return v.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:C(E(t))}),null}}static loadConfig(e){const t=f.findConfigDirectory(e);if(!t)return new f;const n=d.join(t,T),o=f.loadConfigFile(n);if(o===null){const g=new f(void 0,n);return g.loadFailed=!0,g}let r,i=!1;if(o!==""){let g;try{g=JSON.parse(o)}catch(s){throw f.formatZodError(s,T)}const y=_.safeParse(g);if(y.success)r=y.data;else{const s=N.safeParse(g);r=s.success?s.data:{},i=!s.success,s.success?v.warn("Config","fjall-config.json contains keys this version does not recognise (only activeTarget, domains, capacityIdentity and organisation are read). They were most likely written by a newer fjall version \u2014 leave them in place; saves preserve them verbatim.",{file:n}):v.warn("Config","fjall-config.json has a malformed activeTarget, domains or capacityIdentity value, so its contents were ignored for this run \u2014 any capacity-identity pins it holds will NOT apply (a pinned stack deploys unpinned and is blocked by the rename gate). Fix the file by hand; do NOT regenerate it, which would discard the pins.",{file:n})}}const l=new f(r,n);return l.parseDegraded=i,l.loadedSnapshot=structuredClone(l.rootConfig),l}static formatZodError(e,t){if(e instanceof c.ZodError&&e.issues.length>0){const r=e.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join("; ");return new Error(`Failed to parse ${t}: ${r}`)}const o=(e instanceof Error?e.message:String(e)).replace(/\n/g," ").substring(0,500);return new Error(`Failed to parse ${t}: ${o}`)}saveConfig(){const e=d.join(this.resolveProjectDirectory()??d.join(process.cwd(),"fjall"),T);if(this.loadFailed)return m(new Error(`Refusing to save ${e}: the file exists but could not be read when this config loaded, so saving would replace its contents with state that never included them. Fix the file permissions (e.g. chmod u+rw ${e}) and retry.`));const t=d.dirname(e);try{a.mkdirSync(t,{recursive:!0})}catch(l){return m(new Error(`Cannot create config directory ${t}: ${C(E(l))}`))}const n=f.assertWritable(e,t);if(!n.success)return n;const o=this.mergeWithDisk(e);if(!o.success)return o;const r=J(o.data),i=`${e}.tmp-${process.pid}`;try{a.writeFileSync(i,r,{mode:384}),a.renameSync(i,e)}catch(l){return m(new Error(`Failed to save ${e}: ${C(E(l))}`))}return this.configPath=e,w(void 0)}static assertWritable(e,t){if(a.existsSync(e))try{a.accessSync(e,a.constants.W_OK)}catch{return m(new Error(`Cannot save ${e}: the file is read-only. Make it writable (e.g. chmod u+w ${e}) and retry.`))}try{a.accessSync(t,a.constants.W_OK)}catch{return m(new Error(`Cannot save ${e}: the directory ${t} is not writable. Make it writable (e.g. chmod u+w ${t}) and retry.`))}return w(void 0)}mergeWithDisk(e){const t=f.readDiskConfigForMerge(e);if(t.kind==="unmergeable")return m(new Error(`Refusing to save ${e}: ${t.reason}. Writing now would keep only this session's state and drop everything else the file carries (including keys written by other fjall versions). Fix or remove the file and retry.`));if(t.kind==="absent")return w(this.rootConfig);const n=t.disk,o={...n.foreign,...n.known};for(const i of k)i!=="domains"&&this.sessionChangedKey(i)&&(this.rootConfig[i]===void 0?delete o[i]:U(o,this.rootConfig,i));const r=this.mergeDomains(n.known.domains);return r!==void 0?o.domains=r:delete o.domains,w(o)}sessionChangedKey(e){return JSON.stringify(this.rootConfig[e])!==JSON.stringify(this.loadedSnapshot[e])}mergeDomains(e){const t=this.rootConfig.domains,n=this.loadedSnapshot.domains??[];if(t===void 0&&n.length===0)return e;const o=p(s=>s.toLowerCase(),"norm"),r=t??[],i=new Map(n.map(s=>[o(s.name),s])),l=new Map(r.map(s=>[o(s.name),s])),g=[],y=new Set;for(const s of e??[]){const h=o(s.name);y.add(h);const O=l.get(h),S=i.get(h);if(O!==void 0&&!F(O,S)){g.push(O);continue}O===void 0&&S!==void 0||g.push(s)}for(const s of r){const h=o(s.name);y.has(h)||F(s,i.get(h))||g.push(s)}return g}static readDiskConfigForMerge(e){if(!a.existsSync(e))return{kind:"absent"};let t;try{const r=a.readFileSync(e,{encoding:"utf8"});if(r==="")return{kind:"absent"};t=JSON.parse(r)}catch(r){return{kind:"unmergeable",reason:`the existing file could not be read or parsed as JSON (${C(E(r))})`}}const n=N.safeParse(t);if(!n.success){const r=n.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join("; ");return{kind:"unmergeable",reason:`a recognised key in the existing file is malformed (${C(r)})`}}const o={};if(typeof t=="object"&&t!==null){const r=k;for(const[i,l]of Object.entries(t))r.includes(i)||(o[i]=l)}return{kind:"ok",disk:{known:n.data,foreign:o}}}static getConfigDirectory(e){return f.findConfigDirectory(e)}getConfigPath(){return this.configPath}isContentUnavailable(){return this.loadFailed||this.parseDegraded}static warnedLegacyActiveTarget=!1;localConfigCache;resolveProjectDirectory(){return this.configPath!==null?d.dirname(this.configPath):f.findConfigDirectory()}localConfigFile(){const e=this.resolveProjectDirectory();return e===null?null:d.join(e,M,G)}loadLocalConfig(){if(this.localConfigCache===void 0){const e=this.localConfigFile();if(e===null)return this.localConfigCache={};try{const t=a.readFileSync(e,{encoding:"utf8"}),n=K.safeParse(JSON.parse(t));this.localConfigCache=n.success?n.data:{}}catch{this.localConfigCache={}}}return this.localConfigCache}writeLocalConfig(e){const t=this.localConfigFile();if(t===null)return m(new Error("Cannot persist local state: no fjall-config.json was found from this directory, so there is no project to anchor `.fjall/local.json` to. Run this from inside a fjall project, or run `fjall create account` / `fjall create app` first."));try{a.mkdirSync(d.dirname(t),{recursive:!0});const n=`${t}.tmp-${process.pid}`;a.writeFileSync(n,JSON.stringify(e,null,2)+`
2
- `,{encoding:"utf8"}),a.renameSync(n,t),D(d.dirname(d.dirname(t)),[x,...R],{header:b,marker:$})}catch(n){return m(new Error(`Failed to write ${t}: ${C(E(n))}`))}return this.localConfigCache=e,w(void 0)}static warnLegacyActiveTargetOnce(){f.warnedLegacyActiveTarget||(f.warnedLegacyActiveTarget=!0,v.warn("Config","`activeTarget` in fjall-config.json is deprecated and ignored: a committed target must never re-aim a deploy or CI pipeline. Run `fjall target set <name>` to store it locally in .fjall/local.json, then remove `activeTarget` from the committed fjall-config.json."))}getActiveTarget(){const e=this.loadLocalConfig().activeTarget;if(e!==void 0)return e;this.rootConfig.activeTarget!==void 0&&f.warnLegacyActiveTargetOnce()}setActiveTarget(e){const t=this.loadLocalConfig();return t.activeTarget=e,this.writeLocalConfig(t)}clearActiveTarget(){const e=this.loadLocalConfig();return delete e.activeTarget,this.writeLocalConfig(e)}getDomains(){return this.rootConfig.domains??[]}setDomains(e){this.rootConfig.domains=e}addDomain(e){this.rootConfig.domains||(this.rootConfig.domains=[]),this.rootConfig.domains.push(e)}getDomain(e){return this.rootConfig.domains?.find(t=>t.name.toLowerCase()===e.toLowerCase())}removeDomain(e){if(!this.rootConfig.domains)return!1;const t=this.rootConfig.domains.findIndex(n=>n.name.toLowerCase()===e.toLowerCase());return t===-1?!1:(this.rootConfig.domains.splice(t,1),!0)}getOrganisation(){return this.rootConfig.organisation}setOrganisation(e){this.rootConfig.organisation=e}clearOrganisation(){delete this.rootConfig.organisation}}export{ie as ACCOUNT_TRAIL_NAME,se as ACCOUNT_TRAIL_STATES,f as Config,j as DomainConfigSchema,ge as LOCAL_CONFIG_DIRNAME,ue as LOCAL_CONFIG_FILENAME,re as ORGANISATION_TRAIL_NAME,le as ORG_TRAIL_BUCKET_OUTPUT_KEY,L as OrganisationBindingSchema,ne as ROOT_ACCESS_MANAGEMENT_MODES,me as ROOT_CONFIG_FILENAME,N as RootConfigReadSchema,_ as RootConfigSchema,te as S3_BPA_MODES,ae as TRAIL_BUCKET_OUTPUT_KEY,ce as TRAIL_KEY_ARN_OUTPUT_KEY,oe as TRAIL_LIFECYCLE_STATES,ee as VAULT_LOCK_MODES,J as serialiseRootConfig};
1
+ var F=Object.defineProperty;var p=(u,e)=>F(u,"name",{value:e,configurable:!0});import*as s from"fs";import*as g from"path";import{z as c}from"zod";import{CapacityIdentityConfigSchema as A}from"./capacityIdentity.js";import{failure as h,success as w}from"./docker/result.js";import{getErrorMessage as E}from"./errorUtils.js";import{logger as S}from"./logger.js";import{maskSensitiveOutput as C}from"./securityHelpers.js";import{ensureGitignoreEntries as I}from"./scaffold/ensureGitignore.js";import{FJALL_MANAGED_STATE_DIR_ENTRIES as R,FJALL_PROJECT_GITIGNORE_HEADER as b,LOCAL_CONFIG_GITIGNORE_ENTRY as x,LOCAL_STATE_GITIGNORE_MARKER as $}from"./scaffold/gitignore.js";import{LOCAL_CONFIG_DIRNAME as M,LOCAL_CONFIG_FILENAME as G,ROOT_CONFIG_FILENAME as T}from"./configPaths.js";import{LOCAL_CONFIG_DIRNAME as ge,LOCAL_CONFIG_FILENAME as ue,ROOT_CONFIG_FILENAME as he}from"./configPaths.js";const P=10,ee=["compliance","governance","none"],te=["enforced","off"],ne=["centralised","off"],oe=["account","draining","org"],ie="managementEvents",re="organisationManagementEvents",ae=["active","draining","removed"],se="FjallTrailBucketName",ce="FjallTrailKeyArn",le="OrganisationTrailBucketName",j=c.object({name:c.string(),type:c.enum(["apex","delegated"]),parentDomain:c.string().optional(),account:c.string().optional(),region:c.string().optional()}).strict(),L=c.object({id:c.string().min(1),name:c.string().min(1).optional()}).strict(),_=c.object({activeTarget:c.string().optional(),domains:c.array(j).optional(),capacityIdentity:A.optional(),organisation:L.optional()}).strict(),N=c.object({activeTarget:c.string().optional(),domains:c.array(j).optional(),capacityIdentity:A.optional(),organisation:L.optional()}),K=c.object({activeTarget:c.string().optional()});function J(u={}){return JSON.stringify(u,null,2)}p(J,"serialiseRootConfig");const k=_.keyof().options;function U(u,e,t){const n=e[t];n!==void 0&&(u[t]=n)}p(U,"copyDefinedKey");function D(u,e){if(e===void 0)return!1;const t=new Set([...Object.keys(u),...Object.keys(e)]);for(const n of t)if(u[n]!==e[n])return!1;return!0}p(D,"domainEntriesEqual");class f{static{p(this,"Config")}rootConfig;configPath=null;loadFailed=!1;parseDegraded=!1;loadedSnapshot={};searchStartDir;constructor(e,t){this.rootConfig=e??{},this.configPath=t??null}static findConfigDirectory(e){let t=e!==void 0&&e!==""?e:process.cwd();for(let n=0;n<P;n++){const o=g.join(t,"fjall"),r=g.join(o,T);if(s.existsSync(r))return o;const i=g.join(t,T);if(s.existsSync(i))return t;const l=g.dirname(t);if(l===t)break;t=l}return null}static loadConfigFile(e){try{return s.accessSync(e,s.constants.R_OK),s.readFileSync(e,{encoding:"utf8"})}catch(t){return S.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:C(E(t))}),null}}static loadConfig(e){const t=f.findConfigDirectory(e);if(!t){const d=new f;return d.searchStartDir=e,d}const n=g.join(t,T),o=f.loadConfigFile(n);if(o===null){const d=new f(void 0,n);return d.searchStartDir=e,d.loadFailed=!0,d}let r,i=!1;if(o!==""){let d;try{d=JSON.parse(o)}catch(a){throw f.formatZodError(a,T)}const y=_.safeParse(d);if(y.success)r=y.data;else{const a=N.safeParse(d);r=a.success?a.data:{},i=!a.success,a.success?S.warn("Config","fjall-config.json contains keys this version does not recognise (only activeTarget, domains, capacityIdentity and organisation are read). They were most likely written by a newer fjall version \u2014 leave them in place; saves preserve them verbatim.",{file:n}):S.warn("Config","fjall-config.json has a malformed activeTarget, domains or capacityIdentity value, so its contents were ignored for this run \u2014 any capacity-identity pins it holds will NOT apply (a pinned stack deploys unpinned and is blocked by the rename gate). Fix the file by hand; do NOT regenerate it, which would discard the pins.",{file:n})}}const l=new f(r,n);return l.searchStartDir=e,l.parseDegraded=i,l.loadedSnapshot=structuredClone(l.rootConfig),l}static formatZodError(e,t){if(e instanceof c.ZodError&&e.issues.length>0){const r=e.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join("; ");return new Error(`Failed to parse ${t}: ${r}`)}const o=(e instanceof Error?e.message:String(e)).replace(/\n/g," ").substring(0,500);return new Error(`Failed to parse ${t}: ${o}`)}saveConfig(){const e=g.join(this.resolveProjectDirectory()??g.join(this.searchStartDir??process.cwd(),"fjall"),T);if(this.loadFailed)return h(new Error(`Refusing to save ${e}: the file exists but could not be read when this config loaded, so saving would replace its contents with state that never included them. Fix the file permissions (e.g. chmod u+rw ${e}) and retry.`));const t=g.dirname(e);try{s.mkdirSync(t,{recursive:!0})}catch(l){return h(new Error(`Cannot create config directory ${t}: ${C(E(l))}`))}const n=f.assertWritable(e,t);if(!n.success)return n;const o=this.mergeWithDisk(e);if(!o.success)return o;const r=J(o.data),i=`${e}.tmp-${process.pid}`;try{s.writeFileSync(i,r,{mode:384}),s.renameSync(i,e)}catch(l){return h(new Error(`Failed to save ${e}: ${C(E(l))}`))}return this.configPath=e,w(void 0)}static assertWritable(e,t){if(s.existsSync(e))try{s.accessSync(e,s.constants.W_OK)}catch{return h(new Error(`Cannot save ${e}: the file is read-only. Make it writable (e.g. chmod u+w ${e}) and retry.`))}try{s.accessSync(t,s.constants.W_OK)}catch{return h(new Error(`Cannot save ${e}: the directory ${t} is not writable. Make it writable (e.g. chmod u+w ${t}) and retry.`))}return w(void 0)}mergeWithDisk(e){const t=f.readDiskConfigForMerge(e);if(t.kind==="unmergeable")return h(new Error(`Refusing to save ${e}: ${t.reason}. Writing now would keep only this session's state and drop everything else the file carries (including keys written by other fjall versions). Fix or remove the file and retry.`));if(t.kind==="absent")return w(this.rootConfig);const n=t.disk,o={...n.foreign,...n.known};for(const i of k)i!=="domains"&&this.sessionChangedKey(i)&&(this.rootConfig[i]===void 0?delete o[i]:U(o,this.rootConfig,i));const r=this.mergeDomains(n.known.domains);return r!==void 0?o.domains=r:delete o.domains,w(o)}sessionChangedKey(e){return JSON.stringify(this.rootConfig[e])!==JSON.stringify(this.loadedSnapshot[e])}mergeDomains(e){const t=this.rootConfig.domains,n=this.loadedSnapshot.domains??[];if(t===void 0&&n.length===0)return e;const o=p(a=>a.toLowerCase(),"norm"),r=t??[],i=new Map(n.map(a=>[o(a.name),a])),l=new Map(r.map(a=>[o(a.name),a])),d=[],y=new Set;for(const a of e??[]){const m=o(a.name);y.add(m);const O=l.get(m),v=i.get(m);if(O!==void 0&&!D(O,v)){d.push(O);continue}O===void 0&&v!==void 0||d.push(a)}for(const a of r){const m=o(a.name);y.has(m)||D(a,i.get(m))||d.push(a)}return d}static readDiskConfigForMerge(e){if(!s.existsSync(e))return{kind:"absent"};let t;try{const r=s.readFileSync(e,{encoding:"utf8"});if(r==="")return{kind:"absent"};t=JSON.parse(r)}catch(r){return{kind:"unmergeable",reason:`the existing file could not be read or parsed as JSON (${C(E(r))})`}}const n=N.safeParse(t);if(!n.success){const r=n.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join("; ");return{kind:"unmergeable",reason:`a recognised key in the existing file is malformed (${C(r)})`}}const o={};if(typeof t=="object"&&t!==null){const r=k;for(const[i,l]of Object.entries(t))r.includes(i)||(o[i]=l)}return{kind:"ok",disk:{known:n.data,foreign:o}}}static getConfigDirectory(e){return f.findConfigDirectory(e)}getConfigPath(){return this.configPath}isContentUnavailable(){return this.loadFailed||this.parseDegraded}static warnedLegacyActiveTarget=!1;localConfigCache;resolveProjectDirectory(){return this.configPath!==null?g.dirname(this.configPath):f.findConfigDirectory(this.searchStartDir)}localConfigFile(){const e=this.resolveProjectDirectory();return e===null?null:g.join(e,M,G)}loadLocalConfig(){if(this.localConfigCache===void 0){const e=this.localConfigFile();if(e===null)return this.localConfigCache={};try{const t=s.readFileSync(e,{encoding:"utf8"}),n=K.safeParse(JSON.parse(t));this.localConfigCache=n.success?n.data:{}}catch{this.localConfigCache={}}}return this.localConfigCache}writeLocalConfig(e){const t=this.localConfigFile();if(t===null)return h(new Error("Cannot persist local state: no fjall-config.json was found from this directory, so there is no project to anchor `.fjall/local.json` to. Run this from inside a fjall project, or run `fjall create account` / `fjall create app` first."));try{s.mkdirSync(g.dirname(t),{recursive:!0});const n=`${t}.tmp-${process.pid}`;s.writeFileSync(n,JSON.stringify(e,null,2)+`
2
+ `,{encoding:"utf8"}),s.renameSync(n,t),I(g.dirname(g.dirname(t)),[x,...R],{header:b,marker:$})}catch(n){return h(new Error(`Failed to write ${t}: ${C(E(n))}`))}return this.localConfigCache=e,w(void 0)}static warnLegacyActiveTargetOnce(){f.warnedLegacyActiveTarget||(f.warnedLegacyActiveTarget=!0,S.warn("Config","`activeTarget` in fjall-config.json is deprecated and ignored: a committed target must never re-aim a deploy or CI pipeline. Run `fjall target set <name>` to store it locally in .fjall/local.json, then remove `activeTarget` from the committed fjall-config.json."))}getActiveTarget(){const e=this.loadLocalConfig().activeTarget;if(e!==void 0)return e;this.rootConfig.activeTarget!==void 0&&f.warnLegacyActiveTargetOnce()}setActiveTarget(e){const t=this.loadLocalConfig();return t.activeTarget=e,this.writeLocalConfig(t)}clearActiveTarget(){const e=this.loadLocalConfig();return delete e.activeTarget,this.writeLocalConfig(e)}getDomains(){return this.rootConfig.domains??[]}setDomains(e){this.rootConfig.domains=e}addDomain(e){this.rootConfig.domains||(this.rootConfig.domains=[]),this.rootConfig.domains.push(e)}getDomain(e){return this.rootConfig.domains?.find(t=>t.name.toLowerCase()===e.toLowerCase())}removeDomain(e){if(!this.rootConfig.domains)return!1;const t=this.rootConfig.domains.findIndex(n=>n.name.toLowerCase()===e.toLowerCase());return t===-1?!1:(this.rootConfig.domains.splice(t,1),!0)}getOrganisation(){return this.rootConfig.organisation}setOrganisation(e){this.rootConfig.organisation=e}clearOrganisation(){delete this.rootConfig.organisation}}export{ie as ACCOUNT_TRAIL_NAME,ae as ACCOUNT_TRAIL_STATES,f as Config,j as DomainConfigSchema,ge as LOCAL_CONFIG_DIRNAME,ue as LOCAL_CONFIG_FILENAME,re as ORGANISATION_TRAIL_NAME,le as ORG_TRAIL_BUCKET_OUTPUT_KEY,L as OrganisationBindingSchema,ne as ROOT_ACCESS_MANAGEMENT_MODES,he as ROOT_CONFIG_FILENAME,N as RootConfigReadSchema,_ as RootConfigSchema,te as S3_BPA_MODES,se as TRAIL_BUCKET_OUTPUT_KEY,ce as TRAIL_KEY_ARN_OUTPUT_KEY,oe as TRAIL_LIFECYCLE_STATES,ee as VAULT_LOCK_MODES,J as serialiseRootConfig};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "28.1.0",
3
+ "version": "28.2.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",