@fjall/util 2.23.1 → 2.24.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
- 73 files minified at 2026-07-02T12:17:29.289Z
1
+ 75 files minified at 2026-07-05T07:50:20.044Z
@@ -0,0 +1,21 @@
1
+ export interface StaleCdkTmpdir {
2
+ readonly path: string;
3
+ readonly sizeBytes: number;
4
+ readonly mtime: Date;
5
+ }
6
+ export interface FindOptions {
7
+ /** Base tmpdir to scan. Defaults to os.tmpdir(); overridden by tests. */
8
+ readonly tmpdirPath?: string;
9
+ /** Minimum age before a dir is reported. Defaults to 24h. */
10
+ readonly maxAgeMs?: number;
11
+ }
12
+ export interface CleanupResult {
13
+ readonly removed: number;
14
+ readonly failed: number;
15
+ readonly bytesReclaimed: number;
16
+ }
17
+ export declare function findStaleCdkTmpdirs(options?: FindOptions): Promise<StaleCdkTmpdir[]>;
18
+ export declare function removeStaleCdkTmpdirs(dirs: readonly StaleCdkTmpdir[]): Promise<CleanupResult>;
19
+ /** Find + remove in one call — the shape vitest teardown reapers consume. */
20
+ export declare function cleanupStaleCdkTmpdirs(options?: FindOptions): Promise<CleanupResult>;
21
+ export declare function formatBytes(bytes: number): string;
@@ -0,0 +1 @@
1
+ import{readdir as d,stat as l,rm as h}from"fs/promises";import{tmpdir as w}from"os";import{join as m}from"path";import{logger as g}from"./logger.js";const B="cdkTmpdirCleanup";function o(e,t,r={}){g.debug(B,e,{error:t instanceof Error?t.message:String(t),...r})}const T="cdk.out",x=1440*60*1e3;async function u(e){let t=0,r;try{r=await d(e,{withFileTypes:!0})}catch(i){return o("readdir failed",i,{path:e}),0}for(const i of r){const a=m(e,i.name);if(i.isDirectory())t+=await u(a);else if(i.isFile())try{const n=await l(a);t+=n.size}catch(n){o("stat failed",n,{path:a})}}return t}async function D(e={}){const t=e.tmpdirPath??w(),r=e.maxAgeMs??x,i=Date.now()-r;let a;try{a=await d(t)}catch(s){return o("tmpdir readdir failed",s,{root:t}),[]}const n=[];for(const s of a){if(!s.startsWith(T))continue;const c=m(t,s);let f;try{f=await l(c)}catch(y){o("stat failed",y,{path:c});continue}if(!f.isDirectory()||f.mtimeMs>i)continue;const p=await u(c);n.push({path:c,sizeBytes:p,mtime:f.mtime})}return n}async function M(e){let t=0,r=0,i=0;for(const a of e)try{await h(a.path,{recursive:!0,force:!0}),t+=1,i+=a.sizeBytes}catch(n){o("rm failed",n,{path:a.path}),r+=1}return{removed:t,failed:r,bytesReclaimed:i}}async function E(e={}){const t=await D(e);return M(t)}function F(e){const t=["B","KB","MB","GB","TB"];let r=e,i=0;for(;r>=1024&&i<t.length-1;)r/=1024,i+=1;return`${r.toFixed(r<10?2:1)} ${t[i]}`}export{E as cleanupStaleCdkTmpdirs,D as findStaleCdkTmpdirs,F as formatBytes,M as removeStaleCdkTmpdirs};
package/dist/config.d.ts CHANGED
@@ -25,12 +25,8 @@ export type S3BpaMode = (typeof S3_BPA_MODES)[number];
25
25
  * 2026-06-10-centralised-root-access-default-on). Absent ⇒ "centralised"
26
26
  * (default-on); "off" maps to OrgSetupConfig.skipRootAccessManagement at the
27
27
  * adapter boundaries (CLI buildOrgSetupConfig, webapp setup route). The
28
- * webapp carries a twin of this pair at
29
- * webapp/app/.server/constants/rootAccess.ts.
30
- * TODO 2026-06-11, owner: paul — tracked in
31
- * aiDocs/plans/tasks/2026-06-11-root-access-p2-t11-org-config-field-fjall.md:
32
- * once a published @fjall/util ships this tuple, convert the webapp twin into
33
- * a re-export from "@fjall/util/config" (TRAIL_LIFECYCLE_STATES precedent).
28
+ * webapp re-exports this tuple at webapp/app/.server/constants/rootAccess.ts
29
+ * (P2-T11, TRAIL_LIFECYCLE_STATES precedent).
34
30
  */
35
31
  export declare const ROOT_ACCESS_MANAGEMENT_MODES: readonly ["centralised", "off"];
36
32
  export type RootAccessManagementMode = (typeof ROOT_ACCESS_MANAGEMENT_MODES)[number];
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { ACCOUNT_STAGES_WITH_ROOT, STRUCTURAL_ENVIRONMENTS, ACCOUNT_STAGES, ACCO
14
14
  export { RESOURCE_CATEGORIES, type ResourceCategory, categoriseResource, getExpectedDuration, getFriendlyResourceType } from "./resourceCategorisation.js";
15
15
  export { parseGitRemoteUrl, type GitProvider, type ParsedGitRemote } from "./repo/gitRemoteParser.js";
16
16
  export { abbreviateRegion, AWS_REGIONS_METADATA, DEFAULT_REGION, getRegionInfo, MAX_SECONDARY_REGIONS, OPT_IN_REGION_CODES, optInRegionWarning, regions, suggestRegionForTimezone, type RegionCode, type RegionInfo } from "./infra/regions.js";
17
- export { SCOPE_VALUES, type TokenScope } from "./infra/tokenScopes.js";
17
+ export { SCOPE_VALUES, MACHINE_ONLY_SCOPES, USER_GRANTABLE_SCOPES, type TokenScope } from "./infra/tokenScopes.js";
18
18
  export { SECRET_NAME_PATTERN, SECRET_NAME_ERROR, SSM_COMPONENT_PATTERN, SSM_COMPONENT_ERROR, SSM_STANDARD_MAX_VALUE_BYTES, SecretNamespaceSchema, type SecretNamespace, buildNamespaceParts, buildParameterPath, parseParameterPath, isManageablePath, parseDotEnv, escapeDotEnvValue } from "./secrets.js";
19
19
  export { ConnectionWireSchema, type ConnectionWire, ConnectionsListResponseSchema, type ConnectionsListResponse } from "./infra/connectionsWire.js";
20
20
  export { deriveRegionsFromOrgConfig, deriveTargets, deriveAllTargets, environmentOrTier, findTarget, generateTargetName, type OrgConfigRegions, type TargetAccount, type DerivedTarget } from "./targets.js";
@@ -24,4 +24,5 @@ export { findInfrastructurePaths, findBoundaryPath, isInfrastructureFile, type M
24
24
  export { inferContainerFromCandidates } from "./repo/inferContainerFromCandidates.js";
25
25
  export { RESERVED_APP_NAMES, type ReservedAppName, RESERVED_APP_NAME_MESSAGE, isReservedAppName } from "./naming/reservedAppNames.js";
26
26
  export { deriveContentHashTag } from "./infra/deriveContentHashTag.js";
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";
27
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";
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 i}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as _}from"./infra/imageTags.js";import{toPascalCase as m,toKebab as R,toValidDatabaseName as s,toScreamingSnake as N,capitalise as T,getSafeZoneName as C,accountConstructKey as p,hasAsciiStableConstructKey as O}from"./naming/caseConversion.js";import{findAccountNameCollision as c}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as P,suffixedAccountName as I,REGION_SHORT_CODES as u,findTrailingRegionShortCode as x,regionSuffixRejectionMessage as M}from"./naming/connectedAccountName.js";import{normaliseError as l,getErrorMessage as D,hasErrorCode as V,getErrorCode as U,getErrorStack as h,formatErrorString as G}from"./errorUtils.js";import{singleton as L}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as H,filterDangerousEnvVars as F,maskSensitiveOutput as X,parseShellArgs as K}from"./securityHelpers.js";import{sleep as y}from"./async/sleep.js";import{mapSettledWithConcurrency as k}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as j,STRUCTURAL_ENVIRONMENTS as z,ACCOUNT_STAGES as Z,ACCOUNT_STAGE_LABELS as q,isAccountStage as w,ACCOUNT_TIERS as J,AccountTierSchema as Q,isAccountTier as $,environmentToTier as ee,stageFromWireEnvironment as re,accountTier as oe,getEnvironmentLabel as te,ACCOUNT_ROLES as Ee}from"./environments.js";import{RESOURCE_CATEGORIES as ne,categoriseResource as ie,getExpectedDuration as Se,getFriendlyResourceType as _e}from"./resourceCategorisation.js";import{parseGitRemoteUrl as me}from"./repo/gitRemoteParser.js";import{abbreviateRegion as se,AWS_REGIONS_METADATA as Ne,DEFAULT_REGION as Te,getRegionInfo as Ce,MAX_SECONDARY_REGIONS as pe,OPT_IN_REGION_CODES as Oe,optInRegionWarning as fe,regions as ce,suggestRegionForTimezone as ge}from"./infra/regions.js";import{SCOPE_VALUES as Ie}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as xe,SECRET_NAME_ERROR as Me,SSM_COMPONENT_PATTERN as de,SSM_COMPONENT_ERROR as le,SSM_STANDARD_MAX_VALUE_BYTES as De,SecretNamespaceSchema as Ve,buildNamespaceParts as Ue,buildParameterPath as he,parseParameterPath as Ge,isManageablePath as ve,parseDotEnv as Le,escapeDotEnvValue as be}from"./secrets.js";import{ConnectionWireSchema as Fe,ConnectionsListResponseSchema as Xe}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as We,deriveTargets as ye,deriveAllTargets as Be,environmentOrTier as ke,findTarget as Ye,generateTargetName as je}from"./targets.js";import{buildAppConfigPath as Ze}from"./repo/appPath.js";import{findInfrastructurePaths as we,findBoundaryPath as Je,isInfrastructureFile as Qe}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as er}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as or,RESERVED_APP_NAME_MESSAGE as tr,isReservedAppName as Er}from"./naming/reservedAppNames.js";import{deriveContentHashTag as nr}from"./infra/deriveContentHashTag.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Sr,EXPECTED_SCHEMA_VERSION_ENV as _r,EXPECTED_SCHEMA_VERSION_TOOL_ENV as Ar,EXPECTED_CH_SCHEMA_VERSION_ENV as mr,SCHEMA_ADMIN_USER_ENV as Rr,SCHEMA_ADMIN_PASSWORD_ENV as sr,PRISMA_MIGRATION_DIR_RE as Nr,CLICKHOUSE_MIGRATION_SKIP_RE as Tr}from"./migration/constants.js";export{Ee as ACCOUNT_ROLES,Z as ACCOUNT_STAGES,j as ACCOUNT_STAGES_WITH_ROOT,q as ACCOUNT_STAGE_LABELS,J as ACCOUNT_TIERS,i as APPROVAL_TOKEN_OUTPUT_PREFIX,Ne as AWS_REGIONS_METADATA,Q as AccountTierSchema,a as BACKUP_VAULT_NAME,Tr as CLICKHOUSE_MIGRATION_SKIP_RE,Fe as ConnectionWireSchema,Xe as ConnectionsListResponseSchema,H as DANGEROUS_ENV_VARS,Te as DEFAULT_REGION,o as DNS_APEX,mr as EXPECTED_CH_SCHEMA_VERSION_ENV,_r as EXPECTED_SCHEMA_VERSION_ENV,Ar as EXPECTED_SCHEMA_VERSION_TOOL_ENV,pe as MAX_SECONDARY_REGIONS,Sr as MIGRATION_SNAPSHOT_NAME_PREFIX,Oe as OPT_IN_REGION_CODES,Nr as PRISMA_MIGRATION_DIR_RE,u as REGION_SHORT_CODES,or as RESERVED_APP_NAMES,tr as RESERVED_APP_NAME_MESSAGE,ne as RESOURCE_CATEGORIES,sr as SCHEMA_ADMIN_PASSWORD_ENV,Rr as SCHEMA_ADMIN_USER_ENV,Ie as SCOPE_VALUES,Me as SECRET_NAME_ERROR,xe as SECRET_NAME_PATTERN,le as SSM_COMPONENT_ERROR,de as SSM_COMPONENT_PATTERN,De as SSM_STANDARD_MAX_VALUE_BYTES,z as STRUCTURAL_ENVIRONMENTS,Ve as SecretNamespaceSchema,se as abbreviateRegion,p as accountConstructKey,oe as accountTier,Ze as buildAppConfigPath,Ue as buildNamespaceParts,he as buildParameterPath,T as capitalise,ie as categoriseResource,P as defaultConnectedAccountName,Be as deriveAllTargets,nr as deriveContentHashTag,We as deriveRegionsFromOrgConfig,ye as deriveTargets,ke as environmentOrTier,ee as environmentToTier,be as escapeDotEnvValue,F as filterDangerousEnvVars,c as findAccountNameCollision,Je as findBoundaryPath,we as findInfrastructurePaths,Ye as findTarget,x as findTrailingRegionShortCode,G as formatErrorString,je as generateTargetName,t as getDomainExportNames,te as getEnvironmentLabel,U as getErrorCode,D as getErrorMessage,h as getErrorStack,Se as getExpectedDuration,_e as getFriendlyResourceType,Ce as getRegionInfo,C as getSafeZoneName,O as hasAsciiStableConstructKey,V as hasErrorCode,_ as imageTagParameterName,er as inferContainerFromCandidates,w as isAccountStage,$ as isAccountTier,Qe as isInfrastructureFile,ve as isManageablePath,Er as isReservedAppName,k as mapSettledWithConcurrency,X as maskSensitiveOutput,l as normaliseError,fe as optInRegionWarning,Le as parseDotEnv,me as parseGitRemoteUrl,Ge as parseParameterPath,K as parseShellArgs,M as regionSuffixRejectionMessage,ce as regions,L as singleton,y as sleep,re as stageFromWireEnvironment,I as suffixedAccountName,ge as suggestRegionForTimezone,R as toKebab,m as toPascalCase,N as toScreamingSnake,s as toValidDatabaseName};
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 n}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as i}from"./infra/imageTags.js";import{toPascalCase as m,toKebab as T,toValidDatabaseName as R,toScreamingSnake as N,capitalise as s,getSafeZoneName as C,accountConstructKey as O,hasAsciiStableConstructKey as c}from"./naming/caseConversion.js";import{findAccountNameCollision as p}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as P,suffixedAccountName as I,REGION_SHORT_CODES as M,findTrailingRegionShortCode as u,regionSuffixRejectionMessage as x}from"./naming/connectedAccountName.js";import{normaliseError as d,getErrorMessage as l,hasErrorCode as U,getErrorCode as h,getErrorStack as V,formatErrorString as G}from"./errorUtils.js";import{singleton as v}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as b,filterDangerousEnvVars as F,maskSensitiveOutput as y,parseShellArgs as K}from"./securityHelpers.js";import{sleep as W}from"./async/sleep.js";import{mapSettledWithConcurrency as Y}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as j,STRUCTURAL_ENVIRONMENTS as z,ACCOUNT_STAGES as Z,ACCOUNT_STAGE_LABELS as q,isAccountStage as w,ACCOUNT_TIERS as J,AccountTierSchema as Q,isAccountTier as $,environmentToTier as ee,stageFromWireEnvironment as re,accountTier as oe,getEnvironmentLabel as te,ACCOUNT_ROLES as Ee}from"./environments.js";import{RESOURCE_CATEGORIES as Se,categoriseResource as ne,getExpectedDuration as _e,getFriendlyResourceType as ie}from"./resourceCategorisation.js";import{parseGitRemoteUrl as me}from"./repo/gitRemoteParser.js";import{abbreviateRegion as Re,AWS_REGIONS_METADATA as Ne,DEFAULT_REGION as se,getRegionInfo as Ce,MAX_SECONDARY_REGIONS as Oe,OPT_IN_REGION_CODES as ce,optInRegionWarning as fe,regions as pe,suggestRegionForTimezone as ge}from"./infra/regions.js";import{SCOPE_VALUES as Ie,MACHINE_ONLY_SCOPES as Me,USER_GRANTABLE_SCOPES as ue}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as De,SECRET_NAME_ERROR as de,SSM_COMPONENT_PATTERN as le,SSM_COMPONENT_ERROR as Ue,SSM_STANDARD_MAX_VALUE_BYTES as he,SecretNamespaceSchema as Ve,buildNamespaceParts as Ge,buildParameterPath as Le,parseParameterPath as ve,isManageablePath as He,parseDotEnv as be,escapeDotEnvValue as Fe}from"./secrets.js";import{ConnectionWireSchema as Ke,ConnectionsListResponseSchema as Xe}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as Be,deriveTargets as Ye,deriveAllTargets as ke,environmentOrTier as je,findTarget as ze,generateTargetName as Ze}from"./targets.js";import{buildAppConfigPath as we}from"./repo/appPath.js";import{findInfrastructurePaths as Qe,findBoundaryPath as $e,isInfrastructureFile as er}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as or}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Er,RESERVED_APP_NAME_MESSAGE as ar,isReservedAppName as Sr}from"./naming/reservedAppNames.js";import{deriveContentHashTag as _r}from"./infra/deriveContentHashTag.js";import{DEPLOY_MODES as Ar,DeployModeSchema as mr,IMAGE_TAG_PATTERN as Tr,ImageTagSchema as Rr,ServiceArtefactSchema as Nr,ServiceArtefactsSchema as sr,ARTEFACT_OUTPUT_FIELDS as Cr,artefactOutputKey as Or}from"./infra/deployArtefacts.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as fr,EXPECTED_SCHEMA_VERSION_ENV as pr,EXPECTED_SCHEMA_VERSION_TOOL_ENV as gr,EXPECTED_CH_SCHEMA_VERSION_ENV as Pr,SCHEMA_ADMIN_USER_ENV as Ir,SCHEMA_ADMIN_PASSWORD_ENV as Mr,PRISMA_MIGRATION_DIR_RE as ur,CLICKHOUSE_MIGRATION_SKIP_RE as xr}from"./migration/constants.js";export{Ee as ACCOUNT_ROLES,Z as ACCOUNT_STAGES,j as ACCOUNT_STAGES_WITH_ROOT,q as ACCOUNT_STAGE_LABELS,J as ACCOUNT_TIERS,n as APPROVAL_TOKEN_OUTPUT_PREFIX,Cr as ARTEFACT_OUTPUT_FIELDS,Ne as AWS_REGIONS_METADATA,Q as AccountTierSchema,a as BACKUP_VAULT_NAME,xr as CLICKHOUSE_MIGRATION_SKIP_RE,Ke as ConnectionWireSchema,Xe as ConnectionsListResponseSchema,b as DANGEROUS_ENV_VARS,se as DEFAULT_REGION,Ar as DEPLOY_MODES,o as DNS_APEX,mr as DeployModeSchema,Pr as EXPECTED_CH_SCHEMA_VERSION_ENV,pr as EXPECTED_SCHEMA_VERSION_ENV,gr as EXPECTED_SCHEMA_VERSION_TOOL_ENV,Tr as IMAGE_TAG_PATTERN,Rr as ImageTagSchema,Me as MACHINE_ONLY_SCOPES,Oe as MAX_SECONDARY_REGIONS,fr as MIGRATION_SNAPSHOT_NAME_PREFIX,ce as OPT_IN_REGION_CODES,ur as PRISMA_MIGRATION_DIR_RE,M as REGION_SHORT_CODES,Er as RESERVED_APP_NAMES,ar as RESERVED_APP_NAME_MESSAGE,Se as RESOURCE_CATEGORIES,Mr as SCHEMA_ADMIN_PASSWORD_ENV,Ir as SCHEMA_ADMIN_USER_ENV,Ie as SCOPE_VALUES,de as SECRET_NAME_ERROR,De as SECRET_NAME_PATTERN,Ue as SSM_COMPONENT_ERROR,le as SSM_COMPONENT_PATTERN,he as SSM_STANDARD_MAX_VALUE_BYTES,z as STRUCTURAL_ENVIRONMENTS,Ve as SecretNamespaceSchema,Nr as ServiceArtefactSchema,sr as ServiceArtefactsSchema,ue as USER_GRANTABLE_SCOPES,Re as abbreviateRegion,O as accountConstructKey,oe as accountTier,Or as artefactOutputKey,we as buildAppConfigPath,Ge as buildNamespaceParts,Le as buildParameterPath,s as capitalise,ne as categoriseResource,P as defaultConnectedAccountName,ke as deriveAllTargets,_r as deriveContentHashTag,Be as deriveRegionsFromOrgConfig,Ye as deriveTargets,je as environmentOrTier,ee as environmentToTier,Fe as escapeDotEnvValue,F as filterDangerousEnvVars,p as findAccountNameCollision,$e as findBoundaryPath,Qe as findInfrastructurePaths,ze as findTarget,u as findTrailingRegionShortCode,G as formatErrorString,Ze as generateTargetName,t as getDomainExportNames,te as getEnvironmentLabel,h as getErrorCode,l as getErrorMessage,V as getErrorStack,_e as getExpectedDuration,ie as getFriendlyResourceType,Ce as getRegionInfo,C as getSafeZoneName,c as hasAsciiStableConstructKey,U as hasErrorCode,i as imageTagParameterName,or as inferContainerFromCandidates,w as isAccountStage,$ as isAccountTier,er as isInfrastructureFile,He as isManageablePath,Sr as isReservedAppName,Y as mapSettledWithConcurrency,y as maskSensitiveOutput,d as normaliseError,fe as optInRegionWarning,be as parseDotEnv,me as parseGitRemoteUrl,ve as parseParameterPath,K as parseShellArgs,x as regionSuffixRejectionMessage,pe as regions,v as singleton,W as sleep,re as stageFromWireEnvironment,I as suffixedAccountName,ge as suggestRegionForTimezone,T as toKebab,m as toPascalCase,N as toScreamingSnake,R as toValidDatabaseName};
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Shared deploy artefact contract — the single source of truth for the
3
+ * engine→consumer wire shapes that describe WHAT a deploy produced.
4
+ *
5
+ * Three coupled surfaces import from here so none can drift:
6
+ * - `@fjall/deploy-core` — emits `ServiceArtefact[]` on `DeployResult`
7
+ * and derives its per-service output keys via `artefactOutputKey`
8
+ * - `@fjall/cli` — `commandSchemas` re-exports `ImageTagSchema` for
9
+ * `--image-tag` validation
10
+ * - webapp worker — parses the per-service outputs and (Phase 1b)
11
+ * consumes `ServiceArtefact[]` for Release persistence
12
+ *
13
+ * The output-key contract (`artefactOutputKey`) reproduces the historical
14
+ * inline `${serviceName}TaskDefinition` / `${serviceName}ImageTag` / …
15
+ * template literals byte-identically; the regression pins in
16
+ * `util/src/__tests__/deployArtefacts.test.ts` freeze that wire format.
17
+ */
18
+ import { z } from "zod";
19
+ export declare const DEPLOY_MODES: readonly ["full", "code-only", "rollback"];
20
+ export declare const DeployModeSchema: z.ZodEnum<{
21
+ full: "full";
22
+ "code-only": "code-only";
23
+ rollback: "rollback";
24
+ }>;
25
+ export type DeployMode = z.infer<typeof DeployModeSchema>;
26
+ /**
27
+ * Single source for image-tag validation (previously declared 3×: CLI
28
+ * commandSchemas inline regex, webapp IMAGE_TAG_PATTERN, deploymentHelpers
29
+ * inline copy). Matches Docker tag grammar as fjall constrains it.
30
+ */
31
+ export declare const IMAGE_TAG_PATTERN: RegExp;
32
+ export declare const ImageTagSchema: z.ZodString;
33
+ export type ImageTag = z.infer<typeof ImageTagSchema>;
34
+ /**
35
+ * One rolled-out (or pushed) service image identity.
36
+ *
37
+ * `imageDigest` and `taskDefinitionArn` are optional for honesty, not
38
+ * convenience: the code-only rollout falls back to tag pinning when ECR
39
+ * digest resolution fails, and the full-deploy path resolves the live task
40
+ * definition best-effort AFTER CloudFormation has rolled the service —
41
+ * neither may invent placeholder values. `previousTaskDefinitionArn` exists
42
+ * only where an explicit RegisterTaskDefinition rollout captured it.
43
+ * `ecrRepositoryArn` is absent for non-ECR registries.
44
+ */
45
+ export declare const ServiceArtefactSchema: z.ZodObject<{
46
+ serviceName: z.ZodString;
47
+ imageTag: z.ZodString;
48
+ imageDigest: z.ZodOptional<z.ZodString>;
49
+ imageUri: z.ZodString;
50
+ ecrRepositoryArn: z.ZodOptional<z.ZodString>;
51
+ taskDefinitionArn: z.ZodOptional<z.ZodString>;
52
+ previousTaskDefinitionArn: z.ZodOptional<z.ZodString>;
53
+ }, z.core.$strict>;
54
+ export type ServiceArtefact = z.infer<typeof ServiceArtefactSchema>;
55
+ export declare const ServiceArtefactsSchema: z.ZodArray<z.ZodObject<{
56
+ serviceName: z.ZodString;
57
+ imageTag: z.ZodString;
58
+ imageDigest: z.ZodOptional<z.ZodString>;
59
+ imageUri: z.ZodString;
60
+ ecrRepositoryArn: z.ZodOptional<z.ZodString>;
61
+ taskDefinitionArn: z.ZodOptional<z.ZodString>;
62
+ previousTaskDefinitionArn: z.ZodOptional<z.ZodString>;
63
+ }, z.core.$strict>>;
64
+ export type ServiceArtefacts = z.infer<typeof ServiceArtefactsSchema>;
65
+ /**
66
+ * The six per-service output-key suffixes the deploy engine emits and the
67
+ * webapp worker parses (`webapp/scripts/deploymentJobHandler/imageRollout.ts`).
68
+ * Order matters only for readers; the VALUES are a wire contract.
69
+ */
70
+ export declare const ARTEFACT_OUTPUT_FIELDS: readonly ["TaskDefinition", "PreviousTaskDefinition", "ImageTag", "ImageUri", "EcrRepositoryArn", "ImageDigest"];
71
+ export type ArtefactOutputField = (typeof ARTEFACT_OUTPUT_FIELDS)[number];
72
+ /**
73
+ * Derive the per-service deploy output key — MUST stay byte-identical to the
74
+ * historical `${serviceName}${field}` template literals (the worker
75
+ * slices the `serviceName` back off the `TaskDefinition`-suffixed keys).
76
+ */
77
+ export declare function artefactOutputKey(serviceName: string, field: ArtefactOutputField): string;
@@ -0,0 +1 @@
1
+ import{z as e}from"zod";const n=["full","code-only","rollback"],c=e.enum(n),o=/^[a-zA-Z0-9._-]+$/,r=e.string().min(1).max(128).regex(o),a=e.object({serviceName:e.string().min(1),imageTag:r,imageDigest:e.string().min(1).optional(),imageUri:e.string().min(1),ecrRepositoryArn:e.string().min(1).optional(),taskDefinitionArn:e.string().min(1).optional(),previousTaskDefinitionArn:e.string().min(1).optional()}).strict(),m=e.array(a),g=["TaskDefinition","PreviousTaskDefinition","ImageTag","ImageUri","EcrRepositoryArn","ImageDigest"];function p(t,i){return`${t}${i}`}export{g as ARTEFACT_OUTPUT_FIELDS,n as DEPLOY_MODES,c as DeployModeSchema,o as IMAGE_TAG_PATTERN,r as ImageTagSchema,a as ServiceArtefactSchema,m as ServiceArtefactsSchema,p as artefactOutputKey};
@@ -7,5 +7,9 @@
7
7
  * a scope the CLI did not know how to request, or vice versa. Both
8
8
  * consumers now import `SCOPE_VALUES` / `TokenScope` from here.
9
9
  */
10
- export declare const SCOPE_VALUES: readonly ["read", "write", "deploy", "secrets:read", "secrets:write", "destroy", "admin", "applications:read", "applications:deploy"];
10
+ export declare const SCOPE_VALUES: readonly ["read", "write", "deploy", "secrets:read", "secrets:write", "destroy", "admin", "applications:read", "applications:deploy", "deploy:oidc:mint"];
11
11
  export type TokenScope = (typeof SCOPE_VALUES)[number];
12
+ /** Scopes only a machine principal may hold — never a user-minted token. */
13
+ export declare const MACHINE_ONLY_SCOPES: readonly TokenScope[];
14
+ /** Scopes a user `*` grant expands to (excludes `admin` + machine-only). */
15
+ export declare const USER_GRANTABLE_SCOPES: readonly TokenScope[];
@@ -1 +1 @@
1
- const e=["read","write","deploy","secrets:read","secrets:write","destroy","admin","applications:read","applications:deploy"];export{e as SCOPE_VALUES};
1
+ const r=["read","write","deploy","secrets:read","secrets:write","destroy","admin","applications:read","applications:deploy","deploy:oidc:mint"],s={read:"user",write:"user",deploy:"user","secrets:read":"user","secrets:write":"user",destroy:"user",admin:"admin","applications:read":"user","applications:deploy":"user","deploy:oidc:mint":"machine"},t=r.filter(e=>s[e]==="machine"),i=r.filter(e=>s[e]==="user");export{t as MACHINE_ONLY_SCOPES,r as SCOPE_VALUES,i as USER_GRANTABLE_SCOPES};
@@ -12,6 +12,15 @@
12
12
  * customer-owned application sharing that name would collide structurally
13
13
  * with the organisation entry under `[container/]fjall/fjall/infrastructure.ts`.
14
14
  *
15
+ * `"organisation"` / `"platform"` / `"account"` are the org-tier deploy
16
+ * targets (deploy-core `ORGANISATION_TYPES` — the coupled SSoT; a deploy-core
17
+ * parity test pins the subset relation, since util sits below deploy-core and
18
+ * cannot import it). Deploy consumers classify a deploy as org-level from the
19
+ * target NAME alone (`fjall deploy account`, the worker's org-level gate), so
20
+ * an application sharing one of these names would be mis-classified as an
21
+ * org-tier deploy — bypassing the app-scoped session policy and injecting org
22
+ * identity into synth.
23
+ *
15
24
  * **Boundary with `isReservedSlug`** — `webapp/app/.server/utils/reservedSlugs.ts`
16
25
  * guards organisation slugs (URL-scoped, webapp-only). This list is
17
26
  * application-scoped and ships from `@fjall/util` for cross-system reuse
@@ -21,14 +30,14 @@
21
30
  * before testing, so `"Fjall"`, `"FJALL"`, and `"fjALL"` all reject
22
31
  * identically.
23
32
  */
24
- export declare const RESERVED_APP_NAMES: readonly ["fjall"];
33
+ export declare const RESERVED_APP_NAMES: readonly ["fjall", "organisation", "platform", "account"];
25
34
  export type ReservedAppName = (typeof RESERVED_APP_NAMES)[number];
26
35
  /**
27
36
  * Canonical user-facing rejection message for a reserved application name.
28
37
  * Shared by every create surface (the CLI `CreateApplicationSchema`, the webapp
29
38
  * create route, the webapp scaffold schema) so the wording cannot drift.
30
39
  */
31
- export declare const RESERVED_APP_NAME_MESSAGE = "The name 'fjall' is reserved for organisation-tier configuration.";
40
+ export declare const RESERVED_APP_NAME_MESSAGE = "This application name is reserved for Fjall's organisation-tier infrastructure.";
32
41
  /**
33
42
  * Case-insensitive membership check. The canonical entrypoint — consumers
34
43
  * MUST route through this helper rather than calling `.includes(name.toLowerCase())`
@@ -1 +1 @@
1
- const o=["fjall"],r="The name 'fjall' is reserved for organisation-tier configuration.";function n(e){return o.includes(e.toLowerCase())}export{o as RESERVED_APP_NAMES,r as RESERVED_APP_NAME_MESSAGE,n as isReservedAppName};
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};
@@ -20,9 +20,12 @@ export declare const DANGEROUS_ENV_VARS: Set<string>;
20
20
  */
21
21
  export declare function filterDangerousEnvVars(env: Record<string, string | undefined>): Record<string, string | undefined>;
22
22
  /**
23
- * Matches the FULL scoped agent token: prefix (16 base32) + separator (.) + secret (40 base32).
23
+ * Matches the FULL agent token: prefix (16 base32) + separator (.) + secret (40 base32),
24
+ * for both scoped-family kinds — `fjall_ak_` (scoped) and `fjall_dk_` (deploy).
24
25
  * Base32 alphabet: A-Z2-7. The `.` separator MUST be included or the secret leaks unmasked.
25
- * Single source of truth — all masking call sites import this.
26
+ * Single source of truth — all masking call sites import this. Coupled to the prefix
27
+ * SSoT `AGENT_TOKEN_PREFIXES` at webapp/app/.server/models/auth/agent-token-kind.ts —
28
+ * a new token kind there MUST extend this alternation.
26
29
  *
27
30
  * Public form has NO `g` flag — `.test()` consumers must not share `lastIndex` across calls.
28
31
  * The global form lives in `SCOPED_TOKEN_GLOBAL_REGEX` below for the iteration site.
@@ -33,7 +36,8 @@ export declare const SCOPED_TOKEN_REGEX: RegExp;
33
36
  * Patterns: postgres://user:pass@host, password=xxx, secret=xxx, apikey=xxx,
34
37
  * GitHub tokens (ghu_/ghs_/ghp_/gho_/github_pat_), bare AWS access-key IDs
35
38
  * (AKIA-prefixed and ASIA-prefixed), AWS secret keys (env, INI, and JSON key
36
- * spellings), session tokens, ARN account IDs, scoped agent tokens.
39
+ * spellings), session tokens, ARN account IDs, agent tokens (`fjall_ak_`
40
+ * scoped, `fjall_dk_` deploy).
37
41
  *
38
42
  * Single source of truth — consumer loggers (CLI, worker, webapp) MUST
39
43
  * NOT re-implement these patterns inline. See
@@ -1 +1 @@
1
- const A=new Set(["NODE_OPTIONS","NODE_PATH","NODE_EXTRA_CA_CERTS","NODE_DEBUG","NODE_PRESERVE_SYMLINKS","LD_PRELOAD","LD_LIBRARY_PATH","LD_AUDIT","LD_BIND_NOW","DYLD_INSERT_LIBRARIES","DYLD_LIBRARY_PATH","DYLD_FRAMEWORK_PATH","PYTHONPATH","PYTHONSTARTUP","PERL5LIB","PERL5OPT","RUBYLIB","RUBYOPT","HOME","XDG_CONFIG_HOME","AWS_SHARED_CREDENTIALS_FILE","AWS_CONFIG_FILE","SHELL","BASH_ENV","ENV","ZDOTDIR"]);function l(e){return Object.fromEntries(Object.entries(e).filter(([r])=>!A.has(r.toUpperCase())))}const i=/fjall_ak_[A-Z2-7]{16}\.[A-Z2-7]{40}/,c=/fjall_ak_[A-Z2-7]{16}\.[A-Z2-7]{40}/g;function o(e){return e.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----(?:[^"\\]|\\[\\nrt"])*?-----END [A-Z ]*PRIVATE KEY-----/g,"-----BEGIN [REDACTED] PRIVATE KEY-----...-----END [REDACTED] PRIVATE KEY-----").replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,"-----BEGIN [REDACTED] PRIVATE KEY-----...-----END [REDACTED] PRIVATE KEY-----").replace(/(\w+:\/\/[^:]+:)[^@]+(@)/gi,"$1***$2").replace(/(?<![a-zA-Z])(password|passwd|secret|api[_-]?key|token|auth|credential)([=:])["']?[^\s"']+/gi,"$1$2***").replace(/\b(ghu_|ghs_|ghp_|gho_|github_pat_)[A-Za-z0-9_]+/g,"$1***").replace(/\b(sk|rk)_(live|test)_[A-Za-z0-9]{8,}/g,"$1_$2_***").replace(/\bwhsec_[A-Za-z0-9]{8,}/g,"whsec_***").replace(/(?<=Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi,"***").replace(/\b(AKIA|ASIA)[A-Z0-9]{12,}\b/g,"$1***").replace(/(?<=aws_secret_access_key\s*=\s*["']?|SecretAccessKey[=:]\s*|"(aws)?secretAccessKey":\s*"|"aws_secret_access_key":\s*")[A-Za-z0-9/+=]{40,}/gi,"***").replace(/(arn:aws[^:]*:[^:]*:[^:]*:)(\d{12})(:[^\s]*)/g,"$1***$3").replace(/(?<="(aws)?sessionToken":\s*"|"aws_session_token":\s*")[^"]+/gi,"***").replace(/(?<=aws_session_token\s*[=:]\s*["']?|SessionToken[=:]\s*["']?)[^\s"']+/gi,"***").replace(/(?<="(internal[Aa]piKey|fjallCallbackToken)":\s*")[^"]+/g,"***").replace(c,"fjall_ak_***")}function D(e){const r=[];let s="",E=!1,_=!1,n=!1,t=!1;for(const a of e){if(n){s+=a,n=!1;continue}if(a==="\\"&&!E){n=!0;continue}if(a==="'"&&!_){E=!E,t=!0;continue}if(a==='"'&&!E){_=!_,t=!0;continue}if(a===" "&&!E&&!_){(s||t)&&(r.push(s),s="",t=!1);continue}s+=a}if(E||_)throw new Error(`Unbalanced ${E?"single":"double"} quote in command: ${e.slice(0,80)}`);if(n)throw new Error(`Trailing backslash in command: ${e.slice(0,80)}`);return(s||t)&&r.push(s),r}export{A as DANGEROUS_ENV_VARS,i as SCOPED_TOKEN_REGEX,l as filterDangerousEnvVars,o as maskSensitiveOutput,D as parseShellArgs};
1
+ const A=new Set(["NODE_OPTIONS","NODE_PATH","NODE_EXTRA_CA_CERTS","NODE_DEBUG","NODE_PRESERVE_SYMLINKS","LD_PRELOAD","LD_LIBRARY_PATH","LD_AUDIT","LD_BIND_NOW","DYLD_INSERT_LIBRARIES","DYLD_LIBRARY_PATH","DYLD_FRAMEWORK_PATH","PYTHONPATH","PYTHONSTARTUP","PERL5LIB","PERL5OPT","RUBYLIB","RUBYOPT","HOME","XDG_CONFIG_HOME","AWS_SHARED_CREDENTIALS_FILE","AWS_CONFIG_FILE","SHELL","BASH_ENV","ENV","ZDOTDIR"]);function i(e){return Object.fromEntries(Object.entries(e).filter(([r])=>!A.has(r.toUpperCase())))}const l=/fjall_(?:ak|dk)_[A-Z2-7]{16}\.[A-Z2-7]{40}/,c=/(fjall_(?:ak|dk)_)[A-Z2-7]{16}\.[A-Z2-7]{40}/g;function o(e){return e.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----(?:[^"\\]|\\[\\nrt"])*?-----END [A-Z ]*PRIVATE KEY-----/g,"-----BEGIN [REDACTED] PRIVATE KEY-----...-----END [REDACTED] PRIVATE KEY-----").replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,"-----BEGIN [REDACTED] PRIVATE KEY-----...-----END [REDACTED] PRIVATE KEY-----").replace(/(\w+:\/\/[^:]+:)[^@]+(@)/gi,"$1***$2").replace(/(?<![a-zA-Z])(password|passwd|secret|api[_-]?key|token|auth|credential)([=:])["']?[^\s"']+/gi,"$1$2***").replace(/\b(ghu_|ghs_|ghp_|gho_|github_pat_)[A-Za-z0-9_]+/g,"$1***").replace(/\b(sk|rk)_(live|test)_[A-Za-z0-9]{8,}/g,"$1_$2_***").replace(/\bwhsec_[A-Za-z0-9]{8,}/g,"whsec_***").replace(/(?<=Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi,"***").replace(/\b(AKIA|ASIA)[A-Z0-9]{12,}\b/g,"$1***").replace(/(?<=aws_secret_access_key\s*=\s*["']?|SecretAccessKey[=:]\s*|"(aws)?secretAccessKey":\s*"|"aws_secret_access_key":\s*")[A-Za-z0-9/+=]{40,}/gi,"***").replace(/(arn:aws[^:]*:[^:]*:[^:]*:)(\d{12})(:[^\s]*)/g,"$1***$3").replace(/(?<="(aws)?sessionToken":\s*"|"aws_session_token":\s*")[^"]+/gi,"***").replace(/(?<=aws_session_token\s*[=:]\s*["']?|SessionToken[=:]\s*["']?)[^\s"']+/gi,"***").replace(/(?<="(internal[Aa]piKey|fjallCallbackToken)":\s*")[^"]+/g,"***").replace(c,"$1***")}function D(e){const r=[];let s="",E=!1,t=!1,n=!1,_=!1;for(const a of e){if(n){s+=a,n=!1;continue}if(a==="\\"&&!E){n=!0;continue}if(a==="'"&&!t){E=!E,_=!0;continue}if(a==='"'&&!E){t=!t,_=!0;continue}if(a===" "&&!E&&!t){(s||_)&&(r.push(s),s="",_=!1);continue}s+=a}if(E||t)throw new Error(`Unbalanced ${E?"single":"double"} quote in command: ${e.slice(0,80)}`);if(n)throw new Error(`Trailing backslash in command: ${e.slice(0,80)}`);return(s||_)&&r.push(s),r}export{A as DANGEROUS_ENV_VARS,l as SCOPED_TOKEN_REGEX,i as filterDangerousEnvVars,o as maskSensitiveOutput,D as parseShellArgs};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "2.23.1",
3
+ "version": "2.24.0",
4
4
  "description": "Common utility methods",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -42,6 +42,10 @@
42
42
  "types": "./dist/fsHelpers.d.ts",
43
43
  "default": "./dist/fsHelpers.js"
44
44
  },
45
+ "./cdkTmpdirCleanup": {
46
+ "types": "./dist/cdkTmpdirCleanup.d.ts",
47
+ "default": "./dist/cdkTmpdirCleanup.js"
48
+ },
45
49
  "./migration": {
46
50
  "types": "./dist/migration/index.d.ts",
47
51
  "default": "./dist/migration/index.js"
@@ -121,5 +125,5 @@
121
125
  "engines": {
122
126
  "node": ">=22.0.0"
123
127
  },
124
- "gitHead": "749133c6c5cfe24a09a401869f2193c7d177a324"
128
+ "gitHead": "616d7af846f0eebfa3409f5f47c98fbee836bad9"
125
129
  }