@fjall/util 3.9.0 → 3.11.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/.minified CHANGED
@@ -1 +1 @@
1
- 89 files minified at 2026-07-20T22:26:24.729Z
1
+ 90 files minified at 2026-07-23T21:49:13.499Z
package/dist/config.d.ts CHANGED
@@ -214,6 +214,13 @@ export declare class Config {
214
214
  * included the real file's contents, so writing would clobber them.
215
215
  */
216
216
  private loadFailed;
217
+ /**
218
+ * True when the file's JSON parsed but failed even the tolerant
219
+ * RootConfigReadSchema (a recognised key such as `domains` is malformed),
220
+ * so the WHOLE config fell back to empty and any recorded domain pins
221
+ * vanished from memory.
222
+ */
223
+ private parseDegraded;
217
224
  /**
218
225
  * Top-level keys explicitly cleared this session (clearActiveTarget).
219
226
  * The disk-preserving merge in saveConfig would otherwise resurrect them
@@ -289,6 +296,16 @@ export declare class Config {
289
296
  * signal error paths branch on.
290
297
  */
291
298
  getConfigPath(): string | null;
299
+ /**
300
+ * True when a fjall-config.json EXISTS on disk but its contents are not
301
+ * represented in this instance: the file could not be read (loadFailed),
302
+ * or its recognised keys were malformed and the tolerant read fell back
303
+ * to empty (parseDegraded). Callers that enforce recorded truth - the
304
+ * deploy account pin - must fail closed in this state rather than treat
305
+ * a missing entry as the absence of a pin. False for a config that simply
306
+ * does not exist.
307
+ */
308
+ isContentUnavailable(): boolean;
292
309
  getActiveTarget(): string | undefined;
293
310
  setActiveTarget(name: string): void;
294
311
  clearActiveTarget(): void;
package/dist/config.js CHANGED
@@ -1 +1 @@
1
- var x=Object.defineProperty;var p=(l,e)=>x(l,"name",{value:e,configurable:!0});import*as s from"fs";import*as g from"path";import{z as f}from"zod";import{failure as y,success as O}from"./docker/result.js";import{getErrorMessage as S}from"./errorUtils.js";import{logger as T}from"./logger.js";import{maskSensitiveOutput as C}from"./securityHelpers.js";const $=10,w="fjall-config.json",U=["compliance","governance","none"],J=["enforced","off"],B=["centralised","off"],P=["account","draining","org"],G="managementEvents",W="organisationManagementEvents",Y=["active","draining","removed"],Z="FjallTrailBucketName",H="FjallTrailKeyArn",q="OrganisationTrailBucketName",A=f.object({name:f.string(),type:f.enum(["apex","delegated"]),parentDomain:f.string().optional(),account:f.string().optional(),region:f.string().optional()}).strict(),D=f.object({activeTarget:f.string().optional(),domains:f.array(A).optional()}).strict(),j=f.object({activeTarget:f.string().optional(),domains:f.array(A).optional()});function F(l={}){return JSON.stringify(l,null,2)}p(F,"serialiseRootConfig");const _=D.keyof().options;function N(l,e,n){const t=e[n];t!==void 0&&(l[n]=t)}p(N,"copyDefinedKey");function k(l,e){if(e===void 0)return!1;const n=new Set([...Object.keys(l),...Object.keys(e)]);for(const t of n)if(l[t]!==e[t])return!1;return!0}p(k,"domainEntriesEqual");class d{static{p(this,"Config")}rootConfig;configPath=null;loadFailed=!1;clearedKeys=new Set;loadedSnapshot={};constructor(e,n){this.rootConfig=e??{},this.configPath=n??null}static findConfigDirectory(e){let n=e!==void 0&&e!==""?e:process.cwd();for(let t=0;t<$;t++){const r=g.join(n,"fjall"),o=g.join(r,w);if(s.existsSync(o))return r;const i=g.join(n,w);if(s.existsSync(i))return n;const c=g.dirname(n);if(c===n)break;n=c}return null}static loadConfigFile(e){try{return s.accessSync(e,s.constants.R_OK),s.readFileSync(e,{encoding:"utf8"})}catch(n){return T.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:C(S(n))}),null}}static loadConfig(e){const n=d.findConfigDirectory(e);if(!n)return new d;const t=g.join(n,w),r=d.loadConfigFile(t);if(r===null){const c=new d(void 0,t);return c.loadFailed=!0,c}let o;if(r!==""){let c;try{c=JSON.parse(r)}catch(m){throw d.formatZodError(m,w)}const u=D.safeParse(c);if(u.success)o=u.data;else{const m=j.safeParse(c);o=m.success?m.data:{},T.warn("Config","fjall-config.json contains keys this version does not recognise; they were ignored (only activeTarget and domains are read). If this is an old config, regenerate it with `fjall create ...` or re-run `fjall connect`.",{file:t})}}const i=new d(o,t);return i.loadedSnapshot=structuredClone(i.rootConfig),i}static formatZodError(e,n){if(e instanceof f.ZodError&&e.issues.length>0){const o=e.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join("; ");return new Error(`Failed to parse ${n}: ${o}`)}const r=(e instanceof Error?e.message:String(e)).replace(/\n/g," ").substring(0,500);return new Error(`Failed to parse ${n}: ${r}`)}saveConfig(){let e=this.configPath;if(!e){const i=d.findConfigDirectory()||g.join(process.cwd(),"fjall");e=g.join(i,w)}if(this.loadFailed)return y(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 n=g.dirname(e);try{s.mkdirSync(n,{recursive:!0})}catch(i){return y(new Error(`Cannot create config directory ${n}: ${C(S(i))}`))}const t=d.assertWritable(e,n);if(!t.success)return t;const r=F(this.mergeWithDisk(e)),o=`${e}.tmp-${process.pid}`;try{s.writeFileSync(o,r,{mode:384}),s.renameSync(o,e)}catch(i){return y(new Error(`Failed to save ${e}: ${C(S(i))}`))}return O(void 0)}static assertWritable(e,n){if(s.existsSync(e))try{s.accessSync(e,s.constants.W_OK)}catch{return y(new Error(`Cannot save ${e}: the file is read-only. Make it writable (e.g. chmod u+w ${e}) and retry.`))}try{s.accessSync(n,s.constants.W_OK)}catch{return y(new Error(`Cannot save ${e}: the directory ${n} is not writable. Make it writable (e.g. chmod u+w ${n}) and retry.`))}return O(void 0)}mergeWithDisk(e){const n=d.readDiskConfigForMerge(e);if(n===void 0)return this.rootConfig;const t={...n.foreign,...n.known};for(const o of _)o!=="domains"&&this.sessionChangedKey(o)&&(this.rootConfig[o]===void 0?delete t[o]:N(t,this.rootConfig,o));const r=this.mergeDomains(n.known.domains);r!==void 0?t.domains=r:delete t.domains;for(const o of this.clearedKeys)delete t[o];return t}sessionChangedKey(e){return JSON.stringify(this.rootConfig[e])!==JSON.stringify(this.loadedSnapshot[e])}mergeDomains(e){const n=this.rootConfig.domains,t=this.loadedSnapshot.domains??[];if(n===void 0&&t.length===0)return e;const r=p(a=>a.toLowerCase(),"norm"),o=n??[],i=new Map(t.map(a=>[r(a.name),a])),c=new Map(o.map(a=>[r(a.name),a])),u=[],m=new Set;for(const a of e??[]){const h=r(a.name);m.add(h);const E=c.get(h),v=i.get(h);if(E!==void 0&&!k(E,v)){u.push(E);continue}E===void 0&&v!==void 0||u.push(a)}for(const a of o){const h=r(a.name);m.has(h)||k(a,i.get(h))||u.push(a)}return u}static readDiskConfigForMerge(e){if(!s.existsSync(e))return;let n;try{n=JSON.parse(s.readFileSync(e,{encoding:"utf8"}))}catch(o){T.warn("Config",`Could not re-read ${e} before saving; writing in-memory state without merging`,{file:e,error:C(S(o))});return}const t=j.safeParse(n);if(!t.success){T.warn("Config",`On-disk ${e} failed validation before saving; writing in-memory state without merging`,{file:e,error:C(t.error.message)});return}const r={};if(typeof n=="object"&&n!==null){const o=_;for(const[i,c]of Object.entries(n))o.includes(i)||(r[i]=c)}return{known:t.data,foreign:r}}static getConfigDirectory(e){return d.findConfigDirectory(e)}getConfigPath(){return this.configPath}getActiveTarget(){return this.rootConfig.activeTarget}setActiveTarget(e){this.rootConfig.activeTarget=e,this.clearedKeys.delete("activeTarget")}clearActiveTarget(){this.rootConfig.activeTarget=void 0,this.clearedKeys.add("activeTarget")}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(n=>n.name.toLowerCase()===e.toLowerCase())}removeDomain(e){if(!this.rootConfig.domains)return!1;const n=this.rootConfig.domains.findIndex(t=>t.name.toLowerCase()===e.toLowerCase());return n===-1?!1:(this.rootConfig.domains.splice(n,1),!0)}}export{G as ACCOUNT_TRAIL_NAME,Y as ACCOUNT_TRAIL_STATES,d as Config,A as DomainConfigSchema,W as ORGANISATION_TRAIL_NAME,q as ORG_TRAIL_BUCKET_OUTPUT_KEY,B as ROOT_ACCESS_MANAGEMENT_MODES,w as ROOT_CONFIG_FILENAME,j as RootConfigReadSchema,D as RootConfigSchema,J as S3_BPA_MODES,Z as TRAIL_BUCKET_OUTPUT_KEY,H as TRAIL_KEY_ARN_OUTPUT_KEY,P as TRAIL_LIFECYCLE_STATES,U as VAULT_LOCK_MODES,F as serialiseRootConfig};
1
+ var k=Object.defineProperty;var h=(g,e)=>k(g,"name",{value:e,configurable:!0});import*as a from"fs";import*as u from"path";import{z as c}from"zod";import{failure as y,success as O}from"./docker/result.js";import{getErrorMessage as T}from"./errorUtils.js";import{logger as C}from"./logger.js";import{maskSensitiveOutput as w}from"./securityHelpers.js";const F=10,E="fjall-config.json",U=["compliance","governance","none"],J=["enforced","off"],B=["centralised","off"],P=["account","draining","org"],G="managementEvents",W="organisationManagementEvents",Y=["active","draining","removed"],Z="FjallTrailBucketName",H="FjallTrailKeyArn",q="OrganisationTrailBucketName",D=c.object({name:c.string(),type:c.enum(["apex","delegated"]),parentDomain:c.string().optional(),account:c.string().optional(),region:c.string().optional()}).strict(),j=c.object({activeTarget:c.string().optional(),domains:c.array(D).optional()}).strict(),A=c.object({activeTarget:c.string().optional(),domains:c.array(D).optional()});function $(g={}){return JSON.stringify(g,null,2)}h($,"serialiseRootConfig");const x=j.keyof().options;function N(g,e,n){const t=e[n];t!==void 0&&(g[n]=t)}h(N,"copyDefinedKey");function _(g,e){if(e===void 0)return!1;const n=new Set([...Object.keys(g),...Object.keys(e)]);for(const t of n)if(g[t]!==e[t])return!1;return!0}h(_,"domainEntriesEqual");class l{static{h(this,"Config")}rootConfig;configPath=null;loadFailed=!1;parseDegraded=!1;clearedKeys=new Set;loadedSnapshot={};constructor(e,n){this.rootConfig=e??{},this.configPath=n??null}static findConfigDirectory(e){let n=e!==void 0&&e!==""?e:process.cwd();for(let t=0;t<F;t++){const r=u.join(n,"fjall"),o=u.join(r,E);if(a.existsSync(o))return r;const s=u.join(n,E);if(a.existsSync(s))return n;const f=u.dirname(n);if(f===n)break;n=f}return null}static loadConfigFile(e){try{return a.accessSync(e,a.constants.R_OK),a.readFileSync(e,{encoding:"utf8"})}catch(n){return C.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:w(T(n))}),null}}static loadConfig(e){const n=l.findConfigDirectory(e);if(!n)return new l;const t=u.join(n,E),r=l.loadConfigFile(t);if(r===null){const d=new l(void 0,t);return d.loadFailed=!0,d}let o,s=!1;if(r!==""){let d;try{d=JSON.parse(r)}catch(i){throw l.formatZodError(i,E)}const p=j.safeParse(d);if(p.success)o=p.data;else{const i=A.safeParse(d);o=i.success?i.data:{},s=!i.success,i.success?C.warn("Config","fjall-config.json contains keys this version does not recognise; they were ignored (only activeTarget and domains are read). If this is an old config, regenerate it with `fjall create ...` or re-run `fjall connect`.",{file:t}):C.warn("Config","fjall-config.json has a malformed activeTarget or domains value, so its contents were ignored for this run. Fix the file (or regenerate it with `fjall create ...` / `fjall connect`).",{file:t})}}const f=new l(o,t);return f.parseDegraded=s,f.loadedSnapshot=structuredClone(f.rootConfig),f}static formatZodError(e,n){if(e instanceof c.ZodError&&e.issues.length>0){const o=e.issues.map(s=>`${s.path.join(".")}: ${s.message}`).join("; ");return new Error(`Failed to parse ${n}: ${o}`)}const r=(e instanceof Error?e.message:String(e)).replace(/\n/g," ").substring(0,500);return new Error(`Failed to parse ${n}: ${r}`)}saveConfig(){let e=this.configPath;if(!e){const s=l.findConfigDirectory()||u.join(process.cwd(),"fjall");e=u.join(s,E)}if(this.loadFailed)return y(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 n=u.dirname(e);try{a.mkdirSync(n,{recursive:!0})}catch(s){return y(new Error(`Cannot create config directory ${n}: ${w(T(s))}`))}const t=l.assertWritable(e,n);if(!t.success)return t;const r=$(this.mergeWithDisk(e)),o=`${e}.tmp-${process.pid}`;try{a.writeFileSync(o,r,{mode:384}),a.renameSync(o,e)}catch(s){return y(new Error(`Failed to save ${e}: ${w(T(s))}`))}return O(void 0)}static assertWritable(e,n){if(a.existsSync(e))try{a.accessSync(e,a.constants.W_OK)}catch{return y(new Error(`Cannot save ${e}: the file is read-only. Make it writable (e.g. chmod u+w ${e}) and retry.`))}try{a.accessSync(n,a.constants.W_OK)}catch{return y(new Error(`Cannot save ${e}: the directory ${n} is not writable. Make it writable (e.g. chmod u+w ${n}) and retry.`))}return O(void 0)}mergeWithDisk(e){const n=l.readDiskConfigForMerge(e);if(n===void 0)return this.rootConfig;const t={...n.foreign,...n.known};for(const o of x)o!=="domains"&&this.sessionChangedKey(o)&&(this.rootConfig[o]===void 0?delete t[o]:N(t,this.rootConfig,o));const r=this.mergeDomains(n.known.domains);r!==void 0?t.domains=r:delete t.domains;for(const o of this.clearedKeys)delete t[o];return t}sessionChangedKey(e){return JSON.stringify(this.rootConfig[e])!==JSON.stringify(this.loadedSnapshot[e])}mergeDomains(e){const n=this.rootConfig.domains,t=this.loadedSnapshot.domains??[];if(n===void 0&&t.length===0)return e;const r=h(i=>i.toLowerCase(),"norm"),o=n??[],s=new Map(t.map(i=>[r(i.name),i])),f=new Map(o.map(i=>[r(i.name),i])),d=[],p=new Set;for(const i of e??[]){const m=r(i.name);p.add(m);const S=f.get(m),v=s.get(m);if(S!==void 0&&!_(S,v)){d.push(S);continue}S===void 0&&v!==void 0||d.push(i)}for(const i of o){const m=r(i.name);p.has(m)||_(i,s.get(m))||d.push(i)}return d}static readDiskConfigForMerge(e){if(!a.existsSync(e))return;let n;try{n=JSON.parse(a.readFileSync(e,{encoding:"utf8"}))}catch(o){C.warn("Config",`Could not re-read ${e} before saving; writing in-memory state without merging`,{file:e,error:w(T(o))});return}const t=A.safeParse(n);if(!t.success){C.warn("Config",`On-disk ${e} failed validation before saving; writing in-memory state without merging`,{file:e,error:w(t.error.message)});return}const r={};if(typeof n=="object"&&n!==null){const o=x;for(const[s,f]of Object.entries(n))o.includes(s)||(r[s]=f)}return{known:t.data,foreign:r}}static getConfigDirectory(e){return l.findConfigDirectory(e)}getConfigPath(){return this.configPath}isContentUnavailable(){return this.loadFailed||this.parseDegraded}getActiveTarget(){return this.rootConfig.activeTarget}setActiveTarget(e){this.rootConfig.activeTarget=e,this.clearedKeys.delete("activeTarget")}clearActiveTarget(){this.rootConfig.activeTarget=void 0,this.clearedKeys.add("activeTarget")}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(n=>n.name.toLowerCase()===e.toLowerCase())}removeDomain(e){if(!this.rootConfig.domains)return!1;const n=this.rootConfig.domains.findIndex(t=>t.name.toLowerCase()===e.toLowerCase());return n===-1?!1:(this.rootConfig.domains.splice(n,1),!0)}}export{G as ACCOUNT_TRAIL_NAME,Y as ACCOUNT_TRAIL_STATES,l as Config,D as DomainConfigSchema,W as ORGANISATION_TRAIL_NAME,q as ORG_TRAIL_BUCKET_OUTPUT_KEY,B as ROOT_ACCESS_MANAGEMENT_MODES,E as ROOT_CONFIG_FILENAME,A as RootConfigReadSchema,j as RootConfigSchema,J as S3_BPA_MODES,Z as TRAIL_BUCKET_OUTPUT_KEY,H as TRAIL_KEY_ARN_OUTPUT_KEY,P as TRAIL_LIFECYCLE_STATES,U as VAULT_LOCK_MODES,$ as serialiseRootConfig};
@@ -22,14 +22,27 @@ import { type BuildxBuildArgs, type BuildxBuildResult, type DockerCliError } fro
22
22
  import { type BuildxProgressEvent, type DockerCliState, type ImagetoolsInspect, type Result } from "./DockerCli.js";
23
23
  export declare function _buildxBuild(state: DockerCliState, args: BuildxBuildArgs, onProgress: (event: BuildxProgressEvent) => void, abortSignal?: AbortSignal): Promise<Result<BuildxBuildResult, DockerCliError>>;
24
24
  /**
25
- * Create one or more manifest-list tags pointing at an existing image
26
- * digest without re-uploading layers.
25
+ * Create one or more tags pointing at an existing image digest without
26
+ * re-uploading layers.
27
27
  *
28
- * Implementation: `docker buildx imagetools create --tag <tag1> --tag
29
- * <tag2> ... <sourceImage>@<digest>`. The source `image@digest` reference
30
- * pins the immutable content; each `--tag` argument creates (or moves) a
31
- * mutable name pointing at that digest. The registry stores no new blobs,
32
- * only a new manifest reference per tag.
28
+ * Implementation: `docker buildx imagetools create --prefer-index=false
29
+ * --tag <tag1> --tag <tag2> ... <sourceImage>@<digest>`. The source
30
+ * `image@digest` reference pins the immutable content; each `--tag`
31
+ * argument creates (or moves) a mutable name pointing at that digest. The
32
+ * registry stores no new blobs, only a new manifest reference per tag.
33
+ *
34
+ * `--prefer-index=false` is required: `imagetools create` defaults to
35
+ * wrapping even a SINGLE source manifest in a new image index (a "fat
36
+ * manifest") rather than carbon-copying it, on the assumption that its
37
+ * caller might be assembling a multi-arch tag from several
38
+ * platform-specific sources. fjall never does that — every call here
39
+ * re-tags exactly one already-built (single-platform) digest — so the
40
+ * index wrapper is pure overhead, and worse: AWS Lambda's
41
+ * CreateFunction/UpdateFunctionCode rejects an image index outright, so a
42
+ * Lambda pulling by this tag would fail even though the digest-addressed
43
+ * push (buildAndPush) produced a flat manifest. `--prefer-index=false`
44
+ * makes `imagetools create` copy the source manifest byte-for-byte under
45
+ * the new tag instead.
33
46
  *
34
47
  * Discipline:
35
48
  * - argv-only spawn (`shell: false` via `runDocker`)
@@ -1 +1 @@
1
- var A=Object.defineProperty;var g=(s,l)=>A(s,"name",{value:l,configurable:!0});import{rm as R}from"node:fs/promises";import{buildxArgvBuilder as j}from"./buildxArgvBuilder.js";import{BuildxBuildArgsSchema as N}from"./dockerCliSchemas.js";import{DEFAULT_INSPECT_TIMEOUT_MS as M,DOCKER_CLI_BUILDX_LOG_CATEGORY as C}from"./dockerCliConstants.js";import{BuildxBuildMonitor as U,buildPhaseTimeoutMessage as G,publishStallTimeoutMessage as J,resolveBuildxBudgets as P}from"./buildxBuildMonitor.js";import{parseMetadataFile as H}from"./metadataFileParser.js";import{parseRawjsonLine as K}from"./rawjsonParser.js";import{rawjsonToVertexEvent as V}from"./rawjsonToVertexEvent.js";import{maskSensitiveOutput as b}from"../securityHelpers.js";import{failure as t,makeError as a,maskOutput as T,runDocker as F,spawnFailureToError as W,streamDocker as X,success as _}from"./DockerCli.js";function O(s){return s instanceof Error?s.message:String(s)}g(O,"getErrorMessage");async function oe(s,l,e,u){const r=N.safeParse(l);if(!r.success){const n=b(r.error.message);return t(a("validation",`Invalid BuildxBuildArgs: ${n}`,{issues:r.error.issues}))}const o=P(s.env);if(!o.success)return t(a("validation",b(o.error.message)));const p=o.data,c=j(r.data);let f=0,k=0;const E=new Set;let h,v;try{const n=new U(p);h=n,v=r.data.metadataFile;const I=g(x=>{const S=K(x);if(S===null){x.trim()!==""&&s.logger.debug(C,"Skipping malformed rawjson line",{line:b(x)});return}const m=V(S);if(m===null)return;const w=n.observe(m);w!==void 0&&e({type:"status",message:T(w.message),...w.percentage!==void 0&&{percentage:w.percentage}});for(const i of m.vertexes)i.completed!==void 0&&!E.has(i.digest)&&(E.add(i.digest),i.cached===!0?f++:k++),e({type:"vertex",message:T(i.name),vertex:i.digest});for(const i of m.logs)e({type:"log",message:T(i.data),vertex:i.vertex});for(const i of m.statuses)e({type:"status",message:T(`${i.name} ${i.current}`),vertex:i.vertex});for(const i of m.warnings)e({type:"warning",message:T(i.short),vertex:i.vertex})},"onStdoutLine"),L=u!==void 0?AbortSignal.any([n.signal,u]):n.signal,d=await X(s,{args:c,abortSignal:L},I);if(d.spawnError!==void 0)return t(a("daemon_unreachable",`Docker CLI is not available: ${d.spawnError}`,{stderrTail:d.stderrTail}));if(d.aborted||d.exitCode===null)return n.timedOutPhase==="build"?t(a("timeout",G(p),{stderrTail:d.stderrTail})):n.timedOutPhase==="publish"?t(a("timeout",J(p,n.progressSummary()),{stderrTail:d.stderrTail})):t(a("abort","docker buildx build was aborted",{stderrTail:d.stderrTail}));if(d.exitCode!==0)return t(a("build_failed",`docker buildx build failed (exit ${d.exitCode})`,{stderrTail:d.stderrTail}));const B=await H(r.data.metadataFile);if(!B.success)return B;const $=B.data,y=$["containerimage.digest"];if(typeof y!="string"||y==="")return t(a("metadata_missing_digest","Buildx metadata file does not contain containerimage.digest",{metadataFile:r.data.metadataFile}));const D={};for(const x of r.data.tags)D[x]=y;return _({digest:y,imageDigests:D,metadata:$,platforms:r.data.platforms,cacheHits:f,cacheMisses:k})}finally{h!==void 0&&h.dispose(),v!==void 0&&await R(v,{force:!0}).catch(n=>{s.logger.warn(C,"Failed to clean up buildx metadata file",{path:v,error:b(O(n))})})}}g(oe,"_buildxBuild");async function ne(s,l,e,u){if(l.trim()==="")return t(a("validation","tagByDigest: sourceImage must be non-empty"));if(!e.startsWith("sha256:"))return t(a("validation",`tagByDigest: expected sha256:... digest, got ${e.slice(0,32)}`));if(u.length===0)return t(a("validation","tagByDigest: tags must be a non-empty list"));for(const f of u)if(typeof f!="string"||f.trim()==="")return t(a("validation","tagByDigest: every tag must be non-empty"));const r=`${l}@${e}`,o=[];for(const f of u)o.push("--tag",f);const p=["buildx","imagetools","create",...o,r],c=await F(s,{args:p,timeoutMs:M});return c.spawnError!==void 0?t(W(c)):c.exitCode===null?t(a("abort",`docker buildx imagetools create for ${r} was aborted`,{stderrTail:c.stderrTail})):c.exitCode!==0?t(a("tag_failed",`docker buildx imagetools create for ${r} failed (exit ${c.exitCode})`,{stderrTail:c.stderrTail})):_(void 0)}g(ne,"_tagByDigest");async function de(s,l){const e=await F(s,{args:["buildx","imagetools","inspect",l,"--raw"],timeoutMs:M});if(e.spawnError!==void 0)return t(a("daemon_unreachable",`Docker CLI is not available: ${e.spawnError}`,{stderrTail:e.stderrTail}));if(e.exitCode===null)return t(a("abort",`docker buildx imagetools inspect ${l} was aborted`,{stderrTail:e.stderrTail}));if(e.exitCode!==0)return t(a("inspect_failed",`docker buildx imagetools inspect ${l} failed (exit ${e.exitCode})`,{stderrTail:e.stderrTail}));let u,r;try{const o=JSON.parse(e.stdout);typeof o.mediaType=="string"&&(u=o.mediaType),typeof o.digest=="string"&&(r=o.digest)}catch(o){s.logger.debug(C,"imagetools inspect output is not JSON; preserving raw",{error:b(O(o))})}return _({raw:e.stdout,...u!==void 0&&{mediaType:u},...r!==void 0&&{digest:r}})}g(de,"_imagetoolsInspect");export{oe as _buildxBuild,de as _imagetoolsInspect,ne as _tagByDigest};
1
+ var A=Object.defineProperty;var g=(s,l)=>A(s,"name",{value:l,configurable:!0});import{rm as R}from"node:fs/promises";import{buildxArgvBuilder as j}from"./buildxArgvBuilder.js";import{BuildxBuildArgsSchema as N}from"./dockerCliSchemas.js";import{DEFAULT_INSPECT_TIMEOUT_MS as M,DOCKER_CLI_BUILDX_LOG_CATEGORY as C}from"./dockerCliConstants.js";import{BuildxBuildMonitor as U,buildPhaseTimeoutMessage as G,publishStallTimeoutMessage as J,resolveBuildxBudgets as P}from"./buildxBuildMonitor.js";import{parseMetadataFile as H}from"./metadataFileParser.js";import{parseRawjsonLine as K}from"./rawjsonParser.js";import{rawjsonToVertexEvent as V}from"./rawjsonToVertexEvent.js";import{maskSensitiveOutput as b}from"../securityHelpers.js";import{failure as t,makeError as a,maskOutput as T,runDocker as F,spawnFailureToError as W,streamDocker as X,success as _}from"./DockerCli.js";function O(s){return s instanceof Error?s.message:String(s)}g(O,"getErrorMessage");async function oe(s,l,e,u){const r=N.safeParse(l);if(!r.success){const n=b(r.error.message);return t(a("validation",`Invalid BuildxBuildArgs: ${n}`,{issues:r.error.issues}))}const o=P(s.env);if(!o.success)return t(a("validation",b(o.error.message)));const p=o.data,c=j(r.data);let f=0,k=0;const E=new Set;let h,v;try{const n=new U(p);h=n,v=r.data.metadataFile;const I=g(x=>{const S=K(x);if(S===null){x.trim()!==""&&s.logger.debug(C,"Skipping malformed rawjson line",{line:b(x)});return}const m=V(S);if(m===null)return;const w=n.observe(m);w!==void 0&&e({type:"status",message:T(w.message),...w.percentage!==void 0&&{percentage:w.percentage}});for(const i of m.vertexes)i.completed!==void 0&&!E.has(i.digest)&&(E.add(i.digest),i.cached===!0?f++:k++),e({type:"vertex",message:T(i.name),vertex:i.digest});for(const i of m.logs)e({type:"log",message:T(i.data),vertex:i.vertex});for(const i of m.statuses)e({type:"status",message:T(`${i.name} ${i.current}`),vertex:i.vertex});for(const i of m.warnings)e({type:"warning",message:T(i.short),vertex:i.vertex})},"onStdoutLine"),L=u!==void 0?AbortSignal.any([n.signal,u]):n.signal,d=await X(s,{args:c,abortSignal:L},I);if(d.spawnError!==void 0)return t(a("daemon_unreachable",`Docker CLI is not available: ${d.spawnError}`,{stderrTail:d.stderrTail}));if(d.aborted||d.exitCode===null)return n.timedOutPhase==="build"?t(a("timeout",G(p),{stderrTail:d.stderrTail})):n.timedOutPhase==="publish"?t(a("timeout",J(p,n.progressSummary()),{stderrTail:d.stderrTail})):t(a("abort","docker buildx build was aborted",{stderrTail:d.stderrTail}));if(d.exitCode!==0)return t(a("build_failed",`docker buildx build failed (exit ${d.exitCode})`,{stderrTail:d.stderrTail}));const B=await H(r.data.metadataFile);if(!B.success)return B;const $=B.data,y=$["containerimage.digest"];if(typeof y!="string"||y==="")return t(a("metadata_missing_digest","Buildx metadata file does not contain containerimage.digest",{metadataFile:r.data.metadataFile}));const D={};for(const x of r.data.tags)D[x]=y;return _({digest:y,imageDigests:D,metadata:$,platforms:r.data.platforms,cacheHits:f,cacheMisses:k})}finally{h!==void 0&&h.dispose(),v!==void 0&&await R(v,{force:!0}).catch(n=>{s.logger.warn(C,"Failed to clean up buildx metadata file",{path:v,error:b(O(n))})})}}g(oe,"_buildxBuild");async function ne(s,l,e,u){if(l.trim()==="")return t(a("validation","tagByDigest: sourceImage must be non-empty"));if(!e.startsWith("sha256:"))return t(a("validation",`tagByDigest: expected sha256:... digest, got ${e.slice(0,32)}`));if(u.length===0)return t(a("validation","tagByDigest: tags must be a non-empty list"));for(const f of u)if(typeof f!="string"||f.trim()==="")return t(a("validation","tagByDigest: every tag must be non-empty"));const r=`${l}@${e}`,o=[];for(const f of u)o.push("--tag",f);const p=["buildx","imagetools","create","--prefer-index=false",...o,r],c=await F(s,{args:p,timeoutMs:M});return c.spawnError!==void 0?t(W(c)):c.exitCode===null?t(a("abort",`docker buildx imagetools create for ${r} was aborted`,{stderrTail:c.stderrTail})):c.exitCode!==0?t(a("tag_failed",`docker buildx imagetools create for ${r} failed (exit ${c.exitCode})`,{stderrTail:c.stderrTail})):_(void 0)}g(ne,"_tagByDigest");async function de(s,l){const e=await F(s,{args:["buildx","imagetools","inspect",l,"--raw"],timeoutMs:M});if(e.spawnError!==void 0)return t(a("daemon_unreachable",`Docker CLI is not available: ${e.spawnError}`,{stderrTail:e.stderrTail}));if(e.exitCode===null)return t(a("abort",`docker buildx imagetools inspect ${l} was aborted`,{stderrTail:e.stderrTail}));if(e.exitCode!==0)return t(a("inspect_failed",`docker buildx imagetools inspect ${l} failed (exit ${e.exitCode})`,{stderrTail:e.stderrTail}));let u,r;try{const o=JSON.parse(e.stdout);typeof o.mediaType=="string"&&(u=o.mediaType),typeof o.digest=="string"&&(r=o.digest)}catch(o){s.logger.debug(C,"imagetools inspect output is not JSON; preserving raw",{error:b(O(o))})}return _({raw:e.stdout,...u!==void 0&&{mediaType:u},...r!==void 0&&{digest:r}})}g(de,"_imagetoolsInspect");export{oe as _buildxBuild,de as _imagetoolsInspect,ne as _tagByDigest};
@@ -97,9 +97,10 @@ export declare class DockerCli {
97
97
  buildxBuild(args: BuildxBuildArgs, onProgress: (event: BuildxProgressEvent) => void, abortSignal?: AbortSignal): Promise<Result<BuildxBuildResult, DockerCliError>>;
98
98
  tag(source: string, target: string): Promise<Result<void, DockerCliError>>;
99
99
  /**
100
- * Create one or more manifest-list tags pointing at a previously pushed
101
- * digest without re-uploading layers. Uses `docker buildx imagetools
102
- * create` against `<sourceImage>@<digest>`.
100
+ * Create one or more tags pointing at a previously pushed digest without
101
+ * re-uploading layers. Uses `docker buildx imagetools create
102
+ * --prefer-index=false` (a flat carbon-copy, not an image index — see
103
+ * `_tagByDigest`) against `<sourceImage>@<digest>`.
103
104
  */
104
105
  tagByDigest(sourceImage: string, digest: string, tags: readonly string[]): Promise<Result<void, DockerCliError>>;
105
106
  push(image: string, onProgress?: (event: PushProgressEvent) => void): Promise<Result<PushResult, DockerCliError>>;
@@ -0,0 +1,12 @@
1
+ import type { LambdaArchitecture } from "../manifest/schemas.js";
2
+ export declare function dockerPlatformForArchitecture(architecture: LambdaArchitecture): string;
3
+ /**
4
+ * The CPU architecture every fjall ECS task runs on. Coupled to the construct
5
+ * pin `runtimePlatform.cpuArchitecture: CpuArchitecture.ARM64` in
6
+ * `components/infrastructure/lib/resources/aws/compute/ecsTaskDefinition.ts`
7
+ * (both task-definition call sites) — ECS manifest entries carry no
8
+ * `architecture` field, so build orchestrators consume this constant to
9
+ * enumerate the implicit constraint when an ECS service shares a build group
10
+ * with an architecture-declaring Lambda.
11
+ */
12
+ export declare const ECS_TASK_ARCHITECTURE: LambdaArchitecture;
@@ -0,0 +1 @@
1
+ var n=Object.defineProperty;var t=(r,o)=>n(r,"name",{value:o,configurable:!0});const A={arm64:"linux/arm64",x86_64:"linux/amd64"};function _(r){return A[r]}t(_,"dockerPlatformForArchitecture");const c="arm64";export{c as ECS_TASK_ARCHITECTURE,_ as dockerPlatformForArchitecture};
@@ -2,6 +2,7 @@ export { type Result, isSuccess, isFailure, success, failure } from "./result.js
2
2
  export { DOCKER_CLI_LOG_CATEGORY, DOCKER_CLI_BUILDX_LOG_CATEGORY, BUILDX_VERSION_FLOOR, ENGINE_VERSION_FLOOR, PrerequisiteMissingExitCode, DEFAULT_BUILDER_NAME, DEFAULT_DOCKER_BIN, SIGTERM_GRACE_MS, STDERR_TAIL_LINES, DEFAULT_BUILD_TIMEOUT_MS, DEFAULT_PUSH_STALL_TIMEOUT_MS, DEFAULT_PUSH_TIMEOUT_MS, DEFAULT_PULL_TIMEOUT_MS, DEFAULT_INSPECT_TIMEOUT_MS, DEFAULT_DAEMON_PROBE_TIMEOUT_MS } from "./dockerCliConstants.js";
3
3
  export { BuildxBuildArgsSchema, BuildxBuildResultSchema, DockerCliErrorKindSchema, DockerCliErrorSchema, isDockerCliErrorKind, type BuildxBuildArgs, type BuildxBuildResult, type DockerCliError, type DockerCliErrorKind } from "./dockerCliSchemas.js";
4
4
  export { buildxArgvBuilder } from "./buildxArgvBuilder.js";
5
+ export { dockerPlatformForArchitecture, ECS_TASK_ARCHITECTURE } from "./architecture.js";
5
6
  export { BUILD_TIMEOUT_ENV_VAR, PUSH_STALL_TIMEOUT_ENV_VAR, BuildxBuildMonitor, buildPhaseTimeoutMessage, publishStallTimeoutMessage, resolveBuildxBudgets, type BuildxBudgets, type BuildxTimeoutPhase, type SyntheticBuildxProgress } from "./buildxBuildMonitor.js";
6
7
  export { evaluateBakeGuard, evaluateResolvedBuildArgValues, acknowledgedBuildArgKeys, createBakeWarningDeduper, BAKE_GUARD_EXPOSURE_CLAUSE, type BakeGuardFinding, type BakeGuardFindingReason, type BakeGuardWarning, type BakeGuardResult } from "./bakeGuard.js";
7
8
  export { PUBLIC_BUILD_ARG_PREFIXES, isPublicBuildVarName, inferPublicBuildArgKeys, type InferPublicBuildArgKeysInput } from "./buildArgInference.js";
@@ -1 +1 @@
1
- import{isSuccess as _,isFailure as E,success as o,failure as i}from"./result.js";import{DOCKER_CLI_LOG_CATEGORY as s,DOCKER_CLI_BUILDX_LOG_CATEGORY as t,BUILDX_VERSION_FLOOR as T,ENGINE_VERSION_FLOOR as u,PrerequisiteMissingExitCode as a,DEFAULT_BUILDER_NAME as A,DEFAULT_DOCKER_BIN as L,SIGTERM_GRACE_MS as U,STDERR_TAIL_LINES as d,DEFAULT_BUILD_TIMEOUT_MS as D,DEFAULT_PUSH_STALL_TIMEOUT_MS as R,DEFAULT_PUSH_TIMEOUT_MS as S,DEFAULT_PULL_TIMEOUT_MS as B,DEFAULT_INSPECT_TIMEOUT_MS as I,DEFAULT_DAEMON_PROBE_TIMEOUT_MS as O}from"./dockerCliConstants.js";import{BuildxBuildArgsSchema as C,BuildxBuildResultSchema as M,DockerCliErrorKindSchema as x,DockerCliErrorSchema as m,isDockerCliErrorKind as p}from"./dockerCliSchemas.js";import{buildxArgvBuilder as N}from"./buildxArgvBuilder.js";import{BUILD_TIMEOUT_ENV_VAR as g,PUSH_STALL_TIMEOUT_ENV_VAR as n,BuildxBuildMonitor as F,buildPhaseTimeoutMessage as G,publishStallTimeoutMessage as h,resolveBuildxBudgets as V}from"./buildxBuildMonitor.js";import{evaluateBakeGuard as K,evaluateResolvedBuildArgValues as k,acknowledgedBuildArgKeys as v,createBakeWarningDeduper as y,BAKE_GUARD_EXPOSURE_CLAUSE as H}from"./bakeGuard.js";import{PUBLIC_BUILD_ARG_PREFIXES as j,isPublicBuildVarName as w,inferPublicBuildArgKeys as Y}from"./buildArgInference.js";import{parseRawjsonLine as W}from"./rawjsonParser.js";import{rawjsonToVertexEvent as J}from"./rawjsonToVertexEvent.js";import{parseMetadataFile as Z}from"./metadataFileParser.js";import{projectBuildxResult as ee}from"./projectBuildxResult.js";import{abortChildProcess as _e}from"./abortHelpers.js";import{DockerCli as oe}from"./DockerCli.js";import{createEcrAuthSession as le}from"./ecrCredentialStore.js";import{buildCacheRepositoryName as te,buildRegistryCacheRefs as Te,resolveBuildCacheMode as ue,untaggedLifecyclePolicyText as ae,BUILD_CACHE_MODE_ENV_VAR as Ae,BUILD_CACHE_MODES as Le,CACHE_REPO_UNTAGGED_RETENTION_DAYS as Ue}from"./cacheRepository.js";export{H as BAKE_GUARD_EXPOSURE_CLAUSE,T as BUILDX_VERSION_FLOOR,Le as BUILD_CACHE_MODES,Ae as BUILD_CACHE_MODE_ENV_VAR,g as BUILD_TIMEOUT_ENV_VAR,C as BuildxBuildArgsSchema,F as BuildxBuildMonitor,M as BuildxBuildResultSchema,Ue as CACHE_REPO_UNTAGGED_RETENTION_DAYS,A as DEFAULT_BUILDER_NAME,D as DEFAULT_BUILD_TIMEOUT_MS,O as DEFAULT_DAEMON_PROBE_TIMEOUT_MS,L as DEFAULT_DOCKER_BIN,I as DEFAULT_INSPECT_TIMEOUT_MS,B as DEFAULT_PULL_TIMEOUT_MS,R as DEFAULT_PUSH_STALL_TIMEOUT_MS,S as DEFAULT_PUSH_TIMEOUT_MS,t as DOCKER_CLI_BUILDX_LOG_CATEGORY,s as DOCKER_CLI_LOG_CATEGORY,oe as DockerCli,x as DockerCliErrorKindSchema,m as DockerCliErrorSchema,u as ENGINE_VERSION_FLOOR,j as PUBLIC_BUILD_ARG_PREFIXES,n as PUSH_STALL_TIMEOUT_ENV_VAR,a as PrerequisiteMissingExitCode,U as SIGTERM_GRACE_MS,d as STDERR_TAIL_LINES,_e as abortChildProcess,v as acknowledgedBuildArgKeys,te as buildCacheRepositoryName,G as buildPhaseTimeoutMessage,Te as buildRegistryCacheRefs,N as buildxArgvBuilder,y as createBakeWarningDeduper,le as createEcrAuthSession,K as evaluateBakeGuard,k as evaluateResolvedBuildArgValues,i as failure,Y as inferPublicBuildArgKeys,p as isDockerCliErrorKind,E as isFailure,w as isPublicBuildVarName,_ as isSuccess,Z as parseMetadataFile,W as parseRawjsonLine,ee as projectBuildxResult,h as publishStallTimeoutMessage,J as rawjsonToVertexEvent,ue as resolveBuildCacheMode,V as resolveBuildxBudgets,o as success,ae as untaggedLifecyclePolicyText};
1
+ import{isSuccess as _,isFailure as o,success as E,failure as i}from"./result.js";import{DOCKER_CLI_LOG_CATEGORY as t,DOCKER_CLI_BUILDX_LOG_CATEGORY as T,BUILDX_VERSION_FLOOR as s,ENGINE_VERSION_FLOOR as u,PrerequisiteMissingExitCode as a,DEFAULT_BUILDER_NAME as A,DEFAULT_DOCKER_BIN as L,SIGTERM_GRACE_MS as U,STDERR_TAIL_LINES as R,DEFAULT_BUILD_TIMEOUT_MS as d,DEFAULT_PUSH_STALL_TIMEOUT_MS as S,DEFAULT_PUSH_TIMEOUT_MS as D,DEFAULT_PULL_TIMEOUT_MS as I,DEFAULT_INSPECT_TIMEOUT_MS as c,DEFAULT_DAEMON_PROBE_TIMEOUT_MS as B}from"./dockerCliConstants.js";import{BuildxBuildArgsSchema as O,BuildxBuildResultSchema as M,DockerCliErrorKindSchema as m,DockerCliErrorSchema as x,isDockerCliErrorKind as p}from"./dockerCliSchemas.js";import{buildxArgvBuilder as N}from"./buildxArgvBuilder.js";import{dockerPlatformForArchitecture as g,ECS_TASK_ARCHITECTURE as n}from"./architecture.js";import{BUILD_TIMEOUT_ENV_VAR as h,PUSH_STALL_TIMEOUT_ENV_VAR as G,BuildxBuildMonitor as V,buildPhaseTimeoutMessage as K,publishStallTimeoutMessage as b,resolveBuildxBudgets as k}from"./buildxBuildMonitor.js";import{evaluateBakeGuard as H,evaluateResolvedBuildArgValues as y,acknowledgedBuildArgKeys as X,createBakeWarningDeduper as j,BAKE_GUARD_EXPOSURE_CLAUSE as w}from"./bakeGuard.js";import{PUBLIC_BUILD_ARG_PREFIXES as q,isPublicBuildVarName as W,inferPublicBuildArgKeys as z}from"./buildArgInference.js";import{parseRawjsonLine as Q}from"./rawjsonParser.js";import{rawjsonToVertexEvent as $}from"./rawjsonToVertexEvent.js";import{parseMetadataFile as re}from"./metadataFileParser.js";import{projectBuildxResult as oe}from"./projectBuildxResult.js";import{abortChildProcess as ie}from"./abortHelpers.js";import{DockerCli as te}from"./DockerCli.js";import{createEcrAuthSession as se}from"./ecrCredentialStore.js";import{buildCacheRepositoryName as ae,buildRegistryCacheRefs as Ae,resolveBuildCacheMode as Le,untaggedLifecyclePolicyText as Ue,BUILD_CACHE_MODE_ENV_VAR as Re,BUILD_CACHE_MODES as de,CACHE_REPO_UNTAGGED_RETENTION_DAYS as Se}from"./cacheRepository.js";export{w as BAKE_GUARD_EXPOSURE_CLAUSE,s as BUILDX_VERSION_FLOOR,de as BUILD_CACHE_MODES,Re as BUILD_CACHE_MODE_ENV_VAR,h as BUILD_TIMEOUT_ENV_VAR,O as BuildxBuildArgsSchema,V as BuildxBuildMonitor,M as BuildxBuildResultSchema,Se as CACHE_REPO_UNTAGGED_RETENTION_DAYS,A as DEFAULT_BUILDER_NAME,d as DEFAULT_BUILD_TIMEOUT_MS,B as DEFAULT_DAEMON_PROBE_TIMEOUT_MS,L as DEFAULT_DOCKER_BIN,c as DEFAULT_INSPECT_TIMEOUT_MS,I as DEFAULT_PULL_TIMEOUT_MS,S as DEFAULT_PUSH_STALL_TIMEOUT_MS,D as DEFAULT_PUSH_TIMEOUT_MS,T as DOCKER_CLI_BUILDX_LOG_CATEGORY,t as DOCKER_CLI_LOG_CATEGORY,te as DockerCli,m as DockerCliErrorKindSchema,x as DockerCliErrorSchema,n as ECS_TASK_ARCHITECTURE,u as ENGINE_VERSION_FLOOR,q as PUBLIC_BUILD_ARG_PREFIXES,G as PUSH_STALL_TIMEOUT_ENV_VAR,a as PrerequisiteMissingExitCode,U as SIGTERM_GRACE_MS,R as STDERR_TAIL_LINES,ie as abortChildProcess,X as acknowledgedBuildArgKeys,ae as buildCacheRepositoryName,K as buildPhaseTimeoutMessage,Ae as buildRegistryCacheRefs,N as buildxArgvBuilder,j as createBakeWarningDeduper,se as createEcrAuthSession,g as dockerPlatformForArchitecture,H as evaluateBakeGuard,y as evaluateResolvedBuildArgValues,i as failure,z as inferPublicBuildArgKeys,p as isDockerCliErrorKind,o as isFailure,W as isPublicBuildVarName,_ as isSuccess,re as parseMetadataFile,Q as parseRawjsonLine,oe as projectBuildxResult,b as publishStallTimeoutMessage,$ as rawjsonToVertexEvent,Le as resolveBuildCacheMode,k as resolveBuildxBudgets,E as success,Ue as untaggedLifecyclePolicyText};
@@ -6,7 +6,7 @@
6
6
  * that need the docker-shape types without I/O (e.g. the generator) MUST
7
7
  * import from `./schemas` directly.
8
8
  */
9
- import { type DockerBuild, type FjallManifest, type ResourceMapEntry } from "./schemas.js";
9
+ import { type DockerBuild, type FjallManifest, type LambdaArchitecture, type ResourceMapEntry } from "./schemas.js";
10
10
  /**
11
11
  * Get the manifest file path for a cdk.out directory.
12
12
  */
@@ -49,6 +49,14 @@ export interface ManifestDockerService {
49
49
  * (`@fjall/deploy-core` dockerInterface.ts).
50
50
  */
51
51
  readonly isLambda?: boolean;
52
+ /**
53
+ * The Lambda's CPU architecture (absent for ECS services and for
54
+ * bring-your-own-image Lambdas). Lets the Docker build pipeline pick a
55
+ * `buildx --platform` matching the function's configured `Architectures`
56
+ * instead of always building the default arm64 — see
57
+ * `dockerPlatformForArchitecture` in `@fjall/util/docker`.
58
+ */
59
+ readonly architecture?: LambdaArchitecture;
52
60
  }
53
61
  /**
54
62
  * Extract services with a Docker build configuration from the Fjall manifest.
@@ -84,13 +92,14 @@ export declare function parseDockerServicesFromManifest(cdkOutPath: string): Man
84
92
  * contains no Lambda Docker entries. Lambdas without a `docker` block
85
93
  * (bring-your-own-image) are skipped, not an error.
86
94
  *
87
- * Throws deliberately (not a `Result`) on divergent shared-`imageKey` docker
88
- * configs even though `Result`-returning callers sit above it: the throw is a
89
- * config error surfaced before any build starts, and every production
90
- * boundary converts it — the CLI's `catch` in `runApplicationDeployment`
95
+ * Throws deliberately (not a `Result`) on entries sharing an `imageKey` with
96
+ * divergent docker configs or `architecture` even though `Result`-returning
97
+ * callers sit above it: the throw is a config error surfaced before any build
98
+ * starts, and every production boundary converts it — the CLI's `catch` in
99
+ * `deployApplication`
91
100
  * (`cli/src/services/deployment/applicationDeployment.ts`), the webapp
92
101
  * worker's job-level `catch` in `deploymentJobHandler`, and the `fjall build`
93
- * orchestrator's call-site `catch` in `resolveLambdaBuildIdentityKeys`
102
+ * orchestrator's call-site `catch` in `resolveLambdaBuildIdentities`
94
103
  * (`cli/src/services/container/EcrBuildOrchestrator.ts`). Sanctioned per the
95
104
  * 2026-07-16 docker-lambda review; do not re-litigate as a Pitfall-4 escape.
96
105
  */
@@ -102,6 +111,7 @@ export interface ManifestLambdaDockerEntry {
102
111
  /** Shared build/tag key — `imageKey` if set, else `name`. */
103
112
  readonly imageKey: string;
104
113
  readonly docker: DockerBuild;
114
+ readonly architecture?: LambdaArchitecture;
105
115
  }
106
116
  /**
107
117
  * Extract every container-Lambda manifest entry that declared a `docker`
@@ -1 +1 @@
1
- var y=Object.defineProperty;var i=(t,e)=>y(t,"name",{value:e,configurable:!0});import{readFile as M,writeFile as h,unlink as b,rename as F,mkdir as k}from"fs/promises";import{readFileSync as w}from"fs";import{dirname as S,join as f}from"path";import{logger as s}from"../logger.js";import{fileExists as A}from"../fsHelpers.js";import{getErrorMessage as u}from"../errorUtils.js";import{recordToConstructMap as x}from"../constructMap.js";import{FjallManifestSchema as E,FJALL_MANIFEST_FILENAME as d,MANIFEST_SCHEMA_VERSION as v,normaliseDockerBuild as m}from"./schemas.js";function l(t){return f(t,d)}i(l,"getManifestFilePath");async function D(t){const e=l(t);if(!await A(e))return null;try{const n=await M(e,"utf-8"),r=JSON.parse(n),a=E.safeParse(r);return a.success||s.debug("FjallManifest","Manifest validation failed",{path:e,errors:a.error.issues.map(o=>`${o.path.join(".")}: ${o.message}`)}),a.success?a.data:null}catch(n){return s.debug("FjallManifest","Failed to read manifest file",{path:e,error:u(n)}),null}}i(D,"readManifestFile");async function _(t,e){const n=l(t),r=`${n}.${Date.now()}.tmp`;await k(S(n),{recursive:!0});try{await h(r,JSON.stringify(e,null,2),"utf-8"),await F(r,n)}catch(a){try{await b(r)}catch(o){s.debug("FjallManifest","Temp file cleanup failed (non-fatal)",{path:r,error:u(o)})}throw a}}i(_,"writeManifestFile");function C(t){return{version:v,generatedAt:new Date().toISOString(),appName:t,services:[],lambdas:[],stacks:{}}}i(C,"createEmptyManifest");async function R(t){const e=await D(t);return e?.resourceMap?x(e.resourceMap):new Map}i(R,"readConstructMap");function c(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}i(c,"isRecord");function p(t,e){const n=f(t,d);let r;try{r=w(n,"utf-8")}catch{s.debug("FjallManifest",`Manifest file not readable \u2014 no ${e} extracted`,{path:n});return}let a;try{a=JSON.parse(r)}catch{s.debug("FjallManifest",`Manifest is not valid JSON \u2014 no ${e} extracted`,{path:n});return}return c(a)?a:void 0}i(p,"readManifestRecord");function L(t){const e=p(t,"Docker services");if(e===void 0||!Array.isArray(e.services))return[];const n=[];for(const r of e.services){if(!c(r)||typeof r.name!="string")continue;const a=m(r.docker);a!==void 0&&n.push({name:r.name,docker:a,isLambda:!1})}return n}i(L,"parseDockerServicesFromManifest");function B(t){const e=new Map;for(const{name:n,imageKey:r,docker:a}of g(t)){const o=e.get(r);if(o===void 0){e.set(r,{name:r,docker:a,isLambda:!0});continue}if(JSON.stringify(o.docker)!==JSON.stringify(a))throw new Error(`Lambda functions sharing image key "${r}" declare different \`docker\` configs \u2014 every Lambda function sharing an \`image\` must build from the identical Dockerfile/context/target/buildArgs (offending function: "${n}").`)}return Array.from(e.values())}i(B,"parseLambdaDockerServicesFromManifest");function g(t){const e=p(t,"Lambda Docker entries");if(e===void 0||!Array.isArray(e.lambdas))return[];const n=[];for(const r of e.lambdas){if(!c(r)||typeof r.name!="string")continue;const a=m(r.docker);if(a===void 0)continue;const o=typeof r.imageKey=="string"&&r.imageKey.length>0?r.imageKey:r.name;n.push({name:r.name,imageKey:o,docker:a})}return n}i(g,"parseLambdaDockerEntriesFromManifest");function H(t){const e=L(t),n=g(t);return{ecsServices:e,lambdaEntries:n,declaresBuild:e.length>0||n.length>0}}i(H,"parseDockerDeclarationsFromManifest");export{C as createEmptyManifest,l as getManifestFilePath,H as parseDockerDeclarationsFromManifest,L as parseDockerServicesFromManifest,g as parseLambdaDockerEntriesFromManifest,B as parseLambdaDockerServicesFromManifest,R as readConstructMap,D as readManifestFile,_ as writeManifestFile};
1
+ var y=Object.defineProperty;var i=(t,r)=>y(t,"name",{value:r,configurable:!0});import{readFile as M,writeFile as b,unlink as F,rename as k,mkdir as S}from"fs/promises";import{readFileSync as A}from"fs";import{dirname as w,join as u}from"path";import{logger as c}from"../logger.js";import{maskSensitiveOutput as E}from"../securityHelpers.js";import{fileExists as v}from"../fsHelpers.js";import{getErrorMessage as d}from"../errorUtils.js";import{recordToConstructMap as x}from"../constructMap.js";import{FjallManifestSchema as L,FJALL_MANIFEST_FILENAME as m,MANIFEST_SCHEMA_VERSION as D,normaliseDockerBuild as l,LAMBDA_ARCHITECTURE_VALUES as j}from"./schemas.js";function p(t){return u(t,m)}i(p,"getManifestFilePath");async function N(t){const r=p(t);if(!await v(r))return null;try{const n=await M(r,"utf-8"),e=JSON.parse(n),a=L.safeParse(e);return a.success||c.debug("FjallManifest","Manifest validation failed",{path:r,errors:a.error.issues.map(o=>`${o.path.join(".")}: ${o.message}`)}),a.success?a.data:null}catch(n){return c.debug("FjallManifest","Failed to read manifest file",{path:r,error:d(n)}),null}}i(N,"readManifestFile");async function H(t,r){const n=p(t),e=`${n}.${Date.now()}.tmp`;await S(w(n),{recursive:!0});try{await b(e,JSON.stringify(r,null,2),"utf-8"),await k(e,n)}catch(a){try{await F(e)}catch(o){c.debug("FjallManifest","Temp file cleanup failed (non-fatal)",{path:e,error:d(o)})}throw a}}i(H,"writeManifestFile");function U(t){return{version:D,generatedAt:new Date().toISOString(),appName:t,services:[],lambdas:[],stacks:{}}}i(U,"createEmptyManifest");async function V(t){const r=await N(t);return r?.resourceMap?x(r.resourceMap):new Map}i(V,"readConstructMap");function f(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}i(f,"isRecord");function g(t,r){const n=u(t,m);let e;try{e=A(n,"utf-8")}catch{c.debug("FjallManifest",`Manifest file not readable \u2014 no ${r} extracted`,{path:n});return}let a;try{a=JSON.parse(e)}catch{c.debug("FjallManifest",`Manifest is not valid JSON \u2014 no ${r} extracted`,{path:n});return}return f(a)?a:void 0}i(g,"readManifestRecord");function I(t){const r=g(t,"Docker services");if(r===void 0||!Array.isArray(r.services))return[];const n=[];for(const e of r.services){if(!f(e)||typeof e.name!="string")continue;const a=l(e.docker);a!==void 0&&n.push({name:e.name,docker:a,isLambda:!1})}return n}i(I,"parseDockerServicesFromManifest");function q(t){const r=new Map;for(const{name:n,imageKey:e,docker:a,architecture:o}of h(t)){const s=r.get(e);if(s===void 0){r.set(e,{name:e,docker:a,isLambda:!0,...o!==void 0&&{architecture:o}});continue}if(JSON.stringify(s.docker)!==JSON.stringify(a)||s.architecture!==o)throw new Error(`Lambda functions sharing image key "${e}" declare different \`docker\` configs or \`architecture\` \u2014 every Lambda function sharing an \`image\` must build from the identical Dockerfile/context/target/buildArgs and target the same architecture (offending function: "${n}").`)}return Array.from(r.values())}i(q,"parseLambdaDockerServicesFromManifest");function h(t){const r=g(t,"Lambda Docker entries");if(r===void 0||!Array.isArray(r.lambdas))return[];const n=[];for(const e of r.lambdas){if(!f(e)||typeof e.name!="string")continue;const a=l(e.docker);if(a===void 0)continue;const o=typeof e.imageKey=="string"&&e.imageKey.length>0?e.imageKey:e.name;let s;typeof e.architecture=="string"&&(j.includes(e.architecture)?s=e.architecture:c.debug("FjallManifest","Ignoring unrecognised Lambda architecture \u2014 build falls back to the default platform",{lambda:e.name,architecture:E(e.architecture)})),n.push({name:e.name,imageKey:o,docker:a,...s!==void 0&&{architecture:s}})}return n}i(h,"parseLambdaDockerEntriesFromManifest");function z(t){const r=I(t),n=h(t);return{ecsServices:r,lambdaEntries:n,declaresBuild:r.length>0||n.length>0}}i(z,"parseDockerDeclarationsFromManifest");export{U as createEmptyManifest,p as getManifestFilePath,z as parseDockerDeclarationsFromManifest,I as parseDockerServicesFromManifest,h as parseLambdaDockerEntriesFromManifest,q as parseLambdaDockerServicesFromManifest,V as readConstructMap,N as readManifestFile,H as writeManifestFile};
@@ -253,6 +253,15 @@ declare const ManifestEcrSchema: z.ZodObject<{
253
253
  repositoryName: z.ZodString;
254
254
  }, z.core.$strict>;
255
255
  export type ManifestEcr = z.infer<typeof ManifestEcrSchema>;
256
+ /**
257
+ * CPU architecture values a container Lambda's `architecture` prop can take,
258
+ * mirrored from CDK's `Architecture.ARM_64.name` / `Architecture.X86_64.name`
259
+ * so the manifest never depends on aws-cdk-lib. Consumed by the Docker build
260
+ * pipeline (`dockerPlatformForArchitecture`, `@fjall/util/docker`) to pick the
261
+ * `buildx --platform` that matches the Lambda's own CPU architecture.
262
+ */
263
+ export declare const LAMBDA_ARCHITECTURE_VALUES: readonly ["x86_64", "arm64"];
264
+ export type LambdaArchitecture = (typeof LAMBDA_ARCHITECTURE_VALUES)[number];
256
265
  declare const ManifestLambdaSchema: z.ZodObject<{
257
266
  name: z.ZodString;
258
267
  secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -284,6 +293,10 @@ declare const ManifestLambdaSchema: z.ZodObject<{
284
293
  }, z.core.$strict>>>;
285
294
  }, z.core.$strict>>;
286
295
  imageKey: z.ZodOptional<z.ZodString>;
296
+ architecture: z.ZodOptional<z.ZodEnum<{
297
+ x86_64: "x86_64";
298
+ arm64: "arm64";
299
+ }>>;
287
300
  }, z.core.$strict>;
288
301
  export type ManifestLambda = z.infer<typeof ManifestLambdaSchema>;
289
302
  declare const ManifestStackHashSchema: z.ZodObject<{
@@ -375,6 +388,10 @@ export declare const FjallManifestSchema: z.ZodObject<{
375
388
  }, z.core.$strict>>>;
376
389
  }, z.core.$strict>>;
377
390
  imageKey: z.ZodOptional<z.ZodString>;
391
+ architecture: z.ZodOptional<z.ZodEnum<{
392
+ x86_64: "x86_64";
393
+ arm64: "arm64";
394
+ }>>;
378
395
  }, z.core.$strict>>;
379
396
  pattern: z.ZodOptional<z.ZodObject<{
380
397
  type: z.ZodEnum<{
@@ -1 +1 @@
1
- var y=Object.defineProperty;var i=(t,n)=>y(t,"name",{value:n,configurable:!0});import{z as e}from"zod";import{PATTERN_TYPE_VALUES as S}from"../patterns/patternTypes.js";const T="fjall-manifest.json",h=1,x=/^[A-Za-z0-9_.-]+$/,A=e.object({id:e.string().min(1,"buildSecret id cannot be empty").regex(x,"buildSecret id may contain only letters, digits, '_', '.', and '-' (no comma, '=', or whitespace, which would break the buildx --secret argv)"),ssm:e.string().min(1,"ssm parameter name cannot be empty").optional(),secretsManager:e.object({name:e.string().min(1,"secret name cannot be empty").optional(),arn:e.string().min(1,"secret arn cannot be empty").optional(),field:e.string().min(1,"secret field cannot be empty").optional()}).strict().refine(t=>t.name===void 0!=(t.arn===void 0),"secretsManager requires exactly one of name or arn").optional(),env:e.string().min(1,"env variable name cannot be empty").optional()}).strict().superRefine((t,n)=>{[t.ssm,t.secretsManager,t.env].filter(r=>r!==void 0).length!==1&&n.addIssue({code:e.ZodIssueCode.custom,message:"buildSecret requires exactly one source (ssm, secretsManager, or env)"})}),M=e.union([e.string(),e.object({ssm:e.string().min(1,"ssm parameter name cannot be empty").optional(),secretsManager:e.object({name:e.string().min(1,"secret name cannot be empty").optional(),arn:e.string().min(1,"secret arn cannot be empty").optional(),field:e.string().min(1,"secret field cannot be empty").optional()}).strict().refine(t=>t.name===void 0!=(t.arn===void 0),"secretsManager requires exactly one of name or arn").optional(),env:e.string().min(1,"env variable name cannot be empty").optional(),acknowledgePublic:e.boolean().optional()}).strict().superRefine((t,n)=>{[t.ssm,t.secretsManager,t.env].filter(r=>r!==void 0).length!==1&&n.addIssue({code:e.ZodIssueCode.custom,message:"buildArg value requires exactly one source (ssm, secretsManager, or env)"})})]),c=e.object({path:e.string(),context:e.string().min(1,"context cannot be empty").optional(),target:e.string().min(1,"target cannot be empty").optional(),buildArgs:e.record(e.string(),M).optional(),buildSecrets:e.array(A).optional()}).strict(),I=c.partial();function P(t,n){if(n===void 0)return t;const s=n.context??t.context,r=n.target??t.target,o=n.buildArgs??t.buildArgs,a=n.buildSecrets??t.buildSecrets;return{path:n.path??t.path,...s!==void 0&&{context:s},...r!==void 0&&{target:r},...o!==void 0&&{buildArgs:o},...a!==void 0&&{buildSecrets:a}}}i(P,"mergeDockerBuild");function m(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}i(m,"isRecord");function j(t){if(!m(t)||typeof t.path!="string"||t.path.length===0)return;const n=typeof t.context=="string"&&t.context.length>0?t.context:void 0,s=typeof t.target=="string"&&t.target.length>0?t.target:void 0,r=m(t.buildArgs)&&Object.keys(t.buildArgs).length>0?t.buildArgs:void 0,o=Array.isArray(t.buildSecrets)&&t.buildSecrets.length>0?t.buildSecrets:void 0,a={path:t.path,...n!==void 0&&{context:n},...s!==void 0&&{target:s},...r!==void 0&&{buildArgs:r},...o!==void 0&&{buildSecrets:o}},d=c.safeParse(a);return d.success?d.data:void 0}i(j,"normaliseDockerBuild");function _(t){if(t===void 0)return;const n=j(t);return n===void 0?void 0:JSON.stringify(n)}i(_,"dockerBuildFingerprint");const p=e.object({name:e.string(),clusterName:e.string().optional(),docker:c.optional(),containerPort:e.number().optional(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional()}).strict(),g=e.object({type:e.enum(S),name:e.string(),source:e.string()}).strict(),l=e.object({repositoryName:e.string()}).strict(),u=e.object({name:e.string(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional(),docker:c.optional(),imageKey:e.string().optional()}).strict(),f=e.object({templateHash:e.string(),synthTimestamp:e.string()}).strict(),b=e.object({constructPath:e.string().max(512),group:e.string().max(128),resourceType:e.string().max(256)}).strict(),B=e.object({version:e.literal(h),generatedAt:e.string(),appName:e.string(),services:e.array(p),lambdas:e.array(u),pattern:g.optional(),ecr:l.optional(),stacks:e.record(e.string(),f),resourceMap:e.record(e.string(),b).optional()}).strict();export{x as BUILDKIT_SECRET_ID_PATTERN,M as DockerBuildArgValueSchema,I as DockerBuildPartialSchema,c as DockerBuildSchema,A as DockerBuildSecretRefSchema,T as FJALL_MANIFEST_FILENAME,B as FjallManifestSchema,h as MANIFEST_SCHEMA_VERSION,l as ManifestEcrSchema,u as ManifestLambdaSchema,g as ManifestPatternSchema,p as ManifestServiceSchema,f as ManifestStackHashSchema,b as ResourceMapEntrySchema,_ as dockerBuildFingerprint,P as mergeDockerBuild,j as normaliseDockerBuild};
1
+ var y=Object.defineProperty;var i=(t,n)=>y(t,"name",{value:n,configurable:!0});import{z as e}from"zod";import{PATTERN_TYPE_VALUES as S}from"../patterns/patternTypes.js";const I="fjall-manifest.json",h=1,x=/^[A-Za-z0-9_.-]+$/,A=e.object({id:e.string().min(1,"buildSecret id cannot be empty").regex(x,"buildSecret id may contain only letters, digits, '_', '.', and '-' (no comma, '=', or whitespace, which would break the buildx --secret argv)"),ssm:e.string().min(1,"ssm parameter name cannot be empty").optional(),secretsManager:e.object({name:e.string().min(1,"secret name cannot be empty").optional(),arn:e.string().min(1,"secret arn cannot be empty").optional(),field:e.string().min(1,"secret field cannot be empty").optional()}).strict().refine(t=>t.name===void 0!=(t.arn===void 0),"secretsManager requires exactly one of name or arn").optional(),env:e.string().min(1,"env variable name cannot be empty").optional()}).strict().superRefine((t,n)=>{[t.ssm,t.secretsManager,t.env].filter(r=>r!==void 0).length!==1&&n.addIssue({code:e.ZodIssueCode.custom,message:"buildSecret requires exactly one source (ssm, secretsManager, or env)"})}),M=e.union([e.string(),e.object({ssm:e.string().min(1,"ssm parameter name cannot be empty").optional(),secretsManager:e.object({name:e.string().min(1,"secret name cannot be empty").optional(),arn:e.string().min(1,"secret arn cannot be empty").optional(),field:e.string().min(1,"secret field cannot be empty").optional()}).strict().refine(t=>t.name===void 0!=(t.arn===void 0),"secretsManager requires exactly one of name or arn").optional(),env:e.string().min(1,"env variable name cannot be empty").optional(),acknowledgePublic:e.boolean().optional()}).strict().superRefine((t,n)=>{[t.ssm,t.secretsManager,t.env].filter(r=>r!==void 0).length!==1&&n.addIssue({code:e.ZodIssueCode.custom,message:"buildArg value requires exactly one source (ssm, secretsManager, or env)"})})]),c=e.object({path:e.string(),context:e.string().min(1,"context cannot be empty").optional(),target:e.string().min(1,"target cannot be empty").optional(),buildArgs:e.record(e.string(),M).optional(),buildSecrets:e.array(A).optional()}).strict(),N=c.partial();function B(t,n){if(n===void 0)return t;const o=n.context??t.context,r=n.target??t.target,s=n.buildArgs??t.buildArgs,a=n.buildSecrets??t.buildSecrets;return{path:n.path??t.path,...o!==void 0&&{context:o},...r!==void 0&&{target:r},...s!==void 0&&{buildArgs:s},...a!==void 0&&{buildSecrets:a}}}i(B,"mergeDockerBuild");function m(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}i(m,"isRecord");function j(t){if(!m(t)||typeof t.path!="string"||t.path.length===0)return;const n=typeof t.context=="string"&&t.context.length>0?t.context:void 0,o=typeof t.target=="string"&&t.target.length>0?t.target:void 0,r=m(t.buildArgs)&&Object.keys(t.buildArgs).length>0?t.buildArgs:void 0,s=Array.isArray(t.buildSecrets)&&t.buildSecrets.length>0?t.buildSecrets:void 0,a={path:t.path,...n!==void 0&&{context:n},...o!==void 0&&{target:o},...r!==void 0&&{buildArgs:r},...s!==void 0&&{buildSecrets:s}},d=c.safeParse(a);return d.success?d.data:void 0}i(j,"normaliseDockerBuild");function P(t){if(t===void 0)return;const n=j(t);return n===void 0?void 0:JSON.stringify(n)}i(P,"dockerBuildFingerprint");const p=e.object({name:e.string(),clusterName:e.string().optional(),docker:c.optional(),containerPort:e.number().optional(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional()}).strict(),g=e.object({type:e.enum(S),name:e.string(),source:e.string()}).strict(),l=e.object({repositoryName:e.string()}).strict(),E=["x86_64","arm64"],u=e.object({name:e.string(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional(),docker:c.optional(),imageKey:e.string().optional(),architecture:e.enum(E).optional()}).strict(),f=e.object({templateHash:e.string(),synthTimestamp:e.string()}).strict(),b=e.object({constructPath:e.string().max(512),group:e.string().max(128),resourceType:e.string().max(256)}).strict(),R=e.object({version:e.literal(h),generatedAt:e.string(),appName:e.string(),services:e.array(p),lambdas:e.array(u),pattern:g.optional(),ecr:l.optional(),stacks:e.record(e.string(),f),resourceMap:e.record(e.string(),b).optional()}).strict();export{x as BUILDKIT_SECRET_ID_PATTERN,M as DockerBuildArgValueSchema,N as DockerBuildPartialSchema,c as DockerBuildSchema,A as DockerBuildSecretRefSchema,I as FJALL_MANIFEST_FILENAME,R as FjallManifestSchema,E as LAMBDA_ARCHITECTURE_VALUES,h as MANIFEST_SCHEMA_VERSION,l as ManifestEcrSchema,u as ManifestLambdaSchema,g as ManifestPatternSchema,p as ManifestServiceSchema,f as ManifestStackHashSchema,b as ResourceMapEntrySchema,P as dockerBuildFingerprint,B as mergeDockerBuild,j as normaliseDockerBuild};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "3.9.0",
3
+ "version": "3.11.0",
4
4
  "description": "Common utility methods",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -134,5 +134,5 @@
134
134
  "engines": {
135
135
  "node": ">=22.0.0"
136
136
  },
137
- "gitHead": "7cef1c5c3c09bb333888e65ede3d92b2d88c3c63"
137
+ "gitHead": "a15125ea026d52428ff5433d098699899695ca3f"
138
138
  }