@fjall/util 2.25.0 → 2.29.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
- 75 files minified at 2026-07-06T11:56:15.602Z
1
+ 79 files minified at 2026-07-13T22:21:40.141Z
@@ -1,4 +1,5 @@
1
1
  import { type ResourceEvent } from "./cloudformationTypes.js";
2
+ import { type DriftSuspectEvent } from "./driftSuspects.js";
2
3
  export interface RootCause {
3
4
  resource: ResourceEvent;
4
5
  reason: string;
@@ -12,6 +13,13 @@ export interface FailureAnalysis {
12
13
  summary: string;
13
14
  remediation: string[];
14
15
  errorPattern?: string;
16
+ /**
17
+ * Resources whose failure carries the out-of-band-deletion shape (Class 1
18
+ * drift): CloudFormation still tracks them but the service reported
19
+ * NotFound. Suspects only — confirmation requires the deploy-core drift
20
+ * probe. Absent when no failure event matches the NotFound anchors.
21
+ */
22
+ driftSuspects?: DriftSuspectEvent[];
15
23
  }
16
24
  /**
17
25
  * CloudFormationFailureAnalyser provides intelligent analysis of deployment failures
@@ -19,7 +27,25 @@ export interface FailureAnalysis {
19
27
  */
20
28
  export declare class CloudFormationFailureAnalyser {
21
29
  private knownErrorPatterns;
22
- analyseFailure(eventHistory: Map<string, ResourceEvent[]>): FailureAnalysis | null;
30
+ /**
31
+ * `stackName` opts the drift scan into current-operation bounding: the
32
+ * monitor's event history is seeded with the stack's FULL retained event
33
+ * history (~90 days), so an unbounded scan would resurrect suspects from
34
+ * long-past operations that were since cured. Callers whose history is
35
+ * already operation-scoped (callback-fed collections) omit it.
36
+ */
37
+ analyseFailure(eventHistory: Map<string, ResourceEvent[]>, stackName?: string): FailureAnalysis | null;
38
+ /**
39
+ * Scans EVERY failed event, not failedResources: a wedged resource's
40
+ * UPDATE_FAILED is superseded by its rollback event, so latest-per-resource
41
+ * semantics would drop exactly the wedge. When `stackName` is given, the
42
+ * scan is bounded to events at/after the current operation's opening event;
43
+ * if no opening event is visible the scan yields nothing rather than risk
44
+ * classifying a stale prior-operation failure (fail-safe: a missed hint is
45
+ * recoverable via `fjall drift detect`, a stale suspect self-reinforces
46
+ * through the journal).
47
+ */
48
+ private classifyDriftSuspects;
23
49
  private findFailedResources;
24
50
  private findRootCause;
25
51
  private categoriseError;
@@ -1 +1 @@
1
- class a{knownErrorPatterns=[{pattern:/AccessDenied|UnauthorizedOperation|Forbidden/i,category:"permissions",remediation:["Check IAM permissions for the deployment role","Ensure the role has necessary CloudFormation permissions","Verify service-linked roles are created if needed"]},{pattern:/Invalid.*Parameter|ValidationError|Invalid.*Value/i,category:"validation",remediation:["Review parameter values in your CDK code","Check for typos in resource names or ARNs","Ensure all required parameters are provided"]},{pattern:/Limit.*Exceeded|Quota.*Exceeded|Maximum.*reached/i,category:"limit",remediation:["Check AWS service quotas in the Service Quotas console","Request a quota increase if needed","Consider using a different region with available capacity"]},{pattern:/Timeout|Connection.*refused|Network.*unreachable/i,category:"network",remediation:["Check network connectivity and VPC settings","Verify security groups and NACLs","Ensure endpoints are accessible"]},{pattern:/already exists|Duplicate|ConflictException/i,category:"validation",remediation:["Resource with this name already exists","Consider using a different name or deleting the existing resource","Check if you are deploying to the correct account/region"]},{pattern:/Role.*not.*found|Role.*does.*not.*exist/i,category:"dependency",remediation:["Ensure IAM roles are created before dependent resources","Check role names and ARNs are correct","Verify cross-stack references are properly configured"]}];analyseFailure(e){const r=this.findFailedResources(e);if(r.length===0)return null;const s=this.findRootCause(r,e),n=this.buildDependencyChain(s.resource,r),i=this.generateRemediation(s),t=this.generateSummary(s,r);return{rootCause:s,affectedResources:r,dependencyChain:n,summary:t,remediation:i,errorPattern:s.category}}findFailedResources(e){const r=[];for(const[s,n]of e){const i=n[n.length-1];i&&this.isFailedStatus(i.status)&&r.push(i)}return r.sort((s,n)=>s.timestamp.getTime()-n.timestamp.getTime())}findRootCause(e,r){if(e.length===0)return{resource:{logicalId:"Unknown",resourceType:"Unknown",status:"FAILED",timestamp:new Date},reason:"No failed resources found",category:"unknown",isDirectCause:!1};const s=e[0],n=this.categoriseError(s.statusReason||""),i=this.isDirectCause(s,r);return{resource:s,reason:s.statusReason||"Unknown error",category:n,isDirectCause:i}}categoriseError(e){for(const r of this.knownErrorPatterns)if(r.pattern.test(e))return r.category;return"unknown"}isDirectCause(e,r){const s=e.statusReason||"";return s.includes("depends on")||s.includes("referenced by")||s.includes("required by")?!1:!(r.get(e.logicalId)||[]).some(t=>t.status.includes("COMPLETE")&&!t.status.includes("ROLLBACK"))}buildDependencyChain(e,r){const s=[];s.push(`${e.logicalId} (${e.resourceType}) - ROOT CAUSE`);for(const n of r)n.logicalId!==e.logicalId&&((n.statusReason||"").toLowerCase().includes(e.logicalId.toLowerCase())?s.push(` \u2192 ${n.logicalId} (${n.resourceType}) - Failed due to ${e.logicalId}`):s.push(` \u2192 ${n.logicalId} (${n.resourceType})`));return s}generateRemediation(e){const r=[];for(const s of this.knownErrorPatterns)if(s.category===e.category){r.push(...s.remediation);break}return e.resource.resourceType.includes("IAM")?r.push("Review IAM policies and trust relationships"):e.resource.resourceType.includes("Lambda")?r.push("Check Lambda function configuration and runtime"):e.resource.resourceType.includes("ECS")&&r.push("Verify ECS task definition and container configuration"),r.length===0&&r.push("Review the CloudFormation console for detailed error messages","Check the resource configuration in your CDK code","Ensure all dependencies are properly defined"),r}generateSummary(e,r){const s=this.simplifyResourceType(e.resource.resourceType),n=r.length;let i=`Deployment failed: ${s} "${e.resource.logicalId}" `;switch(e.category){case"permissions":i+="failed due to insufficient permissions";break;case"validation":i+="failed validation";break;case"dependency":i+="has missing or invalid dependencies";break;case"limit":i+="exceeded AWS service limits";break;case"network":i+="encountered network issues";break;default:i+="failed to create"}return n>1&&(i+=` (${n-1} dependent resources also failed)`),i}simplifyResourceType(e){return e.replace("AWS::","").replace("::"," ")}isFailedStatus(e){return e.includes("FAILED")}}export{a as CloudFormationFailureAnalyser};
1
+ import{classifyDriftSuspectEvents as c,findCurrentOperationStart as d}from"./driftSuspects.js";class f{knownErrorPatterns=[{pattern:/AccessDenied|UnauthorizedOperation|Forbidden/i,category:"permissions",remediation:["Check IAM permissions for the deployment role","Ensure the role has necessary CloudFormation permissions","Verify service-linked roles are created if needed"]},{pattern:/Invalid.*Parameter|ValidationError|Invalid.*Value/i,category:"validation",remediation:["Review parameter values in your CDK code","Check for typos in resource names or ARNs","Ensure all required parameters are provided"]},{pattern:/Limit.*Exceeded|Quota.*Exceeded|Maximum.*reached/i,category:"limit",remediation:["Check AWS service quotas in the Service Quotas console","Request a quota increase if needed","Consider using a different region with available capacity"]},{pattern:/Timeout|Connection.*refused|Network.*unreachable/i,category:"network",remediation:["Check network connectivity and VPC settings","Verify security groups and NACLs","Ensure endpoints are accessible"]},{pattern:/already exists|Duplicate|ConflictException/i,category:"validation",remediation:["Resource with this name already exists","Consider using a different name or deleting the existing resource","Check if you are deploying to the correct account/region"]},{pattern:/Role.*not.*found|Role.*does.*not.*exist/i,category:"dependency",remediation:["Ensure IAM roles are created before dependent resources","Check role names and ARNs are correct","Verify cross-stack references are properly configured"]}];analyseFailure(e,t){const r=this.findFailedResources(e);if(r.length===0)return null;const s=this.findRootCause(r,e),n=this.buildDependencyChain(s.resource,r),i=this.classifyDriftSuspects(e,t),o=this.generateRemediation(s);i.length>0&&o.unshift("One or more resources CloudFormation still tracks appear to have been deleted outside CloudFormation","Run 'fjall drift detect' to confirm the deletion; confirmed deletions are remediated with 'fjall drift repair'");const a=this.generateSummary(s,r);return{rootCause:s,affectedResources:r,dependencyChain:n,summary:a,remediation:o,errorPattern:s.category,...i.length>0?{driftSuspects:i}:{}}}classifyDriftSuspects(e,t){const r=t!==void 0?d(e.get(t)??[],t):null;if(t!==void 0&&r===null)return[];const s=[];for(const n of e.values())for(const i of n)this.isFailedStatus(i.status)&&(r!==null&&i.timestamp.getTime()<r.getTime()||s.push(i));return c(s)}findFailedResources(e){const t=[];for(const[r,s]of e){const n=s[s.length-1];n&&this.isFailedStatus(n.status)&&t.push(n)}return t.sort((r,s)=>r.timestamp.getTime()-s.timestamp.getTime())}findRootCause(e,t){if(e.length===0)return{resource:{logicalId:"Unknown",resourceType:"Unknown",status:"FAILED",timestamp:new Date},reason:"No failed resources found",category:"unknown",isDirectCause:!1};const r=e[0],s=this.categoriseError(r.statusReason||""),n=this.isDirectCause(r,t);return{resource:r,reason:r.statusReason||"Unknown error",category:s,isDirectCause:n}}categoriseError(e){for(const t of this.knownErrorPatterns)if(t.pattern.test(e))return t.category;return"unknown"}isDirectCause(e,t){const r=e.statusReason||"";return r.includes("depends on")||r.includes("referenced by")||r.includes("required by")?!1:!(t.get(e.logicalId)||[]).some(i=>i.status.includes("COMPLETE")&&!i.status.includes("ROLLBACK"))}buildDependencyChain(e,t){const r=[];r.push(`${e.logicalId} (${e.resourceType}) - ROOT CAUSE`);for(const s of t)s.logicalId!==e.logicalId&&((s.statusReason||"").toLowerCase().includes(e.logicalId.toLowerCase())?r.push(` \u2192 ${s.logicalId} (${s.resourceType}) - Failed due to ${e.logicalId}`):r.push(` \u2192 ${s.logicalId} (${s.resourceType})`));return r}generateRemediation(e){const t=[];for(const r of this.knownErrorPatterns)if(r.category===e.category){t.push(...r.remediation);break}return e.resource.resourceType.includes("IAM")?t.push("Review IAM policies and trust relationships"):e.resource.resourceType.includes("Lambda")?t.push("Check Lambda function configuration and runtime"):e.resource.resourceType.includes("ECS")&&t.push("Verify ECS task definition and container configuration"),t.length===0&&t.push("Review the CloudFormation console for detailed error messages","Check the resource configuration in your CDK code","Ensure all dependencies are properly defined"),t}generateSummary(e,t){const r=this.simplifyResourceType(e.resource.resourceType),s=t.length;let n=`Deployment failed: ${r} "${e.resource.logicalId}" `;switch(e.category){case"permissions":n+="failed due to insufficient permissions";break;case"validation":n+="failed validation";break;case"dependency":n+="has missing or invalid dependencies";break;case"limit":n+="exceeded AWS service limits";break;case"network":n+="encountered network issues";break;default:n+="failed to create"}return s>1&&(n+=` (${s-1} dependent resources also failed)`),n}simplifyResourceType(e){return e.replace("AWS::","").replace("::"," ")}isFailedStatus(e){return e.includes("FAILED")}}export{f as CloudFormationFailureAnalyser};
@@ -13,5 +13,11 @@ export interface ResourceEvent {
13
13
  group?: string;
14
14
  /** CDK construct path (e.g., "/Account/CloudTrail/trail/Resource"). */
15
15
  constructPath?: string;
16
+ /**
17
+ * CloudFormation ClientRequestToken for the operation that emitted this event
18
+ * (§5.9 capture-not-stamp). Lets a reconciler correlate an orphaned CFN
19
+ * operation back to its deployment row. Absent on synthetic/initial events.
20
+ */
21
+ clientRequestToken?: string;
16
22
  }
17
23
  export declare function isResourceEvent(event: unknown): event is ResourceEvent;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Class 1 drift classification — out-of-band deletion suspects.
3
+ *
4
+ * A resource CloudFormation still tracks but which was deleted outside
5
+ * CloudFormation wedges every subsequent update: the service handler returns
6
+ * NotFound and the stack rolls back (the BusinessContinuity CMK incident,
7
+ * account 913524923178). The patterns here are derived from the Phase 0
8
+ * empirical fixtures (`fjall/deploy-core/src/orchestration/drift/__tests__/
9
+ * fixtures/bc-stack-events.json`, captured 2026-07-07): the stable anchors are
10
+ * the registry handler contract marker `HandlerErrorCode: NotFound` and the
11
+ * service message shape `does not exist (Service: …`.
12
+ *
13
+ * Classification is SUSPECTS ONLY — never a confirmed verdict. Confirmation
14
+ * requires a direct per-type probe (deploy-core `driftProbe.ts`); a suspect
15
+ * that cannot be probed stays UNCONFIRMED and remediation is refused
16
+ * (fail-closed: never surgery on a resource not proven dead).
17
+ */
18
+ import type { ResourceEvent } from "./cloudformationTypes.js";
19
+ /**
20
+ * A resource whose failure event carries the out-of-band-deletion shape.
21
+ * `statusReason` is raw here — the analyser's `maskFailureAnalysis` boundary
22
+ * (or the sink) masks before display/persistence.
23
+ */
24
+ export interface DriftSuspectEvent {
25
+ logicalId: string;
26
+ resourceType: string;
27
+ physicalId?: string;
28
+ /** The failed status that carried the NotFound shape (e.g. UPDATE_FAILED). */
29
+ failedStatus: string;
30
+ statusReason: string;
31
+ }
32
+ /** True when a failure reason carries the out-of-band-deletion shape. */
33
+ export declare function isDriftSuspectReason(statusReason: string): boolean;
34
+ /**
35
+ * True for the stack-level event that opens a CloudFormation operation —
36
+ * the shared boundary for "current operation only" scans (the deploy-core
37
+ * last-operation event walk and the failure analyser's drift scan).
38
+ */
39
+ export declare function isOperationOpeningEvent(event: ResourceEvent, stackName: string): boolean;
40
+ /**
41
+ * Timestamp of the most recent operation-opening event for `stackName`, or
42
+ * null when none is visible. Server-clock consistent with event timestamps,
43
+ * so callers can bound scans without local-clock skew.
44
+ */
45
+ export declare function findCurrentOperationStart(events: readonly ResourceEvent[], stackName: string): Date | null;
46
+ /**
47
+ * Extract out-of-band-deletion suspects from failure events. Callers pass
48
+ * EVERY failed event in the operation, not just each resource's latest — a
49
+ * wedged resource's UPDATE_FAILED is typically superseded by a later
50
+ * UPDATE_ROLLBACK_* event, so latest-event-per-resource semantics would drop
51
+ * exactly the suspects this classifier exists to find. Deduplicated by
52
+ * logicalId (first match wins).
53
+ */
54
+ export declare function classifyDriftSuspectEvents(events: readonly ResourceEvent[]): DriftSuspectEvent[];
@@ -0,0 +1 @@
1
+ const o=new Set(["UPDATE_FAILED","DELETE_FAILED"]),i=[/HandlerErrorCode:\s*NotFound\b/,/does not exist \(Service:/i,/\b(?:ResourceNotFoundException|NotFoundException|NoSuchBucket|NoSuchEntity)\b/],c=/Resource (?:creation|update) cancelled/i;function u(e){return c.test(e)?!1:i.some(s=>s.test(e))}const a="AWS::CloudFormation::Stack",r=new Set(["CREATE_IN_PROGRESS","UPDATE_IN_PROGRESS","DELETE_IN_PROGRESS","IMPORT_IN_PROGRESS"]),E="User Initiated";function S(e,s){return e.resourceType===a&&e.logicalId===s&&r.has(e.status)&&e.statusReason!==void 0&&e.statusReason.includes(E)}function d(e,s){let n=null;for(const t of e)S(t,s)&&(n===null||t.timestamp.getTime()>n.getTime())&&(n=t.timestamp);return n}function R(e){const s=[],n=new Set;for(const t of e)n.has(t.logicalId)||o.has(t.status)&&(t.statusReason===void 0||t.statusReason===""||u(t.statusReason)&&(n.add(t.logicalId),s.push({logicalId:t.logicalId,resourceType:t.resourceType,...t.physicalId!==void 0&&t.physicalId!==""?{physicalId:t.physicalId}:{},failedStatus:t.status,statusReason:t.statusReason})));return s}export{R as classifyDriftSuspectEvents,d as findCurrentOperationStart,u as isDriftSuspectReason,S as isOperationOpeningEvent};
@@ -3,6 +3,7 @@ export { STACK_NOT_FOUND_PATTERN, CDK_NO_STACKS_MATCH, type ResourceEvent, isRes
3
3
  export { type ResourceProgressCounts, countResourceProgress, formatElapsed } from "./deployProgress.js";
4
4
  export { CloudFormationFailureAnalyser, type RootCause, type FailureAnalysis } from "./CloudFormationFailureAnalyser.js";
5
5
  export { maskFailureAnalysis } from "./maskFailureAnalysis.js";
6
+ export { classifyDriftSuspectEvents, findCurrentOperationStart, isDriftSuspectReason, isOperationOpeningEvent, type DriftSuspectEvent } from "./driftSuspects.js";
6
7
  export { IPAM_OPERATIONS_POOL_TAG_KEY, formatIpamPairTagValue } from "./ipamTags.js";
7
8
  export { SDK_PRE_EMPTY_TAG_KEY } from "./infraTags.js";
8
9
  export { ACCOUNT_MONITORING_ROLE_NAME } from "./monitoringRole.js";
package/dist/aws/index.js CHANGED
@@ -1 +1 @@
1
- import{AWSError as e,NoRolesFoundError as E,InvalidCredentialsError as s,SSOTokenExpiredError as a,MissingRegionError as i,ProfileNotFoundError as _,CommandError as t,isAWSError as m,isNoRolesFoundError as n,isSSOUnauthorizedError as A}from"./errors.js";import{STACK_NOT_FOUND_PATTERN as O,CDK_NO_STACKS_MATCH as l,isResourceEvent as N}from"./cloudformationTypes.js";import{countResourceProgress as p,formatElapsed as u}from"./deployProgress.js";import{CloudFormationFailureAnalyser as d}from"./CloudFormationFailureAnalyser.js";import{maskFailureAnalysis as x}from"./maskFailureAnalysis.js";import{IPAM_OPERATIONS_POOL_TAG_KEY as P,formatIpamPairTagValue as F}from"./ipamTags.js";import{SDK_PRE_EMPTY_TAG_KEY as K}from"./infraTags.js";import{ACCOUNT_MONITORING_ROLE_NAME as g}from"./monitoringRole.js";export{g as ACCOUNT_MONITORING_ROLE_NAME,e as AWSError,l as CDK_NO_STACKS_MATCH,d as CloudFormationFailureAnalyser,t as CommandError,P as IPAM_OPERATIONS_POOL_TAG_KEY,s as InvalidCredentialsError,i as MissingRegionError,E as NoRolesFoundError,_ as ProfileNotFoundError,K as SDK_PRE_EMPTY_TAG_KEY,a as SSOTokenExpiredError,O as STACK_NOT_FOUND_PATTERN,p as countResourceProgress,u as formatElapsed,F as formatIpamPairTagValue,m as isAWSError,n as isNoRolesFoundError,N as isResourceEvent,A as isSSOUnauthorizedError,x as maskFailureAnalysis};
1
+ import{AWSError as e,NoRolesFoundError as t,InvalidCredentialsError as i,SSOTokenExpiredError as s,MissingRegionError as n,ProfileNotFoundError as E,CommandError as a,isAWSError as p,isNoRolesFoundError as _,isSSOUnauthorizedError as f}from"./errors.js";import{STACK_NOT_FOUND_PATTERN as O,CDK_NO_STACKS_MATCH as u,isResourceEvent as A}from"./cloudformationTypes.js";import{countResourceProgress as T,formatElapsed as l}from"./deployProgress.js";import{CloudFormationFailureAnalyser as d}from"./CloudFormationFailureAnalyser.js";import{maskFailureAnalysis as x}from"./maskFailureAnalysis.js";import{classifyDriftSuspectEvents as P,findCurrentOperationStart as F,isDriftSuspectReason as c,isOperationOpeningEvent as I}from"./driftSuspects.js";import{IPAM_OPERATIONS_POOL_TAG_KEY as M,formatIpamPairTagValue as g}from"./ipamTags.js";import{SDK_PRE_EMPTY_TAG_KEY as v}from"./infraTags.js";import{ACCOUNT_MONITORING_ROLE_NAME as G}from"./monitoringRole.js";export{G as ACCOUNT_MONITORING_ROLE_NAME,e as AWSError,u as CDK_NO_STACKS_MATCH,d as CloudFormationFailureAnalyser,a as CommandError,M as IPAM_OPERATIONS_POOL_TAG_KEY,i as InvalidCredentialsError,n as MissingRegionError,t as NoRolesFoundError,E as ProfileNotFoundError,v as SDK_PRE_EMPTY_TAG_KEY,s as SSOTokenExpiredError,O as STACK_NOT_FOUND_PATTERN,P as classifyDriftSuspectEvents,T as countResourceProgress,F as findCurrentOperationStart,l as formatElapsed,g as formatIpamPairTagValue,p as isAWSError,c as isDriftSuspectReason,_ as isNoRolesFoundError,I as isOperationOpeningEvent,A as isResourceEvent,f as isSSOUnauthorizedError,x as maskFailureAnalysis};
@@ -1 +1 @@
1
- import{maskSensitiveOutput as o}from"../securityHelpers.js";function r(e){return e.statusReason===void 0?e:{...e,statusReason:o(e.statusReason)}}function u(e){const s={...e.rootCause,reason:o(e.rootCause.reason),resource:r(e.rootCause.resource)};return{...e,rootCause:s,affectedResources:e.affectedResources.map(r)}}export{u as maskFailureAnalysis};
1
+ import{maskSensitiveOutput as t}from"../securityHelpers.js";function r(e){return e.statusReason===void 0?e:{...e,statusReason:t(e.statusReason)}}function u(e){return{...e,statusReason:t(e.statusReason)}}function n(e){const s={...e.rootCause,reason:t(e.rootCause.reason),resource:r(e.rootCause.resource)};return{...e,rootCause:s,affectedResources:e.affectedResources.map(r),...e.driftSuspects!==void 0?{driftSuspects:e.driftSuspects.map(u)}:{}}}export{n as maskFailureAnalysis};
@@ -0,0 +1,38 @@
1
+ import { type Result } from "./docker/result.js";
2
+ export interface RunWithBoundedConcurrencyOptions {
3
+ /**
4
+ * When true, stop issuing new jobs after the first failure. The orchestrator
5
+ * is responsible for owning the AbortController and signalling in-flight jobs.
6
+ */
7
+ abortOnFirstFailure?: boolean;
8
+ /**
9
+ * Optional abort signal: once aborted, the runner stops issuing NEW jobs.
10
+ * In-flight jobs are not cancelled — the orchestrator owns the
11
+ * AbortController and is responsible for signalling them (typically via a
12
+ * closure over the same signal inside each job factory).
13
+ */
14
+ abortSignal?: AbortSignal;
15
+ }
16
+ /**
17
+ * Run a list of async jobs with a bounded number in-flight at any tick.
18
+ *
19
+ * Returns results in INPUT order (job N's result lives at index N) regardless
20
+ * of completion order. Slots for jobs never issued (abort or first-failure
21
+ * stop) are left `undefined` — the return type is honest about the holes.
22
+ */
23
+ export declare function runWithBoundedConcurrency<T>(jobs: ReadonlyArray<() => Promise<T>>, concurrency: number, opts?: RunWithBoundedConcurrencyOptions): Promise<Array<Result<T, Error> | undefined>>;
24
+ /**
25
+ * Resolve the per-orchestrator-run build concurrency from the
26
+ * `FJALL_BUILD_CONCURRENCY` environment variable, clamped to `[1, serviceCount]`.
27
+ *
28
+ * The env-var contract is shared by every build surface (`fjall build` via
29
+ * `@fjall/cli` and `fjall deploy` via `@fjall/deploy-core`) — this module is
30
+ * the single source so the two paths cannot drift.
31
+ *
32
+ * - Empty-string env values are rejected (per robustness-standards.md
33
+ * § "Environment Variable Truthy Checks").
34
+ * - Non-integer / non-positive values fall back to the default.
35
+ * - When `serviceCount === 0`, returns `1` (no jobs would be issued anyway).
36
+ * - The default is CPU-aware (see `defaultBuildConcurrency`).
37
+ */
38
+ export declare function resolveBuildConcurrency(serviceCount: number): number;
@@ -0,0 +1 @@
1
+ import p from"os";import{success as y,failure as x}from"./docker/result.js";async function F(r,e,o={}){const t=r.length,i=new Array(t);if(t===0)return i;const l=Math.max(1,Math.min(e,t)),u=o.abortOnFirstFailure===!0;let c=0,s=!1;async function d(){for(;;){if(u&&s||o.abortSignal?.aborted===!0)return;const n=c;if(n>=t)return;c=n+1;const h=r[n];try{const a=await h();i[n]=y(a)}catch(a){const m=a instanceof Error?a:new Error(String(a));i[n]=x(m),u&&(s=!0)}}}const f=[];for(let n=0;n<l;n++)f.push(d());return await Promise.all(f),i}function w(r){const e=p.cpus().length;return Math.min(r,Math.max(2,Math.floor(e/2)))}function g(r){if(r<=0)return 1;const e=process.env.FJALL_BUILD_CONCURRENCY;let o=w(r);if(e!==void 0&&e!==""){const t=parseInt(e,10);Number.isInteger(t)&&t>0&&(o=t)}return Math.max(1,Math.min(o,r))}export{g as resolveBuildConcurrency,F as runWithBoundedConcurrency};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Fallback map for pinned-name detection: CloudFormation resource types whose
3
+ * user-settable NAMING property cannot be derived from the registry schema's
4
+ * `primaryIdentifier` — the identifier is a generated attribute (ARN, URL, ID)
5
+ * that never appears in a template's Properties.
6
+ *
7
+ * Consulted by @fjall/deploy-core's pinned-name detector AFTER the
8
+ * primaryIdentifier derivation fails to yield a template property. Keep this
9
+ * map small: types whose primaryIdentifier IS the naming property
10
+ * (DBInstanceIdentifier, TableName, BucketName, RepositoryName, …) must NOT be
11
+ * added here.
12
+ */
13
+ export declare const PHYSICAL_NAME_FALLBACK_PROPERTIES: Readonly<Record<string, string>>;
@@ -0,0 +1 @@
1
+ const e={"AWS::SecretsManager::Secret":"Name","AWS::SQS::Queue":"QueueName","AWS::SNS::Topic":"TopicName"};export{e as PHYSICAL_NAME_FALLBACK_PROPERTIES};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Derivation for the buildx registry-cache ECR repository that sits beside an
3
+ * app's image repository. Both the `fjall build` provisioner
4
+ * (`@fjall/cli` EcrBuildOrchestrator) and the deploy-path provisioner
5
+ * (`@fjall/deploy-core` dockerBuildHelper) address the SAME physical repo, so
6
+ * the name derivation and the lifecycle retention live here — a re-implemented
7
+ * `\`${name}-cache\`` at either site is silent drift against a shared AWS
8
+ * resource (see code-quality.md § "Runtime resource names must match the CDK
9
+ * construct's derivation" — same rule, orchestrator↔orchestrator seam).
10
+ */
11
+ /** `<repositoryName>-cache`; `repositoryName` is the kebab-case app repo name. */
12
+ export declare function buildCacheRepositoryName(repositoryName: string): string;
13
+ /**
14
+ * Days before untagged cache layers expire. Buildx registry-cache writes leave
15
+ * untagged image layers behind on every push; both provisioners apply the same
16
+ * lifecycle policy idempotently, so a differing value would flip-flop between
17
+ * `fjall build` and `fjall deploy` runs (last writer wins).
18
+ */
19
+ export declare const CACHE_REPO_UNTAGGED_RETENTION_DAYS = 14;
20
+ /**
21
+ * The exact lifecycle-policy document both provisioners PUT on the cache
22
+ * repo. Shared so the stored policy text is byte-identical regardless of
23
+ * which path wrote last — a structurally-equal-but-differently-worded
24
+ * document would rewrite the policy on every alternating build/deploy run.
25
+ */
26
+ export declare function untaggedLifecyclePolicyText(daysUntilExpiry: number): string;
@@ -0,0 +1 @@
1
+ function t(e){return`${e}-cache`}const n=14;function i(e){return JSON.stringify({rules:[{rulePriority:1,description:`Expire untagged images after ${e} days`,selection:{tagStatus:"untagged",countType:"sinceImagePushed",countUnit:"days",countNumber:e},action:{type:"expire"}}]})}export{n as CACHE_REPO_UNTAGGED_RETENTION_DAYS,t as buildCacheRepositoryName,i as untaggedLifecyclePolicyText};
@@ -8,7 +8,7 @@ export declare const DEFAULT_DOCKER_BIN = "docker";
8
8
  export declare const SIGTERM_GRACE_MS = 5000;
9
9
  export declare const STDERR_TAIL_LINES = 50;
10
10
  export declare const STDERR_TAIL_LINE_MAX_CHARS = 2000;
11
- export declare const DEFAULT_BUILD_TIMEOUT_MS = 600000;
11
+ export declare const DEFAULT_BUILD_TIMEOUT_MS = 1800000;
12
12
  export declare const DEFAULT_PUSH_TIMEOUT_MS = 300000;
13
13
  export declare const DEFAULT_PULL_TIMEOUT_MS = 300000;
14
14
  export declare const DEFAULT_INSPECT_TIMEOUT_MS = 30000;
@@ -1 +1 @@
1
- const _="DockerCli",o="DockerCli.buildx",E="0.13.0",t="23.0.0",T=64,L="fjall",e="docker",I=5e3,r=50,O=2e3,D=6e5,c=3e5,s=3e5,R=3e4,U=1e4;export{E as BUILDX_VERSION_FLOOR,L as DEFAULT_BUILDER_NAME,D as DEFAULT_BUILD_TIMEOUT_MS,U as DEFAULT_DAEMON_PROBE_TIMEOUT_MS,e as DEFAULT_DOCKER_BIN,R as DEFAULT_INSPECT_TIMEOUT_MS,s as DEFAULT_PULL_TIMEOUT_MS,c as DEFAULT_PUSH_TIMEOUT_MS,o as DOCKER_CLI_BUILDX_LOG_CATEGORY,_ as DOCKER_CLI_LOG_CATEGORY,t as ENGINE_VERSION_FLOOR,T as PrerequisiteMissingExitCode,I as SIGTERM_GRACE_MS,r as STDERR_TAIL_LINES,O as STDERR_TAIL_LINE_MAX_CHARS};
1
+ const _="DockerCli",o="DockerCli.buildx",E="0.13.0",t="23.0.0",T=64,L="fjall",e="docker",I=5e3,r=50,O=2e3,D=18e5,c=3e5,s=3e5,R=3e4,U=1e4;export{E as BUILDX_VERSION_FLOOR,L as DEFAULT_BUILDER_NAME,D as DEFAULT_BUILD_TIMEOUT_MS,U as DEFAULT_DAEMON_PROBE_TIMEOUT_MS,e as DEFAULT_DOCKER_BIN,R as DEFAULT_INSPECT_TIMEOUT_MS,s as DEFAULT_PULL_TIMEOUT_MS,c as DEFAULT_PUSH_TIMEOUT_MS,o as DOCKER_CLI_BUILDX_LOG_CATEGORY,_ as DOCKER_CLI_LOG_CATEGORY,t as ENGINE_VERSION_FLOOR,T as PrerequisiteMissingExitCode,I as SIGTERM_GRACE_MS,r as STDERR_TAIL_LINES,O as STDERR_TAIL_LINE_MAX_CHARS};
@@ -11,3 +11,4 @@ export { projectBuildxResult, type DockerBuildResultLike, type ProjectBuildxResu
11
11
  export { abortChildProcess } from "./abortHelpers.js";
12
12
  export { DockerCli, type DockerCliLogger, type DockerCliOptions, type BuildxProgressEvent, type PushProgressEvent, type PushResult, type PullProgressEvent, type PullResult, type ImagetoolsInspect, type EcrLoginArgs, type BuildxCapabilities, type DaemonInfo } from "./DockerCli.js";
13
13
  export { createEcrAuthSession, type EcrAuthSession, type CreateEcrAuthSessionParams } from "./ecrCredentialStore.js";
14
+ export { buildCacheRepositoryName, untaggedLifecyclePolicyText, CACHE_REPO_UNTAGGED_RETENTION_DAYS } from "./cacheRepository.js";
@@ -1 +1 @@
1
- import{isSuccess as o,isFailure as E,success as _,failure as i}from"./result.js";import{DOCKER_CLI_LOG_CATEGORY as s,DOCKER_CLI_BUILDX_LOG_CATEGORY as t,BUILDX_VERSION_FLOOR as a,ENGINE_VERSION_FLOOR as u,PrerequisiteMissingExitCode as L,DEFAULT_BUILDER_NAME as T,DEFAULT_DOCKER_BIN as A,SIGTERM_GRACE_MS as R,STDERR_TAIL_LINES as U,DEFAULT_BUILD_TIMEOUT_MS as D,DEFAULT_PUSH_TIMEOUT_MS as I,DEFAULT_PULL_TIMEOUT_MS as S,DEFAULT_INSPECT_TIMEOUT_MS as O,DEFAULT_DAEMON_PROBE_TIMEOUT_MS as d}from"./dockerCliConstants.js";import{BuildxBuildArgsSchema as c,BuildxBuildResultSchema as x,DockerCliErrorKindSchema as m,DockerCliErrorSchema as C,isDockerCliErrorKind as p}from"./dockerCliSchemas.js";import{buildxArgvBuilder as f}from"./buildxArgvBuilder.js";import{evaluateBakeGuard as P,evaluateResolvedBuildArgValues as n,acknowledgedBuildArgKeys as G,BAKE_GUARD_EXPOSURE_CLAUSE as N}from"./bakeGuard.js";import{PUBLIC_BUILD_ARG_PREFIXES as g,isPublicBuildVarName as h,inferPublicBuildArgKeys as k}from"./buildArgInference.js";import{parseRawjsonLine as V}from"./rawjsonParser.js";import{rawjsonToVertexEvent as X}from"./rawjsonToVertexEvent.js";import{parseMetadataFile as w}from"./metadataFileParser.js";import{projectBuildxResult as Y}from"./projectBuildxResult.js";import{abortChildProcess as H}from"./abortHelpers.js";import{DockerCli as J}from"./DockerCli.js";import{createEcrAuthSession as W}from"./ecrCredentialStore.js";export{N as BAKE_GUARD_EXPOSURE_CLAUSE,a as BUILDX_VERSION_FLOOR,c as BuildxBuildArgsSchema,x as BuildxBuildResultSchema,T as DEFAULT_BUILDER_NAME,D as DEFAULT_BUILD_TIMEOUT_MS,d as DEFAULT_DAEMON_PROBE_TIMEOUT_MS,A as DEFAULT_DOCKER_BIN,O as DEFAULT_INSPECT_TIMEOUT_MS,S as DEFAULT_PULL_TIMEOUT_MS,I as DEFAULT_PUSH_TIMEOUT_MS,t as DOCKER_CLI_BUILDX_LOG_CATEGORY,s as DOCKER_CLI_LOG_CATEGORY,J as DockerCli,m as DockerCliErrorKindSchema,C as DockerCliErrorSchema,u as ENGINE_VERSION_FLOOR,g as PUBLIC_BUILD_ARG_PREFIXES,L as PrerequisiteMissingExitCode,R as SIGTERM_GRACE_MS,U as STDERR_TAIL_LINES,H as abortChildProcess,G as acknowledgedBuildArgKeys,f as buildxArgvBuilder,W as createEcrAuthSession,P as evaluateBakeGuard,n as evaluateResolvedBuildArgValues,i as failure,k as inferPublicBuildArgKeys,p as isDockerCliErrorKind,E as isFailure,h as isPublicBuildVarName,o as isSuccess,w as parseMetadataFile,V as parseRawjsonLine,Y as projectBuildxResult,X as rawjsonToVertexEvent,_ as success};
1
+ import{isSuccess as o,isFailure as E,success as _,failure as i}from"./result.js";import{DOCKER_CLI_LOG_CATEGORY as t,DOCKER_CLI_BUILDX_LOG_CATEGORY as a,BUILDX_VERSION_FLOOR as s,ENGINE_VERSION_FLOOR as T,PrerequisiteMissingExitCode as u,DEFAULT_BUILDER_NAME as A,DEFAULT_DOCKER_BIN as L,SIGTERM_GRACE_MS as R,STDERR_TAIL_LINES as D,DEFAULT_BUILD_TIMEOUT_MS as U,DEFAULT_PUSH_TIMEOUT_MS as c,DEFAULT_PULL_TIMEOUT_MS as I,DEFAULT_INSPECT_TIMEOUT_MS as O,DEFAULT_DAEMON_PROBE_TIMEOUT_MS as S}from"./dockerCliConstants.js";import{BuildxBuildArgsSchema as x,BuildxBuildResultSchema as m,DockerCliErrorKindSchema as B,DockerCliErrorSchema as C,isDockerCliErrorKind as p}from"./dockerCliSchemas.js";import{buildxArgvBuilder as M}from"./buildxArgvBuilder.js";import{evaluateBakeGuard as P,evaluateResolvedBuildArgValues as F,acknowledgedBuildArgKeys as G,BAKE_GUARD_EXPOSURE_CLAUSE as n}from"./bakeGuard.js";import{PUBLIC_BUILD_ARG_PREFIXES as K,isPublicBuildVarName as h,inferPublicBuildArgKeys as k}from"./buildArgInference.js";import{parseRawjsonLine as v}from"./rawjsonParser.js";import{rawjsonToVertexEvent as V}from"./rawjsonToVertexEvent.js";import{parseMetadataFile as j}from"./metadataFileParser.js";import{projectBuildxResult as Y}from"./projectBuildxResult.js";import{abortChildProcess as q}from"./abortHelpers.js";import{DockerCli as J}from"./DockerCli.js";import{createEcrAuthSession as W}from"./ecrCredentialStore.js";import{buildCacheRepositoryName as $,untaggedLifecyclePolicyText as ee,CACHE_REPO_UNTAGGED_RETENTION_DAYS as re}from"./cacheRepository.js";export{n as BAKE_GUARD_EXPOSURE_CLAUSE,s as BUILDX_VERSION_FLOOR,x as BuildxBuildArgsSchema,m as BuildxBuildResultSchema,re as CACHE_REPO_UNTAGGED_RETENTION_DAYS,A as DEFAULT_BUILDER_NAME,U as DEFAULT_BUILD_TIMEOUT_MS,S as DEFAULT_DAEMON_PROBE_TIMEOUT_MS,L as DEFAULT_DOCKER_BIN,O as DEFAULT_INSPECT_TIMEOUT_MS,I as DEFAULT_PULL_TIMEOUT_MS,c as DEFAULT_PUSH_TIMEOUT_MS,a as DOCKER_CLI_BUILDX_LOG_CATEGORY,t as DOCKER_CLI_LOG_CATEGORY,J as DockerCli,B as DockerCliErrorKindSchema,C as DockerCliErrorSchema,T as ENGINE_VERSION_FLOOR,K as PUBLIC_BUILD_ARG_PREFIXES,u as PrerequisiteMissingExitCode,R as SIGTERM_GRACE_MS,D as STDERR_TAIL_LINES,q as abortChildProcess,G as acknowledgedBuildArgKeys,$ as buildCacheRepositoryName,M as buildxArgvBuilder,W as createEcrAuthSession,P as evaluateBakeGuard,F as evaluateResolvedBuildArgValues,i as failure,k as inferPublicBuildArgKeys,p as isDockerCliErrorKind,E as isFailure,h as isPublicBuildVarName,o as isSuccess,j as parseMetadataFile,v as parseRawjsonLine,Y as projectBuildxResult,V as rawjsonToVertexEvent,_ as success,ee as untaggedLifecyclePolicyText};
package/dist/index.d.ts CHANGED
@@ -22,7 +22,8 @@ export { buildAppConfigPath } from "./repo/appPath.js";
22
22
  export { type ScanPath } from "./repo/scanTypes.js";
23
23
  export { findInfrastructurePaths, findBoundaryPath, isInfrastructureFile, type MarkerEntry, type FindInfrastructurePathsOptions } from "./repo/findInfrastructurePaths.js";
24
24
  export { inferContainerFromCandidates } from "./repo/inferContainerFromCandidates.js";
25
- export { RESERVED_APP_NAMES, type ReservedAppName, RESERVED_APP_NAME_MESSAGE, isReservedAppName } from "./naming/reservedAppNames.js";
25
+ export { RESERVED_APP_NAMES, type ReservedAppName, RESERVED_APP_NAME_MESSAGE, isReservedAppName, RESERVED_APP_NAME_SUFFIX, RESERVED_APP_NAME_SUFFIX_MESSAGE, hasReservedAppNameSuffix } from "./naming/reservedAppNames.js";
26
26
  export { deriveContentHashTag, CONTENT_HASH_TAG_PATTERN } from "./infra/deriveContentHashTag.js";
27
27
  export { DEPLOY_MODES, DeployModeSchema, type DeployMode, IMAGE_TAG_PATTERN, ImageTagSchema, type ImageTag, ServiceArtefactSchema, type ServiceArtefact, ServiceArtefactsSchema, type ServiceArtefacts, ARTEFACT_OUTPUT_FIELDS, type ArtefactOutputField, artefactOutputKey } from "./infra/deployArtefacts.js";
28
28
  export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_SCHEMA_VERSION_TOOL_ENV, EXPECTED_CH_SCHEMA_VERSION_ENV, SCHEMA_ADMIN_USER_ENV, SCHEMA_ADMIN_PASSWORD_ENV, PRISMA_MIGRATION_DIR_RE, CLICKHOUSE_MIGRATION_SKIP_RE } from "./migration/constants.js";
29
+ export { PHYSICAL_NAME_FALLBACK_PROPERTIES } from "./cfn/physicalNameProperties.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{DNS_APEX as o,getDomainExportNames as t}from"./infra/domainExports.js";import{BACKUP_VAULT_NAME as a}from"./infra/backupVault.js";import{APPROVAL_TOKEN_OUTPUT_PREFIX as _,TOKEN_STDERR_PREFIX as n}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as i}from"./infra/imageTags.js";import{toPascalCase as R,toKebab as m,toValidDatabaseName as N,toScreamingSnake as s,capitalise as O,getSafeZoneName as C,accountConstructKey as c,hasAsciiStableConstructKey as f}from"./naming/caseConversion.js";import{findAccountNameCollision as g}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as I,suffixedAccountName as M,REGION_SHORT_CODES as u,findTrailingRegionShortCode as x,regionSuffixRejectionMessage as D}from"./naming/connectedAccountName.js";import{normaliseError as l,getErrorMessage as U,hasErrorCode as h,getErrorCode as G,getErrorStack as V,formatErrorString as L}from"./errorUtils.js";import{singleton as H}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as b,filterDangerousEnvVars as K,maskSensitiveOutput as X,parseShellArgs as y,SCOPED_TOKEN_REGEX as W}from"./securityHelpers.js";import{sleep as Y}from"./async/sleep.js";import{mapSettledWithConcurrency as j}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as Z,STRUCTURAL_ENVIRONMENTS as q,ACCOUNT_STAGES as w,ACCOUNT_STAGE_LABELS as J,isAccountStage as Q,ACCOUNT_TIERS as $,AccountTierSchema as ee,isAccountTier as re,environmentToTier as oe,stageFromWireEnvironment as te,accountTier as Ee,getEnvironmentLabel as ae,ACCOUNT_ROLES as Se}from"./environments.js";import{RESOURCE_CATEGORIES as ne,categoriseResource as Ae,getExpectedDuration as ie,getFriendlyResourceType as Te}from"./resourceCategorisation.js";import{parseGitRemoteUrl as me}from"./repo/gitRemoteParser.js";import{abbreviateRegion as se,AWS_REGIONS_METADATA as Oe,DEFAULT_REGION as Ce,getRegionInfo as ce,MAX_SECONDARY_REGIONS as fe,OPT_IN_REGION_CODES as pe,optInRegionWarning as ge,regions as Pe,suggestRegionForTimezone as Ie}from"./infra/regions.js";import{SCOPE_VALUES as ue,MACHINE_ONLY_SCOPES as xe,USER_GRANTABLE_SCOPES as De}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as le,SECRET_NAME_ERROR as Ue,SSM_COMPONENT_PATTERN as he,SSM_COMPONENT_ERROR as Ge,SSM_STANDARD_MAX_VALUE_BYTES as Ve,SecretNamespaceSchema as Le,buildNamespaceParts as ve,buildParameterPath as He,parseParameterPath as Fe,isManageablePath as be,parseDotEnv as Ke,escapeDotEnvValue as Xe}from"./secrets.js";import{ConnectionWireSchema as We,ConnectionsListResponseSchema as Be}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as ke,deriveTargets as je,deriveAllTargets as ze,environmentOrTier as Ze,findTarget as qe,generateTargetName as we}from"./targets.js";import{buildAppConfigPath as Qe}from"./repo/appPath.js";import{findInfrastructurePaths as er,findBoundaryPath as rr,isInfrastructureFile as or}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as Er}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Sr,RESERVED_APP_NAME_MESSAGE as _r,isReservedAppName as nr}from"./naming/reservedAppNames.js";import{deriveContentHashTag as ir,CONTENT_HASH_TAG_PATTERN as Tr}from"./infra/deriveContentHashTag.js";import{DEPLOY_MODES as mr,DeployModeSchema as Nr,IMAGE_TAG_PATTERN as sr,ImageTagSchema as Or,ServiceArtefactSchema as Cr,ServiceArtefactsSchema as cr,ARTEFACT_OUTPUT_FIELDS as fr,artefactOutputKey as pr}from"./infra/deployArtefacts.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Pr,EXPECTED_SCHEMA_VERSION_ENV as Ir,EXPECTED_SCHEMA_VERSION_TOOL_ENV as Mr,EXPECTED_CH_SCHEMA_VERSION_ENV as ur,SCHEMA_ADMIN_USER_ENV as xr,SCHEMA_ADMIN_PASSWORD_ENV as Dr,PRISMA_MIGRATION_DIR_RE as dr,CLICKHOUSE_MIGRATION_SKIP_RE as lr}from"./migration/constants.js";export{Se as ACCOUNT_ROLES,w as ACCOUNT_STAGES,Z as ACCOUNT_STAGES_WITH_ROOT,J as ACCOUNT_STAGE_LABELS,$ as ACCOUNT_TIERS,_ as APPROVAL_TOKEN_OUTPUT_PREFIX,fr as ARTEFACT_OUTPUT_FIELDS,Oe as AWS_REGIONS_METADATA,ee as AccountTierSchema,a as BACKUP_VAULT_NAME,lr as CLICKHOUSE_MIGRATION_SKIP_RE,Tr as CONTENT_HASH_TAG_PATTERN,We as ConnectionWireSchema,Be as ConnectionsListResponseSchema,b as DANGEROUS_ENV_VARS,Ce as DEFAULT_REGION,mr as DEPLOY_MODES,o as DNS_APEX,Nr as DeployModeSchema,ur as EXPECTED_CH_SCHEMA_VERSION_ENV,Ir as EXPECTED_SCHEMA_VERSION_ENV,Mr as EXPECTED_SCHEMA_VERSION_TOOL_ENV,sr as IMAGE_TAG_PATTERN,Or as ImageTagSchema,xe as MACHINE_ONLY_SCOPES,fe as MAX_SECONDARY_REGIONS,Pr as MIGRATION_SNAPSHOT_NAME_PREFIX,pe as OPT_IN_REGION_CODES,dr as PRISMA_MIGRATION_DIR_RE,u as REGION_SHORT_CODES,Sr as RESERVED_APP_NAMES,_r as RESERVED_APP_NAME_MESSAGE,ne as RESOURCE_CATEGORIES,Dr as SCHEMA_ADMIN_PASSWORD_ENV,xr as SCHEMA_ADMIN_USER_ENV,W as SCOPED_TOKEN_REGEX,ue as SCOPE_VALUES,Ue as SECRET_NAME_ERROR,le as SECRET_NAME_PATTERN,Ge as SSM_COMPONENT_ERROR,he as SSM_COMPONENT_PATTERN,Ve as SSM_STANDARD_MAX_VALUE_BYTES,q as STRUCTURAL_ENVIRONMENTS,Le as SecretNamespaceSchema,Cr as ServiceArtefactSchema,cr as ServiceArtefactsSchema,n as TOKEN_STDERR_PREFIX,De as USER_GRANTABLE_SCOPES,se as abbreviateRegion,c as accountConstructKey,Ee as accountTier,pr as artefactOutputKey,Qe as buildAppConfigPath,ve as buildNamespaceParts,He as buildParameterPath,O as capitalise,Ae as categoriseResource,I as defaultConnectedAccountName,ze as deriveAllTargets,ir as deriveContentHashTag,ke as deriveRegionsFromOrgConfig,je as deriveTargets,Ze as environmentOrTier,oe as environmentToTier,Xe as escapeDotEnvValue,K as filterDangerousEnvVars,g as findAccountNameCollision,rr as findBoundaryPath,er as findInfrastructurePaths,qe as findTarget,x as findTrailingRegionShortCode,L as formatErrorString,we as generateTargetName,t as getDomainExportNames,ae as getEnvironmentLabel,G as getErrorCode,U as getErrorMessage,V as getErrorStack,ie as getExpectedDuration,Te as getFriendlyResourceType,ce as getRegionInfo,C as getSafeZoneName,f as hasAsciiStableConstructKey,h as hasErrorCode,i as imageTagParameterName,Er as inferContainerFromCandidates,Q as isAccountStage,re as isAccountTier,or as isInfrastructureFile,be as isManageablePath,nr as isReservedAppName,j as mapSettledWithConcurrency,X as maskSensitiveOutput,l as normaliseError,ge as optInRegionWarning,Ke as parseDotEnv,me as parseGitRemoteUrl,Fe as parseParameterPath,y as parseShellArgs,D as regionSuffixRejectionMessage,Pe as regions,H as singleton,Y as sleep,te as stageFromWireEnvironment,M as suffixedAccountName,Ie as suggestRegionForTimezone,m as toKebab,R as toPascalCase,s as toScreamingSnake,N as toValidDatabaseName};
1
+ import{DNS_APEX as o,getDomainExportNames as E}from"./infra/domainExports.js";import{BACKUP_VAULT_NAME as a}from"./infra/backupVault.js";import{APPROVAL_TOKEN_OUTPUT_PREFIX as S,TOKEN_STDERR_PREFIX as A}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as i}from"./infra/imageTags.js";import{toPascalCase as T,toKebab as N,toValidDatabaseName as m,toScreamingSnake as s,capitalise as O,getSafeZoneName as C,accountConstructKey as c,hasAsciiStableConstructKey as f}from"./naming/caseConversion.js";import{findAccountNameCollision as P}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as I,suffixedAccountName as M,REGION_SHORT_CODES as u,findTrailingRegionShortCode as x,regionSuffixRejectionMessage as D}from"./naming/connectedAccountName.js";import{normaliseError as l,getErrorMessage as U,hasErrorCode as V,getErrorCode as h,getErrorStack as G,formatErrorString as L}from"./errorUtils.js";import{singleton as F}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as X,filterDangerousEnvVars as b,maskSensitiveOutput as K,parseShellArgs as y,SCOPED_TOKEN_REGEX as W}from"./securityHelpers.js";import{sleep as Y}from"./async/sleep.js";import{mapSettledWithConcurrency as j}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as Z,STRUCTURAL_ENVIRONMENTS as q,ACCOUNT_STAGES as w,ACCOUNT_STAGE_LABELS as J,isAccountStage as Q,ACCOUNT_TIERS as $,AccountTierSchema as ee,isAccountTier as re,environmentToTier as oe,stageFromWireEnvironment as Ee,accountTier as te,getEnvironmentLabel as ae,ACCOUNT_ROLES as _e}from"./environments.js";import{RESOURCE_CATEGORIES as Ae,categoriseResource as ne,getExpectedDuration as ie,getFriendlyResourceType as Re}from"./resourceCategorisation.js";import{parseGitRemoteUrl as Ne}from"./repo/gitRemoteParser.js";import{abbreviateRegion as se,AWS_REGIONS_METADATA as Oe,DEFAULT_REGION as Ce,getRegionInfo as ce,MAX_SECONDARY_REGIONS as fe,OPT_IN_REGION_CODES as pe,optInRegionWarning as Pe,regions as ge,suggestRegionForTimezone as Ie}from"./infra/regions.js";import{SCOPE_VALUES as ue,MACHINE_ONLY_SCOPES as xe,USER_GRANTABLE_SCOPES as De}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as le,SECRET_NAME_ERROR as Ue,SSM_COMPONENT_PATTERN as Ve,SSM_COMPONENT_ERROR as he,SSM_STANDARD_MAX_VALUE_BYTES as Ge,SecretNamespaceSchema as Le,buildNamespaceParts as ve,buildParameterPath as Fe,parseParameterPath as He,isManageablePath as Xe,parseDotEnv as be,escapeDotEnvValue as Ke}from"./secrets.js";import{ConnectionWireSchema as We,ConnectionsListResponseSchema as Be}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as ke,deriveTargets as je,deriveAllTargets as ze,environmentOrTier as Ze,findTarget as qe,generateTargetName as we}from"./targets.js";import{buildAppConfigPath as Qe}from"./repo/appPath.js";import{findInfrastructurePaths as er,findBoundaryPath as rr,isInfrastructureFile as or}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as tr}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as _r,RESERVED_APP_NAME_MESSAGE as Sr,isReservedAppName as Ar,RESERVED_APP_NAME_SUFFIX as nr,RESERVED_APP_NAME_SUFFIX_MESSAGE as ir,hasReservedAppNameSuffix as Rr}from"./naming/reservedAppNames.js";import{deriveContentHashTag as Nr,CONTENT_HASH_TAG_PATTERN as mr}from"./infra/deriveContentHashTag.js";import{DEPLOY_MODES as Or,DeployModeSchema as Cr,IMAGE_TAG_PATTERN as cr,ImageTagSchema as fr,ServiceArtefactSchema as pr,ServiceArtefactsSchema as Pr,ARTEFACT_OUTPUT_FIELDS as gr,artefactOutputKey as Ir}from"./infra/deployArtefacts.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as ur,EXPECTED_SCHEMA_VERSION_ENV as xr,EXPECTED_SCHEMA_VERSION_TOOL_ENV as Dr,EXPECTED_CH_SCHEMA_VERSION_ENV as dr,SCHEMA_ADMIN_USER_ENV as lr,SCHEMA_ADMIN_PASSWORD_ENV as Ur,PRISMA_MIGRATION_DIR_RE as Vr,CLICKHOUSE_MIGRATION_SKIP_RE as hr}from"./migration/constants.js";import{PHYSICAL_NAME_FALLBACK_PROPERTIES as Lr}from"./cfn/physicalNameProperties.js";export{_e as ACCOUNT_ROLES,w as ACCOUNT_STAGES,Z as ACCOUNT_STAGES_WITH_ROOT,J as ACCOUNT_STAGE_LABELS,$ as ACCOUNT_TIERS,S as APPROVAL_TOKEN_OUTPUT_PREFIX,gr as ARTEFACT_OUTPUT_FIELDS,Oe as AWS_REGIONS_METADATA,ee as AccountTierSchema,a as BACKUP_VAULT_NAME,hr as CLICKHOUSE_MIGRATION_SKIP_RE,mr as CONTENT_HASH_TAG_PATTERN,We as ConnectionWireSchema,Be as ConnectionsListResponseSchema,X as DANGEROUS_ENV_VARS,Ce as DEFAULT_REGION,Or as DEPLOY_MODES,o as DNS_APEX,Cr as DeployModeSchema,dr as EXPECTED_CH_SCHEMA_VERSION_ENV,xr as EXPECTED_SCHEMA_VERSION_ENV,Dr as EXPECTED_SCHEMA_VERSION_TOOL_ENV,cr as IMAGE_TAG_PATTERN,fr as ImageTagSchema,xe as MACHINE_ONLY_SCOPES,fe as MAX_SECONDARY_REGIONS,ur as MIGRATION_SNAPSHOT_NAME_PREFIX,pe as OPT_IN_REGION_CODES,Lr as PHYSICAL_NAME_FALLBACK_PROPERTIES,Vr as PRISMA_MIGRATION_DIR_RE,u as REGION_SHORT_CODES,_r as RESERVED_APP_NAMES,Sr as RESERVED_APP_NAME_MESSAGE,nr as RESERVED_APP_NAME_SUFFIX,ir as RESERVED_APP_NAME_SUFFIX_MESSAGE,Ae as RESOURCE_CATEGORIES,Ur as SCHEMA_ADMIN_PASSWORD_ENV,lr as SCHEMA_ADMIN_USER_ENV,W as SCOPED_TOKEN_REGEX,ue as SCOPE_VALUES,Ue as SECRET_NAME_ERROR,le as SECRET_NAME_PATTERN,he as SSM_COMPONENT_ERROR,Ve as SSM_COMPONENT_PATTERN,Ge as SSM_STANDARD_MAX_VALUE_BYTES,q as STRUCTURAL_ENVIRONMENTS,Le as SecretNamespaceSchema,pr as ServiceArtefactSchema,Pr as ServiceArtefactsSchema,A as TOKEN_STDERR_PREFIX,De as USER_GRANTABLE_SCOPES,se as abbreviateRegion,c as accountConstructKey,te as accountTier,Ir as artefactOutputKey,Qe as buildAppConfigPath,ve as buildNamespaceParts,Fe as buildParameterPath,O as capitalise,ne as categoriseResource,I as defaultConnectedAccountName,ze as deriveAllTargets,Nr as deriveContentHashTag,ke as deriveRegionsFromOrgConfig,je as deriveTargets,Ze as environmentOrTier,oe as environmentToTier,Ke as escapeDotEnvValue,b as filterDangerousEnvVars,P as findAccountNameCollision,rr as findBoundaryPath,er as findInfrastructurePaths,qe as findTarget,x as findTrailingRegionShortCode,L as formatErrorString,we as generateTargetName,E as getDomainExportNames,ae as getEnvironmentLabel,h as getErrorCode,U as getErrorMessage,G as getErrorStack,ie as getExpectedDuration,Re as getFriendlyResourceType,ce as getRegionInfo,C as getSafeZoneName,f as hasAsciiStableConstructKey,V as hasErrorCode,Rr as hasReservedAppNameSuffix,i as imageTagParameterName,tr as inferContainerFromCandidates,Q as isAccountStage,re as isAccountTier,or as isInfrastructureFile,Xe as isManageablePath,Ar as isReservedAppName,j as mapSettledWithConcurrency,K as maskSensitiveOutput,l as normaliseError,Pe as optInRegionWarning,be as parseDotEnv,Ne as parseGitRemoteUrl,He as parseParameterPath,y as parseShellArgs,D as regionSuffixRejectionMessage,ge as regions,F as singleton,Y as sleep,Ee as stageFromWireEnvironment,M as suffixedAccountName,Ie as suggestRegionForTimezone,N as toKebab,T as toPascalCase,s as toScreamingSnake,m as toValidDatabaseName};
@@ -44,3 +44,30 @@ export declare const RESERVED_APP_NAME_MESSAGE = "This application name is reser
44
44
  * inline, so the lowercasing discipline cannot drift across CLI/webapp/worker.
45
45
  */
46
46
  export declare function isReservedAppName(name: string): boolean;
47
+ /**
48
+ * Reserved application-name suffix: `-cache`.
49
+ *
50
+ * Every app's ECR build-cache repository is derived as
51
+ * `` `${toKebab(appName)}-cache` `` (`buildCacheRepositoryName`,
52
+ * `util/src/docker/cacheRepository.ts`). An application whose own kebab form
53
+ * ends in `-cache` would collide with a sibling app's cache repo: the
54
+ * sibling's deploy session policy exempts that ARN from the foreign-ECR
55
+ * mutation Deny (deploy-core `deploySessionPolicy.ts`) and its cache ensure
56
+ * would overwrite the colliding app repo's lifecycle policy. Rejecting the
57
+ * suffix at create time keeps the `-cache` namespace exclusively
58
+ * derivational.
59
+ */
60
+ export declare const RESERVED_APP_NAME_SUFFIX = "-cache";
61
+ /**
62
+ * Canonical user-facing rejection message for a `-cache`-suffixed name.
63
+ * Shared by every create surface so the wording cannot drift.
64
+ */
65
+ export declare const RESERVED_APP_NAME_SUFFIX_MESSAGE = "Application names ending in '-cache' are reserved for Fjall build-cache repositories.";
66
+ /**
67
+ * Suffix check for the reserved `-cache` namespace. Runs on the kebab form,
68
+ * not the raw lowercase — `WebAppCache` contains no hyphen until kebab-cased,
69
+ * yet derives the colliding repo `web-app-cache`. Consumers MUST route
70
+ * through this helper rather than an inline `.endsWith()` so the kebab-first
71
+ * discipline cannot drift across CLI/webapp/worker.
72
+ */
73
+ export declare function hasReservedAppNameSuffix(name: string): boolean;
@@ -1 +1 @@
1
- const r=["fjall","organisation","platform","account"],o="This application name is reserved for Fjall's organisation-tier infrastructure.";function t(e){return r.includes(e.toLowerCase())}export{r as RESERVED_APP_NAMES,o as RESERVED_APP_NAME_MESSAGE,t as isReservedAppName};
1
+ import{toKebab as r}from"./caseConversion.js";const o=["fjall","organisation","platform","account"],a="This application name is reserved for Fjall's organisation-tier infrastructure.";function i(e){return o.includes(e.toLowerCase())}const t="-cache",s="Application names ending in '-cache' are reserved for Fjall build-cache repositories.";function E(e){return r(e).endsWith(t)}export{o as RESERVED_APP_NAMES,a as RESERVED_APP_NAME_MESSAGE,t as RESERVED_APP_NAME_SUFFIX,s as RESERVED_APP_NAME_SUFFIX_MESSAGE,E as hasReservedAppNameSuffix,i as isReservedAppName};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "2.25.0",
3
+ "version": "2.29.0",
4
4
  "description": "Common utility methods",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
12
  },
13
+ "./buildConcurrency": {
14
+ "types": "./dist/buildConcurrency.d.ts",
15
+ "default": "./dist/buildConcurrency.js"
16
+ },
13
17
  "./config": {
14
18
  "types": "./dist/config.d.ts",
15
19
  "default": "./dist/config.js"
@@ -125,5 +129,5 @@
125
129
  "engines": {
126
130
  "node": ">=22.0.0"
127
131
  },
128
- "gitHead": "7c1a329184064aefa557c2c09de0965c4f8cd4fb"
132
+ "gitHead": "c40ea0e1435400f3f42faaad5bcbf50e4400c1d4"
129
133
  }