@fjall/util 3.6.0 → 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 +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +1 -1
- package/dist/fsScan.d.ts +2 -0
- package/dist/fsScan.js +1 -1
- package/dist/repo/findInfrastructurePaths.d.ts +1 -0
- package/dist/repo/findInfrastructurePaths.js +1 -1
- package/dist/repo/findProjectsBelow.d.ts +44 -0
- package/dist/repo/findProjectsBelow.js +1 -0
- package/dist/repo/scanLocalRepository.d.ts +1 -0
- package/dist/repo/scanLocalRepository.js +1 -1
- package/package.json +2 -2
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
89 files minified at 2026-07-19T23:39:36.137Z
|
package/dist/config.d.ts
CHANGED
|
@@ -246,6 +246,12 @@ export declare class Config {
|
|
|
246
246
|
private mergeWithDisk;
|
|
247
247
|
private static readDiskConfigForMerge;
|
|
248
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;
|
|
249
255
|
getActiveTarget(): string | undefined;
|
|
250
256
|
setActiveTarget(name: string): void;
|
|
251
257
|
clearActiveTarget(): void;
|
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",
|
|
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};
|
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
|
|
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};
|
|
@@ -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,
|
|
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};
|
|
@@ -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
|
|
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.6.
|
|
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": "
|
|
137
|
+
"gitHead": "4bf3b77f9ef1472ca7afc6da552a225062ad602c"
|
|
138
138
|
}
|