@fjall/util 2.19.6 → 2.20.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
- 65 files minified at 2026-06-22T23:48:53.644Z
1
+ 66 files minified at 2026-06-23T21:58:47.448Z
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 y}from"./docker/result.js";import{getErrorMessage as m}from"./errorUtils.js";import{logger as p}from"./logger.js";import{maskSensitiveOutput as g}from"./securityHelpers.js";const T=10,$=["compliance","governance","none"],F=["enforced","off"],R=["centralised","off"],K=["account","draining","org"],b="managementEvents",k="organisationManagementEvents",M=["active","draining","removed"],L="FjallTrailBucketName",N="FjallTrailKeyArn",I="OrganisationTrailBucketName",w=s.object({name:s.string(),type:s.enum(["apex","delegated"]),parentDomain:s.string().optional(),account:s.string().optional()}).strict(),C=s.object({activeTarget:s.string().optional(),domains:s.array(w).optional()}).strict(),E=s.object({activeTarget:s.string().optional(),domains:s.array(w).optional()});function S(h={}){return JSON.stringify(h,null,2)}const v=C.keyof().options;function j(h,e,t){const n=e[t];n!==void 0&&(h[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<T;n++){const r=f.join(t,"fjall"),c=f.join(r,"fjall-config.json");if(i.existsSync(c))return r;const o=f.join(t,"fjall-config.json");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 p.warn("Config",`Config file at ${e} could not be read; using defaults`,{file:e,error:g(m(t))}),null}}static loadConfig(e){const t=a.findConfigDirectory(e);if(!t)return new a;const n=f.join(t,"fjall-config.json"),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(u){throw a.formatZodError(u,"fjall-config.json")}const d=C.safeParse(o);if(d.success)c=d.data;else{const u=E.safeParse(o);c=u.success?u.data:{},p.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,"fjall-config.json")}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(m(o))}`))}const n=a.assertWritable(e,t);if(!n.success)return n;const r=S(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(m(o))}`))}return y(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 y(void 0)}mergeWithDisk(e){const t=a.readDiskConfigForMerge(e);if(t===void 0)return this.rootConfig;const n={...t};for(const r of v)j(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){p.warn("Config",`Could not re-read ${e} before saving; writing in-memory state without merging`,{file:e,error:g(m(r))});return}const n=C.safeParse(t);if(!n.success){p.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{b as ACCOUNT_TRAIL_NAME,M as ACCOUNT_TRAIL_STATES,a as Config,k as ORGANISATION_TRAIL_NAME,I as ORG_TRAIL_BUCKET_OUTPUT_KEY,R as ROOT_ACCESS_MANAGEMENT_MODES,C as RootConfigSchema,F as S3_BPA_MODES,L as TRAIL_BUCKET_OUTPUT_KEY,N as TRAIL_KEY_ARN_OUTPUT_KEY,K as TRAIL_LIFECYCLE_STATES,$ as VAULT_LOCK_MODES,S as serialiseRootConfig};
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};
@@ -1 +1 @@
1
- import{rm as C}from"node:fs/promises";import{buildxArgvBuilder as D}from"./buildxArgvBuilder.js";import{BuildxBuildArgsSchema as k}from"./dockerCliSchemas.js";import{DEFAULT_BUILD_TIMEOUT_MS as $,DEFAULT_INSPECT_TIMEOUT_MS as w,DOCKER_CLI_BUILDX_LOG_CATEGORY as T}from"./dockerCliConstants.js";import{parseMetadataFile as B}from"./metadataFileParser.js";import{parseRawjsonLine as M}from"./rawjsonParser.js";import{rawjsonToVertexEvent as S}from"./rawjsonToVertexEvent.js";import{maskSensitiveOutput as x}from"../securityHelpers.js";import{failure as i,makeError as s,maskOutput as b,runDocker as _,spawnFailureToError as F,streamDocker as I,success as v}from"./DockerCli.js";function h(o){return o instanceof Error?o.message:String(o)}async function H(o,u,t){const r=k.safeParse(u);if(!r.success){const a=x(r.error.message);return i(s("validation",`Invalid BuildxBuildArgs: ${a}`,{issues:r.error.issues}))}const l=D(r.data);let n=0,g=0;const d=new Set,f=a=>{const m=M(a);if(m===null){a.trim()!==""&&o.logger.debug(T,"Skipping malformed rawjson line",{line:x(a)});return}const c=S(m);if(c!==null){for(const e of c.vertexes)e.completed!==void 0&&!d.has(e.digest)&&(d.add(e.digest),e.cached===!0?n++:g++),t({type:"vertex",message:b(e.name),vertex:e.digest});for(const e of c.logs)t({type:"log",message:b(e.data),vertex:e.vertex});for(const e of c.statuses)t({type:"status",message:b(`${e.name} ${e.current}`),vertex:e.vertex});for(const e of c.warnings)t({type:"warning",message:b(e.short),vertex:e.vertex})}};let p;try{p=r.data.metadataFile;const a=await I(o,{args:l,timeoutMs:$},f);if(a.spawnError!==void 0)return i(s("daemon_unreachable",`Docker CLI is not available: ${a.spawnError}`,{stderrTail:a.stderrTail}));if(a.aborted||a.exitCode===null)return i(s("abort","docker buildx build was aborted",{stderrTail:a.stderrTail}));if(a.exitCode!==0)return i(s("build_failed",`docker buildx build failed (exit ${a.exitCode})`,{stderrTail:a.stderrTail}));const m=await B(r.data.metadataFile);if(!m.success)return m;const c=m.data,e=c["containerimage.digest"];if(typeof e!="string"||e==="")return i(s("metadata_missing_digest","Buildx metadata file does not contain containerimage.digest",{metadataFile:r.data.metadataFile}));const y={};for(const E of r.data.tags)y[E]=e;return v({imageDigests:y,metadata:c,platforms:r.data.platforms,cacheHits:n,cacheMisses:g})}finally{p!==void 0&&await C(p,{force:!0}).catch(a=>{o.logger.warn(T,"Failed to clean up buildx metadata file",{path:p,error:x(h(a))})})}}async function K(o,u,t,r){if(u.trim()==="")return i(s("validation","tagByDigest: sourceImage must be non-empty"));if(!t.startsWith("sha256:"))return i(s("validation",`tagByDigest: expected sha256:... digest, got ${t.slice(0,32)}`));if(r.length===0)return i(s("validation","tagByDigest: tags must be a non-empty list"));for(const f of r)if(typeof f!="string"||f.trim()==="")return i(s("validation","tagByDigest: every tag must be non-empty"));const l=`${u}@${t}`,n=[];for(const f of r)n.push("--tag",f);const g=["buildx","imagetools","create",...n,l],d=await _(o,{args:g,timeoutMs:w});return d.spawnError!==void 0?i(F(d)):d.exitCode===null?i(s("abort",`docker buildx imagetools create for ${l} was aborted`,{stderrTail:d.stderrTail})):d.exitCode!==0?i(s("tag_failed",`docker buildx imagetools create for ${l} failed (exit ${d.exitCode})`,{stderrTail:d.stderrTail})):v(void 0)}async function V(o,u){const t=await _(o,{args:["buildx","imagetools","inspect",u,"--raw"],timeoutMs:w});if(t.spawnError!==void 0)return i(s("daemon_unreachable",`Docker CLI is not available: ${t.spawnError}`,{stderrTail:t.stderrTail}));if(t.exitCode===null)return i(s("abort",`docker buildx imagetools inspect ${u} was aborted`,{stderrTail:t.stderrTail}));if(t.exitCode!==0)return i(s("inspect_failed",`docker buildx imagetools inspect ${u} failed (exit ${t.exitCode})`,{stderrTail:t.stderrTail}));let r,l;try{const n=JSON.parse(t.stdout);typeof n.mediaType=="string"&&(r=n.mediaType),typeof n.digest=="string"&&(l=n.digest)}catch(n){o.logger.debug(T,"imagetools inspect output is not JSON; preserving raw",{error:x(h(n))})}return v({raw:t.stdout,...r!==void 0&&{mediaType:r},...l!==void 0&&{digest:l}})}export{H as _buildxBuild,V as _imagetoolsInspect,K as _tagByDigest};
1
+ import{rm as C}from"node:fs/promises";import{buildxArgvBuilder as D}from"./buildxArgvBuilder.js";import{BuildxBuildArgsSchema as k}from"./dockerCliSchemas.js";import{DEFAULT_BUILD_TIMEOUT_MS as $,DEFAULT_INSPECT_TIMEOUT_MS as w,DOCKER_CLI_BUILDX_LOG_CATEGORY as T}from"./dockerCliConstants.js";import{parseMetadataFile as B}from"./metadataFileParser.js";import{parseRawjsonLine as M}from"./rawjsonParser.js";import{rawjsonToVertexEvent as S}from"./rawjsonToVertexEvent.js";import{maskSensitiveOutput as x}from"../securityHelpers.js";import{failure as i,makeError as s,maskOutput as b,runDocker as _,spawnFailureToError as F,streamDocker as I,success as v}from"./DockerCli.js";function h(o){return o instanceof Error?o.message:String(o)}async function H(o,u,t){const r=k.safeParse(u);if(!r.success){const a=x(r.error.message);return i(s("validation",`Invalid BuildxBuildArgs: ${a}`,{issues:r.error.issues}))}const l=D(r.data);let n=0,g=0;const d=new Set,f=a=>{const m=M(a);if(m===null){a.trim()!==""&&o.logger.debug(T,"Skipping malformed rawjson line",{line:x(a)});return}const c=S(m);if(c!==null){for(const e of c.vertexes)e.completed!==void 0&&!d.has(e.digest)&&(d.add(e.digest),e.cached===!0?n++:g++),t({type:"vertex",message:b(e.name),vertex:e.digest});for(const e of c.logs)t({type:"log",message:b(e.data),vertex:e.vertex});for(const e of c.statuses)t({type:"status",message:b(`${e.name} ${e.current}`),vertex:e.vertex});for(const e of c.warnings)t({type:"warning",message:b(e.short),vertex:e.vertex})}};let p;try{p=r.data.metadataFile;const a=await I(o,{args:l,timeoutMs:$},f);if(a.spawnError!==void 0)return i(s("daemon_unreachable",`Docker CLI is not available: ${a.spawnError}`,{stderrTail:a.stderrTail}));if(a.aborted||a.exitCode===null)return i(s("abort","docker buildx build was aborted",{stderrTail:a.stderrTail}));if(a.exitCode!==0)return i(s("build_failed",`docker buildx build failed (exit ${a.exitCode})`,{stderrTail:a.stderrTail}));const m=await B(r.data.metadataFile);if(!m.success)return m;const c=m.data,e=c["containerimage.digest"];if(typeof e!="string"||e==="")return i(s("metadata_missing_digest","Buildx metadata file does not contain containerimage.digest",{metadataFile:r.data.metadataFile}));const y={};for(const E of r.data.tags)y[E]=e;return v({digest:e,imageDigests:y,metadata:c,platforms:r.data.platforms,cacheHits:n,cacheMisses:g})}finally{p!==void 0&&await C(p,{force:!0}).catch(a=>{o.logger.warn(T,"Failed to clean up buildx metadata file",{path:p,error:x(h(a))})})}}async function K(o,u,t,r){if(u.trim()==="")return i(s("validation","tagByDigest: sourceImage must be non-empty"));if(!t.startsWith("sha256:"))return i(s("validation",`tagByDigest: expected sha256:... digest, got ${t.slice(0,32)}`));if(r.length===0)return i(s("validation","tagByDigest: tags must be a non-empty list"));for(const f of r)if(typeof f!="string"||f.trim()==="")return i(s("validation","tagByDigest: every tag must be non-empty"));const l=`${u}@${t}`,n=[];for(const f of r)n.push("--tag",f);const g=["buildx","imagetools","create",...n,l],d=await _(o,{args:g,timeoutMs:w});return d.spawnError!==void 0?i(F(d)):d.exitCode===null?i(s("abort",`docker buildx imagetools create for ${l} was aborted`,{stderrTail:d.stderrTail})):d.exitCode!==0?i(s("tag_failed",`docker buildx imagetools create for ${l} failed (exit ${d.exitCode})`,{stderrTail:d.stderrTail})):v(void 0)}async function V(o,u){const t=await _(o,{args:["buildx","imagetools","inspect",u,"--raw"],timeoutMs:w});if(t.spawnError!==void 0)return i(s("daemon_unreachable",`Docker CLI is not available: ${t.spawnError}`,{stderrTail:t.stderrTail}));if(t.exitCode===null)return i(s("abort",`docker buildx imagetools inspect ${u} was aborted`,{stderrTail:t.stderrTail}));if(t.exitCode!==0)return i(s("inspect_failed",`docker buildx imagetools inspect ${u} failed (exit ${t.exitCode})`,{stderrTail:t.stderrTail}));let r,l;try{const n=JSON.parse(t.stdout);typeof n.mediaType=="string"&&(r=n.mediaType),typeof n.digest=="string"&&(l=n.digest)}catch(n){o.logger.debug(T,"imagetools inspect output is not JSON; preserving raw",{error:x(h(n))})}return v({raw:t.stdout,...r!==void 0&&{mediaType:r},...l!==void 0&&{digest:l}})}export{H as _buildxBuild,V as _imagetoolsInspect,K as _tagByDigest};
@@ -1 +1 @@
1
- import{DEFAULT_BUILDER_NAME as f}from"./dockerCliConstants.js";function i(e){const o=["buildx","build"];o.push("--builder",e.builder??f),o.push("--progress=rawjson"),o.push("--metadata-file",e.metadataFile),o.push("--platform",e.platforms.join(","));for(const t of e.tags)o.push("-t",t);o.push("-f",e.dockerfilePath);for(const[t,u]of Object.entries(e.buildArgs))o.push("--build-arg",`${t}=${u}`);if(e.target!==void 0&&o.push("--target",e.target),e.cacheFrom!==void 0)for(const t of e.cacheFrom)o.push("--cache-from",t);if(e.cacheTo!==void 0)for(const t of e.cacheTo)o.push("--cache-to",t);if(e.secrets!==void 0)for(const t of e.secrets)o.push("--secret",`id=${t.id},src=${t.source}`);return o.push(`--provenance=${e.provenance?"true":"false"}`),o.push(`--sbom=${e.sbom?"true":"false"}`),e.push&&o.push("--push"),e.load&&o.push("--load"),o.push(e.contextPath),o}export{i as buildxArgvBuilder};
1
+ import{DEFAULT_BUILDER_NAME as i}from"./dockerCliConstants.js";function p(e){const t=["buildx","build"];t.push("--builder",e.builder??i),t.push("--progress=rawjson"),t.push("--metadata-file",e.metadataFile),t.push("--platform",e.platforms.join(","));for(const u of e.tags)t.push("-t",u);t.push("-f",e.dockerfilePath),e.pushByDigest===!0&&e.imageName!==void 0&&t.push("--output",`type=image,name=${e.imageName},push-by-digest=true,push=true`);for(const[u,o]of Object.entries(e.buildArgs))t.push("--build-arg",`${u}=${o}`);if(e.target!==void 0&&t.push("--target",e.target),e.cacheFrom!==void 0)for(const u of e.cacheFrom)t.push("--cache-from",u);if(e.cacheTo!==void 0)for(const u of e.cacheTo)t.push("--cache-to",u);if(e.secrets!==void 0)for(const u of e.secrets)t.push("--secret",`id=${u.id},src=${u.source}`);return t.push(`--provenance=${e.provenance?"true":"false"}`),t.push(`--sbom=${e.sbom?"true":"false"}`),e.pushByDigest!==!0&&(e.push&&t.push("--push"),e.load&&t.push("--load")),t.push(e.contextPath),t}export{p as buildxArgvBuilder};
@@ -52,11 +52,14 @@ export declare const BuildxBuildArgsSchema: z.ZodObject<{
52
52
  sbom: z.ZodBoolean;
53
53
  push: z.ZodBoolean;
54
54
  load: z.ZodBoolean;
55
+ pushByDigest: z.ZodOptional<z.ZodBoolean>;
56
+ imageName: z.ZodOptional<z.ZodString>;
55
57
  builder: z.ZodOptional<z.ZodString>;
56
58
  metadataFile: z.ZodString;
57
59
  }, z.core.$strict>;
58
60
  export type BuildxBuildArgs = z.infer<typeof BuildxBuildArgsSchema>;
59
61
  export declare const BuildxBuildResultSchema: z.ZodObject<{
62
+ digest: z.ZodString;
60
63
  imageDigests: z.ZodRecord<z.ZodString, z.ZodString>;
61
64
  metadata: z.ZodRecord<z.ZodString, z.ZodUnknown>;
62
65
  platforms: z.ZodArray<z.ZodString>;
@@ -1 +1 @@
1
- import{z as t}from"zod";const a=t.enum(["buildx_unavailable","daemon_unreachable","build_failed","push_failed","pull_failed","tag_failed","inspect_failed","auth_failed","abort","timeout","validation","metadata_missing","metadata_malformed","metadata_missing_digest"]),e=new Set(a.options);function r(i){return e.has(i)}const o=t.object({contextPath:t.string().min(1),dockerfilePath:t.string().min(1),target:t.string().min(1).optional(),platforms:t.array(t.string().min(1)).min(1),tags:t.array(t.string().min(1)).min(1),buildArgs:t.record(t.string(),t.string()),cacheFrom:t.array(t.string().min(1)).optional(),cacheTo:t.array(t.string().min(1)).optional(),secrets:t.array(t.object({id:t.string().min(1),source:t.string().min(1)}).strict()).optional(),provenance:t.boolean(),sbom:t.boolean(),push:t.boolean(),load:t.boolean(),builder:t.string().min(1,"Builder name cannot be empty").optional(),metadataFile:t.string().min(1)}).strict().refine(i=>!(i.platforms.length>1&&i.load),{message:"Multi-arch builds cannot use load:true (Docker limitation); use push:true or omit both for cache-only"}).refine(i=>!(i.push&&i.load),{message:"push and load are mutually exclusive"}),s=t.object({imageDigests:t.record(t.string(),t.string()),metadata:t.record(t.string(),t.unknown()),platforms:t.array(t.string()),cacheHits:t.number().int().nonnegative(),cacheMisses:t.number().int().nonnegative()}).strict(),l=t.object({kind:a,message:t.string(),stderrTail:t.array(t.string()).optional(),details:t.unknown().optional()}).strict();export{o as BuildxBuildArgsSchema,s as BuildxBuildResultSchema,a as DockerCliErrorKindSchema,l as DockerCliErrorSchema,r as isDockerCliErrorKind};
1
+ import{z as e}from"zod";const i=e.enum(["buildx_unavailable","daemon_unreachable","build_failed","push_failed","pull_failed","tag_failed","inspect_failed","auth_failed","abort","timeout","validation","metadata_missing","metadata_malformed","metadata_missing_digest"]),a=new Set(i.options);function r(t){return a.has(t)}const s=e.object({contextPath:e.string().min(1),dockerfilePath:e.string().min(1),target:e.string().min(1).optional(),platforms:e.array(e.string().min(1)).min(1),tags:e.array(e.string().min(1)),buildArgs:e.record(e.string(),e.string()),cacheFrom:e.array(e.string().min(1)).optional(),cacheTo:e.array(e.string().min(1)).optional(),secrets:e.array(e.object({id:e.string().min(1),source:e.string().min(1)}).strict()).optional(),provenance:e.boolean(),sbom:e.boolean(),push:e.boolean(),load:e.boolean(),pushByDigest:e.boolean().optional(),imageName:e.string().min(1).optional(),builder:e.string().min(1,"Builder name cannot be empty").optional(),metadataFile:e.string().min(1)}).strict().refine(t=>!(t.platforms.length>1&&t.load),{message:"Multi-arch builds cannot use load:true (Docker limitation); use push:true or omit both for cache-only"}).refine(t=>!(t.push&&t.load),{message:"push and load are mutually exclusive"}).refine(t=>t.pushByDigest!==!0||t.imageName!==void 0,{message:"pushByDigest requires imageName (the registry repo to push to)"}).refine(t=>t.pushByDigest===!0||t.tags.length>=1,{message:"tags must be non-empty unless pushByDigest is set"}),o=e.object({digest:e.string(),imageDigests:e.record(e.string(),e.string()),metadata:e.record(e.string(),e.unknown()),platforms:e.array(e.string()),cacheHits:e.number().int().nonnegative(),cacheMisses:e.number().int().nonnegative()}).strict(),l=e.object({kind:i,message:e.string(),stderrTail:e.array(e.string()).optional(),details:e.unknown().optional()}).strict();export{s as BuildxBuildArgsSchema,o as BuildxBuildResultSchema,i as DockerCliErrorKindSchema,l as DockerCliErrorSchema,r as isDockerCliErrorKind};
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 n}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 p}from"./naming/accountNameCollision.js";import{normaliseError as O,getErrorMessage as c,hasErrorCode as I,getErrorCode as x,getErrorStack as u,formatErrorString as P}from"./errorUtils.js";import{singleton as M}from"./async/singleton.js";import{DANGEROUS_ENV_VARS as D,filterDangerousEnvVars as V,maskSensitiveOutput as U,parseShellArgs as G}from"./securityHelpers.js";import{sleep as h}from"./async/sleep.js";import{mapSettledWithConcurrency as H}from"./async/concurrency.js";import{ACCOUNT_STAGES_WITH_ROOT as F,STRUCTURAL_ENVIRONMENTS as W,ACCOUNT_STAGES as y,ACCOUNT_STAGE_LABELS as K,isAccountStage as X,ACCOUNT_TIERS as k,AccountTierSchema as B,isAccountTier as z,environmentToTier as Y,stageFromWireEnvironment as Z,accountTier as j,getEnvironmentLabel as q,ACCOUNT_ROLES as w}from"./environments.js";import{RESOURCE_CATEGORIES as Q,categoriseResource as $,getExpectedDuration as ee,getFriendlyResourceType as re}from"./resourceCategorisation.js";import{parseGitRemoteUrl as te}from"./repo/gitRemoteParser.js";import{abbreviateRegion as ne,AWS_REGIONS_METADATA as ae,DEFAULT_REGION as ie,getRegionInfo as Se,MAX_SECONDARY_REGIONS as _e,OPT_IN_REGION_CODES as Ae,optInRegionWarning as me,regions as se,suggestRegionForTimezone as Re}from"./infra/regions.js";import{SCOPE_VALUES as Ne}from"./infra/tokenScopes.js";import{ConnectionWireSchema as ge,ConnectionsListResponseSchema as pe}from"./infra/connectionsWire.js";import{deriveRegionsFromOrgConfig as Oe,deriveTargets as ce,deriveAllTargets as Ie,environmentOrTier as xe,findTarget as ue,generateTargetName as Pe}from"./targets.js";import{buildAppConfigPath as Me}from"./repo/appPath.js";import{findInfrastructurePaths as De,findBoundaryPath as Ve,isInfrastructureFile as Ue}from"./repo/findInfrastructurePaths.js";import{inferContainerFromCandidates as ve}from"./repo/inferContainerFromCandidates.js";import{RESERVED_APP_NAMES as Le,RESERVED_APP_NAME_MESSAGE as He,isReservedAppName as be}from"./naming/reservedAppNames.js";import{deriveContentHashTag as We}from"./infra/deriveContentHashTag.js";import{MIGRATION_SNAPSHOT_NAME_PREFIX as Ke,EXPECTED_SCHEMA_VERSION_ENV as Xe,EXPECTED_SCHEMA_VERSION_TOOL_ENV as ke,EXPECTED_CH_SCHEMA_VERSION_ENV as Be,SCHEMA_ADMIN_USER_ENV as ze,SCHEMA_ADMIN_PASSWORD_ENV as Ye,PRISMA_MIGRATION_DIR_RE as Ze,CLICKHOUSE_MIGRATION_SKIP_RE as je}from"./migration/constants.js";export{w as ACCOUNT_ROLES,y as ACCOUNT_STAGES,F as ACCOUNT_STAGES_WITH_ROOT,K as ACCOUNT_STAGE_LABELS,k as ACCOUNT_TIERS,ae as AWS_REGIONS_METADATA,B as AccountTierSchema,n as BACKUP_VAULT_NAME,je as CLICKHOUSE_MIGRATION_SKIP_RE,ge as ConnectionWireSchema,pe as ConnectionsListResponseSchema,D as DANGEROUS_ENV_VARS,ie as DEFAULT_REGION,o as DNS_APEX,Be as EXPECTED_CH_SCHEMA_VERSION_ENV,Xe as EXPECTED_SCHEMA_VERSION_ENV,ke as EXPECTED_SCHEMA_VERSION_TOOL_ENV,_e as MAX_SECONDARY_REGIONS,Ke as MIGRATION_SNAPSHOT_NAME_PREFIX,Ae as OPT_IN_REGION_CODES,Ze as PRISMA_MIGRATION_DIR_RE,Le as RESERVED_APP_NAMES,He as RESERVED_APP_NAME_MESSAGE,Q as RESOURCE_CATEGORIES,Ye as SCHEMA_ADMIN_PASSWORD_ENV,ze as SCHEMA_ADMIN_USER_ENV,Ne as SCOPE_VALUES,W as STRUCTURAL_ENVIRONMENTS,ne as abbreviateRegion,N as accountConstructKey,j as accountTier,Me as buildAppConfigPath,R as capitalise,$ as categoriseResource,Ie as deriveAllTargets,We as deriveContentHashTag,Oe as deriveRegionsFromOrgConfig,ce as deriveTargets,xe as environmentOrTier,Y as environmentToTier,V as filterDangerousEnvVars,p as findAccountNameCollision,Ve as findBoundaryPath,De as findInfrastructurePaths,ue as findTarget,P as formatErrorString,Pe as generateTargetName,t as getDomainExportNames,q as getEnvironmentLabel,x as getErrorCode,c as getErrorMessage,u as getErrorStack,ee as getExpectedDuration,re as getFriendlyResourceType,Se as getRegionInfo,C as getSafeZoneName,T as hasAsciiStableConstructKey,I as hasErrorCode,i as imageTagParameterName,ve as inferContainerFromCandidates,X as isAccountStage,z as isAccountTier,Ue as isInfrastructureFile,be as isReservedAppName,H as mapSettledWithConcurrency,U as maskSensitiveOutput,O as normaliseError,me as optInRegionWarning,te as parseGitRemoteUrl,G as parseShellArgs,se as regions,M as singleton,h as sleep,Z as stageFromWireEnvironment,Re as suggestRegionForTimezone,A as toKebab,_ as toPascalCase,s as toScreamingSnake,m as toValidDatabaseName};
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 c,parse as m,resolve as o}from"path";const u=[".git","fjall-config.json"];async function h(r){let t=o(r);const{root:n}=m(t);for(;;){for(const s of u){const i=c(t,s);if(await a(i).then(()=>!0,()=>!1))return t}if(t===n)return o(r);const e=f(t);if(e===t)return o(r);t=e}}export{h as findRepoRoot};
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.19.6",
3
+ "version": "2.20.1",
4
4
  "description": "Common utility methods",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -117,5 +117,5 @@
117
117
  "engines": {
118
118
  "node": ">=22.0.0"
119
119
  },
120
- "gitHead": "f41aae2245b7e82af7e8e7f5cf098b83b22cc198"
120
+ "gitHead": "397880361ea533450ea5c888f7e7b9593254f663"
121
121
  }