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.
@@ -283,6 +283,17 @@ var FileStore = class {
283
283
  if (!existsSync2(this.controlsDir)) {
284
284
  return [];
285
285
  }
286
+ let controlOrder = null;
287
+ try {
288
+ const lulaConfigPath = join2(this.baseDir, "lula.yaml");
289
+ if (existsSync2(lulaConfigPath)) {
290
+ const content = readFileSync2(lulaConfigPath, "utf8");
291
+ const metadata = yaml2.load(content);
292
+ controlOrder = metadata?.controlOrder || null;
293
+ }
294
+ } catch (error) {
295
+ console.error("Failed to load lula.yaml for controlOrder:", error);
296
+ }
286
297
  const entries = readdirSync(this.controlsDir);
287
298
  const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
288
299
  if (yamlFiles.length > 0) {
@@ -301,7 +312,11 @@ var FileStore = class {
301
312
  }
302
313
  });
303
314
  const results2 = await Promise.all(promises);
304
- return results2.filter((c) => c !== null);
315
+ const controls2 = results2.filter((c) => c !== null);
316
+ if (controlOrder && controlOrder.length > 0) {
317
+ return this.sortControlsByOrder(controls2, controlOrder);
318
+ }
319
+ return controls2;
305
320
  }
306
321
  const families = entries.filter((name) => {
307
322
  const familyPath = join2(this.controlsDir, name);
@@ -324,7 +339,25 @@ var FileStore = class {
324
339
  allPromises.push(...familyPromises);
325
340
  }
326
341
  const results = await Promise.all(allPromises);
327
- return results.filter((c) => c !== null);
342
+ const controls = results.filter((c) => c !== null);
343
+ if (controlOrder && controlOrder.length > 0) {
344
+ return this.sortControlsByOrder(controls, controlOrder);
345
+ }
346
+ return controls;
347
+ }
348
+ /**
349
+ * Sort controls based on the provided order array
350
+ */
351
+ sortControlsByOrder(controls, controlOrder) {
352
+ const orderMap = /* @__PURE__ */ new Map();
353
+ controlOrder.forEach((controlId, index) => {
354
+ orderMap.set(controlId, index);
355
+ });
356
+ return controls.sort((a, b) => {
357
+ const aIndex = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
358
+ const bIndex = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
359
+ return aIndex - bIndex;
360
+ });
328
361
  }
329
362
  /**
330
363
  * Load mappings from mappings directory
@@ -46,6 +46,8 @@ function getCurrentControlSetPath() {
46
46
  }
47
47
 
48
48
  // cli/server/spreadsheetRoutes.ts
49
+ var MAX_HEADER_CANDIDATES = 5;
50
+ var PREVIEW_COLUMNS = 4;
49
51
  var router = express.Router();
50
52
  var upload = multer({
51
53
  storage: multer.memoryStorage(),
@@ -132,7 +134,7 @@ function processImportParameters(reqBody) {
132
134
  frontendFieldSchema
133
135
  };
134
136
  }
135
- async function parseUploadedFile(file) {
137
+ async function parseUploadedFile(file, sheetName) {
136
138
  const fileName = file.originalname || "";
137
139
  const isCSV = fileName.toLowerCase().endsWith(".csv");
138
140
  let rawData = [];
@@ -141,10 +143,13 @@ async function parseUploadedFile(file) {
141
143
  rawData = parseCSV(csvContent);
142
144
  } else {
143
145
  const workbook = XLSX.read(file.buffer, { type: "buffer" });
144
- const worksheetName = workbook.SheetNames[0];
146
+ const worksheetName = sheetName || workbook.SheetNames[0];
145
147
  if (!worksheetName) {
146
148
  throw new Error("No worksheet found in file");
147
149
  }
150
+ if (sheetName && !workbook.SheetNames.includes(sheetName)) {
151
+ throw new Error(`Sheet "${sheetName}" not found in workbook`);
152
+ }
148
153
  const worksheet = workbook.Sheets[worksheetName];
149
154
  rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
150
155
  }
@@ -156,7 +161,8 @@ router.post("/import-spreadsheet", upload.single("file"), async (req, res) => {
156
161
  return res.status(400).json({ error: "No file uploaded" });
157
162
  }
158
163
  const params = processImportParameters(req.body);
159
- const rawData = await parseUploadedFile(req.file);
164
+ const sheetName = req.body.sheetName;
165
+ const rawData = await parseUploadedFile(req.file, sheetName);
160
166
  const startRowIndex = parseInt(params.startRow) - 1;
161
167
  if (rawData.length <= startRowIndex) {
162
168
  return res.status(400).json({ error: "Start row exceeds sheet data" });
@@ -261,6 +267,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
261
267
  continue;
262
268
  }
263
269
  const family = extractFamilyFromControlId(controlId);
270
+ control._originalRowIndex = i;
264
271
  control.family = family;
265
272
  controls.push(control);
266
273
  if (!families.has(family)) {
@@ -383,6 +390,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
383
390
  params.controlIdField,
384
391
  params.namingConvention
385
392
  );
393
+ const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
386
394
  const controlSetData = {
387
395
  name: params.controlSetName,
388
396
  description: params.controlSetDescription,
@@ -390,12 +398,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
390
398
  control_id_field: controlIdFieldNameClean,
391
399
  controlCount: controls.length,
392
400
  families: uniqueFamilies,
401
+ controlOrder,
393
402
  fieldSchema
394
403
  };
395
404
  writeFileSync(join2(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
396
405
  const controlsDir = join2(baseDir, "controls");
397
406
  const mappingsDir = join2(baseDir, "mappings");
398
- families.forEach((familyControls, family) => {
407
+ const sortedFamilies = Array.from(families.entries()).sort(
408
+ (a, b) => a[0].localeCompare(b[0])
409
+ );
410
+ sortedFamilies.forEach(([family, familyControls]) => {
399
411
  const familyDir = join2(controlsDir, family);
400
412
  const familyMappingsDir = join2(mappingsDir, family);
401
413
  if (!existsSync(familyDir)) {
@@ -404,7 +416,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
404
416
  if (!existsSync(familyMappingsDir)) {
405
417
  mkdirSync(familyMappingsDir, { recursive: true });
406
418
  }
407
- familyControls.forEach((control) => {
419
+ const sortedFamilyControls = familyControls.sort(
420
+ (a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
421
+ );
422
+ sortedFamilyControls.forEach((control) => {
408
423
  const controlId = control[controlIdFieldNameClean];
409
424
  if (!controlId) {
410
425
  console.error("Missing control ID for control:", control);
@@ -426,7 +441,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
426
441
  filteredControl.family = control.family;
427
442
  }
428
443
  Object.keys(control).forEach((fieldName) => {
429
- if (fieldName === "family") return;
444
+ if (fieldName === "family" || fieldName === "_originalRowIndex") return;
430
445
  if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
431
446
  justificationContents.push(control[fieldName]);
432
447
  }
@@ -969,9 +984,9 @@ router.post("/parse-excel", upload.single("file"), async (req, res) => {
969
984
  const worksheet = workbook.Sheets[worksheetName];
970
985
  rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
971
986
  }
972
- const headerCandidates = rows.slice(0, 5).map((row, index) => ({
987
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
973
988
  row: index + 1,
974
- preview: row.slice(0, 4).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
989
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
975
990
  }));
976
991
  res.json({
977
992
  sheets,
@@ -1028,6 +1043,39 @@ router.post("/parse-excel-sheet", upload.single("file"), async (req, res) => {
1028
1043
  res.status(500).json({ error: "Failed to parse Excel sheet" });
1029
1044
  }
1030
1045
  });
1046
+ router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, res) => {
1047
+ try {
1048
+ const { sheetName } = req.body;
1049
+ if (!req.file) {
1050
+ return res.status(400).json({ error: "No file uploaded" });
1051
+ }
1052
+ const fileName = req.file.originalname || "";
1053
+ const isCSV = fileName.toLowerCase().endsWith(".csv");
1054
+ let rows = [];
1055
+ if (isCSV) {
1056
+ const csvContent = req.file.buffer.toString("utf-8");
1057
+ rows = parseCSV(csvContent);
1058
+ } else {
1059
+ const workbook = XLSX.read(req.file.buffer, { type: "buffer" });
1060
+ if (!workbook.SheetNames.includes(sheetName)) {
1061
+ return res.status(400).json({ error: `Sheet "${sheetName}" not found` });
1062
+ }
1063
+ const worksheet = workbook.Sheets[sheetName];
1064
+ rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
1065
+ }
1066
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
1067
+ row: index + 1,
1068
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
1069
+ }));
1070
+ res.json({
1071
+ rowPreviews: headerCandidates,
1072
+ totalRows: rows.length
1073
+ });
1074
+ } catch (error) {
1075
+ console.error("Error getting sheet previews:", error);
1076
+ res.status(500).json({ error: "Failed to get sheet previews" });
1077
+ }
1078
+ });
1031
1079
  var spreadsheetRoutes_default = router;
1032
1080
  export {
1033
1081
  applyNamingConvention,
@@ -309,6 +309,17 @@ var init_fileStore = __esm({
309
309
  if (!existsSync2(this.controlsDir)) {
310
310
  return [];
311
311
  }
312
+ let controlOrder = null;
313
+ try {
314
+ const lulaConfigPath = join2(this.baseDir, "lula.yaml");
315
+ if (existsSync2(lulaConfigPath)) {
316
+ const content = readFileSync2(lulaConfigPath, "utf8");
317
+ const metadata = yaml2.load(content);
318
+ controlOrder = metadata?.controlOrder || null;
319
+ }
320
+ } catch (error) {
321
+ console.error("Failed to load lula.yaml for controlOrder:", error);
322
+ }
312
323
  const entries = readdirSync(this.controlsDir);
313
324
  const yamlFiles = entries.filter((file) => file.endsWith(".yaml"));
314
325
  if (yamlFiles.length > 0) {
@@ -327,7 +338,11 @@ var init_fileStore = __esm({
327
338
  }
328
339
  });
329
340
  const results2 = await Promise.all(promises);
330
- return results2.filter((c) => c !== null);
341
+ const controls2 = results2.filter((c) => c !== null);
342
+ if (controlOrder && controlOrder.length > 0) {
343
+ return this.sortControlsByOrder(controls2, controlOrder);
344
+ }
345
+ return controls2;
331
346
  }
332
347
  const families = entries.filter((name) => {
333
348
  const familyPath = join2(this.controlsDir, name);
@@ -350,7 +365,25 @@ var init_fileStore = __esm({
350
365
  allPromises.push(...familyPromises);
351
366
  }
352
367
  const results = await Promise.all(allPromises);
353
- return results.filter((c) => c !== null);
368
+ const controls = results.filter((c) => c !== null);
369
+ if (controlOrder && controlOrder.length > 0) {
370
+ return this.sortControlsByOrder(controls, controlOrder);
371
+ }
372
+ return controls;
373
+ }
374
+ /**
375
+ * Sort controls based on the provided order array
376
+ */
377
+ sortControlsByOrder(controls, controlOrder) {
378
+ const orderMap = /* @__PURE__ */ new Map();
379
+ controlOrder.forEach((controlId, index) => {
380
+ orderMap.set(controlId, index);
381
+ });
382
+ return controls.sort((a, b) => {
383
+ const aIndex = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
384
+ const bIndex = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
385
+ return aIndex - bIndex;
386
+ });
354
387
  }
355
388
  /**
356
389
  * Load mappings from mappings directory
@@ -1377,7 +1410,7 @@ function processImportParameters(reqBody) {
1377
1410
  frontendFieldSchema
1378
1411
  };
1379
1412
  }
1380
- async function parseUploadedFile(file) {
1413
+ async function parseUploadedFile(file, sheetName) {
1381
1414
  const fileName = file.originalname || "";
1382
1415
  const isCSV = fileName.toLowerCase().endsWith(".csv");
1383
1416
  let rawData = [];
@@ -1386,10 +1419,13 @@ async function parseUploadedFile(file) {
1386
1419
  rawData = parseCSV(csvContent);
1387
1420
  } else {
1388
1421
  const workbook = XLSX.read(file.buffer, { type: "buffer" });
1389
- const worksheetName = workbook.SheetNames[0];
1422
+ const worksheetName = sheetName || workbook.SheetNames[0];
1390
1423
  if (!worksheetName) {
1391
1424
  throw new Error("No worksheet found in file");
1392
1425
  }
1426
+ if (sheetName && !workbook.SheetNames.includes(sheetName)) {
1427
+ throw new Error(`Sheet "${sheetName}" not found in workbook`);
1428
+ }
1393
1429
  const worksheet = workbook.Sheets[worksheetName];
1394
1430
  rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
1395
1431
  }
@@ -1467,6 +1503,7 @@ function processSpreadsheetData(rawData, headers, startRowIndex, params) {
1467
1503
  continue;
1468
1504
  }
1469
1505
  const family = extractFamilyFromControlId(controlId);
1506
+ control._originalRowIndex = i;
1470
1507
  control.family = family;
1471
1508
  controls.push(control);
1472
1509
  if (!families.has(family)) {
@@ -1589,6 +1626,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
1589
1626
  params.controlIdField,
1590
1627
  params.namingConvention
1591
1628
  );
1629
+ const controlOrder = controls.sort((a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)).map((control) => control[controlIdFieldNameClean]);
1592
1630
  const controlSetData = {
1593
1631
  name: params.controlSetName,
1594
1632
  description: params.controlSetDescription,
@@ -1596,12 +1634,16 @@ async function createOutputStructure(processedData, fieldSchema, params) {
1596
1634
  control_id_field: controlIdFieldNameClean,
1597
1635
  controlCount: controls.length,
1598
1636
  families: uniqueFamilies,
1637
+ controlOrder,
1599
1638
  fieldSchema
1600
1639
  };
1601
1640
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
1602
1641
  const controlsDir = join4(baseDir, "controls");
1603
1642
  const mappingsDir = join4(baseDir, "mappings");
1604
- families.forEach((familyControls, family) => {
1643
+ const sortedFamilies = Array.from(families.entries()).sort(
1644
+ (a, b) => a[0].localeCompare(b[0])
1645
+ );
1646
+ sortedFamilies.forEach(([family, familyControls]) => {
1605
1647
  const familyDir = join4(controlsDir, family);
1606
1648
  const familyMappingsDir = join4(mappingsDir, family);
1607
1649
  if (!existsSync3(familyDir)) {
@@ -1610,7 +1652,10 @@ async function createOutputStructure(processedData, fieldSchema, params) {
1610
1652
  if (!existsSync3(familyMappingsDir)) {
1611
1653
  mkdirSync2(familyMappingsDir, { recursive: true });
1612
1654
  }
1613
- familyControls.forEach((control) => {
1655
+ const sortedFamilyControls = familyControls.sort(
1656
+ (a, b) => (a._originalRowIndex || 0) - (b._originalRowIndex || 0)
1657
+ );
1658
+ sortedFamilyControls.forEach((control) => {
1614
1659
  const controlId = control[controlIdFieldNameClean];
1615
1660
  if (!controlId) {
1616
1661
  console.error("Missing control ID for control:", control);
@@ -1632,7 +1677,7 @@ async function createOutputStructure(processedData, fieldSchema, params) {
1632
1677
  filteredControl.family = control.family;
1633
1678
  }
1634
1679
  Object.keys(control).forEach((fieldName) => {
1635
- if (fieldName === "family") return;
1680
+ if (fieldName === "family" || fieldName === "_originalRowIndex") return;
1636
1681
  if (params.justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
1637
1682
  justificationContents.push(control[fieldName]);
1638
1683
  }
@@ -1996,12 +2041,14 @@ function exportAsJSON(controls, metadata, res) {
1996
2041
  res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
1997
2042
  res.json(exportData);
1998
2043
  }
1999
- var router, upload, spreadsheetRoutes_default;
2044
+ var MAX_HEADER_CANDIDATES, PREVIEW_COLUMNS, router, upload, spreadsheetRoutes_default;
2000
2045
  var init_spreadsheetRoutes = __esm({
2001
2046
  "cli/server/spreadsheetRoutes.ts"() {
2002
2047
  "use strict";
2003
2048
  init_debug();
2004
2049
  init_serverState();
2050
+ MAX_HEADER_CANDIDATES = 5;
2051
+ PREVIEW_COLUMNS = 4;
2005
2052
  router = express.Router();
2006
2053
  upload = multer({
2007
2054
  storage: multer.memoryStorage(),
@@ -2014,7 +2061,8 @@ var init_spreadsheetRoutes = __esm({
2014
2061
  return res.status(400).json({ error: "No file uploaded" });
2015
2062
  }
2016
2063
  const params = processImportParameters(req.body);
2017
- const rawData = await parseUploadedFile(req.file);
2064
+ const sheetName = req.body.sheetName;
2065
+ const rawData = await parseUploadedFile(req.file, sheetName);
2018
2066
  const startRowIndex = parseInt(params.startRow) - 1;
2019
2067
  if (rawData.length <= startRowIndex) {
2020
2068
  return res.status(400).json({ error: "Start row exceeds sheet data" });
@@ -2226,9 +2274,9 @@ var init_spreadsheetRoutes = __esm({
2226
2274
  const worksheet = workbook.Sheets[worksheetName];
2227
2275
  rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
2228
2276
  }
2229
- const headerCandidates = rows.slice(0, 5).map((row, index) => ({
2277
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
2230
2278
  row: index + 1,
2231
- preview: row.slice(0, 4).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
2279
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
2232
2280
  }));
2233
2281
  res.json({
2234
2282
  sheets,
@@ -2285,6 +2333,39 @@ var init_spreadsheetRoutes = __esm({
2285
2333
  res.status(500).json({ error: "Failed to parse Excel sheet" });
2286
2334
  }
2287
2335
  });
2336
+ router.post("/parse-excel-sheet-previews", upload.single("file"), async (req, res) => {
2337
+ try {
2338
+ const { sheetName } = req.body;
2339
+ if (!req.file) {
2340
+ return res.status(400).json({ error: "No file uploaded" });
2341
+ }
2342
+ const fileName = req.file.originalname || "";
2343
+ const isCSV = fileName.toLowerCase().endsWith(".csv");
2344
+ let rows = [];
2345
+ if (isCSV) {
2346
+ const csvContent = req.file.buffer.toString("utf-8");
2347
+ rows = parseCSV(csvContent);
2348
+ } else {
2349
+ const workbook = XLSX.read(req.file.buffer, { type: "buffer" });
2350
+ if (!workbook.SheetNames.includes(sheetName)) {
2351
+ return res.status(400).json({ error: `Sheet "${sheetName}" not found` });
2352
+ }
2353
+ const worksheet = workbook.Sheets[sheetName];
2354
+ rows = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: null });
2355
+ }
2356
+ const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
2357
+ row: index + 1,
2358
+ preview: row.slice(0, PREVIEW_COLUMNS).filter((v) => v !== null).filter((v) => v !== void 0).join(", ") + (row.length > 4 ? ", ..." : "")
2359
+ }));
2360
+ res.json({
2361
+ rowPreviews: headerCandidates,
2362
+ totalRows: rows.length
2363
+ });
2364
+ } catch (error) {
2365
+ console.error("Error getting sheet previews:", error);
2366
+ res.status(500).json({ error: "Failed to get sheet previews" });
2367
+ }
2368
+ });
2288
2369
  spreadsheetRoutes_default = router;
2289
2370
  }
2290
2371
  });
package/dist/index.html CHANGED
@@ -6,10 +6,10 @@
6
6
  <link rel="icon" href="/lula.png" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1" />
8
8
 
9
- <link rel="modulepreload" href="/_app/immutable/entry/start.DtOV9FzL.js">
10
- <link rel="modulepreload" href="/_app/immutable/chunks/DtmRvgOL.js">
9
+ <link rel="modulepreload" href="/_app/immutable/entry/start.Qohy1rto.js">
10
+ <link rel="modulepreload" href="/_app/immutable/chunks/B9HtV8_1.js">
11
11
  <link rel="modulepreload" href="/_app/immutable/chunks/DTWPdvjs.js">
12
- <link rel="modulepreload" href="/_app/immutable/entry/app.BU_7sxPU.js">
12
+ <link rel="modulepreload" href="/_app/immutable/entry/app.DI-AYM-e.js">
13
13
  <link rel="modulepreload" href="/_app/immutable/chunks/DsnmJJEf.js">
14
14
  <link rel="modulepreload" href="/_app/immutable/chunks/BXUi170M.js">
15
15
  <link rel="modulepreload" href="/_app/immutable/chunks/WlyXjfrM.js">
@@ -19,15 +19,15 @@
19
19
  <div style="display: contents">
20
20
  <script>
21
21
  {
22
- __sveltekit_tjh1f6 = {
22
+ __sveltekit_157sdlo = {
23
23
  base: ""
24
24
  };
25
25
 
26
26
  const element = document.currentScript.parentElement;
27
27
 
28
28
  Promise.all([
29
- import("/_app/immutable/entry/start.DtOV9FzL.js"),
30
- import("/_app/immutable/entry/app.BU_7sxPU.js")
29
+ import("/_app/immutable/entry/start.Qohy1rto.js"),
30
+ import("/_app/immutable/entry/app.DI-AYM-e.js")
31
31
  ]).then(([kit, app]) => {
32
32
  kit.start(app, element);
33
33
  });