@fjall/util 2.30.3 → 2.32.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
- 84 files minified at 2026-07-16T01:01:28.523Z
1
+ 86 files minified at 2026-07-16T21:23:58.809Z
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The Fjall-owned dev-substrate synth entrypoint, as source text.
3
+ *
4
+ * The webapp worker (B2a) materialises this VERBATIM into
5
+ * `fjall/<appName>/infrastructure.ts` in the ephemeral CDK workspace and writes a
6
+ * sibling `dev-substrate-params.json`; deploy-core then runs it via the workspace
7
+ * `cdk.json` (`"app": "npx tsx infrastructure.ts"`). It is held as a string (not a
8
+ * compiled module) so the worker — which has `@fjall/util` but NOT
9
+ * `@fjall/components-infrastructure` — can emit it without importing the construct
10
+ * package; its imports resolve at synth time in the workspace, where the pinned
11
+ * `@fjall/*` packages are installed.
12
+ *
13
+ * The schema/props drift guard is NOT this string (it is not typechecked): it is
14
+ * the compile-time `IDevSubstrateProps` ≡ `DevSubstrateSynthProps` assertion in
15
+ * `@fjall/components-infrastructure`. This string only wires the parsed params into
16
+ * `addDevSubstrate`.
17
+ */
18
+ export declare const DEV_SUBSTRATE_ENTRYPOINT_SOURCE = "#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\n\nimport { App } from \"@fjall/components-infrastructure\";\nimport {\n DevSubstrateParamsSchema,\n DEV_SUBSTRATE_PARAMS_FILENAME\n} from \"@fjall/util\";\n\n// Deliberate throw (not a Result): a malformed/stale params file aborts synth\n// cleanly before any CFN stack exists, so nothing rolls back. The only caller is\n// this entrypoint under cdk synth, which surfaces the ZodError as a synth failure.\nconst params = DevSubstrateParamsSchema.parse(\n JSON.parse(readFileSync(DEV_SUBSTRATE_PARAMS_FILENAME, \"utf-8\"))\n);\n\n// appName is the single source (design VERDICT-3): it drives the CDK stack prefix\n// here AND must equal the on-disk fjall/<appName> dir basename deploy-core derives\n// operation.appName from \u2014 divergence is a hard \"no stacks match\" no-deploy.\nconst app = App.getApp(params.appName, { network: { maxAzs: 2 } });\n\napp.addDevSubstrate(params);\n";
@@ -0,0 +1,23 @@
1
+ const e=`#!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+
4
+ import { App } from "@fjall/components-infrastructure";
5
+ import {
6
+ DevSubstrateParamsSchema,
7
+ DEV_SUBSTRATE_PARAMS_FILENAME
8
+ } from "@fjall/util";
9
+
10
+ // Deliberate throw (not a Result): a malformed/stale params file aborts synth
11
+ // cleanly before any CFN stack exists, so nothing rolls back. The only caller is
12
+ // this entrypoint under cdk synth, which surfaces the ZodError as a synth failure.
13
+ const params = DevSubstrateParamsSchema.parse(
14
+ JSON.parse(readFileSync(DEV_SUBSTRATE_PARAMS_FILENAME, "utf-8"))
15
+ );
16
+
17
+ // appName is the single source (design VERDICT-3): it drives the CDK stack prefix
18
+ // here AND must equal the on-disk fjall/<appName> dir basename deploy-core derives
19
+ // operation.appName from \u2014 divergence is a hard "no stacks match" no-deploy.
20
+ const app = App.getApp(params.appName, { network: { maxAzs: 2 } });
21
+
22
+ app.addDevSubstrate(params);
23
+ `;export{e as DEV_SUBSTRATE_ENTRYPOINT_SOURCE};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The dev-substrate synth params — the versioned wire contract between the webapp
3
+ * worker (PRODUCER: writes the params JSON into the ephemeral CDK workspace) and
4
+ * the Fjall-owned synth entrypoint (CONSUMER: reads + parses it at `cdk synth`).
5
+ * Both sides import THIS module — the single source, so the shape cannot fork
6
+ * (design C2-4 / cross-system-parity). It lives in `@fjall/util` because that is
7
+ * the only `@fjall/*` leaf the prod worker keeps (`npm ci --omit=dev` strips
8
+ * `@fjall/components-infrastructure`, which holds `addDevSubstrate`).
9
+ *
10
+ * Like `manifest/schemas.ts`, this module imports only `zod` — no `fs`, no
11
+ * `path`, no logger — so it stays a dependency-free contract.
12
+ *
13
+ * Co-versioning: `DevSubstrateSynthProps` (below) MUST stay structurally identical
14
+ * to `IDevSubstrateProps` (the `addDevSubstrate` param, in
15
+ * `@fjall/components-infrastructure`). A compile-time parity guard there asserts it
16
+ * both ways, so a field added to one but not the other is a typecheck failure, not
17
+ * a silent stale-params synth.
18
+ */
19
+ import { z } from "zod";
20
+ /** Bump when the params wire shape changes incompatibly; `z.literal`-pinned below. */
21
+ export declare const DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION: 1;
22
+ /**
23
+ * The params file the worker writes into the workspace and the entrypoint reads.
24
+ * A single const both sides share so the filename cannot drift.
25
+ */
26
+ export declare const DEV_SUBSTRATE_PARAMS_FILENAME = "dev-substrate-params.json";
27
+ /** The `addDevSubstrate` props subset — pinned to `IDevSubstrateProps` by the guard. */
28
+ export declare const DevSubstrateSynthPropsSchema: z.ZodObject<{
29
+ appId: z.ZodString;
30
+ appKebab: z.ZodString;
31
+ engineVersion: z.ZodOptional<z.ZodString>;
32
+ adoptSlotEcr: z.ZodOptional<z.ZodBoolean>;
33
+ phase: z.ZodOptional<z.ZodEnum<{
34
+ full: "full";
35
+ zone: "zone";
36
+ }>>;
37
+ domain: z.ZodOptional<z.ZodObject<{
38
+ appDomain: z.ZodString;
39
+ parentDelegationRoleArn: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strict>>;
41
+ }, z.core.$strict>;
42
+ export type DevSubstrateSynthProps = z.infer<typeof DevSubstrateSynthPropsSchema>;
43
+ /**
44
+ * The full wire envelope: the synth props + the version stamp + the single-source
45
+ * `appName`. `appName` is the `fjall-dev-<app>` identity that drives BOTH the CDK
46
+ * stack prefix (`App.getApp(appName)`) AND the on-disk `fjall/<appName>` dir
47
+ * basename deploy-core derives `operation.appName` from — the two are never
48
+ * reconciled in code, so a divergence is a hard "no stacks match" no-deploy
49
+ * (design VERDICT-3). The refine pins `appName === fjall-dev-<appKebab>` so a
50
+ * mis-constructed params object fails at the parse boundary, not at synth.
51
+ */
52
+ export declare const DevSubstrateParamsSchema: z.ZodObject<{
53
+ version: z.ZodLiteral<1>;
54
+ appName: z.ZodString;
55
+ appId: z.ZodString;
56
+ appKebab: z.ZodString;
57
+ engineVersion: z.ZodOptional<z.ZodString>;
58
+ adoptSlotEcr: z.ZodOptional<z.ZodBoolean>;
59
+ phase: z.ZodOptional<z.ZodEnum<{
60
+ full: "full";
61
+ zone: "zone";
62
+ }>>;
63
+ domain: z.ZodOptional<z.ZodObject<{
64
+ appDomain: z.ZodString;
65
+ parentDelegationRoleArn: z.ZodOptional<z.ZodString>;
66
+ }, z.core.$strict>>;
67
+ }, z.core.$strict>;
68
+ export type DevSubstrateParams = z.infer<typeof DevSubstrateParamsSchema>;
@@ -0,0 +1 @@
1
+ import{z as t}from"zod";const n=1,i="dev-substrate-params.json",o=t.object({appDomain:t.string().min(1),parentDelegationRoleArn:t.string().min(1).optional()}).strict(),a={appId:t.string().min(1),appKebab:t.string().min(1),engineVersion:t.string().min(1).optional(),adoptSlotEcr:t.boolean().optional(),phase:t.enum(["zone","full"]).optional(),domain:o.optional()},s=t.object(a).strict(),r=t.object({...a,version:t.literal(n),appName:t.string().min(1)}).strict().refine(e=>e.appName===`fjall-dev-${e.appKebab}`,{message:"appName must equal `fjall-dev-${appKebab}` (single-source identity)",path:["appName"]});export{i as DEV_SUBSTRATE_PARAMS_FILENAME,n as DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION,r as DevSubstrateParamsSchema,s as DevSubstrateSynthPropsSchema};
package/dist/index.d.ts CHANGED
@@ -29,3 +29,5 @@ export { MIGRATION_SNAPSHOT_NAME_PREFIX, EXPECTED_SCHEMA_VERSION_ENV, EXPECTED_S
29
29
  export { PHYSICAL_NAME_FALLBACK_PROPERTIES } from "./cfn/physicalNameProperties.js";
30
30
  export { PATTERN_TYPE_VALUES, type PatternType, PATTERN_TYPES, isPatternType, type PatternArtefact, type PatternStackPlacement, type PatternDescriptor, PATTERN_REGISTRY, type OpenNextPatternType, OPENNEXT_PATTERN_TYPES, isOpenNextPatternType, STATIC_SITE_ROUTING_VALUES, type StaticSiteRouting } from "./patterns/patternTypes.js";
31
31
  export { DEFAULT_FORMS_FROM_LOCAL_PART, defaultFormsFromAddress, defaultFormsCorsOrigin, isAddressAtDomain } from "./patterns/staticSiteForms.js";
32
+ export { DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION, DEV_SUBSTRATE_PARAMS_FILENAME, DevSubstrateSynthPropsSchema, type DevSubstrateSynthProps, DevSubstrateParamsSchema, type DevSubstrateParams } from "./devSubstrate/params.js";
33
+ export { DEV_SUBSTRATE_ENTRYPOINT_SOURCE } from "./devSubstrate/entrypoint.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{DNS_APEX as E,getDomainExportNames as o}from"./infra/domainExports.js";import{BACKUP_VAULT_NAME as _}from"./infra/backupVault.js";import{APPROVAL_TOKEN_OUTPUT_PREFIX as S,TOKEN_STDERR_PREFIX as A}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as n}from"./infra/imageTags.js";import{toPascalCase as i,toKebab as N,toValidDatabaseName as m,toScreamingSnake as s,capitalise as O,getSafeZoneName as P,accountConstructKey as C,hasAsciiStableConstructKey as p}from"./naming/caseConversion.js";import{findAccountNameCollision as c}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as I,suffixedAccountName as M,REGION_SHORT_CODES as x,findTrailingRegionShortCode as u,regionSuffixRejectionMessage as d}from"./naming/connectedAccountName.js";import{normaliseError as l,getErrorMessage as U,hasErrorCode as L,getErrorCode as V,getErrorStack as F,formatErrorString as G}from"./errorUtils.js";import{singleton as v}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as X,filterDangerousEnvVars as b,maskSensitiveOutput as K,parseShellArgs as y,SCOPED_TOKEN_REGEX as Y}from"./securityHelpers.js";import{sleep as B}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 Ee,stageFromWireEnvironment as oe,accountTier as te,getEnvironmentLabel as _e,ACCOUNT_ROLES as ae}from"./environments.js";import{RESOURCE_CATEGORIES as Ae,categoriseResource as Te,getExpectedDuration as ne,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 Pe,getRegionInfo as Ce,MAX_SECONDARY_REGIONS as pe,OPT_IN_REGION_CODES as fe,optInRegionWarning as ce,regions as ge,suggestRegionForTimezone as Ie}from"./infra/regions.js";import{SCOPE_VALUES as xe,MACHINE_ONLY_SCOPES as ue,USER_GRANTABLE_SCOPES as de}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as le,SECRET_NAME_ERROR as Ue,SSM_COMPONENT_PATTERN as Le,SSM_COMPONENT_ERROR as Ve,SSM_STANDARD_MAX_VALUE_BYTES as Fe,SecretNamespaceSchema as Ge,buildNamespaceParts as he,buildParameterPath as ve,parseParameterPath as He,isManageablePath as Xe,parseDotEnv as be,escapeDotEnvValue as Ke}from"./secrets.js";import{ConnectionWireSchema as Ye,ConnectionsListResponseSchema as We}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 Er}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as tr}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as ar,RESERVED_APP_NAME_MESSAGE as Sr,isReservedAppName as Ar,RESERVED_APP_NAME_SUFFIX as Tr,RESERVED_APP_NAME_SUFFIX_MESSAGE as nr,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 Pr,IMAGE_TAG_PATTERN as Cr,ImageTagSchema as pr,ServiceArtefactSchema as fr,ServiceArtefactsSchema as cr,ARTEFACT_OUTPUT_FIELDS as gr,artefactOutputKey as Ir}from"./infra/deployArtefacts.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as xr,EXPECTED_SCHEMA_VERSION_ENV as ur,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 Lr,CLICKHOUSE_MIGRATION_SKIP_RE as Vr}from"./migration/constants.js";import{PHYSICAL_NAME_FALLBACK_PROPERTIES as Gr}from"./cfn/physicalNameProperties.js";import{PATTERN_TYPE_VALUES as vr,PATTERN_TYPES as Hr,isPatternType as Xr,PATTERN_REGISTRY as br,OPENNEXT_PATTERN_TYPES as Kr,isOpenNextPatternType as yr,STATIC_SITE_ROUTING_VALUES as Yr}from"./patterns/patternTypes.js";import{DEFAULT_FORMS_FROM_LOCAL_PART as Br,defaultFormsFromAddress as kr,defaultFormsCorsOrigin as jr,isAddressAtDomain as zr}from"./patterns/staticSiteForms.js";export{ae 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,_ as BACKUP_VAULT_NAME,Vr as CLICKHOUSE_MIGRATION_SKIP_RE,mr as CONTENT_HASH_TAG_PATTERN,Ye as ConnectionWireSchema,We as ConnectionsListResponseSchema,X as DANGEROUS_ENV_VARS,Br as DEFAULT_FORMS_FROM_LOCAL_PART,Pe as DEFAULT_REGION,Or as DEPLOY_MODES,E as DNS_APEX,Pr as DeployModeSchema,Dr as EXPECTED_CH_SCHEMA_VERSION_ENV,ur as EXPECTED_SCHEMA_VERSION_ENV,dr as EXPECTED_SCHEMA_VERSION_TOOL_ENV,Cr as IMAGE_TAG_PATTERN,pr as ImageTagSchema,ue as MACHINE_ONLY_SCOPES,pe as MAX_SECONDARY_REGIONS,xr as MIGRATION_SNAPSHOT_NAME_PREFIX,Kr as OPENNEXT_PATTERN_TYPES,fe as OPT_IN_REGION_CODES,br as PATTERN_REGISTRY,Hr as PATTERN_TYPES,vr as PATTERN_TYPE_VALUES,Gr as PHYSICAL_NAME_FALLBACK_PROPERTIES,Lr as PRISMA_MIGRATION_DIR_RE,x as REGION_SHORT_CODES,ar as RESERVED_APP_NAMES,Sr as RESERVED_APP_NAME_MESSAGE,Tr as RESERVED_APP_NAME_SUFFIX,nr as RESERVED_APP_NAME_SUFFIX_MESSAGE,Ae as RESOURCE_CATEGORIES,Ur as SCHEMA_ADMIN_PASSWORD_ENV,lr as SCHEMA_ADMIN_USER_ENV,Y as SCOPED_TOKEN_REGEX,xe as SCOPE_VALUES,Ue as SECRET_NAME_ERROR,le as SECRET_NAME_PATTERN,Ve as SSM_COMPONENT_ERROR,Le as SSM_COMPONENT_PATTERN,Fe as SSM_STANDARD_MAX_VALUE_BYTES,Yr as STATIC_SITE_ROUTING_VALUES,q as STRUCTURAL_ENVIRONMENTS,Ge as SecretNamespaceSchema,fr as ServiceArtefactSchema,cr 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,he as buildNamespaceParts,ve as buildParameterPath,O as capitalise,Te as categoriseResource,I as defaultConnectedAccountName,jr as defaultFormsCorsOrigin,kr as defaultFormsFromAddress,ze as deriveAllTargets,Nr as deriveContentHashTag,ke as deriveRegionsFromOrgConfig,je as deriveTargets,Ze as environmentOrTier,Ee as environmentToTier,Ke as escapeDotEnvValue,b as filterDangerousEnvVars,c as findAccountNameCollision,rr as findBoundaryPath,er as findInfrastructurePaths,qe as findTarget,u as findTrailingRegionShortCode,G as formatErrorString,we as generateTargetName,o as getDomainExportNames,_e as getEnvironmentLabel,V as getErrorCode,U as getErrorMessage,F as getErrorStack,ne as getExpectedDuration,Re as getFriendlyResourceType,Ce as getRegionInfo,P as getSafeZoneName,p as hasAsciiStableConstructKey,L as hasErrorCode,Rr as hasReservedAppNameSuffix,n as imageTagParameterName,tr as inferContainerFromCandidates,Q as isAccountStage,re as isAccountTier,zr as isAddressAtDomain,Er as isInfrastructureFile,Xe as isManageablePath,yr as isOpenNextPatternType,Xr as isPatternType,Ar as isReservedAppName,j as mapSettledWithConcurrency,K as maskSensitiveOutput,l as normaliseError,ce as optInRegionWarning,be as parseDotEnv,Ne as parseGitRemoteUrl,He as parseParameterPath,y as parseShellArgs,d as regionSuffixRejectionMessage,ge as regions,v as singleton,B as sleep,oe as stageFromWireEnvironment,M as suffixedAccountName,Ie as suggestRegionForTimezone,N as toKebab,i as toPascalCase,s as toScreamingSnake,m as toValidDatabaseName};
1
+ import{DNS_APEX as E,getDomainExportNames as o}from"./infra/domainExports.js";import{BACKUP_VAULT_NAME as S}from"./infra/backupVault.js";import{APPROVAL_TOKEN_OUTPUT_PREFIX as a,TOKEN_STDERR_PREFIX as A}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as R}from"./infra/imageTags.js";import{toPascalCase as i,toKebab as N,toValidDatabaseName as m,toScreamingSnake as s,capitalise as O,getSafeZoneName as P,accountConstructKey as C,hasAsciiStableConstructKey as p}from"./naming/caseConversion.js";import{findAccountNameCollision as c}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as g,suffixedAccountName as M,REGION_SHORT_CODES as D,findTrailingRegionShortCode as x,regionSuffixRejectionMessage as u}from"./naming/connectedAccountName.js";import{normaliseError as U,getErrorMessage as l,hasErrorCode as V,getErrorCode as L,getErrorStack as h,formatErrorString as F}from"./errorUtils.js";import{singleton as v}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as b,filterDangerousEnvVars as X,maskSensitiveOutput as y,parseShellArgs as K,SCOPED_TOKEN_REGEX as Y}from"./securityHelpers.js";import{sleep as W}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 Ee,stageFromWireEnvironment as oe,accountTier as te,getEnvironmentLabel as Se,ACCOUNT_ROLES as _e}from"./environments.js";import{RESOURCE_CATEGORIES as Ae,categoriseResource as Te,getExpectedDuration as Re,getFriendlyResourceType as ne}from"./resourceCategorisation.js";import{parseGitRemoteUrl as Ne}from"./repo/gitRemoteParser.js";import{abbreviateRegion as se,AWS_REGIONS_METADATA as Oe,DEFAULT_REGION as Pe,getRegionInfo as Ce,MAX_SECONDARY_REGIONS as pe,OPT_IN_REGION_CODES as fe,optInRegionWarning as ce,regions as Ie,suggestRegionForTimezone as ge}from"./infra/regions.js";import{SCOPE_VALUES as De,MACHINE_ONLY_SCOPES as xe,USER_GRANTABLE_SCOPES as ue}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as Ue,SECRET_NAME_ERROR as le,SSM_COMPONENT_PATTERN as Ve,SSM_COMPONENT_ERROR as Le,SSM_STANDARD_MAX_VALUE_BYTES as he,SecretNamespaceSchema as Fe,buildNamespaceParts as Ge,buildParameterPath as ve,parseParameterPath as He,isManageablePath as be,parseDotEnv as Xe,escapeDotEnvValue as ye}from"./secrets.js";import{ConnectionWireSchema as Ye,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 Er}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as tr}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as _r,RESERVED_APP_NAME_MESSAGE as ar,isReservedAppName as Ar,RESERVED_APP_NAME_SUFFIX as Tr,RESERVED_APP_NAME_SUFFIX_MESSAGE as Rr,hasReservedAppNameSuffix as nr}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 Pr,IMAGE_TAG_PATTERN as Cr,ImageTagSchema as pr,ServiceArtefactSchema as fr,ServiceArtefactsSchema as cr,ARTEFACT_OUTPUT_FIELDS as Ir,artefactOutputKey as gr}from"./infra/deployArtefacts.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Dr,EXPECTED_SCHEMA_VERSION_ENV as xr,EXPECTED_SCHEMA_VERSION_TOOL_ENV as ur,EXPECTED_CH_SCHEMA_VERSION_ENV as dr,SCHEMA_ADMIN_USER_ENV as Ur,SCHEMA_ADMIN_PASSWORD_ENV as lr,PRISMA_MIGRATION_DIR_RE as Vr,CLICKHOUSE_MIGRATION_SKIP_RE as Lr}from"./migration/constants.js";import{PHYSICAL_NAME_FALLBACK_PROPERTIES as Fr}from"./cfn/physicalNameProperties.js";import{PATTERN_TYPE_VALUES as vr,PATTERN_TYPES as Hr,isPatternType as br,PATTERN_REGISTRY as Xr,OPENNEXT_PATTERN_TYPES as yr,isOpenNextPatternType as Kr,STATIC_SITE_ROUTING_VALUES as Yr}from"./patterns/patternTypes.js";import{DEFAULT_FORMS_FROM_LOCAL_PART as Wr,defaultFormsFromAddress as kr,defaultFormsCorsOrigin as jr,isAddressAtDomain as zr}from"./patterns/staticSiteForms.js";import{DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION as qr,DEV_SUBSTRATE_PARAMS_FILENAME as wr,DevSubstrateSynthPropsSchema as Jr,DevSubstrateParamsSchema as Qr}from"./devSubstrate/params.js";import{DEV_SUBSTRATE_ENTRYPOINT_SOURCE as eE}from"./devSubstrate/entrypoint.js";export{_e as ACCOUNT_ROLES,w as ACCOUNT_STAGES,Z as ACCOUNT_STAGES_WITH_ROOT,J as ACCOUNT_STAGE_LABELS,$ as ACCOUNT_TIERS,a as APPROVAL_TOKEN_OUTPUT_PREFIX,Ir as ARTEFACT_OUTPUT_FIELDS,Oe as AWS_REGIONS_METADATA,ee as AccountTierSchema,S as BACKUP_VAULT_NAME,Lr as CLICKHOUSE_MIGRATION_SKIP_RE,mr as CONTENT_HASH_TAG_PATTERN,Ye as ConnectionWireSchema,Be as ConnectionsListResponseSchema,b as DANGEROUS_ENV_VARS,Wr as DEFAULT_FORMS_FROM_LOCAL_PART,Pe as DEFAULT_REGION,Or as DEPLOY_MODES,eE as DEV_SUBSTRATE_ENTRYPOINT_SOURCE,wr as DEV_SUBSTRATE_PARAMS_FILENAME,qr as DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION,E as DNS_APEX,Pr as DeployModeSchema,Qr as DevSubstrateParamsSchema,Jr as DevSubstrateSynthPropsSchema,dr as EXPECTED_CH_SCHEMA_VERSION_ENV,xr as EXPECTED_SCHEMA_VERSION_ENV,ur as EXPECTED_SCHEMA_VERSION_TOOL_ENV,Cr as IMAGE_TAG_PATTERN,pr as ImageTagSchema,xe as MACHINE_ONLY_SCOPES,pe as MAX_SECONDARY_REGIONS,Dr as MIGRATION_SNAPSHOT_NAME_PREFIX,yr as OPENNEXT_PATTERN_TYPES,fe as OPT_IN_REGION_CODES,Xr as PATTERN_REGISTRY,Hr as PATTERN_TYPES,vr as PATTERN_TYPE_VALUES,Fr as PHYSICAL_NAME_FALLBACK_PROPERTIES,Vr as PRISMA_MIGRATION_DIR_RE,D as REGION_SHORT_CODES,_r as RESERVED_APP_NAMES,ar as RESERVED_APP_NAME_MESSAGE,Tr as RESERVED_APP_NAME_SUFFIX,Rr as RESERVED_APP_NAME_SUFFIX_MESSAGE,Ae as RESOURCE_CATEGORIES,lr as SCHEMA_ADMIN_PASSWORD_ENV,Ur as SCHEMA_ADMIN_USER_ENV,Y as SCOPED_TOKEN_REGEX,De as SCOPE_VALUES,le as SECRET_NAME_ERROR,Ue as SECRET_NAME_PATTERN,Le as SSM_COMPONENT_ERROR,Ve as SSM_COMPONENT_PATTERN,he as SSM_STANDARD_MAX_VALUE_BYTES,Yr as STATIC_SITE_ROUTING_VALUES,q as STRUCTURAL_ENVIRONMENTS,Fe as SecretNamespaceSchema,fr as ServiceArtefactSchema,cr as ServiceArtefactsSchema,A as TOKEN_STDERR_PREFIX,ue as USER_GRANTABLE_SCOPES,se as abbreviateRegion,C as accountConstructKey,te as accountTier,gr as artefactOutputKey,Qe as buildAppConfigPath,Ge as buildNamespaceParts,ve as buildParameterPath,O as capitalise,Te as categoriseResource,g as defaultConnectedAccountName,jr as defaultFormsCorsOrigin,kr as defaultFormsFromAddress,ze as deriveAllTargets,Nr as deriveContentHashTag,ke as deriveRegionsFromOrgConfig,je as deriveTargets,Ze as environmentOrTier,Ee as environmentToTier,ye as escapeDotEnvValue,X as filterDangerousEnvVars,c as findAccountNameCollision,rr as findBoundaryPath,er as findInfrastructurePaths,qe as findTarget,x as findTrailingRegionShortCode,F as formatErrorString,we as generateTargetName,o as getDomainExportNames,Se as getEnvironmentLabel,L as getErrorCode,l as getErrorMessage,h as getErrorStack,Re as getExpectedDuration,ne as getFriendlyResourceType,Ce as getRegionInfo,P as getSafeZoneName,p as hasAsciiStableConstructKey,V as hasErrorCode,nr as hasReservedAppNameSuffix,R as imageTagParameterName,tr as inferContainerFromCandidates,Q as isAccountStage,re as isAccountTier,zr as isAddressAtDomain,Er as isInfrastructureFile,be as isManageablePath,Kr as isOpenNextPatternType,br as isPatternType,Ar as isReservedAppName,j as mapSettledWithConcurrency,y as maskSensitiveOutput,U as normaliseError,ce as optInRegionWarning,Xe as parseDotEnv,Ne as parseGitRemoteUrl,He as parseParameterPath,K as parseShellArgs,u as regionSuffixRejectionMessage,Ie as regions,v as singleton,W as sleep,oe as stageFromWireEnvironment,M as suffixedAccountName,ge as suggestRegionForTimezone,N as toKebab,i as toPascalCase,s as toScreamingSnake,m as toValidDatabaseName};
@@ -44,10 +44,12 @@ export type ImageTag = z.infer<typeof ImageTagSchema>;
44
44
  * `ecrRepositoryArn` is absent for non-ECR registries.
45
45
  *
46
46
  * `functionArn`/`publishedVersion` are the Lambda analogue of
47
- * `taskDefinitionArn`: present only for a container-image Lambda function
48
- * whose image was rebuilt this deploy AND whose post-deploy
49
- * `lambda:PublishVersion` call succeeded (best-effort — a publish failure
50
- * degrades the artefact, it never fails an already-succeeded deploy).
47
+ * `taskDefinitionArn`: present whenever a fresh version was published for a
48
+ * container-image Lambda on a full deploy via the best-effort post-deploy
49
+ * `lambda:PublishVersion` step (a publish failure degrades the artefact, it
50
+ * never fails an already-succeeded deploy), and on code-only rollouts AND
51
+ * rollbacks via `UpdateFunctionCode`'s `Publish: true`, which publishes
52
+ * alongside the update itself.
51
53
  */
52
54
  export declare const ServiceArtefactSchema: z.ZodObject<{
53
55
  serviceName: z.ZodString;
@@ -7,4 +7,4 @@
7
7
  * the barrel — the package.json `exports` map exposes both subpaths.
8
8
  */
9
9
  export { DockerBuildSchema, DockerBuildArgValueSchema, DockerBuildSecretRefSchema, DockerBuildPartialSchema, mergeDockerBuild, ManifestServiceSchema, ManifestPatternSchema, ManifestEcrSchema, ManifestLambdaSchema, ManifestStackHashSchema, ResourceMapEntrySchema, FjallManifestSchema, FJALL_MANIFEST_FILENAME, MANIFEST_SCHEMA_VERSION, BUILDKIT_SECRET_ID_PATTERN, type DockerBuild, type DockerBuildArgValue, type DockerBuildSecretRef, type DockerBuildPartial, type ManifestService, type ManifestPattern, type ManifestEcr, type ManifestLambda, type ManifestStackHash, type ResourceMapEntry, type FjallManifest } from "./schemas.js";
10
- export { getManifestFilePath, readManifestFile, writeManifestFile, createEmptyManifest, readConstructMap, parseDockerServicesFromManifest, parseLambdaDockerServicesFromManifest, parseLambdaDockerEntriesFromManifest, type ManifestDockerService, type ManifestLambdaDockerEntry } from "./io.js";
10
+ export { getManifestFilePath, readManifestFile, writeManifestFile, createEmptyManifest, readConstructMap, parseDockerServicesFromManifest, parseLambdaDockerServicesFromManifest, parseLambdaDockerEntriesFromManifest, parseDockerDeclarationsFromManifest, type ManifestDockerService, type ManifestLambdaDockerEntry, type ManifestDockerDeclarations } from "./io.js";
@@ -1 +1 @@
1
- import{DockerBuildSchema as r,DockerBuildArgValueSchema as t,DockerBuildSecretRefSchema as c,DockerBuildPartialSchema as i,mergeDockerBuild as s,ManifestServiceSchema as m,ManifestPatternSchema as S,ManifestEcrSchema as M,ManifestLambdaSchema as n,ManifestStackHashSchema as o,ResourceMapEntrySchema as f,FjallManifestSchema as h,FJALL_MANIFEST_FILENAME as E,MANIFEST_SCHEMA_VERSION as l,BUILDKIT_SECRET_ID_PATTERN as F}from"./schemas.js";import{getManifestFilePath as D,readManifestFile as k,writeManifestFile as p,createEmptyManifest as u,readConstructMap as A,parseDockerServicesFromManifest as I,parseLambdaDockerServicesFromManifest as L,parseLambdaDockerEntriesFromManifest as _}from"./io.js";export{F as BUILDKIT_SECRET_ID_PATTERN,t as DockerBuildArgValueSchema,i as DockerBuildPartialSchema,r as DockerBuildSchema,c as DockerBuildSecretRefSchema,E as FJALL_MANIFEST_FILENAME,h as FjallManifestSchema,l as MANIFEST_SCHEMA_VERSION,M as ManifestEcrSchema,n as ManifestLambdaSchema,S as ManifestPatternSchema,m as ManifestServiceSchema,o as ManifestStackHashSchema,f as ResourceMapEntrySchema,u as createEmptyManifest,D as getManifestFilePath,s as mergeDockerBuild,I as parseDockerServicesFromManifest,_ as parseLambdaDockerEntriesFromManifest,L as parseLambdaDockerServicesFromManifest,A as readConstructMap,k as readManifestFile,p as writeManifestFile};
1
+ import{DockerBuildSchema as r,DockerBuildArgValueSchema as t,DockerBuildSecretRefSchema as c,DockerBuildPartialSchema as i,mergeDockerBuild as s,ManifestServiceSchema as m,ManifestPatternSchema as S,ManifestEcrSchema as o,ManifestLambdaSchema as M,ManifestStackHashSchema as n,ResourceMapEntrySchema as f,FjallManifestSchema as h,FJALL_MANIFEST_FILENAME as l,MANIFEST_SCHEMA_VERSION as E,BUILDKIT_SECRET_ID_PATTERN as D}from"./schemas.js";import{getManifestFilePath as d,readManifestFile as k,writeManifestFile as p,createEmptyManifest as u,readConstructMap as A,parseDockerServicesFromManifest as I,parseLambdaDockerServicesFromManifest as L,parseLambdaDockerEntriesFromManifest as _,parseDockerDeclarationsFromManifest as B}from"./io.js";export{D as BUILDKIT_SECRET_ID_PATTERN,t as DockerBuildArgValueSchema,i as DockerBuildPartialSchema,r as DockerBuildSchema,c as DockerBuildSecretRefSchema,l as FJALL_MANIFEST_FILENAME,h as FjallManifestSchema,E as MANIFEST_SCHEMA_VERSION,o as ManifestEcrSchema,M as ManifestLambdaSchema,S as ManifestPatternSchema,m as ManifestServiceSchema,n as ManifestStackHashSchema,f as ResourceMapEntrySchema,u as createEmptyManifest,d as getManifestFilePath,s as mergeDockerBuild,B as parseDockerDeclarationsFromManifest,I as parseDockerServicesFromManifest,_ as parseLambdaDockerEntriesFromManifest,L as parseLambdaDockerServicesFromManifest,A as readConstructMap,k as readManifestFile,p as writeManifestFile};
@@ -75,6 +75,14 @@ export declare function parseDockerServicesFromManifest(cdkOutPath: string): Man
75
75
  * Returns an empty array if the manifest does not exist, cannot be parsed, or
76
76
  * contains no Lambda Docker entries. Lambdas without a `docker` block
77
77
  * (bring-your-own-image) are skipped, not an error.
78
+ *
79
+ * Throws deliberately (not a `Result`) on divergent shared-`imageKey` docker
80
+ * configs even though `Result`-returning callers sit above it: the throw is a
81
+ * config error surfaced before any build starts, and both production
82
+ * boundaries convert it — the CLI's `catch` in `runApplicationDeployment`
83
+ * (`cli/src/services/deployment/applicationDeployment.ts`) and the webapp
84
+ * worker's job-level `catch` in `deploymentJobHandler`. Sanctioned per the
85
+ * 2026-07-16 docker-lambda review; do not re-litigate as a Pitfall-4 escape.
78
86
  */
79
87
  export declare function parseLambdaDockerServicesFromManifest(cdkOutPath: string): ManifestDockerService[];
80
88
  /** A single container-Lambda manifest entry that declared a `docker` block. */
@@ -94,3 +102,36 @@ export interface ManifestLambdaDockerEntry {
94
102
  * (`lambda:PublishVersion` takes a function name, not an image key).
95
103
  */
96
104
  export declare function parseLambdaDockerEntriesFromManifest(cdkOutPath: string): ManifestLambdaDockerEntry[];
105
+ /**
106
+ * Every Docker build the post-synth manifest declares, across BOTH
107
+ * independent namespaces: ECS service `docker` configs (`manifest.services`)
108
+ * and container-Lambda `docker` blocks (`manifest.lambdas`).
109
+ */
110
+ export interface ManifestDockerDeclarations {
111
+ readonly ecsServices: ManifestDockerService[];
112
+ readonly lambdaEntries: ManifestLambdaDockerEntry[];
113
+ /**
114
+ * True when either namespace declares at least one build. This is the
115
+ * canonical "does this app need a Docker build?" presence gate — see
116
+ * `parseDockerDeclarationsFromManifest`.
117
+ */
118
+ readonly declaresBuild: boolean;
119
+ }
120
+ /**
121
+ * Aggregate view of every Docker build declared in the manifest. Presence
122
+ * gates ("should a Docker build run for this app?") MUST consume
123
+ * `declaresBuild` from here rather than composing the namespace parsers
124
+ * themselves: the namespaces are independent, and a gate that checks only one
125
+ * silently misses apps whose builds live entirely in the other — detection
126
+ * once checked only `manifest.services`, so a container-Lambda-only app never
127
+ * built and fell back to welcome-image seeding.
128
+ *
129
+ * When adding a new docker-carrying manifest namespace, extend this aggregate
130
+ * (fields + `declaresBuild`) in the same change as the build-group
131
+ * composition in deploy-core's `dockerBuildHelper` — the two must agree on
132
+ * emptiness, or presence and execution drift apart again.
133
+ *
134
+ * Never throws: both underlying parsers return `[]` on a missing or
135
+ * malformed manifest.
136
+ */
137
+ export declare function parseDockerDeclarationsFromManifest(cdkOutPath: string): ManifestDockerDeclarations;
@@ -1 +1 @@
1
- import{readFile as g,writeFile as y,unlink as h,rename as M,mkdir as b}from"fs/promises";import{readFileSync as S}from"fs";import{dirname as k,join as f}from"path";import{logger as s}from"../logger.js";import{fileExists as A}from"../fsHelpers.js";import{getErrorMessage as d}from"../errorUtils.js";import{recordToConstructMap as F}from"../constructMap.js";import{DockerBuildSchema as w,FjallManifestSchema as x,FJALL_MANIFEST_FILENAME as u,MANIFEST_SCHEMA_VERSION as E}from"./schemas.js";function m(e){return f(e,u)}async function j(e){const n=m(e);if(!await A(n))return null;try{const r=await g(n,"utf-8"),t=JSON.parse(r),i=x.safeParse(t);return i.success||s.debug("FjallManifest","Manifest validation failed",{path:n,errors:i.error.issues.map(a=>`${a.path.join(".")}: ${a.message}`)}),i.success?i.data:null}catch(r){return s.debug("FjallManifest","Failed to read manifest file",{path:n,error:d(r)}),null}}async function C(e,n){const r=m(e),t=`${r}.${Date.now()}.tmp`;await b(k(r),{recursive:!0});try{await y(t,JSON.stringify(n,null,2),"utf-8"),await M(t,r)}catch(i){try{await h(t)}catch(a){s.debug("FjallManifest","Temp file cleanup failed (non-fatal)",{path:t,error:d(a)})}throw i}}function T(e){return{version:E,generatedAt:new Date().toISOString(),appName:e,services:[],lambdas:[],stacks:{}}}async function _(e){const n=await j(e);return n?.resourceMap?F(n.resourceMap):new Map}function o(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function p(e){if(!o(e)||typeof e.path!="string"||e.path.length===0)return;const n=typeof e.context=="string"&&e.context.length>0?e.context:void 0,r=typeof e.target=="string"&&e.target.length>0?e.target:void 0,t=o(e.buildArgs)&&Object.keys(e.buildArgs).length>0?e.buildArgs:void 0,i=Array.isArray(e.buildSecrets)&&e.buildSecrets.length>0?e.buildSecrets:void 0,a={path:e.path,...n!==void 0&&{context:n},...r!==void 0&&{target:r},...t!==void 0&&{buildArgs:t},...i!==void 0&&{buildSecrets:i}},c=w.safeParse(a);return c.success?c.data:void 0}function l(e,n){const r=f(e,u);let t;try{t=S(r,"utf-8")}catch{s.debug("FjallManifest",`Manifest file not readable \u2014 no ${n} extracted`,{path:r});return}let i;try{i=JSON.parse(t)}catch{s.debug("FjallManifest",`Manifest is not valid JSON \u2014 no ${n} extracted`,{path:r});return}return o(i)?i:void 0}function R(e){const n=l(e,"Docker services");if(n===void 0||!Array.isArray(n.services))return[];const r=[];for(const t of n.services){if(!o(t)||typeof t.name!="string")continue;const i=p(t.docker);i!==void 0&&r.push({name:t.name,docker:i})}return r}function B(e){const n=new Map;for(const{name:r,imageKey:t,docker:i}of D(e)){const a=n.get(t);if(a===void 0){n.set(t,{name:t,docker:i});continue}if(JSON.stringify(a.docker)!==JSON.stringify(i))throw new Error(`Lambda functions sharing image key "${t}" declare different \`docker\` configs \u2014 every Lambda function sharing an \`image\` must build from the identical Dockerfile/context/target/buildArgs (offending function: "${r}").`)}return Array.from(n.values())}function D(e){const n=l(e,"Lambda Docker entries");if(n===void 0||!Array.isArray(n.lambdas))return[];const r=[];for(const t of n.lambdas){if(!o(t)||typeof t.name!="string")continue;const i=p(t.docker);if(i===void 0)continue;const a=typeof t.imageKey=="string"&&t.imageKey.length>0?t.imageKey:t.name;r.push({name:t.name,imageKey:a,docker:i})}return r}export{T as createEmptyManifest,m as getManifestFilePath,R as parseDockerServicesFromManifest,D as parseLambdaDockerEntriesFromManifest,B as parseLambdaDockerServicesFromManifest,_ as readConstructMap,j as readManifestFile,C as writeManifestFile};
1
+ import{readFile as g,writeFile as y,unlink as M,rename as h,mkdir as F}from"fs/promises";import{readFileSync as b}from"fs";import{dirname as k,join as c}from"path";import{logger as o}from"../logger.js";import{fileExists as w}from"../fsHelpers.js";import{getErrorMessage as f}from"../errorUtils.js";import{recordToConstructMap as S}from"../constructMap.js";import{FjallManifestSchema as A,FJALL_MANIFEST_FILENAME as u,MANIFEST_SCHEMA_VERSION as x,normaliseDockerBuild as d}from"./schemas.js";function m(t){return c(t,u)}async function E(t){const r=m(t);if(!await w(r))return null;try{const n=await g(r,"utf-8"),e=JSON.parse(n),a=A.safeParse(e);return a.success||o.debug("FjallManifest","Manifest validation failed",{path:r,errors:a.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`)}),a.success?a.data:null}catch(n){return o.debug("FjallManifest","Failed to read manifest file",{path:r,error:f(n)}),null}}async function K(t,r){const n=m(t),e=`${n}.${Date.now()}.tmp`;await F(k(n),{recursive:!0});try{await y(e,JSON.stringify(r,null,2),"utf-8"),await h(e,n)}catch(a){try{await M(e)}catch(i){o.debug("FjallManifest","Temp file cleanup failed (non-fatal)",{path:e,error:f(i)})}throw a}}function P(t){return{version:x,generatedAt:new Date().toISOString(),appName:t,services:[],lambdas:[],stacks:{}}}async function T(t){const r=await E(t);return r?.resourceMap?S(r.resourceMap):new Map}function s(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function l(t,r){const n=c(t,u);let e;try{e=b(n,"utf-8")}catch{o.debug("FjallManifest",`Manifest file not readable \u2014 no ${r} extracted`,{path:n});return}let a;try{a=JSON.parse(e)}catch{o.debug("FjallManifest",`Manifest is not valid JSON \u2014 no ${r} extracted`,{path:n});return}return s(a)?a:void 0}function v(t){const r=l(t,"Docker services");if(r===void 0||!Array.isArray(r.services))return[];const n=[];for(const e of r.services){if(!s(e)||typeof e.name!="string")continue;const a=d(e.docker);a!==void 0&&n.push({name:e.name,docker:a})}return n}function _(t){const r=new Map;for(const{name:n,imageKey:e,docker:a}of p(t)){const i=r.get(e);if(i===void 0){r.set(e,{name:e,docker:a});continue}if(JSON.stringify(i.docker)!==JSON.stringify(a))throw new Error(`Lambda functions sharing image key "${e}" 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(r.values())}function p(t){const r=l(t,"Lambda Docker entries");if(r===void 0||!Array.isArray(r.lambdas))return[];const n=[];for(const e of r.lambdas){if(!s(e)||typeof e.name!="string")continue;const a=d(e.docker);if(a===void 0)continue;const i=typeof e.imageKey=="string"&&e.imageKey.length>0?e.imageKey:e.name;n.push({name:e.name,imageKey:i,docker:a})}return n}function C(t){const r=v(t),n=p(t);return{ecsServices:r,lambdaEntries:n,declaresBuild:r.length>0||n.length>0}}export{P as createEmptyManifest,m as getManifestFilePath,C as parseDockerDeclarationsFromManifest,v as parseDockerServicesFromManifest,p as parseLambdaDockerEntriesFromManifest,_ as parseLambdaDockerServicesFromManifest,T as readConstructMap,E as readManifestFile,K as writeManifestFile};
@@ -176,6 +176,35 @@ export type DockerBuildPartial = z.infer<typeof DockerBuildPartialSchema>;
176
176
  * invoke this when they have a service-side `docker` to merge against.
177
177
  */
178
178
  export declare function mergeDockerBuild(service: DockerBuild, migrationsOverride: DockerBuildPartial | undefined): DockerBuild;
179
+ /**
180
+ * Normalise a candidate `docker` sub-object: empty-string fields are coerced
181
+ * to undefined at the parser boundary so downstream consumers never have to
182
+ * distinguish `target: ""` from `target: undefined` (AC17).
183
+ *
184
+ * `buildArgs` is carried through verbatim — it is the only channel by which
185
+ * Vite-style `VITE_*` values reach the production client bundle, so dropping it
186
+ * here ships a bundle built with no `--build-arg` flags. A value may be a plain
187
+ * string (the common case) OR the public-but-sensitive object form
188
+ * (`DockerBuildArgValue`); both are validated by the final `DockerBuildSchema`
189
+ * safeParse below (a malformed value makes the whole docker candidate invalid,
190
+ * never throwing — safeParse returns undefined). An empty map is coerced to
191
+ * undefined for parity with the empty-string scalar handling.
192
+ *
193
+ * `buildSecrets` is carried through verbatim too — an empty array is coerced to
194
+ * undefined for parity. Each ref is validated by the final `DockerBuildSchema`
195
+ * safeParse below.
196
+ */
197
+ export declare function normaliseDockerBuild(value: unknown): DockerBuild | undefined;
198
+ /**
199
+ * Canonical comparator for "identical docker configs". Both deploy-time
200
+ * guards (assertNoCrossEntityBuildKeyDivergence in deploy-core's
201
+ * dockerBuildHelper; parseLambdaDockerServicesFromManifest in ./io.ts) and
202
+ * the synth-time guard (getOrCreateImageTagParameter in
203
+ * @fjall/components-infrastructure) must agree byte-for-byte — the
204
+ * fingerprint routes through normaliseDockerBuild so the empty-value
205
+ * coercions and field order match on every side.
206
+ */
207
+ export declare function dockerBuildFingerprint(docker: DockerBuild | undefined): string | undefined;
179
208
  declare const ManifestServiceSchema: z.ZodObject<{
180
209
  name: z.ZodString;
181
210
  clusterName: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- import{z as e}from"zod";import{PATTERN_TYPE_VALUES as u}from"../patterns/patternTypes.js";const M="fjall-manifest.json",b=1,f=/^[A-Za-z0-9_.-]+$/,y=e.object({id:e.string().min(1,"buildSecret id cannot be empty").regex(f,"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)"})}),S=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)"})})]),o=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(),S).optional(),buildSecrets:e.array(y).optional()}).strict(),A=o.partial();function j(t,n){if(n===void 0)return t;const a=n.context??t.context,r=n.target??t.target,s=n.buildArgs??t.buildArgs,i=n.buildSecrets??t.buildSecrets;return{path:n.path??t.path,...a!==void 0&&{context:a},...r!==void 0&&{target:r},...s!==void 0&&{buildArgs:s},...i!==void 0&&{buildSecrets:i}}}const c=e.object({name:e.string(),clusterName:e.string().optional(),docker:o.optional(),containerPort:e.number().optional(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional()}).strict(),m=e.object({type:e.enum(u),name:e.string(),source:e.string()}).strict(),l=e.object({repositoryName:e.string()}).strict(),p=e.object({name:e.string(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional(),docker:o.optional(),imageKey:e.string().optional()}).strict(),d=e.object({templateHash:e.string(),synthTimestamp:e.string()}).strict(),g=e.object({constructPath:e.string().max(512),group:e.string().max(128),resourceType:e.string().max(256)}).strict(),E=e.object({version:e.literal(b),generatedAt:e.string(),appName:e.string(),services:e.array(c),lambdas:e.array(p),pattern:m.optional(),ecr:l.optional(),stacks:e.record(e.string(),d),resourceMap:e.record(e.string(),g).optional()}).strict();export{f as BUILDKIT_SECRET_ID_PATTERN,S as DockerBuildArgValueSchema,A as DockerBuildPartialSchema,o as DockerBuildSchema,y as DockerBuildSecretRefSchema,M as FJALL_MANIFEST_FILENAME,E as FjallManifestSchema,b as MANIFEST_SCHEMA_VERSION,l as ManifestEcrSchema,p as ManifestLambdaSchema,m as ManifestPatternSchema,c as ManifestServiceSchema,d as ManifestStackHashSchema,g as ResourceMapEntrySchema,j as mergeDockerBuild};
1
+ import{z as e}from"zod";import{PATTERN_TYPE_VALUES as b}from"../patterns/patternTypes.js";const E="fjall-manifest.json",y=1,S=/^[A-Za-z0-9_.-]+$/,h=e.object({id:e.string().min(1,"buildSecret id cannot be empty").regex(S,"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)"})}),x=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)"})})]),a=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(),x).optional(),buildSecrets:e.array(h).optional()}).strict(),k=a.partial();function N(t,n){if(n===void 0)return t;const s=n.context??t.context,r=n.target??t.target,o=n.buildArgs??t.buildArgs,i=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},...i!==void 0&&{buildSecrets:i}}}function d(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function A(t){if(!d(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=d(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,i={path:t.path,...n!==void 0&&{context:n},...s!==void 0&&{target:s},...r!==void 0&&{buildArgs:r},...o!==void 0&&{buildSecrets:o}},c=a.safeParse(i);return c.success?c.data:void 0}function T(t){if(t===void 0)return;const n=A(t);return n===void 0?void 0:JSON.stringify(n)}const m=e.object({name:e.string(),clusterName:e.string().optional(),docker:a.optional(),containerPort:e.number().optional(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional()}).strict(),p=e.object({type:e.enum(b),name:e.string(),source:e.string()}).strict(),g=e.object({repositoryName:e.string()}).strict(),l=e.object({name:e.string(),secrets:e.array(e.string()).optional(),ssmSecretsPath:e.string().optional(),importedSecretNames:e.array(e.string()).optional(),docker:a.optional(),imageKey:e.string().optional()}).strict(),u=e.object({templateHash:e.string(),synthTimestamp:e.string()}).strict(),f=e.object({constructPath:e.string().max(512),group:e.string().max(128),resourceType:e.string().max(256)}).strict(),I=e.object({version:e.literal(y),generatedAt:e.string(),appName:e.string(),services:e.array(m),lambdas:e.array(l),pattern:p.optional(),ecr:g.optional(),stacks:e.record(e.string(),u),resourceMap:e.record(e.string(),f).optional()}).strict();export{S as BUILDKIT_SECRET_ID_PATTERN,x as DockerBuildArgValueSchema,k as DockerBuildPartialSchema,a as DockerBuildSchema,h as DockerBuildSecretRefSchema,E as FJALL_MANIFEST_FILENAME,I as FjallManifestSchema,y as MANIFEST_SCHEMA_VERSION,g as ManifestEcrSchema,l as ManifestLambdaSchema,p as ManifestPatternSchema,m as ManifestServiceSchema,u as ManifestStackHashSchema,f as ResourceMapEntrySchema,T as dockerBuildFingerprint,N as mergeDockerBuild,A as normaliseDockerBuild};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "2.30.3",
3
+ "version": "2.32.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": "d398edc0c0edf661d7ab6e5a47623529f68dfd2a"
137
+ "gitHead": "876a79a9ad0031a5919019c86867c28c1ab9dc92"
138
138
  }