@fjall/util 2.19.5 → 2.20.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 +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/naming/connectedAccountName.d.ts +46 -0
- package/dist/naming/connectedAccountName.js +1 -0
- package/dist/repo/findRepoRoot.js +1 -1
- package/package.json +3 -3
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
66 files minified at 2026-06-23T06:42:06.638Z
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type Result } from "./docker/result.js";
|
|
3
3
|
import { type AccountTier } from "./environments.js";
|
|
4
|
+
/**
|
|
5
|
+
* The Fjall root config filename. Single source of truth — the repo-root marker
|
|
6
|
+
* in findRepoRoot.ts MUST match this exactly or root detection diverges. Only
|
|
7
|
+
* this exact name is discovered; sibling `.old` / `.backup` copies are inert.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ROOT_CONFIG_FILENAME = "fjall-config.json";
|
|
4
10
|
/**
|
|
5
11
|
* Backup Vault Lock modes for an account's DisasterRecovery vault.
|
|
6
12
|
* See decisions/2026-06-03-backup-vault-lock-mode-by-intent.md.
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as i from"fs";import*as f from"path";import{z as s}from"zod";import{failure as l,success as
|
|
1
|
+
import*as i from"fs";import*as f from"path";import{z as s}from"zod";import{failure as l,success as T}from"./docker/result.js";import{getErrorMessage as p}from"./errorUtils.js";import{logger as h}from"./logger.js";import{maskSensitiveOutput as g}from"./securityHelpers.js";const E=10,u="fjall-config.json",F=["compliance","governance","none"],R=["enforced","off"],K=["centralised","off"],M=["account","draining","org"],N="managementEvents",b="organisationManagementEvents",k=["active","draining","removed"],I="FjallTrailBucketName",L="FjallTrailKeyArn",U="OrganisationTrailBucketName",w=s.object({name:s.string(),type:s.enum(["apex","delegated"]),parentDomain:s.string().optional(),account:s.string().optional()}).strict(),y=s.object({activeTarget:s.string().optional(),domains:s.array(w).optional()}).strict(),S=s.object({activeTarget:s.string().optional(),domains:s.array(w).optional()});function v(C={}){return JSON.stringify(C,null,2)}const O=y.keyof().options;function A(C,e,t){const n=e[t];n!==void 0&&(C[t]=n)}class a{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<E;n++){const r=f.join(t,"fjall"),c=f.join(r,u);if(i.existsSync(c))return r;const o=f.join(t,u);if(i.existsSync(o))return t;const d=f.dirname(t);if(d===t)break;t=d}return null}static loadConfigFile(e){try{return i.accessSync(e,i.constants.R_OK),i.readFileSync(e,{encoding:"utf8"})}catch(t){return h.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:g(p(t))}),null}}static loadConfig(e){const t=a.findConfigDirectory(e);if(!t)return new a;const n=f.join(t,u),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(m){throw a.formatZodError(m,u)}const d=y.safeParse(o);if(d.success)c=d.data;else{const m=S.safeParse(o);c=m.success?m.data:{},h.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,u)}if(this.loadFailed)return l(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 l(new Error(`Cannot create config directory ${t}: ${g(p(o))}`))}const n=a.assertWritable(e,t);if(!n.success)return n;const r=v(this.mergeWithDisk(e)),c=`${e}.tmp-${process.pid}`;try{i.writeFileSync(c,r,{mode:384}),i.renameSync(c,e)}catch(o){return l(new Error(`Failed to save ${e}: ${g(p(o))}`))}return T(void 0)}static assertWritable(e,t){if(i.existsSync(e))try{i.accessSync(e,i.constants.W_OK)}catch{return l(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 l(new Error(`Cannot save ${e}: the directory ${t} is not writable. Make it writable (e.g. chmod u+w ${t}) and retry.`))}return T(void 0)}mergeWithDisk(e){const t=a.readDiskConfigForMerge(e);if(t===void 0)return this.rootConfig;const n={...t};for(const r of O)A(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){h.warn("Config",`Could not re-read ${e} before saving; writing in-memory state without merging`,{file:e,error:g(p(r))});return}const n=y.safeParse(t);if(!n.success){h.warn("Config",`On-disk ${e} failed validation before saving; writing in-memory state without merging`,{file:e,error:g(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{N as ACCOUNT_TRAIL_NAME,k as ACCOUNT_TRAIL_STATES,a as Config,b as ORGANISATION_TRAIL_NAME,U as ORG_TRAIL_BUCKET_OUTPUT_KEY,K as ROOT_ACCESS_MANAGEMENT_MODES,u as ROOT_CONFIG_FILENAME,y as RootConfigSchema,R as S3_BPA_MODES,I as TRAIL_BUCKET_OUTPUT_KEY,L as TRAIL_KEY_ARN_OUTPUT_KEY,M as TRAIL_LIFECYCLE_STATES,F as VAULT_LOCK_MODES,v as serialiseRootConfig};
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { BACKUP_VAULT_NAME } from "./infra/backupVault.js";
|
|
|
3
3
|
export { imageTagParameterName } from "./infra/imageTags.js";
|
|
4
4
|
export { toPascalCase, toKebab, toValidDatabaseName, toScreamingSnake, capitalise, getSafeZoneName, accountConstructKey, hasAsciiStableConstructKey } from "./naming/caseConversion.js";
|
|
5
5
|
export { findAccountNameCollision, type AccountNameCollision } from "./naming/accountNameCollision.js";
|
|
6
|
+
export { defaultConnectedAccountName, suffixedAccountName, REGION_SHORT_CODES, findTrailingRegionShortCode, regionSuffixRejectionMessage } from "./naming/connectedAccountName.js";
|
|
6
7
|
export { normaliseError, getErrorMessage, hasErrorCode, getErrorCode, getErrorStack, formatErrorString } from "./errorUtils.js";
|
|
7
8
|
export { singleton } from "./async/singleton.js";
|
|
8
9
|
export { DANGEROUS_ENV_VARS, filterDangerousEnvVars, maskSensitiveOutput, parseShellArgs } from "./securityHelpers.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
|
|
1
|
+
import{DNS_APEX as o,getDomainExportNames as t}from"./infra/domainExports.js";import{BACKUP_VAULT_NAME as E}from"./infra/backupVault.js";import{imageTagParameterName as i}from"./infra/imageTags.js";import{toPascalCase as _,toKebab as A,toValidDatabaseName as m,toScreamingSnake as s,capitalise as R,getSafeZoneName as C,accountConstructKey as N,hasAsciiStableConstructKey as T}from"./naming/caseConversion.js";import{findAccountNameCollision as g}from"./naming/accountNameCollision.js";import{defaultConnectedAccountName as c,suffixedAccountName as O,REGION_SHORT_CODES as I,findTrailingRegionShortCode as x,regionSuffixRejectionMessage as u}from"./naming/connectedAccountName.js";import{normaliseError as l,getErrorMessage as M,hasErrorCode as P,getErrorCode as D,getErrorStack as V,formatErrorString as U}from"./errorUtils.js";import{singleton as h}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as H,filterDangerousEnvVars as L,maskSensitiveOutput as b,parseShellArgs as F}from"./securityHelpers.js";import{sleep as y}from"./async/sleep.js";import{mapSettledWithConcurrency as X}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as B,STRUCTURAL_ENVIRONMENTS as j,ACCOUNT_STAGES as z,ACCOUNT_STAGE_LABELS as Y,isAccountStage as Z,ACCOUNT_TIERS as q,AccountTierSchema as w,isAccountTier as J,environmentToTier as Q,stageFromWireEnvironment as $,accountTier as ee,getEnvironmentLabel as re,ACCOUNT_ROLES as oe}from"./environments.js";import{RESOURCE_CATEGORIES as ne,categoriseResource as Ee,getExpectedDuration as ae,getFriendlyResourceType as ie}from"./resourceCategorisation.js";import{parseGitRemoteUrl as _e}from"./repo/gitRemoteParser.js";import{abbreviateRegion as me,AWS_REGIONS_METADATA as se,DEFAULT_REGION as Re,getRegionInfo as Ce,MAX_SECONDARY_REGIONS as Ne,OPT_IN_REGION_CODES as Te,optInRegionWarning as fe,regions as ge,suggestRegionForTimezone as pe}from"./infra/regions.js";import{SCOPE_VALUES as Oe}from"./infra/tokenScopes.js";import{ConnectionWireSchema as xe,ConnectionsListResponseSchema as ue}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as le,deriveTargets as Me,deriveAllTargets as Pe,environmentOrTier as De,findTarget as Ve,generateTargetName as Ue}from"./targets.js";import{buildAppConfigPath as he}from"./repo/appPath.js";import{findInfrastructurePaths as He,findBoundaryPath as Le,isInfrastructureFile as be}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as We}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Ke,RESERVED_APP_NAME_MESSAGE as Xe,isReservedAppName as ke}from"./naming/reservedAppNames.js";import{deriveContentHashTag as je}from"./infra/deriveContentHashTag.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Ye,EXPECTED_SCHEMA_VERSION_ENV as Ze,EXPECTED_SCHEMA_VERSION_TOOL_ENV as qe,EXPECTED_CH_SCHEMA_VERSION_ENV as we,SCHEMA_ADMIN_USER_ENV as Je,SCHEMA_ADMIN_PASSWORD_ENV as Qe,PRISMA_MIGRATION_DIR_RE as $e,CLICKHOUSE_MIGRATION_SKIP_RE as er}from"./migration/constants.js";export{oe as ACCOUNT_ROLES,z as ACCOUNT_STAGES,B as ACCOUNT_STAGES_WITH_ROOT,Y as ACCOUNT_STAGE_LABELS,q as ACCOUNT_TIERS,se as AWS_REGIONS_METADATA,w as AccountTierSchema,E as BACKUP_VAULT_NAME,er as CLICKHOUSE_MIGRATION_SKIP_RE,xe as ConnectionWireSchema,ue as ConnectionsListResponseSchema,H as DANGEROUS_ENV_VARS,Re as DEFAULT_REGION,o as DNS_APEX,we as EXPECTED_CH_SCHEMA_VERSION_ENV,Ze as EXPECTED_SCHEMA_VERSION_ENV,qe as EXPECTED_SCHEMA_VERSION_TOOL_ENV,Ne as MAX_SECONDARY_REGIONS,Ye as MIGRATION_SNAPSHOT_NAME_PREFIX,Te as OPT_IN_REGION_CODES,$e as PRISMA_MIGRATION_DIR_RE,I as REGION_SHORT_CODES,Ke as RESERVED_APP_NAMES,Xe as RESERVED_APP_NAME_MESSAGE,ne as RESOURCE_CATEGORIES,Qe as SCHEMA_ADMIN_PASSWORD_ENV,Je as SCHEMA_ADMIN_USER_ENV,Oe as SCOPE_VALUES,j as STRUCTURAL_ENVIRONMENTS,me as abbreviateRegion,N as accountConstructKey,ee as accountTier,he as buildAppConfigPath,R as capitalise,Ee as categoriseResource,c as defaultConnectedAccountName,Pe as deriveAllTargets,je as deriveContentHashTag,le as deriveRegionsFromOrgConfig,Me as deriveTargets,De as environmentOrTier,Q as environmentToTier,L as filterDangerousEnvVars,g as findAccountNameCollision,Le as findBoundaryPath,He as findInfrastructurePaths,Ve as findTarget,x as findTrailingRegionShortCode,U as formatErrorString,Ue as generateTargetName,t as getDomainExportNames,re as getEnvironmentLabel,D as getErrorCode,M as getErrorMessage,V as getErrorStack,ae as getExpectedDuration,ie as getFriendlyResourceType,Ce as getRegionInfo,C as getSafeZoneName,T as hasAsciiStableConstructKey,P as hasErrorCode,i as imageTagParameterName,We as inferContainerFromCandidates,Z as isAccountStage,J as isAccountTier,be as isInfrastructureFile,ke as isReservedAppName,X as mapSettledWithConcurrency,b as maskSensitiveOutput,l as normaliseError,fe as optInRegionWarning,_e as parseGitRemoteUrl,F as parseShellArgs,u as regionSuffixRejectionMessage,ge as regions,h as singleton,y as sleep,$ as stageFromWireEnvironment,O as suffixedAccountName,pe as suggestRegionForTimezone,A as toKebab,_ as toPascalCase,s as toScreamingSnake,m as toValidDatabaseName};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connected-account name generation and validation.
|
|
3
|
+
*
|
|
4
|
+
* A connected AWS account's display name flows into deployment target names
|
|
5
|
+
* (`generateTargetName`) AND into CFN / profile identifiers, so the name must be
|
|
6
|
+
* a clean slug and must NOT already carry a region short-code suffix — otherwise
|
|
7
|
+
* the region is appended twice (`clearcalcs-prod-use2` → target `…-use2-use2`).
|
|
8
|
+
*
|
|
9
|
+
* Single source of truth for the default-name shape and the region-suffix guard,
|
|
10
|
+
* shared by the webapp connect / quick-create ingress and the CLI connect screen.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Default name for a newly-connected account: `<org>-<stage>` as a clean slug.
|
|
14
|
+
* `stage` must be the DECODED workload stage (e.g. "production"), never a raw
|
|
15
|
+
* wire `environment` value. An empty stage yields the org slug alone.
|
|
16
|
+
* e.g. ("Calcs.com", "production") → "calcs-com-production".
|
|
17
|
+
*/
|
|
18
|
+
export declare function defaultConnectedAccountName(orgName: string, stage: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Append a short disambiguating suffix to a base account name when the base
|
|
21
|
+
* collides with an existing account (the `<org>-<stage>` default is taken).
|
|
22
|
+
* e.g. ("calcs-com-production", "a3f9") → "calcs-com-production-a3f9".
|
|
23
|
+
*/
|
|
24
|
+
export declare function suffixedAccountName(base: string, shortId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Every region short code an account name could be mistakenly suffixed with,
|
|
27
|
+
* derived from the region metadata via `abbreviateRegion` (the same derivation
|
|
28
|
+
* `generateTargetName` appends). Deduplicated — distinct regions can abbreviate
|
|
29
|
+
* to the same code (e.g. ap-south-1 and ap-southeast-1 both → "aps1"), which is
|
|
30
|
+
* harmless for membership testing.
|
|
31
|
+
*/
|
|
32
|
+
export declare const REGION_SHORT_CODES: ReadonlySet<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Detect a trailing region short-code segment on an account name — the shape
|
|
35
|
+
* that doubles the region when `generateTargetName` appends its own. Returns the
|
|
36
|
+
* offending code (e.g. "use2") or undefined. Region-agnostic: any trailing
|
|
37
|
+
* known short code is flagged, not only the currently-selected region's, so a
|
|
38
|
+
* `foo-usw2` name picked into us-east-1 is still caught. A region code that
|
|
39
|
+
* appears anywhere but the final segment (`use2-team`) is intentionally allowed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function findTrailingRegionShortCode(name: string): string | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* User-facing rejection copy for a region-suffixed account name. Shared by the
|
|
44
|
+
* webapp and CLI ingress so the wording cannot drift.
|
|
45
|
+
*/
|
|
46
|
+
export declare function regionSuffixRejectionMessage(shortCode: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{abbreviateRegion as r,AWS_REGIONS_METADATA as u}from"../infra/regions.js";import{toKebab as c}from"./caseConversion.js";function i(e){return c(e).replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function g(e,t){const n=i(e),o=i(t);return n===""?o:o===""?n:`${n}-${o}`}function d(e,t){const n=i(t);return n===""?e:`${e}-${n}`}const a=new Set(u.map(e=>r(e.code)));function l(e){const t=e.toLowerCase().split(/[-\s_]+/).filter(o=>o.length>0),n=t[t.length-1];if(n!==void 0&&a.has(n))return n}function m(e){return`Account name must not end with a region code ("-${e}") \u2014 the region is added automatically when deploying. Remove the "-${e}" suffix.`}export{a as REGION_SHORT_CODES,g as defaultConnectedAccountName,l as findTrailingRegionShortCode,m as regionSuffixRejectionMessage,d as suffixedAccountName};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{stat as a}from"fs/promises";import{dirname as f,join as
|
|
1
|
+
import{stat as a}from"fs/promises";import{dirname as f,join as m,parse as c,resolve as o}from"path";import{ROOT_CONFIG_FILENAME as p}from"../config.js";const u=[".git",p];async function x(r){let t=o(r);const{root:n}=c(t);for(;;){for(const i of u){const s=m(t,i);if(await a(s).then(()=>!0,()=>!1))return t}if(t===n)return o(r);const e=f(t);if(e===t)return o(r);t=e}}export{x as findRepoRoot};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/util",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"description": "Common utility methods",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"author": "",
|
|
102
102
|
"license": "SEE LICENSE IN LICENSE",
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@types/node": "^
|
|
104
|
+
"@types/node": "^26.0.0",
|
|
105
105
|
"prettier": "^3.8.3",
|
|
106
106
|
"tsc-watch": "^7.2.0",
|
|
107
107
|
"typescript": "^6.0.3",
|
|
@@ -117,5 +117,5 @@
|
|
|
117
117
|
"engines": {
|
|
118
118
|
"node": ">=22.0.0"
|
|
119
119
|
},
|
|
120
|
-
"gitHead": "
|
|
120
|
+
"gitHead": "ec556d44a8a32fbdc1a2e1ca3c1f22224b0d27c2"
|
|
121
121
|
}
|