norn-cli 2.2.2 → 2.4.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/.claude/settings.local.json +18 -0
- package/.claude/skills/norn-social-campaign/SKILL.md +70 -0
- package/CHANGELOG.md +22 -1
- package/LICENSE +20 -29
- package/README.md +32 -1
- package/demos/nornenv-region-refactor/README.md +64 -0
- package/demos/nornenv-showcase/README.md +62 -0
- package/demos/nornenv-showcase/norn.config.json +16 -0
- package/demos/nornenv-showcase/showcase.norn +70 -0
- package/demos/nornenv-showcase/showcase.nornapi +26 -0
- package/demos/nornenv-showcase/showcase.nornsql +20 -0
- package/dist/cli.js +564 -54
- package/out/apiResponseIntellisenseCache.js +394 -0
- package/out/assertionRunner.js +567 -0
- package/out/cacheDir.js +136 -0
- package/out/chatParticipant.js +763 -0
- package/out/cli/colors.js +127 -0
- package/out/cli/formatters/assertion.js +102 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +246 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +689 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +226 -0
- package/out/codeLensProvider.js +351 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +3739 -0
- package/out/contractAssertionSummary.js +225 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +879 -0
- package/out/coveragePanel.js +597 -0
- package/out/debug/breakpointResolver.js +84 -0
- package/out/debug/breakpoints.js +52 -0
- package/out/debug/nornDebugAdapter.js +166 -0
- package/out/debug/nornDebugSession.js +613 -0
- package/out/debug/sequenceLocationIndex.js +77 -0
- package/out/debug/types.js +3 -0
- package/out/deepClone.js +21 -0
- package/out/diagnosticProvider.js +2554 -0
- package/out/environmentParser.js +736 -0
- package/out/environmentProvider.js +544 -0
- package/out/environmentTemplates.js +146 -0
- package/out/errors/formatError.js +113 -0
- package/out/errors/nornError.js +29 -0
- package/out/formUrlEncoded.js +89 -0
- package/out/httpClient.js +348 -0
- package/out/httpRuntimeOptions.js +16 -0
- package/out/importErrors.js +31 -0
- package/out/inlayHintResolver.js +70 -0
- package/out/jsonFileReader.js +323 -0
- package/out/mcpClient.js +193 -0
- package/out/mcpConfig.js +184 -0
- package/out/mcpToolIntellisenseCache.js +96 -0
- package/out/mcpToolSchema.js +50 -0
- package/out/nornConfig.js +132 -0
- package/out/nornHoverProvider.js +124 -0
- package/out/nornInlayHintsProvider.js +191 -0
- package/out/nornPrompt.js +755 -0
- package/out/nornSqlParser.js +286 -0
- package/out/nornapiHoverProvider.js +135 -0
- package/out/nornapiInlayHintsProvider.js +94 -0
- package/out/nornapiParser.js +324 -0
- package/out/nornenvCodeActionProvider.js +101 -0
- package/out/nornenvDecorationProvider.js +239 -0
- package/out/nornenvFoldingProvider.js +63 -0
- package/out/nornenvHoverProvider.js +114 -0
- package/out/nornenvInlayHintsProvider.js +99 -0
- package/out/nornenvLanguageModel.js +187 -0
- package/out/nornenvRegionRefactor.js +267 -0
- package/out/nornsqlHoverProvider.js +95 -0
- package/out/nornsqlInlayHintsProvider.js +114 -0
- package/out/parser.js +839 -0
- package/out/pathAccess.js +28 -0
- package/out/postmanImportPanel.js +732 -0
- package/out/postmanImportPlanner.js +1155 -0
- package/out/postmanImportSidebarView.js +532 -0
- package/out/quotedString.js +35 -0
- package/out/requestPreparation.js +179 -0
- package/out/requestValidation.js +146 -0
- package/out/responsePanel.js +7754 -0
- package/out/schemaGenerator.js +562 -0
- package/out/scriptRunner.js +419 -0
- package/out/secrets/cliSecrets.js +415 -0
- package/out/secrets/crypto.js +105 -0
- package/out/secrets/envFileSecrets.js +177 -0
- package/out/secrets/keyStore.js +259 -0
- package/out/sequenceDeclaration.js +15 -0
- package/out/sequenceRunner.js +3590 -0
- package/out/sqlAdapterRunner.js +122 -0
- package/out/sqlBuiltInAdapters.js +604 -0
- package/out/sqlConfig.js +184 -0
- package/out/starterCatalog.js +554 -0
- package/out/stringUtils.js +25 -0
- package/out/swaggerBodyIntellisenseCache.js +114 -0
- package/out/swaggerParser.js +464 -0
- package/out/testProvider.js +767 -0
- package/out/theoryCaseLoader.js +113 -0
- package/out/validationCache.js +211 -0
- package/package.json +38 -11
- package/.kanbn/index.md +0 -31
- package/.kanbn/tasks/book-first-mentor-session.md +0 -13
- package/.kanbn/tasks/decide-what-success-in-a-pilot-looks-like.md +0 -9
- package/.kanbn/tasks/do-5-customer-conversations.md +0 -9
- package/.kanbn/tasks/finalise-the-one-line-pitch.md +0 -11
- package/.kanbn/tasks/interview-script.md +0 -49
- package/.kanbn/tasks/make-a-list-of-10-people-to-speak-to.md +0 -11
- package/.kanbn/tasks/prepare-your-customer-interview-questions.md +0 -11
- package/.kanbn/tasks/recruit-2/342/200/2233-pilot-users.md +0 -9
- package/.kanbn/tasks/refine-your-pitch.md +0 -9
- package/.kanbn/tasks/use-the-shiplight-website-as-a-template-to-improve-norn-website.md +0 -9
- package/.kanbn/tasks/write-down-repeated-wording.md +0 -9
- package/.kanbn/tasks/write-the-one-pager.md +0 -27
package/dist/cli.js
CHANGED
|
@@ -127575,12 +127575,20 @@ function stringifyRequestValue(value) {
|
|
|
127575
127575
|
}
|
|
127576
127576
|
return String(value);
|
|
127577
127577
|
}
|
|
127578
|
+
function shouldResolveBareRequestValue(value, variables) {
|
|
127579
|
+
if (!Object.prototype.hasOwnProperty.call(variables, value)) {
|
|
127580
|
+
return false;
|
|
127581
|
+
}
|
|
127582
|
+
const envScope = variables["$env"];
|
|
127583
|
+
const isEnvOnlyValue = envScope && typeof envScope === "object" && Object.prototype.hasOwnProperty.call(envScope, value) && variables[value] === envScope[value];
|
|
127584
|
+
return !isEnvOnlyValue;
|
|
127585
|
+
}
|
|
127578
127586
|
function resolveRequestValueExpression(value, variables) {
|
|
127579
|
-
if (
|
|
127587
|
+
if (shouldResolveBareRequestValue(value, variables)) {
|
|
127580
127588
|
return stringifyRequestValue(variables[value]);
|
|
127581
127589
|
}
|
|
127582
127590
|
const pathMatch2 = value.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(\.\w[\w-]*(?:\.\w[\w-]*|\[\d+\])*|\[\d+\](?:\.\w[\w-]*|\[\d+\])*)$/);
|
|
127583
|
-
if (pathMatch2 &&
|
|
127591
|
+
if (pathMatch2 && shouldResolveBareRequestValue(pathMatch2[1], variables)) {
|
|
127584
127592
|
const nestedValue = getNestedPathValue(variables[pathMatch2[1]], pathMatch2[2].replace(/^\./, ""));
|
|
127585
127593
|
if (nestedValue !== void 0) {
|
|
127586
127594
|
return stringifyRequestValue(nestedValue);
|
|
@@ -127832,7 +127840,7 @@ function resolveEnvironmentTemplateValue(variableName, variables, secretNames =
|
|
|
127832
127840
|
kind: "scope",
|
|
127833
127841
|
variableName: name,
|
|
127834
127842
|
reference: referenceName,
|
|
127835
|
-
message: `.nornenv template reference '${referenceName}' is
|
|
127843
|
+
message: `.nornenv template reference '${referenceName}' is not in scope. Values inside [env:...] sections can reference common variables and inherited ancestor variables only.`
|
|
127836
127844
|
});
|
|
127837
127845
|
}
|
|
127838
127846
|
if (resolved.secret) {
|
|
@@ -132675,24 +132683,58 @@ function listCachedSecretKeyIds(targetPath) {
|
|
|
132675
132683
|
// src/environmentParser.ts
|
|
132676
132684
|
var ENV_FILENAME = ".nornenv";
|
|
132677
132685
|
var importRegex = /^import\s+["']?(.+?)["']?\s*$/;
|
|
132678
|
-
var
|
|
132686
|
+
var sectionHeaderRegex = /^\[(env|template):([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s+extends\s+([^\]]+?))?\]\s*$/;
|
|
132687
|
+
var identifierRegex = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
|
|
132679
132688
|
var varRegex = /^var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
|
|
132680
132689
|
var connectionStringRegex = /^connectionString\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
|
|
132681
132690
|
var secretRegex = /^secret\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
|
|
132682
132691
|
var secretConnectionStringRegex = /^secret\s+connectionString\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$/;
|
|
132692
|
+
function parseExtendsClause(clause) {
|
|
132693
|
+
if (!clause) {
|
|
132694
|
+
return [];
|
|
132695
|
+
}
|
|
132696
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132697
|
+
const names = [];
|
|
132698
|
+
for (const raw of clause.split(",")) {
|
|
132699
|
+
const name = raw.trim();
|
|
132700
|
+
if (!name || !identifierRegex.test(name) || seen.has(name)) {
|
|
132701
|
+
continue;
|
|
132702
|
+
}
|
|
132703
|
+
seen.add(name);
|
|
132704
|
+
names.push(name);
|
|
132705
|
+
}
|
|
132706
|
+
return names;
|
|
132707
|
+
}
|
|
132683
132708
|
function parseEnvFile(content, sourceFilePath) {
|
|
132684
132709
|
const lines = content.split("\n");
|
|
132685
132710
|
const config2 = {
|
|
132686
132711
|
common: {},
|
|
132687
132712
|
environments: [],
|
|
132713
|
+
templates: [],
|
|
132688
132714
|
secretNames: /* @__PURE__ */ new Set(),
|
|
132689
132715
|
secretValues: /* @__PURE__ */ new Map(),
|
|
132690
132716
|
imports: [],
|
|
132691
132717
|
misplacedImports: [],
|
|
132692
132718
|
secretDeclarations: []
|
|
132693
132719
|
};
|
|
132694
|
-
let
|
|
132720
|
+
let currentSection = null;
|
|
132721
|
+
let currentKind = null;
|
|
132695
132722
|
let seenContent = false;
|
|
132723
|
+
const assignVariable = (varName, varValue) => {
|
|
132724
|
+
if (currentSection) {
|
|
132725
|
+
currentSection.variables[varName] = varValue;
|
|
132726
|
+
} else {
|
|
132727
|
+
config2.common[varName] = varValue;
|
|
132728
|
+
}
|
|
132729
|
+
};
|
|
132730
|
+
const buildSecretDeclaration = (varName, varValue, lineNumber) => ({
|
|
132731
|
+
name: varName,
|
|
132732
|
+
value: varValue,
|
|
132733
|
+
envName: currentKind === "env" ? currentSection?.name : void 0,
|
|
132734
|
+
templateName: currentKind === "template" ? currentSection?.name : void 0,
|
|
132735
|
+
lineNumber,
|
|
132736
|
+
filePath: sourceFilePath
|
|
132737
|
+
});
|
|
132696
132738
|
for (let i = 0; i < lines.length; i++) {
|
|
132697
132739
|
const trimmed = lines[i].trim();
|
|
132698
132740
|
if (!trimmed || trimmed.startsWith("#")) {
|
|
@@ -132711,13 +132753,21 @@ function parseEnvFile(content, sourceFilePath) {
|
|
|
132711
132753
|
continue;
|
|
132712
132754
|
}
|
|
132713
132755
|
seenContent = true;
|
|
132714
|
-
const
|
|
132715
|
-
if (
|
|
132716
|
-
|
|
132717
|
-
|
|
132718
|
-
|
|
132719
|
-
|
|
132720
|
-
|
|
132756
|
+
const sectionMatch = trimmed.match(sectionHeaderRegex);
|
|
132757
|
+
if (sectionMatch) {
|
|
132758
|
+
const kind = sectionMatch[1];
|
|
132759
|
+
const name = sectionMatch[2];
|
|
132760
|
+
const parents = parseExtendsClause(sectionMatch[3]);
|
|
132761
|
+
if (kind === "env") {
|
|
132762
|
+
const env3 = { name, variables: {}, parents };
|
|
132763
|
+
config2.environments.push(env3);
|
|
132764
|
+
currentSection = env3;
|
|
132765
|
+
} else {
|
|
132766
|
+
const template = { name, variables: {}, parents };
|
|
132767
|
+
config2.templates.push(template);
|
|
132768
|
+
currentSection = template;
|
|
132769
|
+
}
|
|
132770
|
+
currentKind = kind;
|
|
132721
132771
|
continue;
|
|
132722
132772
|
}
|
|
132723
132773
|
const secretConnectionStringMatch = trimmed.match(secretConnectionStringRegex);
|
|
@@ -132725,40 +132775,20 @@ function parseEnvFile(content, sourceFilePath) {
|
|
|
132725
132775
|
const profileName = secretConnectionStringMatch[1];
|
|
132726
132776
|
const varName = `${profileName}_connectionString`;
|
|
132727
132777
|
const varValue = secretConnectionStringMatch[2].trim();
|
|
132728
|
-
config2.secretDeclarations.push(
|
|
132729
|
-
name: varName,
|
|
132730
|
-
value: varValue,
|
|
132731
|
-
envName: currentEnv?.name,
|
|
132732
|
-
lineNumber: i,
|
|
132733
|
-
filePath: sourceFilePath
|
|
132734
|
-
});
|
|
132778
|
+
config2.secretDeclarations.push(buildSecretDeclaration(varName, varValue, i));
|
|
132735
132779
|
config2.secretNames.add(varName);
|
|
132736
132780
|
config2.secretValues.set(varName, varValue);
|
|
132737
|
-
|
|
132738
|
-
currentEnv.variables[varName] = varValue;
|
|
132739
|
-
} else {
|
|
132740
|
-
config2.common[varName] = varValue;
|
|
132741
|
-
}
|
|
132781
|
+
assignVariable(varName, varValue);
|
|
132742
132782
|
continue;
|
|
132743
132783
|
}
|
|
132744
132784
|
const secretMatch = trimmed.match(secretRegex);
|
|
132745
132785
|
if (secretMatch) {
|
|
132746
132786
|
const varName = secretMatch[1];
|
|
132747
132787
|
const varValue = secretMatch[2].trim();
|
|
132748
|
-
config2.secretDeclarations.push(
|
|
132749
|
-
name: varName,
|
|
132750
|
-
value: varValue,
|
|
132751
|
-
envName: currentEnv?.name,
|
|
132752
|
-
lineNumber: i,
|
|
132753
|
-
filePath: sourceFilePath
|
|
132754
|
-
});
|
|
132788
|
+
config2.secretDeclarations.push(buildSecretDeclaration(varName, varValue, i));
|
|
132755
132789
|
config2.secretNames.add(varName);
|
|
132756
132790
|
config2.secretValues.set(varName, varValue);
|
|
132757
|
-
|
|
132758
|
-
currentEnv.variables[varName] = varValue;
|
|
132759
|
-
} else {
|
|
132760
|
-
config2.common[varName] = varValue;
|
|
132761
|
-
}
|
|
132791
|
+
assignVariable(varName, varValue);
|
|
132762
132792
|
continue;
|
|
132763
132793
|
}
|
|
132764
132794
|
const connectionStringMatch = trimmed.match(connectionStringRegex);
|
|
@@ -132766,22 +132796,14 @@ function parseEnvFile(content, sourceFilePath) {
|
|
|
132766
132796
|
const profileName = connectionStringMatch[1];
|
|
132767
132797
|
const varName = `${profileName}_connectionString`;
|
|
132768
132798
|
const varValue = connectionStringMatch[2].trim();
|
|
132769
|
-
|
|
132770
|
-
currentEnv.variables[varName] = varValue;
|
|
132771
|
-
} else {
|
|
132772
|
-
config2.common[varName] = varValue;
|
|
132773
|
-
}
|
|
132799
|
+
assignVariable(varName, varValue);
|
|
132774
132800
|
continue;
|
|
132775
132801
|
}
|
|
132776
132802
|
const varMatch = trimmed.match(varRegex);
|
|
132777
132803
|
if (varMatch) {
|
|
132778
132804
|
const varName = varMatch[1];
|
|
132779
132805
|
const varValue = varMatch[2].trim();
|
|
132780
|
-
|
|
132781
|
-
currentEnv.variables[varName] = varValue;
|
|
132782
|
-
} else {
|
|
132783
|
-
config2.common[varName] = varValue;
|
|
132784
|
-
}
|
|
132806
|
+
assignVariable(varName, varValue);
|
|
132785
132807
|
}
|
|
132786
132808
|
}
|
|
132787
132809
|
return config2;
|
|
@@ -132910,6 +132932,11 @@ function registerVariableOrigins(config2, filePath, origins) {
|
|
|
132910
132932
|
origins.set(`env:${env3.name}:${varName}`, { filePath, line: -1, varName });
|
|
132911
132933
|
}
|
|
132912
132934
|
}
|
|
132935
|
+
for (const template of config2.templates) {
|
|
132936
|
+
for (const varName of Object.keys(template.variables)) {
|
|
132937
|
+
origins.set(`template:${template.name}:${varName}`, { filePath, line: -1, varName });
|
|
132938
|
+
}
|
|
132939
|
+
}
|
|
132913
132940
|
}
|
|
132914
132941
|
function mergeConfigs(target, source, targetFilePath, sourceFilePath, variableOrigins, errors) {
|
|
132915
132942
|
for (const [varName, varValue] of Object.entries(source.common)) {
|
|
@@ -132932,8 +132959,14 @@ function mergeConfigs(target, source, targetFilePath, sourceFilePath, variableOr
|
|
|
132932
132959
|
for (const sourceEnv of source.environments) {
|
|
132933
132960
|
let targetEnv = target.environments.find((e) => e.name === sourceEnv.name);
|
|
132934
132961
|
if (!targetEnv) {
|
|
132935
|
-
targetEnv = { name: sourceEnv.name, variables: {} };
|
|
132962
|
+
targetEnv = { name: sourceEnv.name, variables: {}, parents: [...sourceEnv.parents] };
|
|
132936
132963
|
target.environments.push(targetEnv);
|
|
132964
|
+
} else {
|
|
132965
|
+
for (const parent of sourceEnv.parents) {
|
|
132966
|
+
if (!targetEnv.parents.includes(parent)) {
|
|
132967
|
+
targetEnv.parents.push(parent);
|
|
132968
|
+
}
|
|
132969
|
+
}
|
|
132937
132970
|
}
|
|
132938
132971
|
for (const [varName, varValue] of Object.entries(sourceEnv.variables)) {
|
|
132939
132972
|
const key = `env:${sourceEnv.name}:${varName}`;
|
|
@@ -132952,6 +132985,35 @@ function mergeConfigs(target, source, targetFilePath, sourceFilePath, variableOr
|
|
|
132952
132985
|
}
|
|
132953
132986
|
}
|
|
132954
132987
|
}
|
|
132988
|
+
for (const sourceTemplate of source.templates) {
|
|
132989
|
+
let targetTemplate = target.templates.find((t) => t.name === sourceTemplate.name);
|
|
132990
|
+
if (!targetTemplate) {
|
|
132991
|
+
targetTemplate = { name: sourceTemplate.name, variables: {}, parents: [...sourceTemplate.parents] };
|
|
132992
|
+
target.templates.push(targetTemplate);
|
|
132993
|
+
} else {
|
|
132994
|
+
for (const parent of sourceTemplate.parents) {
|
|
132995
|
+
if (!targetTemplate.parents.includes(parent)) {
|
|
132996
|
+
targetTemplate.parents.push(parent);
|
|
132997
|
+
}
|
|
132998
|
+
}
|
|
132999
|
+
}
|
|
133000
|
+
for (const [varName, varValue] of Object.entries(sourceTemplate.variables)) {
|
|
133001
|
+
const key = `template:${sourceTemplate.name}:${varName}`;
|
|
133002
|
+
const existing = variableOrigins.get(key);
|
|
133003
|
+
if (existing) {
|
|
133004
|
+
const sourceLabel = toDisplayPath(sourceFilePath, targetFilePath);
|
|
133005
|
+
const existingLabel = toDisplayPath(existing.filePath, targetFilePath);
|
|
133006
|
+
errors.push({
|
|
133007
|
+
message: `Duplicate variable '${varName}' in [template:${sourceTemplate.name}] section. Found in '${existingLabel}' and '${sourceLabel}'.`,
|
|
133008
|
+
filePath: sourceFilePath,
|
|
133009
|
+
line: -1
|
|
133010
|
+
});
|
|
133011
|
+
} else {
|
|
133012
|
+
targetTemplate.variables[varName] = varValue;
|
|
133013
|
+
variableOrigins.set(key, { filePath: sourceFilePath, line: -1, varName });
|
|
133014
|
+
}
|
|
133015
|
+
}
|
|
133016
|
+
}
|
|
132955
133017
|
for (const name of source.secretNames) {
|
|
132956
133018
|
target.secretNames.add(name);
|
|
132957
133019
|
}
|
|
@@ -132983,6 +133045,90 @@ function loadAndResolveEnvFile(filePath) {
|
|
|
132983
133045
|
result.secretErrors.push(...resolveEncryptedSecretValues(result.config, filePath));
|
|
132984
133046
|
return result;
|
|
132985
133047
|
}
|
|
133048
|
+
function findExtendsNode(name, config2) {
|
|
133049
|
+
const env3 = config2.environments.find((e) => e.name === name);
|
|
133050
|
+
if (env3) {
|
|
133051
|
+
return { node: env3, kind: "env" };
|
|
133052
|
+
}
|
|
133053
|
+
const template = config2.templates.find((t) => t.name === name);
|
|
133054
|
+
if (template) {
|
|
133055
|
+
return { node: template, kind: "template" };
|
|
133056
|
+
}
|
|
133057
|
+
return void 0;
|
|
133058
|
+
}
|
|
133059
|
+
function findExtendsTemplate(name, config2) {
|
|
133060
|
+
const template = config2.templates.find((t) => t.name === name);
|
|
133061
|
+
return template ? { node: template, kind: "template" } : void 0;
|
|
133062
|
+
}
|
|
133063
|
+
function resolveEffectiveEnvVariables(envName, config2) {
|
|
133064
|
+
return Object.fromEntries(
|
|
133065
|
+
Array.from(resolveEffectiveEnvVariableDetails(envName, config2).entries()).map(([name, detail]) => [name, detail.value])
|
|
133066
|
+
);
|
|
133067
|
+
}
|
|
133068
|
+
function resolveInheritedVariableDetails(nodeName, config2) {
|
|
133069
|
+
const inherited = /* @__PURE__ */ new Map();
|
|
133070
|
+
const visited = /* @__PURE__ */ new Set();
|
|
133071
|
+
const stack = /* @__PURE__ */ new Set();
|
|
133072
|
+
const self2 = findExtendsNode(nodeName, config2);
|
|
133073
|
+
if (!self2) {
|
|
133074
|
+
return inherited;
|
|
133075
|
+
}
|
|
133076
|
+
const walk = (name) => {
|
|
133077
|
+
if (visited.has(name) || stack.has(name)) {
|
|
133078
|
+
return;
|
|
133079
|
+
}
|
|
133080
|
+
const found = findExtendsTemplate(name, config2);
|
|
133081
|
+
if (!found) {
|
|
133082
|
+
return;
|
|
133083
|
+
}
|
|
133084
|
+
stack.add(name);
|
|
133085
|
+
for (const parent of found.node.parents) {
|
|
133086
|
+
walk(parent);
|
|
133087
|
+
}
|
|
133088
|
+
for (const [varName, value] of Object.entries(found.node.variables)) {
|
|
133089
|
+
inherited.set(varName, {
|
|
133090
|
+
name: varName,
|
|
133091
|
+
value,
|
|
133092
|
+
sourceKind: found.kind,
|
|
133093
|
+
sourceName: found.node.name,
|
|
133094
|
+
inherited: true
|
|
133095
|
+
});
|
|
133096
|
+
}
|
|
133097
|
+
visited.add(name);
|
|
133098
|
+
stack.delete(name);
|
|
133099
|
+
};
|
|
133100
|
+
for (const parent of self2.node.parents) {
|
|
133101
|
+
walk(parent);
|
|
133102
|
+
}
|
|
133103
|
+
return inherited;
|
|
133104
|
+
}
|
|
133105
|
+
function resolveEffectiveEnvVariableDetails(envName, config2) {
|
|
133106
|
+
const details = /* @__PURE__ */ new Map();
|
|
133107
|
+
for (const [name, value] of Object.entries(config2.common)) {
|
|
133108
|
+
details.set(name, {
|
|
133109
|
+
name,
|
|
133110
|
+
value,
|
|
133111
|
+
sourceKind: "common",
|
|
133112
|
+
inherited: true
|
|
133113
|
+
});
|
|
133114
|
+
}
|
|
133115
|
+
for (const [name, detail] of resolveInheritedVariableDetails(envName, config2)) {
|
|
133116
|
+
details.set(name, detail);
|
|
133117
|
+
}
|
|
133118
|
+
const env3 = config2.environments.find((e) => e.name === envName);
|
|
133119
|
+
if (env3) {
|
|
133120
|
+
for (const [name, value] of Object.entries(env3.variables)) {
|
|
133121
|
+
details.set(name, {
|
|
133122
|
+
name,
|
|
133123
|
+
value,
|
|
133124
|
+
sourceKind: "env",
|
|
133125
|
+
sourceName: env3.name,
|
|
133126
|
+
inherited: false
|
|
133127
|
+
});
|
|
133128
|
+
}
|
|
133129
|
+
}
|
|
133130
|
+
return details;
|
|
133131
|
+
}
|
|
132986
133132
|
function resolveEncryptedSecretValues(config2, entryFilePath) {
|
|
132987
133133
|
const errors = [];
|
|
132988
133134
|
for (const declaration of config2.secretDeclarations) {
|
|
@@ -133035,6 +133181,11 @@ function resolveEncryptedSecretValues(config2, entryFilePath) {
|
|
|
133035
133181
|
if (env3) {
|
|
133036
133182
|
env3.variables[declaration.name] = plaintext;
|
|
133037
133183
|
}
|
|
133184
|
+
} else if (declaration.templateName) {
|
|
133185
|
+
const template = config2.templates.find((t) => t.name === declaration.templateName);
|
|
133186
|
+
if (template) {
|
|
133187
|
+
template.variables[declaration.name] = plaintext;
|
|
133188
|
+
}
|
|
133038
133189
|
} else {
|
|
133039
133190
|
config2.common[declaration.name] = plaintext;
|
|
133040
133191
|
}
|
|
@@ -133052,7 +133203,7 @@ var import_process = require("process");
|
|
|
133052
133203
|
// src/secrets/envFileSecrets.ts
|
|
133053
133204
|
var fs16 = __toESM(require("fs"));
|
|
133054
133205
|
var path16 = __toESM(require("path"));
|
|
133055
|
-
var
|
|
133206
|
+
var envRegex = /^\s*\[env:([a-zA-Z_][a-zA-Z0-9_-]*)\]\s*$/;
|
|
133056
133207
|
var secretConnectionStringRegex2 = /^(\s*secret\s+connectionString\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
|
|
133057
133208
|
var secretRegex2 = /^(\s*secret\s+)([a-zA-Z_][a-zA-Z0-9_]*)(\s*=\s*)(.+)$/;
|
|
133058
133209
|
function splitContentLines(content) {
|
|
@@ -133074,7 +133225,7 @@ function extractSecretLines(content, filePath) {
|
|
|
133074
133225
|
if (!trimmed || trimmed.startsWith("#")) {
|
|
133075
133226
|
continue;
|
|
133076
133227
|
}
|
|
133077
|
-
const envMatch = trimmed.match(
|
|
133228
|
+
const envMatch = trimmed.match(envRegex);
|
|
133078
133229
|
if (envMatch) {
|
|
133079
133230
|
currentEnv = envMatch[1];
|
|
133080
133231
|
continue;
|
|
@@ -133619,6 +133770,294 @@ function splitImportResolutionErrors(errors) {
|
|
|
133619
133770
|
return { blockingErrors, warningErrors };
|
|
133620
133771
|
}
|
|
133621
133772
|
|
|
133773
|
+
// src/nornenvLanguageModel.ts
|
|
133774
|
+
var SECTION_HEADER_REGEX = /^\s*\[(env|template):([a-zA-Z_][a-zA-Z0-9_-]*)(?:\s+extends\s+([^\]]+))?\]\s*$/i;
|
|
133775
|
+
function parseParentList(clause) {
|
|
133776
|
+
if (!clause) {
|
|
133777
|
+
return [];
|
|
133778
|
+
}
|
|
133779
|
+
return clause.split(",").map((parent) => parent.trim()).filter((parent) => /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(parent));
|
|
133780
|
+
}
|
|
133781
|
+
function parseNornenvDocumentModel(text) {
|
|
133782
|
+
const sections = [];
|
|
133783
|
+
const declarations = [];
|
|
133784
|
+
const lines = text.split("\n");
|
|
133785
|
+
let currentSection;
|
|
133786
|
+
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
|
|
133787
|
+
const line2 = lines[lineNumber];
|
|
133788
|
+
const sectionMatch = line2.trim().match(SECTION_HEADER_REGEX);
|
|
133789
|
+
if (sectionMatch) {
|
|
133790
|
+
currentSection = {
|
|
133791
|
+
kind: sectionMatch[1].toLowerCase(),
|
|
133792
|
+
name: sectionMatch[2],
|
|
133793
|
+
parents: parseParentList(sectionMatch[3]),
|
|
133794
|
+
lineNumber
|
|
133795
|
+
};
|
|
133796
|
+
sections.push(currentSection);
|
|
133797
|
+
continue;
|
|
133798
|
+
}
|
|
133799
|
+
const connectionMatch = line2.match(/^(\s*)(secret\s+)?connectionString\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/i);
|
|
133800
|
+
if (connectionMatch) {
|
|
133801
|
+
const displayName = connectionMatch[3];
|
|
133802
|
+
const name = `${displayName}_connectionString`;
|
|
133803
|
+
const nameStart = line2.indexOf(displayName);
|
|
133804
|
+
const value = connectionMatch[4];
|
|
133805
|
+
const valueStart = line2.length - value.length;
|
|
133806
|
+
declarations.push({
|
|
133807
|
+
name,
|
|
133808
|
+
displayName,
|
|
133809
|
+
value,
|
|
133810
|
+
secret: Boolean(connectionMatch[2]),
|
|
133811
|
+
sectionKind: currentSection?.kind,
|
|
133812
|
+
sectionName: currentSection?.name,
|
|
133813
|
+
lineNumber,
|
|
133814
|
+
nameStart,
|
|
133815
|
+
nameEnd: nameStart + displayName.length,
|
|
133816
|
+
valueStart,
|
|
133817
|
+
valueEnd: line2.length
|
|
133818
|
+
});
|
|
133819
|
+
continue;
|
|
133820
|
+
}
|
|
133821
|
+
const variableMatch = line2.match(/^(\s*)(secret|var)\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/i);
|
|
133822
|
+
if (variableMatch) {
|
|
133823
|
+
const name = variableMatch[3];
|
|
133824
|
+
const nameStart = line2.indexOf(name);
|
|
133825
|
+
const value = variableMatch[4];
|
|
133826
|
+
const valueStart = line2.length - value.length;
|
|
133827
|
+
declarations.push({
|
|
133828
|
+
name,
|
|
133829
|
+
displayName: name,
|
|
133830
|
+
value,
|
|
133831
|
+
secret: variableMatch[2].toLowerCase() === "secret",
|
|
133832
|
+
sectionKind: currentSection?.kind,
|
|
133833
|
+
sectionName: currentSection?.name,
|
|
133834
|
+
lineNumber,
|
|
133835
|
+
nameStart,
|
|
133836
|
+
nameEnd: nameStart + name.length,
|
|
133837
|
+
valueStart,
|
|
133838
|
+
valueEnd: line2.length
|
|
133839
|
+
});
|
|
133840
|
+
}
|
|
133841
|
+
}
|
|
133842
|
+
return { sections, declarations };
|
|
133843
|
+
}
|
|
133844
|
+
|
|
133845
|
+
// src/nornenvRegionRefactor.ts
|
|
133846
|
+
var MATRIX_ENV_NAME_REGEX = /^([a-zA-Z][a-zA-Z0-9-]*)_([a-zA-Z][a-zA-Z0-9-]*)$/;
|
|
133847
|
+
var MIN_STAGES = 2;
|
|
133848
|
+
var MIN_REGIONS = 2;
|
|
133849
|
+
var MIN_CELLS = 3;
|
|
133850
|
+
function isConnectionStringVar(name) {
|
|
133851
|
+
return name.endsWith("_connectionString");
|
|
133852
|
+
}
|
|
133853
|
+
function inferDeclarationKind(name) {
|
|
133854
|
+
return isConnectionStringVar(name) ? "connectionString" : "var";
|
|
133855
|
+
}
|
|
133856
|
+
function inferDisplayName(name) {
|
|
133857
|
+
return isConnectionStringVar(name) ? name.slice(0, -"_connectionString".length) : name;
|
|
133858
|
+
}
|
|
133859
|
+
function declarationKind(declaration) {
|
|
133860
|
+
return declaration.name.endsWith("_connectionString") && declaration.displayName !== declaration.name ? "connectionString" : "var";
|
|
133861
|
+
}
|
|
133862
|
+
function buildDeclarationMap(text) {
|
|
133863
|
+
const model = parseNornenvDocumentModel(text);
|
|
133864
|
+
const byEnv = /* @__PURE__ */ new Map();
|
|
133865
|
+
for (const declaration of model.declarations) {
|
|
133866
|
+
if (declaration.sectionKind !== "env" || !declaration.sectionName) {
|
|
133867
|
+
continue;
|
|
133868
|
+
}
|
|
133869
|
+
if (!byEnv.has(declaration.sectionName)) {
|
|
133870
|
+
byEnv.set(declaration.sectionName, /* @__PURE__ */ new Map());
|
|
133871
|
+
}
|
|
133872
|
+
byEnv.get(declaration.sectionName).set(declaration.name, declaration);
|
|
133873
|
+
}
|
|
133874
|
+
return byEnv;
|
|
133875
|
+
}
|
|
133876
|
+
function getCellValue(cell, name, declarationsByEnv, config2) {
|
|
133877
|
+
const value = cell.env.variables[name];
|
|
133878
|
+
if (value === void 0) {
|
|
133879
|
+
return void 0;
|
|
133880
|
+
}
|
|
133881
|
+
const declaration = declarationsByEnv.get(cell.envName)?.get(name);
|
|
133882
|
+
return {
|
|
133883
|
+
value,
|
|
133884
|
+
secret: declaration?.secret ?? config2.secretNames.has(name),
|
|
133885
|
+
kind: declaration ? declarationKind(declaration) : inferDeclarationKind(name),
|
|
133886
|
+
displayName: declaration?.displayName ?? inferDisplayName(name)
|
|
133887
|
+
};
|
|
133888
|
+
}
|
|
133889
|
+
function classifyByAxis(name, cells, axisKeys, getAxisKey, declarationsByEnv, config2) {
|
|
133890
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
133891
|
+
let presentCells = 0;
|
|
133892
|
+
for (const key of axisKeys) {
|
|
133893
|
+
const axisCells = cells.filter((cell) => getAxisKey(cell) === key);
|
|
133894
|
+
const presentValues = axisCells.map((cell) => getCellValue(cell, name, declarationsByEnv, config2)).filter((value) => value !== void 0);
|
|
133895
|
+
if (presentValues.length === 0) {
|
|
133896
|
+
continue;
|
|
133897
|
+
}
|
|
133898
|
+
presentCells += presentValues.length;
|
|
133899
|
+
if (presentValues.length !== axisCells.length) {
|
|
133900
|
+
return void 0;
|
|
133901
|
+
}
|
|
133902
|
+
const first = presentValues[0];
|
|
133903
|
+
if (presentValues.some((value) => value.value !== first.value)) {
|
|
133904
|
+
return void 0;
|
|
133905
|
+
}
|
|
133906
|
+
byKey.set(key, {
|
|
133907
|
+
value: first.value,
|
|
133908
|
+
secret: presentValues.some((value) => value.secret),
|
|
133909
|
+
kind: first.kind,
|
|
133910
|
+
displayName: first.displayName
|
|
133911
|
+
});
|
|
133912
|
+
}
|
|
133913
|
+
return byKey.size > 0 && presentCells >= 2 ? byKey : void 0;
|
|
133914
|
+
}
|
|
133915
|
+
function detectRegionPattern(config2, text) {
|
|
133916
|
+
const cells = [];
|
|
133917
|
+
for (const env3 of config2.environments) {
|
|
133918
|
+
const match = env3.name.match(MATRIX_ENV_NAME_REGEX);
|
|
133919
|
+
if (!match) {
|
|
133920
|
+
continue;
|
|
133921
|
+
}
|
|
133922
|
+
if (env3.parents.length > 0) {
|
|
133923
|
+
return void 0;
|
|
133924
|
+
}
|
|
133925
|
+
cells.push({ stage: match[1], region: match[2], envName: env3.name, env: env3 });
|
|
133926
|
+
}
|
|
133927
|
+
const stages = Array.from(new Set(cells.map((c) => c.stage)));
|
|
133928
|
+
const regions = Array.from(new Set(cells.map((c) => c.region)));
|
|
133929
|
+
if (stages.length < MIN_STAGES || regions.length < MIN_REGIONS || cells.length < MIN_CELLS) {
|
|
133930
|
+
return void 0;
|
|
133931
|
+
}
|
|
133932
|
+
if ((/* @__PURE__ */ new Set([...stages, ...regions])).size !== stages.length + regions.length) {
|
|
133933
|
+
return void 0;
|
|
133934
|
+
}
|
|
133935
|
+
const existingTemplates = new Set(config2.templates.map((template) => template.name));
|
|
133936
|
+
if ([...stages, ...regions].some((name) => existingTemplates.has(name))) {
|
|
133937
|
+
return void 0;
|
|
133938
|
+
}
|
|
133939
|
+
const allVarNames = /* @__PURE__ */ new Set();
|
|
133940
|
+
for (const cell of cells) {
|
|
133941
|
+
for (const name of Object.keys(cell.env.variables)) {
|
|
133942
|
+
allVarNames.add(name);
|
|
133943
|
+
}
|
|
133944
|
+
}
|
|
133945
|
+
let liftedToStage = 0;
|
|
133946
|
+
let liftedToRegion = 0;
|
|
133947
|
+
let leafSpecific = 0;
|
|
133948
|
+
let skippedConnectionStrings = 0;
|
|
133949
|
+
const assignments = [];
|
|
133950
|
+
const declarationsByEnv = buildDeclarationMap(text);
|
|
133951
|
+
for (const name of allVarNames) {
|
|
133952
|
+
if (isConnectionStringVar(name)) {
|
|
133953
|
+
skippedConnectionStrings++;
|
|
133954
|
+
}
|
|
133955
|
+
const stageValues = isConnectionStringVar(name) ? void 0 : classifyByAxis(name, cells, stages, (cell) => cell.stage, declarationsByEnv, config2);
|
|
133956
|
+
const regionValues = isConnectionStringVar(name) ? void 0 : classifyByAxis(name, cells, regions, (cell) => cell.region, declarationsByEnv, config2);
|
|
133957
|
+
if (stageValues) {
|
|
133958
|
+
assignments.push({ name, axis: "stage", byKey: stageValues });
|
|
133959
|
+
liftedToStage++;
|
|
133960
|
+
} else if (regionValues) {
|
|
133961
|
+
assignments.push({ name, axis: "region", byKey: regionValues });
|
|
133962
|
+
liftedToRegion++;
|
|
133963
|
+
} else {
|
|
133964
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
133965
|
+
for (const cell of cells) {
|
|
133966
|
+
const value = getCellValue(cell, name, declarationsByEnv, config2);
|
|
133967
|
+
if (value !== void 0) {
|
|
133968
|
+
byKey.set(cell.envName, value);
|
|
133969
|
+
}
|
|
133970
|
+
}
|
|
133971
|
+
if (byKey.size > 0) {
|
|
133972
|
+
assignments.push({ name, axis: "leaf", byKey });
|
|
133973
|
+
leafSpecific++;
|
|
133974
|
+
}
|
|
133975
|
+
}
|
|
133976
|
+
}
|
|
133977
|
+
const model = parseNornenvDocumentModel(text);
|
|
133978
|
+
const matrixNames = new Set(cells.map((c) => c.envName));
|
|
133979
|
+
const matrixSectionLines = model.sections.filter((s) => s.kind === "env" && matrixNames.has(s.name)).map((s) => s.lineNumber).sort((a, b) => a - b);
|
|
133980
|
+
if (matrixSectionLines.length === 0) {
|
|
133981
|
+
return void 0;
|
|
133982
|
+
}
|
|
133983
|
+
const firstLine = matrixSectionLines[0];
|
|
133984
|
+
const lastMatrixHeaderLine = matrixSectionLines[matrixSectionLines.length - 1];
|
|
133985
|
+
const sectionsInReplacementRange = model.sections.filter(
|
|
133986
|
+
(section) => section.lineNumber >= firstLine && section.lineNumber <= lastMatrixHeaderLine
|
|
133987
|
+
);
|
|
133988
|
+
if (sectionsInReplacementRange.some((section) => section.kind !== "env" || !matrixNames.has(section.name))) {
|
|
133989
|
+
return void 0;
|
|
133990
|
+
}
|
|
133991
|
+
const lineCount = text.split("\n").length;
|
|
133992
|
+
const sectionsAfter = model.sections.filter((s) => s.lineNumber > lastMatrixHeaderLine).map((s) => s.lineNumber).sort((a, b) => a - b);
|
|
133993
|
+
const lastLine = sectionsAfter.length > 0 ? sectionsAfter[0] - 1 : lineCount - 1;
|
|
133994
|
+
return {
|
|
133995
|
+
stages,
|
|
133996
|
+
regions,
|
|
133997
|
+
cells,
|
|
133998
|
+
assignments,
|
|
133999
|
+
replaceRange: { startLine: firstLine, endLine: lastLine },
|
|
134000
|
+
summary: { liftedToStage, liftedToRegion, leafSpecific, skippedConnectionStrings }
|
|
134001
|
+
};
|
|
134002
|
+
}
|
|
134003
|
+
function generateRegionRefactor(pattern) {
|
|
134004
|
+
const blocks = [];
|
|
134005
|
+
const fmtVar = (assignment, value) => {
|
|
134006
|
+
if (value.kind === "connectionString") {
|
|
134007
|
+
const secretPrefix = value.secret ? "secret " : "";
|
|
134008
|
+
return ` ${secretPrefix}connectionString ${value.displayName} = ${value.value}`;
|
|
134009
|
+
}
|
|
134010
|
+
const keyword = value.secret ? "secret" : "var";
|
|
134011
|
+
return ` ${keyword} ${assignment.name} = ${value.value}`;
|
|
134012
|
+
};
|
|
134013
|
+
for (const stage of pattern.stages) {
|
|
134014
|
+
const stageVars = pattern.assignments.filter((a) => a.axis === "stage" && a.byKey.has(stage));
|
|
134015
|
+
if (stageVars.length === 0) {
|
|
134016
|
+
continue;
|
|
134017
|
+
}
|
|
134018
|
+
const lines = [`[template:${stage}]`];
|
|
134019
|
+
for (const v of stageVars) {
|
|
134020
|
+
lines.push(fmtVar(v, v.byKey.get(stage)));
|
|
134021
|
+
}
|
|
134022
|
+
blocks.push(lines.join("\n"));
|
|
134023
|
+
}
|
|
134024
|
+
for (const region of pattern.regions) {
|
|
134025
|
+
const regionVars = pattern.assignments.filter((a) => a.axis === "region" && a.byKey.has(region));
|
|
134026
|
+
if (regionVars.length === 0) {
|
|
134027
|
+
continue;
|
|
134028
|
+
}
|
|
134029
|
+
const lines = [`[template:${region}]`];
|
|
134030
|
+
for (const v of regionVars) {
|
|
134031
|
+
lines.push(fmtVar(v, v.byKey.get(region)));
|
|
134032
|
+
}
|
|
134033
|
+
blocks.push(lines.join("\n"));
|
|
134034
|
+
}
|
|
134035
|
+
for (const cell of pattern.cells) {
|
|
134036
|
+
const leafVars = pattern.assignments.filter((a) => a.axis === "leaf" && a.byKey.has(cell.envName));
|
|
134037
|
+
const header = `[env:${cell.envName} extends ${cell.stage}, ${cell.region}]`;
|
|
134038
|
+
if (leafVars.length === 0) {
|
|
134039
|
+
blocks.push(header);
|
|
134040
|
+
continue;
|
|
134041
|
+
}
|
|
134042
|
+
const lines = [header];
|
|
134043
|
+
for (const v of leafVars) {
|
|
134044
|
+
lines.push(fmtVar(v, v.byKey.get(cell.envName)));
|
|
134045
|
+
}
|
|
134046
|
+
blocks.push(lines.join("\n"));
|
|
134047
|
+
}
|
|
134048
|
+
return blocks.join("\n\n");
|
|
134049
|
+
}
|
|
134050
|
+
function applyRegionRefactorToText(text, pattern) {
|
|
134051
|
+
const lines = text.split("\n");
|
|
134052
|
+
const startLine = pattern.replaceRange.startLine;
|
|
134053
|
+
const endLine = Math.min(pattern.replaceRange.endLine, lines.length - 1);
|
|
134054
|
+
const replacementLines = generateRegionRefactor(pattern).split("\n");
|
|
134055
|
+
lines.splice(startLine, endLine - startLine + 1, ...replacementLines);
|
|
134056
|
+
const result = lines.join("\n");
|
|
134057
|
+
return text.endsWith("\n") && !result.endsWith("\n") ? `${result}
|
|
134058
|
+
` : result;
|
|
134059
|
+
}
|
|
134060
|
+
|
|
133622
134061
|
// src/cli.ts
|
|
133623
134062
|
function handleImportResolutionErrors(errors, colors) {
|
|
133624
134063
|
const { blockingErrors, warningErrors } = splitImportResolutionErrors(errors);
|
|
@@ -133653,7 +134092,7 @@ function resolveEnvironmentForPath(targetPath, selectedEnv) {
|
|
|
133653
134092
|
console.error(`Fix .nornenv import errors before running tests.`);
|
|
133654
134093
|
process.exit(1);
|
|
133655
134094
|
}
|
|
133656
|
-
|
|
134095
|
+
let variables = { ...envConfig.common };
|
|
133657
134096
|
const secretNames = new Set(envConfig.secretNames);
|
|
133658
134097
|
const secretValues = new Map(envConfig.secretValues);
|
|
133659
134098
|
const availableEnvironments = envConfig.environments.map((e) => e.name);
|
|
@@ -133672,9 +134111,9 @@ function resolveEnvironmentForPath(targetPath, selectedEnv) {
|
|
|
133672
134111
|
};
|
|
133673
134112
|
}
|
|
133674
134113
|
targetEnvVariables = targetEnv.variables;
|
|
133675
|
-
|
|
133676
|
-
for (const [name, value] of Object.entries(
|
|
133677
|
-
if (secretNames.has(name)) {
|
|
134114
|
+
variables = resolveEffectiveEnvVariables(selectedEnv, envConfig);
|
|
134115
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
134116
|
+
if (secretNames.has(name) && !secretValues.has(name)) {
|
|
133678
134117
|
secretValues.set(name, value);
|
|
133679
134118
|
}
|
|
133680
134119
|
}
|
|
@@ -133733,7 +134172,9 @@ function parseArgs(args) {
|
|
|
133733
134172
|
noRedact: false,
|
|
133734
134173
|
tagFilters: [],
|
|
133735
134174
|
tagsFilter: [],
|
|
133736
|
-
insecure: false
|
|
134175
|
+
insecure: false,
|
|
134176
|
+
refactorRegionPattern: false,
|
|
134177
|
+
writeRefactor: false
|
|
133737
134178
|
};
|
|
133738
134179
|
for (let i = 0; i < args.length; i++) {
|
|
133739
134180
|
const arg = args[i];
|
|
@@ -133751,6 +134192,10 @@ function parseArgs(args) {
|
|
|
133751
134192
|
options.timeout = parseInt(args[++i], 10) * 1e3;
|
|
133752
134193
|
} else if (arg === "--insecure") {
|
|
133753
134194
|
options.insecure = true;
|
|
134195
|
+
} else if (arg === "--refactor-region-pattern" || arg === "--refactor-nornenv-region-pattern") {
|
|
134196
|
+
options.refactorRegionPattern = true;
|
|
134197
|
+
} else if (arg === "--write") {
|
|
134198
|
+
options.writeRefactor = true;
|
|
133754
134199
|
} else if (arg === "--no-fail") {
|
|
133755
134200
|
options.failOnError = false;
|
|
133756
134201
|
} else if (arg === "--no-redact") {
|
|
@@ -133806,6 +134251,9 @@ Options:
|
|
|
133806
134251
|
-o, --output-dir <dir> Output directory for reports (auto-generates timestamped files)
|
|
133807
134252
|
--tag <filter> Filter sequences by tag (AND logic, can be repeated)
|
|
133808
134253
|
--tags <filters> Filter sequences by tags (OR logic, comma-separated)
|
|
134254
|
+
--refactor-region-pattern
|
|
134255
|
+
Refactor a flat .nornenv STAGE_REGION matrix to templates
|
|
134256
|
+
--write Apply --refactor-region-pattern instead of printing result
|
|
133809
134257
|
-h, --help Show this help message
|
|
133810
134258
|
|
|
133811
134259
|
Report Generation:
|
|
@@ -133849,11 +134297,62 @@ Examples:
|
|
|
133849
134297
|
norn api-tests.norn --html report.html # Generate HTML report (explicit)
|
|
133850
134298
|
norn api-tests.norn --insecure # Allow self-signed/local TLS certs
|
|
133851
134299
|
norn api-tests.norn --no-redact # Show all data (no redaction)
|
|
134300
|
+
norn .nornenv --refactor-region-pattern # Print refactored .nornenv
|
|
134301
|
+
norn .nornenv --refactor-region-pattern --write
|
|
133852
134302
|
norn secrets keygen --name team-main # Generate shared key and cache locally
|
|
133853
134303
|
norn secrets import-key --kid team-main # Save shared key from your vault
|
|
133854
134304
|
norn secrets audit . # Fail if plaintext secrets are committed
|
|
133855
134305
|
`);
|
|
133856
134306
|
}
|
|
134307
|
+
function formatRegionPatternSummary(pattern) {
|
|
134308
|
+
const { liftedToStage, liftedToRegion, leafSpecific, skippedConnectionStrings } = pattern.summary;
|
|
134309
|
+
const lines = [
|
|
134310
|
+
`Detected ${pattern.cells.length} envs across ${pattern.stages.length} stages x ${pattern.regions.length} regions.`,
|
|
134311
|
+
`Lifted ${liftedToStage} vars to stage templates (${pattern.stages.join(", ")}).`,
|
|
134312
|
+
`Lifted ${liftedToRegion} vars to region templates (${pattern.regions.join(", ")}).`,
|
|
134313
|
+
`Kept ${leafSpecific} vars leaf-specific.`
|
|
134314
|
+
];
|
|
134315
|
+
if (skippedConnectionStrings > 0) {
|
|
134316
|
+
lines.push(`Kept ${skippedConnectionStrings} connection-string var${skippedConnectionStrings === 1 ? "" : "s"} in leaf envs.`);
|
|
134317
|
+
}
|
|
134318
|
+
return lines;
|
|
134319
|
+
}
|
|
134320
|
+
function runNornenvRegionRefactor(filePath, options) {
|
|
134321
|
+
const content = fs19.readFileSync(filePath, "utf-8");
|
|
134322
|
+
const config2 = parseEnvFile(content, filePath);
|
|
134323
|
+
const pattern = detectRegionPattern(config2, content);
|
|
134324
|
+
if (!pattern) {
|
|
134325
|
+
if (options.output === "json") {
|
|
134326
|
+
console.log(JSON.stringify({ success: false, changed: false, error: "No region pattern detected" }, null, 2));
|
|
134327
|
+
} else {
|
|
134328
|
+
console.error("No region pattern detected in this .nornenv file.");
|
|
134329
|
+
}
|
|
134330
|
+
process.exit(1);
|
|
134331
|
+
}
|
|
134332
|
+
const refactored = applyRegionRefactorToText(content, pattern);
|
|
134333
|
+
if (options.writeRefactor) {
|
|
134334
|
+
fs19.writeFileSync(filePath, refactored, "utf-8");
|
|
134335
|
+
}
|
|
134336
|
+
if (options.output === "json") {
|
|
134337
|
+
console.log(JSON.stringify({
|
|
134338
|
+
success: true,
|
|
134339
|
+
changed: refactored !== content,
|
|
134340
|
+
file: filePath,
|
|
134341
|
+
summary: pattern.summary,
|
|
134342
|
+
stages: pattern.stages,
|
|
134343
|
+
regions: pattern.regions,
|
|
134344
|
+
output: options.writeRefactor ? void 0 : refactored
|
|
134345
|
+
}, null, 2));
|
|
134346
|
+
} else if (options.writeRefactor) {
|
|
134347
|
+
for (const line2 of formatRegionPatternSummary(pattern)) {
|
|
134348
|
+
console.log(line2);
|
|
134349
|
+
}
|
|
134350
|
+
console.log(`Refactored ${filePath}`);
|
|
134351
|
+
} else {
|
|
134352
|
+
console.log(refactored);
|
|
134353
|
+
}
|
|
134354
|
+
process.exit(0);
|
|
134355
|
+
}
|
|
133857
134356
|
async function runSingleRequest(fileContent, variables, cookieJar, apiDefinitions, filePath, envContext) {
|
|
133858
134357
|
const lines = fileContent.split("\n");
|
|
133859
134358
|
const requestLines = [];
|
|
@@ -134018,6 +134517,17 @@ async function main() {
|
|
|
134018
134517
|
process.exit(1);
|
|
134019
134518
|
}
|
|
134020
134519
|
const isDirectory = fs19.statSync(inputPath).isDirectory();
|
|
134520
|
+
if (options.writeRefactor && !options.refactorRegionPattern) {
|
|
134521
|
+
console.error("Error: --write can only be used with --refactor-region-pattern");
|
|
134522
|
+
process.exit(1);
|
|
134523
|
+
}
|
|
134524
|
+
if (options.refactorRegionPattern) {
|
|
134525
|
+
if (isDirectory) {
|
|
134526
|
+
console.error("Error: --refactor-region-pattern requires a specific .nornenv file, not a directory");
|
|
134527
|
+
process.exit(1);
|
|
134528
|
+
}
|
|
134529
|
+
runNornenvRegionRefactor(inputPath, options);
|
|
134530
|
+
}
|
|
134021
134531
|
let filesToRun;
|
|
134022
134532
|
if (isDirectory) {
|
|
134023
134533
|
filesToRun = discoverNornFiles(inputPath);
|