@js-ak/excel-toolbox 1.5.0 → 1.7.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 +41 -62
- package/build/cjs/lib/merge-sheets-to-base-file-process-sync.js +105 -0
- package/build/cjs/lib/merge-sheets-to-base-file-process.js +3 -3
- package/build/cjs/lib/merge-sheets-to-base-file-sync.js +2 -2
- package/build/cjs/lib/merge-sheets-to-base-file.js +1 -1
- package/build/cjs/lib/template/template-fs.js +143 -63
- package/build/cjs/lib/template/template-memory.js +281 -59
- package/build/cjs/lib/template/utils/index.js +25 -0
- package/build/cjs/lib/template/utils/prepare-row-to-cells.js +5 -1
- package/build/cjs/lib/template/utils/regexp.js +32 -0
- package/build/cjs/lib/template/utils/update-dimension.js +15 -0
- package/build/cjs/lib/template/utils/validate-worksheet-xml.js +74 -74
- package/build/cjs/lib/template/utils/write-rows-to-stream.js +57 -17
- package/build/cjs/lib/xml/extract-rows-from-sheet-sync.js +67 -0
- package/build/cjs/lib/xml/extract-rows-from-sheet.js +4 -2
- package/build/cjs/lib/xml/extract-xml-from-sheet-sync.js +43 -0
- package/build/cjs/lib/xml/extract-xml-from-sheet.js +15 -15
- package/build/cjs/lib/xml/index.js +2 -1
- package/build/esm/lib/merge-sheets-to-base-file-process-sync.js +69 -0
- package/build/esm/lib/merge-sheets-to-base-file-process.js +3 -3
- package/build/esm/lib/merge-sheets-to-base-file-sync.js +2 -2
- package/build/esm/lib/merge-sheets-to-base-file.js +1 -1
- package/build/esm/lib/template/template-fs.js +140 -63
- package/build/esm/lib/template/template-memory.js +281 -59
- package/build/esm/lib/template/utils/index.js +2 -0
- package/build/esm/lib/template/utils/prepare-row-to-cells.js +5 -1
- package/build/esm/lib/template/utils/regexp.js +28 -0
- package/build/esm/lib/template/utils/update-dimension.js +15 -0
- package/build/esm/lib/template/utils/validate-worksheet-xml.js +74 -74
- package/build/esm/lib/template/utils/write-rows-to-stream.js +57 -17
- package/build/esm/lib/xml/extract-rows-from-sheet-sync.js +64 -0
- package/build/esm/lib/xml/extract-rows-from-sheet.js +4 -2
- package/build/esm/lib/xml/extract-xml-from-sheet-sync.js +40 -0
- package/build/esm/lib/xml/extract-xml-from-sheet.js +12 -15
- package/build/esm/lib/xml/index.js +2 -1
- package/build/types/lib/merge-sheets-to-base-file-process-sync.d.ts +27 -0
- package/build/types/lib/merge-sheets-to-base-file-process.d.ts +1 -1
- package/build/types/lib/template/template-fs.d.ts +2 -0
- package/build/types/lib/template/template-memory.d.ts +61 -0
- package/build/types/lib/template/utils/index.d.ts +2 -0
- package/build/types/lib/template/utils/prepare-row-to-cells.d.ts +5 -1
- package/build/types/lib/template/utils/regexp.d.ts +24 -0
- package/build/types/lib/template/utils/update-dimension.d.ts +15 -0
- package/build/types/lib/template/utils/write-rows-to-stream.d.ts +22 -9
- package/build/types/lib/xml/extract-rows-from-sheet-sync.d.ts +28 -0
- package/build/types/lib/xml/extract-rows-from-sheet.d.ts +2 -2
- package/build/types/lib/xml/extract-xml-from-sheet-sync.d.ts +14 -0
- package/build/types/lib/xml/extract-xml-from-sheet.d.ts +2 -2
- package/build/types/lib/xml/index.d.ts +2 -1
- package/package.json +1 -5
- package/build/cjs/lib/xml/extract-xml-from-system-content.js +0 -53
- package/build/esm/lib/xml/extract-xml-from-system-content.js +0 -49
- package/build/types/lib/xml/extract-xml-from-system-content.d.ts +0 -15
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
|
2
2
|
import * as fsSync from "node:fs";
|
3
3
|
import * as path from "node:path";
|
4
4
|
import * as readline from "node:readline";
|
5
|
+
import crypto from "node:crypto";
|
5
6
|
import * as Xml from "../xml/index.js";
|
6
7
|
import * as Zip from "../zip/index.js";
|
7
8
|
import * as Utils from "./utils/index.js";
|
@@ -31,6 +32,16 @@ export class TemplateFs {
|
|
31
32
|
* @type {boolean}
|
32
33
|
*/
|
33
34
|
#isProcessing = false;
|
35
|
+
/**
|
36
|
+
* The keys for the Excel files in the template.
|
37
|
+
*/
|
38
|
+
#excelKeys = {
|
39
|
+
contentTypes: "[Content_Types].xml",
|
40
|
+
sharedStrings: "xl/sharedStrings.xml",
|
41
|
+
styles: "xl/styles.xml",
|
42
|
+
workbook: "xl/workbook.xml",
|
43
|
+
workbookRels: "xl/_rels/workbook.xml.rels",
|
44
|
+
};
|
34
45
|
/**
|
35
46
|
* Creates a Template instance.
|
36
47
|
*
|
@@ -113,17 +124,19 @@ export class TemplateFs {
|
|
113
124
|
* @returns The path of the sheet inside the workbook.
|
114
125
|
* @throws {Error} If the sheet is not found.
|
115
126
|
*/
|
116
|
-
async #
|
127
|
+
async #getSheetPathByName(sheetName) {
|
117
128
|
// Read XML workbook to find sheet name and path
|
118
|
-
const workbookXml = Xml.extractXmlFromSheet(await this.#readFile(
|
119
|
-
const sheetMatch = workbookXml.match(
|
120
|
-
if (!sheetMatch)
|
129
|
+
const workbookXml = await Xml.extractXmlFromSheet(await this.#readFile(this.#excelKeys.workbook));
|
130
|
+
const sheetMatch = workbookXml.match(Utils.sheetMatch(sheetName));
|
131
|
+
if (!sheetMatch || !sheetMatch[1]) {
|
121
132
|
throw new Error(`Sheet "${sheetName}" not found`);
|
133
|
+
}
|
122
134
|
const rId = sheetMatch[1];
|
123
|
-
const relsXml = Xml.extractXmlFromSheet(await this.#readFile(
|
124
|
-
const relMatch = relsXml.match(
|
125
|
-
if (!relMatch)
|
135
|
+
const relsXml = await Xml.extractXmlFromSheet(await this.#readFile(this.#excelKeys.workbookRels));
|
136
|
+
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
137
|
+
if (!relMatch || !relMatch[1]) {
|
126
138
|
throw new Error(`Relationship "${rId}" not found`);
|
139
|
+
}
|
127
140
|
return "xl/" + relMatch[1].replace(/^\/?xl\//, "");
|
128
141
|
}
|
129
142
|
/**
|
@@ -173,15 +186,15 @@ export class TemplateFs {
|
|
173
186
|
await fs.writeFile(fullPath, Buffer.isBuffer(content) ? content : Buffer.from(content));
|
174
187
|
}
|
175
188
|
async #substitute(sheetName, replacements) {
|
176
|
-
const sharedStringsPath =
|
177
|
-
const sheetPath = await this.#
|
189
|
+
const sharedStringsPath = this.#excelKeys.sharedStrings;
|
190
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
178
191
|
let sharedStringsContent = "";
|
179
192
|
let sheetContent = "";
|
180
193
|
if (this.fileKeys.has(sharedStringsPath)) {
|
181
|
-
sharedStringsContent = Xml.extractXmlFromSheet(await this.#readFile(sharedStringsPath));
|
194
|
+
sharedStringsContent = await Xml.extractXmlFromSheet(await this.#readFile(sharedStringsPath));
|
182
195
|
}
|
183
196
|
if (this.fileKeys.has(sheetPath)) {
|
184
|
-
sheetContent = Xml.extractXmlFromSheet(await this.#readFile(sheetPath));
|
197
|
+
sheetContent = await Xml.extractXmlFromSheet(await this.#readFile(sheetPath));
|
185
198
|
const TABLE_REGEX = /\$\{table:([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\}/g;
|
186
199
|
const hasTablePlaceholders = TABLE_REGEX.test(sharedStringsContent) || TABLE_REGEX.test(sheetContent);
|
187
200
|
if (hasTablePlaceholders) {
|
@@ -233,30 +246,31 @@ export class TemplateFs {
|
|
233
246
|
this.#ensureNotDestroyed();
|
234
247
|
this.#isProcessing = true;
|
235
248
|
try {
|
249
|
+
if (sourceName === newName) {
|
250
|
+
throw new Error("Source and new sheet names cannot be the same");
|
251
|
+
}
|
236
252
|
// Read workbook.xml and find the source sheet
|
237
|
-
const workbookXmlPath =
|
238
|
-
const workbookXml = Xml.extractXmlFromSheet(await this.#readFile(workbookXmlPath));
|
253
|
+
const workbookXmlPath = this.#excelKeys.workbook;
|
254
|
+
const workbookXml = await Xml.extractXmlFromSheet(await this.#readFile(workbookXmlPath));
|
239
255
|
// Find the source sheet
|
240
|
-
const
|
241
|
-
|
242
|
-
if (!sheetMatch)
|
256
|
+
const sheetMatch = workbookXml.match(Utils.sheetMatch(sourceName));
|
257
|
+
if (!sheetMatch || !sheetMatch[1]) {
|
243
258
|
throw new Error(`Sheet "${sourceName}" not found`);
|
244
|
-
|
259
|
+
}
|
245
260
|
// Check if a sheet with the new name already exists
|
246
261
|
if (new RegExp(`<sheet[^>]+name="${newName}"`).test(workbookXml)) {
|
247
262
|
throw new Error(`Sheet "${newName}" already exists`);
|
248
263
|
}
|
249
264
|
// Read workbook.rels
|
250
265
|
// Find the source sheet path by rId
|
251
|
-
const
|
252
|
-
const
|
253
|
-
const
|
254
|
-
const relMatch = relsXml.match(
|
255
|
-
if (!relMatch)
|
256
|
-
throw new Error(`Relationship "${
|
266
|
+
const rId = sheetMatch[1];
|
267
|
+
const relsXmlPath = this.#excelKeys.workbookRels;
|
268
|
+
const relsXml = await Xml.extractXmlFromSheet(await this.#readFile(relsXmlPath));
|
269
|
+
const relMatch = relsXml.match(Utils.relationshipMatch(rId));
|
270
|
+
if (!relMatch || !relMatch[1]) {
|
271
|
+
throw new Error(`Relationship "${rId}" not found`);
|
272
|
+
}
|
257
273
|
const sourceTarget = relMatch[1]; // sheetN.xml
|
258
|
-
if (!sourceTarget)
|
259
|
-
throw new Error(`Relationship "${sourceRId}" not found`);
|
260
274
|
const sourceSheetPath = "xl/" + sourceTarget.replace(/^\/?.*xl\//, "");
|
261
275
|
// Get the index of the new sheet
|
262
276
|
const sheetNumbers = [...this.fileKeys]
|
@@ -285,8 +299,8 @@ export class TemplateFs {
|
|
285
299
|
await this.#set(relsXmlPath, updatedRelsXml);
|
286
300
|
// Read [Content_Types].xml
|
287
301
|
// Update [Content_Types].xml
|
288
|
-
const contentTypesPath =
|
289
|
-
const contentTypesXml = Xml.extractXmlFromSheet(await this.#readFile(contentTypesPath));
|
302
|
+
const contentTypesPath = this.#excelKeys.contentTypes;
|
303
|
+
const contentTypesXml = await Xml.extractXmlFromSheet(await this.#readFile(contentTypesPath));
|
290
304
|
const overrideTag = `<Override PartName="/xl/worksheets/${newSheetFilename}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`;
|
291
305
|
const updatedContentTypesXml = contentTypesXml.replace("</Types>", overrideTag + "</Types>");
|
292
306
|
await this.#set(contentTypesPath, updatedContentTypesXml);
|
@@ -343,20 +357,9 @@ export class TemplateFs {
|
|
343
357
|
Utils.checkStartRow(startRowNumber);
|
344
358
|
Utils.checkRows(preparedRows);
|
345
359
|
// Find the sheet
|
346
|
-
const
|
347
|
-
const sheetMatch = workbookXml.match(new RegExp(`<sheet[^>]+name="${sheetName}"[^>]+r:id="([^"]+)"[^>]*/>`));
|
348
|
-
if (!sheetMatch || !sheetMatch[1]) {
|
349
|
-
throw new Error(`Sheet "${sheetName}" not found`);
|
350
|
-
}
|
351
|
-
const rId = sheetMatch[1];
|
352
|
-
const relsXml = Xml.extractXmlFromSheet(await this.#readFile("xl/_rels/workbook.xml.rels"));
|
353
|
-
const relMatch = relsXml.match(new RegExp(`<Relationship[^>]+Id="${rId}"[^>]+Target="([^"]+)"[^>]*/>`));
|
354
|
-
if (!relMatch || !relMatch[1]) {
|
355
|
-
throw new Error(`Relationship "${rId}" not found`);
|
356
|
-
}
|
357
|
-
const sheetPath = "xl/" + relMatch[1].replace(/^\/?xl\//, "");
|
360
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
358
361
|
const sheetXmlRaw = await this.#readFile(sheetPath);
|
359
|
-
const sheetXml = Xml.extractXmlFromSheet(sheetXmlRaw);
|
362
|
+
const sheetXml = await Xml.extractXmlFromSheet(sheetXmlRaw);
|
360
363
|
let nextRow = 0;
|
361
364
|
if (!startRowNumber) {
|
362
365
|
// Find the last row
|
@@ -390,7 +393,7 @@ export class TemplateFs {
|
|
390
393
|
else {
|
391
394
|
updatedXml = sheetXml.replace(/<worksheet[^>]*>/, (match) => `${match}<sheetData>${rowsXml}</sheetData>`);
|
392
395
|
}
|
393
|
-
await this.#set(sheetPath, updatedXml);
|
396
|
+
await this.#set(sheetPath, Utils.updateDimension(updatedXml));
|
394
397
|
}
|
395
398
|
finally {
|
396
399
|
this.#isProcessing = false;
|
@@ -418,18 +421,8 @@ export class TemplateFs {
|
|
418
421
|
const { rows, sheetName, startRowNumber } = data;
|
419
422
|
if (!sheetName)
|
420
423
|
throw new Error("Sheet name is required");
|
421
|
-
//
|
422
|
-
const
|
423
|
-
const sheetMatch = workbookXml.match(new RegExp(`<sheet[^>]+name="${sheetName}"[^>]+r:id="([^"]+)"[^>]*/>`));
|
424
|
-
if (!sheetMatch)
|
425
|
-
throw new Error(`Sheet "${sheetName}" not found`);
|
426
|
-
const rId = sheetMatch[1];
|
427
|
-
const relsXml = Xml.extractXmlFromSheet(await this.#readFile("xl/_rels/workbook.xml.rels"));
|
428
|
-
const relMatch = relsXml.match(new RegExp(`<Relationship[^>]+Id="${rId}"[^>]+Target="([^"]+)"[^>]*/>`));
|
429
|
-
if (!relMatch)
|
430
|
-
throw new Error(`Relationship "${rId}" not found`);
|
431
|
-
// Path to the desired sheet (sheet1.xml)
|
432
|
-
const sheetPath = "xl/" + relMatch[1].replace(/^\/?xl\//, "");
|
424
|
+
// Get the path to the sheet
|
425
|
+
const sheetPath = await this.#getSheetPathByName(sheetName);
|
433
426
|
// The temporary file for writing
|
434
427
|
const fullPath = path.join(this.destination, ...sheetPath.split("/"));
|
435
428
|
const tempPath = fullPath + ".tmp";
|
@@ -438,6 +431,13 @@ export class TemplateFs {
|
|
438
431
|
const output = fsSync.createWriteStream(tempPath, { encoding: "utf-8" });
|
439
432
|
// Inserted rows flag
|
440
433
|
let inserted = false;
|
434
|
+
let initialDimension = "";
|
435
|
+
const dimension = {
|
436
|
+
maxColumn: "A",
|
437
|
+
maxRow: 1,
|
438
|
+
minColumn: "A",
|
439
|
+
minRow: 1,
|
440
|
+
};
|
441
441
|
const rl = readline.createInterface({
|
442
442
|
// Process all line breaks
|
443
443
|
crlfDelay: Infinity,
|
@@ -446,6 +446,21 @@ export class TemplateFs {
|
|
446
446
|
let isCollecting = false;
|
447
447
|
let collection = "";
|
448
448
|
for await (const line of rl) {
|
449
|
+
// Process <dimension>
|
450
|
+
if (!initialDimension && /<dimension\s+ref="[^"]*"/.test(line)) {
|
451
|
+
const dimensionMatch = line.match(/<dimension\s+ref="([^"]*)"/);
|
452
|
+
if (dimensionMatch) {
|
453
|
+
const dimensionRef = dimensionMatch[1];
|
454
|
+
if (dimensionRef) {
|
455
|
+
const [min, max] = dimensionRef.split(":");
|
456
|
+
dimension.minColumn = min.slice(0, 1);
|
457
|
+
dimension.minRow = parseInt(min.slice(1));
|
458
|
+
dimension.maxColumn = max.slice(0, 1);
|
459
|
+
dimension.maxRow = parseInt(max.slice(1));
|
460
|
+
}
|
461
|
+
initialDimension = line.match(/<dimension\s+ref="[^"]*"/)?.[0] || "";
|
462
|
+
}
|
463
|
+
}
|
449
464
|
// Collect lines between <sheetData> and </sheetData>
|
450
465
|
if (!inserted && isCollecting) {
|
451
466
|
collection += line;
|
@@ -472,7 +487,13 @@ export class TemplateFs {
|
|
472
487
|
output.write(innerRows);
|
473
488
|
}
|
474
489
|
}
|
475
|
-
const { rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
490
|
+
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
491
|
+
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
492
|
+
dimension.maxColumn = newDimension.maxColumn;
|
493
|
+
}
|
494
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
495
|
+
dimension.maxRow = newDimension.maxRow;
|
496
|
+
}
|
476
497
|
if (innerRows) {
|
477
498
|
const filteredRows = Utils.getRowsAbove(innerRowsMap, actualRowNumber);
|
478
499
|
if (filteredRows)
|
@@ -513,7 +534,13 @@ export class TemplateFs {
|
|
513
534
|
}
|
514
535
|
}
|
515
536
|
// new <row>
|
516
|
-
const { rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
537
|
+
const { dimension: newDimension, rowNumber: actualRowNumber } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
538
|
+
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
539
|
+
dimension.maxColumn = newDimension.maxColumn;
|
540
|
+
}
|
541
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
542
|
+
dimension.maxRow = newDimension.maxRow;
|
543
|
+
}
|
517
544
|
if (innerRows) {
|
518
545
|
const filteredRows = Utils.getRowsAbove(innerRowsMap, actualRowNumber);
|
519
546
|
if (filteredRows) {
|
@@ -540,7 +567,13 @@ export class TemplateFs {
|
|
540
567
|
// Insert opening tag
|
541
568
|
output.write("<sheetData>");
|
542
569
|
// Prepare the rows
|
543
|
-
await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
570
|
+
const { dimension: newDimension } = await Utils.writeRowsToStream(output, rows, maxRowNumber);
|
571
|
+
if (compareColumns(newDimension.maxColumn, dimension.maxColumn) > 0) {
|
572
|
+
dimension.maxColumn = newDimension.maxColumn;
|
573
|
+
}
|
574
|
+
if (newDimension.maxRow > dimension.maxRow) {
|
575
|
+
dimension.maxRow = newDimension.maxRow;
|
576
|
+
}
|
544
577
|
// Insert closing tag
|
545
578
|
output.write("</sheetData>");
|
546
579
|
if (after) {
|
@@ -561,8 +594,42 @@ export class TemplateFs {
|
|
561
594
|
// Close the streams
|
562
595
|
rl.close();
|
563
596
|
output.end();
|
564
|
-
//
|
565
|
-
|
597
|
+
// update dimension
|
598
|
+
{
|
599
|
+
const target = initialDimension;
|
600
|
+
const refRange = `${dimension.minColumn}${dimension.minRow}:${dimension.maxColumn}${dimension.maxRow}`;
|
601
|
+
const replacement = `<dimension ref="${refRange}"`;
|
602
|
+
let buffer = "";
|
603
|
+
let replaced = false;
|
604
|
+
const input = fsSync.createReadStream(tempPath, { encoding: "utf8" });
|
605
|
+
const output = fsSync.createWriteStream(fullPath);
|
606
|
+
await new Promise((resolve, reject) => {
|
607
|
+
input.on("data", chunk => {
|
608
|
+
buffer += chunk;
|
609
|
+
if (!replaced) {
|
610
|
+
const index = buffer.indexOf(target);
|
611
|
+
if (index !== -1) {
|
612
|
+
// Заменяем только первое вхождение
|
613
|
+
buffer = buffer.replace(target, replacement);
|
614
|
+
replaced = true;
|
615
|
+
}
|
616
|
+
}
|
617
|
+
output.write(buffer);
|
618
|
+
buffer = ""; // очищаем, т.к. мы уже записали
|
619
|
+
});
|
620
|
+
input.on("error", reject);
|
621
|
+
output.on("error", reject);
|
622
|
+
input.on("end", () => {
|
623
|
+
// на всякий случай дописываем остаток
|
624
|
+
if (buffer) {
|
625
|
+
output.write(buffer);
|
626
|
+
}
|
627
|
+
output.end();
|
628
|
+
resolve(true);
|
629
|
+
});
|
630
|
+
});
|
631
|
+
}
|
632
|
+
await fs.unlink(tempPath);
|
566
633
|
}
|
567
634
|
finally {
|
568
635
|
this.#isProcessing = false;
|
@@ -663,28 +730,38 @@ export class TemplateFs {
|
|
663
730
|
* @param {Object} data - The data to create the template from.
|
664
731
|
* @param {string} data.source - The path or buffer of the Excel file.
|
665
732
|
* @param {string} data.destination - The path to save the template to.
|
733
|
+
* @param {boolean} data.isUniqueDestination - Whether to add a random UUID to the destination path.
|
666
734
|
* @returns {Promise<Template>} A new Template instance.
|
667
735
|
* @throws {Error} If reading or writing files fails.
|
668
736
|
* @experimental This API is experimental and might change in future versions.
|
669
737
|
*/
|
670
738
|
static async from(data) {
|
671
|
-
const { destination, source } = data;
|
739
|
+
const { destination, isUniqueDestination = true, source } = data;
|
672
740
|
if (!destination) {
|
673
741
|
throw new Error("Destination is required");
|
674
742
|
}
|
743
|
+
// add random uuid to destination
|
744
|
+
const destinationWithUuid = isUniqueDestination
|
745
|
+
? path.join(destination, crypto.randomUUID())
|
746
|
+
: destination;
|
675
747
|
const buffer = typeof source === "string"
|
676
748
|
? await fs.readFile(source)
|
677
749
|
: source;
|
678
750
|
const files = await Zip.read(buffer);
|
679
751
|
// if destination exists, remove it
|
680
|
-
await fs.rm(
|
752
|
+
await fs.rm(destinationWithUuid, { force: true, recursive: true });
|
681
753
|
// Write all files to the file system, preserving exact paths
|
682
|
-
await fs.mkdir(
|
754
|
+
await fs.mkdir(destinationWithUuid, { recursive: true });
|
683
755
|
await Promise.all(Object.entries(files).map(async ([filePath, content]) => {
|
684
|
-
const fullPath = path.join(
|
756
|
+
const fullPath = path.join(destinationWithUuid, ...filePath.split("/"));
|
685
757
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
686
758
|
await fs.writeFile(fullPath, content);
|
687
759
|
}));
|
688
|
-
return new TemplateFs(new Set(Object.keys(files)),
|
760
|
+
return new TemplateFs(new Set(Object.keys(files)), destinationWithUuid);
|
689
761
|
}
|
690
762
|
}
|
763
|
+
const compareColumns = (a, b) => {
|
764
|
+
if (a === b)
|
765
|
+
return 0;
|
766
|
+
return a.length === b.length ? (a < b ? -1 : 1) : (a.length < b.length ? -1 : 1);
|
767
|
+
};
|