helm-env-delta 1.11.1 → 1.12.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/README.md +14 -4
- package/dist/configFile.d.ts +13 -0
- package/dist/configFile.js +4 -0
- package/dist/htmlReporter.d.ts +2 -1
- package/dist/htmlReporter.js +12 -2
- package/dist/index.js +1 -1
- package/dist/reporters/htmlStyles.d.ts +1 -1
- package/dist/reporters/htmlStyles.js +85 -0
- package/dist/reporters/htmlTemplate.d.ts +12 -1
- package/dist/reporters/htmlTemplate.js +47 -4
- package/dist/yamlFormatter.js +47 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -52,11 +52,11 @@ HelmEnvDelta (`hed`) automates environment synchronization for GitOps workflows
|
|
|
52
52
|
|
|
53
53
|
🛡️ **Safety Rules** - Block major version upgrades, scaling violations, and forbidden patterns. Load validation rules from external files. Scan globally or target specific fields.
|
|
54
54
|
|
|
55
|
-
🎨 **Format Enforcement** - Standardize YAML across all environments: key ordering, indentation, quoting, array sorting.
|
|
55
|
+
🎨 **Format Enforcement** - Standardize YAML across all environments: key ordering, alphabetical key sorting, indentation, quoting, array sorting.
|
|
56
56
|
|
|
57
57
|
📦 **Config Inheritance** - Reuse base configurations with environment-specific overrides.
|
|
58
58
|
|
|
59
|
-
📊 **Multiple Reports** - Console, HTML (visual, self-contained), and JSON (CI/CD) output formats. HTML reports include collapsible diff stats dashboard, synchronized side-by-side scrolling, copy diff buttons, file search, and collapse/expand controls. Empty categories are automatically hidden.
|
|
59
|
+
📊 **Multiple Reports** - Console, HTML (visual, self-contained), and JSON (CI/CD) output formats. HTML reports include collapsible diff stats dashboard, stop rule violations table (dry-run only), synchronized side-by-side scrolling, copy diff buttons, file search, and collapse/expand controls. Empty categories are automatically hidden.
|
|
60
60
|
|
|
61
61
|
🔍 **Discovery Tools** - Preview files (`-l`), inspect config (`--show-config`), filter by filename/content (`-f`), filter by change type (`-m`), validate with comprehensive warnings including unused pattern detection.
|
|
62
62
|
|
|
@@ -119,7 +119,7 @@ hed -c config.yaml
|
|
|
119
119
|
hed -c config.yaml -H
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
Self-contained HTML report — works offline, no CDN required. Includes collapsible diff stats dashboard, synchronized side-by-side scrolling, copy buttons, sidebar search, and collapse/expand controls. Empty categories are automatically hidden.
|
|
122
|
+
Self-contained HTML report — works offline, no CDN required. Includes collapsible diff stats dashboard, stop rule violations table (shown in dry-run mode), synchronized side-by-side scrolling, copy buttons, sidebar search, and collapse/expand controls. Empty categories are automatically hidden.
|
|
123
123
|
|
|
124
124
|
### 5️⃣ Get Smart Suggestions (Optional)
|
|
125
125
|
|
|
@@ -208,6 +208,9 @@ outputFormat:
|
|
|
208
208
|
- 'kind'
|
|
209
209
|
- 'metadata'
|
|
210
210
|
- 'spec'
|
|
211
|
+
keySort:
|
|
212
|
+
'**/*.yaml':
|
|
213
|
+
- path: 'spec.template.metadata.labels'
|
|
211
214
|
arraySort:
|
|
212
215
|
'**/*.yaml':
|
|
213
216
|
- path: 'env'
|
|
@@ -664,6 +667,8 @@ stopRules:
|
|
|
664
667
|
|
|
665
668
|
**Override:** Use `--force` to bypass stop rules when needed.
|
|
666
669
|
|
|
670
|
+
**Visibility:** Stop rule violations appear in console output, JSON reports, and HTML reports (dry-run mode only, as a collapsible table in the header area).
|
|
671
|
+
|
|
667
672
|
---
|
|
668
673
|
|
|
669
674
|
### 🎨 Output Formatting
|
|
@@ -675,13 +680,18 @@ outputFormat:
|
|
|
675
680
|
indent: 2 # Indentation size
|
|
676
681
|
keySeparator: true # Blank line between top-level keys (or second-level keys when single top-level key)
|
|
677
682
|
|
|
678
|
-
keyOrders: # Custom key ordering
|
|
683
|
+
keyOrders: # Custom key ordering (pin specific keys to top)
|
|
679
684
|
'apps/*.yaml':
|
|
680
685
|
- 'apiVersion'
|
|
681
686
|
- 'kind'
|
|
682
687
|
- 'metadata'
|
|
683
688
|
- 'spec'
|
|
684
689
|
|
|
690
|
+
keySort: # Sort all keys alphabetically at path
|
|
691
|
+
'**/*.yaml':
|
|
692
|
+
- path: 'spec.template.metadata.labels'
|
|
693
|
+
- path: 'env.vars'
|
|
694
|
+
|
|
685
695
|
arraySort: # Sort arrays
|
|
686
696
|
'services/**/values.yaml':
|
|
687
697
|
- path: 'env'
|
package/dist/configFile.d.ts
CHANGED
|
@@ -77,6 +77,9 @@ declare const arraySortRuleSchema: z.ZodObject<{
|
|
|
77
77
|
desc: "desc";
|
|
78
78
|
}>>;
|
|
79
79
|
}, z.core.$strip>;
|
|
80
|
+
declare const keySortRuleSchema: z.ZodObject<{
|
|
81
|
+
path: z.ZodString;
|
|
82
|
+
}, z.core.$strip>;
|
|
80
83
|
declare const fixedValueRuleSchema: z.ZodObject<{
|
|
81
84
|
path: z.ZodString;
|
|
82
85
|
value: z.ZodUnknown;
|
|
@@ -111,6 +114,9 @@ declare const baseConfigSchema: z.ZodObject<{
|
|
|
111
114
|
keySeparator: z.ZodOptional<z.ZodBoolean>;
|
|
112
115
|
quoteValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
113
116
|
keyOrders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
117
|
+
keySort: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
118
|
+
path: z.ZodString;
|
|
119
|
+
}, z.core.$strip>>>>;
|
|
114
120
|
arraySort: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
115
121
|
path: z.ZodString;
|
|
116
122
|
sortBy: z.ZodString;
|
|
@@ -230,6 +236,9 @@ declare const finalConfigSchema: z.ZodObject<{
|
|
|
230
236
|
keySeparator: z.ZodDefault<z.ZodBoolean>;
|
|
231
237
|
quoteValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
232
238
|
keyOrders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
239
|
+
keySort: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
240
|
+
path: z.ZodString;
|
|
241
|
+
}, z.core.$strip>>>>;
|
|
233
242
|
arraySort: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
234
243
|
path: z.ZodString;
|
|
235
244
|
sortBy: z.ZodString;
|
|
@@ -301,6 +310,9 @@ declare const formatOnlyConfigSchema: z.ZodObject<{
|
|
|
301
310
|
keySeparator: z.ZodDefault<z.ZodBoolean>;
|
|
302
311
|
quoteValues: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
303
312
|
keyOrders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
313
|
+
keySort: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
314
|
+
path: z.ZodString;
|
|
315
|
+
}, z.core.$strip>>>>;
|
|
304
316
|
arraySort: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
305
317
|
path: z.ZodString;
|
|
306
318
|
sortBy: z.ZodString;
|
|
@@ -324,6 +336,7 @@ export type RegexFileRule = z.infer<typeof regexFileRuleSchema>;
|
|
|
324
336
|
export type RegexFileKeyRule = z.infer<typeof regexFileKeyRuleSchema>;
|
|
325
337
|
export type VersionFormatRule = z.infer<typeof versionFormatRuleSchema>;
|
|
326
338
|
export type ArraySortRule = z.infer<typeof arraySortRuleSchema>;
|
|
339
|
+
export type KeySortRule = z.infer<typeof keySortRuleSchema>;
|
|
327
340
|
export type TransformRule = z.infer<typeof transformRuleSchema>;
|
|
328
341
|
export type TransformRules = z.infer<typeof transformRulesSchema>;
|
|
329
342
|
export type TransformConfig = Record<string, TransformRules>;
|
package/dist/configFile.js
CHANGED
|
@@ -79,6 +79,7 @@ const arraySortRuleSchema = zod_1.z.object({
|
|
|
79
79
|
sortBy: zod_1.z.string().min(1),
|
|
80
80
|
order: zod_1.z.enum(['asc', 'desc']).default('asc')
|
|
81
81
|
});
|
|
82
|
+
const keySortRuleSchema = zod_1.z.object({ path: zod_1.z.string().min(1) });
|
|
82
83
|
const fixedValueRuleSchema = zod_1.z.object({
|
|
83
84
|
path: zod_1.z.string().min(1).describe('JSONPath to the value to set'),
|
|
84
85
|
value: zod_1.z.unknown().describe('The constant value to set (any type: string, number, boolean, null, object, array)')
|
|
@@ -128,6 +129,7 @@ const baseConfigSchema = zod_1.z.object({
|
|
|
128
129
|
keySeparator: zod_1.z.boolean().optional(),
|
|
129
130
|
quoteValues: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
|
|
130
131
|
keyOrders: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
|
|
132
|
+
keySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(keySortRuleSchema)).optional(),
|
|
131
133
|
arraySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(arraySortRuleSchema)).optional()
|
|
132
134
|
})
|
|
133
135
|
.optional(),
|
|
@@ -149,6 +151,7 @@ const finalConfigSchema = baseConfigSchema
|
|
|
149
151
|
keySeparator: zod_1.z.boolean().default(false),
|
|
150
152
|
quoteValues: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
|
|
151
153
|
keyOrders: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
|
|
154
|
+
keySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(keySortRuleSchema)).optional(),
|
|
152
155
|
arraySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(arraySortRuleSchema)).optional()
|
|
153
156
|
})
|
|
154
157
|
.optional()
|
|
@@ -176,6 +179,7 @@ const formatOnlyConfigSchema = baseConfigSchema
|
|
|
176
179
|
keySeparator: zod_1.z.boolean().default(false),
|
|
177
180
|
quoteValues: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
|
|
178
181
|
keyOrders: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
|
|
182
|
+
keySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(keySortRuleSchema)).optional(),
|
|
179
183
|
arraySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(arraySortRuleSchema)).optional()
|
|
180
184
|
})
|
|
181
185
|
.optional()
|
package/dist/htmlReporter.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Config } from './configFile';
|
|
2
2
|
import { FileDiffResult } from './fileDiff';
|
|
3
|
+
import type { ValidationResult } from './stopRulesValidator';
|
|
3
4
|
export type { DiffStats, ReportMetadata } from './reporters/htmlTemplate';
|
|
4
5
|
declare const HtmlReporterErrorClass: {
|
|
5
6
|
new (message: string, options?: import("./utils/errors").ErrorOptions): {
|
|
@@ -19,4 +20,4 @@ declare const HtmlReporterErrorClass: {
|
|
|
19
20
|
export declare class HtmlReporterError extends HtmlReporterErrorClass {
|
|
20
21
|
}
|
|
21
22
|
export declare const isHtmlReporterError: (error: unknown) => error is HtmlReporterError;
|
|
22
|
-
export declare const generateHtmlReport: (diffResult: FileDiffResult, formattedFiles: string[], config: Config, dryRun: boolean, logger?: import("./logger").Logger) => Promise<void>;
|
|
23
|
+
export declare const generateHtmlReport: (diffResult: FileDiffResult, formattedFiles: string[], config: Config, dryRun: boolean, logger?: import("./logger").Logger, validationResult?: ValidationResult) => Promise<void>;
|
package/dist/htmlReporter.js
CHANGED
|
@@ -118,7 +118,7 @@ const writeHtmlFile = async (htmlContent, outputPath) => {
|
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
|
-
const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun, logger) => {
|
|
121
|
+
const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun, logger, validationResult) => {
|
|
122
122
|
const reportPath = generateTemporaryFilePath();
|
|
123
123
|
const metadata = {
|
|
124
124
|
timestamp: new Date().toISOString(),
|
|
@@ -151,7 +151,17 @@ const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun, lo
|
|
|
151
151
|
}
|
|
152
152
|
statsArray.sort((a, b) => b.added + b.removed - (a.added + a.removed));
|
|
153
153
|
const diffStats = { totalAdded, totalRemoved, fileStats: statsArray };
|
|
154
|
-
const
|
|
154
|
+
const stopRuleViolations = validationResult && validationResult.violations.length > 0
|
|
155
|
+
? validationResult.violations.map((violation) => ({
|
|
156
|
+
file: violation.file,
|
|
157
|
+
rule: { type: violation.rule.type, path: violation.rule.path },
|
|
158
|
+
path: violation.path,
|
|
159
|
+
oldValue: violation.oldValue,
|
|
160
|
+
updatedValue: violation.updatedValue,
|
|
161
|
+
message: violation.message
|
|
162
|
+
}))
|
|
163
|
+
: undefined;
|
|
164
|
+
const htmlContent = (0, htmlTemplate_1.generateHtmlTemplate)(diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds, addedSections, addedFileIds, diffStats, stopRuleViolations);
|
|
155
165
|
await writeHtmlFile(htmlContent, reportPath);
|
|
156
166
|
logger?.log(`✓ HTML report generated: ${reportPath}, opening in browser...`);
|
|
157
167
|
try {
|
package/dist/index.js
CHANGED
|
@@ -326,7 +326,7 @@ const main = async () => {
|
|
|
326
326
|
}
|
|
327
327
|
const formattedFiles = await (0, fileUpdater_1.updateFiles)(diffResult, sourceFiles, destinationFiles, syncConfig, command.dryRun, command.skipFormat, logger);
|
|
328
328
|
if (command.diffHtml && !command.quiet)
|
|
329
|
-
await (0, htmlReporter_1.generateHtmlReport)(diffResult, formattedFiles, syncConfig, command.dryRun, logger);
|
|
329
|
+
await (0, htmlReporter_1.generateHtmlReport)(diffResult, formattedFiles, syncConfig, command.dryRun, logger, command.dryRun ? validationResult : undefined);
|
|
330
330
|
if (command.diffJson)
|
|
331
331
|
(0, jsonReporter_1.generateJsonReport)(diffResult, formattedFiles, validationResult, syncConfig, command.dryRun, package_json_1.default.version);
|
|
332
332
|
};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const DIFF2HTML_STYLES: string;
|
|
2
|
-
export declare const HTML_STYLES = "\n /* Custom styles */\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background: #f6f8fa;\n }\n\n header {\n background: white;\n padding: 20px;\n border-radius: 6px;\n margin-bottom: 20px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n h1 {\n margin: 0 0 10px 0;\n color: #24292e;\n }\n\n .metadata {\n display: flex;\n gap: 20px;\n margin: 10px 0;\n color: #586069;\n font-size: 14px;\n }\n\n .dry-run-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #cfe2ff;\n color: #084298;\n border-radius: 4px;\n font-weight: bold;\n font-size: 12px;\n }\n\n .summary {\n display: flex;\n gap: 12px;\n margin: 15px 0;\n }\n\n .stat {\n padding: 8px 16px;\n border-radius: 6px;\n font-weight: 600;\n font-size: 14px;\n }\n\n .stat.added { background: #d4edda; color: #155724; }\n .stat.changed { background: #fff3cd; color: #856404; }\n .stat.deleted { background: #f8d7da; color: #721c24; }\n .stat.formatted { background: #d1ecf1; color: #0c5460; }\n .stat.unchanged { background: #e2e3e5; color: #383d41; }\n\n .tabs {\n display: flex;\n background: white;\n border-radius: 6px 6px 0 0;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab {\n padding: 12px 24px;\n border: none;\n background: transparent;\n cursor: pointer;\n font-size: 14px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .tab:hover {\n color: #24292e;\n }\n\n .tab.active {\n border-bottom: 2px solid #0969da;\n color: #0969da;\n font-weight: 600;\n }\n\n main {\n background: white;\n padding: 20px;\n border-radius: 0 0 6px 6px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab-content {\n display: none;\n }\n\n .tab-content.active {\n display: block;\n }\n\n .file-section {\n margin: 12px 0;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n }\n\n .file-section summary {\n padding: 12px 16px;\n background: #f6f8fa;\n cursor: pointer;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #24292e;\n }\n\n .file-section summary:hover {\n background: #eaeef2;\n }\n\n .file-section[open] > summary {\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .filename-transform {\n color: #0969da;\n }\n\n .diff-container {\n padding: 0;\n }\n\n /* Hide diff2html file header with rename badge */\n .d2h-file-header {\n display: none;\n }\n\n .file-list {\n margin: 20px 0;\n }\n\n .file-list ul {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n }\n\n .file-list li {\n padding: 8px 16px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #586069;\n border-bottom: 1px solid #f6f8fa;\n }\n\n .file-list li:hover {\n background: #f6f8fa;\n }\n\n /* Treeview styles */\n .tree-root {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n }\n\n .tree-root ul {\n list-style: none;\n padding-left: 20px;\n margin: 0;\n }\n\n .tree-folder,\n .tree-file {\n padding: 4px 8px;\n border-radius: 4px;\n cursor: default;\n }\n\n .tree-folder:hover,\n .tree-file:hover {\n background: #f6f8fa;\n }\n\n .tree-toggle {\n display: inline-block;\n width: 16px;\n cursor: pointer;\n color: #586069;\n font-size: 10px;\n user-select: none;\n }\n\n .tree-folder.collapsed > .tree-toggle {\n transform: rotate(-90deg);\n }\n\n .tree-folder.collapsed > .tree-children {\n display: none;\n }\n\n .tree-folder-name {\n color: #0969da;\n font-weight: 500;\n }\n\n .tree-file-name {\n color: #586069;\n padding-left: 16px;\n }\n\n /* Sidebar styles */\n .sidebar-container {\n display: flex;\n gap: 0;\n }\n\n .sidebar {\n width: 280px;\n min-width: 280px;\n border-right: 1px solid #d0d7de;\n background: #f6f8fa;\n transition: width 0.2s, min-width 0.2s, padding 0.2s, opacity 0.2s;\n }\n\n .sidebar.collapsed {\n width: 0;\n min-width: 0;\n padding: 0;\n overflow: hidden;\n border-right: none;\n }\n\n .sidebar-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #fff;\n font-weight: 600;\n font-size: 14px;\n color: #24292e;\n position: sticky;\n top: 0;\n z-index: 1;\n }\n\n .sidebar-toggle {\n background: none;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n cursor: pointer;\n padding: 4px 8px;\n color: #586069;\n font-size: 12px;\n }\n\n .sidebar-toggle:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .sidebar-content {\n padding: 8px;\n }\n\n .sidebar-tree .tree-file-link {\n color: #586069;\n text-decoration: none;\n padding-left: 16px;\n display: block;\n }\n\n .sidebar-tree .tree-file-link:hover {\n color: #0969da;\n }\n\n .sidebar-tree .tree-file.active .tree-file-link {\n color: #0969da;\n font-weight: 600;\n }\n\n .changed-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n .sidebar-expand-btn {\n display: none;\n position: fixed;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n background: #f6f8fa;\n border: 1px solid #d0d7de;\n border-left: none;\n border-radius: 0 4px 4px 0;\n padding: 8px 4px;\n cursor: pointer;\n color: #586069;\n z-index: 100;\n }\n\n .sidebar-expand-btn:hover {\n background: #eaeef2;\n color: #24292e;\n }\n\n .sidebar.collapsed ~ .sidebar-expand-btn {\n display: block;\n }\n\n /* Added content area (same as changed-content) */\n .added-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n /* Content container for added files */\n .content-container {\n padding: 16px;\n background: #f6f8fa;\n border-top: 1px solid #d0d7de;\n }\n\n .content-actions {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .copy-btn,\n .download-btn {\n padding: 6px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 13px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-btn:hover,\n .download-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n .file-content {\n margin: 0;\n padding: 16px;\n background: white;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n }\n\n .file-content code {\n font-family: inherit;\n }\n\n /* Scroll-to-top button */\n .scroll-to-top {\n display: none;\n position: fixed;\n bottom: 30px;\n right: 30px;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 50%;\n background: #0969da;\n color: white;\n font-size: 18px;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n transition: opacity 0.2s, background 0.2s;\n z-index: 200;\n line-height: 40px;\n text-align: center;\n padding: 0;\n }\n\n .scroll-to-top:hover {\n background: #0550ae;\n }\n\n .scroll-to-top.visible {\n display: block;\n }\n\n /* Content toolbar (collapse/expand buttons) */\n .content-toolbar {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-bottom: 12px;\n }\n\n .collapse-all-btn,\n .expand-all-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .collapse-all-btn:hover,\n .expand-all-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Line change count badges */\n .summary-badges {\n float: right;\n display: inline-flex;\n gap: 6px;\n margin-left: 12px;\n }\n\n .line-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n line-height: 18px;\n }\n\n .line-added {\n background: #d4edda;\n color: #155724;\n }\n\n .line-removed {\n background: #f8d7da;\n color: #721c24;\n }\n\n /* Diff toolbar */\n .diff-toolbar {\n display: flex;\n justify-content: flex-end;\n padding: 8px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #f6f8fa;\n }\n\n .copy-diff-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-diff-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-diff-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n /* Sidebar search */\n .sidebar-search {\n width: 100%;\n padding: 6px 8px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n margin-bottom: 8px;\n box-sizing: border-box;\n position: sticky;\n top: 0;\n z-index: 1;\n background: #fff;\n }\n\n .sidebar-search:focus {\n outline: none;\n border-color: #0969da;\n box-shadow: 0 0 0 3px rgba(9,105,218,0.15);\n }\n\n /* Stats toggle button */\n .stats-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .stats-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Statistics dashboard */\n .stats-dashboard {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .stats-summary {\n display: flex;\n gap: 16px;\n align-items: center;\n margin-bottom: 10px;\n }\n\n .stats-summary .total-added {\n font-weight: 700;\n color: #155724;\n font-size: 16px;\n }\n\n .stats-summary .total-removed {\n font-weight: 700;\n color: #721c24;\n font-size: 16px;\n }\n\n .stats-bar {\n display: flex;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n background: #e1e4e8;\n margin-bottom: 10px;\n }\n\n .stats-segment {\n height: 100%;\n min-width: 2px;\n }\n\n .stats-segment.added-segment {\n background: #28a745;\n }\n\n .stats-segment.removed-segment {\n background: #d73a49;\n }\n\n .top-changed-files {\n list-style: none;\n padding: 0;\n margin: 0;\n font-size: 13px;\n }\n\n .top-changed-files li {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 3px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n color: #586069;\n }\n\n .top-changed-files .file-path {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-right: 8px;\n }\n\n .top-changed-files .file-stats {\n white-space: nowrap;\n flex-shrink: 0;\n }\n";
|
|
2
|
+
export declare const HTML_STYLES = "\n /* Custom styles */\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background: #f6f8fa;\n }\n\n header {\n background: white;\n padding: 20px;\n border-radius: 6px;\n margin-bottom: 20px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n h1 {\n margin: 0 0 10px 0;\n color: #24292e;\n }\n\n .metadata {\n display: flex;\n gap: 20px;\n margin: 10px 0;\n color: #586069;\n font-size: 14px;\n }\n\n .dry-run-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #cfe2ff;\n color: #084298;\n border-radius: 4px;\n font-weight: bold;\n font-size: 12px;\n }\n\n .summary {\n display: flex;\n gap: 12px;\n margin: 15px 0;\n }\n\n .stat {\n padding: 8px 16px;\n border-radius: 6px;\n font-weight: 600;\n font-size: 14px;\n }\n\n .stat.added { background: #d4edda; color: #155724; }\n .stat.changed { background: #fff3cd; color: #856404; }\n .stat.deleted { background: #f8d7da; color: #721c24; }\n .stat.formatted { background: #d1ecf1; color: #0c5460; }\n .stat.unchanged { background: #e2e3e5; color: #383d41; }\n\n .tabs {\n display: flex;\n background: white;\n border-radius: 6px 6px 0 0;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab {\n padding: 12px 24px;\n border: none;\n background: transparent;\n cursor: pointer;\n font-size: 14px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .tab:hover {\n color: #24292e;\n }\n\n .tab.active {\n border-bottom: 2px solid #0969da;\n color: #0969da;\n font-weight: 600;\n }\n\n main {\n background: white;\n padding: 20px;\n border-radius: 0 0 6px 6px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab-content {\n display: none;\n }\n\n .tab-content.active {\n display: block;\n }\n\n .file-section {\n margin: 12px 0;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n }\n\n .file-section summary {\n padding: 12px 16px;\n background: #f6f8fa;\n cursor: pointer;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #24292e;\n }\n\n .file-section summary:hover {\n background: #eaeef2;\n }\n\n .file-section[open] > summary {\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .filename-transform {\n color: #0969da;\n }\n\n .diff-container {\n padding: 0;\n }\n\n /* Hide diff2html file header with rename badge */\n .d2h-file-header {\n display: none;\n }\n\n .file-list {\n margin: 20px 0;\n }\n\n .file-list ul {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n }\n\n .file-list li {\n padding: 8px 16px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #586069;\n border-bottom: 1px solid #f6f8fa;\n }\n\n .file-list li:hover {\n background: #f6f8fa;\n }\n\n /* Treeview styles */\n .tree-root {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n }\n\n .tree-root ul {\n list-style: none;\n padding-left: 20px;\n margin: 0;\n }\n\n .tree-folder,\n .tree-file {\n padding: 4px 8px;\n border-radius: 4px;\n cursor: default;\n }\n\n .tree-folder:hover,\n .tree-file:hover {\n background: #f6f8fa;\n }\n\n .tree-toggle {\n display: inline-block;\n width: 16px;\n cursor: pointer;\n color: #586069;\n font-size: 10px;\n user-select: none;\n }\n\n .tree-folder.collapsed > .tree-toggle {\n transform: rotate(-90deg);\n }\n\n .tree-folder.collapsed > .tree-children {\n display: none;\n }\n\n .tree-folder-name {\n color: #0969da;\n font-weight: 500;\n }\n\n .tree-file-name {\n color: #586069;\n padding-left: 16px;\n }\n\n /* Sidebar styles */\n .sidebar-container {\n display: flex;\n gap: 0;\n }\n\n .sidebar {\n width: 280px;\n min-width: 280px;\n border-right: 1px solid #d0d7de;\n background: #f6f8fa;\n transition: width 0.2s, min-width 0.2s, padding 0.2s, opacity 0.2s;\n }\n\n .sidebar.collapsed {\n width: 0;\n min-width: 0;\n padding: 0;\n overflow: hidden;\n border-right: none;\n }\n\n .sidebar-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #fff;\n font-weight: 600;\n font-size: 14px;\n color: #24292e;\n position: sticky;\n top: 0;\n z-index: 1;\n }\n\n .sidebar-toggle {\n background: none;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n cursor: pointer;\n padding: 4px 8px;\n color: #586069;\n font-size: 12px;\n }\n\n .sidebar-toggle:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .sidebar-content {\n padding: 8px;\n }\n\n .sidebar-tree .tree-file-link {\n color: #586069;\n text-decoration: none;\n padding-left: 16px;\n display: block;\n }\n\n .sidebar-tree .tree-file-link:hover {\n color: #0969da;\n }\n\n .sidebar-tree .tree-file.active .tree-file-link {\n color: #0969da;\n font-weight: 600;\n }\n\n .changed-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n .sidebar-expand-btn {\n display: none;\n position: fixed;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n background: #f6f8fa;\n border: 1px solid #d0d7de;\n border-left: none;\n border-radius: 0 4px 4px 0;\n padding: 8px 4px;\n cursor: pointer;\n color: #586069;\n z-index: 100;\n }\n\n .sidebar-expand-btn:hover {\n background: #eaeef2;\n color: #24292e;\n }\n\n .sidebar.collapsed ~ .sidebar-expand-btn {\n display: block;\n }\n\n /* Added content area (same as changed-content) */\n .added-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n /* Content container for added files */\n .content-container {\n padding: 16px;\n background: #f6f8fa;\n border-top: 1px solid #d0d7de;\n }\n\n .content-actions {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .copy-btn,\n .download-btn {\n padding: 6px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 13px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-btn:hover,\n .download-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n .file-content {\n margin: 0;\n padding: 16px;\n background: white;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n }\n\n .file-content code {\n font-family: inherit;\n }\n\n /* Scroll-to-top button */\n .scroll-to-top {\n display: none;\n position: fixed;\n bottom: 30px;\n right: 30px;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 50%;\n background: #0969da;\n color: white;\n font-size: 18px;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n transition: opacity 0.2s, background 0.2s;\n z-index: 200;\n line-height: 40px;\n text-align: center;\n padding: 0;\n }\n\n .scroll-to-top:hover {\n background: #0550ae;\n }\n\n .scroll-to-top.visible {\n display: block;\n }\n\n /* Content toolbar (collapse/expand buttons) */\n .content-toolbar {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-bottom: 12px;\n }\n\n .collapse-all-btn,\n .expand-all-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .collapse-all-btn:hover,\n .expand-all-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Line change count badges */\n .summary-badges {\n float: right;\n display: inline-flex;\n gap: 6px;\n margin-left: 12px;\n }\n\n .line-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n line-height: 18px;\n }\n\n .line-added {\n background: #d4edda;\n color: #155724;\n }\n\n .line-removed {\n background: #f8d7da;\n color: #721c24;\n }\n\n /* Diff toolbar */\n .diff-toolbar {\n display: flex;\n justify-content: flex-end;\n padding: 8px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #f6f8fa;\n }\n\n .copy-diff-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-diff-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-diff-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n /* Sidebar search */\n .sidebar-search {\n width: 100%;\n padding: 6px 8px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n margin-bottom: 8px;\n box-sizing: border-box;\n position: sticky;\n top: 0;\n z-index: 1;\n background: #fff;\n }\n\n .sidebar-search:focus {\n outline: none;\n border-color: #0969da;\n box-shadow: 0 0 0 3px rgba(9,105,218,0.15);\n }\n\n /* Stats toggle button */\n .stats-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .stats-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Statistics dashboard */\n .stats-dashboard {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .stats-summary {\n display: flex;\n gap: 16px;\n align-items: center;\n margin-bottom: 10px;\n }\n\n .stats-summary .total-added {\n font-weight: 700;\n color: #155724;\n font-size: 16px;\n }\n\n .stats-summary .total-removed {\n font-weight: 700;\n color: #721c24;\n font-size: 16px;\n }\n\n .stats-bar {\n display: flex;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n background: #e1e4e8;\n margin-bottom: 10px;\n }\n\n .stats-segment {\n height: 100%;\n min-width: 2px;\n }\n\n .stats-segment.added-segment {\n background: #28a745;\n }\n\n .stats-segment.removed-segment {\n background: #d73a49;\n }\n\n .top-changed-files {\n list-style: none;\n padding: 0;\n margin: 0;\n font-size: 13px;\n }\n\n .top-changed-files li {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 3px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n color: #586069;\n }\n\n .top-changed-files .file-path {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-right: 8px;\n }\n\n .top-changed-files .file-stats {\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* Stop rules violations badge */\n .stat.violations { background: #f8d7da; color: #721c24; }\n\n /* Violations section */\n .violations-section {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .violations-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .violations-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .violations-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n margin-top: 10px;\n }\n\n .violations-table th {\n background: #f6f8fa;\n text-align: left;\n padding: 8px 12px;\n border-bottom: 2px solid #d0d7de;\n font-weight: 600;\n color: #24292e;\n }\n\n .violations-table td {\n padding: 8px 12px;\n border-bottom: 1px solid #e1e4e8;\n color: #24292e;\n vertical-align: top;\n }\n\n .violations-table tr:hover td {\n background: #f6f8fa;\n }\n\n .violation-rule-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n background: #fff3cd;\n color: #856404;\n white-space: nowrap;\n }\n\n .violation-value {\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n background: #f6f8fa;\n padding: 2px 6px;\n border-radius: 3px;\n border: 1px solid #e1e4e8;\n }\n";
|
|
3
3
|
export declare const TAB_SCRIPT: string;
|
|
@@ -617,6 +617,79 @@ exports.HTML_STYLES = `
|
|
|
617
617
|
white-space: nowrap;
|
|
618
618
|
flex-shrink: 0;
|
|
619
619
|
}
|
|
620
|
+
|
|
621
|
+
/* Stop rules violations badge */
|
|
622
|
+
.stat.violations { background: #f8d7da; color: #721c24; }
|
|
623
|
+
|
|
624
|
+
/* Violations section */
|
|
625
|
+
.violations-section {
|
|
626
|
+
margin: 15px 0 0;
|
|
627
|
+
padding: 12px 0 0;
|
|
628
|
+
border-top: 1px solid #e1e4e8;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.violations-toggle-btn {
|
|
632
|
+
padding: 4px 12px;
|
|
633
|
+
border: 1px solid #d0d7de;
|
|
634
|
+
border-radius: 4px;
|
|
635
|
+
background: none;
|
|
636
|
+
cursor: pointer;
|
|
637
|
+
font-size: 12px;
|
|
638
|
+
color: #586069;
|
|
639
|
+
transition: all 0.2s;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.violations-toggle-btn:hover {
|
|
643
|
+
background: #f6f8fa;
|
|
644
|
+
color: #24292e;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.violations-table {
|
|
648
|
+
width: 100%;
|
|
649
|
+
border-collapse: collapse;
|
|
650
|
+
font-size: 13px;
|
|
651
|
+
margin-top: 10px;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.violations-table th {
|
|
655
|
+
background: #f6f8fa;
|
|
656
|
+
text-align: left;
|
|
657
|
+
padding: 8px 12px;
|
|
658
|
+
border-bottom: 2px solid #d0d7de;
|
|
659
|
+
font-weight: 600;
|
|
660
|
+
color: #24292e;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.violations-table td {
|
|
664
|
+
padding: 8px 12px;
|
|
665
|
+
border-bottom: 1px solid #e1e4e8;
|
|
666
|
+
color: #24292e;
|
|
667
|
+
vertical-align: top;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.violations-table tr:hover td {
|
|
671
|
+
background: #f6f8fa;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.violation-rule-badge {
|
|
675
|
+
display: inline-block;
|
|
676
|
+
padding: 2px 8px;
|
|
677
|
+
border-radius: 10px;
|
|
678
|
+
font-size: 11px;
|
|
679
|
+
font-weight: 600;
|
|
680
|
+
background: #fff3cd;
|
|
681
|
+
color: #856404;
|
|
682
|
+
white-space: nowrap;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.violation-value {
|
|
686
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
687
|
+
font-size: 12px;
|
|
688
|
+
background: #f6f8fa;
|
|
689
|
+
padding: 2px 6px;
|
|
690
|
+
border-radius: 3px;
|
|
691
|
+
border: 1px solid #e1e4e8;
|
|
692
|
+
}
|
|
620
693
|
`;
|
|
621
694
|
exports.TAB_SCRIPT = String.raw `
|
|
622
695
|
// Tab switching
|
|
@@ -915,6 +988,18 @@ exports.TAB_SCRIPT = String.raw `
|
|
|
915
988
|
});
|
|
916
989
|
}
|
|
917
990
|
|
|
991
|
+
// Violations section toggle
|
|
992
|
+
const violationsToggleBtn = document.getElementById('violations-toggle-btn');
|
|
993
|
+
const violationsContent = document.getElementById('violations-content');
|
|
994
|
+
if (violationsToggleBtn && violationsContent) {
|
|
995
|
+
violationsToggleBtn.addEventListener('click', () => {
|
|
996
|
+
const isHidden = violationsContent.style.display === 'none';
|
|
997
|
+
violationsContent.style.display = isHidden ? 'block' : 'none';
|
|
998
|
+
const count = violationsToggleBtn.textContent.match(/\d+/)?.[0] || '0';
|
|
999
|
+
violationsToggleBtn.textContent = isHidden ? 'Hide Violations (' + count + ')' : 'Show Violations (' + count + ')';
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
918
1003
|
// Synchronized horizontal scrolling for side-by-side diff panels
|
|
919
1004
|
document.querySelectorAll('.d2h-files-diff').forEach(container => {
|
|
920
1005
|
const panels = container.querySelectorAll('.d2h-file-side-diff');
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { FileDiffResult } from '../fileDiff';
|
|
2
|
+
export interface HtmlStopRuleViolation {
|
|
3
|
+
file: string;
|
|
4
|
+
rule: {
|
|
5
|
+
type: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
};
|
|
8
|
+
path: string;
|
|
9
|
+
oldValue: unknown;
|
|
10
|
+
updatedValue: unknown;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
2
13
|
export interface ReportMetadata {
|
|
3
14
|
timestamp: string;
|
|
4
15
|
source: string;
|
|
@@ -14,4 +25,4 @@ export interface DiffStats {
|
|
|
14
25
|
removed: number;
|
|
15
26
|
}>;
|
|
16
27
|
}
|
|
17
|
-
export declare const generateHtmlTemplate: (diffResult: FileDiffResult, formattedFiles: string[], trulyUnchangedFiles: string[], metadata: ReportMetadata, changedSections: string[], changedFileIds?: Map<string, string>, addedSections?: string[], addedFileIds?: Map<string, string>, diffStats?: DiffStats) => string;
|
|
28
|
+
export declare const generateHtmlTemplate: (diffResult: FileDiffResult, formattedFiles: string[], trulyUnchangedFiles: string[], metadata: ReportMetadata, changedSections: string[], changedFileIds?: Map<string, string>, addedSections?: string[], addedFileIds?: Map<string, string>, diffStats?: DiffStats, stopRuleViolations?: HtmlStopRuleViolation[]) => string;
|
|
@@ -4,6 +4,46 @@ exports.generateHtmlTemplate = void 0;
|
|
|
4
4
|
const htmlStyles_1 = require("./htmlStyles");
|
|
5
5
|
const treeBuilder_1 = require("./treeBuilder");
|
|
6
6
|
const treeRenderer_1 = require("./treeRenderer");
|
|
7
|
+
const formatViolationValue = (value) => {
|
|
8
|
+
if (value === undefined || value === null)
|
|
9
|
+
return '<span class="violation-value">-</span>';
|
|
10
|
+
return `<span class="violation-value">${(0, treeRenderer_1.escapeHtml)(String(value))}</span>`;
|
|
11
|
+
};
|
|
12
|
+
const renderStopRulesSection = (violations) => {
|
|
13
|
+
if (violations.length === 0)
|
|
14
|
+
return '';
|
|
15
|
+
const rows = violations
|
|
16
|
+
.map((v) => `<tr>
|
|
17
|
+
<td>${(0, treeRenderer_1.escapeHtml)(v.file)}</td>
|
|
18
|
+
<td><span class="violation-rule-badge">${(0, treeRenderer_1.escapeHtml)(v.rule.type)}</span></td>
|
|
19
|
+
<td>${(0, treeRenderer_1.escapeHtml)(v.path)}</td>
|
|
20
|
+
<td>${formatViolationValue(v.oldValue)}</td>
|
|
21
|
+
<td>${formatViolationValue(v.updatedValue)}</td>
|
|
22
|
+
<td>${(0, treeRenderer_1.escapeHtml)(v.message)}</td>
|
|
23
|
+
</tr>`)
|
|
24
|
+
.join('');
|
|
25
|
+
return `
|
|
26
|
+
<div class="violations-section">
|
|
27
|
+
<button class="violations-toggle-btn" id="violations-toggle-btn">Show Violations (${violations.length})</button>
|
|
28
|
+
<div id="violations-content" style="display: none">
|
|
29
|
+
<table class="violations-table">
|
|
30
|
+
<thead>
|
|
31
|
+
<tr>
|
|
32
|
+
<th>File</th>
|
|
33
|
+
<th>Rule</th>
|
|
34
|
+
<th>Path</th>
|
|
35
|
+
<th>Old Value</th>
|
|
36
|
+
<th>New Value</th>
|
|
37
|
+
<th>Message</th>
|
|
38
|
+
</tr>
|
|
39
|
+
</thead>
|
|
40
|
+
<tbody>
|
|
41
|
+
${rows}
|
|
42
|
+
</tbody>
|
|
43
|
+
</table>
|
|
44
|
+
</div>
|
|
45
|
+
</div>`;
|
|
46
|
+
};
|
|
7
47
|
const renderStatsDashboard = (diffStats) => {
|
|
8
48
|
const total = diffStats.totalAdded + diffStats.totalRemoved;
|
|
9
49
|
if (total === 0)
|
|
@@ -31,7 +71,7 @@ const renderStatsDashboard = (diffStats) => {
|
|
|
31
71
|
</div>
|
|
32
72
|
</div>`;
|
|
33
73
|
};
|
|
34
|
-
const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds = new Map(), addedSections = [], addedFileIds = new Map(), diffStats) => {
|
|
74
|
+
const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds = new Map(), addedSections = [], addedFileIds = new Map(), diffStats, stopRuleViolations) => {
|
|
35
75
|
const changedFilePaths = diffResult.changedFiles.map((f) => f.path);
|
|
36
76
|
const changedTree = (0, treeBuilder_1.buildFileTree)(changedFilePaths);
|
|
37
77
|
const addedFilePaths = diffResult.addedFiles.map((f) => f.path);
|
|
@@ -48,9 +88,11 @@ const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, m
|
|
|
48
88
|
];
|
|
49
89
|
const activeCategories = categories.filter((c) => c.count > 0);
|
|
50
90
|
const firstActiveTab = activeCategories[0]?.id ?? 'changed';
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
const violationsBadge = stopRuleViolations && stopRuleViolations.length > 0
|
|
92
|
+
? `<span class="stat violations">${stopRuleViolations.length} Violation${stopRuleViolations.length === 1 ? '' : 's'}</span>`
|
|
93
|
+
: '';
|
|
94
|
+
const summaryBadges = activeCategories.map((c) => `<span class="stat ${c.id}">${c.count} ${c.label}</span>`).join('\n ') +
|
|
95
|
+
(violationsBadge ? `\n ${violationsBadge}` : '');
|
|
54
96
|
const tabButtons = activeCategories
|
|
55
97
|
.map((c) => `<button class="tab${c.id === firstActiveTab ? ' active' : ''}" data-tab="${c.id}">${c.label} (${c.count})</button>`)
|
|
56
98
|
.join('\n ');
|
|
@@ -140,6 +182,7 @@ ${htmlStyles_1.HTML_STYLES}
|
|
|
140
182
|
${summaryBadges}
|
|
141
183
|
</div>
|
|
142
184
|
${diffStats ? renderStatsDashboard(diffStats) : ''}
|
|
185
|
+
${stopRuleViolations && stopRuleViolations.length > 0 ? renderStopRulesSection(stopRuleViolations) : ''}
|
|
143
186
|
</header>
|
|
144
187
|
|
|
145
188
|
<nav class="tabs">
|
package/dist/yamlFormatter.js
CHANGED
|
@@ -20,12 +20,16 @@ exports.YamlFormatterError = (0, errors_1.createErrorClass)('YAML Formatter Erro
|
|
|
20
20
|
exports.isYamlFormatterError = (0, errors_1.createErrorTypeGuard)(exports.YamlFormatterError);
|
|
21
21
|
const getFormattingRules = (filePath, outputFormat) => {
|
|
22
22
|
const keyOrders = [];
|
|
23
|
+
const keySort = [];
|
|
23
24
|
const arraySort = [];
|
|
24
25
|
const quoteValues = [];
|
|
25
26
|
const allPatterns = new Set();
|
|
26
27
|
if (outputFormat?.keyOrders)
|
|
27
28
|
for (const pattern of Object.keys(outputFormat.keyOrders))
|
|
28
29
|
allPatterns.add(pattern);
|
|
30
|
+
if (outputFormat?.keySort)
|
|
31
|
+
for (const pattern of Object.keys(outputFormat.keySort))
|
|
32
|
+
allPatterns.add(pattern);
|
|
29
33
|
if (outputFormat?.arraySort)
|
|
30
34
|
for (const pattern of Object.keys(outputFormat.arraySort))
|
|
31
35
|
allPatterns.add(pattern);
|
|
@@ -38,6 +42,9 @@ const getFormattingRules = (filePath, outputFormat) => {
|
|
|
38
42
|
const keyOrder = outputFormat?.keyOrders?.[pattern];
|
|
39
43
|
if (keyOrder)
|
|
40
44
|
keyOrders.push(keyOrder);
|
|
45
|
+
const keySortRule = outputFormat?.keySort?.[pattern];
|
|
46
|
+
if (keySortRule)
|
|
47
|
+
keySort.push(keySortRule);
|
|
41
48
|
const arrayRule = outputFormat?.arraySort?.[pattern];
|
|
42
49
|
if (arrayRule)
|
|
43
50
|
arraySort.push(arrayRule);
|
|
@@ -45,7 +52,7 @@ const getFormattingRules = (filePath, outputFormat) => {
|
|
|
45
52
|
if (quoteValue)
|
|
46
53
|
quoteValues.push(quoteValue);
|
|
47
54
|
}
|
|
48
|
-
return { keyOrders, arraySort, quoteValues };
|
|
55
|
+
return { keyOrders, keySort, arraySort, quoteValues };
|
|
49
56
|
};
|
|
50
57
|
const preserveMultilineStrings = (yamlDocument) => {
|
|
51
58
|
if (!yamlDocument.contents)
|
|
@@ -81,6 +88,8 @@ const formatYaml = (content, filePath, outputFormat) => {
|
|
|
81
88
|
const rules = getFormattingRules(filePath, outputFormat);
|
|
82
89
|
if (rules.keyOrders.length > 0)
|
|
83
90
|
applyKeyOrdering(yamlDocument, rules.keyOrders);
|
|
91
|
+
if (rules.keySort.length > 0)
|
|
92
|
+
applyKeySort(yamlDocument, rules.keySort);
|
|
84
93
|
if (rules.arraySort.length > 0)
|
|
85
94
|
applyArraySorting(yamlDocument, rules.arraySort);
|
|
86
95
|
if (rules.quoteValues.length > 0)
|
|
@@ -171,6 +180,43 @@ const applyOrderingToMap = (map, currentPath, orderHierarchy) => {
|
|
|
171
180
|
}
|
|
172
181
|
}
|
|
173
182
|
};
|
|
183
|
+
const applyKeySort = (yamlDocument, sortRules) => {
|
|
184
|
+
if (sortRules.length === 0)
|
|
185
|
+
return;
|
|
186
|
+
const allRules = sortRules.flat();
|
|
187
|
+
for (const rule of allRules) {
|
|
188
|
+
const pathParts = (0, jsonPath_1.parseJsonPath)(rule.path);
|
|
189
|
+
if (pathParts.length === 0)
|
|
190
|
+
continue;
|
|
191
|
+
if (yamlDocument.contents)
|
|
192
|
+
traverseAndSortKeys(yamlDocument.contents, [], pathParts);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const traverseAndSortKeys = (node, currentPath, targetPath) => {
|
|
196
|
+
if (!node || typeof node !== 'object')
|
|
197
|
+
return;
|
|
198
|
+
if (matchPath(currentPath, targetPath)) {
|
|
199
|
+
if ((0, yamlTypeGuards_1.isYamlMap)(node))
|
|
200
|
+
sortMapKeysAlphabetically(node);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if ((0, yamlTypeGuards_1.isYamlMap)(node))
|
|
204
|
+
for (const item of node.items) {
|
|
205
|
+
const keyValue = (0, yamlTypeGuards_1.extractKeyValue)(item);
|
|
206
|
+
if (keyValue && item.value) {
|
|
207
|
+
const childPath = [...currentPath, keyValue];
|
|
208
|
+
if (isPotentialMatch(childPath, targetPath))
|
|
209
|
+
traverseAndSortKeys(item.value, childPath, targetPath);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const sortMapKeysAlphabetically = (map) => {
|
|
214
|
+
map.items.sort((a, b) => {
|
|
215
|
+
const aKey = a.key && typeof a.key === 'object' && 'value' in a.key ? String(a.key.value) : '';
|
|
216
|
+
const bKey = b.key && typeof b.key === 'object' && 'value' in b.key ? String(b.key.value) : '';
|
|
217
|
+
return aKey.localeCompare(bKey);
|
|
218
|
+
});
|
|
219
|
+
};
|
|
174
220
|
const applyValueQuoting = (yamlDocument, quoteLists) => {
|
|
175
221
|
if (quoteLists.length === 0)
|
|
176
222
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helm-env-delta",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "HelmEnvDelta – environment-aware YAML delta and sync for GitOps",
|
|
5
5
|
"author": "BCsabaEngine",
|
|
6
6
|
"license": "ISC",
|
|
@@ -68,15 +68,15 @@
|
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/hogan.js": "^3.0.5",
|
|
71
|
-
"@types/node": "^25.2.
|
|
71
|
+
"@types/node": "^25.2.3",
|
|
72
72
|
"@types/picomatch": "^4.0.2",
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
74
|
-
"@typescript-eslint/parser": "^8.
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
74
|
+
"@typescript-eslint/parser": "^8.55.0",
|
|
75
75
|
"@vitest/coverage-v8": "^4.0.18",
|
|
76
76
|
"eslint": "^9.39.2",
|
|
77
77
|
"eslint-config-prettier": "^10.1.8",
|
|
78
78
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
79
|
-
"eslint-plugin-unicorn": "^
|
|
79
|
+
"eslint-plugin-unicorn": "^63.0.0",
|
|
80
80
|
"prettier": "^3.8.1",
|
|
81
81
|
"tsx": "^4.21.0",
|
|
82
82
|
"typescript": "^5.9.3",
|