@justybase/spreadsheet-tasks 2.1.0 → 2.1.1

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/dist/esm/index.js CHANGED
@@ -402,8 +402,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
402
402
  this.xfIdToNumFmtId = [];
403
403
  this.customDateFormats = null;
404
404
  }
405
- async open(path2, readSharedStrings = true) {
406
- this.zip = new AdmZip(path2);
405
+ async open(path3, readSharedStrings = true) {
406
+ this.zip = new AdmZip(path3);
407
407
  const wbEntry = this.zip.getEntry("xl/workbook.bin");
408
408
  if (wbEntry) {
409
409
  const buf = wbEntry.getData();
@@ -448,8 +448,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
448
448
  this.resultsCount = this.sheets.length;
449
449
  this._currentSheetIndex = -1;
450
450
  }
451
- _readSharedStrings(path2) {
452
- const entry = this.zip.getEntry(path2);
451
+ _readSharedStrings(path3) {
452
+ const entry = this.zip.getEntry(path3);
453
453
  if (!entry) return;
454
454
  const reader = new BiffReaderWriter(entry.getData());
455
455
  while (reader.readSharedStrings()) {
@@ -458,8 +458,8 @@ var XlsbReader = class extends ExcelReaderAbstract {
458
458
  }
459
459
  }
460
460
  }
461
- _readStyles(path2) {
462
- const entry = this.zip.getEntry(path2);
461
+ _readStyles(path3) {
462
+ const entry = this.zip.getEntry(path3);
463
463
  if (!entry) return;
464
464
  const reader = new BiffReaderWriter(entry.getData());
465
465
  while (reader.readStyles()) {
@@ -651,9 +651,9 @@ var XlsxReader = class extends ExcelReaderAbstract {
651
651
  this.xfIdToNumFmtId = [];
652
652
  this.customDateFormats = /* @__PURE__ */ new Set();
653
653
  }
654
- async open(path2, readSharedStrings = true) {
655
- return new Promise((resolve, reject) => {
656
- yauzl.open(path2, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
654
+ async open(path3, readSharedStrings = true) {
655
+ return new Promise((resolve2, reject) => {
656
+ yauzl.open(path3, { lazyEntries: true, autoClose: false }, async (err, zipfile) => {
657
657
  if (err) return reject(err);
658
658
  this.zipfile = zipfile;
659
659
  this.entries = /* @__PURE__ */ new Map();
@@ -701,7 +701,7 @@ var XlsxReader = class extends ExcelReaderAbstract {
701
701
  }
702
702
  this.resultsCount = this.sheets.length;
703
703
  this._currentSheetIndex = -1;
704
- resolve();
704
+ resolve2();
705
705
  } catch (e) {
706
706
  reject(e);
707
707
  }
@@ -715,16 +715,16 @@ var XlsxReader = class extends ExcelReaderAbstract {
715
715
  this.zipfile.close();
716
716
  }
717
717
  }
718
- async _readZipEntryContent(path2) {
719
- const entry = this.entries.get(path2);
718
+ async _readZipEntryContent(path3) {
719
+ const entry = this.entries.get(path3);
720
720
  if (!entry) return null;
721
- return new Promise((resolve, reject) => {
721
+ return new Promise((resolve2, reject) => {
722
722
  this.zipfile.openReadStream(entry, (err, readStream) => {
723
723
  if (err) return reject(err);
724
724
  const chunks = [];
725
725
  readStream.on("data", (chunk) => chunks.push(chunk));
726
726
  readStream.on("end", () => {
727
- resolve(Buffer.concat(chunks).toString("utf8"));
727
+ resolve2(Buffer.concat(chunks).toString("utf8"));
728
728
  });
729
729
  readStream.on("error", reject);
730
730
  });
@@ -3330,7 +3330,7 @@ var XlsbWriter = class _XlsbWriter {
3330
3330
  * Must be awaited before the file is fully written.
3331
3331
  */
3332
3332
  finalize() {
3333
- return new Promise((resolve, reject) => {
3333
+ return new Promise((resolve2, reject) => {
3334
3334
  try {
3335
3335
  this.saveSst();
3336
3336
  this.archive.append(this._buildStylesBin(), { name: "xl/styles.bin" });
@@ -3428,7 +3428,7 @@ var XlsbWriter = class _XlsbWriter {
3428
3428
  </Relationships>`;
3429
3429
  this.archive.append(wsRels, { name: `xl/worksheets/_rels/${sheet.nameInArchive}.rels` });
3430
3430
  }
3431
- this.output.on("close", () => resolve());
3431
+ this.output.on("close", () => resolve2());
3432
3432
  this.archive.on("error", (err) => reject(err));
3433
3433
  this.archive.finalize();
3434
3434
  } catch (err) {
@@ -3776,7 +3776,7 @@ var XlsxWriter = class {
3776
3776
  }
3777
3777
  /** Finalize the ZIP package and close the output stream. */
3778
3778
  finalize() {
3779
- return new Promise((resolve, reject) => {
3779
+ return new Promise((resolve2, reject) => {
3780
3780
  try {
3781
3781
  this._writeSharedStrings();
3782
3782
  this._writeStyles();
@@ -3784,7 +3784,7 @@ var XlsxWriter = class {
3784
3784
  this._writeContentTypes();
3785
3785
  this._writeRels();
3786
3786
  this.output.on("close", () => {
3787
- resolve();
3787
+ resolve2();
3788
3788
  });
3789
3789
  this.archive.on("error", (err) => {
3790
3790
  reject(err);
@@ -3927,7 +3927,7 @@ var XlsxUpdater = class {
3927
3927
  /**
3928
3928
  * @param path Path to an existing `.xlsx` file.
3929
3929
  */
3930
- constructor(path2) {
3930
+ constructor(path3) {
3931
3931
  this.sheetNameToPath = /* @__PURE__ */ new Map();
3932
3932
  this.pivotCacheDefs = [];
3933
3933
  this.pivotTableDefPaths = [];
@@ -3936,12 +3936,12 @@ var XlsxUpdater = class {
3936
3936
  this.sharedStringsOriginalLength = 0;
3937
3937
  this.sharedStringsCount = 0;
3938
3938
  this.stringIndexMap = /* @__PURE__ */ new Map();
3939
- this.sourcePath = path2;
3939
+ this.sourcePath = path3;
3940
3940
  this._oaEpoch = Date.UTC(1899, 11, 30);
3941
- if (!fs3.existsSync(path2)) {
3942
- throw new Error(`XlsxUpdater: file not found: ${path2}`);
3941
+ if (!fs3.existsSync(path3)) {
3942
+ throw new Error(`XlsxUpdater: file not found: ${path3}`);
3943
3943
  }
3944
- this.zip = new AdmZip2(path2);
3944
+ this.zip = new AdmZip2(path3);
3945
3945
  if (this.zip.getEntry("xl/workbook.bin")) {
3946
3946
  throw new Error(
3947
3947
  "XlsxUpdater: XLSB files are not supported yet. Convert the workbook to XLSX first."
@@ -4326,8 +4326,8 @@ var XlsxUpdater = class {
4326
4326
  }
4327
4327
  }
4328
4328
  _addRefreshOnLoadToPivotTables() {
4329
- for (const path2 of this.pivotTableDefPaths) {
4330
- const entry = this.zip.getEntry(path2);
4329
+ for (const path3 of this.pivotTableDefPaths) {
4330
+ const entry = this.zip.getEntry(path3);
4331
4331
  if (!entry) continue;
4332
4332
  let xml = entry.getData().toString("utf8");
4333
4333
  const rootRe = /<pivotTableDefinition\b[^>]*>/;
@@ -4335,7 +4335,7 @@ var XlsxUpdater = class {
4335
4335
  if (!rootMatch || rootMatch[0].includes("refreshOnLoad")) continue;
4336
4336
  const newTag = rootMatch[0].replace(/(\/?>)$/, ' refreshOnLoad="1"$1');
4337
4337
  xml = xml.substring(0, rootMatch.index) + newTag + xml.substring(rootMatch.index + rootMatch[0].length);
4338
- this.zip.updateFile(path2, Buffer.from(xml, "utf8"));
4338
+ this.zip.updateFile(path3, Buffer.from(xml, "utf8"));
4339
4339
  }
4340
4340
  }
4341
4341
  };
@@ -4420,7 +4420,7 @@ var XlsbUpdater = class {
4420
4420
  /**
4421
4421
  * @param path Path to an existing `.xlsb` file.
4422
4422
  */
4423
- constructor(path2) {
4423
+ constructor(path3) {
4424
4424
  this.sheetNameToPath = /* @__PURE__ */ new Map();
4425
4425
  this.pivotCacheDefPaths = [];
4426
4426
  this.sharedStringsBuf = null;
@@ -4433,12 +4433,12 @@ var XlsbUpdater = class {
4433
4433
  // Shared strings
4434
4434
  // ------------------------------------------------------------------
4435
4435
  this._appendCount = 0;
4436
- this.sourcePath = path2;
4436
+ this.sourcePath = path3;
4437
4437
  this._oaEpoch = Date.UTC(1899, 11, 30);
4438
- if (!fs4.existsSync(path2)) {
4439
- throw new Error(`XlsbUpdater: file not found: ${path2}`);
4438
+ if (!fs4.existsSync(path3)) {
4439
+ throw new Error(`XlsbUpdater: file not found: ${path3}`);
4440
4440
  }
4441
- this.zip = new AdmZip3(path2);
4441
+ this.zip = new AdmZip3(path3);
4442
4442
  if (!this.zip.getEntry("xl/workbook.bin")) {
4443
4443
  throw new Error(
4444
4444
  "XlsbUpdater: not an XLSB workbook (xl/workbook.bin missing). Convert the file to XLSB first."
@@ -4850,8 +4850,8 @@ var XlsbUpdater = class {
4850
4850
  // Pivot cache integration
4851
4851
  // ------------------------------------------------------------------
4852
4852
  _patchPivotCaches(sheetName, rwLast, colLast) {
4853
- for (const path2 of this.pivotCacheDefPaths) {
4854
- const entry = this.zip.getEntry(path2);
4853
+ for (const path3 of this.pivotCacheDefPaths) {
4854
+ const entry = this.zip.getEntry(path3);
4855
4855
  if (!entry) continue;
4856
4856
  const buf = Buffer.from(entry.getData());
4857
4857
  const rec = { headerStart: 0, dataStart: 0, dataEnd: 0, id: 0, len: 0 };
@@ -4883,11 +4883,255 @@ var XlsbUpdater = class {
4883
4883
  }
4884
4884
  }
4885
4885
  if (patched) {
4886
- this.zip.updateFile(path2, buf);
4886
+ this.zip.updateFile(path3, buf);
4887
4887
  }
4888
4888
  }
4889
4889
  }
4890
4890
  };
4891
+
4892
+ // src/ExcelConverter.ts
4893
+ import * as fs6 from "fs";
4894
+ import * as path2 from "path";
4895
+ import { randomUUID as randomUUID2 } from "crypto";
4896
+ import { spawn } from "child_process";
4897
+
4898
+ // src/atomicFile.ts
4899
+ import * as fs5 from "fs";
4900
+ import { randomUUID } from "crypto";
4901
+ var realFileOperations = {
4902
+ exists: (filePath) => fs5.existsSync(filePath),
4903
+ rename: (sourcePath, destinationPath) => fs5.renameSync(sourcePath, destinationPath),
4904
+ remove: (filePath) => fs5.rmSync(filePath)
4905
+ };
4906
+ function errorMessage(error) {
4907
+ return error instanceof Error ? error.message : String(error);
4908
+ }
4909
+ function destinationExistsError(destinationPath) {
4910
+ return new Error(
4911
+ `Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${destinationPath}`
4912
+ );
4913
+ }
4914
+ function installTemporaryOutput(temporaryPath, destinationPath, overwrite, operations = realFileOperations) {
4915
+ const backupPath = `${destinationPath}.${randomUUID()}.replacement-backup`;
4916
+ let backupCreated = false;
4917
+ let installed = false;
4918
+ if (operations.exists(destinationPath)) {
4919
+ if (!overwrite) {
4920
+ throw destinationExistsError(destinationPath);
4921
+ }
4922
+ operations.rename(destinationPath, backupPath);
4923
+ backupCreated = true;
4924
+ }
4925
+ try {
4926
+ try {
4927
+ operations.rename(temporaryPath, destinationPath);
4928
+ installed = true;
4929
+ } catch (error) {
4930
+ if (overwrite && !backupCreated && operations.exists(destinationPath)) {
4931
+ operations.rename(destinationPath, backupPath);
4932
+ backupCreated = true;
4933
+ operations.rename(temporaryPath, destinationPath);
4934
+ installed = true;
4935
+ } else {
4936
+ throw error;
4937
+ }
4938
+ }
4939
+ if (backupCreated) {
4940
+ operations.remove(backupPath);
4941
+ }
4942
+ } catch (error) {
4943
+ if (backupCreated && !installed) {
4944
+ if (!operations.exists(destinationPath)) {
4945
+ try {
4946
+ operations.rename(backupPath, destinationPath);
4947
+ backupCreated = false;
4948
+ } catch (rollbackError) {
4949
+ throw new Error(
4950
+ `Excel conversion: final destination install failed (${errorMessage(error)}), and restoring the original destination also failed (${errorMessage(rollbackError)}). Original file remains at ${backupPath}.`
4951
+ );
4952
+ }
4953
+ } else {
4954
+ throw new Error(
4955
+ `Excel conversion: final destination install failed (${errorMessage(error)}). A concurrent destination was preserved; the original file remains at ${backupPath}.`
4956
+ );
4957
+ }
4958
+ }
4959
+ throw error;
4960
+ }
4961
+ }
4962
+
4963
+ // src/ExcelConverter.ts
4964
+ var POWERSHELL_CONVERSION_SCRIPT = `
4965
+ $ErrorActionPreference = 'Stop'
4966
+ $inputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_INPUT')
4967
+ $outputPath = [Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_OUTPUT')
4968
+ $fileFormat = [int][Environment]::GetEnvironmentVariable('JUSTYBASE_EXCEL_FORMAT')
4969
+ $excel = $null
4970
+ $workbook = $null
4971
+ $exitCode = 0
4972
+
4973
+ try {
4974
+ $excel = New-Object -ComObject Excel.Application
4975
+ $excel.Visible = $false
4976
+ $excel.DisplayAlerts = $false
4977
+ $excel.ScreenUpdating = $false
4978
+
4979
+ # Open read-only so the source workbook is never changed by the conversion.
4980
+ $workbook = $excel.Workbooks.Open($inputPath, 0, $true)
4981
+ if ($null -eq $workbook) {
4982
+ throw 'Excel did not return a workbook when opening the source file.'
4983
+ }
4984
+
4985
+ $workbook.SaveAs($outputPath, $fileFormat)
4986
+ } catch {
4987
+ $exitCode = 1
4988
+ [Console]::Error.WriteLine($_.Exception.ToString())
4989
+ } finally {
4990
+ if ($null -ne $workbook) {
4991
+ try { $workbook.Close($false) } catch { }
4992
+ try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook) | Out-Null } catch { }
4993
+ }
4994
+ if ($null -ne $excel) {
4995
+ try { $excel.Quit() } catch { }
4996
+ try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null } catch { }
4997
+ }
4998
+ [GC]::Collect()
4999
+ [GC]::WaitForPendingFinalizers()
5000
+ }
5001
+
5002
+ exit $exitCode
5003
+ `;
5004
+ function errorMessage2(error) {
5005
+ return error instanceof Error ? error.message : String(error);
5006
+ }
5007
+ function validateConversionPaths(inputPath, outputPath, inputExtension, outputExtension, overwrite) {
5008
+ const input = path2.resolve(inputPath);
5009
+ const output = path2.resolve(outputPath);
5010
+ if (path2.extname(input).toLowerCase() !== inputExtension) {
5011
+ throw new Error(
5012
+ `Excel conversion: source must have the ${inputExtension} extension: ${inputPath}`
5013
+ );
5014
+ }
5015
+ if (path2.extname(output).toLowerCase() !== outputExtension) {
5016
+ throw new Error(
5017
+ `Excel conversion: destination must have the ${outputExtension} extension: ${outputPath}`
5018
+ );
5019
+ }
5020
+ if (!fs6.existsSync(input)) {
5021
+ throw new Error(`Excel conversion: source file not found: ${inputPath}`);
5022
+ }
5023
+ if (!fs6.statSync(input).isFile()) {
5024
+ throw new Error(`Excel conversion: source path is not a file: ${inputPath}`);
5025
+ }
5026
+ if (!fs6.existsSync(path2.dirname(output))) {
5027
+ throw new Error(`Excel conversion: destination directory not found: ${path2.dirname(output)}`);
5028
+ }
5029
+ if (fs6.existsSync(output)) {
5030
+ if (fs6.statSync(output).isDirectory()) {
5031
+ throw new Error(`Excel conversion: destination path is a directory: ${outputPath}`);
5032
+ }
5033
+ if (!overwrite) {
5034
+ throw new Error(
5035
+ `Excel conversion: destination already exists (pass { overwrite: true } to replace it): ${outputPath}`
5036
+ );
5037
+ }
5038
+ }
5039
+ if (process.platform === "win32" && input.toLowerCase() === output.toLowerCase()) {
5040
+ throw new Error("Excel conversion: source and destination must be different files.");
5041
+ }
5042
+ return { input, output };
5043
+ }
5044
+ function runPowerShellConversion(inputPath, outputPath, fileFormat) {
5045
+ const encodedCommand = Buffer.from(POWERSHELL_CONVERSION_SCRIPT, "utf16le").toString("base64");
5046
+ const environment = {
5047
+ ...process.env,
5048
+ JUSTYBASE_EXCEL_INPUT: inputPath,
5049
+ JUSTYBASE_EXCEL_OUTPUT: outputPath,
5050
+ JUSTYBASE_EXCEL_FORMAT: String(fileFormat)
5051
+ };
5052
+ return new Promise((resolve2, reject) => {
5053
+ var _a, _b, _c, _d;
5054
+ let stdout = "";
5055
+ let stderr = "";
5056
+ let settled = false;
5057
+ const child = spawn(
5058
+ "powershell.exe",
5059
+ [
5060
+ "-NoLogo",
5061
+ "-NoProfile",
5062
+ "-NonInteractive",
5063
+ "-ExecutionPolicy",
5064
+ "Bypass",
5065
+ "-EncodedCommand",
5066
+ encodedCommand
5067
+ ],
5068
+ { env: environment, windowsHide: true }
5069
+ );
5070
+ (_a = child.stdout) == null ? void 0 : _a.setEncoding("utf8");
5071
+ (_b = child.stderr) == null ? void 0 : _b.setEncoding("utf8");
5072
+ (_c = child.stdout) == null ? void 0 : _c.on("data", (chunk) => {
5073
+ stdout += chunk;
5074
+ });
5075
+ (_d = child.stderr) == null ? void 0 : _d.on("data", (chunk) => {
5076
+ stderr += chunk;
5077
+ });
5078
+ child.once("error", (error) => {
5079
+ if (settled) return;
5080
+ settled = true;
5081
+ reject(new Error(`Excel conversion: unable to start PowerShell/COM: ${error.message}`));
5082
+ });
5083
+ child.once("close", (code) => {
5084
+ if (settled) return;
5085
+ settled = true;
5086
+ if (code === 0) {
5087
+ resolve2();
5088
+ return;
5089
+ }
5090
+ const details = (stderr || stdout).replace(/\s+/g, " ").trim();
5091
+ reject(new Error(
5092
+ `Excel conversion failed (PowerShell exit code ${code ?? "unknown"})${details ? `: ${details}` : "."}`
5093
+ ));
5094
+ });
5095
+ });
5096
+ }
5097
+ async function convertExcelFile(inputPath, outputPath, inputExtension, outputExtension, fileFormat, options = {}) {
5098
+ const { input, output } = validateConversionPaths(
5099
+ inputPath,
5100
+ outputPath,
5101
+ inputExtension,
5102
+ outputExtension,
5103
+ options.overwrite === true
5104
+ );
5105
+ if (process.platform !== "win32") {
5106
+ throw new Error("Excel conversion requires Windows with desktop Microsoft Excel and COM automation.");
5107
+ }
5108
+ const temporaryOutput = path2.join(
5109
+ path2.dirname(output),
5110
+ `.${path2.basename(output, outputExtension)}.${randomUUID2()}.conversion${outputExtension}`
5111
+ );
5112
+ try {
5113
+ await runPowerShellConversion(input, temporaryOutput, fileFormat);
5114
+ if (!fs6.existsSync(temporaryOutput)) {
5115
+ throw new Error("Excel conversion completed without creating the destination file.");
5116
+ }
5117
+ installTemporaryOutput(temporaryOutput, output, options.overwrite === true);
5118
+ } catch (error) {
5119
+ throw new Error(`Excel conversion ${inputExtension} -> ${outputExtension}: ${errorMessage2(error)}`);
5120
+ } finally {
5121
+ if (fs6.existsSync(temporaryOutput)) {
5122
+ try {
5123
+ fs6.rmSync(temporaryOutput);
5124
+ } catch {
5125
+ }
5126
+ }
5127
+ }
5128
+ }
5129
+ function convertXlsbToXlsx(inputPath, outputPath, options) {
5130
+ return convertExcelFile(inputPath, outputPath, ".xlsb", ".xlsx", 51, options);
5131
+ }
5132
+ function convertXlsxToXlsb(inputPath, outputPath, options) {
5133
+ return convertExcelFile(inputPath, outputPath, ".xlsx", ".xlsb", 50, options);
5134
+ }
4891
5135
  export {
4892
5136
  BiffReaderWriter,
4893
5137
  BigBuffer,
@@ -4903,6 +5147,8 @@ export {
4903
5147
  XlsxUpdater,
4904
5148
  XlsxWriter,
4905
5149
  applyHeaderWidths,
5150
+ convertXlsbToXlsx,
5151
+ convertXlsxToXlsb,
4906
5152
  defaultColWidth,
4907
5153
  getFormat,
4908
5154
  initColWidths,