@fjall/deploy-core 2.22.1 → 2.23.1
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
|
-
174 files minified at 2026-
|
|
1
|
+
174 files minified at 2026-07-02T12:17:52.811Z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isAbsolute as _,join as B,dirname as j,resolve as $}from"node:path";import{existsSync as x}from"node:fs";import{success as
|
|
1
|
+
import{isAbsolute as _,join as B,dirname as j,resolve as $}from"node:path";import{existsSync as x}from"node:fs";import{success as k,failure as l}from"@fjall/generator";import{deriveContentHashTag as F,maskSensitiveOutput as f,toKebab as I}from"@fjall/util";import{evaluateBakeGuard as U,evaluateResolvedBuildArgValues as G,acknowledgedBuildArgKeys as H,BAKE_GUARD_EXPOSURE_CLAUSE as V}from"@fjall/util/docker";import{logger as C}from"@fjall/util/logger";import{parseDockerServicesFromManifest as M}from"@fjall/util/manifest";import{STEP_IDS as y}from"../../types/stepDefinitions.js";import{resolveBuildSecrets as W,resolveSecretRefValue as z,resolveBuildSecretSessionProvider as X,sourcedRefsFromBuildArgs as Y}from"./buildSecretResolver.js";import{partitionForRegion as q}from"./buildSecretSession.js";import{resolveBuildArgs as J}from"./buildArgResolver.js";import{validateBuildGroup as Q}from"./buildGroupValidator.js";const P="dockerBuildHelper",E="Building and pushing Docker image";function Z(e,s,t){const r=B(s,e);if(x(r))return{path:r,base:s};if(t!==""&&t!==s){const o=B(t,e);if(x(o))return{path:o,base:t}}return{path:r,base:s}}function ee(e,s,t){const r=e.docker.path,o=e.docker.context;let a,i;if(_(r))a=r,i=s;else{const g=Z(r,s,t);a=g.path,i=g.base}return{buildContext:o?_(o)?$(o):$(i,o):$(j(a)),dockerfilePath:a,target:e.docker.target,resolvedViaFallback:i!==s}}function re(e){return`${e.buildContext}|${e.dockerfilePath}|${e.target??""}`}function te(e,s){return`Dockerfile not found for service '${e}' at ${s}. Checked the synth directory and the working directory. If your Dockerfile lives at the project root with the CDK app in a subdirectory, set an absolute docker.context (the build context must share the Dockerfile's directory).`}function ne(e){return e.startsWith("cn-")?"amazonaws.com.cn":"amazonaws.com"}function oe(e,s,t){return`${e}.dkr.ecr.${s}.${ne(s)}/${I(t)}`}function ie(e,s,t){const r=new Map;for(const o of e){const a=ee(o,s,t),i=re(a),m=r.get(i);m?m.members.push(o):r.set(i,{identity:a,members:[o]})}return Array.from(r.values())}async function se(e,s,t,r,o){const a=await e.buildAndPush({appName:s.appName,appPath:s.path,region:t,accountId:r},(i,m,g,v)=>{o.onDockerProgress?.(f(i),m,g,v)});return a.success?k({imageUri:a.data.imageUri,imageTag:a.data.imageTag}):l(a.error)}async function ae(e,s,t,r,o,a,i,m,g,v,p,h){const u=e.members[0];if(!x(e.identity.dockerfilePath))return l(new Error(te(u.name,e.identity.dockerfilePath)));e.identity.resolvedViaFallback&&i.onLog?.(`Building service '${u.name}' from ${e.identity.dockerfilePath} (resolved at the project root).`,"info");const n=[...u.docker.buildSecrets??[],...h??[]],c=Q({serviceName:u.name,dockerfilePath:e.identity.dockerfilePath,buildContext:e.identity.buildContext,...e.identity.target!==void 0&&{target:e.identity.target},...u.docker.buildArgs!==void 0&&{buildArgs:u.docker.buildArgs},...n.length>0&&{buildSecrets:n},members:e.members.map(d=>({serviceName:d.name,...d.docker.buildArgs!==void 0&&{buildArgs:d.docker.buildArgs},...d.docker.buildSecrets!==void 0&&{buildSecrets:d.docker.buildSecrets}}))});if(!c.success)return l(c.error);const S=U(u.docker.buildArgs,I(t.appName));for(const d of S.warnings)i.onLog?.(f(d.message),"warn");if(S.findings.length>0)return l(new Error(f(S.findings.map(d=>d.message).join(" "))));const R=n,A=Y(u.docker.buildArgs),b={appName:t.appName,region:r,accountId:o,partition:q(r)},w=await X(m,[...R,...A],b);if(!w.success)return l(w.error);const T=w.data,N=await J({manifestBuildArgs:u.docker.buildArgs,serviceDir:e.identity.buildContext,appDir:t.path,...v!==void 0&&{stage:v},shellEnv:process.env,...p!==void 0&&{flagBuildArgs:p},resolveSourcedValue:(d,K)=>z(T,{id:d,...K},g)});if(!N.success)return l(N.error);const O=N.data.buildArgs;for(const d of N.data.inferredDotenvKeys)i.onLog?.(f(`buildArg "${d}" was auto-declared from a .env file and will be baked into the image \u2014 ${V}.`),"warn");const L=G(O,I(t.appName),H(u.docker.buildArgs));if(L.length>0)return l(new Error(f(L.map(d=>d.message).join(" "))));let D;if(R.length>0){const d=await W(T,R,g);if(!d.success)return l(d.error);D=d.data}try{const d={appName:t.appName,appPath:e.identity.buildContext,region:r,accountId:o,buildContext:e.identity.buildContext,dockerfilePath:e.identity.dockerfilePath,pushByDigest:!0,serviceName:u.name,...e.identity.target!==void 0&&{target:e.identity.target},...Object.keys(O).length>0&&{buildArgs:O},...D!==void 0&&{buildSecrets:D.resolved}};return await de(e,d,s,a,i)}finally{D!==void 0&&await D.cleanup().catch(()=>i.onLog?.("Build-secret cleanup failed.","warn"))}}async function de(e,s,t,r,o){const a=await t.buildAndPush(s,(p,h,u,n)=>{o.onDockerProgress?.(f(p),h,u,n)});if(!a.success)return l(a.error);const i=a.data.imageDigest;if(typeof i!="string"||!i.startsWith("sha256:"))return l(new Error(`Buildx push succeeded but returned an invalid digest: ${f(String(i))}`));const m={},g=[];for(const p of e.members){const h=p.docker.target,u=F(i,p.name,h);if(!u.success)return l(u.error);const n=u.data,c=p.name;m[c]=n,g.push(`${r}:${n}`)}const v=await t.tagByDigest({sourceImage:r,digest:i,tags:g});return v.success?k({contentHashTagsByService:m}):l(v.error)}async function Ee(e,s,t,r){const o=e.dockerProvider;if(!o)return k({});const a=s.awsProvider.getAccountId();if(!a)return r.onLog?.("Skipping Docker build \u2014 account ID not available","warn"),k({});const i=s.awsProvider.getRegion(),m=B(t.path,"cdk.out"),g=M(m),v=oe(a,i,t.appName);if(C.debug(P,"runDockerBuild starting",{appName:t.appName,appPath:t.path,accountId:a,region:i,cdkOutPath:m,manifestServiceCount:g.length,manifestServices:g.map(n=>({name:n.name,path:n.docker.path,context:n.docker.context,target:n.docker.target})),stepId:y.DOCKER_OPERATIONS,stepName:E}),r.onStepStart?.(y.DOCKER_OPERATIONS,E),g.length===0){r.onLog?.("Initialising ECR repository\u2026","info");const n=await o.initialiseECR({appName:t.appName,region:i,accountId:a});n.success||r.onLog?.(`ECR initialisation failed: ${f(n.error.message)}`,"warn"),r.onLog?.("Building and pushing Docker image\u2026","info");const c=await se(o,t,i,a,r);if(!c.success){C.debug(P,"Docker buildAndPush (legacy) failed",{appName:t.appName,error:f(c.error.message)}),r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const S=new Error(f(c.error.message));return r.onError?.(S),l(S)}return r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"completed"),r.onLog?.(`Docker image pushed: ${c.data.imageUri}`,"info"),k({})}const p=await o.preflight?.();if(p!==void 0&&!p.success){r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const n=new Error(f(p.error.message));return r.onError?.(n),l(n)}const h=ie(g,t.path,e.workingDirectory);r.onLog?.(`Building and pushing ${g.length} service image(s) in ${h.length} group(s)\u2026`,"info");let u=!1;if(o.beginEcrSession!==void 0){const n=await o.beginEcrSession({accountId:a,region:i});if(!n.success){r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const c=new Error(f(n.error.message));return r.onError?.(c),l(c)}u=!0}try{const n={};for(let c=0;c<h.length;c++){const S=h[c],R=S.members.map(b=>b.name).join(", ");r.onLog?.(`Building group ${c+1}/${h.length} (${R})\u2026`,"info");const A=await ae(S,o,t,i,a,v,r,s.awsProvider,e.abortSignal,e.options?.environment,e.options?.flagBuildArgs,e.options?.flagBuildSecrets);if(!A.success){C.debug(P,"Docker buildAndPush failed",{appName:t.appName,leader:S.members[0]?.name,members:R,error:f(A.error.message)}),r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"error");const b=new Error(f(A.error.message));return r.onError?.(b),l(b)}for(const[b,w]of Object.entries(A.data.contentHashTagsByService))n[b]=w}return C.debug(P,"Docker buildAndPush succeeded",{appName:t.appName,services:g.map(c=>c.name),groupCount:h.length,contentHashTags:n}),r.onStepComplete?.(y.DOCKER_OPERATIONS,E,"completed"),r.onLog?.(`Docker images pushed for ${g.length} service(s)`,"info"),k(n)}finally{u&&o.endEcrSession!==void 0&&await o.endEcrSession().catch(n=>{C.debug(P,"endEcrSession cleanup failed",{error:f(n instanceof Error?n.message:String(n))})})}}export{E as DOCKER_BUILD_STEP_NAME,Ee as runDockerBuild};
|
|
@@ -146,6 +146,23 @@ export interface DockerProvider {
|
|
|
146
146
|
* counterpart is a release-coupled follow-up (worker repo).
|
|
147
147
|
*/
|
|
148
148
|
preflight?(): Promise<Result<void>>;
|
|
149
|
+
/**
|
|
150
|
+
* Optional ECR session bracket. When present, the orchestrator opens ONE ECR
|
|
151
|
+
* session for the whole docker phase — `beginEcrSession` once before the group
|
|
152
|
+
* loop, `endEcrSession` once after — so every `buildAndPush` / `tagByDigest`
|
|
153
|
+
* in the phase reuses a single login instead of each op self-scoping its own
|
|
154
|
+
* (which, on macOS, re-touches the osxkeychain and can hit the -25299 stale-
|
|
155
|
+
* entry block). Absent → each op self-scopes (today's behaviour); the
|
|
156
|
+
* optional-chained call makes the addition backward-compatible while a
|
|
157
|
+
* consumer that hasn't yet implemented these ships. Providers implement both
|
|
158
|
+
* or neither. Token acquisition stays provider-side (the params carry only the
|
|
159
|
+
* account/region the provider needs to mint its own ECR authorisation).
|
|
160
|
+
*/
|
|
161
|
+
beginEcrSession?(params: {
|
|
162
|
+
accountId: string;
|
|
163
|
+
region: string;
|
|
164
|
+
}): Promise<Result<void>>;
|
|
165
|
+
endEcrSession?(): Promise<void>;
|
|
149
166
|
buildAndPush(params: DockerBuildParams, onProgress?: DockerProgressCallback, onBuildxEvent?: (event: BuildxProgressEvent) => void): Promise<Result<DockerBuildResult>>;
|
|
150
167
|
initialiseECR(params: ECRInitParams): Promise<Result<ECRInitResult>>;
|
|
151
168
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.23.1",
|
|
4
4
|
"description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"@aws-sdk/client-ssm": "^3.1038.0",
|
|
82
82
|
"@aws-sdk/client-sso-admin": "^3.1038.0",
|
|
83
83
|
"@aws-sdk/client-sts": "^3.1038.0",
|
|
84
|
-
"@fjall/generator": "^2.
|
|
85
|
-
"@fjall/util": "^2.
|
|
84
|
+
"@fjall/generator": "^2.23.1",
|
|
85
|
+
"@fjall/util": "^2.23.1",
|
|
86
86
|
"@smithy/node-http-handler": "^4.6.1",
|
|
87
87
|
"aws-cdk": "^2.1128.1",
|
|
88
88
|
"tsx": "^4.21.0",
|
|
@@ -93,5 +93,5 @@
|
|
|
93
93
|
"typescript": "^6.0.3",
|
|
94
94
|
"vitest": "^4.1.5"
|
|
95
95
|
},
|
|
96
|
-
"gitHead": "
|
|
96
|
+
"gitHead": "749133c6c5cfe24a09a401869f2193c7d177a324"
|
|
97
97
|
}
|