create-interview-cockpit 0.30.0 → 0.31.0
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/package.json +1 -1
- package/template/client/src/components/GithubActionsLabModal.tsx +253 -163
- package/template/client/src/components/LabsPanel.tsx +7 -0
- package/template/client/src/githubActionsLab.ts +14 -0
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
package/package.json
CHANGED
|
@@ -236,18 +236,105 @@ function mapJobStatusToCheck(
|
|
|
236
236
|
return s;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
interface FileTreeFileNode {
|
|
240
|
+
type: "file";
|
|
241
|
+
name: string;
|
|
242
|
+
path: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
interface FileTreeFolderNode {
|
|
246
|
+
type: "folder";
|
|
247
|
+
displayName: string;
|
|
248
|
+
path: string;
|
|
249
|
+
children: FileTreeNode[];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
type FileTreeNode = FileTreeFileNode | FileTreeFolderNode;
|
|
253
|
+
|
|
254
|
+
interface MutableFileTreeFolder {
|
|
255
|
+
name: string;
|
|
256
|
+
path: string;
|
|
257
|
+
files: FileTreeFileNode[];
|
|
258
|
+
folders: Map<string, MutableFileTreeFolder>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function sortFileTreeNodes<T extends { name?: string; displayName?: string }>(
|
|
262
|
+
a: T,
|
|
263
|
+
b: T,
|
|
264
|
+
) {
|
|
265
|
+
return (a.displayName ?? a.name ?? "").localeCompare(
|
|
266
|
+
b.displayName ?? b.name ?? "",
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function getMutableFolderChildren(
|
|
271
|
+
folder: MutableFileTreeFolder,
|
|
272
|
+
): FileTreeNode[] {
|
|
273
|
+
const files = [...folder.files].sort(sortFileTreeNodes);
|
|
274
|
+
const folders = Array.from(folder.folders.values())
|
|
275
|
+
.map(compactFileTreeFolder)
|
|
276
|
+
.sort(sortFileTreeNodes);
|
|
277
|
+
return [...files, ...folders];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function compactFileTreeFolder(
|
|
281
|
+
folder: MutableFileTreeFolder,
|
|
282
|
+
): FileTreeFolderNode {
|
|
283
|
+
const names = [folder.name];
|
|
284
|
+
let current = folder;
|
|
285
|
+
|
|
286
|
+
while (current.files.length === 0 && current.folders.size === 1) {
|
|
287
|
+
const next = Array.from(current.folders.values())[0];
|
|
288
|
+
names.push(next.name);
|
|
289
|
+
current = next;
|
|
247
290
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
type: "folder",
|
|
294
|
+
displayName: names.join("/"),
|
|
295
|
+
path: current.path,
|
|
296
|
+
children: getMutableFolderChildren(current),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function buildCompactFileTree(paths: string[]): FileTreeNode[] {
|
|
301
|
+
const root: MutableFileTreeFolder = {
|
|
302
|
+
name: "",
|
|
303
|
+
path: "",
|
|
304
|
+
files: [],
|
|
305
|
+
folders: new Map(),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
for (const filePath of paths) {
|
|
309
|
+
const parts = filePath.split("/").filter(Boolean);
|
|
310
|
+
if (parts.length === 0) continue;
|
|
311
|
+
|
|
312
|
+
let current = root;
|
|
313
|
+
let currentPath = "";
|
|
314
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
315
|
+
const name = parts[i];
|
|
316
|
+
currentPath = currentPath ? `${currentPath}/${name}` : name;
|
|
317
|
+
let next = current.folders.get(name);
|
|
318
|
+
if (!next) {
|
|
319
|
+
next = {
|
|
320
|
+
name,
|
|
321
|
+
path: currentPath,
|
|
322
|
+
files: [],
|
|
323
|
+
folders: new Map(),
|
|
324
|
+
};
|
|
325
|
+
current.folders.set(name, next);
|
|
326
|
+
}
|
|
327
|
+
current = next;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
current.files.push({
|
|
331
|
+
type: "file",
|
|
332
|
+
name: parts[parts.length - 1],
|
|
333
|
+
path: filePath,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return getMutableFolderChildren(root);
|
|
251
338
|
}
|
|
252
339
|
|
|
253
340
|
// ─── Component ───────────────────────────────────────────────────────────
|
|
@@ -514,7 +601,7 @@ export default function GithubActionsLabModal() {
|
|
|
514
601
|
|
|
515
602
|
// ── File operations ───────────────────────────────────────────────
|
|
516
603
|
const fileOrder = useMemo(() => getGhaLabFileOrder(workspace), [workspace]);
|
|
517
|
-
const
|
|
604
|
+
const fileTree = useMemo(() => buildCompactFileTree(fileOrder), [fileOrder]);
|
|
518
605
|
const [collapsedFolders, setCollapsedFolders] = useState<Set<string>>(
|
|
519
606
|
() => new Set(),
|
|
520
607
|
);
|
|
@@ -1706,10 +1793,161 @@ interface ImportMeta {
|
|
|
1706
1793
|
minHeight: MIN_H,
|
|
1707
1794
|
};
|
|
1708
1795
|
|
|
1796
|
+
const renderFileTreeNode = (node: FileTreeNode, depth: number) => {
|
|
1797
|
+
const paddingLeft = 6 + depth * 14;
|
|
1798
|
+
|
|
1799
|
+
if (node.type === "folder") {
|
|
1800
|
+
const collapsed = collapsedFolders.has(node.path);
|
|
1801
|
+
return (
|
|
1802
|
+
<div key={`folder:${node.path || node.displayName}`}>
|
|
1803
|
+
<button
|
|
1804
|
+
onClick={() => toggleFolder(node.path)}
|
|
1805
|
+
onDragOver={(e) => handleFolderDragOver(e, node.path)}
|
|
1806
|
+
onDragLeave={() =>
|
|
1807
|
+
setDragOverFolder((current) =>
|
|
1808
|
+
current === node.path ? null : current,
|
|
1809
|
+
)
|
|
1810
|
+
}
|
|
1811
|
+
onDrop={(e) => handleFolderDrop(e, node.path)}
|
|
1812
|
+
className="flex items-center gap-1 w-full pr-1 py-0.5 text-slate-400 hover:text-slate-200"
|
|
1813
|
+
style={{ paddingLeft }}
|
|
1814
|
+
title="Drop a file here to move it. Hold Option/Alt while dropping to copy."
|
|
1815
|
+
>
|
|
1816
|
+
{collapsed ? (
|
|
1817
|
+
<ChevronRight className="w-3 h-3 shrink-0" />
|
|
1818
|
+
) : (
|
|
1819
|
+
<ChevronDown className="w-3 h-3 shrink-0" />
|
|
1820
|
+
)}
|
|
1821
|
+
<Folder className="w-3 h-3 shrink-0" />
|
|
1822
|
+
<span
|
|
1823
|
+
className={`truncate rounded px-1 ${
|
|
1824
|
+
dragOverFolder === node.path
|
|
1825
|
+
? "bg-amber-500/15 text-amber-200"
|
|
1826
|
+
: ""
|
|
1827
|
+
}`}
|
|
1828
|
+
>
|
|
1829
|
+
{node.displayName}/
|
|
1830
|
+
</span>
|
|
1831
|
+
</button>
|
|
1832
|
+
{!collapsed &&
|
|
1833
|
+
node.children.map((child) => renderFileTreeNode(child, depth + 1))}
|
|
1834
|
+
</div>
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
const filePath = node.path;
|
|
1839
|
+
return (
|
|
1840
|
+
<div
|
|
1841
|
+
key={`file:${filePath}`}
|
|
1842
|
+
data-selected={selectedFiles.has(filePath)}
|
|
1843
|
+
draggable
|
|
1844
|
+
onDragStart={(e) => handleFileDragStart(e, filePath)}
|
|
1845
|
+
onDragEnd={() => {
|
|
1846
|
+
setDraggingFile(null);
|
|
1847
|
+
setDragOverFolder(null);
|
|
1848
|
+
}}
|
|
1849
|
+
className={`group relative flex items-center gap-1 pr-1 py-0.5 rounded cursor-pointer ${
|
|
1850
|
+
activeFile === filePath
|
|
1851
|
+
? "bg-amber-500/15 text-amber-200"
|
|
1852
|
+
: selectedFiles.has(filePath)
|
|
1853
|
+
? "bg-sky-500/10 text-sky-100 hover:bg-sky-500/15"
|
|
1854
|
+
: "text-slate-300 hover:bg-slate-800/40"
|
|
1855
|
+
}`}
|
|
1856
|
+
onClick={() => setActiveFile(filePath)}
|
|
1857
|
+
style={{ paddingLeft }}
|
|
1858
|
+
>
|
|
1859
|
+
{(selectMode || selectedFiles.has(filePath)) && (
|
|
1860
|
+
<input
|
|
1861
|
+
type="checkbox"
|
|
1862
|
+
checked={selectedFiles.has(filePath)}
|
|
1863
|
+
onClick={(e) => e.stopPropagation()}
|
|
1864
|
+
onChange={() => toggleFileSelection(filePath)}
|
|
1865
|
+
className="h-3 w-3 shrink-0 accent-amber-400"
|
|
1866
|
+
title="Select file"
|
|
1867
|
+
/>
|
|
1868
|
+
)}
|
|
1869
|
+
<span className="truncate flex-1">{node.name}</span>
|
|
1870
|
+
<button
|
|
1871
|
+
onClick={(e) => {
|
|
1872
|
+
e.stopPropagation();
|
|
1873
|
+
setOpenFileMenu((current) =>
|
|
1874
|
+
current === filePath ? null : filePath,
|
|
1875
|
+
);
|
|
1876
|
+
setBulkMenuOpen(false);
|
|
1877
|
+
}}
|
|
1878
|
+
className="rounded px-1 text-slate-500 opacity-70 hover:bg-slate-800/70 hover:text-amber-200 group-hover:opacity-100"
|
|
1879
|
+
title="File actions"
|
|
1880
|
+
>
|
|
1881
|
+
⋯
|
|
1882
|
+
</button>
|
|
1883
|
+
{openFileMenu === filePath && (
|
|
1884
|
+
<div
|
|
1885
|
+
onClick={(e) => e.stopPropagation()}
|
|
1886
|
+
className="absolute right-1 top-6 z-40 w-40 overflow-hidden rounded-lg border border-slate-700 bg-slate-950 py-1 text-[11px] shadow-xl"
|
|
1887
|
+
>
|
|
1888
|
+
<button
|
|
1889
|
+
onClick={() => moveFile(filePath)}
|
|
1890
|
+
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
1891
|
+
>
|
|
1892
|
+
<Pencil className="w-3 h-3 text-amber-300" />
|
|
1893
|
+
Move / rename…
|
|
1894
|
+
</button>
|
|
1895
|
+
<button
|
|
1896
|
+
onClick={() => copyFile(filePath)}
|
|
1897
|
+
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
1898
|
+
>
|
|
1899
|
+
<Copy className="w-3 h-3 text-sky-300" />
|
|
1900
|
+
Copy to path…
|
|
1901
|
+
</button>
|
|
1902
|
+
<button
|
|
1903
|
+
onClick={() => {
|
|
1904
|
+
toggleFileSelection(filePath);
|
|
1905
|
+
setOpenFileMenu(null);
|
|
1906
|
+
}}
|
|
1907
|
+
className="flex w-full items-center gap-2 border-t border-slate-800 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
1908
|
+
>
|
|
1909
|
+
<ListChecks className="w-3 h-3 text-amber-300" />
|
|
1910
|
+
{selectedFiles.has(filePath) ? "Deselect" : "Select"}
|
|
1911
|
+
</button>
|
|
1912
|
+
{selectedFileList.length > 1 && selectedFiles.has(filePath) && (
|
|
1913
|
+
<>
|
|
1914
|
+
<button
|
|
1915
|
+
onClick={() => {
|
|
1916
|
+
moveFilesToFolder(selectedFileList);
|
|
1917
|
+
setOpenFileMenu(null);
|
|
1918
|
+
}}
|
|
1919
|
+
className="flex w-full items-center gap-2 border-t border-slate-800 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
1920
|
+
>
|
|
1921
|
+
Move selected…
|
|
1922
|
+
</button>
|
|
1923
|
+
<button
|
|
1924
|
+
onClick={() => {
|
|
1925
|
+
copyFilesToFolder(selectedFileList);
|
|
1926
|
+
setOpenFileMenu(null);
|
|
1927
|
+
}}
|
|
1928
|
+
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
1929
|
+
>
|
|
1930
|
+
Copy selected…
|
|
1931
|
+
</button>
|
|
1932
|
+
</>
|
|
1933
|
+
)}
|
|
1934
|
+
<button
|
|
1935
|
+
onClick={() => deleteFile(filePath)}
|
|
1936
|
+
className="flex w-full items-center gap-2 border-t border-slate-800 px-2 py-1.5 text-left text-red-300 hover:bg-red-500/10"
|
|
1937
|
+
>
|
|
1938
|
+
<Trash2 className="w-3 h-3" />
|
|
1939
|
+
Delete
|
|
1940
|
+
</button>
|
|
1941
|
+
</div>
|
|
1942
|
+
)}
|
|
1943
|
+
</div>
|
|
1944
|
+
);
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1709
1947
|
return (
|
|
1710
|
-
<div className="fixed inset-0 z-40
|
|
1948
|
+
<div className="fixed inset-0 z-40 pointer-events-none">
|
|
1711
1949
|
<div
|
|
1712
|
-
className="absolute flex flex-col rounded-2xl border border-slate-800 bg-slate-950 shadow-2xl overflow-hidden"
|
|
1950
|
+
className="absolute pointer-events-auto flex flex-col rounded-2xl border border-slate-800 bg-slate-950 shadow-2xl overflow-hidden"
|
|
1713
1951
|
style={containerStyle}
|
|
1714
1952
|
>
|
|
1715
1953
|
{/* Resize handles */}
|
|
@@ -2021,155 +2259,7 @@ interface ImportMeta {
|
|
|
2021
2259
|
in workspace root • hold Option/Alt to copy
|
|
2022
2260
|
</div>
|
|
2023
2261
|
)}
|
|
2024
|
-
{
|
|
2025
|
-
const collapsed = collapsedFolders.has(folder);
|
|
2026
|
-
return (
|
|
2027
|
-
<div key={folder || "root"} className="mb-1">
|
|
2028
|
-
{folder && (
|
|
2029
|
-
<button
|
|
2030
|
-
onClick={() => toggleFolder(folder)}
|
|
2031
|
-
onDragOver={(e) => handleFolderDragOver(e, folder)}
|
|
2032
|
-
onDragLeave={() =>
|
|
2033
|
-
setDragOverFolder((current) =>
|
|
2034
|
-
current === folder ? null : current,
|
|
2035
|
-
)
|
|
2036
|
-
}
|
|
2037
|
-
onDrop={(e) => handleFolderDrop(e, folder)}
|
|
2038
|
-
className="flex items-center gap-1 w-full px-1 py-0.5 text-slate-400 hover:text-slate-200"
|
|
2039
|
-
title="Drop a file here to move it. Hold Option/Alt while dropping to copy."
|
|
2040
|
-
>
|
|
2041
|
-
{collapsed ? (
|
|
2042
|
-
<ChevronRight className="w-3 h-3" />
|
|
2043
|
-
) : (
|
|
2044
|
-
<ChevronDown className="w-3 h-3" />
|
|
2045
|
-
)}
|
|
2046
|
-
<Folder className="w-3 h-3" />
|
|
2047
|
-
<span
|
|
2048
|
-
className={`truncate rounded px-1 ${
|
|
2049
|
-
dragOverFolder === folder
|
|
2050
|
-
? "bg-amber-500/15 text-amber-200"
|
|
2051
|
-
: ""
|
|
2052
|
-
}`}
|
|
2053
|
-
>
|
|
2054
|
-
{folder}/
|
|
2055
|
-
</span>
|
|
2056
|
-
</button>
|
|
2057
|
-
)}
|
|
2058
|
-
{!collapsed &&
|
|
2059
|
-
files.map((filePath) => (
|
|
2060
|
-
<div
|
|
2061
|
-
key={filePath}
|
|
2062
|
-
data-selected={selectedFiles.has(filePath)}
|
|
2063
|
-
draggable
|
|
2064
|
-
onDragStart={(e) => handleFileDragStart(e, filePath)}
|
|
2065
|
-
onDragEnd={() => {
|
|
2066
|
-
setDraggingFile(null);
|
|
2067
|
-
setDragOverFolder(null);
|
|
2068
|
-
}}
|
|
2069
|
-
className={`group relative flex items-center gap-1 pl-${folder ? 5 : 1} pr-1 py-0.5 rounded cursor-pointer ${
|
|
2070
|
-
activeFile === filePath
|
|
2071
|
-
? "bg-amber-500/15 text-amber-200"
|
|
2072
|
-
: selectedFiles.has(filePath)
|
|
2073
|
-
? "bg-sky-500/10 text-sky-100 hover:bg-sky-500/15"
|
|
2074
|
-
: "text-slate-300 hover:bg-slate-800/40"
|
|
2075
|
-
}`}
|
|
2076
|
-
onClick={() => setActiveFile(filePath)}
|
|
2077
|
-
style={{ paddingLeft: folder ? 20 : 6 }}
|
|
2078
|
-
>
|
|
2079
|
-
{(selectMode || selectedFiles.has(filePath)) && (
|
|
2080
|
-
<input
|
|
2081
|
-
type="checkbox"
|
|
2082
|
-
checked={selectedFiles.has(filePath)}
|
|
2083
|
-
onClick={(e) => e.stopPropagation()}
|
|
2084
|
-
onChange={() => toggleFileSelection(filePath)}
|
|
2085
|
-
className="h-3 w-3 shrink-0 accent-amber-400"
|
|
2086
|
-
title="Select file"
|
|
2087
|
-
/>
|
|
2088
|
-
)}
|
|
2089
|
-
<span className="truncate flex-1">
|
|
2090
|
-
{baseName(filePath)}
|
|
2091
|
-
</span>
|
|
2092
|
-
<button
|
|
2093
|
-
onClick={(e) => {
|
|
2094
|
-
e.stopPropagation();
|
|
2095
|
-
setOpenFileMenu((current) =>
|
|
2096
|
-
current === filePath ? null : filePath,
|
|
2097
|
-
);
|
|
2098
|
-
setBulkMenuOpen(false);
|
|
2099
|
-
}}
|
|
2100
|
-
className="rounded px-1 text-slate-500 opacity-70 hover:bg-slate-800/70 hover:text-amber-200 group-hover:opacity-100"
|
|
2101
|
-
title="File actions"
|
|
2102
|
-
>
|
|
2103
|
-
⋯
|
|
2104
|
-
</button>
|
|
2105
|
-
{openFileMenu === filePath && (
|
|
2106
|
-
<div
|
|
2107
|
-
onClick={(e) => e.stopPropagation()}
|
|
2108
|
-
className="absolute right-1 top-6 z-40 w-40 overflow-hidden rounded-lg border border-slate-700 bg-slate-950 py-1 text-[11px] shadow-xl"
|
|
2109
|
-
>
|
|
2110
|
-
<button
|
|
2111
|
-
onClick={() => moveFile(filePath)}
|
|
2112
|
-
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
2113
|
-
>
|
|
2114
|
-
<Pencil className="w-3 h-3 text-amber-300" />
|
|
2115
|
-
Move / rename…
|
|
2116
|
-
</button>
|
|
2117
|
-
<button
|
|
2118
|
-
onClick={() => copyFile(filePath)}
|
|
2119
|
-
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
2120
|
-
>
|
|
2121
|
-
<Copy className="w-3 h-3 text-sky-300" />
|
|
2122
|
-
Copy to path…
|
|
2123
|
-
</button>
|
|
2124
|
-
<button
|
|
2125
|
-
onClick={() => {
|
|
2126
|
-
toggleFileSelection(filePath);
|
|
2127
|
-
setOpenFileMenu(null);
|
|
2128
|
-
}}
|
|
2129
|
-
className="flex w-full items-center gap-2 border-t border-slate-800 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
2130
|
-
>
|
|
2131
|
-
<ListChecks className="w-3 h-3 text-amber-300" />
|
|
2132
|
-
{selectedFiles.has(filePath)
|
|
2133
|
-
? "Deselect"
|
|
2134
|
-
: "Select"}
|
|
2135
|
-
</button>
|
|
2136
|
-
{selectedFileList.length > 1 &&
|
|
2137
|
-
selectedFiles.has(filePath) && (
|
|
2138
|
-
<>
|
|
2139
|
-
<button
|
|
2140
|
-
onClick={() => {
|
|
2141
|
-
moveFilesToFolder(selectedFileList);
|
|
2142
|
-
setOpenFileMenu(null);
|
|
2143
|
-
}}
|
|
2144
|
-
className="flex w-full items-center gap-2 border-t border-slate-800 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
2145
|
-
>
|
|
2146
|
-
Move selected…
|
|
2147
|
-
</button>
|
|
2148
|
-
<button
|
|
2149
|
-
onClick={() => {
|
|
2150
|
-
copyFilesToFolder(selectedFileList);
|
|
2151
|
-
setOpenFileMenu(null);
|
|
2152
|
-
}}
|
|
2153
|
-
className="flex w-full items-center gap-2 px-2 py-1.5 text-left text-slate-200 hover:bg-slate-800/70"
|
|
2154
|
-
>
|
|
2155
|
-
Copy selected…
|
|
2156
|
-
</button>
|
|
2157
|
-
</>
|
|
2158
|
-
)}
|
|
2159
|
-
<button
|
|
2160
|
-
onClick={() => deleteFile(filePath)}
|
|
2161
|
-
className="flex w-full items-center gap-2 border-t border-slate-800 px-2 py-1.5 text-left text-red-300 hover:bg-red-500/10"
|
|
2162
|
-
>
|
|
2163
|
-
<Trash2 className="w-3 h-3" />
|
|
2164
|
-
Delete
|
|
2165
|
-
</button>
|
|
2166
|
-
</div>
|
|
2167
|
-
)}
|
|
2168
|
-
</div>
|
|
2169
|
-
))}
|
|
2170
|
-
</div>
|
|
2171
|
-
);
|
|
2172
|
-
})}
|
|
2262
|
+
{fileTree.map((node) => renderFileTreeNode(node, 0))}
|
|
2173
2263
|
</div>
|
|
2174
2264
|
</div>
|
|
2175
2265
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
AWS_GOVERNANCE_GHA_LAB,
|
|
12
12
|
DEFAULT_GHA_LAB,
|
|
13
|
+
EMPTY_GITHUB_GHA_LAB,
|
|
13
14
|
GOVERNANCE_GHA_LAB,
|
|
14
15
|
parseGhaLabWorkspace,
|
|
15
16
|
REACT_VITE_TYPESCRIPT_GHA_LAB,
|
|
@@ -693,6 +694,12 @@ export default function LabsPanel() {
|
|
|
693
694
|
origin="github-actions"
|
|
694
695
|
emptyText="Save a GitHub lab to reopen it here"
|
|
695
696
|
newLabMenu={[
|
|
697
|
+
{
|
|
698
|
+
label: "Empty GitHub Template",
|
|
699
|
+
description:
|
|
700
|
+
"Minimal repo with blank .github/workflows/ci.yml and CODEOWNERS files",
|
|
701
|
+
onClick: () => openGhaLab(EMPTY_GITHUB_GHA_LAB),
|
|
702
|
+
},
|
|
696
703
|
{
|
|
697
704
|
label: "React Vite TypeScript Starter",
|
|
698
705
|
description:
|
|
@@ -513,6 +513,11 @@ li {
|
|
|
513
513
|
`,
|
|
514
514
|
};
|
|
515
515
|
|
|
516
|
+
const EMPTY_GITHUB_LAB_FILES: Record<string, string> = {
|
|
517
|
+
".github/workflows/ci.yml": "",
|
|
518
|
+
".github/CODEOWNERS": "",
|
|
519
|
+
};
|
|
520
|
+
|
|
516
521
|
// ─── Platform Governance Template ────────────────────────────────────────
|
|
517
522
|
//
|
|
518
523
|
// Mirrors a real-world "PLF-governance" mono-repo: one repo that owns
|
|
@@ -2435,6 +2440,15 @@ export const REACT_VITE_TYPESCRIPT_GHA_LAB: GithubActionsLabWorkspace = {
|
|
|
2435
2440
|
files: REACT_VITE_TYPESCRIPT_FILES,
|
|
2436
2441
|
};
|
|
2437
2442
|
|
|
2443
|
+
export const EMPTY_GITHUB_GHA_LAB: GithubActionsLabWorkspace = {
|
|
2444
|
+
version: 1,
|
|
2445
|
+
label: "Empty GitHub Lab Template",
|
|
2446
|
+
activeFile: ".github/workflows/ci.yml",
|
|
2447
|
+
defaultEvent: "push",
|
|
2448
|
+
defaultWorkflow: ".github/workflows/ci.yml",
|
|
2449
|
+
files: EMPTY_GITHUB_LAB_FILES,
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2438
2452
|
// ─── Helpers (mirror infraLab.ts API surface) ────────────────────────────
|
|
2439
2453
|
|
|
2440
2454
|
function cloneGhaLabEnvironment(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/enterpriselocallab.ts","./src/ghaconcurrency.ts","./src/githubactionslab.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/diagramsmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/ghaconcurrencypanel.tsx","./src/components/ghahistorypanel.tsx","./src/components/ghajobspanel.tsx","./src/components/gitdiffpanel.tsx","./src/components/gitdiffviewermodal.tsx","./src/components/githubactionslabmodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/app.tsx","./src/api.ts","./src/awsgovernanceiamlab.ts","./src/browsersecuritytemplates.ts","./src/codeowners.ts","./src/enterpriselocallab.ts","./src/ghaconcurrency.ts","./src/githubactionslab.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/diagramsmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/ghaconcurrencypanel.tsx","./src/components/ghahistorypanel.tsx","./src/components/ghajobspanel.tsx","./src/components/gitdiffpanel.tsx","./src/components/gitdiffviewermodal.tsx","./src/components/githubactionslabmodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/pullrequestpanel.tsx","./src/components/settingspanel.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"version":"5.9.3"}
|
package/template/cockpit.json
CHANGED