@fjall/util 26.0.0 → 27.0.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.
- package/dist/.build-source-hash +4 -4
- package/dist/.minified +1 -1
- package/dist/config.d.ts +8 -5
- package/dist/config.js +1 -1
- package/package.json +1 -1
package/dist/.build-source-hash
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"package.json",
|
|
10
10
|
"tsconfig.json"
|
|
11
11
|
],
|
|
12
|
-
"hash": "
|
|
12
|
+
"hash": "eb1dbb5d20f5acd6814d5e0332a14f716286a9aec27ff8dbadfeb35254e9e0de",
|
|
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": "
|
|
36
|
+
"src/config.ts": "cea64a749e4cdaaf",
|
|
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": "
|
|
134
|
+
"package.json": "dfb97322fc6e7f70",
|
|
135
135
|
"tsconfig.json": "db759831ad95f396"
|
|
136
136
|
},
|
|
137
|
-
"outputHash": "
|
|
137
|
+
"outputHash": "347559a2b5a5a0f6ed879bac890d21a46e0bc8cec87bf73ba7d8432116caf642",
|
|
138
138
|
"outputFileCount": 247
|
|
139
139
|
}
|
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
123 files minified at 2026-08-
|
|
1
|
+
123 files minified at 2026-08-31T09:47:05.898Z
|
package/dist/config.d.ts
CHANGED
|
@@ -160,11 +160,14 @@ export type DomainConfig = z.infer<typeof DomainConfigSchema>;
|
|
|
160
160
|
* ONE Fjall organisation.
|
|
161
161
|
*
|
|
162
162
|
* This is the ONE organisation fact that belongs in fjall-config.json, and it
|
|
163
|
-
* is not a mirror of API state: it is a project ASSERTION
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
163
|
+
* is not a mirror of API state: it is a project ASSERTION that says which
|
|
164
|
+
* organisation this source tree deploys into. Two writers, both recording
|
|
165
|
+
* stated intent: `fjall create organisation` writes it at scaffold time (the
|
|
166
|
+
* organisation being created IS the command's subject — the project is born
|
|
167
|
+
* bound), and `fjall org bind` records or deliberately re-records it for
|
|
168
|
+
* projects that predate birth binding or genuinely move. Everything else
|
|
169
|
+
* about an organisation (accounts, regions, tiers) is mutable API state that
|
|
170
|
+
* a committed copy would misreport, which is why it stays out of this file.
|
|
168
171
|
*
|
|
169
172
|
* `id` is the only field ever compared. `name` is display-only, so renaming an
|
|
170
173
|
* organisation neither trips the guard nor dirties a checkout.
|
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 and
|
|
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
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};
|