litecms 0.1.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/LICENSE +21 -0
- package/README.md +1387 -0
- package/dist/admin/CmsAdminLayout.d.ts +27 -0
- package/dist/admin/CmsAdminLayout.d.ts.map +1 -0
- package/dist/admin/CmsAdminPage.d.ts +31 -0
- package/dist/admin/CmsAdminPage.d.ts.map +1 -0
- package/dist/admin/config.d.ts +83 -0
- package/dist/admin/config.d.ts.map +1 -0
- package/dist/admin/config.js +53 -0
- package/dist/admin/exports.d.ts +7 -0
- package/dist/admin/exports.d.ts.map +1 -0
- package/dist/admin/exports.js +452 -0
- package/dist/admin/index.d.ts +147 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/components/CmsAutoForm.d.ts +73 -0
- package/dist/components/CmsAutoForm.d.ts.map +1 -0
- package/dist/components/CmsField.d.ts +50 -0
- package/dist/components/CmsField.d.ts.map +1 -0
- package/dist/components/CmsForm.d.ts +74 -0
- package/dist/components/CmsForm.d.ts.map +1 -0
- package/dist/components/CmsImageField.d.ts +33 -0
- package/dist/components/CmsImageField.d.ts.map +1 -0
- package/dist/components/CmsNavSection.d.ts +7 -0
- package/dist/components/CmsNavSection.d.ts.map +1 -0
- package/dist/components/CmsSimpleForm.d.ts +54 -0
- package/dist/components/CmsSimpleForm.d.ts.map +1 -0
- package/dist/components/index.d.ts +43 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +619 -0
- package/dist/domain/index.d.ts +1 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/index-8zcd33mx.js +39 -0
- package/dist/index-pmb5m3ek.js +4135 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/schema/index.d.ts +80 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +46 -0
- package/dist/server/index.d.ts +79 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +117 -0
- package/dist/shared/utils.d.ts +23 -0
- package/dist/shared/utils.d.ts.map +1 -0
- package/dist/storage/index.d.ts +86 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +86 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/package.json +90 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AAEzB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CmsAutoForm,
|
|
3
|
+
CmsForm,
|
|
4
|
+
CmsFormError,
|
|
5
|
+
CmsFormSuccess,
|
|
6
|
+
CmsSubmitButton,
|
|
7
|
+
useCmsForm
|
|
8
|
+
} from "./components/index.js";
|
|
9
|
+
import {
|
|
10
|
+
CmsCheckbox,
|
|
11
|
+
CmsField,
|
|
12
|
+
CmsHiddenField,
|
|
13
|
+
CmsImageField
|
|
14
|
+
} from "./index-pmb5m3ek.js";
|
|
15
|
+
import {
|
|
16
|
+
defineSchema,
|
|
17
|
+
getEditableFields
|
|
18
|
+
} from "./schema/index.js";
|
|
19
|
+
export {
|
|
20
|
+
useCmsForm,
|
|
21
|
+
getEditableFields,
|
|
22
|
+
defineSchema,
|
|
23
|
+
CmsSubmitButton,
|
|
24
|
+
CmsImageField,
|
|
25
|
+
CmsHiddenField,
|
|
26
|
+
CmsFormSuccess,
|
|
27
|
+
CmsFormError,
|
|
28
|
+
CmsForm,
|
|
29
|
+
CmsField,
|
|
30
|
+
CmsCheckbox,
|
|
31
|
+
CmsAutoForm
|
|
32
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Field UI metadata for auto-generating forms
|
|
4
|
+
*/
|
|
5
|
+
export type FieldMeta = {
|
|
6
|
+
/** Display label for the field */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Whether the field is editable in the admin panel (default: true) */
|
|
9
|
+
editable?: boolean;
|
|
10
|
+
/** Input type override */
|
|
11
|
+
type?: 'text' | 'textarea' | 'number' | 'email' | 'url' | 'select' | 'checkbox' | 'image';
|
|
12
|
+
/** Placeholder text */
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
/** Help text shown below the input */
|
|
15
|
+
helpText?: string;
|
|
16
|
+
/** Options for select fields */
|
|
17
|
+
options?: Array<{
|
|
18
|
+
value: string;
|
|
19
|
+
label: string;
|
|
20
|
+
}>;
|
|
21
|
+
/** Number of rows for textarea (default: 3) */
|
|
22
|
+
rows?: number;
|
|
23
|
+
/** Order in the form (lower = higher) */
|
|
24
|
+
order?: number;
|
|
25
|
+
/** Group name for organizing fields */
|
|
26
|
+
group?: string;
|
|
27
|
+
/** Accepted file types for image fields (default: 'image/*') */
|
|
28
|
+
accept?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Schema definition with field metadata
|
|
32
|
+
*/
|
|
33
|
+
export type SchemaDefinition<T extends z.ZodRawShape> = {
|
|
34
|
+
/** The Zod schema */
|
|
35
|
+
schema: z.ZodObject<T>;
|
|
36
|
+
/** Field metadata keyed by field name */
|
|
37
|
+
fields: {
|
|
38
|
+
[K in keyof T]?: FieldMeta;
|
|
39
|
+
};
|
|
40
|
+
/** Default values */
|
|
41
|
+
defaults: z.infer<z.ZodObject<T>>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Define a CMS schema with field metadata for auto-generating forms.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const HomePageDef = defineSchema({
|
|
49
|
+
* schema: z.object({
|
|
50
|
+
* heroTitle: z.string().min(1),
|
|
51
|
+
* heroSubtitle: z.string().optional(),
|
|
52
|
+
* internalNote: z.string().optional(),
|
|
53
|
+
* }),
|
|
54
|
+
* fields: {
|
|
55
|
+
* heroTitle: { label: 'Hero Title', placeholder: 'Welcome...' },
|
|
56
|
+
* heroSubtitle: { label: 'Subtitle', type: 'textarea', rows: 3 },
|
|
57
|
+
* internalNote: { label: 'Internal Note', editable: false },
|
|
58
|
+
* },
|
|
59
|
+
* defaults: {
|
|
60
|
+
* heroTitle: 'Welcome',
|
|
61
|
+
* heroSubtitle: '',
|
|
62
|
+
* internalNote: '',
|
|
63
|
+
* },
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function defineSchema<T extends z.ZodRawShape>(definition: SchemaDefinition<T>): SchemaDefinition<T>;
|
|
68
|
+
/**
|
|
69
|
+
* Field info extracted from schema
|
|
70
|
+
*/
|
|
71
|
+
export type FieldInfo = {
|
|
72
|
+
name: string;
|
|
73
|
+
meta: FieldMeta;
|
|
74
|
+
required: boolean;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Get editable fields from a schema definition
|
|
78
|
+
*/
|
|
79
|
+
export declare function getEditableFields<T extends z.ZodRawShape>(definition: SchemaDefinition<T>): FieldInfo[];
|
|
80
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IAC1F,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,IAAI;IACpD,qBAAqB;IACrB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACvB,yCAAyC;IACzC,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS;KAAE,CAAC;IACvC,qBAAqB;IACrB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CACrC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAChD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAChC,gBAAgB,CAAC,CAAC,CAAC,CAErB;AAED;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EACrD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAChC,SAAS,EAAE,CA+Bb"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/schema/index.ts
|
|
2
|
+
function defineSchema(definition) {
|
|
3
|
+
return definition;
|
|
4
|
+
}
|
|
5
|
+
function getEditableFields(definition) {
|
|
6
|
+
const shape = definition.schema.shape;
|
|
7
|
+
const fields = [];
|
|
8
|
+
for (const name of Object.keys(shape)) {
|
|
9
|
+
const meta = definition.fields[name];
|
|
10
|
+
if (meta?.editable === false)
|
|
11
|
+
continue;
|
|
12
|
+
const fieldMeta = {
|
|
13
|
+
label: meta?.label ?? formatLabel(name),
|
|
14
|
+
editable: meta?.editable ?? true,
|
|
15
|
+
type: meta?.type ?? "text",
|
|
16
|
+
placeholder: meta?.placeholder,
|
|
17
|
+
helpText: meta?.helpText,
|
|
18
|
+
options: meta?.options,
|
|
19
|
+
rows: meta?.rows,
|
|
20
|
+
order: meta?.order ?? 999,
|
|
21
|
+
group: meta?.group
|
|
22
|
+
};
|
|
23
|
+
const isRequired = !isOptionalField(shape[name]);
|
|
24
|
+
fields.push({
|
|
25
|
+
name,
|
|
26
|
+
meta: fieldMeta,
|
|
27
|
+
required: isRequired
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return fields.sort((a, b) => (a.meta.order ?? 999) - (b.meta.order ?? 999));
|
|
31
|
+
}
|
|
32
|
+
function isOptionalField(zodType) {
|
|
33
|
+
const typeDef = zodType;
|
|
34
|
+
const typeName = typeDef?._def?.typeName;
|
|
35
|
+
if (typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function formatLabel(name) {
|
|
41
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
getEditableFields,
|
|
45
|
+
defineSchema
|
|
46
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Action state returned from server actions
|
|
4
|
+
*/
|
|
5
|
+
export type ActionState<T = unknown> = {
|
|
6
|
+
success: boolean;
|
|
7
|
+
data?: T;
|
|
8
|
+
errors?: {
|
|
9
|
+
fieldErrors?: Record<string, string[]>;
|
|
10
|
+
formError?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Options for createSaveAction
|
|
15
|
+
*/
|
|
16
|
+
export type ActionOptions<TData> = {
|
|
17
|
+
/** Callback to save data (e.g., Drizzle upsert) */
|
|
18
|
+
save: (data: TData) => Promise<void>;
|
|
19
|
+
/** Path to revalidate after successful save (passed to onRevalidate) */
|
|
20
|
+
revalidatePath?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Callback to revalidate the path after successful save.
|
|
23
|
+
* Import revalidatePath from next/cache and pass it here.
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { revalidatePath } from 'next/cache';
|
|
27
|
+
* createSaveAction(schema, { onRevalidate: revalidatePath, revalidatePath: '/' });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
onRevalidate?: (path: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Optional authentication check. Return true if authorized, false otherwise.
|
|
33
|
+
* If not provided, no auth check is performed.
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* createSaveAction(schema, {
|
|
37
|
+
* checkAuth: async () => {
|
|
38
|
+
* const session = await getSession();
|
|
39
|
+
* return !!session;
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
checkAuth?: () => Promise<boolean>;
|
|
45
|
+
};
|
|
46
|
+
export declare const isDevelopmentEnv: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a type-safe server action for form submission.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { revalidatePath } from 'next/cache';
|
|
53
|
+
*
|
|
54
|
+
* const saveHome = createSaveAction(HomePageSchema, {
|
|
55
|
+
* save: async (data) => {
|
|
56
|
+
* await db.upsert(cmsDocuments).values({ key: "home", data });
|
|
57
|
+
* },
|
|
58
|
+
* revalidatePath: "/",
|
|
59
|
+
* onRevalidate: revalidatePath,
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createSaveAction<TSchema extends z.ZodType>(schema: TSchema, options: ActionOptions<z.infer<TSchema>>): (_prevState: ActionState<z.infer<TSchema>>, formData: FormData) => Promise<ActionState<z.infer<TSchema>>>;
|
|
64
|
+
/**
|
|
65
|
+
* Parse FormData into a plain object with type coercion.
|
|
66
|
+
* Handles:
|
|
67
|
+
* - Empty strings as undefined (for optional fields)
|
|
68
|
+
* - Number coercion for fields ending with _number or containing numeric values
|
|
69
|
+
* - Boolean coercion for checkbox fields
|
|
70
|
+
* - Nested objects using dot notation (e.g., "address.city")
|
|
71
|
+
* - Arrays using bracket notation (e.g., "tags[0]", "tags[1]")
|
|
72
|
+
* - Arrays of objects (e.g., "navLinks[0].label", "navLinks[0].href")
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseFormDataToObject(formData: FormData): Record<string, unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* Helper to parse FormData with a specific schema
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseFormData<T extends z.ZodType>(schema: T, formData: FormData): z.infer<T> | null;
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE;QACL,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACvC,SAAS,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACL,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,KAAK,IAAI;IAC/B,mDAAmD;IACnD,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,eAAO,MAAM,gBAAgB,SAIY,CAAC;AAE1C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,CAAC,CAAC,OAAO,EACtD,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAGpC,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EACzC,UAAU,QAAQ,KACnB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAmE5C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,QAAQ,GACnB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyCzB;AAmDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAC7C,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,QAAQ,GACnB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAInB"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// src/server/index.ts
|
|
2
|
+
var isDevelopmentEnv = typeof globalThis !== "undefined" && "process" in globalThis && globalThis.process?.env?.NODE_ENV === "development";
|
|
3
|
+
function createSaveAction(schema, options) {
|
|
4
|
+
return async (_prevState, formData) => {
|
|
5
|
+
try {
|
|
6
|
+
if (options.checkAuth) {
|
|
7
|
+
const isAuthorized = await options.checkAuth();
|
|
8
|
+
if (!isAuthorized) {
|
|
9
|
+
return {
|
|
10
|
+
success: false,
|
|
11
|
+
errors: { formError: "Unauthorized" }
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const rawData = parseFormDataToObject(formData);
|
|
16
|
+
if (isDevelopmentEnv) {
|
|
17
|
+
console.log("[litecms] Parsed form data:", JSON.stringify(rawData, null, 2));
|
|
18
|
+
}
|
|
19
|
+
const result = schema.safeParse(rawData);
|
|
20
|
+
if (!result.success) {
|
|
21
|
+
const fieldErrors = {};
|
|
22
|
+
for (const issue of result.error.issues) {
|
|
23
|
+
const path = issue.path.join(".");
|
|
24
|
+
if (!fieldErrors[path]) {
|
|
25
|
+
fieldErrors[path] = [];
|
|
26
|
+
}
|
|
27
|
+
fieldErrors[path].push(issue.message);
|
|
28
|
+
}
|
|
29
|
+
if (isDevelopmentEnv) {
|
|
30
|
+
console.log("[litecms] Validation failed:", result.error.issues);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
errors: {
|
|
35
|
+
fieldErrors,
|
|
36
|
+
formError: `Validation failed: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
await options.save(result.data);
|
|
41
|
+
if (options.revalidatePath && options.onRevalidate) {
|
|
42
|
+
options.onRevalidate(options.revalidatePath);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
data: result.data
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("[litecms] Save action failed:", error);
|
|
50
|
+
const message = error instanceof Error ? error.message : "An unexpected error occurred";
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
errors: { formError: `Save failed: ${message}` }
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function parseFormDataToObject(formData) {
|
|
59
|
+
const result = {};
|
|
60
|
+
for (const [key, value] of formData.entries()) {
|
|
61
|
+
if (value instanceof File)
|
|
62
|
+
continue;
|
|
63
|
+
const stringValue = value.toString();
|
|
64
|
+
let parsedValue = stringValue;
|
|
65
|
+
if (stringValue === "") {
|
|
66
|
+
parsedValue = undefined;
|
|
67
|
+
} else if (stringValue === "true" || stringValue === "on") {
|
|
68
|
+
parsedValue = true;
|
|
69
|
+
} else if (stringValue === "false") {
|
|
70
|
+
parsedValue = false;
|
|
71
|
+
} else if (key.endsWith("_number") || /^-?\d+(\.\d+)?$/.test(stringValue) && !key.toLowerCase().includes("href") && !key.toLowerCase().includes("url") && !key.toLowerCase().includes("link") && !key.toLowerCase().includes("value") && !key.toLowerCase().includes("label") && !key.toLowerCase().includes("title") && !key.toLowerCase().includes("name") && !key.toLowerCase().includes("text")) {
|
|
72
|
+
const num = Number(stringValue);
|
|
73
|
+
if (!isNaN(num)) {
|
|
74
|
+
parsedValue = num;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
setValueByPath(result, key, parsedValue);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
function setValueByPath(obj, path, value) {
|
|
82
|
+
const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
83
|
+
let current = obj;
|
|
84
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
85
|
+
const segment = segments[i];
|
|
86
|
+
const nextSegment = segments[i + 1];
|
|
87
|
+
const isNextArray = /^\d+$/.test(nextSegment);
|
|
88
|
+
if (!(segment in current)) {
|
|
89
|
+
current[segment] = isNextArray ? [] : {};
|
|
90
|
+
}
|
|
91
|
+
const currentValue = current[segment];
|
|
92
|
+
if (isNextArray && !Array.isArray(currentValue)) {
|
|
93
|
+
current[segment] = [];
|
|
94
|
+
} else if (!isNextArray && (Array.isArray(currentValue) || typeof currentValue !== "object")) {
|
|
95
|
+
current[segment] = {};
|
|
96
|
+
}
|
|
97
|
+
current = current[segment];
|
|
98
|
+
}
|
|
99
|
+
const lastSegment = segments[segments.length - 1];
|
|
100
|
+
if (/^\d+$/.test(lastSegment)) {
|
|
101
|
+
const index = parseInt(lastSegment, 10);
|
|
102
|
+
current[index] = value;
|
|
103
|
+
} else {
|
|
104
|
+
current[lastSegment] = value;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function parseFormData(schema, formData) {
|
|
108
|
+
const rawData = parseFormDataToObject(formData);
|
|
109
|
+
const result = schema.safeParse(rawData);
|
|
110
|
+
return result.success ? result.data : null;
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
parseFormDataToObject,
|
|
114
|
+
parseFormData,
|
|
115
|
+
isDevelopmentEnv,
|
|
116
|
+
createSaveAction
|
|
117
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PageGroup } from '../admin';
|
|
2
|
+
import type { FieldGroupData } from '../components';
|
|
3
|
+
import type { FieldInfo } from '../schema';
|
|
4
|
+
import { type ClassValue } from 'clsx';
|
|
5
|
+
export declare function cn(...inputs: ClassValue[]): string;
|
|
6
|
+
export declare function groupPages(pages: Array<{
|
|
7
|
+
slug: string;
|
|
8
|
+
title: string;
|
|
9
|
+
group?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
}>): PageGroup[];
|
|
12
|
+
/**
|
|
13
|
+
* Get nested error from react-hook-form errors object
|
|
14
|
+
*/
|
|
15
|
+
export declare function getNestedError(errors: Record<string, unknown>, path: string): {
|
|
16
|
+
message?: string;
|
|
17
|
+
} | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Flatten a value into form field entries using bracket/dot notation.
|
|
20
|
+
*/
|
|
21
|
+
export declare function flattenValue(key: string, value: unknown, result?: Array<[string, string]>): Array<[string, string]>;
|
|
22
|
+
export declare function groupFields(fields: FieldInfo[]): FieldGroupData[];
|
|
23
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/shared/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED,wBAAgB,UAAU,CACtB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7E,SAAS,EAAE,CAoBb;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,GACb;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAalC;AAED;;GAEG;AACH,wBAAgB,YAAY,CACxB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,EACd,MAAM,GAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GACrC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBzB;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,cAAc,EAAE,CAgBjE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for storage client
|
|
4
|
+
*/
|
|
5
|
+
export type StorageConfig = {
|
|
6
|
+
/** S3-compatible endpoint URL (e.g., Minio endpoint) */
|
|
7
|
+
endpoint: string;
|
|
8
|
+
/** AWS region (defaults to 'us-east-1') */
|
|
9
|
+
region?: string;
|
|
10
|
+
/** Access key ID */
|
|
11
|
+
accessKeyId: string;
|
|
12
|
+
/** Secret access key */
|
|
13
|
+
secretAccessKey: string;
|
|
14
|
+
/** Bucket name */
|
|
15
|
+
bucket: string;
|
|
16
|
+
/** Use path-style URLs (required for Minio, defaults to true) */
|
|
17
|
+
forcePathStyle?: boolean;
|
|
18
|
+
/** Base URL path for serving files (defaults to '/api/storage/images') */
|
|
19
|
+
publicUrlBase?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* File info returned from list operations
|
|
23
|
+
*/
|
|
24
|
+
export type FileInfo = {
|
|
25
|
+
key: string;
|
|
26
|
+
url: string;
|
|
27
|
+
lastModified?: Date;
|
|
28
|
+
size?: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Upload result
|
|
32
|
+
*/
|
|
33
|
+
export type UploadResult = {
|
|
34
|
+
url: string;
|
|
35
|
+
key: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Storage client instance with all operations
|
|
39
|
+
*/
|
|
40
|
+
export type StorageClient = {
|
|
41
|
+
/** Upload a file to storage */
|
|
42
|
+
uploadFile: (file: File, path?: string) => Promise<UploadResult>;
|
|
43
|
+
/** List files in storage */
|
|
44
|
+
listFiles: (prefix?: string) => Promise<FileInfo[]>;
|
|
45
|
+
/** Delete a file from storage */
|
|
46
|
+
deleteFile: (key: string) => Promise<void>;
|
|
47
|
+
/** Get file content as buffer */
|
|
48
|
+
getFile: (key: string) => Promise<{
|
|
49
|
+
buffer: ArrayBuffer;
|
|
50
|
+
contentType: string | undefined;
|
|
51
|
+
}>;
|
|
52
|
+
/** The underlying S3 client */
|
|
53
|
+
s3Client: S3Client;
|
|
54
|
+
/** The bucket name */
|
|
55
|
+
bucket: string;
|
|
56
|
+
/** The public URL base */
|
|
57
|
+
publicUrlBase: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Create a storage client with the given configuration.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* // In your app's lib/storage.ts
|
|
65
|
+
* import { createStorageClient } from 'litecms/storage';
|
|
66
|
+
*
|
|
67
|
+
* export const storage = createStorageClient({
|
|
68
|
+
* endpoint: process.env.STORAGE_URL!,
|
|
69
|
+
* accessKeyId: process.env.STORAGE_ACCESS_KEY!,
|
|
70
|
+
* secretAccessKey: process.env.STORAGE_SECRET_KEY!,
|
|
71
|
+
* bucket: process.env.STORAGE_BUCKET || 'mybucket',
|
|
72
|
+
* publicUrlBase: '/api/storage/images',
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // Then use it
|
|
76
|
+
* const { url, key } = await storage.uploadFile(file);
|
|
77
|
+
* const files = await storage.listFiles('images/');
|
|
78
|
+
* await storage.deleteFile(key);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createStorageClient(config: StorageConfig): StorageClient;
|
|
82
|
+
/**
|
|
83
|
+
* Re-export S3 client and commands for advanced usage
|
|
84
|
+
*/
|
|
85
|
+
export { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, GetObjectCommand };
|
|
86
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE7H;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IACxB,+BAA+B;IAC/B,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACjE,4BAA4B;IAC5B,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACpD,iCAAiC;IACjC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,iCAAiC;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,WAAW,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAC5F,+BAA+B;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAyGxE;AAED;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// src/storage/index.ts
|
|
2
|
+
import { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
|
|
3
|
+
function createStorageClient(config) {
|
|
4
|
+
const {
|
|
5
|
+
endpoint,
|
|
6
|
+
region = "us-east-1",
|
|
7
|
+
accessKeyId,
|
|
8
|
+
secretAccessKey,
|
|
9
|
+
bucket,
|
|
10
|
+
forcePathStyle = true,
|
|
11
|
+
publicUrlBase = "/api/storage/images"
|
|
12
|
+
} = config;
|
|
13
|
+
const s3Client = new S3Client({
|
|
14
|
+
endpoint,
|
|
15
|
+
region,
|
|
16
|
+
credentials: {
|
|
17
|
+
accessKeyId,
|
|
18
|
+
secretAccessKey
|
|
19
|
+
},
|
|
20
|
+
forcePathStyle
|
|
21
|
+
});
|
|
22
|
+
async function uploadFile(file, path) {
|
|
23
|
+
const buffer = new Uint8Array(await file.arrayBuffer());
|
|
24
|
+
const timestamp = Date.now();
|
|
25
|
+
const sanitizedName = file.name.replace(/[^a-zA-Z0-9.-]/g, "_");
|
|
26
|
+
const key = path || `images/${timestamp}-${sanitizedName}`;
|
|
27
|
+
const command = new PutObjectCommand({
|
|
28
|
+
Bucket: bucket,
|
|
29
|
+
Key: key,
|
|
30
|
+
Body: buffer,
|
|
31
|
+
ContentType: file.type
|
|
32
|
+
});
|
|
33
|
+
await s3Client.send(command);
|
|
34
|
+
const url = `${publicUrlBase}/${key}`;
|
|
35
|
+
return { url, key };
|
|
36
|
+
}
|
|
37
|
+
async function listFiles(prefix = "images/") {
|
|
38
|
+
const command = new ListObjectsV2Command({
|
|
39
|
+
Bucket: bucket,
|
|
40
|
+
Prefix: prefix
|
|
41
|
+
});
|
|
42
|
+
const response = await s3Client.send(command);
|
|
43
|
+
return (response.Contents || []).map((item) => ({
|
|
44
|
+
key: item.Key,
|
|
45
|
+
url: `${publicUrlBase}/${item.Key}`,
|
|
46
|
+
lastModified: item.LastModified,
|
|
47
|
+
size: item.Size
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
async function deleteFile(key) {
|
|
51
|
+
const command = new DeleteObjectCommand({
|
|
52
|
+
Bucket: bucket,
|
|
53
|
+
Key: key
|
|
54
|
+
});
|
|
55
|
+
await s3Client.send(command);
|
|
56
|
+
}
|
|
57
|
+
async function getFile(key) {
|
|
58
|
+
const command = new GetObjectCommand({
|
|
59
|
+
Bucket: bucket,
|
|
60
|
+
Key: key
|
|
61
|
+
});
|
|
62
|
+
const response = await s3Client.send(command);
|
|
63
|
+
const buffer = new Uint8Array(await response.Body.transformToByteArray());
|
|
64
|
+
return {
|
|
65
|
+
buffer: buffer.buffer,
|
|
66
|
+
contentType: response.ContentType
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
uploadFile,
|
|
71
|
+
listFiles,
|
|
72
|
+
deleteFile,
|
|
73
|
+
getFile,
|
|
74
|
+
s3Client,
|
|
75
|
+
bucket,
|
|
76
|
+
publicUrlBase
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
createStorageClient,
|
|
81
|
+
S3Client,
|
|
82
|
+
PutObjectCommand,
|
|
83
|
+
ListObjectsV2Command,
|
|
84
|
+
GetObjectCommand,
|
|
85
|
+
DeleteObjectCommand
|
|
86
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stores/index.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "litecms",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Timo Weiss",
|
|
7
|
+
"description": "A super lightweight CMS for Next.js",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"cms",
|
|
10
|
+
"nextjs",
|
|
11
|
+
"react",
|
|
12
|
+
"admin",
|
|
13
|
+
"zod",
|
|
14
|
+
"forms",
|
|
15
|
+
"typescript"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/exddc/litecms"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/exddc/litecms",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/exddc/litecms/issues"
|
|
24
|
+
},
|
|
25
|
+
"files": ["dist"],
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./server": {
|
|
34
|
+
"types": "./dist/server/index.d.ts",
|
|
35
|
+
"default": "./dist/server/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./components": {
|
|
38
|
+
"types": "./dist/components/index.d.ts",
|
|
39
|
+
"default": "./dist/components/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./schema": {
|
|
42
|
+
"types": "./dist/schema/index.d.ts",
|
|
43
|
+
"default": "./dist/schema/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./admin": {
|
|
46
|
+
"types": "./dist/admin/exports.d.ts",
|
|
47
|
+
"default": "./dist/admin/exports.js"
|
|
48
|
+
},
|
|
49
|
+
"./admin/config": {
|
|
50
|
+
"types": "./dist/admin/config.d.ts",
|
|
51
|
+
"default": "./dist/admin/config.js"
|
|
52
|
+
},
|
|
53
|
+
"./storage": {
|
|
54
|
+
"types": "./dist/storage/index.d.ts",
|
|
55
|
+
"default": "./dist/storage/index.js"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": ">=18",
|
|
60
|
+
"react-dom": ">=18",
|
|
61
|
+
"next": ">=14",
|
|
62
|
+
"@aws-sdk/client-s3": ">=3"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"@aws-sdk/client-s3": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@hookform/resolvers": "^5.2.2",
|
|
71
|
+
"clsx": "^2.1.1",
|
|
72
|
+
"react-hook-form": "^7.71.0",
|
|
73
|
+
"tailwind-merge": "^3.4.0",
|
|
74
|
+
"zod": "^4.3.5"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@types/node": "^20",
|
|
78
|
+
"@types/react": "^18",
|
|
79
|
+
"@types/react-dom": "^18",
|
|
80
|
+
"typescript": "^5"
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "bun run build:js && bun run build:types && bun run build:fix",
|
|
84
|
+
"build:js": "bun build src/index.ts src/server/index.ts src/components/index.ts src/schema/index.ts src/admin/exports.ts src/admin/config.ts src/storage/index.ts --outdir dist --target node --format esm --external react --external react-dom --external next --external @aws-sdk/client-s3 --splitting",
|
|
85
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
86
|
+
"build:fix": "node scripts/fix-build.cjs",
|
|
87
|
+
"dev": "bun run build --watch",
|
|
88
|
+
"test": "bun test"
|
|
89
|
+
}
|
|
90
|
+
}
|