@flaredev/cli 0.4.0 → 0.4.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.
@@ -1,165 +1,165 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import { useRouter } from "next/navigation";
5
- import { useMemo, useState, useTransition, type FormEvent } from "react";
6
- import { createValidators, formValuesToInput, initialFormValues, issuesByField, storedFields, type Resource } from "@flaredev/core";
7
- import { toast } from "sonner";
8
- import { createRecordAction, updateRecordAction } from "@/app/dashboard/actions";
9
- import { Alert, AlertDescription } from "@/components/ui/alert";
10
- import { Button } from "@/components/ui/button";
11
- import { Card, CardContent, CardFooter } from "@/components/ui/card";
12
- import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
13
- import { Spinner } from "@/components/ui/spinner";
14
- import { FieldWidget, type RelationMeta } from "./fields/field-widget";
15
-
16
- export interface ResourceFormProps {
17
- resource: Resource;
18
- mode: "create" | "edit";
19
- /** Record id (edit mode). */
20
- id?: string;
21
- record?: Record<string, unknown> | null;
22
- /** belongsTo field key → related resource metadata and the current value's title. */
23
- relations?: Record<string, RelationMeta & { initialTitle?: string }>;
24
- /** Where Cancel and a successful save go, when the form owns the page. */
25
- listHref: string;
26
- /** In a dialog: close it instead of navigating, and drop the card around the fields. */
27
- onDone?: () => void;
28
- }
29
-
30
- const WIDE = new Set(["text", "file"]);
31
-
32
- /**
33
- * Create/edit form rendered from field metadata. Values are validated in the browser
34
- * with the descriptor's zod schemas, then again on the server by the same store the
35
- * REST API uses; server errors (e.g. unique conflicts) land on their fields.
36
- */
37
- export function ResourceForm({ resource, mode, id, record, relations = {}, listHref, onDone }: ResourceFormProps) {
38
- const router = useRouter();
39
- const validators = useMemo(() => createValidators(resource), [resource]);
40
- const fields = useMemo(() => storedFields(resource), [resource]);
41
- const [values, setValues] = useState(() => initialFormValues(resource, record));
42
- const [errors, setErrors] = useState<Record<string, string>>({});
43
- const [pending, startTransition] = useTransition();
44
-
45
- const setValue = (key: string, value: string | boolean) => {
46
- setValues((current) => ({ ...current, [key]: value }));
47
- if (errors[key]) setErrors(({ [key]: _removed, ...rest }) => rest);
48
- };
49
-
50
- function focusFirstError(fieldErrors: Record<string, string>) {
51
- const first = fields.find(([key]) => fieldErrors[key]);
52
- if (first) document.getElementById(`field-${first[0]}`)?.focus();
53
- }
54
-
55
- function onSubmit(event: FormEvent) {
56
- event.preventDefault();
57
- const input = formValuesToInput(resource, values, mode);
58
- const parsed = (mode === "create" ? validators.create : validators.update).safeParse(input);
59
- if (!parsed.success) {
60
- const fieldErrors = issuesByField(parsed.error.issues);
61
- setErrors(fieldErrors);
62
- focusFirstError(fieldErrors);
63
- return;
64
- }
65
-
66
- startTransition(async () => {
67
- const result = mode === "create" ? await createRecordAction(resource.name, input) : await updateRecordAction(resource.name, id!, input);
68
- if (result.ok) {
69
- toast.success(`${resource.label} ${mode === "create" ? "created" : "saved"}.`);
70
- if (onDone) onDone();
71
- else router.push(listHref);
72
- router.refresh();
73
- return;
74
- }
75
- const fieldErrors = result.issues ? issuesByField(result.issues) : result.field ? { [result.field]: result.error } : { _form: result.error };
76
- setErrors(fieldErrors);
77
- focusFirstError(fieldErrors);
78
- });
79
- }
80
-
81
- const Frame = onDone ? DialogFrame : Card;
82
- const Body = onDone ? DialogBody : CardContent;
83
- const Footer = onDone ? DialogFooterRow : CardFooter;
84
-
85
- return (
86
- <form onSubmit={onSubmit} noValidate>
87
- <Frame>
88
- <Body>
89
- {errors._form && (
90
- <Alert variant="destructive" className="mb-6">
91
- <AlertDescription>{errors._form}</AlertDescription>
92
- </Alert>
93
- )}
94
- <FieldGroup className="grid gap-6 md:grid-cols-2">
95
- {fields.map(([key, def]) => {
96
- const error = errors[key];
97
- const widgetId = `field-${key}`;
98
- const horizontal = def.kind === "boolean";
99
- return (
100
- <Field
101
- key={key}
102
- data-invalid={error ? true : undefined}
103
- className={WIDE.has(def.kind) ? "md:col-span-2" : undefined}
104
- >
105
- <FieldLabel htmlFor={widgetId}>
106
- {def.label}
107
- {def.required && def.kind !== "boolean" && (
108
- <span className="ml-0.5 text-muted-foreground" aria-hidden>
109
- *
110
- </span>
111
- )}
112
- </FieldLabel>
113
- <div className={horizontal ? "flex h-9 items-center" : undefined}>
114
- <FieldWidget
115
- id={widgetId}
116
- name={key}
117
- resourceName={resource.name}
118
- fieldKey={key}
119
- field={def}
120
- value={values[key] ?? ""}
121
- onChange={(value) => setValue(key, value)}
122
- invalid={Boolean(error)}
123
- disabled={pending}
124
- relation={relations[key]}
125
- />
126
- </div>
127
- {def.helpText && <FieldDescription>{def.helpText}</FieldDescription>}
128
- {error && <FieldError>{error}</FieldError>}
129
- </Field>
130
- );
131
- })}
132
- </FieldGroup>
133
- </Body>
134
- <Footer className="flex justify-end gap-2 border-t pt-4">
135
- {onDone ? (
136
- <Button type="button" variant="outline" onClick={onDone} disabled={pending}>
137
- Cancel
138
- </Button>
139
- ) : (
140
- <Button variant="outline" asChild>
141
- <Link href={listHref}>Cancel</Link>
142
- </Button>
143
- )}
144
- <Button type="submit" disabled={pending}>
145
- {pending && <Spinner data-icon="inline-start" />}
146
- {mode === "create" ? `Create ${resource.label.toLowerCase()}` : "Save changes"}
147
- </Button>
148
- </Footer>
149
- </Frame>
150
- </form>
151
- );
152
- }
153
-
154
- /** Plain wrappers so the same form works in a page (a card) and in a dialog (no card). */
155
- function DialogFrame({ children }: { children: React.ReactNode }) {
156
- return <div className="flex flex-col gap-4">{children}</div>;
157
- }
158
-
159
- function DialogBody({ children }: { children: React.ReactNode }) {
160
- return <div className="max-h-[60vh] overflow-y-auto px-1">{children}</div>;
161
- }
162
-
163
- function DialogFooterRow({ children, className }: { children: React.ReactNode; className?: string }) {
164
- return <div className={className}>{children}</div>;
165
- }
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useRouter } from "next/navigation";
5
+ import { useMemo, useState, useTransition, type FormEvent } from "react";
6
+ import { createValidators, formValuesToInput, initialFormValues, issuesByField, storedFields, type ClientResource } from "@flaredev/core";
7
+ import { toast } from "sonner";
8
+ import { createRecordAction, updateRecordAction } from "@/app/dashboard/actions";
9
+ import { Alert, AlertDescription } from "@/components/ui/alert";
10
+ import { Button } from "@/components/ui/button";
11
+ import { Card, CardContent, CardFooter } from "@/components/ui/card";
12
+ import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
13
+ import { Spinner } from "@/components/ui/spinner";
14
+ import { FieldWidget, type RelationMeta } from "./fields/field-widget";
15
+
16
+ export interface ResourceFormProps {
17
+ resource: ClientResource;
18
+ mode: "create" | "edit";
19
+ /** Record id (edit mode). */
20
+ id?: string;
21
+ record?: Record<string, unknown> | null;
22
+ /** belongsTo field key → related resource metadata and the current value's title. */
23
+ relations?: Record<string, RelationMeta & { initialTitle?: string }>;
24
+ /** Where Cancel and a successful save go, when the form owns the page. */
25
+ listHref: string;
26
+ /** In a dialog: close it instead of navigating, and drop the card around the fields. */
27
+ onDone?: () => void;
28
+ }
29
+
30
+ const WIDE = new Set(["text", "file"]);
31
+
32
+ /**
33
+ * Create/edit form rendered from field metadata. Values are validated in the browser
34
+ * with the descriptor's zod schemas, then again on the server by the same store the
35
+ * REST API uses; server errors (e.g. unique conflicts) land on their fields.
36
+ */
37
+ export function ResourceForm({ resource, mode, id, record, relations = {}, listHref, onDone }: ResourceFormProps) {
38
+ const router = useRouter();
39
+ const validators = useMemo(() => createValidators(resource), [resource]);
40
+ const fields = useMemo(() => storedFields(resource), [resource]);
41
+ const [values, setValues] = useState(() => initialFormValues(resource, record));
42
+ const [errors, setErrors] = useState<Record<string, string>>({});
43
+ const [pending, startTransition] = useTransition();
44
+
45
+ const setValue = (key: string, value: string | boolean) => {
46
+ setValues((current) => ({ ...current, [key]: value }));
47
+ if (errors[key]) setErrors(({ [key]: _removed, ...rest }) => rest);
48
+ };
49
+
50
+ function focusFirstError(fieldErrors: Record<string, string>) {
51
+ const first = fields.find(([key]) => fieldErrors[key]);
52
+ if (first) document.getElementById(`field-${first[0]}`)?.focus();
53
+ }
54
+
55
+ function onSubmit(event: FormEvent) {
56
+ event.preventDefault();
57
+ const input = formValuesToInput(resource, values, mode);
58
+ const parsed = (mode === "create" ? validators.create : validators.update).safeParse(input);
59
+ if (!parsed.success) {
60
+ const fieldErrors = issuesByField(parsed.error.issues);
61
+ setErrors(fieldErrors);
62
+ focusFirstError(fieldErrors);
63
+ return;
64
+ }
65
+
66
+ startTransition(async () => {
67
+ const result = mode === "create" ? await createRecordAction(resource.name, input) : await updateRecordAction(resource.name, id!, input);
68
+ if (result.ok) {
69
+ toast.success(`${resource.label} ${mode === "create" ? "created" : "saved"}.`);
70
+ if (onDone) onDone();
71
+ else router.push(listHref);
72
+ router.refresh();
73
+ return;
74
+ }
75
+ const fieldErrors = result.issues ? issuesByField(result.issues) : result.field ? { [result.field]: result.error } : { _form: result.error };
76
+ setErrors(fieldErrors);
77
+ focusFirstError(fieldErrors);
78
+ });
79
+ }
80
+
81
+ const Frame = onDone ? DialogFrame : Card;
82
+ const Body = onDone ? DialogBody : CardContent;
83
+ const Footer = onDone ? DialogFooterRow : CardFooter;
84
+
85
+ return (
86
+ <form onSubmit={onSubmit} noValidate>
87
+ <Frame>
88
+ <Body>
89
+ {errors._form && (
90
+ <Alert variant="destructive" className="mb-6">
91
+ <AlertDescription>{errors._form}</AlertDescription>
92
+ </Alert>
93
+ )}
94
+ <FieldGroup className="grid gap-6 md:grid-cols-2">
95
+ {fields.map(([key, def]) => {
96
+ const error = errors[key];
97
+ const widgetId = `field-${key}`;
98
+ const horizontal = def.kind === "boolean";
99
+ return (
100
+ <Field
101
+ key={key}
102
+ data-invalid={error ? true : undefined}
103
+ className={WIDE.has(def.kind) ? "md:col-span-2" : undefined}
104
+ >
105
+ <FieldLabel htmlFor={widgetId}>
106
+ {def.label}
107
+ {def.required && def.kind !== "boolean" && (
108
+ <span className="ml-0.5 text-muted-foreground" aria-hidden>
109
+ *
110
+ </span>
111
+ )}
112
+ </FieldLabel>
113
+ <div className={horizontal ? "flex h-9 items-center" : undefined}>
114
+ <FieldWidget
115
+ id={widgetId}
116
+ name={key}
117
+ resourceName={resource.name}
118
+ fieldKey={key}
119
+ field={def}
120
+ value={values[key] ?? ""}
121
+ onChange={(value) => setValue(key, value)}
122
+ invalid={Boolean(error)}
123
+ disabled={pending}
124
+ relation={relations[key]}
125
+ />
126
+ </div>
127
+ {def.helpText && <FieldDescription>{def.helpText}</FieldDescription>}
128
+ {error && <FieldError>{error}</FieldError>}
129
+ </Field>
130
+ );
131
+ })}
132
+ </FieldGroup>
133
+ </Body>
134
+ <Footer className="flex justify-end gap-2 border-t pt-4">
135
+ {onDone ? (
136
+ <Button type="button" variant="outline" onClick={onDone} disabled={pending}>
137
+ Cancel
138
+ </Button>
139
+ ) : (
140
+ <Button variant="outline" asChild>
141
+ <Link href={listHref}>Cancel</Link>
142
+ </Button>
143
+ )}
144
+ <Button type="submit" disabled={pending}>
145
+ {pending && <Spinner data-icon="inline-start" />}
146
+ {mode === "create" ? `Create ${resource.label.toLowerCase()}` : "Save changes"}
147
+ </Button>
148
+ </Footer>
149
+ </Frame>
150
+ </form>
151
+ );
152
+ }
153
+
154
+ /** Plain wrappers so the same form works in a page (a card) and in a dialog (no card). */
155
+ function DialogFrame({ children }: { children: React.ReactNode }) {
156
+ return <div className="flex flex-col gap-4">{children}</div>;
157
+ }
158
+
159
+ function DialogBody({ children }: { children: React.ReactNode }) {
160
+ return <div className="max-h-[60vh] overflow-y-auto px-1">{children}</div>;
161
+ }
162
+
163
+ function DialogFooterRow({ children, className }: { children: React.ReactNode; className?: string }) {
164
+ return <div className={className}>{children}</div>;
165
+ }
@@ -3,7 +3,7 @@
3
3
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
4
4
  import { useState, type FormEvent } from "react";
5
5
  import { SearchIcon, XIcon } from "lucide-react";
6
- import { optionLabel, storedFields, type Resource } from "@flaredev/core";
6
+ import { optionLabel, storedFields, type ClientResource } from "@flaredev/core";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
9
9
  import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -12,7 +12,7 @@ import { hrefWith } from "./query";
12
12
  const ALL = "__all__";
13
13
 
14
14
  /** Search box and enum/boolean filters for ResourceTable; all state goes into the URL. */
15
- export function ResourceTableToolbar({ resource }: { resource: Resource }) {
15
+ export function ResourceTableToolbar({ resource }: { resource: ClientResource }) {
16
16
  const router = useRouter();
17
17
  const pathname = usePathname();
18
18
  const params = new URLSearchParams(useSearchParams().toString());
@@ -1,6 +1,6 @@
1
1
  import Link from "next/link";
2
2
  import { ArrowDownIcon, ArrowUpIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsUpDownIcon, PlusIcon } from "lucide-react";
3
- import { formatValue, optionLabel, statusTone, storedFields, type Resource, type StoredField } from "@flaredev/core";
3
+ import { clientResource, formatValue, optionLabel, statusTone, storedFields, type Resource, type StoredField } from "@flaredev/core";
4
4
  import { isSortable } from "@flaredev/core/server";
5
5
  import { Alert, AlertDescription } from "@/components/ui/alert";
6
6
  import { Badge } from "@/components/ui/badge";
@@ -44,6 +44,9 @@ const NUMERIC = new Set(["int", "float"]);
44
44
  */
45
45
  export async function ResourceTable({ resource, searchParams }: { resource: Resource; searchParams: SearchParams }) {
46
46
  await requireAccess(resource, "read");
47
+ // Hooks and computed values are functions, and a function can't be sent to the browser.
48
+ // This is the same resource without them, for the client components below.
49
+ const forClient = clientResource(resource);
47
50
  const basePath = resourcePath(resource);
48
51
  const permissions = await adminPermissions(resource.name);
49
52
  const params = toSearchParams(searchParams);
@@ -102,15 +105,15 @@ export async function ResourceTable({ resource, searchParams }: { resource: Reso
102
105
  <TableSelection ids={rowIds}>
103
106
  <div className="flex flex-col gap-4">
104
107
  <div className="flex flex-wrap items-center justify-between gap-2">
105
- <ResourceTableToolbar resource={resource} />
108
+ <ResourceTableToolbar resource={forClient} />
106
109
  <div className="flex flex-wrap items-center gap-2">
107
110
  <SaveViewButton resourceName={resource.name} label={resource.pluralLabel} />
108
111
  <ColumnMenu columns={allColumns.map(({ key, label }) => ({ key, label }))} visible={columns.map((column) => column.key)} />
109
112
  <ExportButton resourceName={resource.name} pluralLabel={resource.pluralLabel} />
110
- {permissions.create && <ImportDialog resource={resource} />}
113
+ {permissions.create && <ImportDialog resource={forClient} />}
111
114
  {permissions.create &&
112
115
  (modalForms ? (
113
- <NewRecordButton resource={resource} relations={relations} listHref={basePath} />
116
+ <NewRecordButton resource={forClient} relations={relations} listHref={basePath} />
114
117
  ) : (
115
118
  <Button asChild>
116
119
  <Link href={resourcePath(resource, "new")}>
@@ -281,7 +284,7 @@ export async function ResourceTable({ resource, searchParams }: { resource: Reso
281
284
  })}
282
285
  <TableCell className="text-right">
283
286
  <RowActions
284
- resource={resource}
287
+ resource={forClient}
285
288
  id={id}
286
289
  record={modalForms ? row : undefined}
287
290
  relations={relations}
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
5
5
  import { useState, useTransition } from "react";
6
6
  import { ExternalLinkIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from "lucide-react";
7
7
  import { toast } from "sonner";
8
- import type { Resource } from "@flaredev/core";
8
+ import type { ClientResource } from "@flaredev/core";
9
9
  import { deleteRecordAction } from "@/app/dashboard/actions";
10
10
  import {
11
11
  AlertDialog,
@@ -43,7 +43,7 @@ export function RowActions({
43
43
  canUpdate = true,
44
44
  canDelete = true,
45
45
  }: {
46
- resource: Resource;
46
+ resource: ClientResource;
47
47
  id: string;
48
48
  /** The record itself, when the form opens in a dialog. */
49
49
  record?: Record<string, unknown>;
@@ -4973,9 +4973,9 @@
4973
4973
  }
4974
4974
  },
4975
4975
  "node_modules/@vitejs/plugin-rsc/node_modules/magic-string": {
4976
- "version": "1.4.1",
4977
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz",
4978
- "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==",
4976
+ "version": "1.4.2",
4977
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz",
4978
+ "integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==",
4979
4979
  "devOptional": true,
4980
4980
  "license": "MIT",
4981
4981
  "dependencies": {
@@ -6253,9 +6253,9 @@
6253
6253
  }
6254
6254
  },
6255
6255
  "node_modules/electron-to-chromium": {
6256
- "version": "1.5.437",
6257
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.437.tgz",
6258
- "integrity": "sha512-1z60R0Nb+kYPnGUrQ6PvMjTVINDGMfqQZ/mI5FJIHvTotlA6noI+YJWakbjaFM+qhLXg9WnK3RUeYSO5ixSHJA==",
6256
+ "version": "1.5.438",
6257
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.438.tgz",
6258
+ "integrity": "sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==",
6259
6259
  "license": "ISC",
6260
6260
  "peer": true
6261
6261
  },
@@ -7124,9 +7124,9 @@
7124
7124
  "license": "MIT"
7125
7125
  },
7126
7126
  "node_modules/node-releases": {
7127
- "version": "2.0.56",
7128
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz",
7129
- "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==",
7127
+ "version": "2.0.57",
7128
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz",
7129
+ "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==",
7130
7130
  "license": "MIT",
7131
7131
  "peer": true,
7132
7132
  "engines": {
@@ -2640,8 +2640,8 @@ packages:
2640
2640
  sqlite3:
2641
2641
  optional: true
2642
2642
 
2643
- electron-to-chromium@1.5.437:
2644
- resolution: {integrity: sha512-1z60R0Nb+kYPnGUrQ6PvMjTVINDGMfqQZ/mI5FJIHvTotlA6noI+YJWakbjaFM+qhLXg9WnK3RUeYSO5ixSHJA==}
2643
+ electron-to-chromium@1.5.438:
2644
+ resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==}
2645
2645
 
2646
2646
  emoji-regex-xs@2.0.1:
2647
2647
  resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==}
@@ -2950,8 +2950,8 @@ packages:
2950
2950
  magic-string@0.30.21:
2951
2951
  resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
2952
2952
 
2953
- magic-string@1.4.1:
2954
- resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==}
2953
+ magic-string@1.4.2:
2954
+ resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==}
2955
2955
 
2956
2956
  merge-stream@2.0.0:
2957
2957
  resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -3039,8 +3039,8 @@ packages:
3039
3039
  neo-async@2.6.2:
3040
3040
  resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
3041
3041
 
3042
- node-releases@2.0.56:
3043
- resolution: {integrity: sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==}
3042
+ node-releases@2.0.57:
3043
+ resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==}
3044
3044
  engines: {node: '>=18'}
3045
3045
 
3046
3046
  obug@3.0.0:
@@ -5214,7 +5214,7 @@ snapshots:
5214
5214
  '@rolldown/pluginutils': 1.0.1
5215
5215
  es-module-lexer: 2.3.2
5216
5216
  estree-walker: 3.0.3
5217
- magic-string: 1.4.1
5217
+ magic-string: 1.4.2
5218
5218
  obug: 3.0.0
5219
5219
  react: 19.3.0
5220
5220
  react-dom: 19.3.0(react@19.3.0)
@@ -5388,8 +5388,8 @@ snapshots:
5388
5388
  dependencies:
5389
5389
  baseline-browser-mapping: 2.11.25
5390
5390
  caniuse-lite: 1.0.30001810
5391
- electron-to-chromium: 1.5.437
5392
- node-releases: 2.0.56
5391
+ electron-to-chromium: 1.5.438
5392
+ node-releases: 2.0.57
5393
5393
  update-browserslist-db: 1.3.3(browserslist@4.29.0)
5394
5394
 
5395
5395
  buffer-from@1.1.2: {}
@@ -5459,7 +5459,7 @@ snapshots:
5459
5459
  optionalDependencies:
5460
5460
  kysely: 0.29.6
5461
5461
 
5462
- electron-to-chromium@1.5.437: {}
5462
+ electron-to-chromium@1.5.438: {}
5463
5463
 
5464
5464
  emoji-regex-xs@2.0.1: {}
5465
5465
 
@@ -5780,7 +5780,7 @@ snapshots:
5780
5780
  dependencies:
5781
5781
  '@jridgewell/sourcemap-codec': 1.6.0
5782
5782
 
5783
- magic-string@1.4.1:
5783
+ magic-string@1.4.2:
5784
5784
  dependencies:
5785
5785
  '@jridgewell/sourcemap-codec': 1.6.0
5786
5786
 
@@ -5827,7 +5827,7 @@ snapshots:
5827
5827
 
5828
5828
  neo-async@2.6.2: {}
5829
5829
 
5830
- node-releases@2.0.56: {}
5830
+ node-releases@2.0.57: {}
5831
5831
 
5832
5832
  obug@3.0.0: {}
5833
5833
 
@@ -2879,9 +2879,9 @@ magic-string@^0.30.11, magic-string@^0.30.21:
2879
2879
  "@jridgewell/sourcemap-codec" "^1.5.5"
2880
2880
 
2881
2881
  magic-string@^1.2.3:
2882
- version "1.4.1"
2883
- resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.1.tgz#d0f91b82cd286a8a8de14f4254ccbf1d7e07f55e"
2884
- integrity sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==
2882
+ version "1.4.2"
2883
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.2.tgz#508aef4aac7319496dc074ec60ea511c49daf138"
2884
+ integrity sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==
2885
2885
  dependencies:
2886
2886
  "@jridgewell/sourcemap-codec" "^1.6.0"
2887
2887