@fjall/util 3.5.2 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/.minified CHANGED
@@ -1 +1 @@
1
- 88 files minified at 2026-07-19T20:58:59.626Z
1
+ 89 files minified at 2026-07-19T23:39:36.137Z
package/dist/config.d.ts CHANGED
@@ -130,7 +130,13 @@ export type SSOSession = {
130
130
  ssoRegion: string;
131
131
  ssoStartUrl: string;
132
132
  };
133
- declare const DomainConfigSchema: z.ZodObject<{
133
+ /**
134
+ * STRICT wire contract for one `domains[]` entry in fjall-config.json —
135
+ * unknown keys are rejected, never silently carried. This is the write-path
136
+ * schema shared with the webapp's domain-topology resolver; a scaffold or
137
+ * saver emitting a key this schema rejects is exactly the drift it prevents.
138
+ */
139
+ export declare const DomainConfigSchema: z.ZodObject<{
134
140
  name: z.ZodString;
135
141
  type: z.ZodEnum<{
136
142
  apex: "apex";
@@ -153,6 +159,30 @@ export declare const RootConfigSchema: z.ZodObject<{
153
159
  }, z.core.$strict>>>;
154
160
  }, z.core.$strict>;
155
161
  export type RootConfig = z.infer<typeof RootConfigSchema>;
162
+ /**
163
+ * Tolerant READ schema for the deploy-time load path. Intentionally NOT
164
+ * `.strict()`: a fjall-config.json written by a different fjall version, or a
165
+ * legacy pre-config-split file, may carry top-level keys this version does not
166
+ * know (`version`, `services`, `providerAccounts`, `generatorVersion`, …).
167
+ * Stripping them and reading only the recognised keys keeps `fjall deploy` from
168
+ * hard-failing on an otherwise-harmless older file. The strict RootConfigSchema
169
+ * remains the contract for the WRITE/save path — never use this schema to
170
+ * validate what you are about to write. Domain entries stay nested-strict
171
+ * (DomainConfigSchema): tolerance applies to unknown TOP-LEVEL keys only.
172
+ */
173
+ export declare const RootConfigReadSchema: z.ZodObject<{
174
+ activeTarget: z.ZodOptional<z.ZodString>;
175
+ domains: z.ZodOptional<z.ZodArray<z.ZodObject<{
176
+ name: z.ZodString;
177
+ type: z.ZodEnum<{
178
+ apex: "apex";
179
+ delegated: "delegated";
180
+ }>;
181
+ parentDomain: z.ZodOptional<z.ZodString>;
182
+ account: z.ZodOptional<z.ZodString>;
183
+ }, z.core.$strict>>>;
184
+ }, z.core.$strip>;
185
+ export type RootConfigRead = z.infer<typeof RootConfigReadSchema>;
156
186
  /**
157
187
  * Canonical serialiser for the root fjall-config.json — the single source of
158
188
  * truth for the file's on-disk shape. Both `Config.saveConfig` and the webapp
@@ -216,6 +246,12 @@ export declare class Config {
216
246
  private mergeWithDisk;
217
247
  private static readDiskConfigForMerge;
218
248
  static getConfigDirectory(startDir?: string): string | null;
249
+ /**
250
+ * Absolute path of the config file this instance was loaded from, or null
251
+ * when no fjall-config.json was found — the "not inside a fjall project"
252
+ * signal error paths branch on.
253
+ */
254
+ getConfigPath(): string | null;
219
255
  getActiveTarget(): string | undefined;
220
256
  setActiveTarget(name: string): void;
221
257
  clearActiveTarget(): void;
@@ -225,4 +261,3 @@ export declare class Config {
225
261
  getDomain(name: string): DomainConfig | undefined;
226
262
  removeDomain(name: string): boolean;
227
263
  }
228
- export {};
package/dist/config.js CHANGED
@@ -1 +1 @@
1
- var S=Object.defineProperty;var h=(d,e)=>S(d,"name",{value:e,configurable:!0});import*as i from"fs";import*as f from"path";import{z as s}from"zod";import{failure as g,success as w}from"./docker/result.js";import{getErrorMessage as C}from"./errorUtils.js";import{logger as y}from"./logger.js";import{maskSensitiveOutput as u}from"./securityHelpers.js";const v=10,m="fjall-config.json",M=["compliance","governance","none"],N=["enforced","off"],b=["centralised","off"],k=["account","draining","org"],I="managementEvents",L="organisationManagementEvents",U=["active","draining","removed"],J="FjallTrailBucketName",G="FjallTrailKeyArn",W="OrganisationTrailBucketName",E=s.object({name:s.string(),type:s.enum(["apex","delegated"]),parentDomain:s.string().optional(),account:s.string().optional()}).strict(),T=s.object({activeTarget:s.string().optional(),domains:s.array(E).optional()}).strict(),O=s.object({activeTarget:s.string().optional(),domains:s.array(E).optional()});function A(d={}){return JSON.stringify(d,null,2)}h(A,"serialiseRootConfig");const _=T.keyof().options;function D(d,e,t){const n=e[t];n!==void 0&&(d[t]=n)}h(D,"copyDefinedKey");class a{static{h(this,"Config")}rootConfig;configPath=null;loadFailed=!1;clearedKeys=new Set;constructor(e,t){this.rootConfig=e??{},this.configPath=t??null}static findConfigDirectory(e){let t=e!==void 0&&e!==""?e:process.cwd();for(let n=0;n<v;n++){const r=f.join(t,"fjall"),c=f.join(r,m);if(i.existsSync(c))return r;const o=f.join(t,m);if(i.existsSync(o))return t;const l=f.dirname(t);if(l===t)break;t=l}return null}static loadConfigFile(e){try{return i.accessSync(e,i.constants.R_OK),i.readFileSync(e,{encoding:"utf8"})}catch(t){return y.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:u(C(t))}),null}}static loadConfig(e){const t=a.findConfigDirectory(e);if(!t)return new a;const n=f.join(t,m),r=a.loadConfigFile(n);if(r===null){const o=new a(void 0,n);return o.loadFailed=!0,o}let c;if(r!==""){let o;try{o=JSON.parse(r)}catch(p){throw a.formatZodError(p,m)}const l=T.safeParse(o);if(l.success)c=l.data;else{const p=O.safeParse(o);c=p.success?p.data:{},y.warn("Config","fjall-config.json contains keys this version does not recognise; they were ignored (only activeTarget and domains are read). If this is an old config, regenerate it with `fjall create ...` or re-run `fjall connect`.",{file:n})}}return new a(c,n)}static formatZodError(e,t){if(e instanceof s.ZodError&&e.issues.length>0){const c=e.issues.map(o=>`${o.path.join(".")}: ${o.message}`).join("; ");return new Error(`Failed to parse ${t}: ${c}`)}const r=(e instanceof Error?e.message:String(e)).replace(/\n/g," ").substring(0,500);return new Error(`Failed to parse ${t}: ${r}`)}saveConfig(){let e=this.configPath;if(!e){const o=a.findConfigDirectory()||f.join(process.cwd(),"fjall");e=f.join(o,m)}if(this.loadFailed)return g(new Error(`Refusing to save ${e}: the file exists but could not be read when this config loaded, so saving would replace its contents with state that never included them. Fix the file permissions (e.g. chmod u+rw ${e}) and retry.`));const t=f.dirname(e);try{i.mkdirSync(t,{recursive:!0})}catch(o){return g(new Error(`Cannot create config directory ${t}: ${u(C(o))}`))}const n=a.assertWritable(e,t);if(!n.success)return n;const r=A(this.mergeWithDisk(e)),c=`${e}.tmp-${process.pid}`;try{i.writeFileSync(c,r,{mode:384}),i.renameSync(c,e)}catch(o){return g(new Error(`Failed to save ${e}: ${u(C(o))}`))}return w(void 0)}static assertWritable(e,t){if(i.existsSync(e))try{i.accessSync(e,i.constants.W_OK)}catch{return g(new Error(`Cannot save ${e}: the file is read-only. Make it writable (e.g. chmod u+w ${e}) and retry.`))}try{i.accessSync(t,i.constants.W_OK)}catch{return g(new Error(`Cannot save ${e}: the directory ${t} is not writable. Make it writable (e.g. chmod u+w ${t}) and retry.`))}return w(void 0)}mergeWithDisk(e){const t=a.readDiskConfigForMerge(e);if(t===void 0)return this.rootConfig;const n={...t};for(const r of _)D(n,this.rootConfig,r);for(const r of this.clearedKeys)delete n[r];return n}static readDiskConfigForMerge(e){if(!i.existsSync(e))return;let t;try{t=JSON.parse(i.readFileSync(e,{encoding:"utf8"}))}catch(r){y.warn("Config",`Could not re-read ${e} before saving; writing in-memory state without merging`,{file:e,error:u(C(r))});return}const n=T.safeParse(t);if(!n.success){y.warn("Config",`On-disk ${e} failed validation before saving; writing in-memory state without merging`,{file:e,error:u(n.error.message)});return}return n.data}static getConfigDirectory(e){return a.findConfigDirectory(e)}getActiveTarget(){return this.rootConfig.activeTarget}setActiveTarget(e){this.rootConfig.activeTarget=e,this.clearedKeys.delete("activeTarget")}clearActiveTarget(){this.rootConfig.activeTarget=void 0,this.clearedKeys.add("activeTarget")}getDomains(){return this.rootConfig.domains??[]}setDomains(e){this.rootConfig.domains=e}addDomain(e){this.rootConfig.domains||(this.rootConfig.domains=[]),this.rootConfig.domains.push(e)}getDomain(e){return this.rootConfig.domains?.find(t=>t.name.toLowerCase()===e.toLowerCase())}removeDomain(e){if(!this.rootConfig.domains)return!1;const t=this.rootConfig.domains.findIndex(n=>n.name.toLowerCase()===e.toLowerCase());return t===-1?!1:(this.rootConfig.domains.splice(t,1),!0)}}export{I as ACCOUNT_TRAIL_NAME,U as ACCOUNT_TRAIL_STATES,a as Config,L as ORGANISATION_TRAIL_NAME,W as ORG_TRAIL_BUCKET_OUTPUT_KEY,b as ROOT_ACCESS_MANAGEMENT_MODES,m as ROOT_CONFIG_FILENAME,T as RootConfigSchema,N as S3_BPA_MODES,J as TRAIL_BUCKET_OUTPUT_KEY,G as TRAIL_KEY_ARN_OUTPUT_KEY,k as TRAIL_LIFECYCLE_STATES,M as VAULT_LOCK_MODES,A as serialiseRootConfig};
1
+ var S=Object.defineProperty;var h=(d,e)=>S(d,"name",{value:e,configurable:!0});import*as i from"fs";import*as f from"path";import{z as s}from"zod";import{failure as g,success as w}from"./docker/result.js";import{getErrorMessage as C}from"./errorUtils.js";import{logger as y}from"./logger.js";import{maskSensitiveOutput as u}from"./securityHelpers.js";const v=10,m="fjall-config.json",M=["compliance","governance","none"],N=["enforced","off"],b=["centralised","off"],k=["account","draining","org"],I="managementEvents",L="organisationManagementEvents",U=["active","draining","removed"],J="FjallTrailBucketName",P="FjallTrailKeyArn",G="OrganisationTrailBucketName",E=s.object({name:s.string(),type:s.enum(["apex","delegated"]),parentDomain:s.string().optional(),account:s.string().optional()}).strict(),T=s.object({activeTarget:s.string().optional(),domains:s.array(E).optional()}).strict(),O=s.object({activeTarget:s.string().optional(),domains:s.array(E).optional()});function A(d={}){return JSON.stringify(d,null,2)}h(A,"serialiseRootConfig");const _=T.keyof().options;function x(d,e,t){const n=e[t];n!==void 0&&(d[t]=n)}h(x,"copyDefinedKey");class a{static{h(this,"Config")}rootConfig;configPath=null;loadFailed=!1;clearedKeys=new Set;constructor(e,t){this.rootConfig=e??{},this.configPath=t??null}static findConfigDirectory(e){let t=e!==void 0&&e!==""?e:process.cwd();for(let n=0;n<v;n++){const r=f.join(t,"fjall"),c=f.join(r,m);if(i.existsSync(c))return r;const o=f.join(t,m);if(i.existsSync(o))return t;const l=f.dirname(t);if(l===t)break;t=l}return null}static loadConfigFile(e){try{return i.accessSync(e,i.constants.R_OK),i.readFileSync(e,{encoding:"utf8"})}catch(t){return y.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:u(C(t))}),null}}static loadConfig(e){const t=a.findConfigDirectory(e);if(!t)return new a;const n=f.join(t,m),r=a.loadConfigFile(n);if(r===null){const o=new a(void 0,n);return o.loadFailed=!0,o}let c;if(r!==""){let o;try{o=JSON.parse(r)}catch(p){throw a.formatZodError(p,m)}const l=T.safeParse(o);if(l.success)c=l.data;else{const p=O.safeParse(o);c=p.success?p.data:{},y.warn("Config","fjall-config.json contains keys this version does not recognise; they were ignored (only activeTarget and domains are read). If this is an old config, regenerate it with `fjall create ...` or re-run `fjall connect`.",{file:n})}}return new a(c,n)}static formatZodError(e,t){if(e instanceof s.ZodError&&e.issues.length>0){const c=e.issues.map(o=>`${o.path.join(".")}: ${o.message}`).join("; ");return new Error(`Failed to parse ${t}: ${c}`)}const r=(e instanceof Error?e.message:String(e)).replace(/\n/g," ").substring(0,500);return new Error(`Failed to parse ${t}: ${r}`)}saveConfig(){let e=this.configPath;if(!e){const o=a.findConfigDirectory()||f.join(process.cwd(),"fjall");e=f.join(o,m)}if(this.loadFailed)return g(new Error(`Refusing to save ${e}: the file exists but could not be read when this config loaded, so saving would replace its contents with state that never included them. Fix the file permissions (e.g. chmod u+rw ${e}) and retry.`));const t=f.dirname(e);try{i.mkdirSync(t,{recursive:!0})}catch(o){return g(new Error(`Cannot create config directory ${t}: ${u(C(o))}`))}const n=a.assertWritable(e,t);if(!n.success)return n;const r=A(this.mergeWithDisk(e)),c=`${e}.tmp-${process.pid}`;try{i.writeFileSync(c,r,{mode:384}),i.renameSync(c,e)}catch(o){return g(new Error(`Failed to save ${e}: ${u(C(o))}`))}return w(void 0)}static assertWritable(e,t){if(i.existsSync(e))try{i.accessSync(e,i.constants.W_OK)}catch{return g(new Error(`Cannot save ${e}: the file is read-only. Make it writable (e.g. chmod u+w ${e}) and retry.`))}try{i.accessSync(t,i.constants.W_OK)}catch{return g(new Error(`Cannot save ${e}: the directory ${t} is not writable. Make it writable (e.g. chmod u+w ${t}) and retry.`))}return w(void 0)}mergeWithDisk(e){const t=a.readDiskConfigForMerge(e);if(t===void 0)return this.rootConfig;const n={...t};for(const r of _)x(n,this.rootConfig,r);for(const r of this.clearedKeys)delete n[r];return n}static readDiskConfigForMerge(e){if(!i.existsSync(e))return;let t;try{t=JSON.parse(i.readFileSync(e,{encoding:"utf8"}))}catch(r){y.warn("Config",`Could not re-read ${e} before saving; writing in-memory state without merging`,{file:e,error:u(C(r))});return}const n=T.safeParse(t);if(!n.success){y.warn("Config",`On-disk ${e} failed validation before saving; writing in-memory state without merging`,{file:e,error:u(n.error.message)});return}return n.data}static getConfigDirectory(e){return a.findConfigDirectory(e)}getConfigPath(){return this.configPath}getActiveTarget(){return this.rootConfig.activeTarget}setActiveTarget(e){this.rootConfig.activeTarget=e,this.clearedKeys.delete("activeTarget")}clearActiveTarget(){this.rootConfig.activeTarget=void 0,this.clearedKeys.add("activeTarget")}getDomains(){return this.rootConfig.domains??[]}setDomains(e){this.rootConfig.domains=e}addDomain(e){this.rootConfig.domains||(this.rootConfig.domains=[]),this.rootConfig.domains.push(e)}getDomain(e){return this.rootConfig.domains?.find(t=>t.name.toLowerCase()===e.toLowerCase())}removeDomain(e){if(!this.rootConfig.domains)return!1;const t=this.rootConfig.domains.findIndex(n=>n.name.toLowerCase()===e.toLowerCase());return t===-1?!1:(this.rootConfig.domains.splice(t,1),!0)}}export{I as ACCOUNT_TRAIL_NAME,U as ACCOUNT_TRAIL_STATES,a as Config,E as DomainConfigSchema,L as ORGANISATION_TRAIL_NAME,G as ORG_TRAIL_BUCKET_OUTPUT_KEY,b as ROOT_ACCESS_MANAGEMENT_MODES,m as ROOT_CONFIG_FILENAME,O as RootConfigReadSchema,T as RootConfigSchema,N as S3_BPA_MODES,J as TRAIL_BUCKET_OUTPUT_KEY,P as TRAIL_KEY_ARN_OUTPUT_KEY,k as TRAIL_LIFECYCLE_STATES,M as VAULT_LOCK_MODES,A as serialiseRootConfig};
@@ -31,8 +31,8 @@ export declare const DevSubstrateSynthPropsSchema: z.ZodObject<{
31
31
  engineVersion: z.ZodOptional<z.ZodString>;
32
32
  adoptSlotEcr: z.ZodOptional<z.ZodBoolean>;
33
33
  phase: z.ZodOptional<z.ZodEnum<{
34
- full: "full";
35
34
  zone: "zone";
35
+ full: "full";
36
36
  }>>;
37
37
  domain: z.ZodOptional<z.ZodObject<{
38
38
  devDomain: z.ZodString;
@@ -59,8 +59,8 @@ export declare const DevSubstrateParamsSchema: z.ZodObject<{
59
59
  engineVersion: z.ZodOptional<z.ZodString>;
60
60
  adoptSlotEcr: z.ZodOptional<z.ZodBoolean>;
61
61
  phase: z.ZodOptional<z.ZodEnum<{
62
- full: "full";
63
62
  zone: "zone";
63
+ full: "full";
64
64
  }>>;
65
65
  domain: z.ZodOptional<z.ZodObject<{
66
66
  devDomain: z.ZodString;
package/dist/fsScan.d.ts CHANGED
@@ -12,4 +12,6 @@
12
12
  */
13
13
  export { scanLocalRepository, type ScanLocalRepositoryResult } from "./repo/scanLocalRepository.js";
14
14
  export { findRepoRoot } from "./repo/findRepoRoot.js";
15
+ export { findProjectsBelow, hasFjallAppLayout, type DiscoveredProject, type FindProjectsBelowOptions } from "./repo/findProjectsBelow.js";
16
+ export { INFRASTRUCTURE_FILE, MARKER_DIRECTORY } from "./repo/findInfrastructurePaths.js";
15
17
  export { type ScanPath } from "./repo/scanTypes.js";
package/dist/fsScan.js CHANGED
@@ -1 +1 @@
1
- import{scanLocalRepository as e}from"./repo/scanLocalRepository.js";import{findRepoRoot as t}from"./repo/findRepoRoot.js";export{t as findRepoRoot,e as scanLocalRepository};
1
+ import{scanLocalRepository as R}from"./repo/scanLocalRepository.js";import{findRepoRoot as p}from"./repo/findRepoRoot.js";import{findProjectsBelow as f,hasFjallAppLayout as a}from"./repo/findProjectsBelow.js";import{INFRASTRUCTURE_FILE as m,MARKER_DIRECTORY as s}from"./repo/findInfrastructurePaths.js";export{m as INFRASTRUCTURE_FILE,s as MARKER_DIRECTORY,f as findProjectsBelow,p as findRepoRoot,a as hasFjallAppLayout,R as scanLocalRepository};
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { DNS_APEX, getDomainExportNames, isManagedDomainBinding, type ManagedDomainBinding, type ManagedDomainExports } from "./infra/domainExports.js";
1
+ export { DNS_APEX, getDomainExportNames, getDomainStackName, getDomainUsEast1CertificatesStackName, isManagedDomainBinding, DOMAIN_DEPLOY_DEFAULT_PHASE, type DomainDeployPhase, type ManagedDomainBinding, type ManagedDomainExports } from "./infra/domainExports.js";
2
2
  export { BACKUP_VAULT_NAME } from "./infra/backupVault.js";
3
3
  export { APPROVAL_TOKEN_OUTPUT_PREFIX, TOKEN_STDERR_PREFIX } from "./deploy/approvalTokenOutput.js";
4
4
  export { imageTagParameterName } from "./infra/imageTags.js";
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{DNS_APEX as E,getDomainExportNames as o,isManagedDomainBinding as t}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 T}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as n}from"./infra/imageTags.js";import{toPascalCase as N,toKebab as m,toValidDatabaseName as s,toScreamingSnake as O,capitalise as P,getSafeZoneName as C,accountConstructKey as p,hasAsciiStableConstructKey as f}from"./naming/caseConversion.js";import{findAccountNameCollision as g}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as M,suffixedAccountName as D,REGION_SHORT_CODES as x,findTrailingRegionShortCode as u,regionSuffixRejectionMessage as d}from"./naming/connectedAccountName.js";import{normaliseError as l,getErrorMessage as L,hasErrorCode as V,getErrorCode as h,getErrorStack as F,formatErrorString as G}from"./errorUtils.js";import{singleton as H}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as X,filterDangerousEnvVars as Y,maskSensitiveOutput as y,parseShellArgs as B,SCOPED_TOKEN_REGEX as K}from"./securityHelpers.js";import{sleep as k}from"./async/sleep.js";import{mapSettledWithConcurrency as z}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as q,STRUCTURAL_ENVIRONMENTS as w,ACCOUNT_STAGES as J,ACCOUNT_STAGE_LABELS as Q,isAccountStage as $,ACCOUNT_TIERS as ee,AccountTierSchema as re,isAccountTier as Ee,environmentToTier as oe,stageFromWireEnvironment as te,accountTier as _e,getEnvironmentLabel as Se,ACCOUNT_ROLES as ae}from"./environments.js";import{RESOURCE_CATEGORIES as Te,categoriseResource as Re,getExpectedDuration as ne,getFriendlyResourceType as ie}from"./resourceCategorisation.js";import{parseGitRemoteUrl as me}from"./repo/gitRemoteParser.js";import{abbreviateRegion as Oe,AWS_REGIONS_METADATA as Pe,DEFAULT_REGION as Ce,getRegionInfo as pe,MAX_SECONDARY_REGIONS as fe,OPT_IN_REGION_CODES as ce,optInRegionWarning as ge,regions as Ie,suggestRegionForTimezone as Me}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 Le,SSM_COMPONENT_PATTERN as Ve,SSM_COMPONENT_ERROR as he,SSM_STANDARD_MAX_VALUE_BYTES as Fe,SecretNamespaceSchema as Ge,buildNamespaceParts as ve,buildParameterPath as He,parseParameterPath as be,isManageablePath as Xe,parseDotEnv as Ye,escapeDotEnvValue as ye}from"./secrets.js";import{ConnectionWireSchema as Ke,ConnectionsListResponseSchema as We}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as je,deriveTargets as ze,deriveAllTargets as Ze,environmentOrTier as qe,findTarget as we,generateTargetName as Je}from"./targets.js";import{buildAppConfigPath as $e}from"./repo/appPath.js";import{findInfrastructurePaths as rr,findBoundaryPath as Er,isInfrastructureFile as or}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as _r}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as ar,RESERVED_APP_NAME_MESSAGE as Ar,isReservedAppName as Tr,RESERVED_APP_NAME_SUFFIX as Rr,RESERVED_APP_NAME_SUFFIX_MESSAGE as nr,hasReservedAppNameSuffix as ir}from"./naming/reservedAppNames.js";import{deriveContentHashTag as mr,CONTENT_HASH_TAG_PATTERN as sr}from"./infra/deriveContentHashTag.js";import{DEPLOY_MODES as Pr,DeployModeSchema as Cr,IMAGE_TAG_PATTERN as pr,ImageTagSchema as fr,ServiceArtefactSchema as cr,ServiceArtefactsSchema as gr,ARTEFACT_OUTPUT_FIELDS as Ir,artefactOutputKey as Mr}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 Ur,SCHEMA_ADMIN_USER_ENV as lr,SCHEMA_ADMIN_PASSWORD_ENV as Lr,PRISMA_MIGRATION_DIR_RE as Vr,CLICKHOUSE_MIGRATION_SKIP_RE as hr}from"./migration/constants.js";import{PHYSICAL_NAME_FALLBACK_PROPERTIES as Gr}from"./cfn/physicalNameProperties.js";import{PATTERN_TYPE_VALUES as Hr,PATTERN_TYPES as br,isPatternType as Xr,PATTERN_REGISTRY as Yr,DEPLOYABLE_PATTERN_TYPES as yr,OPENNEXT_PATTERN_TYPES as Br,isOpenNextPatternType as Kr,STATIC_SITE_ROUTING_VALUES as Wr}from"./patterns/patternTypes.js";import{DEFAULT_FORMS_FROM_LOCAL_PART as jr,defaultFormsFromAddress as zr,defaultFormsCorsOrigin as Zr,isAddressAtDomain as qr}from"./patterns/staticSiteForms.js";import{DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION as Jr,DEV_SUBSTRATE_PARAMS_FILENAME as Qr,DevSubstrateSynthPropsSchema as $r,DevSubstrateParamsSchema as eE}from"./devSubstrate/params.js";import{DEV_SUBSTRATE_ENTRYPOINT_SOURCE as EE}from"./devSubstrate/entrypoint.js";export{ae as ACCOUNT_ROLES,J as ACCOUNT_STAGES,q as ACCOUNT_STAGES_WITH_ROOT,Q as ACCOUNT_STAGE_LABELS,ee as ACCOUNT_TIERS,A as APPROVAL_TOKEN_OUTPUT_PREFIX,Ir as ARTEFACT_OUTPUT_FIELDS,Pe as AWS_REGIONS_METADATA,re as AccountTierSchema,S as BACKUP_VAULT_NAME,hr as CLICKHOUSE_MIGRATION_SKIP_RE,sr as CONTENT_HASH_TAG_PATTERN,Ke as ConnectionWireSchema,We as ConnectionsListResponseSchema,X as DANGEROUS_ENV_VARS,jr as DEFAULT_FORMS_FROM_LOCAL_PART,Ce as DEFAULT_REGION,yr as DEPLOYABLE_PATTERN_TYPES,Pr as DEPLOY_MODES,EE as DEV_SUBSTRATE_ENTRYPOINT_SOURCE,Qr as DEV_SUBSTRATE_PARAMS_FILENAME,Jr as DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION,E as DNS_APEX,Cr as DeployModeSchema,eE as DevSubstrateParamsSchema,$r as DevSubstrateSynthPropsSchema,Ur as EXPECTED_CH_SCHEMA_VERSION_ENV,ur as EXPECTED_SCHEMA_VERSION_ENV,dr as EXPECTED_SCHEMA_VERSION_TOOL_ENV,pr as IMAGE_TAG_PATTERN,fr as ImageTagSchema,ue as MACHINE_ONLY_SCOPES,fe as MAX_SECONDARY_REGIONS,xr as MIGRATION_SNAPSHOT_NAME_PREFIX,Br as OPENNEXT_PATTERN_TYPES,ce as OPT_IN_REGION_CODES,Yr as PATTERN_REGISTRY,br as PATTERN_TYPES,Hr as PATTERN_TYPE_VALUES,Gr as PHYSICAL_NAME_FALLBACK_PROPERTIES,Vr as PRISMA_MIGRATION_DIR_RE,x as REGION_SHORT_CODES,ar as RESERVED_APP_NAMES,Ar as RESERVED_APP_NAME_MESSAGE,Rr as RESERVED_APP_NAME_SUFFIX,nr as RESERVED_APP_NAME_SUFFIX_MESSAGE,Te as RESOURCE_CATEGORIES,Lr as SCHEMA_ADMIN_PASSWORD_ENV,lr as SCHEMA_ADMIN_USER_ENV,K as SCOPED_TOKEN_REGEX,xe as SCOPE_VALUES,Le as SECRET_NAME_ERROR,le as SECRET_NAME_PATTERN,he as SSM_COMPONENT_ERROR,Ve as SSM_COMPONENT_PATTERN,Fe as SSM_STANDARD_MAX_VALUE_BYTES,Wr as STATIC_SITE_ROUTING_VALUES,w as STRUCTURAL_ENVIRONMENTS,Ge as SecretNamespaceSchema,cr as ServiceArtefactSchema,gr as ServiceArtefactsSchema,T as TOKEN_STDERR_PREFIX,de as USER_GRANTABLE_SCOPES,Oe as abbreviateRegion,p as accountConstructKey,_e as accountTier,Mr as artefactOutputKey,$e as buildAppConfigPath,ve as buildNamespaceParts,He as buildParameterPath,P as capitalise,Re as categoriseResource,M as defaultConnectedAccountName,Zr as defaultFormsCorsOrigin,zr as defaultFormsFromAddress,Ze as deriveAllTargets,mr as deriveContentHashTag,je as deriveRegionsFromOrgConfig,ze as deriveTargets,qe as environmentOrTier,oe as environmentToTier,ye as escapeDotEnvValue,Y as filterDangerousEnvVars,g as findAccountNameCollision,Er as findBoundaryPath,rr as findInfrastructurePaths,we as findTarget,u as findTrailingRegionShortCode,G as formatErrorString,Je as generateTargetName,o as getDomainExportNames,Se as getEnvironmentLabel,h as getErrorCode,L as getErrorMessage,F as getErrorStack,ne as getExpectedDuration,ie as getFriendlyResourceType,pe as getRegionInfo,C as getSafeZoneName,f as hasAsciiStableConstructKey,V as hasErrorCode,ir as hasReservedAppNameSuffix,n as imageTagParameterName,_r as inferContainerFromCandidates,$ as isAccountStage,Ee as isAccountTier,qr as isAddressAtDomain,or as isInfrastructureFile,Xe as isManageablePath,t as isManagedDomainBinding,Kr as isOpenNextPatternType,Xr as isPatternType,Tr as isReservedAppName,z as mapSettledWithConcurrency,y as maskSensitiveOutput,l as normaliseError,ge as optInRegionWarning,Ye as parseDotEnv,me as parseGitRemoteUrl,be as parseParameterPath,B as parseShellArgs,d as regionSuffixRejectionMessage,Ie as regions,H as singleton,k as sleep,te as stageFromWireEnvironment,D as suffixedAccountName,Me as suggestRegionForTimezone,m as toKebab,N as toPascalCase,O as toScreamingSnake,s as toValidDatabaseName};
1
+ import{DNS_APEX as E,getDomainExportNames as o,getDomainStackName as t,getDomainUsEast1CertificatesStackName as a,isManagedDomainBinding as _,DOMAIN_DEPLOY_DEFAULT_PHASE as S}from"./infra/domainExports.js";import{BACKUP_VAULT_NAME as T}from"./infra/backupVault.js";import{APPROVAL_TOKEN_OUTPUT_PREFIX as n,TOKEN_STDERR_PREFIX as i}from"./deploy/approvalTokenOutput.js";import{imageTagParameterName as m}from"./infra/imageTags.js";import{toPascalCase as O,toKebab as P,toValidDatabaseName as C,toScreamingSnake as p,capitalise as f,getSafeZoneName as c,accountConstructKey as g,hasAsciiStableConstructKey as I}from"./naming/caseConversion.js";import{findAccountNameCollision as M}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as u,suffixedAccountName as d,REGION_SHORT_CODES as U,findTrailingRegionShortCode as L,regionSuffixRejectionMessage as l}from"./naming/connectedAccountName.js";import{normaliseError as F,getErrorMessage as h,hasErrorCode as G,getErrorCode as v,getErrorStack as H,formatErrorString as b}from"./errorUtils.js";import{singleton as Y}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as B,filterDangerousEnvVars as K,maskSensitiveOutput as W,parseShellArgs as k,SCOPED_TOKEN_REGEX as j}from"./securityHelpers.js";import{sleep as Z}from"./async/sleep.js";import{mapSettledWithConcurrency as w}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as Q,STRUCTURAL_ENVIRONMENTS as $,ACCOUNT_STAGES as ee,ACCOUNT_STAGE_LABELS as re,isAccountStage as Ee,ACCOUNT_TIERS as oe,AccountTierSchema as te,isAccountTier as ae,environmentToTier as _e,stageFromWireEnvironment as Se,accountTier as Ae,getEnvironmentLabel as Te,ACCOUNT_ROLES as Re}from"./environments.js";import{RESOURCE_CATEGORIES as ie,categoriseResource as Ne,getExpectedDuration as me,getFriendlyResourceType as se}from"./resourceCategorisation.js";import{parseGitRemoteUrl as Pe}from"./repo/gitRemoteParser.js";import{abbreviateRegion as pe,AWS_REGIONS_METADATA as fe,DEFAULT_REGION as ce,getRegionInfo as ge,MAX_SECONDARY_REGIONS as Ie,OPT_IN_REGION_CODES as De,optInRegionWarning as Me,regions as xe,suggestRegionForTimezone as ue}from"./infra/regions.js";import{SCOPE_VALUES as Ue,MACHINE_ONLY_SCOPES as Le,USER_GRANTABLE_SCOPES as le}from"./infra/tokenScopes.js";import{SECRET_NAME_PATTERN as Fe,SECRET_NAME_ERROR as he,SSM_COMPONENT_PATTERN as Ge,SSM_COMPONENT_ERROR as ve,SSM_STANDARD_MAX_VALUE_BYTES as He,SecretNamespaceSchema as be,buildNamespaceParts as Xe,buildParameterPath as Ye,parseParameterPath as ye,isManageablePath as Be,parseDotEnv as Ke,escapeDotEnvValue as We}from"./secrets.js";import{ConnectionWireSchema as je,ConnectionsListResponseSchema as ze}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as qe,deriveTargets as we,deriveAllTargets as Je,environmentOrTier as Qe,findTarget as $e,generateTargetName as er}from"./targets.js";import{buildAppConfigPath as Er}from"./repo/appPath.js";import{findInfrastructurePaths as tr,findBoundaryPath as ar,isInfrastructureFile as _r}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as Ar}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Rr,RESERVED_APP_NAME_MESSAGE as nr,isReservedAppName as ir,RESERVED_APP_NAME_SUFFIX as Nr,RESERVED_APP_NAME_SUFFIX_MESSAGE as mr,hasReservedAppNameSuffix as sr}from"./naming/reservedAppNames.js";import{deriveContentHashTag as Pr,CONTENT_HASH_TAG_PATTERN as Cr}from"./infra/deriveContentHashTag.js";import{DEPLOY_MODES as fr,DeployModeSchema as cr,IMAGE_TAG_PATTERN as gr,ImageTagSchema as Ir,ServiceArtefactSchema as Dr,ServiceArtefactsSchema as Mr,ARTEFACT_OUTPUT_FIELDS as xr,artefactOutputKey as ur}from"./infra/deployArtefacts.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Ur,EXPECTED_SCHEMA_VERSION_ENV as Lr,EXPECTED_SCHEMA_VERSION_TOOL_ENV as lr,EXPECTED_CH_SCHEMA_VERSION_ENV as Vr,SCHEMA_ADMIN_USER_ENV as Fr,SCHEMA_ADMIN_PASSWORD_ENV as hr,PRISMA_MIGRATION_DIR_RE as Gr,CLICKHOUSE_MIGRATION_SKIP_RE as vr}from"./migration/constants.js";import{PHYSICAL_NAME_FALLBACK_PROPERTIES as br}from"./cfn/physicalNameProperties.js";import{PATTERN_TYPE_VALUES as Yr,PATTERN_TYPES as yr,isPatternType as Br,PATTERN_REGISTRY as Kr,DEPLOYABLE_PATTERN_TYPES as Wr,OPENNEXT_PATTERN_TYPES as kr,isOpenNextPatternType as jr,STATIC_SITE_ROUTING_VALUES as zr}from"./patterns/patternTypes.js";import{DEFAULT_FORMS_FROM_LOCAL_PART as qr,defaultFormsFromAddress as wr,defaultFormsCorsOrigin as Jr,isAddressAtDomain as Qr}from"./patterns/staticSiteForms.js";import{DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION as eE,DEV_SUBSTRATE_PARAMS_FILENAME as rE,DevSubstrateSynthPropsSchema as EE,DevSubstrateParamsSchema as oE}from"./devSubstrate/params.js";import{DEV_SUBSTRATE_ENTRYPOINT_SOURCE as aE}from"./devSubstrate/entrypoint.js";export{Re as ACCOUNT_ROLES,ee as ACCOUNT_STAGES,Q as ACCOUNT_STAGES_WITH_ROOT,re as ACCOUNT_STAGE_LABELS,oe as ACCOUNT_TIERS,n as APPROVAL_TOKEN_OUTPUT_PREFIX,xr as ARTEFACT_OUTPUT_FIELDS,fe as AWS_REGIONS_METADATA,te as AccountTierSchema,T as BACKUP_VAULT_NAME,vr as CLICKHOUSE_MIGRATION_SKIP_RE,Cr as CONTENT_HASH_TAG_PATTERN,je as ConnectionWireSchema,ze as ConnectionsListResponseSchema,B as DANGEROUS_ENV_VARS,qr as DEFAULT_FORMS_FROM_LOCAL_PART,ce as DEFAULT_REGION,Wr as DEPLOYABLE_PATTERN_TYPES,fr as DEPLOY_MODES,aE as DEV_SUBSTRATE_ENTRYPOINT_SOURCE,rE as DEV_SUBSTRATE_PARAMS_FILENAME,eE as DEV_SUBSTRATE_PARAMS_SCHEMA_VERSION,E as DNS_APEX,S as DOMAIN_DEPLOY_DEFAULT_PHASE,cr as DeployModeSchema,oE as DevSubstrateParamsSchema,EE as DevSubstrateSynthPropsSchema,Vr as EXPECTED_CH_SCHEMA_VERSION_ENV,Lr as EXPECTED_SCHEMA_VERSION_ENV,lr as EXPECTED_SCHEMA_VERSION_TOOL_ENV,gr as IMAGE_TAG_PATTERN,Ir as ImageTagSchema,Le as MACHINE_ONLY_SCOPES,Ie as MAX_SECONDARY_REGIONS,Ur as MIGRATION_SNAPSHOT_NAME_PREFIX,kr as OPENNEXT_PATTERN_TYPES,De as OPT_IN_REGION_CODES,Kr as PATTERN_REGISTRY,yr as PATTERN_TYPES,Yr as PATTERN_TYPE_VALUES,br as PHYSICAL_NAME_FALLBACK_PROPERTIES,Gr as PRISMA_MIGRATION_DIR_RE,U as REGION_SHORT_CODES,Rr as RESERVED_APP_NAMES,nr as RESERVED_APP_NAME_MESSAGE,Nr as RESERVED_APP_NAME_SUFFIX,mr as RESERVED_APP_NAME_SUFFIX_MESSAGE,ie as RESOURCE_CATEGORIES,hr as SCHEMA_ADMIN_PASSWORD_ENV,Fr as SCHEMA_ADMIN_USER_ENV,j as SCOPED_TOKEN_REGEX,Ue as SCOPE_VALUES,he as SECRET_NAME_ERROR,Fe as SECRET_NAME_PATTERN,ve as SSM_COMPONENT_ERROR,Ge as SSM_COMPONENT_PATTERN,He as SSM_STANDARD_MAX_VALUE_BYTES,zr as STATIC_SITE_ROUTING_VALUES,$ as STRUCTURAL_ENVIRONMENTS,be as SecretNamespaceSchema,Dr as ServiceArtefactSchema,Mr as ServiceArtefactsSchema,i as TOKEN_STDERR_PREFIX,le as USER_GRANTABLE_SCOPES,pe as abbreviateRegion,g as accountConstructKey,Ae as accountTier,ur as artefactOutputKey,Er as buildAppConfigPath,Xe as buildNamespaceParts,Ye as buildParameterPath,f as capitalise,Ne as categoriseResource,u as defaultConnectedAccountName,Jr as defaultFormsCorsOrigin,wr as defaultFormsFromAddress,Je as deriveAllTargets,Pr as deriveContentHashTag,qe as deriveRegionsFromOrgConfig,we as deriveTargets,Qe as environmentOrTier,_e as environmentToTier,We as escapeDotEnvValue,K as filterDangerousEnvVars,M as findAccountNameCollision,ar as findBoundaryPath,tr as findInfrastructurePaths,$e as findTarget,L as findTrailingRegionShortCode,b as formatErrorString,er as generateTargetName,o as getDomainExportNames,t as getDomainStackName,a as getDomainUsEast1CertificatesStackName,Te as getEnvironmentLabel,v as getErrorCode,h as getErrorMessage,H as getErrorStack,me as getExpectedDuration,se as getFriendlyResourceType,ge as getRegionInfo,c as getSafeZoneName,I as hasAsciiStableConstructKey,G as hasErrorCode,sr as hasReservedAppNameSuffix,m as imageTagParameterName,Ar as inferContainerFromCandidates,Ee as isAccountStage,ae as isAccountTier,Qr as isAddressAtDomain,_r as isInfrastructureFile,Be as isManageablePath,_ as isManagedDomainBinding,jr as isOpenNextPatternType,Br as isPatternType,ir as isReservedAppName,w as mapSettledWithConcurrency,W as maskSensitiveOutput,F as normaliseError,Me as optInRegionWarning,Ke as parseDotEnv,Pe as parseGitRemoteUrl,ye as parseParameterPath,k as parseShellArgs,l as regionSuffixRejectionMessage,xe as regions,Y as singleton,Z as sleep,Se as stageFromWireEnvironment,d as suffixedAccountName,ue as suggestRegionForTimezone,P as toKebab,O as toPascalCase,p as toScreamingSnake,C as toValidDatabaseName};
@@ -1,4 +1,48 @@
1
1
  export declare const DNS_APEX: "@";
2
+ /**
3
+ * CloudFormation stack name for a domain component (D2 wiring contract).
4
+ *
5
+ * Mirrors the domain generator's app naming (`cli/generators/domain/
6
+ * generator.ts`: `toPascalCase(domainName.split(".").join(""))` + `Domain`,
7
+ * the name passed to both `App.getApp(...)` and `app.getStack(...)` in the
8
+ * emitted `infrastructure.ts` — the stack key IS the deployed stack name),
9
+ * e.g. `example.com` → `ExamplecomDomain`. Both the deploy-state record
10
+ * (`domainDeployOperation`) and the binding resolution
11
+ * (`DomainService.resolveDomainForApp` → DescribeStacks) MUST derive the name
12
+ * through this helper so the stack the deploy recorded is the stack the
13
+ * resolution describes.
14
+ */
15
+ export declare function getDomainStackName(domainName: string): string;
16
+ /**
17
+ * Name of a domain stack's paired us-east-1 certificate stack (D3 contract).
18
+ *
19
+ * When a domain declares a `cloudFront: true` certificate and the domain
20
+ * stack itself does NOT deploy to us-east-1, the Domain construct mints the
21
+ * certificate (and the `<zone>-us-east-1-certificate-arn` export) in this
22
+ * paired stack instead, us-east-1 being the only region CloudFront accepts
23
+ * viewer certificates from. Both the construct side
24
+ * (`composeDomainCertificates`) and the read side
25
+ * (`DomainService.resolveDomainForApp`, which describes the paired stack
26
+ * with a us-east-1 CloudFormation client) MUST derive the name here so the
27
+ * stack the synth mints is the stack the resolution reads.
28
+ */
29
+ export declare function getDomainUsEast1CertificatesStackName(domainStackName: string): string;
30
+ /**
31
+ * Two-step deploy phase for constructs whose certificates must not be issued
32
+ * before their zone's NS delegation has propagated (design R2 cert-hang
33
+ * guard). `"zone"` synthesises the hosted zone (+ delegation record) only;
34
+ * `"full"` additionally issues certificates. Shared by the delegated `Domain`
35
+ * topology, `DevSubstrate`, and the deploy-core two-phase orchestration —
36
+ * homed here (the lowest layer) so the construct and deploy sides read one
37
+ * contract.
38
+ */
39
+ export type DomainDeployPhase = "zone" | "full";
40
+ /**
41
+ * Default deploy phase when `phase` is omitted — the complete build. The
42
+ * two-step guard (R2) sets `"zone"` explicitly for step 1, so the safe
43
+ * default for a single-shot deploy is everything.
44
+ */
45
+ export declare const DOMAIN_DEPLOY_DEFAULT_PHASE: DomainDeployPhase;
2
46
  /**
3
47
  * Compute predictable CloudFormation export names for domain stack outputs.
4
48
  * Used by both infrastructure constructs (to set export names) and CLI services
@@ -1 +1 @@
1
- var r=Object.defineProperty;var n=(t,e)=>r(t,"name",{value:e,configurable:!0});const a="@";function i(t){const e=t.replace(/\./g,"-");return{hostedZoneId:`${e}-hosted-zone-id`,certificateArn:`${e}-certificate-arn`,usEast1CertificateArn:`${e}-us-east-1-certificate-arn`,delegationRoleArn:`${e}-delegation-role-arn`,nameservers:`${e}-nameservers`}}n(i,"getDomainExportNames");function s(t){return typeof t.hostedZoneId=="string"}n(s,"isManagedDomainBinding");export{a as DNS_APEX,i as getDomainExportNames,s as isManagedDomainBinding};
1
+ var r=Object.defineProperty;var n=(e,t)=>r(e,"name",{value:t,configurable:!0});import{toPascalCase as a}from"../naming/caseConversion.js";const s="@";function c(e){return`${a(e.split(".").join(""))}Domain`}n(c,"getDomainStackName");function f(e){return`${e}UsEast1Certificates`}n(f,"getDomainUsEast1CertificatesStackName");const m="full";function p(e){const t=e.replace(/\./g,"-");return{hostedZoneId:`${t}-hosted-zone-id`,certificateArn:`${t}-certificate-arn`,usEast1CertificateArn:`${t}-us-east-1-certificate-arn`,delegationRoleArn:`${t}-delegation-role-arn`,nameservers:`${t}-nameservers`}}n(p,"getDomainExportNames");function u(e){return typeof e.hostedZoneId=="string"}n(u,"isManagedDomainBinding");export{s as DNS_APEX,m as DOMAIN_DEPLOY_DEFAULT_PHASE,p as getDomainExportNames,c as getDomainStackName,f as getDomainUsEast1CertificatesStackName,u as isManagedDomainBinding};
@@ -20,6 +20,7 @@
20
20
  * for the rationale behind extracting this module.
21
21
  */
22
22
  import type { ScanPath } from "./scanTypes.js";
23
+ export declare const INFRASTRUCTURE_FILE = "infrastructure.ts";
23
24
  export declare const MARKER_DIRECTORY = "fjall";
24
25
  export interface MarkerEntry {
25
26
  /** POSIX-style path relative to the repo root. */
@@ -1 +1 @@
1
- var c=Object.defineProperty;var r=(t,n)=>c(t,"name",{value:n,configurable:!0});const s="infrastructure.ts",a="fjall";function P(t,n){const e=n?.excludedPathPrefixes,u=n?.excludedPathSegments,o=[];for(const i of t){if(!l(i.path)||e&&x(i.path,e)||u&&p(i.path,u))continue;const f=h(i.path);f!==null&&o.push({configPath:d(i.path),boundaryPath:f})}return o}r(P,"findInfrastructurePaths");function l(t){return t===s||t.endsWith(`/${s}`)}r(l,"isInfrastructureFile");function h(t){const n=t.split("/");for(let e=0;e<n.length-1;e++)if(n[e]===a)return n.slice(0,e+1).join("/");return null}r(h,"findBoundaryPath");function d(t){return t.slice(0,-`/${s}`.length)}r(d,"toConfigDir");function x(t,n){for(const e of n)if(t.startsWith(e))return!0;return!1}r(x,"hasExcludedPrefix");function p(t,n){const e=t.split("/");for(let u=0;u<e.length-1;u++)if(n.has(e[u]))return!0;return!1}r(p,"hasExcludedSegment");export{a as MARKER_DIRECTORY,h as findBoundaryPath,P as findInfrastructurePaths,l as isInfrastructureFile};
1
+ var c=Object.defineProperty;var r=(t,e)=>c(t,"name",{value:e,configurable:!0});const s="infrastructure.ts",a="fjall";function P(t,e){const n=e?.excludedPathPrefixes,u=e?.excludedPathSegments,o=[];for(const i of t){if(!l(i.path)||n&&x(i.path,n)||u&&p(i.path,u))continue;const f=h(i.path);f!==null&&o.push({configPath:d(i.path),boundaryPath:f})}return o}r(P,"findInfrastructurePaths");function l(t){return t===s||t.endsWith(`/${s}`)}r(l,"isInfrastructureFile");function h(t){const e=t.split("/");for(let n=0;n<e.length-1;n++)if(e[n]===a)return e.slice(0,n+1).join("/");return null}r(h,"findBoundaryPath");function d(t){return t.slice(0,-`/${s}`.length)}r(d,"toConfigDir");function x(t,e){for(const n of e)if(t.startsWith(n))return!0;return!1}r(x,"hasExcludedPrefix");function p(t,e){const n=t.split("/");for(let u=0;u<n.length-1;u++)if(e.has(n[u]))return!0;return!1}r(p,"hasExcludedSegment");export{s as INFRASTRUCTURE_FILE,a as MARKER_DIRECTORY,h as findBoundaryPath,P as findInfrastructurePaths,l as isInfrastructureFile};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Bounded downward scan for fjall projects in child directories.
3
+ *
4
+ * The complement of `findRepoRoot`: config discovery walks UP from the
5
+ * current directory, so a user sitting one level ABOVE their project (a
6
+ * container directory holding several checkouts) gets no signal that the
7
+ * project they meant is right below them. This scanner powers the
8
+ * "did you mean cd <dir>?" guidance in CLI error messages.
9
+ */
10
+ export interface DiscoveredProject {
11
+ /** Absolute path of the project directory (the one to cd into). */
12
+ projectDir: string;
13
+ /** Absolute path of the project's fjall-config.json. */
14
+ configPath: string;
15
+ /** The config's activeTarget, when the file is readable and carries one. */
16
+ activeTarget: string | undefined;
17
+ /**
18
+ * App directory names carrying an infrastructure.ts marker — direct
19
+ * subdirectories of the config's directory, plus one monorepo container
20
+ * level (`<projectDir>/<container>/fjall/<app>`). Deeper nesting is not
21
+ * enumerated; the list is a sample, not a census.
22
+ */
23
+ apps: string[];
24
+ }
25
+ export interface FindProjectsBelowOptions {
26
+ /** How many levels below startDir to descend (default 3). */
27
+ maxDepth?: number;
28
+ /** Total child directories to inspect before giving up (default 2000). */
29
+ maxDirectories?: number;
30
+ }
31
+ /**
32
+ * Find fjall projects strictly below `startDir`. A project is a directory
33
+ * containing `fjall/fjall-config.json` or a direct `fjall-config.json` —
34
+ * the same two shapes Config.loadConfig discovers when walking upward.
35
+ * Breadth-first with alphabetical siblings, so results are deterministic
36
+ * and shallower projects come first; found projects are not descended into.
37
+ */
38
+ export declare function findProjectsBelow(startDir: string, options?: FindProjectsBelowOptions): Promise<DiscoveredProject[]>;
39
+ /**
40
+ * True when `dir` has the scaffolded app layout `fjall/<app>/infrastructure.ts`
41
+ * — the signal that config creation at `dir/fjall/fjall-config.json` lands in
42
+ * a real project rather than planting a stray file in an unrelated tree.
43
+ */
44
+ export declare function hasFjallAppLayout(dir: string): Promise<boolean>;
@@ -0,0 +1 @@
1
+ var C=Object.defineProperty;var o=(t,r)=>C(t,"name",{value:r,configurable:!0});import{readdir as D,readFile as _,stat as j}from"fs/promises";import{join as a}from"path";import{ROOT_CONFIG_FILENAME as E}from"../config.js";import{getErrorMessage as T}from"../errorUtils.js";import{logger as y}from"../logger.js";import{maskSensitiveOutput as g}from"../securityHelpers.js";import{INFRASTRUCTURE_FILE as x,MARKER_DIRECTORY as s}from"./findInfrastructurePaths.js";import{EXCLUDED_DIRECTORY_NAMES as I}from"./scanLocalRepository.js";const O=3,S=2e3;async function q(t,r={}){const e=r.maxDepth??O,n=r.maxDirectories??S,i=[];let c=[t],l=0;for(let m=0;m<e&&c.length>0;m++){const d=[];for(const A of c){const R=await f(A);for(const w of R){if(l>=n)return i;l++;const h=await v(w);h?i.push(h):d.push(w)}}c=d}return i}o(q,"findProjectsBelow");async function G(t){return(await u(a(t,s))).length>0}o(G,"hasFjallAppLayout");async function v(t){const r=a(t,s,E);if(await p(r))return F(t,r,a(t,s));const e=a(t,E);return await p(e)?F(t,e,t):null}o(v,"inspectForProject");async function F(t,r,e){return{projectDir:t,configPath:r,activeTarget:await b(r),apps:await L(t,e)}}o(F,"buildProject");async function L(t,r){const e=new Set(await u(r));for(const n of await f(t))if(n!==r)for(const i of await u(a(n,s)))e.add(i);return[...e].sort()}o(L,"listProjectApps");async function b(t){try{const r=await _(t,"utf8"),e=JSON.parse(r);if(e!==null&&typeof e=="object"&&"activeTarget"in e){const n=e.activeTarget;if(typeof n=="string"&&n!=="")return n}}catch(r){y.debug("FindProjectsBelow",`Could not read activeTarget from ${t}`,{error:g(T(r))})}}o(b,"readActiveTarget");async function u(t){const r=[];for(const e of await f(t))await p(a(e,x))&&r.push(e.slice(t.length+1));return r}o(u,"listApps");async function f(t){let r;try{r=await D(t,{withFileTypes:!0})}catch(e){return y.debug("FindProjectsBelow",`Could not read directory ${t}`,{error:g(T(e))}),[]}return r.filter(e=>!e.isSymbolicLink()&&e.isDirectory()&&!e.name.startsWith(".")&&!I.has(e.name)).map(e=>e.name).sort().map(e=>a(t,e))}o(f,"listSubdirectories");function p(t){return j(t).then(r=>r.isFile(),()=>!1)}o(p,"fileExists");export{q as findProjectsBelow,G as hasFjallAppLayout};
@@ -18,6 +18,7 @@
18
18
  * .next, .turbo, .vite, .cache.
19
19
  */
20
20
  import type { ScanPath } from "./scanTypes.js";
21
+ export declare const EXCLUDED_DIRECTORY_NAMES: ReadonlySet<string>;
21
22
  export interface ScanLocalRepositoryResult {
22
23
  paths: ScanPath[];
23
24
  }
@@ -1 +1 @@
1
- var f=Object.defineProperty;var r=(t,i)=>f(t,"name",{value:i,configurable:!0});import{readdir as l}from"fs/promises";import{join as m,relative as h,sep as o}from"path";import{findInfrastructurePaths as d,isInfrastructureFile as p}from"./findInfrastructurePaths.js";const c=new Set(["node_modules",".git","dist","build","coverage",".next",".turbo",".vite",".cache"]);async function x(t){const i=[];return await u(t,t,i),{paths:d(i,{excludedPathSegments:c})}}r(x,"scanLocalRepository");async function u(t,i,n){let a;try{a=await l(i,{withFileTypes:!0})}catch{return}for(const e of a){if(e.isSymbolicLink())continue;const s=m(i,e.name);if(e.isDirectory()){if(c.has(e.name))continue;await u(t,s,n);continue}e.isFile()&&p(e.name)&&n.push({path:y(t,s)})}}r(u,"walk");function y(t,i){const n=h(t,i);return o==="/"?n:n.split(o).join("/")}r(y,"toPosixRelative");export{x as scanLocalRepository};
1
+ var f=Object.defineProperty;var r=(t,i)=>f(t,"name",{value:i,configurable:!0});import{readdir as l}from"fs/promises";import{join as m,relative as h,sep as o}from"path";import{findInfrastructurePaths as p,isInfrastructureFile as d}from"./findInfrastructurePaths.js";const c=new Set(["node_modules",".git","dist","build","coverage",".next",".turbo",".vite",".cache"]);async function v(t){const i=[];return await u(t,t,i),{paths:p(i,{excludedPathSegments:c})}}r(v,"scanLocalRepository");async function u(t,i,n){let a;try{a=await l(i,{withFileTypes:!0})}catch{return}for(const e of a){if(e.isSymbolicLink())continue;const s=m(i,e.name);if(e.isDirectory()){if(c.has(e.name))continue;await u(t,s,n);continue}e.isFile()&&d(e.name)&&n.push({path:y(t,s)})}}r(u,"walk");function y(t,i){const n=h(t,i);return o==="/"?n:n.split(o).join("/")}r(y,"toPosixRelative");export{c as EXCLUDED_DIRECTORY_NAMES,v as scanLocalRepository};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/util",
3
- "version": "3.5.2",
3
+ "version": "3.6.1",
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": "f59755647b2fd81ff692a3545e8e199d93c3bdf8"
137
+ "gitHead": "4bf3b77f9ef1472ca7afc6da552a225062ad602c"
138
138
  }