@hachej/boring-core 0.1.5 → 0.1.7
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/dist/app/front/index.js +1 -1
- package/dist/app/server/index.js +24 -7
- package/dist/{chunk-VTOS4C7B.js → chunk-A5TMALZR.js} +115 -5
- package/dist/{chunk-CZ4HIXII.js → chunk-RIEZ2IPH.js} +6 -1
- package/dist/front/index.d.ts +1 -1
- package/dist/front/index.js +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +4 -4
package/dist/app/front/index.js
CHANGED
package/dist/app/server/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
registerRoutes,
|
|
10
10
|
registerSettingsRoutes,
|
|
11
11
|
registerWorkspaceRoutes
|
|
12
|
-
} from "../../chunk-
|
|
12
|
+
} from "../../chunk-RIEZ2IPH.js";
|
|
13
13
|
import {
|
|
14
14
|
PostgresUserStore,
|
|
15
15
|
PostgresWorkspaceStore,
|
|
@@ -328,11 +328,23 @@ async function createCoreWorkspaceAgentServer(options = {}) {
|
|
|
328
328
|
plugins: options.plugins,
|
|
329
329
|
excludeDefaults: options.excludeDefaults
|
|
330
330
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
const provisionedWorkspaceRoots = /* @__PURE__ */ new Map();
|
|
332
|
+
const ensureWorkspaceProvisioned = (root) => {
|
|
333
|
+
if (pluginCollection.provisioningContributions.length === 0) return Promise.resolve();
|
|
334
|
+
const existing = provisionedWorkspaceRoots.get(root);
|
|
335
|
+
if (existing) return existing;
|
|
336
|
+
const pending = provisionWorkspaceAgentServer({
|
|
337
|
+
workspaceRoot: root,
|
|
338
|
+
provisioningContributions: pluginCollection.provisioningContributions,
|
|
339
|
+
force: options.forceProvisioning
|
|
340
|
+
}).catch((error) => {
|
|
341
|
+
provisionedWorkspaceRoots.delete(root);
|
|
342
|
+
throw error;
|
|
343
|
+
});
|
|
344
|
+
provisionedWorkspaceRoots.set(root, pending);
|
|
345
|
+
return pending;
|
|
346
|
+
};
|
|
347
|
+
await ensureWorkspaceProvisioned(workspaceRoot);
|
|
336
348
|
const bridges = /* @__PURE__ */ new Map();
|
|
337
349
|
const getUiBridge = (workspaceId) => {
|
|
338
350
|
let bridge = bridges.get(workspaceId);
|
|
@@ -343,11 +355,16 @@ async function createCoreWorkspaceAgentServer(options = {}) {
|
|
|
343
355
|
return bridge;
|
|
344
356
|
};
|
|
345
357
|
const resolveWorkspaceId = async (request) => options.getWorkspaceId ? await options.getWorkspaceId(request) : await resolveAuthorizedWorkspaceId(request, workspaceStore);
|
|
346
|
-
const resolveRoot = async (workspaceId, request) =>
|
|
358
|
+
const resolveRoot = async (workspaceId, request) => {
|
|
359
|
+
const root = options.getWorkspaceRoot ? await options.getWorkspaceRoot(workspaceId, request) : await resolveWorkspaceRoot(workspaceRoot, workspaceId);
|
|
360
|
+
await ensureWorkspaceProvisioned(root);
|
|
361
|
+
return root;
|
|
362
|
+
};
|
|
347
363
|
await app.register(registerAgentRoutes, {
|
|
348
364
|
workspaceRoot,
|
|
349
365
|
sessionId: options.sessionId,
|
|
350
366
|
templatePath: options.templatePath,
|
|
367
|
+
getTemplatePath: options.getTemplatePath,
|
|
351
368
|
mode: options.mode,
|
|
352
369
|
version: options.version,
|
|
353
370
|
extraTools: [
|
|
@@ -1809,11 +1809,11 @@ function AuthGate({
|
|
|
1809
1809
|
clearTimeout(redirectTimerRef.current);
|
|
1810
1810
|
redirectTimerRef.current = null;
|
|
1811
1811
|
}
|
|
1812
|
-
const
|
|
1812
|
+
const pathname2 = normalizePath(currentLocation.pathname);
|
|
1813
1813
|
const currentPath = buildCurrentPath(currentLocation);
|
|
1814
1814
|
if (session.data) {
|
|
1815
1815
|
nullSinceRef.current = null;
|
|
1816
|
-
if (
|
|
1816
|
+
if (pathname2 === routes.signin) {
|
|
1817
1817
|
const destination = readSafeRedirect(currentLocation.search) ?? "/";
|
|
1818
1818
|
if (destination !== currentPath) {
|
|
1819
1819
|
goTo(destination, { replace: true });
|
|
@@ -1821,7 +1821,11 @@ function AuthGate({
|
|
|
1821
1821
|
}
|
|
1822
1822
|
return;
|
|
1823
1823
|
}
|
|
1824
|
-
if (
|
|
1824
|
+
if (isPublicPath(pathname2, normalizedPublicPaths)) {
|
|
1825
|
+
nullSinceRef.current = null;
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (session.isPending) {
|
|
1825
1829
|
nullSinceRef.current = null;
|
|
1826
1830
|
return;
|
|
1827
1831
|
}
|
|
@@ -1840,6 +1844,8 @@ function AuthGate({
|
|
|
1840
1844
|
}, remainingMs);
|
|
1841
1845
|
redirectTimerRef.current.unref?.();
|
|
1842
1846
|
}, [currentLocation, goTo, graceMs, normalizedPublicPaths, readNow, session.data, session.isPending]);
|
|
1847
|
+
const pathname = normalizePath(currentLocation.pathname);
|
|
1848
|
+
if (session.isPending && !isPublicPath(pathname, normalizedPublicPaths)) return null;
|
|
1843
1849
|
return /* @__PURE__ */ jsx14(Fragment, { children });
|
|
1844
1850
|
}
|
|
1845
1851
|
|
|
@@ -2752,7 +2758,7 @@ function MembersPage() {
|
|
|
2752
2758
|
}
|
|
2753
2759
|
|
|
2754
2760
|
// src/front/workspace/WorkspaceSettingsPage.tsx
|
|
2755
|
-
import { useCallback as useCallback8, useState as useState18 } from "react";
|
|
2761
|
+
import { useCallback as useCallback8, useEffect as useEffect10, useState as useState18 } from "react";
|
|
2756
2762
|
import { useQuery as useQuery6, useMutation as useMutation4, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
2757
2763
|
import { useNavigate as useNavigate4 } from "react-router-dom";
|
|
2758
2764
|
import {
|
|
@@ -2775,6 +2781,7 @@ import {
|
|
|
2775
2781
|
} from "@hachej/boring-ui-kit";
|
|
2776
2782
|
import {
|
|
2777
2783
|
AlertCircle,
|
|
2784
|
+
FileImage,
|
|
2778
2785
|
HardDrive,
|
|
2779
2786
|
RefreshCw,
|
|
2780
2787
|
Settings2 as Settings22,
|
|
@@ -2852,6 +2859,7 @@ function roleLabel(role) {
|
|
|
2852
2859
|
var WORKSPACE_NAV_ITEMS = [
|
|
2853
2860
|
{ href: "#general", label: "General", description: "Name and access" },
|
|
2854
2861
|
{ href: "#runtime", label: "Runtime", description: "Provisioning state" },
|
|
2862
|
+
{ href: "#files", label: "Files", description: "Markdown assets" },
|
|
2855
2863
|
{ href: "#danger-zone", label: "Danger zone", description: "Permanent actions" }
|
|
2856
2864
|
];
|
|
2857
2865
|
function WorkspaceSettingsPage({ topBar } = {}) {
|
|
@@ -2866,8 +2874,11 @@ function WorkspaceSettingsPage({ topBar } = {}) {
|
|
|
2866
2874
|
const [deleteConfirmName, setDeleteConfirmName] = useState18("");
|
|
2867
2875
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState18(false);
|
|
2868
2876
|
const [deleteError, setDeleteError] = useState18(null);
|
|
2877
|
+
const [imageUploadDirValue, setImageUploadDirValue] = useState18(null);
|
|
2878
|
+
const [fileSettingsError, setFileSettingsError] = useState18(null);
|
|
2869
2879
|
const displayName = nameValue ?? workspace?.name ?? "";
|
|
2870
2880
|
const encodedWorkspaceId = encodeURIComponent(workspaceId);
|
|
2881
|
+
const requestHeaders = workspaceId ? { "x-boring-workspace-id": workspaceId } : void 0;
|
|
2871
2882
|
const runtimeQuery = useQuery6({
|
|
2872
2883
|
queryKey: ["runtime", workspaceId],
|
|
2873
2884
|
queryFn: async () => {
|
|
@@ -2884,6 +2895,50 @@ function WorkspaceSettingsPage({ topBar } = {}) {
|
|
|
2884
2895
|
},
|
|
2885
2896
|
enabled: workspaceId.length > 0
|
|
2886
2897
|
});
|
|
2898
|
+
const fileSettingsQuery = useQuery6({
|
|
2899
|
+
queryKey: ["workspace-file-settings", workspaceId],
|
|
2900
|
+
queryFn: async () => {
|
|
2901
|
+
try {
|
|
2902
|
+
const data = await apiFetchJson(
|
|
2903
|
+
"/api/v1/workspace-settings",
|
|
2904
|
+
{ headers: requestHeaders }
|
|
2905
|
+
);
|
|
2906
|
+
return data.settings;
|
|
2907
|
+
} catch (err) {
|
|
2908
|
+
const detail = getHttpErrorDetail(err);
|
|
2909
|
+
if (detail.status === 404) return null;
|
|
2910
|
+
throw err;
|
|
2911
|
+
}
|
|
2912
|
+
},
|
|
2913
|
+
enabled: workspaceId.length > 0,
|
|
2914
|
+
retry: false
|
|
2915
|
+
});
|
|
2916
|
+
useEffect10(() => {
|
|
2917
|
+
const next = fileSettingsQuery.data?.markdown?.imageUploadDir;
|
|
2918
|
+
if (typeof next === "string") setImageUploadDirValue(next);
|
|
2919
|
+
}, [fileSettingsQuery.data?.markdown?.imageUploadDir]);
|
|
2920
|
+
const fileSettingsMutation = useMutation4({
|
|
2921
|
+
mutationFn: async (imageUploadDir) => {
|
|
2922
|
+
const data = await apiFetchJson(
|
|
2923
|
+
"/api/v1/workspace-settings",
|
|
2924
|
+
{
|
|
2925
|
+
method: "PUT",
|
|
2926
|
+
headers: { ...requestHeaders, "Content-Type": "application/json" },
|
|
2927
|
+
body: JSON.stringify({ settings: { markdown: { imageUploadDir } } })
|
|
2928
|
+
}
|
|
2929
|
+
);
|
|
2930
|
+
return data.settings;
|
|
2931
|
+
},
|
|
2932
|
+
onSuccess: (settings) => {
|
|
2933
|
+
setFileSettingsError(null);
|
|
2934
|
+
setImageUploadDirValue(settings.markdown?.imageUploadDir ?? "assets/images");
|
|
2935
|
+
queryClient.invalidateQueries({ queryKey: ["workspace-file-settings", workspaceId] });
|
|
2936
|
+
},
|
|
2937
|
+
onError: (err) => {
|
|
2938
|
+
const detail = getHttpErrorDetail(err);
|
|
2939
|
+
setFileSettingsError(detail.message);
|
|
2940
|
+
}
|
|
2941
|
+
});
|
|
2887
2942
|
const renameMutation = useMutation4({
|
|
2888
2943
|
mutationFn: async (name) => {
|
|
2889
2944
|
await apiFetch(`/api/v1/workspaces/${encodedWorkspaceId}`, {
|
|
@@ -2950,15 +3005,28 @@ function WorkspaceSettingsPage({ topBar } = {}) {
|
|
|
2950
3005
|
setDeleteError(null);
|
|
2951
3006
|
deleteMutation.mutate();
|
|
2952
3007
|
}, [deleteMutation]);
|
|
3008
|
+
const handleSaveFileSettings = useCallback8(() => {
|
|
3009
|
+
const trimmed = (imageUploadDirValue ?? "").trim();
|
|
3010
|
+
if (!trimmed) return;
|
|
3011
|
+
fileSettingsMutation.mutate(trimmed);
|
|
3012
|
+
}, [fileSettingsMutation, imageUploadDirValue]);
|
|
2953
3013
|
const runtime = runtimeQuery.data ?? null;
|
|
2954
3014
|
const hasRuntime = runtime !== null && runtimeQuery.isSuccess;
|
|
3015
|
+
const fileSettings = fileSettingsQuery.data ?? null;
|
|
3016
|
+
const hasFileSettings = fileSettings !== null && fileSettingsQuery.isSuccess;
|
|
3017
|
+
const currentImageUploadDir = fileSettings?.markdown?.imageUploadDir ?? "assets/images";
|
|
3018
|
+
const fileSettingsChanged = imageUploadDirValue !== null && imageUploadDirValue.trim() !== currentImageUploadDir;
|
|
2955
3019
|
const nameChanged = nameValue !== null && nameValue.trim() !== workspace?.name;
|
|
2956
3020
|
const canEditName = role !== "viewer";
|
|
2957
3021
|
const canDeleteWorkspace = role === "owner" || role === null;
|
|
2958
3022
|
const workspaceName = workspace?.name ?? "Workspace";
|
|
2959
3023
|
const workspaceInitial2 = (workspace?.name?.trim()?.[0] ?? "W").toUpperCase();
|
|
2960
3024
|
const topBarNode = topBar === void 0 ? /* @__PURE__ */ jsx19(SettingsTopBar2, { workspaceId, workspaceName }) : topBar;
|
|
2961
|
-
const navItems =
|
|
3025
|
+
const navItems = WORKSPACE_NAV_ITEMS.filter((item) => {
|
|
3026
|
+
if (item.href === "#runtime") return hasRuntime;
|
|
3027
|
+
if (item.href === "#files") return hasFileSettings;
|
|
3028
|
+
return true;
|
|
3029
|
+
});
|
|
2962
3030
|
return /* @__PURE__ */ jsxs13("main", { className: "boring-settings-shell", children: [
|
|
2963
3031
|
topBarNode,
|
|
2964
3032
|
/* @__PURE__ */ jsx19("div", { className: "boring-settings-scroll", children: /* @__PURE__ */ jsxs13("div", { className: "boring-settings-layout", children: [
|
|
@@ -3091,6 +3159,48 @@ function WorkspaceSettingsPage({ topBar } = {}) {
|
|
|
3091
3159
|
] })
|
|
3092
3160
|
}
|
|
3093
3161
|
),
|
|
3162
|
+
hasFileSettings && /* @__PURE__ */ jsx19(
|
|
3163
|
+
UiSettingsPanel2,
|
|
3164
|
+
{
|
|
3165
|
+
id: "files",
|
|
3166
|
+
testId: "file-settings-card",
|
|
3167
|
+
icon: /* @__PURE__ */ jsx19(FileImage, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
3168
|
+
title: "Files",
|
|
3169
|
+
description: "Configure where markdown editor image uploads are stored. Direct/local workspaces can also edit .boring/settings.",
|
|
3170
|
+
footer: /* @__PURE__ */ jsx19(
|
|
3171
|
+
Button14,
|
|
3172
|
+
{
|
|
3173
|
+
"data-testid": "save-file-settings",
|
|
3174
|
+
size: "sm",
|
|
3175
|
+
disabled: !fileSettingsChanged || fileSettingsMutation.isPending || !canEditName,
|
|
3176
|
+
onClick: handleSaveFileSettings,
|
|
3177
|
+
children: fileSettingsMutation.isPending ? "Saving..." : "Save file settings"
|
|
3178
|
+
}
|
|
3179
|
+
),
|
|
3180
|
+
children: /* @__PURE__ */ jsxs13("div", { className: "space-y-4", children: [
|
|
3181
|
+
fileSettingsError && /* @__PURE__ */ jsx19(Notice5, { "data-testid": "file-settings-error", role: "alert", tone: "error", description: fileSettingsError }),
|
|
3182
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
3183
|
+
/* @__PURE__ */ jsx19(Label9, { htmlFor: "markdown-image-upload-dir", className: "text-[12px]", children: "Markdown image upload path" }),
|
|
3184
|
+
/* @__PURE__ */ jsx19(
|
|
3185
|
+
Input9,
|
|
3186
|
+
{
|
|
3187
|
+
id: "markdown-image-upload-dir",
|
|
3188
|
+
"data-testid": "markdown-image-upload-dir-input",
|
|
3189
|
+
className: "h-8 font-mono text-[13px]",
|
|
3190
|
+
value: imageUploadDirValue ?? currentImageUploadDir,
|
|
3191
|
+
onChange: (e) => setImageUploadDirValue(e.target.value),
|
|
3192
|
+
disabled: !canEditName
|
|
3193
|
+
}
|
|
3194
|
+
),
|
|
3195
|
+
/* @__PURE__ */ jsxs13(FieldNote, { children: [
|
|
3196
|
+
"Relative workspace path used by markdown image uploads. Stored in ",
|
|
3197
|
+
/* @__PURE__ */ jsx19("code", { children: ".boring/settings" }),
|
|
3198
|
+
"."
|
|
3199
|
+
] })
|
|
3200
|
+
] })
|
|
3201
|
+
] })
|
|
3202
|
+
}
|
|
3203
|
+
),
|
|
3094
3204
|
/* @__PURE__ */ jsx19(
|
|
3095
3205
|
UiSettingsPanel2,
|
|
3096
3206
|
{
|
|
@@ -672,11 +672,16 @@ async function createCoreApp(config, options) {
|
|
|
672
672
|
],
|
|
673
673
|
styleSrc: [
|
|
674
674
|
"'self'",
|
|
675
|
+
"https://fonts.googleapis.com",
|
|
675
676
|
(request) => `'nonce-${request.cspNonce ?? ""}'`
|
|
676
677
|
],
|
|
678
|
+
// React/DockView use style attributes for runtime layout sizing.
|
|
679
|
+
// Keep stylesheet loading nonce/domain-bound, but allow attributes
|
|
680
|
+
// so production CSP does not collapse workspace/workbench panes.
|
|
681
|
+
styleSrcAttr: ["'unsafe-inline'"],
|
|
677
682
|
imgSrc: ["'self'", "data:", "blob:"],
|
|
678
683
|
connectSrc: ["'self'"],
|
|
679
|
-
fontSrc: ["'self'"],
|
|
684
|
+
fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
|
|
680
685
|
objectSrc: ["'none'"],
|
|
681
686
|
frameAncestors: ["'none'"],
|
|
682
687
|
baseUri: ["'self'"],
|
package/dist/front/index.d.ts
CHANGED
|
@@ -410,7 +410,7 @@ interface AuthGateProps {
|
|
|
410
410
|
}) => void;
|
|
411
411
|
now?: () => number;
|
|
412
412
|
}
|
|
413
|
-
declare function AuthGate({ children, publicPaths, graceMs, location, navigate, now, }: AuthGateProps): react_jsx_runtime.JSX.Element;
|
|
413
|
+
declare function AuthGate({ children, publicPaths, graceMs, location, navigate, now, }: AuthGateProps): react_jsx_runtime.JSX.Element | null;
|
|
414
414
|
|
|
415
415
|
interface CoreCommand {
|
|
416
416
|
id: string;
|
package/dist/front/index.js
CHANGED
package/dist/server/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Foundation package for boring-ui-v2 apps: DB, auth, config, HTTP app factory, and frontend app shell.",
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
"react-router-dom": "^7.14.2",
|
|
79
79
|
"smol-toml": "^1.6.1",
|
|
80
80
|
"zod": "^3.25.76",
|
|
81
|
-
"@hachej/boring-
|
|
82
|
-
"@hachej/boring-
|
|
83
|
-
"@hachej/boring-
|
|
81
|
+
"@hachej/boring-agent": "0.1.7",
|
|
82
|
+
"@hachej/boring-workspace": "0.1.7",
|
|
83
|
+
"@hachej/boring-ui-kit": "0.1.7"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@testing-library/jest-dom": "^6.9.1",
|