lula2 0.1.1 → 0.2.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.
@@ -2828,6 +2828,7 @@ __export(spreadsheetRoutes_exports, {
2828
2828
  default: () => spreadsheetRoutes_default,
2829
2829
  scanControlSets: () => scanControlSets
2830
2830
  });
2831
+ import crypto from "crypto";
2831
2832
  import { parse as parseCSVSync } from "csv-parse/sync";
2832
2833
  import ExcelJS from "exceljs";
2833
2834
  import express from "express";
@@ -3146,6 +3147,15 @@ var init_spreadsheetRoutes = __esm({
3146
3147
  controlSetName = "Imported Control Set",
3147
3148
  controlSetDescription = "Imported from spreadsheet"
3148
3149
  } = req.body;
3150
+ let justificationFields = [];
3151
+ if (req.body.justificationFields) {
3152
+ try {
3153
+ justificationFields = JSON.parse(req.body.justificationFields);
3154
+ debug("Justification fields received:", justificationFields);
3155
+ } catch (e) {
3156
+ console.error("Failed to parse justification fields:", e);
3157
+ }
3158
+ }
3149
3159
  debug("Import parameters received:", {
3150
3160
  controlIdField,
3151
3161
  startRow,
@@ -3393,11 +3403,17 @@ var init_spreadsheetRoutes = __esm({
3393
3403
  };
3394
3404
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3395
3405
  const controlsDir = join4(baseDir, "controls");
3406
+ const mappingsDir = join4(baseDir, "mappings");
3407
+ const justificationFieldNames = justificationFields;
3396
3408
  families.forEach((familyControls, family) => {
3397
3409
  const familyDir = join4(controlsDir, family);
3410
+ const familyMappingsDir = join4(mappingsDir, family);
3398
3411
  if (!existsSync3(familyDir)) {
3399
3412
  mkdirSync2(familyDir, { recursive: true });
3400
3413
  }
3414
+ if (!existsSync3(familyMappingsDir)) {
3415
+ mkdirSync2(familyMappingsDir, { recursive: true });
3416
+ }
3401
3417
  familyControls.forEach((control) => {
3402
3418
  const controlId = control[controlIdFieldNameClean];
3403
3419
  if (!controlId) {
@@ -3407,12 +3423,24 @@ var init_spreadsheetRoutes = __esm({
3407
3423
  const controlIdStr = String(controlId).slice(0, 50);
3408
3424
  const fileName2 = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}.yaml`;
3409
3425
  const filePath = join4(familyDir, fileName2);
3426
+ const mappingFileName = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}-mappings.yaml`;
3427
+ const mappingFilePath = join4(familyMappingsDir, mappingFileName);
3410
3428
  const filteredControl = {};
3429
+ const mappingData = {
3430
+ control_id: controlIdStr,
3431
+ justification: "",
3432
+ uuid: crypto.randomUUID()
3433
+ };
3434
+ const justificationContents = [];
3411
3435
  if (control.family !== void 0) {
3412
3436
  filteredControl.family = control.family;
3413
3437
  }
3414
3438
  Object.keys(control).forEach((fieldName) => {
3415
3439
  if (fieldName === "family") return;
3440
+ if (justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3441
+ justificationContents.push(control[fieldName]);
3442
+ return;
3443
+ }
3416
3444
  const isInFrontendSchema = frontendFieldSchema?.some((f) => f.fieldName === fieldName);
3417
3445
  const isInFieldsMetadata = fields.hasOwnProperty(fieldName);
3418
3446
  if (isInFrontendSchema || isInFieldsMetadata) {
@@ -3420,6 +3448,13 @@ var init_spreadsheetRoutes = __esm({
3420
3448
  }
3421
3449
  });
3422
3450
  writeFileSync2(filePath, yaml4.dump(filteredControl));
3451
+ if (justificationContents.length > 0) {
3452
+ mappingData.justification = justificationContents.join("\n\n");
3453
+ }
3454
+ if (mappingData.justification && mappingData.justification.trim() !== "") {
3455
+ const mappingArray = [mappingData];
3456
+ writeFileSync2(mappingFilePath, yaml4.dump(mappingArray));
3457
+ }
3423
3458
  });
3424
3459
  });
3425
3460
  res.json({
@@ -4533,8 +4568,8 @@ var WebSocketManager = class {
4533
4568
  if (payload && payload.control_id) {
4534
4569
  const mapping = payload;
4535
4570
  if (!mapping.uuid) {
4536
- const crypto = await import("crypto");
4537
- mapping.uuid = crypto.randomUUID();
4571
+ const crypto2 = await import("crypto");
4572
+ mapping.uuid = crypto2.randomUUID();
4538
4573
  }
4539
4574
  await state.fileStore.saveMapping(mapping);
4540
4575
  state.mappingsCache.set(mapping.uuid, mapping);
@@ -2810,6 +2810,7 @@ __export(spreadsheetRoutes_exports, {
2810
2810
  default: () => spreadsheetRoutes_default,
2811
2811
  scanControlSets: () => scanControlSets
2812
2812
  });
2813
+ import crypto from "crypto";
2813
2814
  import { parse as parseCSVSync } from "csv-parse/sync";
2814
2815
  import ExcelJS from "exceljs";
2815
2816
  import express from "express";
@@ -3128,6 +3129,15 @@ var init_spreadsheetRoutes = __esm({
3128
3129
  controlSetName = "Imported Control Set",
3129
3130
  controlSetDescription = "Imported from spreadsheet"
3130
3131
  } = req.body;
3132
+ let justificationFields = [];
3133
+ if (req.body.justificationFields) {
3134
+ try {
3135
+ justificationFields = JSON.parse(req.body.justificationFields);
3136
+ debug("Justification fields received:", justificationFields);
3137
+ } catch (e) {
3138
+ console.error("Failed to parse justification fields:", e);
3139
+ }
3140
+ }
3131
3141
  debug("Import parameters received:", {
3132
3142
  controlIdField,
3133
3143
  startRow,
@@ -3375,11 +3385,17 @@ var init_spreadsheetRoutes = __esm({
3375
3385
  };
3376
3386
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3377
3387
  const controlsDir = join4(baseDir, "controls");
3388
+ const mappingsDir = join4(baseDir, "mappings");
3389
+ const justificationFieldNames = justificationFields;
3378
3390
  families.forEach((familyControls, family) => {
3379
3391
  const familyDir = join4(controlsDir, family);
3392
+ const familyMappingsDir = join4(mappingsDir, family);
3380
3393
  if (!existsSync3(familyDir)) {
3381
3394
  mkdirSync2(familyDir, { recursive: true });
3382
3395
  }
3396
+ if (!existsSync3(familyMappingsDir)) {
3397
+ mkdirSync2(familyMappingsDir, { recursive: true });
3398
+ }
3383
3399
  familyControls.forEach((control) => {
3384
3400
  const controlId = control[controlIdFieldNameClean];
3385
3401
  if (!controlId) {
@@ -3389,12 +3405,24 @@ var init_spreadsheetRoutes = __esm({
3389
3405
  const controlIdStr = String(controlId).slice(0, 50);
3390
3406
  const fileName2 = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}.yaml`;
3391
3407
  const filePath = join4(familyDir, fileName2);
3408
+ const mappingFileName = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}-mappings.yaml`;
3409
+ const mappingFilePath = join4(familyMappingsDir, mappingFileName);
3392
3410
  const filteredControl = {};
3411
+ const mappingData = {
3412
+ control_id: controlIdStr,
3413
+ justification: "",
3414
+ uuid: crypto.randomUUID()
3415
+ };
3416
+ const justificationContents = [];
3393
3417
  if (control.family !== void 0) {
3394
3418
  filteredControl.family = control.family;
3395
3419
  }
3396
3420
  Object.keys(control).forEach((fieldName) => {
3397
3421
  if (fieldName === "family") return;
3422
+ if (justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3423
+ justificationContents.push(control[fieldName]);
3424
+ return;
3425
+ }
3398
3426
  const isInFrontendSchema = frontendFieldSchema?.some((f) => f.fieldName === fieldName);
3399
3427
  const isInFieldsMetadata = fields.hasOwnProperty(fieldName);
3400
3428
  if (isInFrontendSchema || isInFieldsMetadata) {
@@ -3402,6 +3430,13 @@ var init_spreadsheetRoutes = __esm({
3402
3430
  }
3403
3431
  });
3404
3432
  writeFileSync2(filePath, yaml4.dump(filteredControl));
3433
+ if (justificationContents.length > 0) {
3434
+ mappingData.justification = justificationContents.join("\n\n");
3435
+ }
3436
+ if (mappingData.justification && mappingData.justification.trim() !== "") {
3437
+ const mappingArray = [mappingData];
3438
+ writeFileSync2(mappingFilePath, yaml4.dump(mappingArray));
3439
+ }
3405
3440
  });
3406
3441
  });
3407
3442
  res.json({
@@ -4510,8 +4545,8 @@ var WebSocketManager = class {
4510
4545
  if (payload && payload.control_id) {
4511
4546
  const mapping = payload;
4512
4547
  if (!mapping.uuid) {
4513
- const crypto = await import("crypto");
4514
- mapping.uuid = crypto.randomUUID();
4548
+ const crypto2 = await import("crypto");
4549
+ mapping.uuid = crypto2.randomUUID();
4515
4550
  }
4516
4551
  await state.fileStore.saveMapping(mapping);
4517
4552
  state.mappingsCache.set(mapping.uuid, mapping);
@@ -2810,6 +2810,7 @@ __export(spreadsheetRoutes_exports, {
2810
2810
  default: () => spreadsheetRoutes_default,
2811
2811
  scanControlSets: () => scanControlSets
2812
2812
  });
2813
+ import crypto from "crypto";
2813
2814
  import { parse as parseCSVSync } from "csv-parse/sync";
2814
2815
  import ExcelJS from "exceljs";
2815
2816
  import express from "express";
@@ -3128,6 +3129,15 @@ var init_spreadsheetRoutes = __esm({
3128
3129
  controlSetName = "Imported Control Set",
3129
3130
  controlSetDescription = "Imported from spreadsheet"
3130
3131
  } = req.body;
3132
+ let justificationFields = [];
3133
+ if (req.body.justificationFields) {
3134
+ try {
3135
+ justificationFields = JSON.parse(req.body.justificationFields);
3136
+ debug("Justification fields received:", justificationFields);
3137
+ } catch (e) {
3138
+ console.error("Failed to parse justification fields:", e);
3139
+ }
3140
+ }
3131
3141
  debug("Import parameters received:", {
3132
3142
  controlIdField,
3133
3143
  startRow,
@@ -3375,11 +3385,17 @@ var init_spreadsheetRoutes = __esm({
3375
3385
  };
3376
3386
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3377
3387
  const controlsDir = join4(baseDir, "controls");
3388
+ const mappingsDir = join4(baseDir, "mappings");
3389
+ const justificationFieldNames = justificationFields;
3378
3390
  families.forEach((familyControls, family) => {
3379
3391
  const familyDir = join4(controlsDir, family);
3392
+ const familyMappingsDir = join4(mappingsDir, family);
3380
3393
  if (!existsSync3(familyDir)) {
3381
3394
  mkdirSync2(familyDir, { recursive: true });
3382
3395
  }
3396
+ if (!existsSync3(familyMappingsDir)) {
3397
+ mkdirSync2(familyMappingsDir, { recursive: true });
3398
+ }
3383
3399
  familyControls.forEach((control) => {
3384
3400
  const controlId = control[controlIdFieldNameClean];
3385
3401
  if (!controlId) {
@@ -3389,12 +3405,24 @@ var init_spreadsheetRoutes = __esm({
3389
3405
  const controlIdStr = String(controlId).slice(0, 50);
3390
3406
  const fileName2 = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}.yaml`;
3391
3407
  const filePath = join4(familyDir, fileName2);
3408
+ const mappingFileName = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}-mappings.yaml`;
3409
+ const mappingFilePath = join4(familyMappingsDir, mappingFileName);
3392
3410
  const filteredControl = {};
3411
+ const mappingData = {
3412
+ control_id: controlIdStr,
3413
+ justification: "",
3414
+ uuid: crypto.randomUUID()
3415
+ };
3416
+ const justificationContents = [];
3393
3417
  if (control.family !== void 0) {
3394
3418
  filteredControl.family = control.family;
3395
3419
  }
3396
3420
  Object.keys(control).forEach((fieldName) => {
3397
3421
  if (fieldName === "family") return;
3422
+ if (justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3423
+ justificationContents.push(control[fieldName]);
3424
+ return;
3425
+ }
3398
3426
  const isInFrontendSchema = frontendFieldSchema?.some((f) => f.fieldName === fieldName);
3399
3427
  const isInFieldsMetadata = fields.hasOwnProperty(fieldName);
3400
3428
  if (isInFrontendSchema || isInFieldsMetadata) {
@@ -3402,6 +3430,13 @@ var init_spreadsheetRoutes = __esm({
3402
3430
  }
3403
3431
  });
3404
3432
  writeFileSync2(filePath, yaml4.dump(filteredControl));
3433
+ if (justificationContents.length > 0) {
3434
+ mappingData.justification = justificationContents.join("\n\n");
3435
+ }
3436
+ if (mappingData.justification && mappingData.justification.trim() !== "") {
3437
+ const mappingArray = [mappingData];
3438
+ writeFileSync2(mappingFilePath, yaml4.dump(mappingArray));
3439
+ }
3405
3440
  });
3406
3441
  });
3407
3442
  res.json({
@@ -4510,8 +4545,8 @@ var WebSocketManager = class {
4510
4545
  if (payload && payload.control_id) {
4511
4546
  const mapping = payload;
4512
4547
  if (!mapping.uuid) {
4513
- const crypto = await import("crypto");
4514
- mapping.uuid = crypto.randomUUID();
4548
+ const crypto2 = await import("crypto");
4549
+ mapping.uuid = crypto2.randomUUID();
4515
4550
  }
4516
4551
  await state.fileStore.saveMapping(mapping);
4517
4552
  state.mappingsCache.set(mapping.uuid, mapping);
@@ -1,4 +1,5 @@
1
1
  // cli/server/spreadsheetRoutes.ts
2
+ import crypto from "crypto";
2
3
  import { parse as parseCSVSync } from "csv-parse/sync";
3
4
  import ExcelJS from "exceljs";
4
5
  import express from "express";
@@ -93,6 +94,15 @@ router.post("/import-spreadsheet", upload.single("file"), async (req, res) => {
93
94
  controlSetName = "Imported Control Set",
94
95
  controlSetDescription = "Imported from spreadsheet"
95
96
  } = req.body;
97
+ let justificationFields = [];
98
+ if (req.body.justificationFields) {
99
+ try {
100
+ justificationFields = JSON.parse(req.body.justificationFields);
101
+ debug("Justification fields received:", justificationFields);
102
+ } catch (e) {
103
+ console.error("Failed to parse justification fields:", e);
104
+ }
105
+ }
96
106
  debug("Import parameters received:", {
97
107
  controlIdField,
98
108
  startRow,
@@ -340,11 +350,17 @@ router.post("/import-spreadsheet", upload.single("file"), async (req, res) => {
340
350
  };
341
351
  writeFileSync(join(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
342
352
  const controlsDir = join(baseDir, "controls");
353
+ const mappingsDir = join(baseDir, "mappings");
354
+ const justificationFieldNames = justificationFields;
343
355
  families.forEach((familyControls, family) => {
344
356
  const familyDir = join(controlsDir, family);
357
+ const familyMappingsDir = join(mappingsDir, family);
345
358
  if (!existsSync(familyDir)) {
346
359
  mkdirSync(familyDir, { recursive: true });
347
360
  }
361
+ if (!existsSync(familyMappingsDir)) {
362
+ mkdirSync(familyMappingsDir, { recursive: true });
363
+ }
348
364
  familyControls.forEach((control) => {
349
365
  const controlId = control[controlIdFieldNameClean];
350
366
  if (!controlId) {
@@ -354,12 +370,24 @@ router.post("/import-spreadsheet", upload.single("file"), async (req, res) => {
354
370
  const controlIdStr = String(controlId).slice(0, 50);
355
371
  const fileName2 = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}.yaml`;
356
372
  const filePath = join(familyDir, fileName2);
373
+ const mappingFileName = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}-mappings.yaml`;
374
+ const mappingFilePath = join(familyMappingsDir, mappingFileName);
357
375
  const filteredControl = {};
376
+ const mappingData = {
377
+ control_id: controlIdStr,
378
+ justification: "",
379
+ uuid: crypto.randomUUID()
380
+ };
381
+ const justificationContents = [];
358
382
  if (control.family !== void 0) {
359
383
  filteredControl.family = control.family;
360
384
  }
361
385
  Object.keys(control).forEach((fieldName) => {
362
386
  if (fieldName === "family") return;
387
+ if (justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
388
+ justificationContents.push(control[fieldName]);
389
+ return;
390
+ }
363
391
  const isInFrontendSchema = frontendFieldSchema?.some((f) => f.fieldName === fieldName);
364
392
  const isInFieldsMetadata = fields.hasOwnProperty(fieldName);
365
393
  if (isInFrontendSchema || isInFieldsMetadata) {
@@ -367,6 +395,13 @@ router.post("/import-spreadsheet", upload.single("file"), async (req, res) => {
367
395
  }
368
396
  });
369
397
  writeFileSync(filePath, yaml4.dump(filteredControl));
398
+ if (justificationContents.length > 0) {
399
+ mappingData.justification = justificationContents.join("\n\n");
400
+ }
401
+ if (mappingData.justification && mappingData.justification.trim() !== "") {
402
+ const mappingArray = [mappingData];
403
+ writeFileSync(mappingFilePath, yaml4.dump(mappingArray));
404
+ }
370
405
  });
371
406
  });
372
407
  res.json({
@@ -1257,6 +1257,7 @@ __export(spreadsheetRoutes_exports, {
1257
1257
  default: () => spreadsheetRoutes_default,
1258
1258
  scanControlSets: () => scanControlSets
1259
1259
  });
1260
+ import crypto from "crypto";
1260
1261
  import { parse as parseCSVSync } from "csv-parse/sync";
1261
1262
  import ExcelJS from "exceljs";
1262
1263
  import express from "express";
@@ -1575,6 +1576,15 @@ var init_spreadsheetRoutes = __esm({
1575
1576
  controlSetName = "Imported Control Set",
1576
1577
  controlSetDescription = "Imported from spreadsheet"
1577
1578
  } = req.body;
1579
+ let justificationFields = [];
1580
+ if (req.body.justificationFields) {
1581
+ try {
1582
+ justificationFields = JSON.parse(req.body.justificationFields);
1583
+ debug("Justification fields received:", justificationFields);
1584
+ } catch (e) {
1585
+ console.error("Failed to parse justification fields:", e);
1586
+ }
1587
+ }
1578
1588
  debug("Import parameters received:", {
1579
1589
  controlIdField,
1580
1590
  startRow,
@@ -1822,11 +1832,17 @@ var init_spreadsheetRoutes = __esm({
1822
1832
  };
1823
1833
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
1824
1834
  const controlsDir = join4(baseDir, "controls");
1835
+ const mappingsDir = join4(baseDir, "mappings");
1836
+ const justificationFieldNames = justificationFields;
1825
1837
  families.forEach((familyControls, family) => {
1826
1838
  const familyDir = join4(controlsDir, family);
1839
+ const familyMappingsDir = join4(mappingsDir, family);
1827
1840
  if (!existsSync3(familyDir)) {
1828
1841
  mkdirSync2(familyDir, { recursive: true });
1829
1842
  }
1843
+ if (!existsSync3(familyMappingsDir)) {
1844
+ mkdirSync2(familyMappingsDir, { recursive: true });
1845
+ }
1830
1846
  familyControls.forEach((control) => {
1831
1847
  const controlId = control[controlIdFieldNameClean];
1832
1848
  if (!controlId) {
@@ -1836,12 +1852,24 @@ var init_spreadsheetRoutes = __esm({
1836
1852
  const controlIdStr = String(controlId).slice(0, 50);
1837
1853
  const fileName2 = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}.yaml`;
1838
1854
  const filePath = join4(familyDir, fileName2);
1855
+ const mappingFileName = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}-mappings.yaml`;
1856
+ const mappingFilePath = join4(familyMappingsDir, mappingFileName);
1839
1857
  const filteredControl = {};
1858
+ const mappingData = {
1859
+ control_id: controlIdStr,
1860
+ justification: "",
1861
+ uuid: crypto.randomUUID()
1862
+ };
1863
+ const justificationContents = [];
1840
1864
  if (control.family !== void 0) {
1841
1865
  filteredControl.family = control.family;
1842
1866
  }
1843
1867
  Object.keys(control).forEach((fieldName) => {
1844
1868
  if (fieldName === "family") return;
1869
+ if (justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
1870
+ justificationContents.push(control[fieldName]);
1871
+ return;
1872
+ }
1845
1873
  const isInFrontendSchema = frontendFieldSchema?.some((f) => f.fieldName === fieldName);
1846
1874
  const isInFieldsMetadata = fields.hasOwnProperty(fieldName);
1847
1875
  if (isInFrontendSchema || isInFieldsMetadata) {
@@ -1849,6 +1877,13 @@ var init_spreadsheetRoutes = __esm({
1849
1877
  }
1850
1878
  });
1851
1879
  writeFileSync2(filePath, yaml4.dump(filteredControl));
1880
+ if (justificationContents.length > 0) {
1881
+ mappingData.justification = justificationContents.join("\n\n");
1882
+ }
1883
+ if (mappingData.justification && mappingData.justification.trim() !== "") {
1884
+ const mappingArray = [mappingData];
1885
+ writeFileSync2(mappingFilePath, yaml4.dump(mappingArray));
1886
+ }
1852
1887
  });
1853
1888
  });
1854
1889
  res.json({
@@ -2090,8 +2125,8 @@ var WebSocketManager = class {
2090
2125
  if (payload && payload.control_id) {
2091
2126
  const mapping = payload;
2092
2127
  if (!mapping.uuid) {
2093
- const crypto = await import("crypto");
2094
- mapping.uuid = crypto.randomUUID();
2128
+ const crypto2 = await import("crypto");
2129
+ mapping.uuid = crypto2.randomUUID();
2095
2130
  }
2096
2131
  await state.fileStore.saveMapping(mapping);
2097
2132
  state.mappingsCache.set(mapping.uuid, mapping);
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.DuJH3lYs.js">
10
- <link rel="modulepreload" href="/_app/immutable/chunks/RAfQ0xU4.js">
9
+ <link rel="modulepreload" href="/_app/immutable/entry/start.8aoHS084.js">
10
+ <link rel="modulepreload" href="/_app/immutable/chunks/ByYV7ZA-.js">
11
11
  <link rel="modulepreload" href="/_app/immutable/chunks/Cby0Z7eP.js">
12
- <link rel="modulepreload" href="/_app/immutable/entry/app.CcHyV-rk.js">
12
+ <link rel="modulepreload" href="/_app/immutable/entry/app.fwEu_ZMM.js">
13
13
  <link rel="modulepreload" href="/_app/immutable/chunks/DsnmJJEf.js">
14
14
  <link rel="modulepreload" href="/_app/immutable/chunks/DqsOU3kV.js">
15
15
  <link rel="modulepreload" href="/_app/immutable/chunks/CoF2vljD.js">
@@ -19,15 +19,15 @@
19
19
  <div style="display: contents">
20
20
  <script>
21
21
  {
22
- __sveltekit_iojf5q = {
22
+ __sveltekit_fjsoc9 = {
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.DuJH3lYs.js"),
30
- import("/_app/immutable/entry/app.CcHyV-rk.js")
29
+ import("/_app/immutable/entry/start.8aoHS084.js"),
30
+ import("/_app/immutable/entry/app.fwEu_ZMM.js")
31
31
  ]).then(([kit, app]) => {
32
32
  kit.start(app, element);
33
33
  });
package/dist/index.js CHANGED
@@ -2829,6 +2829,7 @@ __export(spreadsheetRoutes_exports, {
2829
2829
  default: () => spreadsheetRoutes_default,
2830
2830
  scanControlSets: () => scanControlSets
2831
2831
  });
2832
+ import crypto from "crypto";
2832
2833
  import { parse as parseCSVSync } from "csv-parse/sync";
2833
2834
  import ExcelJS from "exceljs";
2834
2835
  import express from "express";
@@ -3147,6 +3148,15 @@ var init_spreadsheetRoutes = __esm({
3147
3148
  controlSetName = "Imported Control Set",
3148
3149
  controlSetDescription = "Imported from spreadsheet"
3149
3150
  } = req.body;
3151
+ let justificationFields = [];
3152
+ if (req.body.justificationFields) {
3153
+ try {
3154
+ justificationFields = JSON.parse(req.body.justificationFields);
3155
+ debug("Justification fields received:", justificationFields);
3156
+ } catch (e) {
3157
+ console.error("Failed to parse justification fields:", e);
3158
+ }
3159
+ }
3150
3160
  debug("Import parameters received:", {
3151
3161
  controlIdField,
3152
3162
  startRow,
@@ -3394,11 +3404,17 @@ var init_spreadsheetRoutes = __esm({
3394
3404
  };
3395
3405
  writeFileSync2(join4(baseDir, "lula.yaml"), yaml4.dump(controlSetData));
3396
3406
  const controlsDir = join4(baseDir, "controls");
3407
+ const mappingsDir = join4(baseDir, "mappings");
3408
+ const justificationFieldNames = justificationFields;
3397
3409
  families.forEach((familyControls, family) => {
3398
3410
  const familyDir = join4(controlsDir, family);
3411
+ const familyMappingsDir = join4(mappingsDir, family);
3399
3412
  if (!existsSync3(familyDir)) {
3400
3413
  mkdirSync2(familyDir, { recursive: true });
3401
3414
  }
3415
+ if (!existsSync3(familyMappingsDir)) {
3416
+ mkdirSync2(familyMappingsDir, { recursive: true });
3417
+ }
3402
3418
  familyControls.forEach((control) => {
3403
3419
  const controlId = control[controlIdFieldNameClean];
3404
3420
  if (!controlId) {
@@ -3408,12 +3424,24 @@ var init_spreadsheetRoutes = __esm({
3408
3424
  const controlIdStr = String(controlId).slice(0, 50);
3409
3425
  const fileName2 = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}.yaml`;
3410
3426
  const filePath = join4(familyDir, fileName2);
3427
+ const mappingFileName = `${controlIdStr.replace(/[^a-zA-Z0-9-]/g, "_")}-mappings.yaml`;
3428
+ const mappingFilePath = join4(familyMappingsDir, mappingFileName);
3411
3429
  const filteredControl = {};
3430
+ const mappingData = {
3431
+ control_id: controlIdStr,
3432
+ justification: "",
3433
+ uuid: crypto.randomUUID()
3434
+ };
3435
+ const justificationContents = [];
3412
3436
  if (control.family !== void 0) {
3413
3437
  filteredControl.family = control.family;
3414
3438
  }
3415
3439
  Object.keys(control).forEach((fieldName) => {
3416
3440
  if (fieldName === "family") return;
3441
+ if (justificationFields.includes(fieldName) && control[fieldName] !== void 0 && control[fieldName] !== null) {
3442
+ justificationContents.push(control[fieldName]);
3443
+ return;
3444
+ }
3417
3445
  const isInFrontendSchema = frontendFieldSchema?.some((f) => f.fieldName === fieldName);
3418
3446
  const isInFieldsMetadata = fields.hasOwnProperty(fieldName);
3419
3447
  if (isInFrontendSchema || isInFieldsMetadata) {
@@ -3421,6 +3449,13 @@ var init_spreadsheetRoutes = __esm({
3421
3449
  }
3422
3450
  });
3423
3451
  writeFileSync2(filePath, yaml4.dump(filteredControl));
3452
+ if (justificationContents.length > 0) {
3453
+ mappingData.justification = justificationContents.join("\n\n");
3454
+ }
3455
+ if (mappingData.justification && mappingData.justification.trim() !== "") {
3456
+ const mappingArray = [mappingData];
3457
+ writeFileSync2(mappingFilePath, yaml4.dump(mappingArray));
3458
+ }
3424
3459
  });
3425
3460
  });
3426
3461
  res.json({
@@ -4537,8 +4572,8 @@ var WebSocketManager = class {
4537
4572
  if (payload && payload.control_id) {
4538
4573
  const mapping = payload;
4539
4574
  if (!mapping.uuid) {
4540
- const crypto = await import("crypto");
4541
- mapping.uuid = crypto.randomUUID();
4575
+ const crypto2 = await import("crypto");
4576
+ mapping.uuid = crypto2.randomUUID();
4542
4577
  }
4543
4578
  await state.fileStore.saveMapping(mapping);
4544
4579
  state.mappingsCache.set(mapping.uuid, mapping);
@@ -5313,6 +5348,7 @@ function crawlCommand() {
5313
5348
  return new Command().command("crawl").description("Detect compliance-related changes between @lulaStart and @lulaEnd in PR files").addOption(
5314
5349
  new Option("--post-mode <mode>", "How to post findings").choices(["review", "comment"]).default("review")
5315
5350
  ).action(async (opts) => {
5351
+ let leavePost = false;
5316
5352
  const { owner, repo, pull_number } = getPRContext();
5317
5353
  console.log(`Analyzing PR #${pull_number} in ${owner}/${repo} for compliance changes...`);
5318
5354
  const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
@@ -5338,6 +5374,7 @@ Lula reviewed ${files.length} files changed that affect compliance.
5338
5374
  ]);
5339
5375
  const changedBlocks = getChangedBlocks(oldText, newText);
5340
5376
  for (const block of changedBlocks) {
5377
+ leavePost = true;
5341
5378
  commentBody += `
5342
5379
 
5343
5380
  ---
@@ -5356,7 +5393,7 @@ Lula reviewed ${files.length} files changed that affect compliance.
5356
5393
  console.error(`Error processing ${file.filename}: ${err}`);
5357
5394
  }
5358
5395
  }
5359
- if (files.length > 0) {
5396
+ if (leavePost) {
5360
5397
  await postFinding({
5361
5398
  octokit,
5362
5399
  postMode: opts.postMode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lula2",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "A tool for managing compliance as code in your GitHub repositories.",
5
5
  "bin": {
6
6
  "lula2": "./dist/lula2"