helm-env-delta 1.11.0 → 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 +130 -6
- package/dist/reporters/htmlTemplate.d.ts +12 -4
- package/dist/reporters/htmlTemplate.js +130 -89
- package/dist/reporters/treeRenderer.d.ts +1 -4
- package/dist/reporters/treeRenderer.js +5 -11
- 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 diff stats dashboard,
|
|
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 diff stats,
|
|
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 .sidebar-tree .line-badge {\n font-size: 10px;\n padding: 0 5px;\n line-height: 16px;\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 /* 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;
|
|
@@ -471,12 +471,6 @@ exports.HTML_STYLES = `
|
|
|
471
471
|
color: #721c24;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
.sidebar-tree .line-badge {
|
|
475
|
-
font-size: 10px;
|
|
476
|
-
padding: 0 5px;
|
|
477
|
-
line-height: 16px;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
474
|
/* Diff toolbar */
|
|
481
475
|
.diff-toolbar {
|
|
482
476
|
display: flex;
|
|
@@ -531,6 +525,23 @@ exports.HTML_STYLES = `
|
|
|
531
525
|
box-shadow: 0 0 0 3px rgba(9,105,218,0.15);
|
|
532
526
|
}
|
|
533
527
|
|
|
528
|
+
/* Stats toggle button */
|
|
529
|
+
.stats-toggle-btn {
|
|
530
|
+
padding: 4px 12px;
|
|
531
|
+
border: 1px solid #d0d7de;
|
|
532
|
+
border-radius: 4px;
|
|
533
|
+
background: none;
|
|
534
|
+
cursor: pointer;
|
|
535
|
+
font-size: 12px;
|
|
536
|
+
color: #586069;
|
|
537
|
+
transition: all 0.2s;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.stats-toggle-btn:hover {
|
|
541
|
+
background: #f6f8fa;
|
|
542
|
+
color: #24292e;
|
|
543
|
+
}
|
|
544
|
+
|
|
534
545
|
/* Statistics dashboard */
|
|
535
546
|
.stats-dashboard {
|
|
536
547
|
margin: 15px 0 0;
|
|
@@ -606,6 +617,79 @@ exports.HTML_STYLES = `
|
|
|
606
617
|
white-space: nowrap;
|
|
607
618
|
flex-shrink: 0;
|
|
608
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
|
+
}
|
|
609
693
|
`;
|
|
610
694
|
exports.TAB_SCRIPT = String.raw `
|
|
611
695
|
// Tab switching
|
|
@@ -892,4 +976,44 @@ exports.TAB_SCRIPT = String.raw `
|
|
|
892
976
|
});
|
|
893
977
|
});
|
|
894
978
|
});
|
|
979
|
+
|
|
980
|
+
// Stats dashboard toggle
|
|
981
|
+
const statsToggleBtn = document.getElementById('stats-toggle-btn');
|
|
982
|
+
const statsDashboardContent = document.getElementById('stats-dashboard-content');
|
|
983
|
+
if (statsToggleBtn && statsDashboardContent) {
|
|
984
|
+
statsToggleBtn.addEventListener('click', () => {
|
|
985
|
+
const isHidden = statsDashboardContent.style.display === 'none';
|
|
986
|
+
statsDashboardContent.style.display = isHidden ? 'block' : 'none';
|
|
987
|
+
statsToggleBtn.textContent = isHidden ? 'Hide Details' : 'Show Details';
|
|
988
|
+
});
|
|
989
|
+
}
|
|
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
|
+
|
|
1003
|
+
// Synchronized horizontal scrolling for side-by-side diff panels
|
|
1004
|
+
document.querySelectorAll('.d2h-files-diff').forEach(container => {
|
|
1005
|
+
const panels = container.querySelectorAll('.d2h-file-side-diff');
|
|
1006
|
+
if (panels.length === 2) {
|
|
1007
|
+
let isSyncing = false;
|
|
1008
|
+
panels.forEach((panel, index) => {
|
|
1009
|
+
panel.addEventListener('scroll', () => {
|
|
1010
|
+
if (isSyncing) return;
|
|
1011
|
+
isSyncing = true;
|
|
1012
|
+
const other = panels[1 - index];
|
|
1013
|
+
other.scrollLeft = panel.scrollLeft;
|
|
1014
|
+
isSyncing = false;
|
|
1015
|
+
});
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
895
1019
|
`;
|
|
@@ -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,7 +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>,
|
|
18
|
-
added: number;
|
|
19
|
-
removed: number;
|
|
20
|
-
}>, 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,31 +4,74 @@ 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)
|
|
10
50
|
return '';
|
|
11
51
|
const addedPercent = Math.round((diffStats.totalAdded / total) * 100);
|
|
12
52
|
const removedPercent = 100 - addedPercent;
|
|
13
|
-
const
|
|
14
|
-
const topFilesHtml =
|
|
53
|
+
const top10 = diffStats.fileStats.slice(0, 10);
|
|
54
|
+
const topFilesHtml = top10
|
|
15
55
|
.map((f) => `<li><span class="file-path">${(0, treeRenderer_1.escapeHtml)(f.path)}</span><span class="file-stats"><span class="line-badge line-added">+${f.added}</span> <span class="line-badge line-removed">-${f.removed}</span></span></li>`)
|
|
16
56
|
.join('');
|
|
17
57
|
return `
|
|
18
58
|
<div class="stats-dashboard">
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<div class="stats-
|
|
59
|
+
<button class="stats-toggle-btn" id="stats-toggle-btn">Show Details</button>
|
|
60
|
+
<div id="stats-dashboard-content" style="display: none">
|
|
61
|
+
<div class="stats-summary">
|
|
62
|
+
<span class="total-added">+${diffStats.totalAdded}</span>
|
|
63
|
+
<span class="total-removed">-${diffStats.totalRemoved}</span>
|
|
64
|
+
<span style="color: #586069; font-size: 13px;">lines across ${diffStats.fileStats.length} file${diffStats.fileStats.length === 1 ? '' : 's'}</span>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="stats-bar">
|
|
67
|
+
<div class="stats-segment added-segment" style="width: ${addedPercent}%"></div>
|
|
68
|
+
<div class="stats-segment removed-segment" style="width: ${removedPercent}%"></div>
|
|
69
|
+
</div>
|
|
70
|
+
${top10.length > 0 ? `<ul class="top-changed-files">${topFilesHtml}</ul>` : ''}
|
|
27
71
|
</div>
|
|
28
|
-
${top5.length > 0 ? `<ul class="top-changed-files">${topFilesHtml}</ul>` : ''}
|
|
29
72
|
</div>`;
|
|
30
73
|
};
|
|
31
|
-
const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds = new Map(), addedSections = [], addedFileIds = new Map(),
|
|
74
|
+
const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds = new Map(), addedSections = [], addedFileIds = new Map(), diffStats, stopRuleViolations) => {
|
|
32
75
|
const changedFilePaths = diffResult.changedFiles.map((f) => f.path);
|
|
33
76
|
const changedTree = (0, treeBuilder_1.buildFileTree)(changedFilePaths);
|
|
34
77
|
const addedFilePaths = diffResult.addedFiles.map((f) => f.path);
|
|
@@ -36,47 +79,33 @@ const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, m
|
|
|
36
79
|
const deletedTree = (0, treeBuilder_1.buildFileTree)(diffResult.deletedFiles);
|
|
37
80
|
const formattedTree = (0, treeBuilder_1.buildFileTree)(formattedFiles);
|
|
38
81
|
const unchangedTree = (0, treeBuilder_1.buildFileTree)(trulyUnchangedFiles);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
${diffStats ? renderStatsDashboard(diffStats) : ''}
|
|
67
|
-
</header>
|
|
68
|
-
|
|
69
|
-
<nav class="tabs">
|
|
70
|
-
<button class="tab active" data-tab="changed">Changed (${diffResult.changedFiles.length})</button>
|
|
71
|
-
<button class="tab" data-tab="added">Added (${diffResult.addedFiles.length})</button>
|
|
72
|
-
<button class="tab" data-tab="deleted">Deleted (${diffResult.deletedFiles.length})</button>
|
|
73
|
-
<button class="tab" data-tab="formatted">Formatted (${formattedFiles.length})</button>
|
|
74
|
-
<button class="tab" data-tab="unchanged">Unchanged (${trulyUnchangedFiles.length})</button>
|
|
75
|
-
</nav>
|
|
76
|
-
|
|
77
|
-
<main>
|
|
78
|
-
<section id="changed" class="tab-content active">
|
|
79
|
-
${changedSections.length > 0
|
|
82
|
+
const categories = [
|
|
83
|
+
{ id: 'changed', label: 'Changed', count: diffResult.changedFiles.length },
|
|
84
|
+
{ id: 'added', label: 'Added', count: diffResult.addedFiles.length },
|
|
85
|
+
{ id: 'deleted', label: 'Deleted', count: diffResult.deletedFiles.length },
|
|
86
|
+
{ id: 'formatted', label: 'Formatted', count: formattedFiles.length },
|
|
87
|
+
{ id: 'unchanged', label: 'Unchanged', count: trulyUnchangedFiles.length }
|
|
88
|
+
];
|
|
89
|
+
const activeCategories = categories.filter((c) => c.count > 0);
|
|
90
|
+
const firstActiveTab = activeCategories[0]?.id ?? 'changed';
|
|
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}` : '');
|
|
96
|
+
const tabButtons = activeCategories
|
|
97
|
+
.map((c) => `<button class="tab${c.id === firstActiveTab ? ' active' : ''}" data-tab="${c.id}">${c.label} (${c.count})</button>`)
|
|
98
|
+
.join('\n ');
|
|
99
|
+
const renderSection = (id, content) => {
|
|
100
|
+
const cat = categories.find((c) => c.id === id);
|
|
101
|
+
if (!cat || cat.count === 0)
|
|
102
|
+
return '';
|
|
103
|
+
return `
|
|
104
|
+
<section id="${id}" class="tab-content${id === firstActiveTab ? ' active' : ''}">
|
|
105
|
+
${content}
|
|
106
|
+
</section>`;
|
|
107
|
+
};
|
|
108
|
+
const changedContent = changedSections.length > 0
|
|
80
109
|
? `
|
|
81
110
|
<div class="sidebar-container">
|
|
82
111
|
<aside class="sidebar" id="changed-sidebar">
|
|
@@ -86,7 +115,7 @@ ${htmlStyles_1.HTML_STYLES}
|
|
|
86
115
|
</div>
|
|
87
116
|
<div class="sidebar-content">
|
|
88
117
|
<input type="text" class="sidebar-search" placeholder="Filter files..." />
|
|
89
|
-
${(0, treeRenderer_1.renderSidebarTree)(changedTree, changedFileIds
|
|
118
|
+
${(0, treeRenderer_1.renderSidebarTree)(changedTree, changedFileIds)}
|
|
90
119
|
</div>
|
|
91
120
|
</aside>
|
|
92
121
|
<button class="sidebar-expand-btn">▶</button>
|
|
@@ -99,11 +128,8 @@ ${htmlStyles_1.HTML_STYLES}
|
|
|
99
128
|
</div>
|
|
100
129
|
</div>
|
|
101
130
|
`
|
|
102
|
-
: '<p style="color: #586069; text-align: center; padding: 40px;">No changed files</p>'
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<section id="added" class="tab-content">
|
|
106
|
-
${addedSections.length > 0
|
|
131
|
+
: '<p style="color: #586069; text-align: center; padding: 40px;">No changed files</p>';
|
|
132
|
+
const addedContent = addedSections.length > 0
|
|
107
133
|
? `
|
|
108
134
|
<div class="sidebar-container">
|
|
109
135
|
<aside class="sidebar" id="added-sidebar">
|
|
@@ -122,38 +148,53 @@ ${htmlStyles_1.HTML_STYLES}
|
|
|
122
148
|
</div>
|
|
123
149
|
</div>
|
|
124
150
|
`
|
|
125
|
-
: '<p style="color: #586069; text-align: center; padding: 40px;">No added files</p>'
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
?
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
</div
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
: '<p style="color: #586069; text-align: center; padding: 40px;">No added files</p>';
|
|
152
|
+
const deletedContent = diffResult.deletedFiles.length > 0
|
|
153
|
+
? `<div class="file-list">${(0, treeRenderer_1.renderTreeview)(deletedTree)}</div>`
|
|
154
|
+
: '<p style="color: #586069; text-align: center; padding: 40px;">No deleted files</p>';
|
|
155
|
+
const formattedContent = formattedFiles.length > 0
|
|
156
|
+
? `<div class="file-list">${(0, treeRenderer_1.renderTreeview)(formattedTree)}</div>`
|
|
157
|
+
: '<p style="color: #586069; text-align: center; padding: 40px;">No files with only formatting changes</p>';
|
|
158
|
+
const unchangedContent = trulyUnchangedFiles.length > 0
|
|
159
|
+
? `<div class="file-list">${(0, treeRenderer_1.renderTreeview)(unchangedTree)}</div>`
|
|
160
|
+
: '<p style="color: #586069; text-align: center; padding: 40px;">No unchanged files</p>';
|
|
161
|
+
return `
|
|
162
|
+
<!DOCTYPE html>
|
|
163
|
+
<html>
|
|
164
|
+
<head>
|
|
165
|
+
<meta charset="UTF-8">
|
|
166
|
+
<title>helm-env-delta Report - ${metadata.timestamp}</title>
|
|
167
|
+
<style>${htmlStyles_1.DIFF2HTML_STYLES}</style>
|
|
168
|
+
<style>
|
|
169
|
+
${htmlStyles_1.HTML_STYLES}
|
|
170
|
+
</style>
|
|
171
|
+
</head>
|
|
172
|
+
<body>
|
|
173
|
+
<header>
|
|
174
|
+
<h1>helm-env-delta Diff Report</h1>
|
|
175
|
+
<div class="metadata">
|
|
176
|
+
<span>📅 Generated: ${metadata.timestamp}</span>
|
|
177
|
+
<span>📂 Source: ${metadata.source}</span>
|
|
178
|
+
<span>🎯 Destination: ${metadata.destination}</span>
|
|
179
|
+
${metadata.dryRun ? '<span class="dry-run-badge">DRY RUN - No Files Modified</span>' : ''}
|
|
180
|
+
</div>
|
|
181
|
+
<div class="summary">
|
|
182
|
+
${summaryBadges}
|
|
183
|
+
</div>
|
|
184
|
+
${diffStats ? renderStatsDashboard(diffStats) : ''}
|
|
185
|
+
${stopRuleViolations && stopRuleViolations.length > 0 ? renderStopRulesSection(stopRuleViolations) : ''}
|
|
186
|
+
</header>
|
|
137
187
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<div class="file-list">
|
|
142
|
-
${(0, treeRenderer_1.renderTreeview)(formattedTree)}
|
|
143
|
-
</div>
|
|
144
|
-
`
|
|
145
|
-
: '<p style="color: #586069; text-align: center; padding: 40px;">No files with only formatting changes</p>'}
|
|
146
|
-
</section>
|
|
188
|
+
<nav class="tabs">
|
|
189
|
+
${tabButtons}
|
|
190
|
+
</nav>
|
|
147
191
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`
|
|
155
|
-
: '<p style="color: #586069; text-align: center; padding: 40px;">No unchanged files</p>'}
|
|
156
|
-
</section>
|
|
192
|
+
<main>
|
|
193
|
+
${renderSection('changed', changedContent)}
|
|
194
|
+
${renderSection('added', addedContent)}
|
|
195
|
+
${renderSection('deleted', deletedContent)}
|
|
196
|
+
${renderSection('formatted', formattedContent)}
|
|
197
|
+
${renderSection('unchanged', unchangedContent)}
|
|
157
198
|
</main>
|
|
158
199
|
|
|
159
200
|
<button class="scroll-to-top" title="Scroll to top">▲</button>
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { TreeNode } from './treeBuilder';
|
|
2
2
|
export declare const renderTreeview: (nodes: TreeNode[]) => string;
|
|
3
|
-
export declare const renderSidebarTree: (nodes: TreeNode[], fileIds: Map<string, string
|
|
4
|
-
added: number;
|
|
5
|
-
removed: number;
|
|
6
|
-
}>) => string;
|
|
3
|
+
export declare const renderSidebarTree: (nodes: TreeNode[], fileIds: Map<string, string>) => string;
|
|
7
4
|
export declare const escapeHtml: (text: string) => string;
|
|
@@ -22,17 +22,15 @@ const renderTreeNode = (node) => {
|
|
|
22
22
|
<span class="tree-file-name">${(0, exports.escapeHtml)(node.name)}</span>
|
|
23
23
|
</li>`;
|
|
24
24
|
};
|
|
25
|
-
const renderSidebarTree = (nodes, fileIds
|
|
25
|
+
const renderSidebarTree = (nodes, fileIds) => {
|
|
26
26
|
if (nodes.length === 0)
|
|
27
27
|
return '';
|
|
28
|
-
return `<ul class="tree-root sidebar-tree">${nodes.map((node) => renderSidebarNode(node, fileIds
|
|
28
|
+
return `<ul class="tree-root sidebar-tree">${nodes.map((node) => renderSidebarNode(node, fileIds)).join('')}</ul>`;
|
|
29
29
|
};
|
|
30
30
|
exports.renderSidebarTree = renderSidebarTree;
|
|
31
|
-
const renderSidebarNode = (node, fileIds
|
|
31
|
+
const renderSidebarNode = (node, fileIds) => {
|
|
32
32
|
if (node.isFolder) {
|
|
33
|
-
const children = node.children
|
|
34
|
-
? node.children.map((child) => renderSidebarNode(child, fileIds, fileStats)).join('')
|
|
35
|
-
: '';
|
|
33
|
+
const children = node.children ? node.children.map((child) => renderSidebarNode(child, fileIds)).join('') : '';
|
|
36
34
|
return `
|
|
37
35
|
<li class="tree-folder" data-path="${(0, exports.escapeHtml)(node.path)}">
|
|
38
36
|
<span class="tree-toggle">▼</span>
|
|
@@ -41,13 +39,9 @@ const renderSidebarNode = (node, fileIds, fileStats) => {
|
|
|
41
39
|
</li>`;
|
|
42
40
|
}
|
|
43
41
|
const fileId = fileIds.get(node.path) || '';
|
|
44
|
-
const stats = fileStats?.get(node.path);
|
|
45
|
-
const badges = stats
|
|
46
|
-
? ` <span class="line-badge line-added">+${stats.added}</span><span class="line-badge line-removed">-${stats.removed}</span>`
|
|
47
|
-
: '';
|
|
48
42
|
return `
|
|
49
43
|
<li class="tree-file" data-path="${(0, exports.escapeHtml)(node.path)}" data-file-id="${(0, exports.escapeHtml)(fileId)}">
|
|
50
|
-
<a href="#${(0, exports.escapeHtml)(fileId)}" class="tree-file-link">${(0, exports.escapeHtml)(node.name)}</a
|
|
44
|
+
<a href="#${(0, exports.escapeHtml)(fileId)}" class="tree-file-link">${(0, exports.escapeHtml)(node.name)}</a>
|
|
51
45
|
</li>`;
|
|
52
46
|
};
|
|
53
47
|
const escapeHtml = (text) => text
|
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",
|