@redpanda-data/docs-extensions-and-macros 4.14.1 → 4.15.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/bin/doc-tools.js
CHANGED
|
@@ -783,7 +783,7 @@ automation
|
|
|
783
783
|
)
|
|
784
784
|
.option('-t, --tag <tag>', 'Git tag for released content (GA/beta)')
|
|
785
785
|
.option('-b, --branch <branch>', 'Branch name for in-progress content')
|
|
786
|
-
.option('--diff <oldTag>', '
|
|
786
|
+
.option('--diff <oldTag>', 'Diff properties against <oldTag> and restore removed deprecated properties. Recommended for accurate output; falls back to latest-redpanda-tag from antora.yml if not specified')
|
|
787
787
|
.option('--overrides <path>', 'Optional JSON file with property description overrides', 'docs-data/property-overrides.json')
|
|
788
788
|
.option('--output-dir <dir>', 'Where to write all generated files', 'modules/reference')
|
|
789
789
|
.option('--cloud-support', 'Add AsciiDoc tags to generated property docs to indicate which ones are supported in Redpanda Cloud. This data is fetched from the cloudv2 repository so requires a GitHub token with repo permissions. Set the token as an environment variable using GITHUB_TOKEN, GH_TOKEN, or REDPANDA_GITHUB_TOKEN', true)
|
|
@@ -826,11 +826,17 @@ automation
|
|
|
826
826
|
}
|
|
827
827
|
}
|
|
828
828
|
|
|
829
|
+
if (!oldTag) {
|
|
830
|
+
console.warn('Warning: No previous version specified (--diff) and no latest-redpanda-tag found in Antora attributes.')
|
|
831
|
+
console.warn(' Deprecated properties that were removed from source (v26.1+) will not be detected.')
|
|
832
|
+
console.warn(' For accurate output, specify --diff <previous-tag> or set latest-redpanda-tag in antora.yml.')
|
|
833
|
+
}
|
|
834
|
+
|
|
829
835
|
const overridesPath = options.overrides
|
|
830
836
|
const outputDir = options.outputDir
|
|
831
837
|
const cwd = path.resolve(__dirname, '../tools/property-extractor')
|
|
832
838
|
|
|
833
|
-
const make = (tag, overrides, templates = {}, outDir = 'modules/reference/') => {
|
|
839
|
+
const make = (tag, overrides, templates = {}, outDir = 'modules/reference/', { skipPartials = false } = {}) => {
|
|
834
840
|
console.log(`Building property docs for ${tag}…`)
|
|
835
841
|
const args = ['build', `TAG=${tag}`]
|
|
836
842
|
const env = { ...process.env }
|
|
@@ -843,7 +849,7 @@ automation
|
|
|
843
849
|
if (templates.deprecatedProperty) env.TEMPLATE_DEPRECATED_PROPERTY = path.resolve(templates.deprecatedProperty)
|
|
844
850
|
env.OUTPUT_JSON_DIR = path.resolve(outDir, 'attachments')
|
|
845
851
|
env.OUTPUT_AUTOGENERATED_DIR = path.resolve(outDir)
|
|
846
|
-
if (options.generatePartials) {
|
|
852
|
+
if (options.generatePartials && !skipPartials) {
|
|
847
853
|
env.GENERATE_PARTIALS = '1'
|
|
848
854
|
env.OUTPUT_PARTIALS_DIR = path.resolve(outDir, options.partialsDir || 'partials')
|
|
849
855
|
}
|
|
@@ -864,11 +870,21 @@ automation
|
|
|
864
870
|
}
|
|
865
871
|
|
|
866
872
|
const tagsAreSame = oldTag && newTag && oldTag === newTag
|
|
867
|
-
|
|
868
|
-
|
|
873
|
+
const needsDiff = oldTag && !tagsAreSame
|
|
874
|
+
|
|
875
|
+
// Phase 1: Extract JSON from C++ source.
|
|
876
|
+
// When a diff is needed, skip AsciiDoc generation during extraction so we
|
|
877
|
+
// can merge removed deprecated properties first and generate only once.
|
|
878
|
+
if (needsDiff) {
|
|
879
|
+
make(oldTag, overridesPath, templates, outputDir, { skipPartials: true })
|
|
880
|
+
make(newTag, overridesPath, templates, outputDir, { skipPartials: true })
|
|
881
|
+
} else {
|
|
882
|
+
make(newTag, overridesPath, templates, outputDir)
|
|
869
883
|
}
|
|
870
|
-
|
|
871
|
-
|
|
884
|
+
|
|
885
|
+
// Phase 2: Compare old vs new and merge removed deprecated properties into
|
|
886
|
+
// the new JSON so they appear in the generated documentation.
|
|
887
|
+
if (needsDiff) {
|
|
872
888
|
const diffOutputDir = overridesPath ? path.dirname(path.resolve(overridesPath)) : outputDir
|
|
873
889
|
generatePropertyComparisonReport(oldTag, newTag, diffOutputDir)
|
|
874
890
|
|
|
@@ -888,6 +904,18 @@ automation
|
|
|
888
904
|
}
|
|
889
905
|
|
|
890
906
|
cleanupOldDiffs(diffOutputDir)
|
|
907
|
+
|
|
908
|
+
// Phase 3: Generate AsciiDoc once from the complete JSON (includes merged deprecated properties)
|
|
909
|
+
if (options.generatePartials) {
|
|
910
|
+
const updatedJsonPath = path.resolve(outputDir, 'attachments', `redpanda-properties-${newTag}.json`)
|
|
911
|
+
if (fs.existsSync(updatedJsonPath)) {
|
|
912
|
+
process.env.GENERATE_PARTIALS = '1'
|
|
913
|
+
process.env.OUTPUT_PARTIALS_DIR = path.resolve(outputDir, options.partialsDir || 'partials')
|
|
914
|
+
const { generateAllDocs } = require('../tools/property-extractor/generate-handlebars-docs')
|
|
915
|
+
console.log('Generating AsciiDoc from complete property data…')
|
|
916
|
+
generateAllDocs(updatedJsonPath, path.resolve(outputDir))
|
|
917
|
+
}
|
|
918
|
+
}
|
|
891
919
|
}
|
|
892
920
|
|
|
893
921
|
if (!options.diff && !tagsAreSame) {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts markdown from llms.adoc page and generates llms.txt and llms-full.txt.
|
|
5
|
+
*
|
|
6
|
+
* This extension:
|
|
7
|
+
* 1. Adds site-url attribute to home component:
|
|
8
|
+
* - In preview builds (PREVIEW=true): Uses DEPLOY_PRIME_URL
|
|
9
|
+
* - In production builds: Uses playbook.site.url
|
|
10
|
+
* 2. Finds llms page in home component (after AsciiDoc processing)
|
|
11
|
+
* 3. Gets the markdown content from page.markdownContents (set by convert-to-markdown extension)
|
|
12
|
+
* 4. Unpublishes the HTML page
|
|
13
|
+
* 5. Places llms.txt (markdown) at site root
|
|
14
|
+
* 6. Generates llms-full.txt with markdown from latest versions
|
|
15
|
+
*
|
|
16
|
+
* Must run after convert-to-markdown extension to access page.markdownContents.
|
|
17
|
+
*/
|
|
18
|
+
module.exports.register = function () {
|
|
19
|
+
const logger = this.getLogger('convert-llms-to-txt-extension');
|
|
20
|
+
let siteUrl = '';
|
|
21
|
+
|
|
22
|
+
// Add site-url attribute to home component
|
|
23
|
+
this.on('playbookBuilt', ({ playbook }) => {
|
|
24
|
+
// In preview builds, always use the deploy preview URL
|
|
25
|
+
if (process.env.PREVIEW === 'true' && process.env.DEPLOY_PRIME_URL) {
|
|
26
|
+
siteUrl = process.env.DEPLOY_PRIME_URL;
|
|
27
|
+
logger.info(`Using deploy preview URL: ${siteUrl}`);
|
|
28
|
+
} else {
|
|
29
|
+
siteUrl = playbook.site?.url || 'https://docs.redpanda.com';
|
|
30
|
+
logger.info(`Using site URL: ${siteUrl}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.on('contentClassified', ({ contentCatalog }) => {
|
|
35
|
+
// Add site-url attribute to home component
|
|
36
|
+
const homeComponent = contentCatalog.getComponents().find(c => c.name === 'home');
|
|
37
|
+
if (homeComponent && homeComponent.versions) {
|
|
38
|
+
homeComponent.versions.forEach(version => {
|
|
39
|
+
if (!version.asciidoc) version.asciidoc = {};
|
|
40
|
+
if (!version.asciidoc.attributes) version.asciidoc.attributes = {};
|
|
41
|
+
version.asciidoc.attributes['site-url'] = siteUrl;
|
|
42
|
+
logger.debug(`Added site-url attribute to home component: ${siteUrl}`);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Run after pagesComposed so convert-to-markdown has already run
|
|
48
|
+
this.on('beforePublish', ({ contentCatalog, siteCatalog }) => {
|
|
49
|
+
// Find llms.adoc page in home component (after markdown conversion)
|
|
50
|
+
const llmsPage = contentCatalog.findBy({
|
|
51
|
+
component: 'home',
|
|
52
|
+
family: 'page',
|
|
53
|
+
}).find(page => page.src.stem === 'llms');
|
|
54
|
+
|
|
55
|
+
if (!llmsPage) {
|
|
56
|
+
logger.warn('No llms page found, skipping llms.txt generation');
|
|
57
|
+
} else {
|
|
58
|
+
logger.info(`Found llms page: ${llmsPage.src.path}`);
|
|
59
|
+
logger.info(`Has markdownContents: ${!!llmsPage.markdownContents}`);
|
|
60
|
+
logger.info(`Has out: ${!!llmsPage.out}`);
|
|
61
|
+
try {
|
|
62
|
+
// The convert-to-markdown extension has already processed this page
|
|
63
|
+
// and stored the markdown in page.markdownContents
|
|
64
|
+
if (!llmsPage.markdownContents) {
|
|
65
|
+
throw new Error('No markdown content found on llms page. Ensure convert-to-markdown extension runs before this extension.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let content = llmsPage.markdownContents.toString('utf8');
|
|
69
|
+
logger.info(`Extracted ${content.length} bytes of markdown content`);
|
|
70
|
+
|
|
71
|
+
// Strip HTML comments added by convert-to-markdown extension
|
|
72
|
+
// These reference the unpublished /home/llms/ URL which doesn't make sense for llms.txt
|
|
73
|
+
content = content.replace(/^<!--[\s\S]*?-->\s*/gm, '').trim();
|
|
74
|
+
logger.debug(`Stripped HTML comments, now ${content.length} bytes`);
|
|
75
|
+
|
|
76
|
+
// Unpublish the HTML page FIRST (following unpublish-pages pattern)
|
|
77
|
+
if (llmsPage.out) {
|
|
78
|
+
if (!siteCatalog.unpublishedPages) siteCatalog.unpublishedPages = [];
|
|
79
|
+
if (llmsPage.pub?.url) {
|
|
80
|
+
siteCatalog.unpublishedPages.push(llmsPage.pub.url);
|
|
81
|
+
}
|
|
82
|
+
delete llmsPage.out;
|
|
83
|
+
logger.info('Unpublished llms HTML page');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Store cleaned markdown content for adding after llms-full.txt
|
|
87
|
+
llmsPage.llmsTxtContent = content;
|
|
88
|
+
|
|
89
|
+
} catch (err) {
|
|
90
|
+
logger.error(`Failed to extract markdown from llms page: ${err.message}`);
|
|
91
|
+
logger.debug(err.stack);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Generate llms-full.txt - aggregate markdown from latest version of each component
|
|
96
|
+
logger.info('Generating llms-full.txt from latest version pages with markdown...');
|
|
97
|
+
|
|
98
|
+
// Get all components and identify latest versions
|
|
99
|
+
const components = contentCatalog.getComponents();
|
|
100
|
+
const latestVersions = new Set();
|
|
101
|
+
|
|
102
|
+
components.forEach(component => {
|
|
103
|
+
// Find the latest version (non-prerelease if available, otherwise the first version)
|
|
104
|
+
const latest = component.latest || component.versions[0];
|
|
105
|
+
if (latest) {
|
|
106
|
+
latestVersions.add(`${component.name}@${latest.version}`);
|
|
107
|
+
logger.debug(`Latest version for ${component.name}: ${latest.version}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Filter pages to only include latest versions
|
|
112
|
+
const allPages = contentCatalog.getPages((p) => p.markdownContents);
|
|
113
|
+
const pages = allPages.filter(page => {
|
|
114
|
+
const pageKey = `${page.src.component}@${page.src.version}`;
|
|
115
|
+
return latestVersions.has(pageKey);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (!pages.length) {
|
|
119
|
+
logger.warn('No pages with markdown content found in latest versions, skipping llms-full.txt generation');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
logger.info(`Filtered to ${pages.length} pages from ${latestVersions.size} latest component versions (from ${allPages.length} total pages)`);
|
|
124
|
+
|
|
125
|
+
let fullContent = `# Redpanda Documentation - Full Markdown Export\n\n`;
|
|
126
|
+
fullContent += `> This file contains all documentation pages in markdown format for AI agent consumption.\n`;
|
|
127
|
+
fullContent += `> Generated from ${pages.length} pages on ${new Date().toISOString()}\n`;
|
|
128
|
+
fullContent += `> Site: ${siteUrl}\n\n`;
|
|
129
|
+
fullContent += `## About This Export\n\n`;
|
|
130
|
+
fullContent += `This export includes only the **latest version** of each component's documentation:\n`;
|
|
131
|
+
components.forEach(component => {
|
|
132
|
+
const latest = component.latest || component.versions[0];
|
|
133
|
+
if (latest) {
|
|
134
|
+
fullContent += `- **${component.title}**: version ${latest.version}\n`;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
fullContent += `\n`;
|
|
138
|
+
fullContent += `### Accessing Versioned Content\n\n`;
|
|
139
|
+
fullContent += `For components with versioned documentation (like Redpanda Self-Managed), older versions can be accessed by replacing the version segment in the URL:\n`;
|
|
140
|
+
fullContent += `- Latest: \`${siteUrl}/current/page-path\`\n`;
|
|
141
|
+
fullContent += `- Specific version: \`${siteUrl}/24.3/page-path\`, \`${siteUrl}/25.1/page-path\`, etc.\n\n`;
|
|
142
|
+
fullContent += `Available versioned components: ${components.filter(c => c.versions.length > 1).map(c => c.name).join(', ')}\n\n`;
|
|
143
|
+
fullContent += `---\n\n`;
|
|
144
|
+
|
|
145
|
+
// Sort pages by URL for consistent ordering
|
|
146
|
+
pages.sort((a, b) => {
|
|
147
|
+
const urlA = a.pub?.url || '';
|
|
148
|
+
const urlB = b.pub?.url || '';
|
|
149
|
+
return urlA.localeCompare(urlB);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
pages.forEach((page, index) => {
|
|
153
|
+
const pageUrl = page.pub?.url ? `${siteUrl}${page.pub.url}` : 'unknown';
|
|
154
|
+
const pageTitle = page.asciidoc?.doctitle || page.src?.stem || 'Untitled';
|
|
155
|
+
|
|
156
|
+
fullContent += `# Page ${index + 1}: ${pageTitle}\n\n`;
|
|
157
|
+
fullContent += `**URL**: ${pageUrl}\n\n`;
|
|
158
|
+
fullContent += `---\n\n`;
|
|
159
|
+
fullContent += page.markdownContents.toString('utf8');
|
|
160
|
+
fullContent += `\n\n---\n\n`;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Add llms-full.txt to site root
|
|
164
|
+
siteCatalog.addFile({
|
|
165
|
+
contents: Buffer.from(fullContent, 'utf8'),
|
|
166
|
+
out: { path: 'llms-full.txt' },
|
|
167
|
+
});
|
|
168
|
+
logger.info(`Generated llms-full.txt with ${pages.length} pages`);
|
|
169
|
+
|
|
170
|
+
// Add llms.txt to site root (using content extracted earlier)
|
|
171
|
+
if (llmsPage && llmsPage.llmsTxtContent) {
|
|
172
|
+
logger.info('Adding llms.txt to site root');
|
|
173
|
+
siteCatalog.addFile({
|
|
174
|
+
contents: Buffer.from(llmsPage.llmsTxtContent, 'utf8'),
|
|
175
|
+
out: { path: 'llms.txt' },
|
|
176
|
+
});
|
|
177
|
+
logger.info('Successfully added llms.txt');
|
|
178
|
+
} else {
|
|
179
|
+
logger.warn('llms.txt not generated - page not found or no content extracted');
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redpanda-data/docs-extensions-and-macros",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"description": "Antora extensions and macros developed for Redpanda documentation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antora",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"./extensions/find-related-docs": "./extensions/find-related-docs.js",
|
|
66
66
|
"./extensions/unpublish-pages": "./extensions/unpublish-pages.js",
|
|
67
67
|
"./extensions/find-related-labs": "./extensions/find-related-labs.js",
|
|
68
|
+
"./extensions/convert-llms-to-txt": "./extensions/convert-llms-to-txt.js",
|
|
68
69
|
"./extensions/modify-redirects": "./extensions/produce-redirects.js",
|
|
69
70
|
"./extensions/algolia-indexer/index": "./extensions/algolia-indexer/index.js",
|
|
70
71
|
"./extensions/aggregate-terms": "./extensions/aggregate-terms.js",
|
|
@@ -86,7 +86,11 @@ redpanda-git:
|
|
|
86
86
|
git clone -q https://github.com/redpanda-data/redpanda.git "$(REDPANDA_SRC)"; \
|
|
87
87
|
fi; \
|
|
88
88
|
fi; \
|
|
89
|
-
if git -C "$(REDPANDA_SRC)" rev-parse --verify -q "
|
|
89
|
+
if git -C "$(REDPANDA_SRC)" rev-parse --verify -q "origin/$(TAG)" >/dev/null 2>&1; then \
|
|
90
|
+
echo "🔖 Checking out remote branch 'origin/$(TAG)'"; \
|
|
91
|
+
git -C "$(REDPANDA_SRC)" checkout -q "$(TAG)" 2>/dev/null || git -C "$(REDPANDA_SRC)" checkout -q -b "$(TAG)" "origin/$(TAG)"; \
|
|
92
|
+
git -C "$(REDPANDA_SRC)" reset --hard "origin/$(TAG)" -q; \
|
|
93
|
+
elif git -C "$(REDPANDA_SRC)" rev-parse --verify -q "$(TAG)" >/dev/null 2>&1; then \
|
|
90
94
|
echo "🔖 Checking out '$(TAG)'"; \
|
|
91
95
|
git -C "$(REDPANDA_SRC)" checkout -q "$(TAG)"; \
|
|
92
96
|
else \
|
|
@@ -9,12 +9,46 @@
|
|
|
9
9
|
* - Properties with changed descriptions
|
|
10
10
|
* - Properties with changed types
|
|
11
11
|
* - Deprecated properties
|
|
12
|
+
* - Removed deprecated properties (deprecated in old version, deleted from source in new)
|
|
12
13
|
* - Removed properties
|
|
13
14
|
* - Properties with empty descriptions (excluding deprecated)
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
const fs = require('fs');
|
|
17
18
|
const path = require('path');
|
|
19
|
+
const semver = require('semver');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The minimum version where Redpanda removes deprecated properties from the
|
|
23
|
+
* C++ source code instead of keeping them tagged as deprecated_property.
|
|
24
|
+
*/
|
|
25
|
+
const DEPRECATED_REMOVAL_MIN_VERSION = '26.1.0';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the new version deletes deprecated properties from source.
|
|
29
|
+
*
|
|
30
|
+
* Before v26.1, deprecated properties were kept in the C++ source tagged as
|
|
31
|
+
* deprecated_property. Starting from v26.1, they are deleted entirely. When
|
|
32
|
+
* a property exists in the old version but is absent in the new version, and
|
|
33
|
+
* the new version is >= v26.1, we treat it as a deprecation (the property was
|
|
34
|
+
* removed because it was deprecated).
|
|
35
|
+
*
|
|
36
|
+
* Returns false only when the new version is explicitly below v26.1.
|
|
37
|
+
* For unparseable versions (e.g. "dev", "main"), assumes the latest behavior.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} newVersion - The new version string.
|
|
40
|
+
* @returns {boolean} True if the new version deletes deprecated properties.
|
|
41
|
+
*/
|
|
42
|
+
function isCrossDeprecatedRemovalBoundary(newVersion) {
|
|
43
|
+
// Only skip if the new version is explicitly below the boundary
|
|
44
|
+
const newCleaned = semver.coerce(newVersion);
|
|
45
|
+
if (newCleaned && semver.lt(newCleaned, DEPRECATED_REMOVAL_MIN_VERSION)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
// New version is either >= 26.1 or unparseable (e.g. "dev", "main").
|
|
49
|
+
// In both cases, assume the new version has the removal behavior.
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
18
52
|
|
|
19
53
|
/**
|
|
20
54
|
* Recursively compares two values for structural deep equality.
|
|
@@ -134,12 +168,15 @@ function compareProperties(oldData, newData, oldVersion, newVersion) {
|
|
|
134
168
|
const oldProps = extractProperties(oldData);
|
|
135
169
|
const newProps = extractProperties(newData);
|
|
136
170
|
|
|
171
|
+
const removesDeprecated = isCrossDeprecatedRemovalBoundary(newVersion);
|
|
172
|
+
|
|
137
173
|
const report = {
|
|
138
174
|
newProperties: [],
|
|
139
175
|
changedDefaults: [],
|
|
140
176
|
changedDescriptions: [],
|
|
141
177
|
changedTypes: [],
|
|
142
178
|
deprecatedProperties: [],
|
|
179
|
+
removedDeprecatedProperties: [],
|
|
143
180
|
removedProperties: [],
|
|
144
181
|
emptyDescriptions: []
|
|
145
182
|
};
|
|
@@ -206,11 +243,22 @@ function compareProperties(oldData, newData, oldVersion, newVersion) {
|
|
|
206
243
|
// Check both the is_experimental_property field and development_ prefix
|
|
207
244
|
const isExperimental = oldProp.is_experimental_property || name.startsWith('development_');
|
|
208
245
|
if (!isExperimental) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
246
|
+
// In v26.1+, deprecated properties are deleted from source instead of
|
|
247
|
+
// being tagged. If a property existed before and is gone now, it was
|
|
248
|
+
// deprecated.
|
|
249
|
+
if (removesDeprecated) {
|
|
250
|
+
report.removedDeprecatedProperties.push({
|
|
251
|
+
name,
|
|
252
|
+
type: oldProp.type,
|
|
253
|
+
description: oldProp.description || 'No description'
|
|
254
|
+
});
|
|
255
|
+
} else {
|
|
256
|
+
report.removedProperties.push({
|
|
257
|
+
name,
|
|
258
|
+
type: oldProp.type,
|
|
259
|
+
description: oldProp.description || 'No description'
|
|
260
|
+
});
|
|
261
|
+
}
|
|
214
262
|
}
|
|
215
263
|
}
|
|
216
264
|
}
|
|
@@ -288,6 +336,14 @@ function generateConsoleReport(report, oldVersion, newVersion) {
|
|
|
288
336
|
});
|
|
289
337
|
}
|
|
290
338
|
|
|
339
|
+
if (report.removedDeprecatedProperties.length > 0) {
|
|
340
|
+
console.log(`\n➤ Removed deprecated properties (${report.removedDeprecatedProperties.length}):`);
|
|
341
|
+
console.log(` These were deprecated in ${oldVersion} and removed from source in ${newVersion}.`);
|
|
342
|
+
report.removedDeprecatedProperties.forEach(prop => {
|
|
343
|
+
console.log(` • ${prop.name} (${prop.type})`);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
291
347
|
if (report.removedProperties.length > 0) {
|
|
292
348
|
console.log(`\n➤ Removed properties (${report.removedProperties.length}):`);
|
|
293
349
|
report.removedProperties.forEach(prop => {
|
|
@@ -329,6 +385,7 @@ function generateJsonReport(report, oldVersion, newVersion, outputPath) {
|
|
|
329
385
|
changedDescriptions: report.changedDescriptions.length,
|
|
330
386
|
changedTypes: report.changedTypes.length,
|
|
331
387
|
deprecatedProperties: report.deprecatedProperties.length,
|
|
388
|
+
removedDeprecatedProperties: report.removedDeprecatedProperties.length,
|
|
332
389
|
removedProperties: report.removedProperties.length,
|
|
333
390
|
emptyDescriptions: report.emptyDescriptions.length
|
|
334
391
|
},
|
|
@@ -370,17 +427,39 @@ function comparePropertyFiles(oldFilePath, newFilePath, oldVersion, newVersion,
|
|
|
370
427
|
const newData = JSON.parse(fs.readFileSync(newFilePath, 'utf8'));
|
|
371
428
|
|
|
372
429
|
const report = compareProperties(oldData, newData, oldVersion, newVersion);
|
|
373
|
-
|
|
430
|
+
|
|
431
|
+
// Merge removed deprecated properties back into the new JSON so they
|
|
432
|
+
// remain in generated documentation with is_deprecated + removed_deprecated flags
|
|
433
|
+
if (report.removedDeprecatedProperties.length > 0) {
|
|
434
|
+
const oldProps = extractProperties(oldData);
|
|
435
|
+
const newProps = extractProperties(newData);
|
|
436
|
+
|
|
437
|
+
report.removedDeprecatedProperties.forEach(({ name }) => {
|
|
438
|
+
if (oldProps[name] && !newProps[name]) {
|
|
439
|
+
newProps[name] = {
|
|
440
|
+
...oldProps[name],
|
|
441
|
+
is_deprecated: true,
|
|
442
|
+
removed_deprecated: true,
|
|
443
|
+
visibility: 'deprecated'
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
newData.properties = newProps;
|
|
449
|
+
fs.writeFileSync(newFilePath, JSON.stringify(newData, null, 2), 'utf8');
|
|
450
|
+
console.log(`\nMerged ${report.removedDeprecatedProperties.length} removed deprecated properties back into ${newVersion} JSON`);
|
|
451
|
+
}
|
|
452
|
+
|
|
374
453
|
// Generate console report
|
|
375
454
|
generateConsoleReport(report, oldVersion, newVersion);
|
|
376
|
-
|
|
455
|
+
|
|
377
456
|
// Generate JSON report if output directory provided
|
|
378
457
|
if (outputDir) {
|
|
379
458
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
380
459
|
const jsonReportPath = path.join(outputDir, filename);
|
|
381
460
|
generateJsonReport(report, oldVersion, newVersion, jsonReportPath);
|
|
382
461
|
}
|
|
383
|
-
|
|
462
|
+
|
|
384
463
|
return report;
|
|
385
464
|
} catch (error) {
|
|
386
465
|
console.error(`Error: Error comparing properties: ${error.message}`);
|