lula2 0.0.5 → 0.0.6

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.
Files changed (108) hide show
  1. package/README.md +291 -8
  2. package/dist/_app/env.js +1 -0
  3. package/dist/_app/immutable/assets/0.DtiRW3lO.css +1 -0
  4. package/dist/_app/immutable/assets/DynamicControlEditor.BkVTzFZ-.css +1 -0
  5. package/dist/_app/immutable/chunks/7x_q-1ab.js +1 -0
  6. package/dist/_app/immutable/chunks/B19gt6-g.js +2 -0
  7. package/dist/_app/immutable/chunks/BR-0Dorr.js +1 -0
  8. package/dist/_app/immutable/chunks/B_3ksxz5.js +2 -0
  9. package/dist/_app/immutable/chunks/Bg_R1qWi.js +3 -0
  10. package/dist/_app/immutable/chunks/D3aNP_lg.js +1 -0
  11. package/dist/_app/immutable/chunks/D4Q_ObIy.js +1 -0
  12. package/dist/_app/immutable/chunks/DsnmJJEf.js +1 -0
  13. package/dist/_app/immutable/chunks/XY2j_owG.js +66 -0
  14. package/dist/_app/immutable/chunks/rzN25oDf.js +1 -0
  15. package/dist/_app/immutable/entry/app.r0uOd9qg.js +2 -0
  16. package/dist/_app/immutable/entry/start.DvoqR0rc.js +1 -0
  17. package/dist/_app/immutable/nodes/0.Ct6FAss_.js +1 -0
  18. package/dist/_app/immutable/nodes/1.DLoKuy8Q.js +1 -0
  19. package/dist/_app/immutable/nodes/2.IRkwSmiB.js +1 -0
  20. package/dist/_app/immutable/nodes/3.BrTg-ZHv.js +1 -0
  21. package/dist/_app/immutable/nodes/4.Blq-4WQS.js +9 -0
  22. package/dist/_app/version.json +1 -0
  23. package/dist/cli/commands/crawl.js +128 -0
  24. package/dist/cli/commands/ui.js +2769 -0
  25. package/dist/cli/commands/version.js +30 -0
  26. package/dist/cli/server/index.js +2713 -0
  27. package/dist/cli/server/server.js +2702 -0
  28. package/dist/cli/server/serverState.js +1199 -0
  29. package/dist/cli/server/spreadsheetRoutes.js +788 -0
  30. package/dist/cli/server/types.js +0 -0
  31. package/dist/cli/server/websocketServer.js +2625 -0
  32. package/dist/cli/utils/debug.js +24 -0
  33. package/dist/favicon.svg +1 -0
  34. package/dist/index.html +38 -0
  35. package/dist/index.js +2924 -37
  36. package/dist/lula.png +0 -0
  37. package/dist/lula2 +2 -0
  38. package/package.json +120 -72
  39. package/src/app.css +192 -0
  40. package/src/app.d.ts +13 -0
  41. package/src/app.html +13 -0
  42. package/src/lib/actions/fadeWhenScrollable.ts +39 -0
  43. package/src/lib/actions/modal.ts +230 -0
  44. package/src/lib/actions/tooltip.ts +82 -0
  45. package/src/lib/components/control-sets/ControlSetInfo.svelte +20 -0
  46. package/src/lib/components/control-sets/ControlSetSelector.svelte +46 -0
  47. package/src/lib/components/control-sets/index.ts +5 -0
  48. package/src/lib/components/controls/ControlDetailsPanel.svelte +235 -0
  49. package/src/lib/components/controls/ControlsList.svelte +608 -0
  50. package/src/lib/components/controls/DynamicControlEditor.svelte +298 -0
  51. package/src/lib/components/controls/MappingCard.svelte +105 -0
  52. package/src/lib/components/controls/MappingForm.svelte +188 -0
  53. package/src/lib/components/controls/index.ts +9 -0
  54. package/src/lib/components/controls/renderers/EditableFieldRenderer.svelte +103 -0
  55. package/src/lib/components/controls/renderers/FieldRenderer.svelte +49 -0
  56. package/src/lib/components/controls/renderers/index.ts +5 -0
  57. package/src/lib/components/controls/tabs/CustomFieldsTab.svelte +130 -0
  58. package/src/lib/components/controls/tabs/ImplementationTab.svelte +127 -0
  59. package/src/lib/components/controls/tabs/MappingsTab.svelte +182 -0
  60. package/src/lib/components/controls/tabs/OverviewTab.svelte +151 -0
  61. package/src/lib/components/controls/tabs/TimelineTab.svelte +41 -0
  62. package/src/lib/components/controls/tabs/index.ts +8 -0
  63. package/src/lib/components/controls/utils/ProcessedTextRenderer.svelte +63 -0
  64. package/src/lib/components/controls/utils/textProcessor.ts +164 -0
  65. package/src/lib/components/forms/DynamicControlForm.svelte +340 -0
  66. package/src/lib/components/forms/DynamicField.svelte +494 -0
  67. package/src/lib/components/forms/FormField.svelte +107 -0
  68. package/src/lib/components/forms/index.ts +6 -0
  69. package/src/lib/components/setup/ExistingControlSets.svelte +284 -0
  70. package/src/lib/components/setup/SpreadsheetImport.svelte +968 -0
  71. package/src/lib/components/setup/index.ts +5 -0
  72. package/src/lib/components/ui/Dropdown.svelte +107 -0
  73. package/src/lib/components/ui/EmptyState.svelte +80 -0
  74. package/src/lib/components/ui/FeatureToggle.svelte +50 -0
  75. package/src/lib/components/ui/SearchBar.svelte +73 -0
  76. package/src/lib/components/ui/StatusBadge.svelte +79 -0
  77. package/src/lib/components/ui/TabNavigation.svelte +48 -0
  78. package/src/lib/components/ui/Tooltip.svelte +120 -0
  79. package/src/lib/components/ui/index.ts +10 -0
  80. package/src/lib/components/version-control/DiffViewer.svelte +292 -0
  81. package/src/lib/components/version-control/TimelineItem.svelte +107 -0
  82. package/src/lib/components/version-control/YamlDiffViewer.svelte +428 -0
  83. package/src/lib/components/version-control/index.ts +6 -0
  84. package/src/lib/form-types.ts +57 -0
  85. package/src/lib/formatUtils.ts +17 -0
  86. package/src/lib/index.ts +5 -0
  87. package/src/lib/types.ts +180 -0
  88. package/src/lib/websocket.ts +359 -0
  89. package/src/routes/+layout.svelte +236 -0
  90. package/src/routes/+page.svelte +38 -0
  91. package/src/routes/control/[id]/+page.svelte +112 -0
  92. package/src/routes/setup/+page.svelte +241 -0
  93. package/src/stores/compliance.ts +95 -0
  94. package/src/styles/highlightjs.css +20 -0
  95. package/src/styles/modal.css +58 -0
  96. package/src/styles/tables.css +111 -0
  97. package/src/styles/tooltip.css +65 -0
  98. package/dist/controls/index.d.ts +0 -18
  99. package/dist/controls/index.d.ts.map +0 -1
  100. package/dist/controls/index.js +0 -18
  101. package/dist/crawl.d.ts +0 -62
  102. package/dist/crawl.d.ts.map +0 -1
  103. package/dist/crawl.js +0 -172
  104. package/dist/index.d.ts +0 -8
  105. package/dist/index.d.ts.map +0 -1
  106. package/src/controls/index.ts +0 -19
  107. package/src/crawl.ts +0 -227
  108. package/src/index.ts +0 -46
@@ -0,0 +1,151 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2023-Present The Lula Authors -->
3
+
4
+ <script lang="ts">
5
+ import type { Control, FieldSchema } from '$lib/types';
6
+ import { FieldRenderer } from '../renderers';
7
+
8
+ interface Props {
9
+ control: Control;
10
+ fieldSchema: Record<string, FieldSchema>;
11
+ }
12
+
13
+ let { control, fieldSchema }: Props = $props();
14
+
15
+ // Get fields for overview tab
16
+ function getOverviewFields(): Array<[string, FieldSchema]> {
17
+ return Object.entries(fieldSchema)
18
+ .filter(([fieldName, field]) => {
19
+ // Exclude id and family fields from overview since they're in the header
20
+ if (fieldName === 'id' || fieldName === 'family') {
21
+ return false;
22
+ }
23
+ // Map category to tab if tab not explicitly set
24
+ const fieldTab = field.tab || getDefaultTabForCategory(field.category);
25
+ return fieldTab === 'overview' && field.visible;
26
+ })
27
+ .sort((a, b) => a[1].display_order - b[1].display_order);
28
+ }
29
+
30
+ function getDefaultTabForCategory(category: string): 'overview' | 'implementation' | 'custom' {
31
+ switch (category) {
32
+ case 'core':
33
+ case 'metadata':
34
+ return 'overview';
35
+ case 'compliance':
36
+ case 'content':
37
+ return 'implementation';
38
+ default:
39
+ return 'custom';
40
+ }
41
+ }
42
+
43
+ // Helper to determine field layout class based on field type
44
+ function getFieldLayoutClass(field: FieldSchema): string {
45
+ // Textareas and long text fields get full width
46
+ if (field.ui_type === 'textarea' || field.ui_type === 'long_text') {
47
+ return 'col-span-full';
48
+ }
49
+ // Medium text fields also get full width
50
+ if (field.ui_type === 'medium_text' && field.max_length && field.max_length > 100) {
51
+ return 'col-span-full';
52
+ }
53
+ // Dropdowns and short fields can be side by side
54
+ if (
55
+ field.ui_type === 'select' ||
56
+ field.ui_type === 'boolean' ||
57
+ field.ui_type === 'date' ||
58
+ field.ui_type === 'number' ||
59
+ (field.ui_type === 'short_text' && field.max_length && field.max_length <= 50)
60
+ ) {
61
+ return 'col-span-1';
62
+ }
63
+ // Default to full width for everything else
64
+ return 'col-span-full';
65
+ }
66
+
67
+ // Helper to group fields by layout type
68
+ function groupFieldsForLayout(fields: Array<[string, FieldSchema]>) {
69
+ const groups: Array<Array<[string, FieldSchema]>> = [];
70
+ let currentGroup: Array<[string, FieldSchema]> = [];
71
+
72
+ for (const field of fields) {
73
+ const layoutClass = getFieldLayoutClass(field[1]);
74
+
75
+ if (layoutClass === 'col-span-full') {
76
+ // Full width fields go in their own group
77
+ if (currentGroup.length > 0) {
78
+ groups.push(currentGroup);
79
+ currentGroup = [];
80
+ }
81
+ groups.push([field]);
82
+ } else {
83
+ // Half width fields can be grouped
84
+ currentGroup.push(field);
85
+ if (currentGroup.length === 2) {
86
+ groups.push(currentGroup);
87
+ currentGroup = [];
88
+ }
89
+ }
90
+ }
91
+
92
+ // Add any remaining fields
93
+ if (currentGroup.length > 0) {
94
+ groups.push(currentGroup);
95
+ }
96
+
97
+ return groups;
98
+ }
99
+
100
+ const overviewFields = $derived(getOverviewFields());
101
+ const fieldGroups = $derived(groupFieldsForLayout(overviewFields));
102
+ </script>
103
+
104
+ <!-- Readonly Overview Tab with Clean Styling -->
105
+ <div class="space-y-8">
106
+ {#if overviewFields.length > 0}
107
+ <div class="space-y-4">
108
+ <div class="bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm hover:shadow-md transition-all duration-200 p-6">
109
+ <div class="space-y-8">
110
+ {#each fieldGroups as fieldGroup}
111
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
112
+ {#each fieldGroup as [fieldName, field]}
113
+ <div class={getFieldLayoutClass(field)}>
114
+ <FieldRenderer
115
+ {fieldName}
116
+ {field}
117
+ value={control[fieldName]}
118
+ readonly={true}
119
+ />
120
+ </div>
121
+ {/each}
122
+ </div>
123
+ {/each}
124
+ </div>
125
+ </div>
126
+ </div>
127
+ {:else}
128
+ <div class="text-center py-12">
129
+ <p class="text-gray-500 dark:text-gray-400">No overview fields available</p>
130
+ </div>
131
+ {/if}
132
+
133
+ <!-- Control Properties -->
134
+ {#if control.properties && Object.keys(control.properties).length > 0}
135
+ <div class="space-y-4">
136
+ <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Control Properties</h3>
137
+ <div class="bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm hover:shadow-md transition-all duration-200 p-6">
138
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
139
+ {#each Object.entries(control.properties) as [key, value]}
140
+ <FieldRenderer
141
+ fieldName={key.replace(/_/g, ' ')}
142
+ field={null}
143
+ value={typeof value === 'object' ? JSON.stringify(value, null, 2) : value}
144
+ readonly={true}
145
+ />
146
+ {/each}
147
+ </div>
148
+ </div>
149
+ </div>
150
+ {/if}
151
+ </div>
@@ -0,0 +1,41 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2023-Present The Lula Authors -->
3
+
4
+ <script lang="ts">
5
+ import { TimelineItem } from '$components/version-control';
6
+ import type { Control } from '$lib/types';
7
+ import { EmptyState } from '../../ui';
8
+
9
+ interface Props {
10
+ control: Control;
11
+ timeline?: any; // Timeline type from control.timeline
12
+ }
13
+
14
+ let { control, timeline }: Props = $props();
15
+
16
+ const commits = $derived(timeline?.commits || []);
17
+ </script>
18
+
19
+ <div>
20
+ {#if !timeline}
21
+ <EmptyState
22
+ title="No timeline data available"
23
+ description="Timeline information is not available for this control."
24
+ size="lg"
25
+ />
26
+ {:else if commits.length > 0}
27
+ <div class="mb-4">
28
+ <div class="space-y-6">
29
+ {#each commits as commit, index}
30
+ <TimelineItem {commit} showConnector={index < commits.length - 1} />
31
+ {/each}
32
+ </div>
33
+ </div>
34
+ {:else}
35
+ <EmptyState
36
+ title="No activity history found"
37
+ description="This control and its mapping files are new and haven't been committed to git yet."
38
+ size="lg"
39
+ />
40
+ {/if}
41
+ </div>
@@ -0,0 +1,8 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+
4
+ export { default as CustomFieldsTab } from './CustomFieldsTab.svelte';
5
+ export { default as ImplementationTab } from './ImplementationTab.svelte';
6
+ export { default as MappingsTab } from './MappingsTab.svelte';
7
+ export { default as OverviewTab } from './OverviewTab.svelte';
8
+ export { default as TimelineTab } from './TimelineTab.svelte';
@@ -0,0 +1,63 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2023-Present The Lula Authors -->
3
+
4
+ <script lang="ts">
5
+ import { processMultilineText } from './textProcessor';
6
+
7
+ interface Props {
8
+ text: string;
9
+ }
10
+
11
+ let { text }: Props = $props();
12
+
13
+ const sections = $derived(processMultilineText(text));
14
+ </script>
15
+
16
+ <div class="space-y-3">
17
+ {#each sections as section}
18
+ {#if section.type === 'header'}
19
+ <h4 class="font-semibold text-gray-900 dark:text-gray-100 mt-4 first:mt-0">
20
+ {section.content}
21
+ </h4>
22
+ {:else if section.type === 'table' && section.data?.rows}
23
+ <div class="overflow-x-auto">
24
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
25
+ <tbody class="divide-y divide-gray-200 dark:divide-gray-700">
26
+ {#each section.data.rows as row, i}
27
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
28
+ {#each row.columns as column, j}
29
+ <td class="px-3 py-2 text-sm {j === 0 ? 'font-medium text-gray-900 dark:text-gray-100' : 'text-gray-600 dark:text-gray-400'}">
30
+ {#if column.startsWith('CCI-')}
31
+ <code class="px-1.5 py-0.5 text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded">
32
+ {column}
33
+ </code>
34
+ {:else if /^[A-Z]{2}-\d+/.test(column)}
35
+ <code class="px-1.5 py-0.5 text-xs bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 rounded">
36
+ {column}
37
+ </code>
38
+ {:else}
39
+ {column}
40
+ {/if}
41
+ </td>
42
+ {/each}
43
+ </tr>
44
+ {/each}
45
+ </tbody>
46
+ </table>
47
+ </div>
48
+ {:else if section.type === 'list' && section.data?.items}
49
+ <ul class="space-y-1 ml-4">
50
+ {#each section.data.items as item}
51
+ <li class="flex items-start">
52
+ <span class="text-gray-400 dark:text-gray-500 mr-2 flex-shrink-0">•</span>
53
+ <span class="text-gray-600 dark:text-gray-400 text-sm">{item}</span>
54
+ </li>
55
+ {/each}
56
+ </ul>
57
+ {:else}
58
+ <p class="text-gray-600 dark:text-gray-400 text-sm whitespace-pre-wrap">
59
+ {section.content}
60
+ </p>
61
+ {/if}
62
+ {/each}
63
+ </div>
@@ -0,0 +1,164 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+
4
+ export interface ProcessedSection {
5
+ type: 'header' | 'paragraph' | 'table' | 'list';
6
+ content: string;
7
+ data?: any;
8
+ }
9
+
10
+ export interface TableRow {
11
+ columns: string[];
12
+ }
13
+
14
+ export function processMultilineText(text: string): ProcessedSection[] {
15
+ if (!text) return [];
16
+
17
+ // First check if the entire content is a table
18
+ if (isEntireContentTable(text)) {
19
+ return processAsTable(text);
20
+ }
21
+
22
+ const lines = text.split('\n');
23
+ const sections: ProcessedSection[] = [];
24
+ let currentSection: string[] = [];
25
+ let currentType: 'paragraph' | 'list' = 'paragraph';
26
+
27
+ for (let i = 0; i < lines.length; i++) {
28
+ const line = lines[i];
29
+ const trimmedLine = line.trim();
30
+
31
+ // Check for empty lines
32
+ if (!trimmedLine) {
33
+ // Process accumulated section if any
34
+ if (currentSection.length > 0) {
35
+ flushSection();
36
+ }
37
+ continue;
38
+ }
39
+
40
+ // Check for headers (lines ending with colon and followed by content)
41
+ if (trimmedLine.endsWith(':') && i < lines.length - 1) {
42
+ const nextLine = lines[i + 1]?.trim();
43
+ // Only treat as header if next line has content or is followed by content
44
+ if (nextLine || (i + 2 < lines.length && lines[i + 2]?.trim())) {
45
+ // Flush any accumulated section
46
+ if (currentSection.length > 0) {
47
+ flushSection();
48
+ }
49
+
50
+ // Add header
51
+ sections.push({
52
+ type: 'header',
53
+ content: trimmedLine.slice(0, -1) // Remove the colon
54
+ });
55
+ continue;
56
+ }
57
+ }
58
+
59
+ // Check for list items
60
+ if (isListItem(trimmedLine)) {
61
+ if (currentType !== 'list') {
62
+ // Flush previous section if not list
63
+ if (currentSection.length > 0) {
64
+ flushSection();
65
+ }
66
+ currentType = 'list';
67
+ }
68
+ currentSection.push(trimmedLine);
69
+ continue;
70
+ }
71
+
72
+ // Default to paragraph
73
+ if (currentType === 'list') {
74
+ // Flush previous section if switching from list to paragraph
75
+ if (currentSection.length > 0) {
76
+ flushSection();
77
+ }
78
+ currentType = 'paragraph';
79
+ }
80
+
81
+ currentSection.push(line); // Preserve original line formatting for paragraphs
82
+ }
83
+
84
+ // Flush any remaining section
85
+ if (currentSection.length > 0) {
86
+ flushSection();
87
+ }
88
+
89
+ function flushSection() {
90
+ if (currentType === 'list') {
91
+ const items = currentSection.map(line => {
92
+ // Remove common list prefixes
93
+ return line.replace(/^[-*•]\s*/, '')
94
+ .replace(/^\d+\.\s*/, '')
95
+ .replace(/^[a-zA-Z]\.\s*/, '');
96
+ });
97
+
98
+ sections.push({
99
+ type: 'list',
100
+ content: currentSection.join('\n'),
101
+ data: { items }
102
+ });
103
+ } else {
104
+ sections.push({
105
+ type: 'paragraph',
106
+ content: currentSection.join('\n').trim()
107
+ });
108
+ }
109
+
110
+ currentSection = [];
111
+ }
112
+
113
+ return sections;
114
+ }
115
+
116
+ function isEntireContentTable(text: string): boolean {
117
+ const lines = text.split('\n').filter(line => line.trim());
118
+ if (lines.length === 0) return false;
119
+
120
+ // Check if ALL non-empty lines are table rows
121
+ return lines.every(line => isTableRow(line.trim()));
122
+ }
123
+
124
+ function processAsTable(text: string): ProcessedSection[] {
125
+ const lines = text.split('\n').filter(line => line.trim());
126
+ const rows: TableRow[] = lines.map(line => ({
127
+ columns: line.split(',').map(col => col.trim())
128
+ }));
129
+
130
+ return [{
131
+ type: 'table',
132
+ content: text,
133
+ data: { rows }
134
+ }];
135
+ }
136
+
137
+ function isTableRow(line: string): boolean {
138
+ // Check if line contains comma-separated values
139
+ // Must have at least 2 commas and consistent structure
140
+ const parts = line.split(',');
141
+ if (parts.length < 2) return false;
142
+
143
+ // Common patterns for table data:
144
+ // - Control IDs (AC-10.1)
145
+ // - CCI codes (CCI-000054)
146
+ // - Short descriptive text
147
+ const hasControlPattern = /^[A-Z]{2}-\d+(\.\d+)?/.test(parts[0].trim());
148
+ const hasCCIPattern = parts.some(part => /^CCI-\d+/.test(part.trim()));
149
+
150
+ // If it looks like structured data with consistent separators
151
+ return hasControlPattern || hasCCIPattern ||
152
+ (parts.length >= 2 && parts.every(p => p.trim().length > 0 && p.trim().length < 100));
153
+ }
154
+
155
+ function isListItem(line: string): boolean {
156
+ // Check for common list patterns
157
+ return /^[-*•]\s+/.test(line) || // Bullet points
158
+ /^\d+\.\s+/.test(line) || // Numbered lists
159
+ /^[a-zA-Z]\.\s+/.test(line) || // Letter lists
160
+ /^\[SELECT FROM:/.test(line) || // Special NIST format
161
+ /^Examine:/.test(line) ||
162
+ /^Interview:/.test(line) ||
163
+ /^Test:/.test(line);
164
+ }