lula2 0.6.2 → 0.6.3-nightly.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.
@@ -1880,6 +1880,17 @@ var init_fileStore = __esm({
1880
1880
  if (!existsSync2(this.controlsDir)) {
1881
1881
  return [];
1882
1882
  }
1883
+ let controlOrder = null;
1884
+ try {
1885
+ const lulaConfigPath = join2(this.baseDir, "lula.yaml");
1886
+ if (existsSync2(lulaConfigPath)) {
1887
+ const content = readFileSync2(lulaConfigPath, "utf8");
1888
+ const metadata = yaml2.load(content);
1889
+ controlOrder = metadata?.controlOrder || null;
1890
+ }
1891
+ } catch (error) {
1892
+ console.error("Failed to load lula.yaml for controlOrder:", error);
1893
+ }
1883
1894
  const entries = readdirSync(this.controlsDir);
1884
1895
  const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
1885
1896
  if (yamlFiles.length > 0) {
@@ -1898,7 +1909,11 @@ var init_fileStore = __esm({
1898
1909
  }
1899
1910
  });
1900
1911
  const results2 = await Promise.all(promises);
1901
- return results2.filter((c) => c !== null);
1912
+ const controls2 = results2.filter((c) => c !== null);
1913
+ if (controlOrder && controlOrder.length > 0) {
1914
+ return this.sortControlsByOrder(controls2, controlOrder);
1915
+ }
1916
+ return controls2;
1902
1917
  }
1903
1918
  const families = entries.filter((name) => {
1904
1919
  const familyPath = join2(this.controlsDir, name);
@@ -1921,7 +1936,25 @@ var init_fileStore = __esm({
1921
1936
  allPromises.push(...familyPromises);
1922
1937
  }
1923
1938
  const results = await Promise.all(allPromises);
1924
- return results.filter((c) => c !== null);
1939
+ const controls = results.filter((c) => c !== null);
1940
+ if (controlOrder && controlOrder.length > 0) {
1941
+ return this.sortControlsByOrder(controls, controlOrder);
1942
+ }
1943
+ return controls;
1944
+ }
1945
+ /**
1946
+ * Sort controls based on the provided order array
1947
+ */
1948
+ sortControlsByOrder(controls, controlOrder) {
1949
+ const orderMap = /* @__PURE__ */ new Map();
1950
+ controlOrder.forEach((controlId, index) => {
1951
+ orderMap.set(controlId, index);
1952
+ });
1953
+ return controls.sort((a, b) => {
1954
+ const aIndex = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
1955
+ const bIndex = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
1956
+ return aIndex - bIndex;
1957
+ });
1925
1958
  }
1926
1959
  /**
1927
1960
  * Load mappings from mappings directory
@@ -2948,7 +2981,7 @@ function processImportParameters(reqBody) {
2948
2981
  frontendFieldSchema
2949
2982
  };
2950
2983
  }
2951
- async function parseUploadedFile(file) {
2984
+ async function parseUploadedFile(file, sheetName) {
2952
2985
  const fileName = file.originalname || "";
2953
2986
  const isCSV = fileName.toLowerCase().endsWith(".csv");
2954
2987
  let rawData = [];
@@ -2957,10 +2990,13 @@ async function parseUploadedFile(file) {
2957
2990
  rawData = parseCSV(csvContent);
2958
2991
  } else {
2959
2992
  const workbook = XLSX.read(file.buffer, { type: "buffer" });
2960
- const worksheetName = workbook.SheetNames[0];
2993
+ const worksheetName = sheetName || workbook.SheetNames[0];
2961
2994
  if (!worksheetName) {
2962
2995
  throw new Error("No worksheet found in file");
2963
2996
  }
2997
+ if (sheetName && !workbook.SheetNames.includes(sheetName)) {
2998
+ throw new Error(`Sheet "${sheetName}" not found in workbook`);
2999
+ }
2964
3000
  const worksheet = workbook.Sheets[worksheetName];
2965
3001
  rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
2966
3002
  }
@@ -3038,6 +3074,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
3038
3074
  continue;
3039
3075
  }
3040
3076
  const family = extractFamilyFromControlId(controlId);
3077
+ control._originalRowIndex = i;
3041
3078
  control.family = family;
3042
3079
  controls.push(control);
3043
3080
  if (!families.has(family)) {
@@ -3160,6 +3197,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3160
3197
  params.controlIdField,
3161
3198
  params.namingConvention
3162
3199
  );
3200
+ const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
3163
3201
  const controlSetData = {
3164
3202
  name: params.controlSetName,
3165
3203
  description: params.controlSetDescription,
@@ -3167,12 +3205,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3167
3205
  control_id_field: controlIdFieldNameClean,
3168
3206
  controlCount: controls.length,
3169
3207
  families: uniqueFamilies,
3208
+ controlOrder,
3170
3209
  fieldSchema
3171
3210
  };
3172
3211
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3173
3212
  const controlsDir = join4(baseDir, "controls");
3174
3213
  const mappingsDir = join4(baseDir, "mappings");
3175
- families.forEach((familyControls, family) => {
3214
+ const sortedFamilies = Array.from(families.entries()).sort(
3215
+ (a, b) => a[0].localeCompare(b[0])
3216
+ );
3217
+ sortedFamilies.forEach(([family, familyControls]) => {
3176
3218
  const familyDir = join4(controlsDir, family);
3177
3219
  const familyMappingsDir = join4(mappingsDir, family);
3178
3220
  if (!existsSync3(familyDir)) {
@@ -3181,7 +3223,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3181
3223
  if (!existsSync3(familyMappingsDir)) {
3182
3224
  mkdirSync2(familyMappingsDir, { recursive: true });
3183
3225
  }
3184
- familyControls.forEach((control) => {
3226
+ const sortedFamilyControls = familyControls.sort(
3227
+ (a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
3228
+ );
3229
+ sortedFamilyControls.forEach((control) => {
3185
3230
  const controlId = control[controlIdFieldNameClean];
3186
3231
  if (!controlId) {
3187
3232
  console.error("Missing control ID for control:", control);
@@ -3203,7 +3248,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3203
3248
  filteredControl.family = control.family;
3204
3249
  }
3205
3250
  Object.keys(control).forEach((fieldName) => {
3206
- if (fieldName === "family") return;
3251
+ if (fieldName === "family" || fieldName === "_originalRowIndex") return;
3207
3252
  if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3208
3253
  justificationContents.push(control[fieldName]);
3209
3254
  }
@@ -3567,12 +3612,14 @@ function exportAsJSON(controls, metadata, res) {
3567
3612
  res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
3568
3613
  res.json(exportData);
3569
3614
  }
3570
- var router, upload, spreadsheetRoutes_default;
3615
+ var MAX_HEADER_CANDIDATES, PREVIEW_COLUMNS, router, upload, spreadsheetRoutes_default;
3571
3616
  var init_spreadsheetRoutes = __esm({
3572
3617
  "cli/server/spreadsheetRoutes.ts"() {
3573
3618
  "use strict";
3574
3619
  init_debug();
3575
3620
  init_serverState();
3621
+ MAX_HEADER_CANDIDATES = 5;
3622
+ PREVIEW_COLUMNS = 4;
3576
3623
  router = express.Router();
3577
3624
  upload = multer({
3578
3625
  storage: multer.memoryStorage(),
@@ -3585,7 +3632,8 @@ var init_spreadsheetRoutes = __esm({
3585
3632
  return res.status(400).json({ error: "No file uploaded" });
3586
3633
  }
3587
3634
  const params = processImportParameters(req.body);
3588
- const rawData = await parseUploadedFile(req.file);
3635
+ const sheetName = req.body.sheetName;
3636
+ const rawData = await parseUploadedFile(req.file, sheetName);
3589
3637
  const startRowIndex = parseInt(params.startRow) - 1;
3590
3638
  if (rawData.length <= startRowIndex) {
3591
3639
  return res.status(400).json({ error: "Start row exceeds sheet data" });
@@ -3797,9 +3845,9 @@ var init_spreadsheetRoutes = __esm({
3797
3845
  const worksheet = workbook.Sheets[worksheetName];
3798
3846
  rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
3799
3847
  }
3800
- const headerCandidates = rows.slice(0, 5).map((row, index) => ({
3848
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
3801
3849
  row: index + 1,
3802
- preview: row.slice(0, 4).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3850
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3803
3851
  }));
3804
3852
  res.json({
3805
3853
  sheets,
@@ -3856,6 +3904,39 @@ var init_spreadsheetRoutes = __esm({
3856
3904
  res.status(500).json({ error: "Failed to parse Excel sheet" });
3857
3905
  }
3858
3906
  });
3907
+ router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, res) => {
3908
+ try {
3909
+ const { sheetName } = req.body;
3910
+ if (!req.file) {
3911
+ return res.status(400).json({ error: "No file uploaded" });
3912
+ }
3913
+ const fileName = req.file.originalname || "";
3914
+ const isCSV = fileName.toLowerCase().endsWith(".csv");
3915
+ let rows = [];
3916
+ if (isCSV) {
3917
+ const csvContent = req.file.buffer.toString("utf-8");
3918
+ rows = parseCSV(csvContent);
3919
+ } else {
3920
+ const workbook = XLSX.read(req.file.buffer, { type: "buffer" });
3921
+ if (!workbook.SheetNames.includes(sheetName)) {
3922
+ return res.status(400).json({ error: `Sheet "${sheetName}" not found` });
3923
+ }
3924
+ const worksheet = workbook.Sheets[sheetName];
3925
+ rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
3926
+ }
3927
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
3928
+ row: index + 1,
3929
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3930
+ }));
3931
+ res.json({
3932
+ rowPreviews: headerCandidates,
3933
+ totalRows: rows.length
3934
+ });
3935
+ } catch (error) {
3936
+ console.error("Error getting sheet previews:", error);
3937
+ res.status(500).json({ error: "Failed to get sheet previews" });
3938
+ }
3939
+ });
3859
3940
  spreadsheetRoutes_default = router;
3860
3941
  }
3861
3942
  });
@@ -1862,6 +1862,17 @@ var init_fileStore = __esm({
1862
1862
  if (!existsSync2(this.controlsDir)) {
1863
1863
  return [];
1864
1864
  }
1865
+ let controlOrder = null;
1866
+ try {
1867
+ const lulaConfigPath = join2(this.baseDir, "lula.yaml");
1868
+ if (existsSync2(lulaConfigPath)) {
1869
+ const content = readFileSync2(lulaConfigPath, "utf8");
1870
+ const metadata = yaml2.load(content);
1871
+ controlOrder = metadata?.controlOrder || null;
1872
+ }
1873
+ } catch (error) {
1874
+ console.error("Failed to load lula.yaml for controlOrder:", error);
1875
+ }
1865
1876
  const entries = readdirSync(this.controlsDir);
1866
1877
  const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
1867
1878
  if (yamlFiles.length > 0) {
@@ -1880,7 +1891,11 @@ var init_fileStore = __esm({
1880
1891
  }
1881
1892
  });
1882
1893
  const results2 = await Promise.all(promises);
1883
- return results2.filter((c) => c !== null);
1894
+ const controls2 = results2.filter((c) => c !== null);
1895
+ if (controlOrder && controlOrder.length > 0) {
1896
+ return this.sortControlsByOrder(controls2, controlOrder);
1897
+ }
1898
+ return controls2;
1884
1899
  }
1885
1900
  const families = entries.filter((name) => {
1886
1901
  const familyPath = join2(this.controlsDir, name);
@@ -1903,7 +1918,25 @@ var init_fileStore = __esm({
1903
1918
  allPromises.push(...familyPromises);
1904
1919
  }
1905
1920
  const results = await Promise.all(allPromises);
1906
- return results.filter((c) => c !== null);
1921
+ const controls = results.filter((c) => c !== null);
1922
+ if (controlOrder && controlOrder.length > 0) {
1923
+ return this.sortControlsByOrder(controls, controlOrder);
1924
+ }
1925
+ return controls;
1926
+ }
1927
+ /**
1928
+ * Sort controls based on the provided order array
1929
+ */
1930
+ sortControlsByOrder(controls, controlOrder) {
1931
+ const orderMap = /* @__PURE__ */ new Map();
1932
+ controlOrder.forEach((controlId, index) => {
1933
+ orderMap.set(controlId, index);
1934
+ });
1935
+ return controls.sort((a, b) => {
1936
+ const aIndex = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
1937
+ const bIndex = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
1938
+ return aIndex - bIndex;
1939
+ });
1907
1940
  }
1908
1941
  /**
1909
1942
  * Load mappings from mappings directory
@@ -2930,7 +2963,7 @@ function processImportParameters(reqBody) {
2930
2963
  frontendFieldSchema
2931
2964
  };
2932
2965
  }
2933
- async function parseUploadedFile(file) {
2966
+ async function parseUploadedFile(file, sheetName) {
2934
2967
  const fileName = file.originalname || "";
2935
2968
  const isCSV = fileName.toLowerCase().endsWith(".csv");
2936
2969
  let rawData = [];
@@ -2939,10 +2972,13 @@ async function parseUploadedFile(file) {
2939
2972
  rawData = parseCSV(csvContent);
2940
2973
  } else {
2941
2974
  const workbook = XLSX.read(file.buffer, { type: "buffer" });
2942
- const worksheetName = workbook.SheetNames[0];
2975
+ const worksheetName = sheetName || workbook.SheetNames[0];
2943
2976
  if (!worksheetName) {
2944
2977
  throw new Error("No worksheet found in file");
2945
2978
  }
2979
+ if (sheetName && !workbook.SheetNames.includes(sheetName)) {
2980
+ throw new Error(`Sheet "${sheetName}" not found in workbook`);
2981
+ }
2946
2982
  const worksheet = workbook.Sheets[worksheetName];
2947
2983
  rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
2948
2984
  }
@@ -3020,6 +3056,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
3020
3056
  continue;
3021
3057
  }
3022
3058
  const family = extractFamilyFromControlId(controlId);
3059
+ control._originalRowIndex = i;
3023
3060
  control.family = family;
3024
3061
  controls.push(control);
3025
3062
  if (!families.has(family)) {
@@ -3142,6 +3179,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3142
3179
  params.controlIdField,
3143
3180
  params.namingConvention
3144
3181
  );
3182
+ const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
3145
3183
  const controlSetData = {
3146
3184
  name: params.controlSetName,
3147
3185
  description: params.controlSetDescription,
@@ -3149,12 +3187,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3149
3187
  control_id_field: controlIdFieldNameClean,
3150
3188
  controlCount: controls.length,
3151
3189
  families: uniqueFamilies,
3190
+ controlOrder,
3152
3191
  fieldSchema
3153
3192
  };
3154
3193
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3155
3194
  const controlsDir = join4(baseDir, "controls");
3156
3195
  const mappingsDir = join4(baseDir, "mappings");
3157
- families.forEach((familyControls, family) => {
3196
+ const sortedFamilies = Array.from(families.entries()).sort(
3197
+ (a, b) => a[0].localeCompare(b[0])
3198
+ );
3199
+ sortedFamilies.forEach(([family, familyControls]) => {
3158
3200
  const familyDir = join4(controlsDir, family);
3159
3201
  const familyMappingsDir = join4(mappingsDir, family);
3160
3202
  if (!existsSync3(familyDir)) {
@@ -3163,7 +3205,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3163
3205
  if (!existsSync3(familyMappingsDir)) {
3164
3206
  mkdirSync2(familyMappingsDir, { recursive: true });
3165
3207
  }
3166
- familyControls.forEach((control) => {
3208
+ const sortedFamilyControls = familyControls.sort(
3209
+ (a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
3210
+ );
3211
+ sortedFamilyControls.forEach((control) => {
3167
3212
  const controlId = control[controlIdFieldNameClean];
3168
3213
  if (!controlId) {
3169
3214
  console.error("Missing control ID for control:", control);
@@ -3185,7 +3230,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3185
3230
  filteredControl.family = control.family;
3186
3231
  }
3187
3232
  Object.keys(control).forEach((fieldName) => {
3188
- if (fieldName === "family") return;
3233
+ if (fieldName === "family" || fieldName === "_originalRowIndex") return;
3189
3234
  if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3190
3235
  justificationContents.push(control[fieldName]);
3191
3236
  }
@@ -3549,12 +3594,14 @@ function exportAsJSON(controls, metadata, res) {
3549
3594
  res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
3550
3595
  res.json(exportData);
3551
3596
  }
3552
- var router, upload, spreadsheetRoutes_default;
3597
+ var MAX_HEADER_CANDIDATES, PREVIEW_COLUMNS, router, upload, spreadsheetRoutes_default;
3553
3598
  var init_spreadsheetRoutes = __esm({
3554
3599
  "cli/server/spreadsheetRoutes.ts"() {
3555
3600
  "use strict";
3556
3601
  init_debug();
3557
3602
  init_serverState();
3603
+ MAX_HEADER_CANDIDATES = 5;
3604
+ PREVIEW_COLUMNS = 4;
3558
3605
  router = express.Router();
3559
3606
  upload = multer({
3560
3607
  storage: multer.memoryStorage(),
@@ -3567,7 +3614,8 @@ var init_spreadsheetRoutes = __esm({
3567
3614
  return res.status(400).json({ error: "No file uploaded" });
3568
3615
  }
3569
3616
  const params = processImportParameters(req.body);
3570
- const rawData = await parseUploadedFile(req.file);
3617
+ const sheetName = req.body.sheetName;
3618
+ const rawData = await parseUploadedFile(req.file, sheetName);
3571
3619
  const startRowIndex = parseInt(params.startRow) - 1;
3572
3620
  if (rawData.length <= startRowIndex) {
3573
3621
  return res.status(400).json({ error: "Start row exceeds sheet data" });
@@ -3779,9 +3827,9 @@ var init_spreadsheetRoutes = __esm({
3779
3827
  const worksheet = workbook.Sheets[worksheetName];
3780
3828
  rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
3781
3829
  }
3782
- const headerCandidates = rows.slice(0, 5).map((row, index) => ({
3830
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
3783
3831
  row: index + 1,
3784
- preview: row.slice(0, 4).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3832
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3785
3833
  }));
3786
3834
  res.json({
3787
3835
  sheets,
@@ -3838,6 +3886,39 @@ var init_spreadsheetRoutes = __esm({
3838
3886
  res.status(500).json({ error: "Failed to parse Excel sheet" });
3839
3887
  }
3840
3888
  });
3889
+ router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, res) => {
3890
+ try {
3891
+ const { sheetName } = req.body;
3892
+ if (!req.file) {
3893
+ return res.status(400).json({ error: "No file uploaded" });
3894
+ }
3895
+ const fileName = req.file.originalname || "";
3896
+ const isCSV = fileName.toLowerCase().endsWith(".csv");
3897
+ let rows = [];
3898
+ if (isCSV) {
3899
+ const csvContent = req.file.buffer.toString("utf-8");
3900
+ rows = parseCSV(csvContent);
3901
+ } else {
3902
+ const workbook = XLSX.read(req.file.buffer, { type: "buffer" });
3903
+ if (!workbook.SheetNames.includes(sheetName)) {
3904
+ return res.status(400).json({ error: `Sheet "${sheetName}" not found` });
3905
+ }
3906
+ const worksheet = workbook.Sheets[sheetName];
3907
+ rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
3908
+ }
3909
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
3910
+ row: index + 1,
3911
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3912
+ }));
3913
+ res.json({
3914
+ rowPreviews: headerCandidates,
3915
+ totalRows: rows.length
3916
+ });
3917
+ } catch (error) {
3918
+ console.error("Error getting sheet previews:", error);
3919
+ res.status(500).json({ error: "Failed to get sheet previews" });
3920
+ }
3921
+ });
3841
3922
  spreadsheetRoutes_default = router;
3842
3923
  }
3843
3924
  });
@@ -1862,6 +1862,17 @@ var init_fileStore = __esm({
1862
1862
  if (!existsSync2(this.controlsDir)) {
1863
1863
  return [];
1864
1864
  }
1865
+ let controlOrder = null;
1866
+ try {
1867
+ const lulaConfigPath = join2(this.baseDir, "lula.yaml");
1868
+ if (existsSync2(lulaConfigPath)) {
1869
+ const content = readFileSync2(lulaConfigPath, "utf8");
1870
+ const metadata = yaml2.load(content);
1871
+ controlOrder = metadata?.controlOrder || null;
1872
+ }
1873
+ } catch (error) {
1874
+ console.error("Failed to load lula.yaml for controlOrder:", error);
1875
+ }
1865
1876
  const entries = readdirSync(this.controlsDir);
1866
1877
  const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
1867
1878
  if (yamlFiles.length > 0) {
@@ -1880,7 +1891,11 @@ var init_fileStore = __esm({
1880
1891
  }
1881
1892
  });
1882
1893
  const results2 = await Promise.all(promises);
1883
- return results2.filter((c) => c !== null);
1894
+ const controls2 = results2.filter((c) => c !== null);
1895
+ if (controlOrder && controlOrder.length > 0) {
1896
+ return this.sortControlsByOrder(controls2, controlOrder);
1897
+ }
1898
+ return controls2;
1884
1899
  }
1885
1900
  const families = entries.filter((name) => {
1886
1901
  const familyPath = join2(this.controlsDir, name);
@@ -1903,7 +1918,25 @@ var init_fileStore = __esm({
1903
1918
  allPromises.push(...familyPromises);
1904
1919
  }
1905
1920
  const results = await Promise.all(allPromises);
1906
- return results.filter((c) => c !== null);
1921
+ const controls = results.filter((c) => c !== null);
1922
+ if (controlOrder && controlOrder.length > 0) {
1923
+ return this.sortControlsByOrder(controls, controlOrder);
1924
+ }
1925
+ return controls;
1926
+ }
1927
+ /**
1928
+ * Sort controls based on the provided order array
1929
+ */
1930
+ sortControlsByOrder(controls, controlOrder) {
1931
+ const orderMap = /* @__PURE__ */ new Map();
1932
+ controlOrder.forEach((controlId, index) => {
1933
+ orderMap.set(controlId, index);
1934
+ });
1935
+ return controls.sort((a, b) => {
1936
+ const aIndex = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
1937
+ const bIndex = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
1938
+ return aIndex - bIndex;
1939
+ });
1907
1940
  }
1908
1941
  /**
1909
1942
  * Load mappings from mappings directory
@@ -2930,7 +2963,7 @@ function processImportParameters(reqBody) {
2930
2963
  frontendFieldSchema
2931
2964
  };
2932
2965
  }
2933
- async function parseUploadedFile(file) {
2966
+ async function parseUploadedFile(file, sheetName) {
2934
2967
  const fileName = file.originalname || "";
2935
2968
  const isCSV = fileName.toLowerCase().endsWith(".csv");
2936
2969
  let rawData = [];
@@ -2939,10 +2972,13 @@ async function parseUploadedFile(file) {
2939
2972
  rawData = parseCSV(csvContent);
2940
2973
  } else {
2941
2974
  const workbook = XLSX.read(file.buffer, { type: "buffer" });
2942
- const worksheetName = workbook.SheetNames[0];
2975
+ const worksheetName = sheetName || workbook.SheetNames[0];
2943
2976
  if (!worksheetName) {
2944
2977
  throw new Error("No worksheet found in file");
2945
2978
  }
2979
+ if (sheetName && !workbook.SheetNames.includes(sheetName)) {
2980
+ throw new Error(`Sheet "${sheetName}" not found in workbook`);
2981
+ }
2946
2982
  const worksheet = workbook.Sheets[worksheetName];
2947
2983
  rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
2948
2984
  }
@@ -3020,6 +3056,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
3020
3056
  continue;
3021
3057
  }
3022
3058
  const family = extractFamilyFromControlId(controlId);
3059
+ control._originalRowIndex = i;
3023
3060
  control.family = family;
3024
3061
  controls.push(control);
3025
3062
  if (!families.has(family)) {
@@ -3142,6 +3179,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3142
3179
  params.controlIdField,
3143
3180
  params.namingConvention
3144
3181
  );
3182
+ const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
3145
3183
  const controlSetData = {
3146
3184
  name: params.controlSetName,
3147
3185
  description: params.controlSetDescription,
@@ -3149,12 +3187,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3149
3187
  control_id_field: controlIdFieldNameClean,
3150
3188
  controlCount: controls.length,
3151
3189
  families: uniqueFamilies,
3190
+ controlOrder,
3152
3191
  fieldSchema
3153
3192
  };
3154
3193
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3155
3194
  const controlsDir = join4(baseDir, "controls");
3156
3195
  const mappingsDir = join4(baseDir, "mappings");
3157
- families.forEach((familyControls, family) => {
3196
+ const sortedFamilies = Array.from(families.entries()).sort(
3197
+ (a, b) => a[0].localeCompare(b[0])
3198
+ );
3199
+ sortedFamilies.forEach(([family, familyControls]) => {
3158
3200
  const familyDir = join4(controlsDir, family);
3159
3201
  const familyMappingsDir = join4(mappingsDir, family);
3160
3202
  if (!existsSync3(familyDir)) {
@@ -3163,7 +3205,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3163
3205
  if (!existsSync3(familyMappingsDir)) {
3164
3206
  mkdirSync2(familyMappingsDir, { recursive: true });
3165
3207
  }
3166
- familyControls.forEach((control) => {
3208
+ const sortedFamilyControls = familyControls.sort(
3209
+ (a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
3210
+ );
3211
+ sortedFamilyControls.forEach((control) => {
3167
3212
  const controlId = control[controlIdFieldNameClean];
3168
3213
  if (!controlId) {
3169
3214
  console.error("Missing control ID for control:", control);
@@ -3185,7 +3230,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
3185
3230
  filteredControl.family = control.family;
3186
3231
  }
3187
3232
  Object.keys(control).forEach((fieldName) => {
3188
- if (fieldName === "family") return;
3233
+ if (fieldName === "family" || fieldName === "_originalRowIndex") return;
3189
3234
  if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3190
3235
  justificationContents.push(control[fieldName]);
3191
3236
  }
@@ -3549,12 +3594,14 @@ function exportAsJSON(controls, metadata, res) {
3549
3594
  res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
3550
3595
  res.json(exportData);
3551
3596
  }
3552
- var router, upload, spreadsheetRoutes_default;
3597
+ var MAX_HEADER_CANDIDATES, PREVIEW_COLUMNS, router, upload, spreadsheetRoutes_default;
3553
3598
  var init_spreadsheetRoutes = __esm({
3554
3599
  "cli/server/spreadsheetRoutes.ts"() {
3555
3600
  "use strict";
3556
3601
  init_debug();
3557
3602
  init_serverState();
3603
+ MAX_HEADER_CANDIDATES = 5;
3604
+ PREVIEW_COLUMNS = 4;
3558
3605
  router = express.Router();
3559
3606
  upload = multer({
3560
3607
  storage: multer.memoryStorage(),
@@ -3567,7 +3614,8 @@ var init_spreadsheetRoutes = __esm({
3567
3614
  return res.status(400).json({ error: "No file uploaded" });
3568
3615
  }
3569
3616
  const params = processImportParameters(req.body);
3570
- const rawData = await parseUploadedFile(req.file);
3617
+ const sheetName = req.body.sheetName;
3618
+ const rawData = await parseUploadedFile(req.file, sheetName);
3571
3619
  const startRowIndex = parseInt(params.startRow) - 1;
3572
3620
  if (rawData.length <= startRowIndex) {
3573
3621
  return res.status(400).json({ error: "Start row exceeds sheet data" });
@@ -3779,9 +3827,9 @@ var init_spreadsheetRoutes = __esm({
3779
3827
  const worksheet = workbook.Sheets[worksheetName];
3780
3828
  rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
3781
3829
  }
3782
- const headerCandidates = rows.slice(0, 5).map((row, index) => ({
3830
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
3783
3831
  row: index + 1,
3784
- preview: row.slice(0, 4).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3832
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3785
3833
  }));
3786
3834
  res.json({
3787
3835
  sheets,
@@ -3838,6 +3886,39 @@ var init_spreadsheetRoutes = __esm({
3838
3886
  res.status(500).json({ error: "Failed to parse Excel sheet" });
3839
3887
  }
3840
3888
  });
3889
+ router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, res) => {
3890
+ try {
3891
+ const { sheetName } = req.body;
3892
+ if (!req.file) {
3893
+ return res.status(400).json({ error: "No file uploaded" });
3894
+ }
3895
+ const fileName = req.file.originalname || "";
3896
+ const isCSV = fileName.toLowerCase().endsWith(".csv");
3897
+ let rows = [];
3898
+ if (isCSV) {
3899
+ const csvContent = req.file.buffer.toString("utf-8");
3900
+ rows = parseCSV(csvContent);
3901
+ } else {
3902
+ const workbook = XLSX.read(req.file.buffer, { type: "buffer" });
3903
+ if (!workbook.SheetNames.includes(sheetName)) {
3904
+ return res.status(400).json({ error: `Sheet "${sheetName}" not found` });
3905
+ }
3906
+ const worksheet = workbook.Sheets[sheetName];
3907
+ rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
3908
+ }
3909
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
3910
+ row: index + 1,
3911
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
3912
+ }));
3913
+ res.json({
3914
+ rowPreviews: headerCandidates,
3915
+ totalRows: rows.length
3916
+ });
3917
+ } catch (error) {
3918
+ console.error("Error getting sheet previews:", error);
3919
+ res.status(500).json({ error: "Failed to get sheet previews" });
3920
+ }
3921
+ });
3841
3922
  spreadsheetRoutes_default = router;
3842
3923
  }
3843
3924
  });