spec-up-t 1.2.8 → 1.3.0-beta

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.
Files changed (38) hide show
  1. package/.github/copilot-instructions.md +3 -1
  2. package/assets/compiled/body.js +5 -4
  3. package/assets/compiled/head.css +1 -0
  4. package/assets/compiled/refs.json +1 -1
  5. package/assets/css/highlight-heading-plus-sibling-nodes.css +6 -0
  6. package/assets/css/index.css +9 -0
  7. package/assets/js/addAnchorsToTerms.js +13 -5
  8. package/assets/js/collapse-definitions.js +0 -6
  9. package/assets/js/fix-last-dd.js +6 -3
  10. package/assets/js/highlight-heading-plus-sibling-nodes.js +258 -0
  11. package/assets/js/insert-trefs.js +32 -28
  12. package/config/asset-map.json +2 -0
  13. package/gulpfile.js +8 -2
  14. package/index.js +45 -241
  15. package/package.json +2 -1
  16. package/sonar-project.properties +6 -0
  17. package/src/collect-external-references.js +22 -11
  18. package/src/collect-external-references.test.js +153 -2
  19. package/src/collectExternalReferences/fetchTermsFromIndex.js +65 -110
  20. package/src/collectExternalReferences/processXTrefsData.js +9 -11
  21. package/src/create-docx.js +332 -0
  22. package/src/create-pdf.js +243 -122
  23. package/src/escape-handler.js +67 -0
  24. package/src/fix-markdown-files.js +31 -34
  25. package/src/html-dom-processor.js +290 -0
  26. package/src/init.js +3 -0
  27. package/src/install-from-boilerplate/boilerplate/.github/workflows/menu.yml +4 -13
  28. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +0 -1
  29. package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -5
  30. package/src/install-from-boilerplate/config-scripts-keys.js +4 -4
  31. package/src/install-from-boilerplate/menu.sh +6 -6
  32. package/src/markdown-it-extensions.js +60 -31
  33. package/src/references.js +18 -6
  34. package/templates/template.html +2 -0
  35. package/test-default-definitions.js +55 -0
  36. package/test-edge-cases.md +20 -0
  37. package/test-fix-markdown.js +11 -0
  38. package/test-no-def.md +22 -0
package/src/create-pdf.js CHANGED
@@ -3,14 +3,220 @@ const puppeteer = require('puppeteer');
3
3
  const path = require('path');
4
4
  const pdfLib = require('pdf-lib');
5
5
 
6
+ // ISO compliance configuration
7
+ const ISO_CONFIG = {
8
+ embedFonts: true,
9
+ deviceIndependentColor: true,
10
+ tagged: true, // For PDF/UA accessibility
11
+ pdfVersion: '1.7', // ISO 32000-1 compliant
12
+ metadata: {
13
+ format: 'PDF/A-2b', // Archive-friendly format
14
+ conformance: 'B' // Basic conformance level
15
+ }
16
+ };
17
+
18
+ /**
19
+ * Creates ISO-compliant PDF metadata
20
+ */
21
+ function createISOMetadata(config) {
22
+ const now = new Date();
23
+ return {
24
+ title: config.specs[0].title || 'Untitled Document',
25
+ author: config.specs[0].author || '',
26
+ subject: config.specs[0].description || '',
27
+ keywords: config.specs[0].keywords || [],
28
+ creator: 'Spec-Up PDF Generator',
29
+ producer: 'Spec-Up with ISO Compliance',
30
+ creationDate: now,
31
+ modificationDate: now
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Configures Puppeteer page for ISO compliance
37
+ */
38
+ async function configurePageForISO(page) {
39
+ // Set device-independent color profile and font embedding
40
+ await page.emulateMediaType('print');
41
+ await page.evaluateOnNewDocument(() => {
42
+ // Force device-independent color rendering
43
+ document.documentElement.style.colorRendering = 'optimizeQuality';
44
+ document.documentElement.style.textRendering = 'optimizeLegibility';
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Applies accessibility tags for PDF/UA compliance
50
+ */
51
+ async function applyAccessibilityTags(page) {
52
+ await page.evaluate(() => {
53
+ // Add semantic structure for accessibility
54
+ const main = document.querySelector('main') || document.body;
55
+ if (main && !main.getAttribute('role')) {
56
+ main.setAttribute('role', 'main');
57
+ }
58
+
59
+ // Tag headings for proper structure
60
+ document.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(heading => {
61
+ if (!heading.getAttribute('role')) {
62
+ heading.setAttribute('role', 'heading');
63
+ heading.setAttribute('aria-level', heading.tagName.charAt(1));
64
+ }
65
+ });
66
+
67
+ // Tag navigation elements
68
+ const toc = document.getElementById('toc') || document.getElementById('pdf-toc');
69
+ if (toc && !toc.getAttribute('role')) {
70
+ toc.setAttribute('role', 'navigation');
71
+ toc.setAttribute('aria-label', 'Table of Contents');
72
+ }
73
+
74
+ // Tag tables for accessibility
75
+ document.querySelectorAll('table').forEach(table => {
76
+ if (!table.getAttribute('role')) {
77
+ table.setAttribute('role', 'table');
78
+ }
79
+ });
80
+
81
+ // Add alt text to images if missing
82
+ document.querySelectorAll('img').forEach(img => {
83
+ if (!img.getAttribute('alt')) {
84
+ img.setAttribute('alt', img.getAttribute('title') || 'Image');
85
+ }
86
+ });
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Creates table of contents for PDF
92
+ */
93
+ async function createTOCIfNeeded(page, logo, logoLink, title, description) {
94
+ await page.evaluate((logo, logoLink, title, description) => {
95
+ const titleWrapper = document.createElement('div');
96
+ titleWrapper.className = 'text-center mb-5 pb-4 border-bottom';
97
+
98
+ if (logo) {
99
+ const logoContainer = document.createElement('a');
100
+ logoContainer.href = logoLink;
101
+ logoContainer.className = 'd-block mb-3';
102
+ const logoImg = document.createElement('img');
103
+ logoImg.src = logo;
104
+ logoImg.className = 'img-fluid';
105
+ logoContainer.appendChild(logoImg);
106
+ titleWrapper.appendChild(logoContainer);
107
+ }
108
+
109
+ if (title) {
110
+ const titleElement = document.createElement('h1');
111
+ titleElement.textContent = title;
112
+ titleElement.className = 'display-4 mb-2 pdf-title';
113
+ titleWrapper.appendChild(titleElement);
114
+ }
115
+
116
+ if (description) {
117
+ const descriptionElement = document.createElement('p');
118
+ descriptionElement.textContent = description;
119
+ descriptionElement.className = 'lead mb-0';
120
+ titleWrapper.appendChild(descriptionElement);
121
+ }
122
+
123
+ document.body.insertBefore(titleWrapper, document.body.firstChild);
124
+
125
+ // Create a Table of Contents if it doesn't exist
126
+ if (!document.getElementById('toc')) {
127
+ // Generate a TOC based on the headings in the document
128
+ const headings = Array.from(document.querySelectorAll('h1:not(.pdf-title), h2, h3, h4, h5, h6')).filter(h => {
129
+ // Only include headings with IDs that can be linked to
130
+ return h.id && h.id.trim() !== '';
131
+ });
132
+
133
+ if (headings.length > 0) {
134
+ // Create TOC container
135
+ const tocContainer = document.createElement('div');
136
+ tocContainer.id = 'toc';
137
+ tocContainer.className = 'toc-container';
138
+
139
+ // Create TOC heading
140
+ const tocHeading = document.createElement('h2');
141
+ tocHeading.textContent = 'Contents';
142
+ tocHeading.className = 'toc-heading';
143
+ tocContainer.appendChild(tocHeading);
144
+
145
+ // Create TOC list
146
+ const tocList = document.createElement('ul');
147
+ tocList.className = 'toc-list';
148
+ tocContainer.appendChild(tocList);
149
+
150
+ // Add all headings to the TOC
151
+ let currentLevel = 0;
152
+ let currentList = tocList;
153
+ let listStack = [tocList];
154
+
155
+ headings.forEach(heading => {
156
+ // Get heading level (1-6 for h1-h6)
157
+ const level = parseInt(heading.tagName[1]);
158
+
159
+ // Navigate to the correct nesting level
160
+ if (level > currentLevel) {
161
+ // Go deeper - create a new nested list
162
+ for (let i = currentLevel; i < level; i++) {
163
+ if (currentList.lastChild) {
164
+ const nestedList = document.createElement('ul');
165
+ currentList.lastChild.appendChild(nestedList);
166
+ listStack.push(nestedList);
167
+ currentList = nestedList;
168
+ } else {
169
+ // If no items exist yet, add a dummy item
170
+ const dummyItem = document.createElement('li');
171
+ const nestedList = document.createElement('ul');
172
+ dummyItem.appendChild(nestedList);
173
+ currentList.appendChild(dummyItem);
174
+ listStack.push(nestedList);
175
+ currentList = nestedList;
176
+ }
177
+ }
178
+ } else if (level < currentLevel) {
179
+ // Go up - pop from the stack
180
+ for (let i = currentLevel; i > level; i--) {
181
+ listStack.pop();
182
+ }
183
+ currentList = listStack[listStack.length - 1];
184
+ }
185
+
186
+ currentLevel = level;
187
+
188
+ // Create list item with link
189
+ const listItem = document.createElement('li');
190
+ const link = document.createElement('a');
191
+ link.href = '#' + heading.id;
192
+ link.textContent = heading.textContent;
193
+ listItem.appendChild(link);
194
+ currentList.appendChild(listItem);
195
+ });
196
+
197
+ // Insert the TOC after the title section
198
+ document.body.insertBefore(tocContainer, titleWrapper.nextSibling);
199
+
200
+ console.log('Generated a Table of Contents with ' + headings.length + ' entries.');
201
+ }
202
+ }
203
+ }, logo, logoLink, title, description);
204
+ }
205
+
6
206
  (async () => {
7
207
  try {
8
- // Launch a new browser instance
9
- const browser = await puppeteer.launch();
208
+ // Launch a new browser instance with ISO-compliant settings
209
+ const browser = await puppeteer.launch({
210
+ args: ['--disable-web-security', '--allow-running-insecure-content']
211
+ });
10
212
  const page = await browser.newPage();
11
213
 
214
+ // Configure page for ISO compliance
215
+ await configurePageForISO(page);
216
+
12
217
  // Read and parse the specs.json file
13
218
  const config = fs.readJsonSync('specs.json');
219
+ const metadata = createISOMetadata(config);
14
220
 
15
221
  // Extract configuration details
16
222
  const outputPath = config.specs[0].output_path;
@@ -36,6 +242,9 @@ const pdfLib = require('pdf-lib');
36
242
  // Navigate to the HTML file
37
243
  await page.goto(fileUrl, { waitUntil: 'networkidle2' });
38
244
 
245
+ // Apply accessibility tags for PDF/UA compliance
246
+ await applyAccessibilityTags(page);
247
+
39
248
  // Clean up unnecessary elements but be careful not to remove styles we need
40
249
  await page.evaluate(() => {
41
250
  // Preserve TOC if it exists (skip removing it even if it has display:none)
@@ -131,116 +340,7 @@ const pdfLib = require('pdf-lib');
131
340
  });
132
341
 
133
342
  // Inject logo, title, and description AND handle TOC creation if needed
134
- await page.evaluate((logo, logoLink, title, description) => {
135
- const titleWrapper = document.createElement('div');
136
- titleWrapper.className = 'text-center mb-5 pb-4 border-bottom';
137
-
138
- if (logo) {
139
- const logoContainer = document.createElement('a');
140
- logoContainer.href = logoLink;
141
- logoContainer.className = 'd-block mb-3';
142
- const logoImg = document.createElement('img');
143
- logoImg.src = logo;
144
- logoImg.className = 'img-fluid';
145
- logoContainer.appendChild(logoImg);
146
- titleWrapper.appendChild(logoContainer);
147
- }
148
-
149
- if (title) {
150
- const titleElement = document.createElement('h1');
151
- titleElement.textContent = title;
152
- titleElement.className = 'display-4 mb-2 pdf-title';
153
- titleWrapper.appendChild(titleElement);
154
- }
155
-
156
- if (description) {
157
- const descriptionElement = document.createElement('p');
158
- descriptionElement.textContent = description;
159
- descriptionElement.className = 'lead mb-0';
160
- titleWrapper.appendChild(descriptionElement);
161
- }
162
-
163
- document.body.insertBefore(titleWrapper, document.body.firstChild);
164
-
165
- // Create a Table of Contents if it doesn't exist
166
- if (!document.getElementById('toc')) {
167
- // Generate a TOC based on the headings in the document
168
- const headings = Array.from(document.querySelectorAll('h1:not(.pdf-title), h2, h3, h4, h5, h6')).filter(h => {
169
- // Only include headings with IDs that can be linked to
170
- return h.id && h.id.trim() !== '';
171
- });
172
-
173
- if (headings.length > 0) {
174
- // Create TOC container
175
- const tocContainer = document.createElement('div');
176
- tocContainer.id = 'toc';
177
- tocContainer.className = 'toc-container';
178
-
179
- // Create TOC heading
180
- const tocHeading = document.createElement('h2');
181
- tocHeading.textContent = 'Contents';
182
- tocHeading.className = 'toc-heading';
183
- tocContainer.appendChild(tocHeading);
184
-
185
- // Create TOC list
186
- const tocList = document.createElement('ul');
187
- tocList.className = 'toc-list';
188
- tocContainer.appendChild(tocList);
189
-
190
- // Add all headings to the TOC
191
- let currentLevel = 0;
192
- let currentList = tocList;
193
- let listStack = [tocList];
194
-
195
- headings.forEach(heading => {
196
- // Get heading level (1-6 for h1-h6)
197
- const level = parseInt(heading.tagName[1]);
198
-
199
- // Navigate to the correct nesting level
200
- if (level > currentLevel) {
201
- // Go deeper - create a new nested list
202
- for (let i = currentLevel; i < level; i++) {
203
- if (currentList.lastChild) {
204
- const nestedList = document.createElement('ul');
205
- currentList.lastChild.appendChild(nestedList);
206
- listStack.push(nestedList);
207
- currentList = nestedList;
208
- } else {
209
- // If no items exist yet, add a dummy item
210
- const dummyItem = document.createElement('li');
211
- const nestedList = document.createElement('ul');
212
- dummyItem.appendChild(nestedList);
213
- currentList.appendChild(dummyItem);
214
- listStack.push(nestedList);
215
- currentList = nestedList;
216
- }
217
- }
218
- } else if (level < currentLevel) {
219
- // Go up - pop from the stack
220
- for (let i = currentLevel; i > level; i--) {
221
- listStack.pop();
222
- }
223
- currentList = listStack[listStack.length - 1];
224
- }
225
-
226
- currentLevel = level;
227
-
228
- // Create list item with link
229
- const listItem = document.createElement('li');
230
- const link = document.createElement('a');
231
- link.href = '#' + heading.id;
232
- link.textContent = heading.textContent;
233
- listItem.appendChild(link);
234
- currentList.appendChild(listItem);
235
- });
236
-
237
- // Insert the TOC after the title section
238
- document.body.insertBefore(tocContainer, titleWrapper.nextSibling);
239
-
240
- console.log('Generated a Table of Contents with ' + headings.length + ' entries.');
241
- }
242
- }
243
- }, logo, logoLink, title, description);
343
+ await createTOCIfNeeded(page, logo, logoLink, title, description);
244
344
 
245
345
  // Direct manipulation of definition lists and TOC to ensure proper styling in PDF
246
346
  await page.evaluate(() => {
@@ -542,16 +642,37 @@ const pdfLib = require('pdf-lib');
542
642
 
543
643
  await browser.close();
544
644
 
545
- // Optimize PDF with pdf-lib
546
- const pdfDoc = await pdfLib.PDFDocument.load(pdfBuffer);
547
- pdfDoc.setTitle(title);
548
- pdfDoc.setAuthor('');
549
- pdfDoc.setSubject('');
550
- pdfDoc.setKeywords([]);
551
- pdfDoc.setProducer('');
552
- pdfDoc.setCreator('');
553
- const optimizedPdfBytes = await pdfDoc.save();
554
- fs.writeFileSync('docs/index.pdf', optimizedPdfBytes);
645
+ console.log('✅ PDF generated by Puppeteer. Processing with pdf-lib for ISO compliance...');
646
+
647
+ // Optimize PDF with pdf-lib for ISO compliance
648
+ try {
649
+ const pdfDoc = await pdfLib.PDFDocument.load(pdfBuffer);
650
+
651
+ // Set ISO-compliant metadata (this is safer than XMP embedding)
652
+ pdfDoc.setTitle(metadata.title);
653
+ pdfDoc.setAuthor(metadata.author);
654
+ pdfDoc.setSubject(metadata.subject);
655
+ pdfDoc.setKeywords(metadata.keywords);
656
+ pdfDoc.setProducer(metadata.producer);
657
+ pdfDoc.setCreator(metadata.creator);
658
+ pdfDoc.setCreationDate(metadata.creationDate);
659
+ pdfDoc.setModificationDate(metadata.modificationDate);
660
+
661
+ console.log('✅ ISO metadata applied successfully.');
662
+
663
+ // Save with conservative settings to ensure compatibility
664
+ const optimizedPdfBytes = await pdfDoc.save({
665
+ useObjectStreams: false, // Required for PDF/A compliance
666
+ addDefaultPage: false
667
+ });
668
+
669
+ fs.writeFileSync('docs/index.pdf', optimizedPdfBytes);
670
+ console.log('✅ PDF saved with ISO compliance features.');
671
+ } catch (pdfError) {
672
+ console.warn('⚠️ Warning: Could not apply ISO metadata, saving original PDF:', pdfError.message);
673
+ // Fallback: save the original PDF if post-processing fails
674
+ fs.writeFileSync('docs/index.pdf', pdfBuffer);
675
+ }
555
676
 
556
677
  console.log('✅ PDF generated successfully! Find the PDF in the docs directory.');
557
678
  } catch (error) {
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Escape handler for substitution tags in Spec-Up
3
+ * Provides mechanism to display literal [[tag]] syntax without processing
4
+ *
5
+ * Implements a three-phase approach:
6
+ * 1. Pre-processing: Convert escaped sequences to temporary placeholders
7
+ * 2. Tag Processing: Existing substitution logic runs normally (placeholders ignored)
8
+ * 3. Post-processing: Restore escaped sequences as literals
9
+ */
10
+
11
+ const ESCAPED_PLACEHOLDER = '__SPEC_UP_ESCAPED_TAG__';
12
+
13
+ /**
14
+ * Pre-processes text to handle escaped substitution tags
15
+ * @param {string} text - Input text with potential escaped tags
16
+ * @returns {string} Text with escaped tags replaced by placeholders
17
+ */
18
+ function preProcessEscapes(text) {
19
+ if (!text || typeof text !== 'string') {
20
+ return text;
21
+ }
22
+
23
+ // Handle double backslash first: \\[[ → \__PLACEHOLDER__
24
+ let processed = text.replace(/\\\\(\[\[)/g, `\\${ESCAPED_PLACEHOLDER}`);
25
+
26
+ // Handle single backslash: \[[ → __PLACEHOLDER__
27
+ processed = processed.replace(/\\(\[\[)/g, ESCAPED_PLACEHOLDER);
28
+
29
+ return processed;
30
+ }
31
+
32
+ /**
33
+ * Post-processes text to restore escaped tags as literals
34
+ * @param {string} text - Text after substitution processing
35
+ * @returns {string} Text with placeholders restored to literal tags
36
+ */
37
+ function postProcessEscapes(text) {
38
+ if (!text || typeof text !== 'string') {
39
+ return text;
40
+ }
41
+
42
+ // Restore placeholders to literal [[
43
+ return text.replace(new RegExp(ESCAPED_PLACEHOLDER, 'g'), '[[');
44
+ }
45
+
46
+ /**
47
+ * Main processing function that wraps existing substitution logic
48
+ * @param {string} content - Raw markdown content
49
+ * @param {Function} processSubstitutions - Existing substitution processor
50
+ * @returns {string} Processed content with escape handling
51
+ */
52
+ function processWithEscapes(content, processSubstitutions) {
53
+ if (!content || typeof content !== 'string') {
54
+ return content;
55
+ }
56
+
57
+ const preProcessed = preProcessEscapes(content);
58
+ const substituted = processSubstitutions(preProcessed);
59
+ return postProcessEscapes(substituted);
60
+ }
61
+
62
+ module.exports = {
63
+ preProcessEscapes,
64
+ postProcessEscapes,
65
+ processWithEscapes,
66
+ ESCAPED_PLACEHOLDER
67
+ };
@@ -3,7 +3,24 @@ const path = require('path');
3
3
  const { shouldProcessFile } = require('./utils/file-filter');
4
4
 
5
5
  /**
6
- * Handles specific functionality for `[[def:` lines
6
+ * Checks if a term has a definition starting from the given line index
7
+ * @param {string[]} lines - Array of file lines
8
+ * @param {number} startIndex - Index to start checking from
9
+ * @returns {boolean} - True if definition exists, false otherwise
10
+ */
11
+ function hasDefinition(lines, startIndex) {
12
+ for (let i = startIndex; i < lines.length; i++) {
13
+ const line = lines[i].trim();
14
+ if (line === '') continue; // Skip empty lines
15
+ if (line.startsWith('~')) return true; // Found definition
16
+ if (line.startsWith('[[def:') || line.startsWith('[[tref:')) return false; // Found next term
17
+ if (line.length > 0) return false; // Found other content
18
+ }
19
+ return false;
20
+ }
21
+
22
+ /**
23
+ * Handles specific functionality for `[[def:` and `[[tref:` lines
7
24
  * @param {string[]} lines - Array of file lines
8
25
  * @returns {object} - Object containing modified lines and modification status
9
26
  */
@@ -12,10 +29,19 @@ function processDefLines(lines) {
12
29
  let modified = false;
13
30
 
14
31
  for (let i = 0; i < result.length; i++) {
15
- if (result[i].startsWith('[[def:')) {
16
- // Ensure a blank line immediately follows `[[def:` lines
17
- if (i + 1 < result.length && result[i + 1].trim() !== '') {
18
- result.splice(i + 1, 0, ''); // Insert blank line
32
+ if (result[i].startsWith('[[def:') || result[i].startsWith('[[tref:')) {
33
+ let insertIndex = i + 1;
34
+
35
+ // Ensure a blank line immediately follows `[[def:` and `[[tref:` lines
36
+ if (insertIndex < result.length && result[insertIndex].trim() !== '') {
37
+ result.splice(insertIndex, 0, ''); // Insert blank line
38
+ insertIndex++;
39
+ modified = true;
40
+ }
41
+
42
+ // Check if term has a definition
43
+ if (!hasDefinition(result, insertIndex)) {
44
+ result.splice(insertIndex, 0, '', '~ No local definition found.', '');
19
45
  modified = true;
20
46
  }
21
47
  }
@@ -51,31 +77,6 @@ function normalizeParagraphSpacing(lines) {
51
77
  return { lines: newLines, modified };
52
78
  }
53
79
 
54
- /**
55
- * Prepends `~ ` to appropriate lines
56
- * @param {string[]} lines - Array of file lines
57
- * @returns {object} - Object containing modified lines and modification status
58
- */
59
- function prependTildeToLines(lines) {
60
- const result = [...lines];
61
- let modified = false;
62
-
63
- for (let i = 0; i < result.length; i++) {
64
- if (
65
- !result[i].startsWith('[[def:') &&
66
- !result[i].startsWith('[[tref:') &&
67
- result[i].trim() !== '' &&
68
- !result[i].startsWith('~ ') &&
69
- !result[i].trim().startsWith('<!--')
70
- ) {
71
- result[i] = `~ ${result[i]}`;
72
- modified = true;
73
- }
74
- }
75
-
76
- return { lines: result, modified };
77
- }
78
-
79
80
  /**
80
81
  * Ensures there is exactly one blank line at the end of the file
81
82
  * @param {string[]} lines - Array of file lines
@@ -116,10 +117,6 @@ function processMarkdownFile(filePath, fileName) {
116
117
  lines = spacingResult.lines;
117
118
  modified = modified || spacingResult.modified;
118
119
 
119
- const tildeResult = prependTildeToLines(lines);
120
- lines = tildeResult.lines;
121
- modified = modified || tildeResult.modified;
122
-
123
120
  const newlineResult = ensureTrailingNewline(lines);
124
121
  lines = newlineResult.lines;
125
122
  modified = modified || newlineResult.modified;