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.
- package/README.md +34 -0
- package/dist/_app/immutable/chunks/B9HtV8_1.js +3 -0
- package/dist/_app/immutable/chunks/{CDqxzo-U.js → CxWn_Y-j.js} +1 -1
- package/dist/_app/immutable/chunks/{CrzX_dHQ.js → Dx7QZ9kE.js} +1 -1
- package/dist/_app/immutable/entry/{app.BU_7sxPU.js → app.DI-AYM-e.js} +2 -2
- package/dist/_app/immutable/entry/start.Qohy1rto.js +1 -0
- package/dist/_app/immutable/nodes/{0.XCpjvc2-.js → 0.c6B21yPf.js} +1 -1
- package/dist/_app/immutable/nodes/{1.C1uVrnSL.js → 1.BfZiPaZF.js} +1 -1
- package/dist/_app/immutable/nodes/{2.DsoxAUFC.js → 2.CSWUUbIh.js} +1 -1
- package/dist/_app/immutable/nodes/{3.C5xOlBQo.js → 3.hCMigK9L.js} +1 -1
- package/dist/_app/immutable/nodes/4.DQ3ddGz3.js +11 -0
- package/dist/_app/version.json +1 -1
- package/dist/cli/commands/crawl.js +151 -90
- package/dist/cli/commands/ui.js +92 -11
- package/dist/cli/server/index.js +92 -11
- package/dist/cli/server/server.js +92 -11
- package/dist/cli/server/serverState.js +35 -2
- package/dist/cli/server/spreadsheetRoutes.js +56 -8
- package/dist/cli/server/websocketServer.js +92 -11
- package/dist/index.html +6 -6
- package/dist/index.js +237 -102
- package/package.json +21 -22
- package/src/lib/components/setup/SpreadsheetImport.svelte +40 -15
- package/dist/_app/immutable/chunks/DtmRvgOL.js +0 -3
- package/dist/_app/immutable/entry/start.DtOV9FzL.js +0 -1
- package/dist/_app/immutable/nodes/4.CHRd9Q51.js +0 -11
package/dist/cli/commands/ui.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
|
3848
|
+
const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
|
|
3801
3849
|
row: index + 1,
|
|
3802
|
-
preview: row.slice(0,
|
|
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
|
});
|
package/dist/cli/server/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
|
3830
|
+
const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
|
|
3783
3831
|
row: index + 1,
|
|
3784
|
-
preview: row.slice(0,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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,
|
|
3830
|
+
const headerCandidates = rows.slice(0, MAX_HEADER_CANDIDATES).map((row, index) => ({
|
|
3783
3831
|
row: index + 1,
|
|
3784
|
-
preview: row.slice(0,
|
|
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
|
});
|