@maverickcer/env-cap 0.2.0 → 0.3.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/build.cjs +1 -1
- package/dist/build.js +1 -1
- package/dist/cli/index.js +1 -1
- package/package.json +4 -4
- package/dist/.dts/build/manifest-snapshot.d.ts +0 -229
- package/dist/.dts/build/manifest-snapshot.d.ts.map +0 -1
- package/dist/.dts/build/resolve-import.d.ts +0 -50
- package/dist/.dts/build/resolve-import.d.ts.map +0 -1
- package/dist/.dts/build/resolve-package-schema.d.ts +0 -99
- package/dist/.dts/build/resolve-package-schema.d.ts.map +0 -1
- package/dist/.dts/build/resolve-tsconfig-paths.d.ts +0 -100
- package/dist/.dts/build/resolve-tsconfig-paths.d.ts.map +0 -1
- package/dist/.dts/build/resolve-within-root.d.ts +0 -35
- package/dist/.dts/build/resolve-within-root.d.ts.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ ${"-".repeat(Math.max(issue.variable.length,3))}
|
|
|
7
7
|
Declared in:
|
|
8
8
|
${issue.files.join("\n")}`);return[header,...blocks].join("\n\n")}function countedHeader(prefix,singularNoun,issues){const noun=issues.length===1?singularNoun:`${singularNoun}s`;return`${prefix}
|
|
9
9
|
|
|
10
|
-
${issues.length} ${noun} found:`}var EnvProjectGenerationError=class _EnvProjectGenerationError extends Error{constructor(issues){super(formatIssues(countedHeader("Project generation failed.","blocking issue",issues),issues));this.code="ENV_PROJECT_GENERATION_FAILED";this.name="EnvProjectGenerationError";this.issues=issues;Error.captureStackTrace?.(this,_EnvProjectGenerationError)}};import crypto2 from"crypto";import path15 from"path";import path11 from"path";function detectExclusiveGroupIssues(contracts){const byGroup=new Map;for(const contract of contracts){if(!contract.active||!contract.exclusiveGroup)continue;const members=byGroup.get(contract.exclusiveGroup)??[];members.push(contract);byGroup.set(contract.exclusiveGroup,members)}const issues=[];for(const[group,members]of[...byGroup.entries()].sort((a,b)=>a[0].localeCompare(b[0]))){for(let i=0;i<members.length;i++){for(let j=i+1;j<members.length;j++){const a=members[i];const b=members[j];issues.push({severity:"error",variable:`Exclusive group "${group}"`,files:[a.file,b.file],reason:`"${a.contractName}" and "${b.contractName}" are both active and both declare exclusiveGroup "${group}" -- only one active contract per exclusive group is allowed. Set active: false on whichever one isn't in use.`})}}}return issues}import path10 from"path";function discoverValidationContexts(contracts){const contexts=new Set;for(const contract of contracts){if(!contract.active)continue;for(const variable of contract.variables){if(variable.context!==void 0)contexts.add(variable.context)}}return[...contexts].sort()}function renderManifest(contracts,outputFile){const sorted=[...contracts].sort((a,b)=>a.file.localeCompare(b.file));const outputDir=path10.dirname(outputFile);const usedNames=new Set;const importLines=[];const referenceNames=[];for(const contract of sorted){let localName=contract.exportName;let suffix=1;while(usedNames.has(localName)){localName=`${contract.exportName}_${suffix}`;suffix+=1}usedNames.add(localName);referenceNames.push(localName);let specifier;if(contract.packageOrigin){specifier=contract.packageOrigin.packageName}else{const withoutExt=contract.file.replace(/\.tsx?$/,"");specifier=path10.relative(outputDir,withoutExt).split(path10.sep).join("/");if(!specifier.startsWith("."))specifier=`./${specifier}`}const binding=localName===contract.exportName?contract.exportName:`${contract.exportName} as ${localName}`;importLines.push(`import { ${binding} } from "${specifier}";`)}const activeContexts=discoverValidationContexts(contracts);const activeContextsLines=activeContexts.length>0?["// all currently active validation contexts",`export const activeContexts = [${activeContexts.map(c=>JSON.stringify(c)).join(", ")}];`,""]:[];const lines=[generatedBanner("ts"),"",...importLines,"",...activeContextsLines,"export const manifest = [",...referenceNames.map(name=>` ${name},`),"];",""];return lines.join("\n")}function defaultInclude(){return["**/env.schema.ts"]}function defaultExclude(){return["**/node_modules/**","**/dist/**","**/.git/**"]}function computeManifest(root,linkResult,onIncompatibility){const{contracts}=linkResult;const activeContracts=contracts.filter(contract=>contract.active);const compatibilityIssues=[...detectCompatibilityIssues(activeContracts),...detectExclusiveGroupIssues(activeContracts),...detectDuplicateVariableShapes(activeContracts)];const compatibilityErrors=compatibilityIssues.filter(issue=>issue.severity==="error");const blocking=onIncompatibility==="throw"?compatibilityIssues.filter(issue=>issue.severity!=="info"):compatibilityErrors;return{activeContracts,contractSummaries:contracts.map(contract=>summarizeContract(contract,root)),warnings:compatibilityIssues.filter(issue=>issue.severity==="warning"),blocking}}async function writeManifest(outputPath,activeContracts,fs2){const manifestSource=renderManifest(activeContracts,outputPath);await fs2.mkdir(path11.dirname(outputPath),{recursive:true});await fs2.writeFile(outputPath,manifestSource,"utf8")}import path12 from"path";function sortedByContractName(entries){return[...entries].sort((a,b)=>a.contractName.localeCompare(b.contractName))}function renderDependencyOwnershipTable(entries){if(entries.length===0)return[];const lines=["## Dependency ownership","","Who owns each contract, who depends on it, and the blast radius if it changes.","","| Contract | Owner | Variables | Consumers | Blast radius |","|---|---|---|---|---|"];for(const entry of sortedByContractName(entries)){const consumers=entry.consumers.length>0?entry.consumers.join(", "):"--";lines.push(`| ${entry.contractName} (\`${entry.file}\`) | ${entry.owner??"--"} | ${entry.variableCount} | ${consumers} | ${entry.consumers.length} |`)}lines.push("");return lines}function renderAbandoned(findings){if(findings.length===0)return[];const lines=["## Abandoned ownership","","Contracts never imported anywhere in the scanned repository -- a feature's schema outliving the feature.","","| Contract | Owner | File |","|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| ${f.contractName} | ${f.owner??"--"} | \`${f.file}\` |`);lines.push("");return lines}function renderUnresolvedConsumers(findings){if(findings.length===0)return[];const lines=["## Unresolved consumers (barrel re-exports)","","Contracts reachable only through an unresolved `export * from` barrel re-export -- cannot be proven abandoned or consumed. Advisory only; never fails a build.",""];for(const f of sortedByContractName(findings))lines.push(`- **${f.contractName}** (\`${f.file}\`): ${f.reason}`);lines.push("");return lines}function formatStaleOrMissingCitations(citations){if(citations.length===0)return"--";return citations.map(c=>`${c.position.file}:${c.position.line}:${c.position.column} (${c.acknowledgment})`).join(", ")}function renderUnconsumedOwned(findings,scannedSurfaces){if(findings.length===0)return[];const surfaces=scannedSurfaces.map(s=>s.label).join(", ");const lines=["## Unconsumed owned dependencies","","Variables no consumer reads within the scanned surfaces below. Not proof of dead code. Common reasons a real consumer wouldn't show up here: it's read by a separate, out-of-repo service or webhook handler; it's consumed by non-TypeScript code (a shell script, a Dockerfile, a Terraform/Kubernetes manifest); or it's read from an allow-listed package whose directory wasn't included in this project's own `packages` configuration (ADR 0014).","",`Searched: ${surfaces}.`,"",'A blank Stale/missing citations cell is the strongest "looks genuinely unused" signal -- no developer has ever claimed otherwise. A non-blank cell means someone specifically claimed dynamic access here via `dynamicAccess`, and that claim can no longer be verified (ADR 0037) -- check with them before deleting.',"","| Variable | Contract | Owner | Stale/missing citations |","|---|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| \`${f.key}\` | ${f.contractName} | ${f.owner??"--"} | ${formatStaleOrMissingCitations(f.staleOrMissingCitations)} |`);lines.push("");return lines}function renderIndeterminate(findings){if(findings.length===0)return[];const lines=["## Indeterminate (dynamic access)","","Dynamic (computed) property access was observed -- usage cannot be determined statically. Never guessed at.","","| Variable | Contract | Reason | Stale/missing citations |","|---|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| \`${f.key}\` | ${f.contractName} | ${f.reason} | ${formatStaleOrMissingCitations(f.staleOrMissingCitations)} |`);lines.push("");return lines}function formatAssertions(assertions){const sites=assertions.map(a=>`${a.file}:${a.line}:${a.column}`).join(", ");return`Per developers, this data point is dynamically accessed at ${sites}.`}function renderAsserted(findings){if(findings.length===0)return[];const lines=["## Asserted (developer-acknowledged dynamic access)","","Variables env-cap's own scan would otherwise flag as unconsumed or indeterminate, but a developer has cited exactly where the dynamic access happens via `dynamicAccess`. The raw static status is shown alongside the citation, never hidden behind it -- a citation is a re-acknowledgment, not proof.","","| Variable | Contract | Static status | Developer assertion |","|---|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| \`${f.key}\` | ${f.contractName} | ${f.wouldBeStatus} | ${formatAssertions(f.dynamicAccessAssertions)} |`);lines.push("");return lines}function renderParseWarnings(warnings){if(warnings.length===0)return[];const lines=["## Parse warnings","","Includes any `packages` (ADR 0014) resolution failures, alongside ordinary schema-discovery warnings.",""];for(const w of warnings)lines.push(`- **${w.file}**: ${w.message}`);lines.push("");return lines}function renderUsageReport(computed){const lines=[generatedBanner("markdown"),"",`> ${evidenceDisclaimer()}`,"",`> ${evidenceProjectionNote()}`,"","# Dependency & Ownership Report","","_Produced by `env-cap --ownership`._","","Which feature owns each variable, which features consume that contract, and what the blast radius is if it changes.","",...renderDependencyOwnershipTable(computed.dependencyOwnership),...renderAbandoned(computed.abandonedContracts),...renderUnresolvedConsumers(computed.unresolvedConsumers),...renderUnconsumedOwned(computed.unconsumedOwnedVariables,computed.scannedSurfaces),...renderIndeterminate(computed.indeterminate),...renderAsserted(computed.asserted),...renderParseWarnings(computed.parseWarnings)];return lines.join("\n").replace(/\n{3,}/g,"\n\n")}async function computeScanSurface(root,exclude,origins,fs2){const localScanFiles=await discoverSchemaFiles({fs:fs2,root,include:["**/*.ts","**/*.tsx"],exclude});const scannedSurfaces=[{label:"application",root:"."}];const scanFiles=[...localScanFiles];const seenPackageDirs=new Set;for(const origin of origins.values()){if(seenPackageDirs.has(origin.packageDir))continue;seenPackageDirs.add(origin.packageDir);const packageScanFiles=await discoverSchemaFiles({fs:fs2,root:origin.packageDir,include:["**/*.ts","**/*.tsx"],exclude:defaultExclude()});scanFiles.push(...packageScanFiles);scannedSurfaces.push({label:`package:${origin.packageName}`,root:displayPath(root,origin.packageDir)})}return{scanFiles,scannedSurfaces}}async function computeUsage(root,contracts,scanFiles,readFile2,context,parseWarnings,scannedSurfaces,dynamicAccessAcknowledgments){const graph=await buildDependencyGraph(contracts,scanFiles,readFile2,context,scannedSurfaces,dynamicAccessAcknowledgments);const findings=deriveOwnershipFindings(graph);const contractByIdentity=new Map;for(const contract of contracts)contractByIdentity.set(`${contract.file}#${contract.exportName}`,contract);const ownerFor=(file,exportName)=>contractByIdentity.get(`${file}#${exportName}`)?.owner;const effectiveOwnerFor=(file,exportName,key)=>{const contract=contractByIdentity.get(`${file}#${exportName}`);if(!contract)return void 0;const variable=contract.variables.find(v=>v.key===key);return variable?effectiveOwner(contract,variable):contract.owner};const dependencyOwnership=graph.contracts.map(c=>({contractName:c.contractName,file:path12.relative(root,c.file),owner:ownerFor(c.file,c.exportName),variableCount:c.variables.size,consumers:c.consumingFiles.map(f=>path12.relative(root,f))}));const abandonedContracts=findings.abandoned.map(f=>({contractName:f.contractName,file:path12.relative(root,f.file),owner:ownerFor(f.file,f.exportName)}));const unresolvedConsumers=findings.unresolvedConsumers.map(f=>({contractName:f.contractName,file:path12.relative(root,f.file),reason:f.reason}));const staleOrMissingCitationsFor=(contractName,file,exportName,key)=>{const relativeFile=displayPath(root,file);const identity=dynamicAccessVariableIdentity(relativeFile,exportName,key);const problems=[];for(const a of dynamicAccessAcknowledgments?.get(identity)??[]){if(a.acknowledgment==="fresh")continue;problems.push({contractName,file:relativeFile,exportName,key,position:{file:a.file,line:a.line,column:a.column},acknowledgment:a.acknowledgment})}return problems};const unconsumedOwnedVariables=findings.unconsumedOwned.map(f=>({contractName:f.contractName,owner:effectiveOwnerFor(f.file,f.exportName,f.key),key:f.key,staleOrMissingCitations:staleOrMissingCitationsFor(f.contractName,f.file,f.exportName,f.key)}));const indeterminate=findings.indeterminate.map(f=>({contractName:f.contractName,key:f.key,reason:f.reason,dynamicAccessSites:f.dynamicAccessSites.map(p=>({...p,file:displayPath(root,p.file)})),staleOrMissingCitations:staleOrMissingCitationsFor(f.contractName,f.file,f.exportName,f.key)}));const asserted=findings.asserted.map(f=>({contractName:f.contractName,key:f.key,wouldBeStatus:f.wouldBeStatus,dynamicAccessAssertions:f.dynamicAccessAssertions}));return{graph,result:{dependencyOwnership,abandonedContracts,unresolvedConsumers,unconsumedOwnedVariables,indeterminate,asserted,parseWarnings,scannedSurfaces:graph.scannedSurfaces}}}async function writeUsageReport(reportPath,computed,fs2){const source=renderUsageReport(computed);await fs2.mkdir(path12.dirname(reportPath),{recursive:true});await fs2.writeFile(reportPath,source,"utf8")}var FINDING_MODEL_SCHEMA_VERSION=3;var NO_POSITION=void 0;function fromCompatibilityIssue(root,issue,code){return{severity:issue.severity,code,family:"compatibility",message:issue.reason,location:{model:"contract",file:displayPath(root,issue.files[0]),exportName:void 0,variable:issue.variable,position:NO_POSITION}}}function buildFindingModel(input){const{root}=input;const findings=[];for(const issue of input.compatibilityIssues??[]){findings.push(fromCompatibilityIssue(root,issue,issue.code??"DUPLICATE_VARIABLE_DOCUMENTATION"))}for(const issue of input.exclusiveGroupIssues??[]){findings.push(fromCompatibilityIssue(root,issue,"EXCLUSIVE_GROUP_VIOLATION"))}for(const check of input.artifactCheckFindings??[]){if(check.status==="ok")continue;findings.push({severity:"warning",code:check.status==="missing"?"ARTIFACT_MISSING":"ARTIFACT_STALE",family:"drift",message:check.detail??`${check.artifact} artifact is ${check.status}.`,location:{model:"change",path:check.path}})}const documentation=input.documentation;if(documentation){for(const c of documentation.undocumentedContracts){findings.push({severity:"warning",code:"UNDOCUMENTED_CONTRACT",family:"documentation",message:`"${c.exportName}" has no documentEnv() call linked to it.`,location:{model:"contract",file:displayPath(root,c.file),exportName:c.exportName,variable:void 0,position:NO_POSITION}})}for(const v of documentation.undocumentedVariables){findings.push({severity:"warning",code:"UNDOCUMENTED_VARIABLE",family:"documentation",message:`"${v.key}" (declared by "${v.exportName}") has no matching entry in a linked documentEnv()'s "variables".`,location:{model:"contract",file:displayPath(root,v.file),exportName:v.exportName,variable:v.key,position:NO_POSITION}})}for(const s of documentation.staleDocEntries){findings.push({severity:"warning",code:"STALE_DOC_ENTRY",family:"documentation",message:`"${s.key}" is documented under "${s.exportName}" but no longer exists in that contract's schema.`,location:{model:"contract",file:displayPath(root,s.file),exportName:s.exportName,variable:s.key,position:NO_POSITION}})}for(const e of documentation.expiringSoon){const expired=e.daysRemaining<0;findings.push({severity:"warning",code:expired?"EXPIRED":"EXPIRING_SOON",family:"documentation",message:expired?`"${e.key??e.exportName}" expired ${Math.abs(e.daysRemaining)} day(s) ago (expiresAt: ${e.expiresAt}).`:`"${e.key??e.exportName}" expires in ${e.daysRemaining} day(s) (expiresAt: ${e.expiresAt}).`,location:{model:"contract",file:displayPath(root,e.file),exportName:e.exportName,variable:e.key,position:NO_POSITION}})}for(const n of documentation.nonstandardSensitivityLevels){findings.push({severity:"info",code:"NONSTANDARD_SENSITIVITY_LEVEL",family:"documentation",message:`"${n.key??n.exportName}" declares sensitivity "${n.sensitivity}", which isn't one of the standard levels (secret/credential/pii/config) -- still honored verbatim, just flagged for vocabulary drift.`,location:{model:"contract",file:displayPath(root,n.file),exportName:n.exportName,variable:n.key,position:NO_POSITION}})}for(const u of documentation.unresolvedLinks){findings.push({severity:"warning",code:"UNRESOLVED_DOCUMENTENV_LINK",family:"documentation",message:u.reason,location:{model:"contract",file:displayPath(root,u.file),exportName:void 0,variable:void 0,position:NO_POSITION}})}}for(const a of input.abandonedContracts??[]){findings.push({severity:"warning",code:"ABANDONED_CONTRACT",family:"ownership",message:`"${a.contractName}" is never imported anywhere in the scanned repository.`,location:{model:"ownership",contractName:a.contractName,file:a.file,variable:void 0,position:NO_POSITION}})}for(const u of input.unresolvedConsumers??[]){findings.push({severity:"warning",code:"UNRESOLVED_CONSUMER",family:"ownership",message:u.reason,location:{model:"ownership",contractName:u.contractName,file:u.file,variable:void 0,position:NO_POSITION}})}for(const u of input.unconsumedOwnedVariables??[]){findings.push({severity:"warning",code:"UNCONSUMED_OWNED_VARIABLE",family:"ownership",message:`"${u.key}" (declared by "${u.contractName}") has no consumer found in the scanned repository.`,location:{model:"ownership",contractName:u.contractName,file:void 0,variable:u.key,position:NO_POSITION}})}for(const i of input.indeterminateOwnership??[]){findings.push({severity:"warning",code:"INDETERMINATE_OWNERSHIP",family:"ownership",message:i.reason,location:{model:"ownership",contractName:i.contractName,file:void 0,variable:i.key,position:NO_POSITION}})}for(const p of input.dynamicAccessCitationProblems??[]){const missing=p.acknowledgment==="missing";findings.push({severity:"warning",code:missing?"MISSING_DYNAMIC_ACCESS_CITATION":"STALE_DYNAMIC_ACCESS_CITATION",family:"ownership",message:missing?`"${p.key}" (declared by "${p.contractName}") cites dynamic access at ${p.position.file}:${p.position.line}:${p.position.column}, but that file no longer exists. Re-run generate:env once the citation is corrected.`:`"${p.key}" (declared by "${p.contractName}") cites dynamic access at ${p.position.file}:${p.position.line}:${p.position.column}, but that file's content has changed since it was last acknowledged. Re-run generate:env once you've re-confirmed the citation still applies.`,location:{model:"ownership",contractName:p.contractName,file:p.file,variable:p.key,position:p.position}})}return{schemaVersion:FINDING_MODEL_SCHEMA_VERSION,findings}}import path14 from"path";function humanizeKey(key){const spaced=key.replace(/([a-z0-9])([A-Z])/g,"$1 $2");return spaced.charAt(0).toUpperCase()+spaced.slice(1)}function renderMetadataValue(value){return typeof value==="string"?value:JSON.stringify(value)}function defineEvidenceProjection(schema){function project(evidence){const value={};const sources={};for(const key of Object.keys(schema)){const projector=schema[key];const{proxy,readPaths}=createTrackingProxy(evidence);value[key]=projector(proxy);sources[key]=readPaths()}return{value,sources}}function invoke(evidence){return project(evidence).value}return Object.assign(invoke,{project})}function readOnlyMembraneError(){return new TypeError("EvidenceModel is read-only inside a projector -- a projection must be a pure function of its evidence argument. See ADR 0032.")}function createTrackingProxy(evidence){const clone=structuredClone(evidence);const paths=new Set;const wrapped=new WeakMap;function wrap(value,path18){if(value===null||typeof value!=="object")return value;const target=value;const cached=wrapped.get(target);if(cached)return cached;const proxy=new Proxy(target,{get(currentTarget,prop,receiver){const result=Reflect.get(currentTarget,prop,receiver);if(typeof prop!=="string")return result;if(Array.isArray(currentTarget)&&prop==="length")return result;const nextPath=[...path18,prop];paths.add(nextPath.join("."));return wrap(result,nextPath)},set(){throw readOnlyMembraneError()},deleteProperty(){throw readOnlyMembraneError()},defineProperty(){throw readOnlyMembraneError()},setPrototypeOf(){throw readOnlyMembraneError()}});wrapped.set(target,proxy);return proxy}return{proxy:wrap(clone,[]),readPaths:()=>[...paths].sort()}}function byContractThenKey(a,b){return a.file.localeCompare(b.file)||a.exportName.localeCompare(b.exportName)||a.key.localeCompare(b.key)}function contractIndex(contracts){return new Map(contracts.map(c=>[`${c.file}#${c.exportName}`,c]))}function groupVariablesByOwner(contracts,ownerOf){const byOwner=new Map;for(const contract of contracts){for(const variable of contract.variables){const owner=ownerOf(contract,variable);if(owner===void 0)continue;const list=byOwner.get(owner)??[];list.push({contract,variable});byOwner.set(owner,list)}}return byOwner}var configurationReference=defineEvidenceProjection({disclaimer:()=>evidenceDisclaimer(),entries:evidence=>{const entries=[];for(const contract of evidence.contract.contracts){for(const variable of contract.variables){entries.push({file:contract.file,exportName:contract.exportName,contractName:contract.contractName,key:variable.key,description:variable.description,owner:variable.owner??contract.owner,sensitivity:variable.sensitivity??contract.sensitivity,required:variable.required,hasDefault:variable.hasDefault,hasProcessor:variable.hasProcessor,hasValidator:variable.hasValidator,expiresAt:variable.expiresAt,active:contract.active})}}return entries.sort(byContractThenKey)}});var ownershipSummary=defineEvidenceProjection({disclaimer:()=>evidenceDisclaimer(),owners:evidence=>{const contracts=contractIndex(evidence.contract.contracts);const nameOf=(file,exportName)=>contracts.get(`${file}#${exportName}`)?.contractName??exportName;const variablesByOwner=groupVariablesByOwner(evidence.ownership.contracts,(_contract,variable)=>variable.owner);const contractsByOwner=new Map;for(const contract of evidence.ownership.contracts){if(contract.owner===void 0)continue;const list=contractsByOwner.get(contract.owner)??[];list.push(nameOf(contract.file,contract.exportName));contractsByOwner.set(contract.owner,list)}const owners=new Set([...variablesByOwner.keys(),...contractsByOwner.keys()]);return[...owners].sort().map(owner=>({owner,variables:(variablesByOwner.get(owner)??[]).map(({contract,variable})=>`${nameOf(contract.file,contract.exportName)}.${variable.key}`).sort(),contracts:(contractsByOwner.get(owner)??[]).sort()}))},unowned:evidence=>{const contracts=contractIndex(evidence.contract.contracts);return evidence.ownership.unownedVariables.map(ref=>{const contractName=contracts.get(`${ref.file}#${ref.exportName}`)?.contractName??ref.exportName;return`${contractName}.${ref.key}`}).sort()}});var expiringSoonReport=defineEvidenceProjection({disclaimer:()=>evidenceDisclaimer(),entries:evidence=>{const contracts=contractIndex(evidence.contract.contracts);const ownerByVariable=new Map;for(const contract of evidence.ownership.contracts)for(const variable of contract.variables)ownerByVariable.set(`${contract.file}#${contract.exportName}#${variable.key}`,variable.owner);return evidence.lifecycle.expiring.map(entry=>{const contract=contracts.get(`${entry.file}#${entry.exportName}`);const variable=entry.key===void 0?void 0:contract?.variables.find(v=>v.key===entry.key);return{file:entry.file,exportName:entry.exportName,contractName:contract?.contractName??entry.exportName,key:entry.key,expiresAt:entry.expiresAt,daysRemaining:entry.daysRemaining,expired:entry.daysRemaining<0,refreshInstructions:variable?.refreshInstructions,owner:entry.key===void 0?contract?.owner:ownerByVariable.get(`${entry.file}#${entry.exportName}#${entry.key}`)??contract?.owner}})},expiredCount:evidence=>evidence.lifecycle.expiring.filter(e=>e.daysRemaining<0).length});function parseIsoDate(value){const date=new Date(value);return Number.isNaN(date.getTime())?void 0:date}var MS_PER_DAY=864e5;function daysRemainingFrom(date,now){return Math.ceil((date.getTime()-now.getTime())/MS_PER_DAY)}function computeExpiringEntries(contracts,expiringWithinDays,now){const entries=[];for(const contract of contracts){if(contract.expiresAt){const date=parseIsoDate(contract.expiresAt);if(date){const daysRemaining=daysRemainingFrom(date,now);if(daysRemaining<=expiringWithinDays){entries.push({file:contract.file,exportName:contract.exportName,key:void 0,expiresAt:contract.expiresAt,daysRemaining})}}}for(const variable of contract.variables){if(!variable.expiresAt)continue;const date=parseIsoDate(variable.expiresAt);if(!date)continue;const daysRemaining=daysRemainingFrom(date,now);if(daysRemaining<=expiringWithinDays){entries.push({file:contract.file,exportName:contract.exportName,key:variable.key,expiresAt:variable.expiresAt,daysRemaining})}}}return entries.sort((a,b)=>a.daysRemaining-b.daysRemaining)}function buildCatalog(contracts){return sortedContracts(contracts).map(contract=>{const variables={};for(const variable of contract.variables){variables[variable.key]={description:variable.description,owner:effectiveOwner(contract,variable),sensitivity:effectiveSensitivity(contract,variable),expiresAt:variable.expiresAt,refreshInstructions:variable.refreshInstructions,setupInstructions:variable.setupInstructions,required:variable.required,hasDefault:variable.hasDefault,hasProcessor:variable.hasProcessor,processorReturnType:variable.processorReturnType,hasValidator:variable.hasValidator,documented:variable.documented,context:variable.context,purpose:effectivePurpose(contract,variable),legalBasis:effectiveLegalBasis(contract,variable),retention:effectiveRetention(contract,variable),dataResidency:effectiveDataResidency(contract,variable),auditRequired:effectiveAuditRequired(contract,variable),metadata:variable.metadata,evidence:variable.evidence}}return{file:contract.file,exportName:contract.exportName,contractName:contract.contractName,active:contract.active,documented:contract.documented,category:contract.category,exclusiveGroup:contract.exclusiveGroup,...governanceFieldsOf(contract),variables}})}function extractPreviouslyDocumentedKeys(previousContent){const keys=new Set;for(const match of previousContent.matchAll(/^### `([A-Za-z_][A-Za-z0-9_]*)`$/gm)){if(match[1])keys.add(match[1])}return keys}function extractPreviouslyActiveKeys(previousContent){const keys=new Set;let currentContractActive=false;for(const line of previousContent.split("\n")){const activeMatch=/^- Active: (yes|no)$/.exec(line);if(activeMatch){currentContractActive=activeMatch[1]==="yes";continue}const headingMatch=/^### `([A-Za-z_][A-Za-z0-9_]*)`$/.exec(line);if(headingMatch&¤tContractActive){keys.add(headingMatch[1])}}return keys}function computeChangeSummary(contracts,previousContent){if(previousContent===void 0)return{added:[],removed:[],commented:[]};const previousKeys=extractPreviouslyDocumentedKeys(previousContent);const previousActiveKeys=extractPreviouslyActiveKeys(previousContent);const currentKeys=new Set;const currentActiveKeys=new Set;for(const contract of contracts){for(const variable of contract.variables){currentKeys.add(variable.key);if(contract.active)currentActiveKeys.add(variable.key)}}return{added:[...currentKeys].filter(k=>!previousKeys.has(k)).sort(),removed:[...previousKeys].filter(k=>!currentKeys.has(k)).sort(),commented:[...previousActiveKeys].filter(k=>currentKeys.has(k)&&!currentActiveKeys.has(k)).sort()}}function slugify(text){return text.toLowerCase().replace(/[^a-z0-9_]+/g,"-").replace(/^-+|-+$/g,"")||"section"}var AnchorRegistry=class{constructor(){this.used=new Map;this.assigned=new Map}anchorFor(target,baseText){const existing=this.assigned.get(target);if(existing)return existing;const base=slugify(baseText);const count=this.used.get(base)??0;this.used.set(base,count+1);const anchor=count===0?base:`${base}-${count}`;this.assigned.set(target,anchor);return anchor}};function mdLink(text,anchor){return`[${text}](#${anchor})`}function sortedContracts(contracts){return[...contracts].sort((a,b)=>a.contractName.localeCompare(b.contractName))}function renderHeader(contracts,options,anchors,hasOwnership,hasDuplicates,hasLifecycle){const lines=[generatedBanner("markdown"),"",`> ${evidenceDisclaimer()}`,"",`> ${evidenceProjectionNote()}`,"","# Environment Variables","","_Produced by `env-cap --docs`._","",`_Generated ${options.generatedAt.toISOString()}_`,""];const summary=computeChangeSummary(contracts,options.previousContent);lines.push("## Changes since last report","");if(summary.added.length===0&&summary.removed.length===0&&summary.commented.length===0){lines.push("No changes.","")}else{if(summary.added.length>0)lines.push(`- **Added:** ${summary.added.map(k=>`\`${k}\``).join(", ")}`);if(summary.removed.length>0)lines.push(`- **Removed:** ${summary.removed.map(k=>`\`${k}\``).join(", ")}`);if(summary.commented.length>0)lines.push(`- **No longer required (inactive):** ${summary.commented.map(k=>`\`${k}\``).join(", ")}`);lines.push("")}lines.push("## Table of contents","",`- ${mdLink("Catalog","catalog")}`);for(const contract of sortedContracts(contracts)){lines.push(` - ${mdLink(contract.contractName,anchors.anchorFor(contract,`contract-${contract.contractName}`))}`)}if(hasOwnership)lines.push(`- ${mdLink("Ownership matrix","ownership-matrix")}`);if(hasDuplicates)lines.push(`- ${mdLink("Dependency graph","dependency-graph")}`);if(hasLifecycle)lines.push(`- ${mdLink("Lifecycle report","lifecycle-report")}`);lines.push(`- ${mdLink("Security review","security-review")}`,"");return lines}function renderCatalog(contracts,anchors,undocumented){const lines=["## Catalog","",'<a id="catalog"></a>',""];const undocumentedByIdentity=new Set(undocumented.map(u=>`${u.file}#${u.exportName}`));const usesValidationContexts=contracts.some(c=>c.variables.some(v=>v.context));if(usesValidationContexts){lines.push("> Validation contexts describe when validation participates. They do not restrict access to values, and they do not remove a variable's schema (or its `default` value) from wherever this manifest is imported.","")}for(const contract of sortedContracts(contracts)){lines.push(`<a id="${anchors.anchorFor(contract,`contract-${contract.contractName}`)}"></a>`);lines.push(`## ${contract.contractName}`,"",`Source: \`${contract.file}\``);if(undocumentedByIdentity.has(`${contract.file}#${contract.exportName}`)){lines.push("","> \u26A0\uFE0F **Undocumented.** No `documentEnv()` call is linked to this contract.")}lines.push(`- Active: ${contract.active?"yes":"no"}`);if(contract.category!==void 0)lines.push(`- Category: ${contract.category}`);if(contract.exclusiveGroup!==void 0)lines.push(`- Exclusive group: ${contract.exclusiveGroup}`);if(contract.owner!==void 0)lines.push(`- Owner: ${contract.owner}`);if(contract.sensitivity!==void 0)lines.push(`- Sensitivity: ${contract.sensitivity}`);if(contract.expiresAt!==void 0)lines.push(`- Expires: ${contract.expiresAt}`);if(contract.purpose!==void 0)lines.push(`- Purpose: ${contract.purpose}`);if(contract.legalBasis!==void 0)lines.push(`- Legal basis: ${contract.legalBasis}`);if(contract.retention!==void 0)lines.push(`- Retention: ${contract.retention}`);if(contract.dataResidency!==void 0)lines.push(`- Data residency: ${renderMetadataValue(contract.dataResidency)}`);if(contract.auditRequired!==void 0)lines.push(`- Audit required: ${contract.auditRequired?"yes":"no"}`);if(contract.metadata){for(const[metaKey,metaValue]of Object.entries(contract.metadata)){lines.push(`- ${humanizeKey(metaKey)}: ${renderMetadataValue(metaValue)}`)}}lines.push("");for(const variable of[...contract.variables].sort((a,b)=>a.key<b.key?-1:1)){lines.push(`<a id="${anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`)}"></a>`);lines.push(`### \`${variable.key}\``,"");if(variable.description)lines.push(variable.description,"");if(!variable.documented)lines.push("> \u26A0\uFE0F **Undocumented.**","");lines.push(`- Default: ${variable.hasDefault?"yes":"no"}`,`- Processor: ${variable.hasProcessor?"yes":"no"}`,`- Validator: ${variable.hasValidator?"yes":"no"}`);if(variable.context!==void 0)lines.push(`- Validation context: ${variable.context}`);const owner=effectiveOwner(contract,variable);if(owner!==void 0)lines.push(`- Owner: ${owner}`);const sensitivity=effectiveSensitivity(contract,variable);if(sensitivity!==void 0)lines.push(`- Sensitivity: ${sensitivity}`);if(variable.expiresAt!==void 0)lines.push(`- Expires: ${variable.expiresAt}`);if(variable.setupInstructions!==void 0)lines.push(`- Setup instructions: ${variable.setupInstructions}`);if(variable.refreshInstructions!==void 0)lines.push(`- Refresh instructions: ${variable.refreshInstructions}`);if(variable.required!==void 0)lines.push(`- Required: ${variable.required?"yes":"no"}`);const purpose=effectivePurpose(contract,variable);if(purpose!==void 0)lines.push(`- Purpose: ${purpose}`);const legalBasis=effectiveLegalBasis(contract,variable);if(legalBasis!==void 0)lines.push(`- Legal basis: ${legalBasis}`);const retention=effectiveRetention(contract,variable);if(retention!==void 0)lines.push(`- Retention: ${retention}`);const dataResidency=effectiveDataResidency(contract,variable);if(dataResidency!==void 0)lines.push(`- Data residency: ${renderMetadataValue(dataResidency)}`);const auditRequired=effectiveAuditRequired(contract,variable);if(auditRequired!==void 0)lines.push(`- Audit required: ${auditRequired?"yes":"no"}`);const dynamicAccess=variable.evidence?.dynamicAccess;if(dynamicAccess&&dynamicAccess.length>0)lines.push(`- Dynamic access: ${dynamicAccess.join(", ")}`);for(const[metaKey,metaValue]of Object.entries(variable.metadata??{})){lines.push(`- ${humanizeKey(metaKey)}: ${renderMetadataValue(metaValue)}`)}lines.push("")}}return lines}function renderOwnershipMatrix(contracts,anchors){const grouped=groupVariablesByOwner(sortedContracts(contracts),effectiveOwner);const byOwner=new Map;for(const[owner,entries]of grouped){byOwner.set(owner,entries.map(({contract,variable})=>mdLink(`\`${variable.key}\` (${contract.contractName})`,anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`))))}if(byOwner.size===0)return[];const lines=["## Ownership matrix","",'<a id="ownership-matrix"></a>',"","| Owner | Variables |","|---|---|"];for(const owner of[...byOwner.keys()].sort()){lines.push(`| ${owner} | ${mustGet(byOwner,owner).join(", ")} |`)}lines.push("");return lines}function renderDependencyGraph(contracts,anchors){const byKey=new Map;for(const contract of contracts){for(const variable of contract.variables){const list=byKey.get(variable.key)??[];list.push({contract,variable});byKey.set(variable.key,list)}}const hasDuplicates=[...byKey.values()].some(declarations=>declarations.length>1);const lines=["## Dependency graph","",'<a id="dependency-graph"></a>',"",`One row per unique variable name; more than one location means more than one feature declares it (see the package README's "Duplicate variables" section for how that's handled at runtime).`,"","| Variable | Declared in |","|---|---|"];for(const key of[...byKey.keys()].sort()){const declarations=mustGet(byKey,key);const locations=declarations.map(({contract,variable})=>mdLink(`${contract.contractName} (\`${contract.file}\`)`,anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`))).join(", ");lines.push(`| \`${key}\` | ${locations} |`)}lines.push("");return{lines,hasDuplicates}}function renderLifecycleReport(contracts,anchors,expiringWithinDays,now){const rows=[];for(const contract of sortedContracts(contracts)){for(const variable of contract.variables){const owner=effectiveOwner(contract,variable);if(variable.expiresAt===void 0&&owner===void 0&&variable.refreshInstructions===void 0)continue;const expiresAt=variable.expiresAt;const expiry=expiresAt?parseIsoDate(expiresAt):void 0;let expiresCell=expiresAt??"--";if(expiry&&expiresAt){const daysRemaining=daysRemainingFrom(expiry,now);if(daysRemaining<0)expiresCell=`${expiresAt} (**expired ${Math.abs(daysRemaining)}d ago**)`;else if(daysRemaining<=expiringWithinDays)expiresCell=`${expiresAt} (**${daysRemaining}d remaining**)`}rows.push(`| ${mdLink(`\`${variable.key}\``,anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`))} | ${owner??"--"} | ${expiresCell} | ${variable.refreshInstructions??"--"} |`)}}if(rows.length===0)return{lines:[],hasLifecycle:false};const lines=["## Lifecycle report","",'<a id="lifecycle-report"></a>',"","| Variable | Owner | Expires | Refresh instructions |","|---|---|---|---|",...rows,""];return{lines,hasLifecycle:true}}function computeSecurityReviewCounters(contracts,expiringWithinDays,now,undocumentedContractCount,undocumentedVariableCount){let totalVariableDeclarations=0;let activeVariableDeclarations=0;const uniqueKeys=new Set;const keyContractCount=new Map;let expiresAtSetCount=0;let expiredCount=0;let expiringSoonCount=0;let requiredCount=0;let refreshInstructionsCount=0;let noOwnerCount=0;for(const contract of contracts){for(const variable of contract.variables){totalVariableDeclarations+=1;if(contract.active)activeVariableDeclarations+=1;uniqueKeys.add(variable.key);keyContractCount.set(variable.key,(keyContractCount.get(variable.key)??0)+1);if(variable.required)requiredCount+=1;if(variable.refreshInstructions)refreshInstructionsCount+=1;if(effectiveOwner(contract,variable)===void 0)noOwnerCount+=1;if(variable.expiresAt){expiresAtSetCount+=1;const date=parseIsoDate(variable.expiresAt);if(date){const daysRemaining=daysRemainingFrom(date,now);if(daysRemaining<0)expiredCount+=1;else if(daysRemaining<=expiringWithinDays)expiringSoonCount+=1}}}}const duplicateVariableNameCount=[...keyContractCount.values()].filter(count=>count>1).length;return{totalContracts:contracts.length,totalVariableDeclarations,activeVariableDeclarations,uniqueVariableNames:uniqueKeys.size,expiresAtSetCount,expiredCount,expiringSoonCount,requiredCount,refreshInstructionsCount,noOwnerCount,duplicateVariableNameCount,undocumentedContractCount,undocumentedVariableCount}}function renderSecurityReview(contracts,options,now){const counters=computeSecurityReviewCounters(contracts,options.expiringWithinDays,now,options.undocumentedContracts.length,options.undocumentedVariables.length);return["## Security review","",'<a id="security-review"></a>',"",`- Total contracts: ${counters.totalContracts}`,`- Total variable declarations: ${counters.totalVariableDeclarations} (${counters.activeVariableDeclarations} from active contracts)`,`- Unique variable names: ${counters.uniqueVariableNames}`,`- Variables with \`expiresAt\` set: ${counters.expiresAtSetCount}`,` - Already expired: ${counters.expiredCount}`,` - Expiring within ${options.expiringWithinDays} days: ${counters.expiringSoonCount}`,`- Variables marked \`required: true\`: ${counters.requiredCount}`,`- Variables with refresh instructions: ${counters.refreshInstructionsCount}`,`- Variables with no assigned owner: ${counters.noOwnerCount}`,`- Variable names declared by more than one contract: ${counters.duplicateVariableNameCount}`,`- Undocumented contracts: ${counters.undocumentedContractCount}`,`- Undocumented variables: ${counters.undocumentedVariableCount}`,""]}function renderDocs(contracts,options){const anchors=new AnchorRegistry;const sorted=sortedContracts(contracts);const ownership=renderOwnershipMatrix(sorted,anchors);const{lines:dependencyLines,hasDuplicates}=renderDependencyGraph(sorted,anchors);const{lines:lifecycleLines,hasLifecycle}=renderLifecycleReport(sorted,anchors,options.expiringWithinDays,options.generatedAt);const header=renderHeader(sorted,options,anchors,ownership.length>0,hasDuplicates,hasLifecycle);const catalog=renderCatalog(sorted,anchors,options.undocumentedContracts);const security=renderSecurityReview(sorted,options,options.generatedAt);return[...header,...catalog,...ownership,...dependencyLines,...lifecycleLines,...security].join("\n").replace(/\n{3,}/g,"\n\n")}function normalizeDocsForComparison(content){return content.replace(/^_Generated .+_$/m,"_Generated <normalized-for-comparison>_").replace(/\(\*\*\d+d remaining\*\*\)/g,"(**Nd remaining**)").replace(/\(\*\*expired \d+d ago\*\*\)/g,"(**expired Nd ago**)").replace(/^(- Already expired:) \d+$/gm,"$1 N").replace(/^(- Expiring within \d+ days:) \d+$/gm,"$1 N")}import path13 from"path";function isPrimitiveLiteral(value){return typeof value==="string"||typeof value==="number"||typeof value==="boolean"}function defaultLiteralFor(variable){return variable.defaultValue?.ok===true&&isPrimitiveLiteral(variable.defaultValue.value)?String(variable.defaultValue.value):void 0}function renderVariableLines(variable,options={}){const lines=[];if(options.note)lines.push(`# ${options.note}`);if(variable.description)lines.push(`# ${variable.description}`);if(variable.context)lines.push(`# Validation context: ${variable.context}`);if(variable.owner)lines.push(`# Owner: ${variable.owner}`);if(variable.setupInstructions)lines.push(`# Setup: ${variable.setupInstructions}`);if(variable.expiresAt)lines.push(`# Expires At: ${variable.expiresAt}`);if(variable.refreshInstructions)lines.push(`# Refresh Instructions: ${variable.refreshInstructions}`);if(variable.required)lines.push(`# Required: yes`);if(variable.purpose)lines.push(`# Purpose: ${variable.purpose}`);if(variable.legalBasis)lines.push(`# Legal Basis: ${variable.legalBasis}`);if(variable.retention)lines.push(`# Retention Policy: ${variable.retention}`);if(variable.dataResidency)lines.push(`# Data Residency: ${renderMetadataValue(variable.dataResidency)}`);if(variable.auditRequired)lines.push(`# Audit Required: yes`);for(const[metaKey,metaValue]of Object.entries(variable.metadata??{})){lines.push(`# ${humanizeKey(metaKey)}: ${renderMetadataValue(metaValue)}`)}const prefix=options.commented?"# ":"";lines.push(`${prefix}${variable.key}=${defaultLiteralFor(variable)??""}`,"");return lines}function groupByKey(contracts){const byKey=new Map;for(const contract of contracts){for(const variable of contract.variables){const list=byKey.get(variable.key)??[];list.push({contract,variable});byKey.set(variable.key,list)}}return byKey}function renderEnvExample(contracts,reconciliationHeader=[]){const sorted=[...contracts].sort((a,b)=>a.file.localeCompare(b.file));const active=sorted.filter(c=>c.active);const inactive=sorted.filter(c=>!c.active);const lines=["# AUTO-GENERATED EXAMPLE FILE.","# Copy to .env and fill in real values. Do not commit .env."];if(sorted.some(c=>c.variables.some(v=>v.context))){lines.push("# Validation context annotations describe when validation participates.","# They do not restrict access to values, and every variable below is","# still written to this file regardless of its context.")}if(reconciliationHeader.length>0)lines.push(...reconciliationHeader);lines.push("");const activeByKey=groupByKey(active);for(const key of[...activeByKey.keys()].sort()){const[first,...rest]=mustGet(activeByKey,key);lines.push(...renderVariableLines(first.variable));for(const dup of rest){lines.push(...renderVariableLines(dup.variable,{commented:true,note:`Also declared by "${dup.contract.contractName}" -- see "${first.contract.contractName}" above.`}))}}const inactiveByKey=groupByKey(inactive);for(const key of[...inactiveByKey.keys()].sort()){if(activeByKey.has(key))continue;const[first]=mustGet(inactiveByKey,key);lines.push(...renderVariableLines(first.variable,{commented:true,note:`Disabled -- feature "${first.contract.contractName}" is not active.`}))}return lines.join("\n").replace(/\n+$/,"\n")}function extractDeclaredVariables(source){const names=[];for(const line of source.split("\n")){const trimmed=line.trim();if(!trimmed||trimmed.startsWith("#"))continue;const match=/^([A-Za-z_][A-Za-z0-9_]*)=/.exec(trimmed);if(match?.[1])names.push(match[1])}return names}function extractCommentedVariables(source){const names=[];for(const line of source.split("\n")){const trimmed=line.trim();if(!trimmed.startsWith("#"))continue;const withoutHash=trimmed.replace(/^#+\s*/,"");const match=/^([A-Za-z_][A-Za-z0-9_]*)=/.exec(withoutHash);if(match?.[1])names.push(match[1])}return names}function computeReconciliation(contracts,existingContent){const activeKeys=new Set(contracts.filter(c=>c.active).flatMap(c=>c.variables.map(v=>v.key)));const knownKeys=new Set(contracts.flatMap(c=>c.variables.map(v=>v.key)));const oldLive=new Set(extractDeclaredVariables(existingContent));const oldKnown=new Set([...oldLive,...extractCommentedVariables(existingContent)]);const staleVariables=[...oldKnown].filter(key=>!knownKeys.has(key)).sort();const variablesToComment=[...oldLive].filter(key=>!activeKeys.has(key)&&knownKeys.has(key)).sort();const variablesToAdd=[...activeKeys].filter(key=>!oldLive.has(key)).sort();return{staleVariables,variablesToComment,variablesToAdd}}function renderReconciliationHeader(reconciliation){const lines=[];if(reconciliation.staleVariables.length>0){lines.push("#","# Remove the following variables (no longer declared by any feature):");for(const key of reconciliation.staleVariables)lines.push(`# - ${key}`)}if(reconciliation.variablesToComment.length>0){lines.push("#","# Comment the following variables (their feature is no longer active):");for(const key of reconciliation.variablesToComment)lines.push(`# - ${key}`)}if(reconciliation.variablesToAdd.length>0){lines.push("#","# Add the following variables (required by the current configuration):");for(const key of reconciliation.variablesToAdd)lines.push(`# - ${key}`)}return lines}async function writeEnvExample(contracts,location,fs2,options={}){const onExisting=options.onExisting??"keep-sibling";let existingContent;try{existingContent=await fs2.readFile(location,"utf8")}catch{}if(existingContent===void 0){const content2=renderEnvExample(contracts,[]);await fs2.mkdir(path13.dirname(location),{recursive:true});await fs2.writeFile(location,content2,"utf8");return{writtenPath:location,skippedExistingPath:void 0,staleVariables:[],variablesToComment:[],variablesToAdd:[]}}const reconciliation=computeReconciliation(contracts,existingContent);const{staleVariables,variablesToComment,variablesToAdd}=reconciliation;if(onExisting==="skip"){return{writtenPath:void 0,skippedExistingPath:location,staleVariables,variablesToComment,variablesToAdd}}const headerLines=onExisting==="keep-sibling"?renderReconciliationHeader(reconciliation):[];const content=renderEnvExample(contracts,headerLines);const writtenPath=onExisting==="overwrite"?location:`${location}.${Date.now()}`;const skippedExistingPath=onExisting==="overwrite"?void 0:location;await fs2.mkdir(path13.dirname(writtenPath),{recursive:true});await fs2.writeFile(writtenPath,content,"utf8");return{writtenPath,skippedExistingPath,staleVariables,variablesToComment,variablesToAdd}}function collectVariableNames(contracts){const names=new Set;for(const contract of contracts){for(const variable of contract.variables)names.add(variable.key)}return[...names]}function overrideVariable(variable,overrides){const override=overrides[variable.key];const date=override===void 0?void 0:parseIsoDate(override);return{...variable,expiresAt:date?override:variable.expiresAt,metadata:variable.metadata?{...variable.metadata}:variable.metadata}}function applyLiveExpirationOverrides(contracts,overrides){return contracts.map(contract=>({...contract,metadata:contract.metadata?{...contract.metadata}:contract.metadata,variables:contract.variables.map(variable=>overrideVariable(variable,overrides))}))}async function resolveLiveExpirationDates(contracts,liveExpirationDates){if(!liveExpirationDates)return contracts;const overrides=await liveExpirationDates(collectVariableNames(contracts));return deepFreeze(applyLiveExpirationOverrides(contracts,overrides))}var DEFAULT_EXPIRING_WITHIN_DAYS=30;var STANDARD_SENSITIVITY_LEVELS=new Set(["secret","credential","pii","config"]);function findNonstandardSensitivityLevels(contracts){const entries=[];for(const contract of contracts){if(contract.sensitivity!==void 0&&!STANDARD_SENSITIVITY_LEVELS.has(contract.sensitivity))entries.push({file:contract.file,exportName:contract.exportName,key:void 0,sensitivity:contract.sensitivity});for(const variable of[...contract.variables].sort((a,b)=>a.key.localeCompare(b.key))){if(variable.sensitivity===void 0)continue;if(STANDARD_SENSITIVITY_LEVELS.has(variable.sensitivity))continue;entries.push({file:contract.file,exportName:contract.exportName,key:variable.key,sensitivity:variable.sensitivity})}}return entries}function relativizeRef(root,ref){return{...ref,file:displayPath(root,ref.file)}}function computeDocumentation(root,linkResult,expiringWithinDays,generatedAt){const{contracts}=linkResult;return{contractSummaries:contracts.map(contract=>summarizeContract(contract,root)),catalog:buildCatalog(contracts),documentation:{undocumentedContracts:linkResult.undocumentedContracts,undocumentedVariables:linkResult.undocumentedVariables,staleDocEntries:linkResult.staleDocEntries,expiringSoon:computeExpiringEntries(contracts,expiringWithinDays,generatedAt),nonstandardSensitivityLevels:findNonstandardSensitivityLevels(contracts),unresolvedLinks:linkResult.unresolvedLinks},contractModelContracts:buildContractModel(contracts,root).contracts}}async function writeDocumentation(docsPath,envExamplePath,root,contracts,contractModelContracts,documentation,expiringWithinDays,generatedAt,fs2,envExampleOnExisting){let previousContent;try{previousContent=await fs2.readFile(docsPath,"utf8")}catch{}const docsSource=renderDocs(contractModelContracts,{expiringWithinDays,undocumentedContracts:documentation.undocumentedContracts.map(ref=>relativizeRef(root,ref)),undocumentedVariables:documentation.undocumentedVariables.map(ref=>relativizeRef(root,ref)),generatedAt,previousContent});await fs2.mkdir(path14.dirname(docsPath),{recursive:true});await fs2.writeFile(docsPath,docsSource,"utf8");const envExample=envExamplePath?await writeEnvExample(contracts,envExamplePath,fs2,{onExisting:envExampleOnExisting}):void 0;return{envExample}}var LIFECYCLE_MODEL_SCHEMA_VERSION=2;function hasLifecycleData(variable){return variable.expiresAt!==void 0||variable.refreshInstructions!==void 0||variable.deprecated!==void 0||variable.removeBy!==void 0||variable.renamedFrom!==void 0||variable.retention!==void 0}function buildLifecycleModel(contracts,expiringWithinDays,now,root){const modelContracts=[];for(const contract of contracts){const variables=[...contract.variables].filter(hasLifecycleData).sort((a,b)=>a.key.localeCompare(b.key)).map(variable=>({key:variable.key,expiresAt:variable.expiresAt,refreshInstructions:variable.refreshInstructions,deprecated:variable.deprecated,deprecatedReason:variable.deprecatedReason,removeBy:variable.removeBy,renamedFrom:variable.renamedFrom,retention:variable.retention}));const hasContractLevelData=contract.expiresAt!==void 0||contract.deprecated!==void 0||contract.deprecatedReason!==void 0||contract.retention!==void 0;if(!hasContractLevelData&&variables.length===0)continue;modelContracts.push({file:displayPath(root,contract.file),exportName:contract.exportName,contractName:contract.contractName,expiresAt:contract.expiresAt,deprecated:contract.deprecated,deprecatedReason:contract.deprecatedReason,retention:contract.retention,variables})}modelContracts.sort(byContractIdentity);return{schemaVersion:LIFECYCLE_MODEL_SCHEMA_VERSION,contracts:modelContracts,expiring:computeExpiringEntries(contracts,expiringWithinDays,now).map(entry=>({...entry,file:displayPath(root,entry.file)}))}}var OWNERSHIP_MODEL_SCHEMA_VERSION=1;function buildOwnershipModel(contracts,root){const modelContracts=contracts.map(contract=>({file:displayPath(root,contract.file),exportName:contract.exportName,contractName:contract.contractName,owner:contract.owner,variables:[...contract.variables].sort((a,b)=>a.key.localeCompare(b.key)).map(variable=>({key:variable.key,owner:effectiveOwner(contract,variable)}))}));modelContracts.sort(byContractIdentity);const unownedContracts=modelContracts.filter(c=>c.owner===void 0).map(({file,exportName})=>({file,exportName}));const unownedVariables=[];for(const contract of modelContracts){for(const variable of contract.variables){if(variable.owner===void 0){unownedVariables.push({file:contract.file,exportName:contract.exportName,key:variable.key})}}}return{schemaVersion:OWNERSHIP_MODEL_SCHEMA_VERSION,contracts:modelContracts,unownedContracts,unownedVariables}}function readToolVersion(){return"0.2.0"}async function computeSourceFingerprint(options){const{fs:fs2,root,include,exclude,packages}=options;const localSchemaFiles=await discoverSchemaFiles({fs:fs2,root,include,exclude});const packageCache=new Map;const{files:packageFiles,origins}=await resolveAllowlistedPackages(packages,root,packageCache,fs2);const schemaFiles=await mergeLocalAndPackageFiles(localSchemaFiles,packageFiles.map(f=>f.file),fs2);const{scanFiles}=await computeScanSurface(root,exclude,origins,fs2);const allFiles=[...new Set([...schemaFiles,...scanFiles])].sort();const hash=crypto2.createHash("sha256");hash.update(readToolVersion());for(const file of allFiles){hash.update(displayPath(root,file));try{hash.update(await fs2.readFile(file,"utf8"))}catch{hash.update("(unreadable)")}}return hash.digest("hex")}function fingerprintPathFor(evidencePath){return`${evidencePath}.fingerprint`}async function writeEvidenceFingerprint(evidencePath,fingerprint,fs2){await fs2.writeFile(fingerprintPathFor(evidencePath),`${fingerprint}
|
|
10
|
+
${issues.length} ${noun} found:`}var EnvProjectGenerationError=class _EnvProjectGenerationError extends Error{constructor(issues){super(formatIssues(countedHeader("Project generation failed.","blocking issue",issues),issues));this.code="ENV_PROJECT_GENERATION_FAILED";this.name="EnvProjectGenerationError";this.issues=issues;Error.captureStackTrace?.(this,_EnvProjectGenerationError)}};import crypto2 from"crypto";import path15 from"path";import path11 from"path";function detectExclusiveGroupIssues(contracts){const byGroup=new Map;for(const contract of contracts){if(!contract.active||!contract.exclusiveGroup)continue;const members=byGroup.get(contract.exclusiveGroup)??[];members.push(contract);byGroup.set(contract.exclusiveGroup,members)}const issues=[];for(const[group,members]of[...byGroup.entries()].sort((a,b)=>a[0].localeCompare(b[0]))){for(let i=0;i<members.length;i++){for(let j=i+1;j<members.length;j++){const a=members[i];const b=members[j];issues.push({severity:"error",variable:`Exclusive group "${group}"`,files:[a.file,b.file],reason:`"${a.contractName}" and "${b.contractName}" are both active and both declare exclusiveGroup "${group}" -- only one active contract per exclusive group is allowed. Set active: false on whichever one isn't in use.`})}}}return issues}import path10 from"path";function discoverValidationContexts(contracts){const contexts=new Set;for(const contract of contracts){if(!contract.active)continue;for(const variable of contract.variables){if(variable.context!==void 0)contexts.add(variable.context)}}return[...contexts].sort()}function renderManifest(contracts,outputFile){const sorted=[...contracts].sort((a,b)=>a.file.localeCompare(b.file));const outputDir=path10.dirname(outputFile);const usedNames=new Set;const importLines=[];const referenceNames=[];for(const contract of sorted){let localName=contract.exportName;let suffix=1;while(usedNames.has(localName)){localName=`${contract.exportName}_${suffix}`;suffix+=1}usedNames.add(localName);referenceNames.push(localName);let specifier;if(contract.packageOrigin){specifier=contract.packageOrigin.packageName}else{const withoutExt=contract.file.replace(/\.tsx?$/,"");specifier=path10.relative(outputDir,withoutExt).split(path10.sep).join("/");if(!specifier.startsWith("."))specifier=`./${specifier}`}const binding=localName===contract.exportName?contract.exportName:`${contract.exportName} as ${localName}`;importLines.push(`import { ${binding} } from "${specifier}";`)}const activeContexts=discoverValidationContexts(contracts);const activeContextsLines=activeContexts.length>0?["// all currently active validation contexts",`export const activeContexts = [${activeContexts.map(c=>JSON.stringify(c)).join(", ")}];`,""]:[];const lines=[generatedBanner("ts"),"",...importLines,"",...activeContextsLines,"export const manifest = [",...referenceNames.map(name=>` ${name},`),"];",""];return lines.join("\n")}function defaultInclude(){return["**/env.schema.ts"]}function defaultExclude(){return["**/node_modules/**","**/dist/**","**/.git/**"]}function computeManifest(root,linkResult,onIncompatibility){const{contracts}=linkResult;const activeContracts=contracts.filter(contract=>contract.active);const compatibilityIssues=[...detectCompatibilityIssues(activeContracts),...detectExclusiveGroupIssues(activeContracts),...detectDuplicateVariableShapes(activeContracts)];const compatibilityErrors=compatibilityIssues.filter(issue=>issue.severity==="error");const blocking=onIncompatibility==="throw"?compatibilityIssues.filter(issue=>issue.severity!=="info"):compatibilityErrors;return{activeContracts,contractSummaries:contracts.map(contract=>summarizeContract(contract,root)),warnings:compatibilityIssues.filter(issue=>issue.severity==="warning"),blocking}}async function writeManifest(outputPath,activeContracts,fs2){const manifestSource=renderManifest(activeContracts,outputPath);await fs2.mkdir(path11.dirname(outputPath),{recursive:true});await fs2.writeFile(outputPath,manifestSource,"utf8")}import path12 from"path";function sortedByContractName(entries){return[...entries].sort((a,b)=>a.contractName.localeCompare(b.contractName))}function renderDependencyOwnershipTable(entries){if(entries.length===0)return[];const lines=["## Dependency ownership","","Who owns each contract, who depends on it, and the blast radius if it changes.","","| Contract | Owner | Variables | Consumers | Blast radius |","|---|---|---|---|---|"];for(const entry of sortedByContractName(entries)){const consumers=entry.consumers.length>0?entry.consumers.join(", "):"--";lines.push(`| ${entry.contractName} (\`${entry.file}\`) | ${entry.owner??"--"} | ${entry.variableCount} | ${consumers} | ${entry.consumers.length} |`)}lines.push("");return lines}function renderAbandoned(findings){if(findings.length===0)return[];const lines=["## Abandoned ownership","","Contracts never imported anywhere in the scanned repository -- a feature's schema outliving the feature.","","| Contract | Owner | File |","|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| ${f.contractName} | ${f.owner??"--"} | \`${f.file}\` |`);lines.push("");return lines}function renderUnresolvedConsumers(findings){if(findings.length===0)return[];const lines=["## Unresolved consumers (barrel re-exports)","","Contracts reachable only through an unresolved `export * from` barrel re-export -- cannot be proven abandoned or consumed. Advisory only; never fails a build.",""];for(const f of sortedByContractName(findings))lines.push(`- **${f.contractName}** (\`${f.file}\`): ${f.reason}`);lines.push("");return lines}function formatStaleOrMissingCitations(citations){if(citations.length===0)return"--";return citations.map(c=>`${c.position.file}:${c.position.line}:${c.position.column} (${c.acknowledgment})`).join(", ")}function renderUnconsumedOwned(findings,scannedSurfaces){if(findings.length===0)return[];const surfaces=scannedSurfaces.map(s=>s.label).join(", ");const lines=["## Unconsumed owned dependencies","","Variables no consumer reads within the scanned surfaces below. Not proof of dead code. Common reasons a real consumer wouldn't show up here: it's read by a separate, out-of-repo service or webhook handler; it's consumed by non-TypeScript code (a shell script, a Dockerfile, a Terraform/Kubernetes manifest); or it's read from an allow-listed package whose directory wasn't included in this project's own `packages` configuration (ADR 0014).","",`Searched: ${surfaces}.`,"",'A blank Stale/missing citations cell is the strongest "looks genuinely unused" signal -- no developer has ever claimed otherwise. A non-blank cell means someone specifically claimed dynamic access here via `dynamicAccess`, and that claim can no longer be verified (ADR 0037) -- check with them before deleting.',"","| Variable | Contract | Owner | Stale/missing citations |","|---|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| \`${f.key}\` | ${f.contractName} | ${f.owner??"--"} | ${formatStaleOrMissingCitations(f.staleOrMissingCitations)} |`);lines.push("");return lines}function renderIndeterminate(findings){if(findings.length===0)return[];const lines=["## Indeterminate (dynamic access)","","Dynamic (computed) property access was observed -- usage cannot be determined statically. Never guessed at.","","| Variable | Contract | Reason | Stale/missing citations |","|---|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| \`${f.key}\` | ${f.contractName} | ${f.reason} | ${formatStaleOrMissingCitations(f.staleOrMissingCitations)} |`);lines.push("");return lines}function formatAssertions(assertions){const sites=assertions.map(a=>`${a.file}:${a.line}:${a.column}`).join(", ");return`Per developers, this data point is dynamically accessed at ${sites}.`}function renderAsserted(findings){if(findings.length===0)return[];const lines=["## Asserted (developer-acknowledged dynamic access)","","Variables env-cap's own scan would otherwise flag as unconsumed or indeterminate, but a developer has cited exactly where the dynamic access happens via `dynamicAccess`. The raw static status is shown alongside the citation, never hidden behind it -- a citation is a re-acknowledgment, not proof.","","| Variable | Contract | Static status | Developer assertion |","|---|---|---|---|"];for(const f of sortedByContractName(findings))lines.push(`| \`${f.key}\` | ${f.contractName} | ${f.wouldBeStatus} | ${formatAssertions(f.dynamicAccessAssertions)} |`);lines.push("");return lines}function renderParseWarnings(warnings){if(warnings.length===0)return[];const lines=["## Parse warnings","","Includes any `packages` (ADR 0014) resolution failures, alongside ordinary schema-discovery warnings.",""];for(const w of warnings)lines.push(`- **${w.file}**: ${w.message}`);lines.push("");return lines}function renderUsageReport(computed){const lines=[generatedBanner("markdown"),"",`> ${evidenceDisclaimer()}`,"",`> ${evidenceProjectionNote()}`,"","# Dependency & Ownership Report","","_Produced by `env-cap --ownership`._","","Which feature owns each variable, which features consume that contract, and what the blast radius is if it changes.","",...renderDependencyOwnershipTable(computed.dependencyOwnership),...renderAbandoned(computed.abandonedContracts),...renderUnresolvedConsumers(computed.unresolvedConsumers),...renderUnconsumedOwned(computed.unconsumedOwnedVariables,computed.scannedSurfaces),...renderIndeterminate(computed.indeterminate),...renderAsserted(computed.asserted),...renderParseWarnings(computed.parseWarnings)];return lines.join("\n").replace(/\n{3,}/g,"\n\n")}async function computeScanSurface(root,exclude,origins,fs2){const localScanFiles=await discoverSchemaFiles({fs:fs2,root,include:["**/*.ts","**/*.tsx"],exclude});const scannedSurfaces=[{label:"application",root:"."}];const scanFiles=[...localScanFiles];const seenPackageDirs=new Set;for(const origin of origins.values()){if(seenPackageDirs.has(origin.packageDir))continue;seenPackageDirs.add(origin.packageDir);const packageScanFiles=await discoverSchemaFiles({fs:fs2,root:origin.packageDir,include:["**/*.ts","**/*.tsx"],exclude:defaultExclude()});scanFiles.push(...packageScanFiles);scannedSurfaces.push({label:`package:${origin.packageName}`,root:displayPath(root,origin.packageDir)})}return{scanFiles,scannedSurfaces}}async function computeUsage(root,contracts,scanFiles,readFile2,context,parseWarnings,scannedSurfaces,dynamicAccessAcknowledgments){const graph=await buildDependencyGraph(contracts,scanFiles,readFile2,context,scannedSurfaces,dynamicAccessAcknowledgments);const findings=deriveOwnershipFindings(graph);const contractByIdentity=new Map;for(const contract of contracts)contractByIdentity.set(`${contract.file}#${contract.exportName}`,contract);const ownerFor=(file,exportName)=>contractByIdentity.get(`${file}#${exportName}`)?.owner;const effectiveOwnerFor=(file,exportName,key)=>{const contract=contractByIdentity.get(`${file}#${exportName}`);if(!contract)return void 0;const variable=contract.variables.find(v=>v.key===key);return variable?effectiveOwner(contract,variable):contract.owner};const dependencyOwnership=graph.contracts.map(c=>({contractName:c.contractName,file:path12.relative(root,c.file),owner:ownerFor(c.file,c.exportName),variableCount:c.variables.size,consumers:c.consumingFiles.map(f=>path12.relative(root,f))}));const abandonedContracts=findings.abandoned.map(f=>({contractName:f.contractName,file:path12.relative(root,f.file),owner:ownerFor(f.file,f.exportName)}));const unresolvedConsumers=findings.unresolvedConsumers.map(f=>({contractName:f.contractName,file:path12.relative(root,f.file),reason:f.reason}));const staleOrMissingCitationsFor=(contractName,file,exportName,key)=>{const relativeFile=displayPath(root,file);const identity=dynamicAccessVariableIdentity(relativeFile,exportName,key);const problems=[];for(const a of dynamicAccessAcknowledgments?.get(identity)??[]){if(a.acknowledgment==="fresh")continue;problems.push({contractName,file:relativeFile,exportName,key,position:{file:a.file,line:a.line,column:a.column},acknowledgment:a.acknowledgment})}return problems};const unconsumedOwnedVariables=findings.unconsumedOwned.map(f=>({contractName:f.contractName,owner:effectiveOwnerFor(f.file,f.exportName,f.key),key:f.key,staleOrMissingCitations:staleOrMissingCitationsFor(f.contractName,f.file,f.exportName,f.key)}));const indeterminate=findings.indeterminate.map(f=>({contractName:f.contractName,key:f.key,reason:f.reason,dynamicAccessSites:f.dynamicAccessSites.map(p=>({...p,file:displayPath(root,p.file)})),staleOrMissingCitations:staleOrMissingCitationsFor(f.contractName,f.file,f.exportName,f.key)}));const asserted=findings.asserted.map(f=>({contractName:f.contractName,key:f.key,wouldBeStatus:f.wouldBeStatus,dynamicAccessAssertions:f.dynamicAccessAssertions}));return{graph,result:{dependencyOwnership,abandonedContracts,unresolvedConsumers,unconsumedOwnedVariables,indeterminate,asserted,parseWarnings,scannedSurfaces:graph.scannedSurfaces}}}async function writeUsageReport(reportPath,computed,fs2){const source=renderUsageReport(computed);await fs2.mkdir(path12.dirname(reportPath),{recursive:true});await fs2.writeFile(reportPath,source,"utf8")}var FINDING_MODEL_SCHEMA_VERSION=3;var NO_POSITION=void 0;function fromCompatibilityIssue(root,issue,code){return{severity:issue.severity,code,family:"compatibility",message:issue.reason,location:{model:"contract",file:displayPath(root,issue.files[0]),exportName:void 0,variable:issue.variable,position:NO_POSITION}}}function buildFindingModel(input){const{root}=input;const findings=[];for(const issue of input.compatibilityIssues??[]){findings.push(fromCompatibilityIssue(root,issue,issue.code??"DUPLICATE_VARIABLE_DOCUMENTATION"))}for(const issue of input.exclusiveGroupIssues??[]){findings.push(fromCompatibilityIssue(root,issue,"EXCLUSIVE_GROUP_VIOLATION"))}for(const check of input.artifactCheckFindings??[]){if(check.status==="ok")continue;findings.push({severity:"warning",code:check.status==="missing"?"ARTIFACT_MISSING":"ARTIFACT_STALE",family:"drift",message:check.detail??`${check.artifact} artifact is ${check.status}.`,location:{model:"change",path:check.path}})}const documentation=input.documentation;if(documentation){for(const c of documentation.undocumentedContracts){findings.push({severity:"warning",code:"UNDOCUMENTED_CONTRACT",family:"documentation",message:`"${c.exportName}" has no documentEnv() call linked to it.`,location:{model:"contract",file:displayPath(root,c.file),exportName:c.exportName,variable:void 0,position:NO_POSITION}})}for(const v of documentation.undocumentedVariables){findings.push({severity:"warning",code:"UNDOCUMENTED_VARIABLE",family:"documentation",message:`"${v.key}" (declared by "${v.exportName}") has no matching entry in a linked documentEnv()'s "variables".`,location:{model:"contract",file:displayPath(root,v.file),exportName:v.exportName,variable:v.key,position:NO_POSITION}})}for(const s of documentation.staleDocEntries){findings.push({severity:"warning",code:"STALE_DOC_ENTRY",family:"documentation",message:`"${s.key}" is documented under "${s.exportName}" but no longer exists in that contract's schema.`,location:{model:"contract",file:displayPath(root,s.file),exportName:s.exportName,variable:s.key,position:NO_POSITION}})}for(const e of documentation.expiringSoon){const expired=e.daysRemaining<0;findings.push({severity:"warning",code:expired?"EXPIRED":"EXPIRING_SOON",family:"documentation",message:expired?`"${e.key??e.exportName}" expired ${Math.abs(e.daysRemaining)} day(s) ago (expiresAt: ${e.expiresAt}).`:`"${e.key??e.exportName}" expires in ${e.daysRemaining} day(s) (expiresAt: ${e.expiresAt}).`,location:{model:"contract",file:displayPath(root,e.file),exportName:e.exportName,variable:e.key,position:NO_POSITION}})}for(const n of documentation.nonstandardSensitivityLevels){findings.push({severity:"info",code:"NONSTANDARD_SENSITIVITY_LEVEL",family:"documentation",message:`"${n.key??n.exportName}" declares sensitivity "${n.sensitivity}", which isn't one of the standard levels (secret/credential/pii/config) -- still honored verbatim, just flagged for vocabulary drift.`,location:{model:"contract",file:displayPath(root,n.file),exportName:n.exportName,variable:n.key,position:NO_POSITION}})}for(const u of documentation.unresolvedLinks){findings.push({severity:"warning",code:"UNRESOLVED_DOCUMENTENV_LINK",family:"documentation",message:u.reason,location:{model:"contract",file:displayPath(root,u.file),exportName:void 0,variable:void 0,position:NO_POSITION}})}}for(const a of input.abandonedContracts??[]){findings.push({severity:"warning",code:"ABANDONED_CONTRACT",family:"ownership",message:`"${a.contractName}" is never imported anywhere in the scanned repository.`,location:{model:"ownership",contractName:a.contractName,file:a.file,variable:void 0,position:NO_POSITION}})}for(const u of input.unresolvedConsumers??[]){findings.push({severity:"warning",code:"UNRESOLVED_CONSUMER",family:"ownership",message:u.reason,location:{model:"ownership",contractName:u.contractName,file:u.file,variable:void 0,position:NO_POSITION}})}for(const u of input.unconsumedOwnedVariables??[]){findings.push({severity:"warning",code:"UNCONSUMED_OWNED_VARIABLE",family:"ownership",message:`"${u.key}" (declared by "${u.contractName}") has no consumer found in the scanned repository.`,location:{model:"ownership",contractName:u.contractName,file:void 0,variable:u.key,position:NO_POSITION}})}for(const i of input.indeterminateOwnership??[]){findings.push({severity:"warning",code:"INDETERMINATE_OWNERSHIP",family:"ownership",message:i.reason,location:{model:"ownership",contractName:i.contractName,file:void 0,variable:i.key,position:NO_POSITION}})}for(const p of input.dynamicAccessCitationProblems??[]){const missing=p.acknowledgment==="missing";findings.push({severity:"warning",code:missing?"MISSING_DYNAMIC_ACCESS_CITATION":"STALE_DYNAMIC_ACCESS_CITATION",family:"ownership",message:missing?`"${p.key}" (declared by "${p.contractName}") cites dynamic access at ${p.position.file}:${p.position.line}:${p.position.column}, but that file no longer exists. Re-run generate:env once the citation is corrected.`:`"${p.key}" (declared by "${p.contractName}") cites dynamic access at ${p.position.file}:${p.position.line}:${p.position.column}, but that file's content has changed since it was last acknowledged. Re-run generate:env once you've re-confirmed the citation still applies.`,location:{model:"ownership",contractName:p.contractName,file:p.file,variable:p.key,position:p.position}})}return{schemaVersion:FINDING_MODEL_SCHEMA_VERSION,findings}}import path14 from"path";function humanizeKey(key){const spaced=key.replace(/([a-z0-9])([A-Z])/g,"$1 $2");return spaced.charAt(0).toUpperCase()+spaced.slice(1)}function renderMetadataValue(value){return typeof value==="string"?value:JSON.stringify(value)}function defineEvidenceProjection(schema){function project(evidence){const value={};const sources={};for(const key of Object.keys(schema)){const projector=schema[key];const{proxy,readPaths}=createTrackingProxy(evidence);value[key]=projector(proxy);sources[key]=readPaths()}return{value,sources}}function invoke(evidence){return project(evidence).value}return Object.assign(invoke,{project})}function readOnlyMembraneError(){return new TypeError("EvidenceModel is read-only inside a projector -- a projection must be a pure function of its evidence argument. See ADR 0032.")}function createTrackingProxy(evidence){const clone=structuredClone(evidence);const paths=new Set;const wrapped=new WeakMap;function wrap(value,path18){if(value===null||typeof value!=="object")return value;const target=value;const cached=wrapped.get(target);if(cached)return cached;const proxy=new Proxy(target,{get(currentTarget,prop,receiver){const result=Reflect.get(currentTarget,prop,receiver);if(typeof prop!=="string")return result;if(Array.isArray(currentTarget)&&prop==="length")return result;const nextPath=[...path18,prop];paths.add(nextPath.join("."));return wrap(result,nextPath)},set(){throw readOnlyMembraneError()},deleteProperty(){throw readOnlyMembraneError()},defineProperty(){throw readOnlyMembraneError()},setPrototypeOf(){throw readOnlyMembraneError()}});wrapped.set(target,proxy);return proxy}return{proxy:wrap(clone,[]),readPaths:()=>[...paths].sort()}}function byContractThenKey(a,b){return a.file.localeCompare(b.file)||a.exportName.localeCompare(b.exportName)||a.key.localeCompare(b.key)}function contractIndex(contracts){return new Map(contracts.map(c=>[`${c.file}#${c.exportName}`,c]))}function groupVariablesByOwner(contracts,ownerOf){const byOwner=new Map;for(const contract of contracts){for(const variable of contract.variables){const owner=ownerOf(contract,variable);if(owner===void 0)continue;const list=byOwner.get(owner)??[];list.push({contract,variable});byOwner.set(owner,list)}}return byOwner}var configurationReference=defineEvidenceProjection({disclaimer:()=>evidenceDisclaimer(),entries:evidence=>{const entries=[];for(const contract of evidence.contract.contracts){for(const variable of contract.variables){entries.push({file:contract.file,exportName:contract.exportName,contractName:contract.contractName,key:variable.key,description:variable.description,owner:variable.owner??contract.owner,sensitivity:variable.sensitivity??contract.sensitivity,required:variable.required,hasDefault:variable.hasDefault,hasProcessor:variable.hasProcessor,hasValidator:variable.hasValidator,expiresAt:variable.expiresAt,active:contract.active})}}return entries.sort(byContractThenKey)}});var ownershipSummary=defineEvidenceProjection({disclaimer:()=>evidenceDisclaimer(),owners:evidence=>{const contracts=contractIndex(evidence.contract.contracts);const nameOf=(file,exportName)=>contracts.get(`${file}#${exportName}`)?.contractName??exportName;const variablesByOwner=groupVariablesByOwner(evidence.ownership.contracts,(_contract,variable)=>variable.owner);const contractsByOwner=new Map;for(const contract of evidence.ownership.contracts){if(contract.owner===void 0)continue;const list=contractsByOwner.get(contract.owner)??[];list.push(nameOf(contract.file,contract.exportName));contractsByOwner.set(contract.owner,list)}const owners=new Set([...variablesByOwner.keys(),...contractsByOwner.keys()]);return[...owners].sort().map(owner=>({owner,variables:(variablesByOwner.get(owner)??[]).map(({contract,variable})=>`${nameOf(contract.file,contract.exportName)}.${variable.key}`).sort(),contracts:(contractsByOwner.get(owner)??[]).sort()}))},unowned:evidence=>{const contracts=contractIndex(evidence.contract.contracts);return evidence.ownership.unownedVariables.map(ref=>{const contractName=contracts.get(`${ref.file}#${ref.exportName}`)?.contractName??ref.exportName;return`${contractName}.${ref.key}`}).sort()}});var expiringSoonReport=defineEvidenceProjection({disclaimer:()=>evidenceDisclaimer(),entries:evidence=>{const contracts=contractIndex(evidence.contract.contracts);const ownerByVariable=new Map;for(const contract of evidence.ownership.contracts)for(const variable of contract.variables)ownerByVariable.set(`${contract.file}#${contract.exportName}#${variable.key}`,variable.owner);return evidence.lifecycle.expiring.map(entry=>{const contract=contracts.get(`${entry.file}#${entry.exportName}`);const variable=entry.key===void 0?void 0:contract?.variables.find(v=>v.key===entry.key);return{file:entry.file,exportName:entry.exportName,contractName:contract?.contractName??entry.exportName,key:entry.key,expiresAt:entry.expiresAt,daysRemaining:entry.daysRemaining,expired:entry.daysRemaining<0,refreshInstructions:variable?.refreshInstructions,owner:entry.key===void 0?contract?.owner:ownerByVariable.get(`${entry.file}#${entry.exportName}#${entry.key}`)??contract?.owner}})},expiredCount:evidence=>evidence.lifecycle.expiring.filter(e=>e.daysRemaining<0).length});function parseIsoDate(value){const date=new Date(value);return Number.isNaN(date.getTime())?void 0:date}var MS_PER_DAY=864e5;function daysRemainingFrom(date,now){return Math.ceil((date.getTime()-now.getTime())/MS_PER_DAY)}function computeExpiringEntries(contracts,expiringWithinDays,now){const entries=[];for(const contract of contracts){if(contract.expiresAt){const date=parseIsoDate(contract.expiresAt);if(date){const daysRemaining=daysRemainingFrom(date,now);if(daysRemaining<=expiringWithinDays){entries.push({file:contract.file,exportName:contract.exportName,key:void 0,expiresAt:contract.expiresAt,daysRemaining})}}}for(const variable of contract.variables){if(!variable.expiresAt)continue;const date=parseIsoDate(variable.expiresAt);if(!date)continue;const daysRemaining=daysRemainingFrom(date,now);if(daysRemaining<=expiringWithinDays){entries.push({file:contract.file,exportName:contract.exportName,key:variable.key,expiresAt:variable.expiresAt,daysRemaining})}}}return entries.sort((a,b)=>a.daysRemaining-b.daysRemaining)}function buildCatalog(contracts){return sortedContracts(contracts).map(contract=>{const variables={};for(const variable of contract.variables){variables[variable.key]={description:variable.description,owner:effectiveOwner(contract,variable),sensitivity:effectiveSensitivity(contract,variable),expiresAt:variable.expiresAt,refreshInstructions:variable.refreshInstructions,setupInstructions:variable.setupInstructions,required:variable.required,hasDefault:variable.hasDefault,hasProcessor:variable.hasProcessor,processorReturnType:variable.processorReturnType,hasValidator:variable.hasValidator,documented:variable.documented,context:variable.context,purpose:effectivePurpose(contract,variable),legalBasis:effectiveLegalBasis(contract,variable),retention:effectiveRetention(contract,variable),dataResidency:effectiveDataResidency(contract,variable),auditRequired:effectiveAuditRequired(contract,variable),metadata:variable.metadata,evidence:variable.evidence}}return{file:contract.file,exportName:contract.exportName,contractName:contract.contractName,active:contract.active,documented:contract.documented,category:contract.category,exclusiveGroup:contract.exclusiveGroup,...governanceFieldsOf(contract),variables}})}function extractPreviouslyDocumentedKeys(previousContent){const keys=new Set;for(const match of previousContent.matchAll(/^### `([A-Za-z_][A-Za-z0-9_]*)`$/gm)){if(match[1])keys.add(match[1])}return keys}function extractPreviouslyActiveKeys(previousContent){const keys=new Set;let currentContractActive=false;for(const line of previousContent.split("\n")){const activeMatch=/^- Active: (yes|no)$/.exec(line);if(activeMatch){currentContractActive=activeMatch[1]==="yes";continue}const headingMatch=/^### `([A-Za-z_][A-Za-z0-9_]*)`$/.exec(line);if(headingMatch&¤tContractActive){keys.add(headingMatch[1])}}return keys}function computeChangeSummary(contracts,previousContent){if(previousContent===void 0)return{added:[],removed:[],commented:[]};const previousKeys=extractPreviouslyDocumentedKeys(previousContent);const previousActiveKeys=extractPreviouslyActiveKeys(previousContent);const currentKeys=new Set;const currentActiveKeys=new Set;for(const contract of contracts){for(const variable of contract.variables){currentKeys.add(variable.key);if(contract.active)currentActiveKeys.add(variable.key)}}return{added:[...currentKeys].filter(k=>!previousKeys.has(k)).sort(),removed:[...previousKeys].filter(k=>!currentKeys.has(k)).sort(),commented:[...previousActiveKeys].filter(k=>currentKeys.has(k)&&!currentActiveKeys.has(k)).sort()}}function slugify(text){return text.toLowerCase().replace(/[^a-z0-9_]+/g,"-").replace(/^-+|-+$/g,"")||"section"}var AnchorRegistry=class{constructor(){this.used=new Map;this.assigned=new Map}anchorFor(target,baseText){const existing=this.assigned.get(target);if(existing)return existing;const base=slugify(baseText);const count=this.used.get(base)??0;this.used.set(base,count+1);const anchor=count===0?base:`${base}-${count}`;this.assigned.set(target,anchor);return anchor}};function mdLink(text,anchor){return`[${text}](#${anchor})`}function sortedContracts(contracts){return[...contracts].sort((a,b)=>a.contractName.localeCompare(b.contractName))}function renderHeader(contracts,options,anchors,hasOwnership,hasDuplicates,hasLifecycle){const lines=[generatedBanner("markdown"),"",`> ${evidenceDisclaimer()}`,"",`> ${evidenceProjectionNote()}`,"","# Environment Variables","","_Produced by `env-cap --docs`._","",`_Generated ${options.generatedAt.toISOString()}_`,""];const summary=computeChangeSummary(contracts,options.previousContent);lines.push("## Changes since last report","");if(summary.added.length===0&&summary.removed.length===0&&summary.commented.length===0){lines.push("No changes.","")}else{if(summary.added.length>0)lines.push(`- **Added:** ${summary.added.map(k=>`\`${k}\``).join(", ")}`);if(summary.removed.length>0)lines.push(`- **Removed:** ${summary.removed.map(k=>`\`${k}\``).join(", ")}`);if(summary.commented.length>0)lines.push(`- **No longer required (inactive):** ${summary.commented.map(k=>`\`${k}\``).join(", ")}`);lines.push("")}lines.push("## Table of contents","",`- ${mdLink("Catalog","catalog")}`);for(const contract of sortedContracts(contracts)){lines.push(` - ${mdLink(contract.contractName,anchors.anchorFor(contract,`contract-${contract.contractName}`))}`)}if(hasOwnership)lines.push(`- ${mdLink("Ownership matrix","ownership-matrix")}`);if(hasDuplicates)lines.push(`- ${mdLink("Dependency graph","dependency-graph")}`);if(hasLifecycle)lines.push(`- ${mdLink("Lifecycle report","lifecycle-report")}`);lines.push(`- ${mdLink("Security review","security-review")}`,"");return lines}function renderCatalog(contracts,anchors,undocumented){const lines=["## Catalog","",'<a id="catalog"></a>',""];const undocumentedByIdentity=new Set(undocumented.map(u=>`${u.file}#${u.exportName}`));const usesValidationContexts=contracts.some(c=>c.variables.some(v=>v.context));if(usesValidationContexts){lines.push("> Validation contexts describe when validation participates. They do not restrict access to values, and they do not remove a variable's schema (or its `default` value) from wherever this manifest is imported.","")}for(const contract of sortedContracts(contracts)){lines.push(`<a id="${anchors.anchorFor(contract,`contract-${contract.contractName}`)}"></a>`);lines.push(`## ${contract.contractName}`,"",`Source: \`${contract.file}\``);if(undocumentedByIdentity.has(`${contract.file}#${contract.exportName}`)){lines.push("","> \u26A0\uFE0F **Undocumented.** No `documentEnv()` call is linked to this contract.")}lines.push(`- Active: ${contract.active?"yes":"no"}`);if(contract.category!==void 0)lines.push(`- Category: ${contract.category}`);if(contract.exclusiveGroup!==void 0)lines.push(`- Exclusive group: ${contract.exclusiveGroup}`);if(contract.owner!==void 0)lines.push(`- Owner: ${contract.owner}`);if(contract.sensitivity!==void 0)lines.push(`- Sensitivity: ${contract.sensitivity}`);if(contract.expiresAt!==void 0)lines.push(`- Expires: ${contract.expiresAt}`);if(contract.purpose!==void 0)lines.push(`- Purpose: ${contract.purpose}`);if(contract.legalBasis!==void 0)lines.push(`- Legal basis: ${contract.legalBasis}`);if(contract.retention!==void 0)lines.push(`- Retention: ${contract.retention}`);if(contract.dataResidency!==void 0)lines.push(`- Data residency: ${renderMetadataValue(contract.dataResidency)}`);if(contract.auditRequired!==void 0)lines.push(`- Audit required: ${contract.auditRequired?"yes":"no"}`);if(contract.metadata){for(const[metaKey,metaValue]of Object.entries(contract.metadata)){lines.push(`- ${humanizeKey(metaKey)}: ${renderMetadataValue(metaValue)}`)}}lines.push("");for(const variable of[...contract.variables].sort((a,b)=>a.key<b.key?-1:1)){lines.push(`<a id="${anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`)}"></a>`);lines.push(`### \`${variable.key}\``,"");if(variable.description)lines.push(variable.description,"");if(!variable.documented)lines.push("> \u26A0\uFE0F **Undocumented.**","");lines.push(`- Default: ${variable.hasDefault?"yes":"no"}`,`- Processor: ${variable.hasProcessor?"yes":"no"}`,`- Validator: ${variable.hasValidator?"yes":"no"}`);if(variable.context!==void 0)lines.push(`- Validation context: ${variable.context}`);const owner=effectiveOwner(contract,variable);if(owner!==void 0)lines.push(`- Owner: ${owner}`);const sensitivity=effectiveSensitivity(contract,variable);if(sensitivity!==void 0)lines.push(`- Sensitivity: ${sensitivity}`);if(variable.expiresAt!==void 0)lines.push(`- Expires: ${variable.expiresAt}`);if(variable.setupInstructions!==void 0)lines.push(`- Setup instructions: ${variable.setupInstructions}`);if(variable.refreshInstructions!==void 0)lines.push(`- Refresh instructions: ${variable.refreshInstructions}`);if(variable.required!==void 0)lines.push(`- Required: ${variable.required?"yes":"no"}`);const purpose=effectivePurpose(contract,variable);if(purpose!==void 0)lines.push(`- Purpose: ${purpose}`);const legalBasis=effectiveLegalBasis(contract,variable);if(legalBasis!==void 0)lines.push(`- Legal basis: ${legalBasis}`);const retention=effectiveRetention(contract,variable);if(retention!==void 0)lines.push(`- Retention: ${retention}`);const dataResidency=effectiveDataResidency(contract,variable);if(dataResidency!==void 0)lines.push(`- Data residency: ${renderMetadataValue(dataResidency)}`);const auditRequired=effectiveAuditRequired(contract,variable);if(auditRequired!==void 0)lines.push(`- Audit required: ${auditRequired?"yes":"no"}`);const dynamicAccess=variable.evidence?.dynamicAccess;if(dynamicAccess&&dynamicAccess.length>0)lines.push(`- Dynamic access: ${dynamicAccess.join(", ")}`);for(const[metaKey,metaValue]of Object.entries(variable.metadata??{})){lines.push(`- ${humanizeKey(metaKey)}: ${renderMetadataValue(metaValue)}`)}lines.push("")}}return lines}function renderOwnershipMatrix(contracts,anchors){const grouped=groupVariablesByOwner(sortedContracts(contracts),effectiveOwner);const byOwner=new Map;for(const[owner,entries]of grouped){byOwner.set(owner,entries.map(({contract,variable})=>mdLink(`\`${variable.key}\` (${contract.contractName})`,anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`))))}if(byOwner.size===0)return[];const lines=["## Ownership matrix","",'<a id="ownership-matrix"></a>',"","| Owner | Variables |","|---|---|"];for(const owner of[...byOwner.keys()].sort()){lines.push(`| ${owner} | ${mustGet(byOwner,owner).join(", ")} |`)}lines.push("");return lines}function renderDependencyGraph(contracts,anchors){const byKey=new Map;for(const contract of contracts){for(const variable of contract.variables){const list=byKey.get(variable.key)??[];list.push({contract,variable});byKey.set(variable.key,list)}}const hasDuplicates=[...byKey.values()].some(declarations=>declarations.length>1);const lines=["## Dependency graph","",'<a id="dependency-graph"></a>',"",`One row per unique variable name; more than one location means more than one feature declares it (see the package README's "Duplicate variables" section for how that's handled at runtime).`,"","| Variable | Declared in |","|---|---|"];for(const key of[...byKey.keys()].sort()){const declarations=mustGet(byKey,key);const locations=declarations.map(({contract,variable})=>mdLink(`${contract.contractName} (\`${contract.file}\`)`,anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`))).join(", ");lines.push(`| \`${key}\` | ${locations} |`)}lines.push("");return{lines,hasDuplicates}}function renderLifecycleReport(contracts,anchors,expiringWithinDays,now){const rows=[];for(const contract of sortedContracts(contracts)){for(const variable of contract.variables){const owner=effectiveOwner(contract,variable);if(variable.expiresAt===void 0&&owner===void 0&&variable.refreshInstructions===void 0)continue;const expiresAt=variable.expiresAt;const expiry=expiresAt?parseIsoDate(expiresAt):void 0;let expiresCell=expiresAt??"--";if(expiry&&expiresAt){const daysRemaining=daysRemainingFrom(expiry,now);if(daysRemaining<0)expiresCell=`${expiresAt} (**expired ${Math.abs(daysRemaining)}d ago**)`;else if(daysRemaining<=expiringWithinDays)expiresCell=`${expiresAt} (**${daysRemaining}d remaining**)`}rows.push(`| ${mdLink(`\`${variable.key}\``,anchors.anchorFor(variable,`${contract.contractName}-${variable.key}`))} | ${owner??"--"} | ${expiresCell} | ${variable.refreshInstructions??"--"} |`)}}if(rows.length===0)return{lines:[],hasLifecycle:false};const lines=["## Lifecycle report","",'<a id="lifecycle-report"></a>',"","| Variable | Owner | Expires | Refresh instructions |","|---|---|---|---|",...rows,""];return{lines,hasLifecycle:true}}function computeSecurityReviewCounters(contracts,expiringWithinDays,now,undocumentedContractCount,undocumentedVariableCount){let totalVariableDeclarations=0;let activeVariableDeclarations=0;const uniqueKeys=new Set;const keyContractCount=new Map;let expiresAtSetCount=0;let expiredCount=0;let expiringSoonCount=0;let requiredCount=0;let refreshInstructionsCount=0;let noOwnerCount=0;for(const contract of contracts){for(const variable of contract.variables){totalVariableDeclarations+=1;if(contract.active)activeVariableDeclarations+=1;uniqueKeys.add(variable.key);keyContractCount.set(variable.key,(keyContractCount.get(variable.key)??0)+1);if(variable.required)requiredCount+=1;if(variable.refreshInstructions)refreshInstructionsCount+=1;if(effectiveOwner(contract,variable)===void 0)noOwnerCount+=1;if(variable.expiresAt){expiresAtSetCount+=1;const date=parseIsoDate(variable.expiresAt);if(date){const daysRemaining=daysRemainingFrom(date,now);if(daysRemaining<0)expiredCount+=1;else if(daysRemaining<=expiringWithinDays)expiringSoonCount+=1}}}}const duplicateVariableNameCount=[...keyContractCount.values()].filter(count=>count>1).length;return{totalContracts:contracts.length,totalVariableDeclarations,activeVariableDeclarations,uniqueVariableNames:uniqueKeys.size,expiresAtSetCount,expiredCount,expiringSoonCount,requiredCount,refreshInstructionsCount,noOwnerCount,duplicateVariableNameCount,undocumentedContractCount,undocumentedVariableCount}}function renderSecurityReview(contracts,options,now){const counters=computeSecurityReviewCounters(contracts,options.expiringWithinDays,now,options.undocumentedContracts.length,options.undocumentedVariables.length);return["## Security review","",'<a id="security-review"></a>',"",`- Total contracts: ${counters.totalContracts}`,`- Total variable declarations: ${counters.totalVariableDeclarations} (${counters.activeVariableDeclarations} from active contracts)`,`- Unique variable names: ${counters.uniqueVariableNames}`,`- Variables with \`expiresAt\` set: ${counters.expiresAtSetCount}`,` - Already expired: ${counters.expiredCount}`,` - Expiring within ${options.expiringWithinDays} days: ${counters.expiringSoonCount}`,`- Variables marked \`required: true\`: ${counters.requiredCount}`,`- Variables with refresh instructions: ${counters.refreshInstructionsCount}`,`- Variables with no assigned owner: ${counters.noOwnerCount}`,`- Variable names declared by more than one contract: ${counters.duplicateVariableNameCount}`,`- Undocumented contracts: ${counters.undocumentedContractCount}`,`- Undocumented variables: ${counters.undocumentedVariableCount}`,""]}function renderDocs(contracts,options){const anchors=new AnchorRegistry;const sorted=sortedContracts(contracts);const ownership=renderOwnershipMatrix(sorted,anchors);const{lines:dependencyLines,hasDuplicates}=renderDependencyGraph(sorted,anchors);const{lines:lifecycleLines,hasLifecycle}=renderLifecycleReport(sorted,anchors,options.expiringWithinDays,options.generatedAt);const header=renderHeader(sorted,options,anchors,ownership.length>0,hasDuplicates,hasLifecycle);const catalog=renderCatalog(sorted,anchors,options.undocumentedContracts);const security=renderSecurityReview(sorted,options,options.generatedAt);return[...header,...catalog,...ownership,...dependencyLines,...lifecycleLines,...security].join("\n").replace(/\n{3,}/g,"\n\n")}function normalizeDocsForComparison(content){return content.replace(/^_Generated .+_$/m,"_Generated <normalized-for-comparison>_").replace(/\(\*\*\d+d remaining\*\*\)/g,"(**Nd remaining**)").replace(/\(\*\*expired \d+d ago\*\*\)/g,"(**expired Nd ago**)").replace(/^(- Already expired:) \d+$/gm,"$1 N").replace(/^(- Expiring within \d+ days:) \d+$/gm,"$1 N")}import path13 from"path";function isPrimitiveLiteral(value){return typeof value==="string"||typeof value==="number"||typeof value==="boolean"}function defaultLiteralFor(variable){return variable.defaultValue?.ok===true&&isPrimitiveLiteral(variable.defaultValue.value)?String(variable.defaultValue.value):void 0}function renderVariableLines(variable,options={}){const lines=[];if(options.note)lines.push(`# ${options.note}`);if(variable.description)lines.push(`# ${variable.description}`);if(variable.context)lines.push(`# Validation context: ${variable.context}`);if(variable.owner)lines.push(`# Owner: ${variable.owner}`);if(variable.setupInstructions)lines.push(`# Setup: ${variable.setupInstructions}`);if(variable.expiresAt)lines.push(`# Expires At: ${variable.expiresAt}`);if(variable.refreshInstructions)lines.push(`# Refresh Instructions: ${variable.refreshInstructions}`);if(variable.required)lines.push(`# Required: yes`);if(variable.purpose)lines.push(`# Purpose: ${variable.purpose}`);if(variable.legalBasis)lines.push(`# Legal Basis: ${variable.legalBasis}`);if(variable.retention)lines.push(`# Retention Policy: ${variable.retention}`);if(variable.dataResidency)lines.push(`# Data Residency: ${renderMetadataValue(variable.dataResidency)}`);if(variable.auditRequired)lines.push(`# Audit Required: yes`);for(const[metaKey,metaValue]of Object.entries(variable.metadata??{})){lines.push(`# ${humanizeKey(metaKey)}: ${renderMetadataValue(metaValue)}`)}const prefix=options.commented?"# ":"";lines.push(`${prefix}${variable.key}=${defaultLiteralFor(variable)??""}`,"");return lines}function groupByKey(contracts){const byKey=new Map;for(const contract of contracts){for(const variable of contract.variables){const list=byKey.get(variable.key)??[];list.push({contract,variable});byKey.set(variable.key,list)}}return byKey}function renderEnvExample(contracts,reconciliationHeader=[]){const sorted=[...contracts].sort((a,b)=>a.file.localeCompare(b.file));const active=sorted.filter(c=>c.active);const inactive=sorted.filter(c=>!c.active);const lines=["# AUTO-GENERATED EXAMPLE FILE.","# Copy to .env and fill in real values. Do not commit .env."];if(sorted.some(c=>c.variables.some(v=>v.context))){lines.push("# Validation context annotations describe when validation participates.","# They do not restrict access to values, and every variable below is","# still written to this file regardless of its context.")}if(reconciliationHeader.length>0)lines.push(...reconciliationHeader);lines.push("");const activeByKey=groupByKey(active);for(const key of[...activeByKey.keys()].sort()){const[first,...rest]=mustGet(activeByKey,key);lines.push(...renderVariableLines(first.variable));for(const dup of rest){lines.push(...renderVariableLines(dup.variable,{commented:true,note:`Also declared by "${dup.contract.contractName}" -- see "${first.contract.contractName}" above.`}))}}const inactiveByKey=groupByKey(inactive);for(const key of[...inactiveByKey.keys()].sort()){if(activeByKey.has(key))continue;const[first]=mustGet(inactiveByKey,key);lines.push(...renderVariableLines(first.variable,{commented:true,note:`Disabled -- feature "${first.contract.contractName}" is not active.`}))}return lines.join("\n").replace(/\n+$/,"\n")}function extractDeclaredVariables(source){const names=[];for(const line of source.split("\n")){const trimmed=line.trim();if(!trimmed||trimmed.startsWith("#"))continue;const match=/^([A-Za-z_][A-Za-z0-9_]*)=/.exec(trimmed);if(match?.[1])names.push(match[1])}return names}function extractCommentedVariables(source){const names=[];for(const line of source.split("\n")){const trimmed=line.trim();if(!trimmed.startsWith("#"))continue;const withoutHash=trimmed.replace(/^#+\s*/,"");const match=/^([A-Za-z_][A-Za-z0-9_]*)=/.exec(withoutHash);if(match?.[1])names.push(match[1])}return names}function computeReconciliation(contracts,existingContent){const activeKeys=new Set(contracts.filter(c=>c.active).flatMap(c=>c.variables.map(v=>v.key)));const knownKeys=new Set(contracts.flatMap(c=>c.variables.map(v=>v.key)));const oldLive=new Set(extractDeclaredVariables(existingContent));const oldKnown=new Set([...oldLive,...extractCommentedVariables(existingContent)]);const staleVariables=[...oldKnown].filter(key=>!knownKeys.has(key)).sort();const variablesToComment=[...oldLive].filter(key=>!activeKeys.has(key)&&knownKeys.has(key)).sort();const variablesToAdd=[...activeKeys].filter(key=>!oldLive.has(key)).sort();return{staleVariables,variablesToComment,variablesToAdd}}function renderReconciliationHeader(reconciliation){const lines=[];if(reconciliation.staleVariables.length>0){lines.push("#","# Remove the following variables (no longer declared by any feature):");for(const key of reconciliation.staleVariables)lines.push(`# - ${key}`)}if(reconciliation.variablesToComment.length>0){lines.push("#","# Comment the following variables (their feature is no longer active):");for(const key of reconciliation.variablesToComment)lines.push(`# - ${key}`)}if(reconciliation.variablesToAdd.length>0){lines.push("#","# Add the following variables (required by the current configuration):");for(const key of reconciliation.variablesToAdd)lines.push(`# - ${key}`)}return lines}async function writeEnvExample(contracts,location,fs2,options={}){const onExisting=options.onExisting??"keep-sibling";let existingContent;try{existingContent=await fs2.readFile(location,"utf8")}catch{}if(existingContent===void 0){const content2=renderEnvExample(contracts,[]);await fs2.mkdir(path13.dirname(location),{recursive:true});await fs2.writeFile(location,content2,"utf8");return{writtenPath:location,skippedExistingPath:void 0,staleVariables:[],variablesToComment:[],variablesToAdd:[]}}const reconciliation=computeReconciliation(contracts,existingContent);const{staleVariables,variablesToComment,variablesToAdd}=reconciliation;if(onExisting==="skip"){return{writtenPath:void 0,skippedExistingPath:location,staleVariables,variablesToComment,variablesToAdd}}const headerLines=onExisting==="keep-sibling"?renderReconciliationHeader(reconciliation):[];const content=renderEnvExample(contracts,headerLines);const writtenPath=onExisting==="overwrite"?location:`${location}.${Date.now()}`;const skippedExistingPath=onExisting==="overwrite"?void 0:location;await fs2.mkdir(path13.dirname(writtenPath),{recursive:true});await fs2.writeFile(writtenPath,content,"utf8");return{writtenPath,skippedExistingPath,staleVariables,variablesToComment,variablesToAdd}}function collectVariableNames(contracts){const names=new Set;for(const contract of contracts){for(const variable of contract.variables)names.add(variable.key)}return[...names]}function overrideVariable(variable,overrides){const override=overrides[variable.key];const date=override===void 0?void 0:parseIsoDate(override);return{...variable,expiresAt:date?override:variable.expiresAt,metadata:variable.metadata?{...variable.metadata}:variable.metadata}}function applyLiveExpirationOverrides(contracts,overrides){return contracts.map(contract=>({...contract,metadata:contract.metadata?{...contract.metadata}:contract.metadata,variables:contract.variables.map(variable=>overrideVariable(variable,overrides))}))}async function resolveLiveExpirationDates(contracts,liveExpirationDates){if(!liveExpirationDates)return contracts;const overrides=await liveExpirationDates(collectVariableNames(contracts));return deepFreeze(applyLiveExpirationOverrides(contracts,overrides))}var DEFAULT_EXPIRING_WITHIN_DAYS=30;var STANDARD_SENSITIVITY_LEVELS=new Set(["secret","credential","pii","config"]);function findNonstandardSensitivityLevels(contracts){const entries=[];for(const contract of contracts){if(contract.sensitivity!==void 0&&!STANDARD_SENSITIVITY_LEVELS.has(contract.sensitivity))entries.push({file:contract.file,exportName:contract.exportName,key:void 0,sensitivity:contract.sensitivity});for(const variable of[...contract.variables].sort((a,b)=>a.key.localeCompare(b.key))){if(variable.sensitivity===void 0)continue;if(STANDARD_SENSITIVITY_LEVELS.has(variable.sensitivity))continue;entries.push({file:contract.file,exportName:contract.exportName,key:variable.key,sensitivity:variable.sensitivity})}}return entries}function relativizeRef(root,ref){return{...ref,file:displayPath(root,ref.file)}}function computeDocumentation(root,linkResult,expiringWithinDays,generatedAt){const{contracts}=linkResult;return{contractSummaries:contracts.map(contract=>summarizeContract(contract,root)),catalog:buildCatalog(contracts),documentation:{undocumentedContracts:linkResult.undocumentedContracts,undocumentedVariables:linkResult.undocumentedVariables,staleDocEntries:linkResult.staleDocEntries,expiringSoon:computeExpiringEntries(contracts,expiringWithinDays,generatedAt),nonstandardSensitivityLevels:findNonstandardSensitivityLevels(contracts),unresolvedLinks:linkResult.unresolvedLinks},contractModelContracts:buildContractModel(contracts,root).contracts}}async function writeDocumentation(docsPath,envExamplePath,root,contracts,contractModelContracts,documentation,expiringWithinDays,generatedAt,fs2,envExampleOnExisting){let previousContent;try{previousContent=await fs2.readFile(docsPath,"utf8")}catch{}const docsSource=renderDocs(contractModelContracts,{expiringWithinDays,undocumentedContracts:documentation.undocumentedContracts.map(ref=>relativizeRef(root,ref)),undocumentedVariables:documentation.undocumentedVariables.map(ref=>relativizeRef(root,ref)),generatedAt,previousContent});await fs2.mkdir(path14.dirname(docsPath),{recursive:true});await fs2.writeFile(docsPath,docsSource,"utf8");const envExample=envExamplePath?await writeEnvExample(contracts,envExamplePath,fs2,{onExisting:envExampleOnExisting}):void 0;return{envExample}}var LIFECYCLE_MODEL_SCHEMA_VERSION=2;function hasLifecycleData(variable){return variable.expiresAt!==void 0||variable.refreshInstructions!==void 0||variable.deprecated!==void 0||variable.removeBy!==void 0||variable.renamedFrom!==void 0||variable.retention!==void 0}function buildLifecycleModel(contracts,expiringWithinDays,now,root){const modelContracts=[];for(const contract of contracts){const variables=[...contract.variables].filter(hasLifecycleData).sort((a,b)=>a.key.localeCompare(b.key)).map(variable=>({key:variable.key,expiresAt:variable.expiresAt,refreshInstructions:variable.refreshInstructions,deprecated:variable.deprecated,deprecatedReason:variable.deprecatedReason,removeBy:variable.removeBy,renamedFrom:variable.renamedFrom,retention:variable.retention}));const hasContractLevelData=contract.expiresAt!==void 0||contract.deprecated!==void 0||contract.deprecatedReason!==void 0||contract.retention!==void 0;if(!hasContractLevelData&&variables.length===0)continue;modelContracts.push({file:displayPath(root,contract.file),exportName:contract.exportName,contractName:contract.contractName,expiresAt:contract.expiresAt,deprecated:contract.deprecated,deprecatedReason:contract.deprecatedReason,retention:contract.retention,variables})}modelContracts.sort(byContractIdentity);return{schemaVersion:LIFECYCLE_MODEL_SCHEMA_VERSION,contracts:modelContracts,expiring:computeExpiringEntries(contracts,expiringWithinDays,now).map(entry=>({...entry,file:displayPath(root,entry.file)}))}}var OWNERSHIP_MODEL_SCHEMA_VERSION=1;function buildOwnershipModel(contracts,root){const modelContracts=contracts.map(contract=>({file:displayPath(root,contract.file),exportName:contract.exportName,contractName:contract.contractName,owner:contract.owner,variables:[...contract.variables].sort((a,b)=>a.key.localeCompare(b.key)).map(variable=>({key:variable.key,owner:effectiveOwner(contract,variable)}))}));modelContracts.sort(byContractIdentity);const unownedContracts=modelContracts.filter(c=>c.owner===void 0).map(({file,exportName})=>({file,exportName}));const unownedVariables=[];for(const contract of modelContracts){for(const variable of contract.variables){if(variable.owner===void 0){unownedVariables.push({file:contract.file,exportName:contract.exportName,key:variable.key})}}}return{schemaVersion:OWNERSHIP_MODEL_SCHEMA_VERSION,contracts:modelContracts,unownedContracts,unownedVariables}}function readToolVersion(){return"0.3.0"}async function computeSourceFingerprint(options){const{fs:fs2,root,include,exclude,packages}=options;const localSchemaFiles=await discoverSchemaFiles({fs:fs2,root,include,exclude});const packageCache=new Map;const{files:packageFiles,origins}=await resolveAllowlistedPackages(packages,root,packageCache,fs2);const schemaFiles=await mergeLocalAndPackageFiles(localSchemaFiles,packageFiles.map(f=>f.file),fs2);const{scanFiles}=await computeScanSurface(root,exclude,origins,fs2);const allFiles=[...new Set([...schemaFiles,...scanFiles])].sort();const hash=crypto2.createHash("sha256");hash.update(readToolVersion());for(const file of allFiles){hash.update(displayPath(root,file));try{hash.update(await fs2.readFile(file,"utf8"))}catch{hash.update("(unreadable)")}}return hash.digest("hex")}function fingerprintPathFor(evidencePath){return`${evidencePath}.fingerprint`}async function writeEvidenceFingerprint(evidencePath,fingerprint,fs2){await fs2.writeFile(fingerprintPathFor(evidencePath),`${fingerprint}
|
|
11
11
|
`,"utf8")}function escalatedFindings(findings,family){return findings.filter(f=>f.family===family&&f.severity==="warning").map(f=>({severity:"error",variable:findingSubject(f),files:findingFiles(f),reason:`[${f.code}] ${f.message}`}))}function findingSubject(finding){const location=finding.location;if(location.model==="change")return`(artifact) ${location.path}`;if(location.variable!==void 0)return location.variable;const name=location.model==="ownership"?location.contractName:location.exportName;return`(contract) ${name??"unknown"}`}function findingFiles(finding){const location=finding.location;if(location.model==="change")return[location.path];return location.file===void 0?[]:[location.file]}async function computeArtifacts(options){const root=path16.resolve(options.root??process.cwd());const include=options.include??defaultInclude();const exclude=options.exclude??defaultExclude();const packages=options.packages??[];const manifestOptions=options.manifest===false?void 0:options.manifest;const docsOptions=options.docs===false?void 0:options.docs;const usageOptions=options.usage===false?void 0:options.usage;const evidenceOptions=options.evidence===false?void 0:options.evidence;const pathIssues=[];let manifestOutputPath;if(manifestOptions){const result=resolveWithinRoot(root,manifestOptions.location,"manifest.location","generateEnvArtifacts");if(result.ok)manifestOutputPath=result.resolved;else pathIssues.push(result.issue)}let docsPath;let envExamplePath;if(docsOptions){const result=resolveWithinRoot(root,docsOptions.location,"docs.location","generateEnvArtifacts");if(result.ok)docsPath=result.resolved;else pathIssues.push(result.issue);if(docsOptions.envExample){const exampleResult=resolveWithinRoot(root,docsOptions.envExample.location,"docs.envExample.location","generateEnvArtifacts");if(exampleResult.ok)envExamplePath=exampleResult.resolved;else pathIssues.push(exampleResult.issue)}}let usageReportPath;if(usageOptions?.report){const result=resolveWithinRoot(root,usageOptions.report.location,"usage.report.location","generateEnvArtifacts");if(result.ok)usageReportPath=result.resolved;else pathIssues.push(result.issue)}let evidencePath;if(evidenceOptions){const result=resolveWithinRoot(root,evidenceOptions.location,"evidence.location","generateEnvArtifacts");if(result.ok)evidencePath=result.resolved;else pathIssues.push(result.issue)}if(pathIssues.length>0)throw new EnvProjectGenerationError(pathIssues);const{readFileCached,linkResult,context,origins,packageWarnings,tsconfigWarnings}=await assembleProject({fs:options.fs,root,include,exclude,packages,tsconfig:options.tsconfig});const generatedAt=new Date;const activeContracts=linkResult.contracts.filter(c=>c.active);const liveExpirationContracts=options.liveExpirationDates?await resolveLiveExpirationDates(linkResult.contracts,options.liveExpirationDates):linkResult.contracts;const docsContracts=docsOptions?liveExpirationContracts:linkResult.contracts;const blocking=[];const manifestComputed=manifestOptions?computeManifest(root,linkResult,manifestOptions.onIncompatibility??"warn"):void 0;if(manifestComputed)blocking.push(...manifestComputed.blocking);const docsComputed=computeDocumentation(root,{...linkResult,contracts:liveExpirationContracts},docsOptions?.expiringWithinDays??DEFAULT_EXPIRING_WITHIN_DAYS,generatedAt);const{scanFiles,scannedSurfaces}=await computeScanSurface(root,exclude,origins,options.fs);const contract=buildContractModel(linkResult.contracts,root);const evidenceChanges=evidencePath?await computeEvidenceChanges(root,evidencePath,activeContracts,contract.contracts,readFileCached,options.fs):void 0;const usageComputed=await computeUsage(root,linkResult.contracts,scanFiles,readFileCached,context,[...packageWarnings,...tsconfigWarnings,...linkResult.warnings],scannedSurfaces,evidenceChanges?.dynamicAccessAcknowledgments);const ownership=buildOwnershipModel(linkResult.contracts,root);const lifecycle=buildLifecycleModel(liveExpirationContracts,docsOptions?.expiringWithinDays??DEFAULT_EXPIRING_WITHIN_DAYS,generatedAt,root);const change=buildChangeModel(evidenceChanges?.report??{addedContracts:[],removedContracts:[],addedVariables:[],removedVariables:[],updatedContracts:[],updatedVariables:[]},linkResult.contracts,root);const dependency=await buildDependencyModel(linkResult.contracts,scanFiles,readFileCached,context,root,scannedSurfaces,evidenceChanges?.dynamicAccessAcknowledgments);const finding=buildFindingModel({root,compatibilityIssues:[...detectCompatibilityIssues(activeContracts),...detectDuplicateVariableShapes(activeContracts)],exclusiveGroupIssues:detectExclusiveGroupIssues(activeContracts),documentation:docsComputed.documentation,abandonedContracts:usageComputed.result.abandonedContracts,unresolvedConsumers:usageComputed.result.unresolvedConsumers,unconsumedOwnedVariables:usageComputed.result.unconsumedOwnedVariables,indeterminateOwnership:usageComputed.result.indeterminate,dynamicAccessCitationProblems:evidenceChanges?.dynamicAccessCitationProblems??[]});if(options.onUndocumented==="throw")blocking.push(...escalatedFindings(finding.findings,"documentation"));if(options.onOwnershipIssue==="throw")blocking.push(...escalatedFindings(finding.findings,"ownership"));const evidence=deepFreeze({schemaVersion:EVIDENCE_MODEL_SCHEMA_VERSION,provenance:{generatedAt:generatedAt.toISOString(),toolVersion:readToolVersion(),commit:void 0},contract,dependency,ownership,lifecycle,finding,change});return{root,manifestOptions,manifestOutputPath,manifestComputed,docsOptions,docsPath,envExamplePath,docsComputed,docsContracts,usageOptions,usageReportPath,usageComputed,evidencePath,evidence,blocking,packageWarnings:[...packageWarnings,...tsconfigWarnings],linkWarnings:linkResult.warnings,generatedAt}}async function generateEnvArtifacts(options){const computed=await computeArtifacts(options);const{manifestOptions,manifestOutputPath,manifestComputed,docsOptions,docsPath,envExamplePath,docsComputed,docsContracts,usageOptions,usageReportPath,usageComputed,evidencePath,evidence,blocking,packageWarnings,linkWarnings,root,generatedAt}=computed;if(blocking.length>0)throw new EnvProjectGenerationError(blocking);let manifestResult;if(manifestOptions&&manifestComputed&&manifestOutputPath){await writeManifest(manifestOutputPath,manifestComputed.activeContracts,options.fs);manifestResult={outputPath:manifestOutputPath,contracts:manifestComputed.contractSummaries,warnings:manifestComputed.warnings,parseWarnings:[...packageWarnings,...linkWarnings]}}let docsResult;if(docsOptions&&docsPath){const{envExample}=await writeDocumentation(docsPath,envExamplePath,root,docsContracts,docsComputed.contractModelContracts,docsComputed.documentation,docsOptions.expiringWithinDays??DEFAULT_EXPIRING_WITHIN_DAYS,generatedAt,options.fs,docsOptions.envExample?.onExisting);docsResult={docsPath,envExample,contracts:docsComputed.contractSummaries,catalog:docsComputed.catalog,parseWarnings:[...packageWarnings,...linkWarnings],documentation:docsComputed.documentation}}let usageResult;if(usageOptions){if(usageReportPath)await writeUsageReport(usageReportPath,usageComputed.result,options.fs);usageResult={reportPath:usageReportPath,...usageComputed.result}}if(evidencePath){await writeEvidenceSnapshot(evidencePath,evidence,options.fs);const fingerprint=await computeSourceFingerprint({fs:options.fs,root,include:options.include??defaultInclude(),exclude:options.exclude??defaultExclude(),packages:options.packages??[]});await writeEvidenceFingerprint(evidencePath,fingerprint,options.fs)}return{manifest:manifestResult,docs:docsResult,usage:usageResult,evidence}}async function readIfExists(filePath,fs2){try{return await fs2.readFile(filePath,"utf8")}catch{return void 0}}function normalizeEvidenceJsonForComparison(text){try{const parsed=JSON.parse(text);return JSON.stringify(normalizeEvidenceSnapshotForComparison(parsed),null,2)}catch{return text}}async function compareTextArtifact(artifact,filePath,expected,fs2,normalize=s=>s){const actual=await readIfExists(filePath,fs2);if(actual===void 0)return{artifact,path:filePath,status:"missing",detail:"not yet generated"};if(normalize(actual)===normalize(expected))return{artifact,path:filePath,status:"ok"};return{artifact,path:filePath,status:"stale",detail:"generated content differs from what's committed"}}async function checkEnvArtifacts(options){const c=await computeArtifacts(options);if(c.blocking.length>0)throw new EnvProjectGenerationError(c.blocking);const findings=[];if(c.manifestOptions&&c.manifestComputed&&c.manifestOutputPath){const expected=renderManifest(c.manifestComputed.activeContracts,c.manifestOutputPath);findings.push(await compareTextArtifact("manifest",c.manifestOutputPath,expected,options.fs))}if(c.docsOptions&&c.docsPath){const previousContent=await readIfExists(c.docsPath,options.fs);const expected=renderDocs(c.docsComputed.contractModelContracts,{expiringWithinDays:c.docsOptions.expiringWithinDays??DEFAULT_EXPIRING_WITHIN_DAYS,undocumentedContracts:c.docsComputed.documentation.undocumentedContracts.map(ref=>relativizeRef(c.root,ref)),undocumentedVariables:c.docsComputed.documentation.undocumentedVariables.map(ref=>relativizeRef(c.root,ref)),generatedAt:c.generatedAt,previousContent});findings.push(await compareTextArtifact("docs",c.docsPath,expected,options.fs,normalizeDocsForComparison));if(c.docsOptions.envExample&&c.envExamplePath){const existing=await readIfExists(c.envExamplePath,options.fs);if(existing===void 0){findings.push({artifact:"envExample",path:c.envExamplePath,status:"missing",detail:"not yet generated"})}else{const{staleVariables,variablesToComment,variablesToAdd}=computeReconciliation(c.docsContracts,existing);const driftCount=staleVariables.length+variablesToComment.length+variablesToAdd.length;findings.push({artifact:"envExample",path:c.envExamplePath,status:driftCount>0?"stale":"ok",detail:driftCount>0?`${staleVariables.length} stale, ${variablesToComment.length} to comment, ${variablesToAdd.length} to add`:void 0})}}}if(c.usageOptions&&c.usageReportPath){const expected=renderUsageReport(c.usageComputed.result);findings.push(await compareTextArtifact("usage",c.usageReportPath,expected,options.fs))}if(c.evidencePath){const expected=JSON.stringify(c.evidence,null,2);findings.push(await compareTextArtifact("evidence",c.evidencePath,expected,options.fs,normalizeEvidenceJsonForComparison))}return{ok:findings.every(f=>f.status==="ok"),findings}}import fs from"fs/promises";function readFile(path18,encoding){return fs.readFile(path18,encoding)}function writeFile(path18,data,encoding){return fs.writeFile(path18,data,encoding)}async function mkdir(path18,options){await fs.mkdir(path18,options)}function readdir(path18,options){return fs.readdir(path18,options)}function stat(path18){return fs.stat(path18)}function realpath(path18){return fs.realpath(path18)}var nodeBuildFileSystem={readFile,writeFile,mkdir,readdir,stat,realpath};import{existsSync,mkdirSync,readFileSync,statSync,writeFileSync}from"fs";import path17 from"path";var USAGE=`Usage: env-cap init
|
|
12
12
|
|
|
13
13
|
Scaffolds a minimal env-cap starting point into the current directory:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maverickcer/env-cap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Secure, type-safe, framework-agnostic environment contracts. Contracts own the environment variables they consume; the package provides runtime validation and build-time contract discovery.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"environment",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/MaverickCER/env-cap.git"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://github.com/
|
|
22
|
+
"homepage": "https://github.com/MaverickCER/env-cap#readme",
|
|
23
23
|
"bugs": {
|
|
24
|
-
"url": "https://github.com/
|
|
24
|
+
"url": "https://github.com/MaverickCER/env-cap/issues"
|
|
25
25
|
},
|
|
26
26
|
"author": "MaverickCER",
|
|
27
27
|
"type": "module",
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import type { DiscoveredContract } from "./link.js";
|
|
2
|
-
import type { DiscoveredClassification, ParseWarning } from "./parse.js";
|
|
3
|
-
import type { DynamicAccessAssertion, SourcePosition } from "./source-position.js";
|
|
4
|
-
/**
|
|
5
|
-
* The manifest's own `.ts` output is deliberately metadata-free (imports +
|
|
6
|
-
* an array, see `manifest.ts`'s docstring) -- there is nothing in it for a
|
|
7
|
-
* later `generateEnvManifest()` run to read back and diff against. This
|
|
8
|
-
* module is the persisted, committed sidecar that makes "what changed in
|
|
9
|
-
* `documentEnv()` since last time" answerable at all. See ADR 0021.
|
|
10
|
-
*/
|
|
11
|
-
/** Bump only when a reader could misinterpret the new shape (a field changes
|
|
12
|
-
* type/meaning, or is removed) -- NOT for every additive field. Same
|
|
13
|
-
* discipline `src/cli/json.ts`'s `JSON_SCHEMA_VERSION` already documents;
|
|
14
|
-
* this format is meant to stay backwards-compatible across ordinary
|
|
15
|
-
* releases. */
|
|
16
|
-
export declare const MANIFEST_SNAPSHOT_SCHEMA_VERSION = 2;
|
|
17
|
-
export interface ManifestSnapshotVariable {
|
|
18
|
-
readonly key: string;
|
|
19
|
-
readonly description: string | undefined;
|
|
20
|
-
readonly owner: string | undefined;
|
|
21
|
-
readonly classification: DiscoveredClassification | undefined;
|
|
22
|
-
readonly expiresAt: string | undefined;
|
|
23
|
-
readonly refreshInstructions: string | undefined;
|
|
24
|
-
readonly setupInstructions: string | undefined;
|
|
25
|
-
readonly required: boolean | undefined;
|
|
26
|
-
readonly deprecated: boolean | undefined;
|
|
27
|
-
readonly deprecatedReason: string | undefined;
|
|
28
|
-
readonly removeBy: string | undefined;
|
|
29
|
-
readonly renamedFrom: string | undefined;
|
|
30
|
-
readonly purpose: string | undefined;
|
|
31
|
-
readonly legalBasis: string | undefined;
|
|
32
|
-
readonly retentionPolicy: string | undefined;
|
|
33
|
-
readonly dataResidency: string | readonly string[] | undefined;
|
|
34
|
-
readonly auditRequired: boolean | undefined;
|
|
35
|
-
readonly metadata: Readonly<Record<string, unknown>> | undefined;
|
|
36
|
-
readonly documented: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* One entry per {@link runtime.VariableDocs.dynamicAccess} citation whose
|
|
39
|
-
* cited file existed at snapshot-build time, each carrying a SHA-256 hex
|
|
40
|
-
* digest of that file's full content at that moment -- the committed
|
|
41
|
-
* baseline a later run's citation is compared against to tell "fresh" from
|
|
42
|
-
* "stale" (ADR 0037). A citation whose file doesn't exist at build time is
|
|
43
|
-
* simply omitted here, not recorded as an error -- `computeManifestChanges()`'s
|
|
44
|
-
* own live existence check is what reports a missing file, every run,
|
|
45
|
-
* independent of this stored baseline. `buildManifestSnapshot()` always
|
|
46
|
-
* populates this (as `[]` at minimum); typed `| undefined` because it's
|
|
47
|
-
* purely additive (no `MANIFEST_SNAPSHOT_SCHEMA_VERSION` bump), so a real
|
|
48
|
-
* snapshot committed before this field existed reads back as "ok" while
|
|
49
|
-
* genuinely lacking the key -- every reader must treat it as optional.
|
|
50
|
-
*/
|
|
51
|
-
readonly dynamicAccessSnapshots: readonly (SourcePosition & {
|
|
52
|
-
readonly acknowledgedContentHash: string;
|
|
53
|
-
})[] | undefined;
|
|
54
|
-
}
|
|
55
|
-
export interface ManifestSnapshotContract {
|
|
56
|
-
/** Root-relative, POSIX-separated -- matches `DiscoveredContractSummary.file`. */
|
|
57
|
-
readonly file: string;
|
|
58
|
-
readonly exportName: string;
|
|
59
|
-
readonly contractName: string;
|
|
60
|
-
readonly active: boolean;
|
|
61
|
-
readonly category: string | undefined;
|
|
62
|
-
readonly exclusiveGroup: string | undefined;
|
|
63
|
-
readonly owner: string | undefined;
|
|
64
|
-
readonly classification: DiscoveredClassification | undefined;
|
|
65
|
-
readonly expiresAt: string | undefined;
|
|
66
|
-
readonly deprecated: boolean | undefined;
|
|
67
|
-
readonly deprecatedReason: string | undefined;
|
|
68
|
-
readonly purpose: string | undefined;
|
|
69
|
-
readonly legalBasis: string | undefined;
|
|
70
|
-
readonly retentionPolicy: string | undefined;
|
|
71
|
-
readonly dataResidency: string | readonly string[] | undefined;
|
|
72
|
-
readonly auditRequired: boolean | undefined;
|
|
73
|
-
readonly metadata: Readonly<Record<string, unknown>> | undefined;
|
|
74
|
-
readonly variables: readonly ManifestSnapshotVariable[];
|
|
75
|
-
}
|
|
76
|
-
export interface ManifestSnapshot {
|
|
77
|
-
readonly schemaVersion: typeof MANIFEST_SNAPSHOT_SCHEMA_VERSION;
|
|
78
|
-
readonly contracts: readonly ManifestSnapshotContract[];
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Sorted deterministically (by file, then exportName, then variable key) so
|
|
82
|
-
* `JSON.stringify` output is stable and diffs cleanly in PRs. Scoped to
|
|
83
|
-
* **active** contracts only, matching `renderManifest()`'s own scope -- a
|
|
84
|
-
* contract flipping to `active: false` disappears from `manifest.ts` itself,
|
|
85
|
-
* so it disappears from this snapshot too.
|
|
86
|
-
*/
|
|
87
|
-
export declare function buildManifestSnapshot(activeContracts: readonly DiscoveredContract[], root: string, readFile: (filePath: string) => Promise<string>): Promise<ManifestSnapshot>;
|
|
88
|
-
/** Derives the sidecar snapshot path from a manifest's own output path (e.g. `src/generated/env.manifest.ts` -> `src/generated/env.manifest.snapshot.json`). */
|
|
89
|
-
export declare function manifestSnapshotPath(manifestOutputPath: string): string;
|
|
90
|
-
/**
|
|
91
|
-
* Distinguishes *why* there's no usable previous snapshot, rather than
|
|
92
|
-
* collapsing every case into a bare `undefined`. A missing file is the
|
|
93
|
-
* normal, silent, expected first-run case. An unparseable file or a
|
|
94
|
-
* `schemaVersion` this build doesn't recognize are both real, diagnosable
|
|
95
|
-
* problems -- `computeManifestChanges()` below surfaces those two (never
|
|
96
|
-
* `"missing"`) as a `ParseWarning` instead of silently re-baselining.
|
|
97
|
-
*/
|
|
98
|
-
export type ManifestSnapshotReadResult = {
|
|
99
|
-
readonly status: "missing";
|
|
100
|
-
} | {
|
|
101
|
-
readonly status: "invalid-json";
|
|
102
|
-
readonly detail: string;
|
|
103
|
-
} | {
|
|
104
|
-
readonly status: "unsupported-version";
|
|
105
|
-
readonly foundVersion: unknown;
|
|
106
|
-
} | {
|
|
107
|
-
readonly status: "ok";
|
|
108
|
-
readonly snapshot: ManifestSnapshot;
|
|
109
|
-
};
|
|
110
|
-
export declare function readManifestSnapshot(snapshotPath: string): Promise<ManifestSnapshotReadResult>;
|
|
111
|
-
export declare function writeManifestSnapshot(snapshotPath: string, snapshot: ManifestSnapshot): Promise<void>;
|
|
112
|
-
/** One field's before/after value in a {@link ManifestContractUpdate} or {@link ManifestVariableUpdate}. */
|
|
113
|
-
export interface ManifestFieldChange {
|
|
114
|
-
/** The changed field's name (dotted, e.g. `metadata.runbook`, for a per-key record change). */
|
|
115
|
-
readonly field: string;
|
|
116
|
-
/** The value from the previous snapshot, or `undefined` if the field was unset. */
|
|
117
|
-
readonly previous: string | undefined;
|
|
118
|
-
/** The value in the current run, or `undefined` if the field is now unset. */
|
|
119
|
-
readonly current: string | undefined;
|
|
120
|
-
}
|
|
121
|
-
/** Identifies one contract for {@link ManifestChangeReport} purposes. */
|
|
122
|
-
export interface ManifestContractRef {
|
|
123
|
-
/** Stable identity: `${file}#${exportName}`. */
|
|
124
|
-
readonly identity: string;
|
|
125
|
-
/** Root-relative, POSIX-separated file path. */
|
|
126
|
-
readonly file: string;
|
|
127
|
-
/** The binding name the `createEnv()` result is exported as. */
|
|
128
|
-
readonly exportName: string;
|
|
129
|
-
/** Resolved display name (see {@link DiscoveredContract.contractName}). */
|
|
130
|
-
readonly contractName: string;
|
|
131
|
-
}
|
|
132
|
-
/** Identifies one variable for {@link ManifestChangeReport} purposes. */
|
|
133
|
-
export interface ManifestVariableRef {
|
|
134
|
-
/** Stable identity: `${contractIdentity}#${key}`. */
|
|
135
|
-
readonly identity: string;
|
|
136
|
-
/** The owning contract's {@link ManifestContractRef.identity}. */
|
|
137
|
-
readonly contractIdentity: string;
|
|
138
|
-
/** Root-relative, POSIX-separated file path of the owning contract. */
|
|
139
|
-
readonly file: string;
|
|
140
|
-
/** The owning contract's exported binding name. */
|
|
141
|
-
readonly exportName: string;
|
|
142
|
-
/** The owning contract's resolved display name. */
|
|
143
|
-
readonly contractName: string;
|
|
144
|
-
/** The environment variable name. */
|
|
145
|
-
readonly key: string;
|
|
146
|
-
}
|
|
147
|
-
/** A contract present in both snapshots, with at least one changed field. */
|
|
148
|
-
export interface ManifestContractUpdate extends ManifestContractRef {
|
|
149
|
-
/** Every field that changed between the previous and current snapshot. */
|
|
150
|
-
readonly changes: readonly ManifestFieldChange[];
|
|
151
|
-
}
|
|
152
|
-
/** A variable present in both snapshots, with at least one changed field. */
|
|
153
|
-
export interface ManifestVariableUpdate extends ManifestVariableRef {
|
|
154
|
-
/** Every field that changed between the previous and current snapshot. */
|
|
155
|
-
readonly changes: readonly ManifestFieldChange[];
|
|
156
|
-
}
|
|
157
|
-
/** The result of diffing two manifest snapshots -- see `result.manifest.changes` (ADR 0021). */
|
|
158
|
-
export interface ManifestChangeReport {
|
|
159
|
-
/** Contracts present now but not in the previous snapshot. */
|
|
160
|
-
readonly addedContracts: readonly ManifestContractRef[];
|
|
161
|
-
/** Contracts present in the previous snapshot but not now. */
|
|
162
|
-
readonly removedContracts: readonly ManifestContractRef[];
|
|
163
|
-
/** Variables present now but not in the previous snapshot. */
|
|
164
|
-
readonly addedVariables: readonly ManifestVariableRef[];
|
|
165
|
-
/** Variables present in the previous snapshot but not now. */
|
|
166
|
-
readonly removedVariables: readonly ManifestVariableRef[];
|
|
167
|
-
/** Contracts present in both snapshots with at least one changed field. */
|
|
168
|
-
readonly updatedContracts: readonly ManifestContractUpdate[];
|
|
169
|
-
/** Variables present in both snapshots with at least one changed field. */
|
|
170
|
-
readonly updatedVariables: readonly ManifestVariableUpdate[];
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Pure. `previous === undefined` (no snapshot last time, for any reason --
|
|
174
|
-
* see `ManifestSnapshotReadResult`) reports everything in `current` as
|
|
175
|
-
* added, nothing as removed/updated.
|
|
176
|
-
*/
|
|
177
|
-
export declare function diffManifestSnapshots(previous: ManifestSnapshot | undefined, current: ManifestSnapshot): ManifestChangeReport;
|
|
178
|
-
/** Stable key `dynamicAccessAcknowledgments` (below) and `dependency-graph.ts`'s consumer of it agree on -- `file` must be root-relative, POSIX-separated on both sides for a lookup to ever hit. */
|
|
179
|
-
export declare function dynamicAccessVariableIdentity(file: string, exportName: string, key: string): string;
|
|
180
|
-
/**
|
|
181
|
-
* Re-checks every current `dynamicAccess` citation against the previous
|
|
182
|
-
* snapshot's committed baseline, keyed by `${file}#${exportName}#${key}`
|
|
183
|
-
* (matching `ManifestVariableRef.identity`'s own scheme). Existence is
|
|
184
|
-
* always re-checked live (never trusted from either snapshot); content drift
|
|
185
|
-
* is only detectable when a previous baseline exists at all -- a citation
|
|
186
|
-
* with no previous baseline (first time it's ever been seen) is reported
|
|
187
|
-
* `"fresh"`, matching the same "nothing to contradict yet" logic every other
|
|
188
|
-
* committed-baseline field in this module already follows. See ADR 0037.
|
|
189
|
-
*/
|
|
190
|
-
export declare function computeDynamicAccessAcknowledgments(activeContracts: readonly DiscoveredContract[], previous: ManifestSnapshot | undefined, root: string, readFile: (filePath: string) => Promise<string>): Promise<ReadonlyMap<string, readonly DynamicAccessAssertion[]>>;
|
|
191
|
-
/** One `dynamicAccess` citation env-cap can no longer vouch for -- the direct input to `finding-model.ts`'s `"dynamic-access-citation-stale"`/`"dynamic-access-citation-missing"` findings. See ADR 0037. */
|
|
192
|
-
export interface DynamicAccessCitationProblem {
|
|
193
|
-
readonly contractName: string;
|
|
194
|
-
/** Root-relative, POSIX-separated. */
|
|
195
|
-
readonly file: string;
|
|
196
|
-
readonly exportName: string;
|
|
197
|
-
/** The variable whose `dynamicAccess` citation this is. */
|
|
198
|
-
readonly key: string;
|
|
199
|
-
/** Where the citation points -- not the variable's own declaration. */
|
|
200
|
-
readonly position: SourcePosition;
|
|
201
|
-
readonly acknowledgment: "stale" | "missing";
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Flattens `dynamicAccessAcknowledgments` down to just the citations that
|
|
205
|
-
* need attention -- every `"stale"`/`"missing"` assertion, enriched with the
|
|
206
|
-
* owning contract's display name (the acknowledgments map itself only keys
|
|
207
|
-
* by identity, not a renderable name). `"fresh"` assertions are silently
|
|
208
|
-
* omitted -- nothing to report about a citation that's still trustworthy.
|
|
209
|
-
*/
|
|
210
|
-
export declare function findDynamicAccessCitationProblems(activeContracts: readonly DiscoveredContract[], root: string, acknowledgments: ReadonlyMap<string, readonly DynamicAccessAssertion[]>): readonly DynamicAccessCitationProblem[];
|
|
211
|
-
export interface ManifestChangesComputation {
|
|
212
|
-
readonly report: ManifestChangeReport;
|
|
213
|
-
readonly snapshot: ManifestSnapshot;
|
|
214
|
-
/** Set only when the previous snapshot existed but was unusable (corrupt JSON or an unrecognized `schemaVersion`) -- never set for the ordinary "no snapshot yet" first-run case. */
|
|
215
|
-
readonly readWarning: ParseWarning | undefined;
|
|
216
|
-
/** Every current `dynamicAccess` citation's freshness, keyed by `${file}#${exportName}#${key}` -- see `computeDynamicAccessAcknowledgments()`. See ADR 0037. */
|
|
217
|
-
readonly dynamicAccessAcknowledgments: ReadonlyMap<string, readonly DynamicAccessAssertion[]>;
|
|
218
|
-
/** Every citation that isn't `"fresh"` -- see `findDynamicAccessCitationProblems()`. Derived from `dynamicAccessAcknowledgments` above for convenience; a caller could equally recompute it. */
|
|
219
|
-
readonly dynamicAccessCitationProblems: readonly DynamicAccessCitationProblem[];
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* The one I/O-performing helper both `generateEnvManifest()` and
|
|
223
|
-
* `generate-env-artifacts.ts`'s `computeArtifacts()` share: builds the
|
|
224
|
-
* current snapshot, reads the previous one, diffs. Writing the new snapshot
|
|
225
|
-
* stays a separate, explicit step on the real write path -- this never
|
|
226
|
-
* writes anything itself, so it's safe to call from a read-only compute too.
|
|
227
|
-
*/
|
|
228
|
-
export declare function computeManifestChanges(root: string, manifestOutputPath: string, activeContracts: readonly DiscoveredContract[], readFile: (filePath: string) => Promise<string>): Promise<ManifestChangesComputation>;
|
|
229
|
-
//# sourceMappingURL=manifest-snapshot.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-snapshot.d.ts","sourceRoot":"","sources":["../../../src/build/manifest-snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { type PackageSchemaResolutionResult } from "./resolve-package-schema.js";
|
|
2
|
-
import { type AliasResolutionCache, type TsconfigPathsResolution } from "./resolve-tsconfig-paths.js";
|
|
3
|
-
/**
|
|
4
|
-
* Resolves a relative import specifier (as written in source: `"./schema.js"`,
|
|
5
|
-
* matching this codebase's own convention of `.js`-suffixed relative imports
|
|
6
|
-
* pointing at `.ts` source files) to an absolute file path, relative to the
|
|
7
|
-
* file that contains the import.
|
|
8
|
-
*
|
|
9
|
-
* @remarks
|
|
10
|
-
* Deliberately narrow: only handles a direct relative specifier resolving to
|
|
11
|
-
* a real `.ts`/`.tsx` file on disk. Bare/package specifiers, namespace
|
|
12
|
-
* imports, and anything requiring real module resolution (re-export chains,
|
|
13
|
-
* `exports` map lookups, etc.) return `undefined` -- the caller treats that
|
|
14
|
-
* as "couldn't statically link" and warns rather than guesses, the same
|
|
15
|
-
* philosophy `evaluateLiteral` already uses for non-literal expressions.
|
|
16
|
-
*
|
|
17
|
-
* @returns The resolved absolute path, or `undefined` when the specifier isn't relative or doesn't resolve to a real file.
|
|
18
|
-
*/
|
|
19
|
-
export declare function resolveRelativeImport(importingFile: string, specifier: string): Promise<string | undefined>;
|
|
20
|
-
/** Shared inputs threaded through every call to {@link resolveImportSpecifier} for one discovery/link run. */
|
|
21
|
-
export interface ImportResolutionContext {
|
|
22
|
-
/** Absolute path of the project root, used to resolve package specifiers. */
|
|
23
|
-
readonly root: string;
|
|
24
|
-
/** Explicit allowlist -- see ADR 0014. Empty/omitted means package resolution never fires, identical to today's behavior. */
|
|
25
|
-
readonly packages: readonly string[];
|
|
26
|
-
/** Memoizes package resolution per specifier across the whole run -- see `resolvePackageImport`'s own doc comment for why. */
|
|
27
|
-
readonly cache: Map<string, Promise<PackageSchemaResolutionResult>>;
|
|
28
|
-
/** Parsed `tsconfig.json` `paths`/`baseUrl`, or `undefined` when alias resolution found nothing to do or was disabled -- see ADR 0023. */
|
|
29
|
-
readonly tsconfigPaths: TsconfigPathsResolution | undefined;
|
|
30
|
-
/** Memoizes alias resolution across the whole run -- see `resolveAliasImport`'s own doc comment for why. Always allocated, even when `tsconfigPaths` is `undefined`, mirroring `cache` above. */
|
|
31
|
-
readonly aliasCache: AliasResolutionCache;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The one specifier-resolution entry point `link.ts` and `dependency-graph.ts`
|
|
35
|
-
* call, instead of each duplicating a
|
|
36
|
-
* `(await resolveRelativeImport(...)) ?? resolveAliasImport(...) ?? (await resolvePackageImport(...))`
|
|
37
|
-
* chain at every call site.
|
|
38
|
-
*
|
|
39
|
-
* @remarks
|
|
40
|
-
* Purely a composition point -- no new resolution logic lives here. Tries, in order: the
|
|
41
|
-
* relative resolver (cheap, no `fs.stat` beyond the local filesystem check it already
|
|
42
|
-
* does); tsconfig path-alias resolution (ADR 0023, on by default) for a bare specifier
|
|
43
|
-
* matching the project's own `tsconfig.json` `paths`/`baseUrl`; then package resolution
|
|
44
|
-
* (ADR 0014) for a bare specifier matching an allow-listed package name. Alias resolution
|
|
45
|
-
* runs before package resolution because it resolves the consuming project's own local
|
|
46
|
-
* source (already-trusted, no versioning boundary), the same precedence relative
|
|
47
|
-
* resolution already has over package resolution.
|
|
48
|
-
*/
|
|
49
|
-
export declare function resolveImportSpecifier(importingFile: string, specifier: string, context: ImportResolutionContext): Promise<string | undefined>;
|
|
50
|
-
//# sourceMappingURL=resolve-import.d.ts.map
|