@davincicoding/payload-plugin-kit 0.0.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.
- package/.swcrc +18 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -0
- package/dist/fields.d.ts +7 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +55 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/procedure.d.ts +34 -0
- package/dist/procedure.d.ts.map +1 -0
- package/dist/procedure.js +111 -0
- package/dist/procedure.js.map +1 -0
- package/package.json +61 -0
- package/scripts/build.js +141 -0
- package/scripts/generate-types.js +171 -0
- package/tsconfig.base.json +22 -0
package/.swcrc
ADDED
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload';
|
|
2
|
+
export declare const createCollectionConfigFactory: <T extends Record<string, unknown>>(factory: Omit<CollectionConfig, "slug"> | ((options: T & {
|
|
3
|
+
slug: string;
|
|
4
|
+
}) => Omit<CollectionConfig, "slug">)) => (options: T & {
|
|
5
|
+
slug: string;
|
|
6
|
+
}) => CollectionConfig;
|
|
7
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,eAAO,MAAM,6BAA6B,GACvC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WAE5B,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,GAC9B,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC,eAE/D,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,KAAG,gBAG/B,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload';\n\nexport const createCollectionConfigFactory =\n <T extends Record<string, unknown>>(\n factory:\n | Omit<CollectionConfig, 'slug'>\n | ((options: T & { slug: string }) => Omit<CollectionConfig, 'slug'>),\n ) =>\n (options: T & { slug: string }): CollectionConfig => ({\n slug: options.slug,\n ...(typeof factory === 'function' ? factory(options) : factory),\n });\n"],"names":["createCollectionConfigFactory","factory","options","slug"],"mappings":"AAEA,OAAO,MAAMA,gCACX,CACEC,UAIF,CAACC,UAAqD,CAAA;YACpDC,MAAMD,QAAQC,IAAI;YAClB,GAAI,OAAOF,YAAY,aAAaA,QAAQC,WAAWD,OAAO;QAChE,CAAA,EAAG"}
|
package/dist/fields.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Field } from 'payload';
|
|
2
|
+
export type FieldWithPath<T extends Field> = T & {
|
|
3
|
+
path: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare function findFields<T extends Field>(fields: Field[], condition: (field: Field) => field is T, path?: string[]): FieldWithPath<T>[];
|
|
6
|
+
export declare function findFields(fields: Field[], condition: (field: Field) => boolean, path?: string[]): FieldWithPath<Field>[];
|
|
7
|
+
//# sourceMappingURL=fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,GAAG;IAC/C,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,wBAAgB,UAAU,CAAC,CAAC,SAAS,KAAK,EACxC,MAAM,EAAE,KAAK,EAAE,EACf,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,IAAI,CAAC,EACvC,IAAI,CAAC,EAAE,MAAM,EAAE,GACd,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;AACtB,wBAAgB,UAAU,CACxB,MAAM,EAAE,KAAK,EAAE,EACf,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,EACpC,IAAI,CAAC,EAAE,MAAM,EAAE,GACd,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC"}
|
package/dist/fields.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { uncaughtSwitchCase } from '@davincicoding/payload-utils';
|
|
2
|
+
export function findFields(fields, condition, path = []) {
|
|
3
|
+
return fields.flatMap((field)=>{
|
|
4
|
+
if (condition(field)) {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
...field,
|
|
8
|
+
path: 'name' in field ? [
|
|
9
|
+
...path,
|
|
10
|
+
field.name
|
|
11
|
+
] : path
|
|
12
|
+
}
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
if ('fields' in field) {
|
|
16
|
+
return findFields(field.fields, condition, 'name' in field ? [
|
|
17
|
+
...path,
|
|
18
|
+
field.name
|
|
19
|
+
] : path);
|
|
20
|
+
}
|
|
21
|
+
switch(field.type){
|
|
22
|
+
case 'blocks':
|
|
23
|
+
return field.blocks.flatMap((block)=>findFields(block.fields, condition, [
|
|
24
|
+
...path,
|
|
25
|
+
field.name
|
|
26
|
+
]));
|
|
27
|
+
case 'tabs':
|
|
28
|
+
return field.tabs.flatMap((tab)=>findFields(tab.fields, condition, 'name' in tab ? [
|
|
29
|
+
...path,
|
|
30
|
+
tab.name
|
|
31
|
+
] : path));
|
|
32
|
+
case 'text':
|
|
33
|
+
case 'richText':
|
|
34
|
+
case 'number':
|
|
35
|
+
case 'checkbox':
|
|
36
|
+
case 'date':
|
|
37
|
+
case 'email':
|
|
38
|
+
case 'select':
|
|
39
|
+
case 'json':
|
|
40
|
+
case 'code':
|
|
41
|
+
case 'join':
|
|
42
|
+
case 'point':
|
|
43
|
+
case 'radio':
|
|
44
|
+
case 'textarea':
|
|
45
|
+
case 'ui':
|
|
46
|
+
case 'relationship':
|
|
47
|
+
case 'upload':
|
|
48
|
+
return [];
|
|
49
|
+
default:
|
|
50
|
+
return uncaughtSwitchCase(field);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fields.ts"],"sourcesContent":["import { uncaughtSwitchCase } from '@davincicoding/payload-utils';\nimport type { Field } from 'payload';\n\nexport type FieldWithPath<T extends Field> = T & {\n path: string[];\n};\n\nexport function findFields<T extends Field>(\n fields: Field[],\n condition: (field: Field) => field is T,\n path?: string[],\n): FieldWithPath<T>[];\nexport function findFields(\n fields: Field[],\n condition: (field: Field) => boolean,\n path?: string[],\n): FieldWithPath<Field>[];\nexport function findFields(\n fields: Field[],\n condition: (field: Field) => boolean,\n path: string[] = [],\n): FieldWithPath<Field>[] {\n return fields.flatMap((field) => {\n if (condition(field)) {\n return [\n { ...field, path: 'name' in field ? [...path, field.name] : path },\n ];\n }\n\n if ('fields' in field) {\n return findFields(\n field.fields,\n condition,\n 'name' in field ? [...path, field.name] : path,\n );\n }\n\n switch (field.type) {\n case 'blocks':\n return field.blocks.flatMap((block) =>\n findFields(block.fields, condition, [...path, field.name]),\n );\n case 'tabs':\n return field.tabs.flatMap((tab) =>\n findFields(\n tab.fields,\n condition,\n 'name' in tab ? [...path, tab.name] : path,\n ),\n );\n case 'text':\n case 'richText':\n case 'number':\n case 'checkbox':\n case 'date':\n case 'email':\n case 'select':\n case 'json':\n case 'code':\n case 'join':\n case 'point':\n case 'radio':\n case 'textarea':\n case 'ui':\n case 'relationship':\n case 'upload':\n return [];\n default:\n return uncaughtSwitchCase(field);\n }\n });\n}\n"],"names":["uncaughtSwitchCase","findFields","fields","condition","path","flatMap","field","name","type","blocks","block","tabs","tab"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,+BAA+B;AAiBlE,OAAO,SAASC,WACdC,MAAe,EACfC,SAAoC,EACpCC,OAAiB,EAAE;IAEnB,OAAOF,OAAOG,OAAO,CAAC,CAACC;QACrB,IAAIH,UAAUG,QAAQ;YACpB,OAAO;gBACL;oBAAE,GAAGA,KAAK;oBAAEF,MAAM,UAAUE,QAAQ;2BAAIF;wBAAME,MAAMC,IAAI;qBAAC,GAAGH;gBAAK;aAClE;QACH;QAEA,IAAI,YAAYE,OAAO;YACrB,OAAOL,WACLK,MAAMJ,MAAM,EACZC,WACA,UAAUG,QAAQ;mBAAIF;gBAAME,MAAMC,IAAI;aAAC,GAAGH;QAE9C;QAEA,OAAQE,MAAME,IAAI;YAChB,KAAK;gBACH,OAAOF,MAAMG,MAAM,CAACJ,OAAO,CAAC,CAACK,QAC3BT,WAAWS,MAAMR,MAAM,EAAEC,WAAW;2BAAIC;wBAAME,MAAMC,IAAI;qBAAC;YAE7D,KAAK;gBACH,OAAOD,MAAMK,IAAI,CAACN,OAAO,CAAC,CAACO,MACzBX,WACEW,IAAIV,MAAM,EACVC,WACA,UAAUS,MAAM;2BAAIR;wBAAMQ,IAAIL,IAAI;qBAAC,GAAGH;YAG5C,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;YACL,KAAK;gBACH,OAAO,EAAE;YACX;gBACE,OAAOJ,mBAAmBM;QAC9B;IACF;AACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from '@davincicoding/payload-utils';
|
|
2
|
+
export { createCollectionConfigFactory } from './config';
|
|
3
|
+
export { type FieldWithPath, findFields } from './fields';
|
|
4
|
+
export { defineProcedure, type Procedure, type ProcedureBuilder, } from './procedure';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,8BAA8B,CAAC;AAC7C,OAAO,EAAE,6BAA6B,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,KAAK,aAAa,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EACL,eAAe,EACf,KAAK,SAAS,EACd,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Re-export everything from payload-utils
|
|
2
|
+
export * from '@davincicoding/payload-utils';
|
|
3
|
+
export { createCollectionConfigFactory } from './config';
|
|
4
|
+
export { findFields } from './fields';
|
|
5
|
+
// Plugin-kit specific exports
|
|
6
|
+
export { defineProcedure } from './procedure';
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// Re-export everything from payload-utils\nexport * from '@davincicoding/payload-utils';\nexport { createCollectionConfigFactory } from './config';\nexport { type FieldWithPath, findFields } from './fields';\n// Plugin-kit specific exports\nexport {\n defineProcedure,\n type Procedure,\n type ProcedureBuilder,\n} from './procedure';\n"],"names":["createCollectionConfigFactory","findFields","defineProcedure"],"mappings":"AAAA,0CAA0C;AAC1C,cAAc,+BAA+B;AAC7C,SAASA,6BAA6B,QAAQ,WAAW;AACzD,SAA6BC,UAAU,QAAQ,WAAW;AAC1D,8BAA8B;AAC9B,SACEC,eAAe,QAGV,cAAc"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Endpoint, PayloadRequest } from 'payload';
|
|
2
|
+
type Method = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
3
|
+
/** Any Zod-like schema with safeParse and inferred output */
|
|
4
|
+
interface ZodLike<TOutput = unknown> {
|
|
5
|
+
safeParse(data: unknown): {
|
|
6
|
+
success: true;
|
|
7
|
+
data: TOutput;
|
|
8
|
+
} | {
|
|
9
|
+
success: false;
|
|
10
|
+
error: unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
type InferOutput<T> = T extends ZodLike<infer O> ? O : never;
|
|
14
|
+
interface ProcedureConfig<TSchema extends ZodLike | undefined = undefined> {
|
|
15
|
+
path: `/${string}`;
|
|
16
|
+
method: Method;
|
|
17
|
+
input?: TSchema;
|
|
18
|
+
}
|
|
19
|
+
export interface Procedure<TInput, TOutput> {
|
|
20
|
+
path: `/${string}`;
|
|
21
|
+
method: Method;
|
|
22
|
+
endpoint(handler: (req: PayloadRequest, ...args: TInput extends void ? [] : [input: TInput]) => Promise<unknown | Response>): Endpoint;
|
|
23
|
+
call(apiUrl: string, ...args: TInput extends void ? [] : [input: TInput]): Promise<TOutput>;
|
|
24
|
+
}
|
|
25
|
+
export interface ProcedureBuilder<TInput> {
|
|
26
|
+
path: string;
|
|
27
|
+
method: Method;
|
|
28
|
+
returns<TOutput>(): Procedure<TInput, TOutput>;
|
|
29
|
+
endpoint(handler: (req: PayloadRequest, ...args: TInput extends void ? [] : [input: TInput]) => Promise<unknown | Response>): Endpoint;
|
|
30
|
+
call(apiUrl: string, ...args: TInput extends void ? [] : [input: TInput]): Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
export declare function defineProcedure<TSchema extends ZodLike | undefined = undefined>(config: ProcedureConfig<TSchema>): ProcedureBuilder<TSchema extends ZodLike ? InferOutput<TSchema> : void>;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=procedure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedure.d.ts","sourceRoot":"","sources":["../src/procedure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAExD,KAAK,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1D,6DAA6D;AAC7D,UAAU,OAAO,CAAC,OAAO,GAAG,OAAO;IACjC,SAAS,CACP,IAAI,EAAE,OAAO,GACZ;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1E;AAED,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE7D,UAAU,eAAe,CAAC,OAAO,SAAS,OAAO,GAAG,SAAS,GAAG,SAAS;IACvE,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,SAAS,CAAC,MAAM,EAAE,OAAO;IACxC,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CACN,OAAO,EAAE,CACP,GAAG,EAAE,cAAc,EACnB,GAAG,IAAI,EAAE,MAAM,SAAS,IAAI,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,KAChD,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,GAC/B,QAAQ,CAAC;IACZ,IAAI,CACF,MAAM,EAAE,MAAM,EACd,GAAG,IAAI,EAAE,MAAM,SAAS,IAAI,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAClD,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB,CAAC,MAAM;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/C,QAAQ,CACN,OAAO,EAAE,CACP,GAAG,EAAE,cAAc,EACnB,GAAG,IAAI,EAAE,MAAM,SAAS,IAAI,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,KAChD,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,GAC/B,QAAQ,CAAC;IACZ,IAAI,CACF,MAAM,EAAE,MAAM,EACd,GAAG,IAAI,EAAE,MAAM,SAAS,IAAI,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAClD,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AA4GD,wBAAgB,eAAe,CAC7B,OAAO,SAAS,OAAO,GAAG,SAAS,GAAG,SAAS,EAE/C,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,GAC/B,gBAAgB,CAAC,OAAO,SAAS,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAazE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
function wrapOutput(output) {
|
|
2
|
+
if (output instanceof Response) return output;
|
|
3
|
+
return Response.json(output);
|
|
4
|
+
}
|
|
5
|
+
function createProcedure(config, inputSchema) {
|
|
6
|
+
return {
|
|
7
|
+
path: config.path,
|
|
8
|
+
method: config.method,
|
|
9
|
+
endpoint (handler) {
|
|
10
|
+
return {
|
|
11
|
+
path: config.path,
|
|
12
|
+
method: config.method,
|
|
13
|
+
handler: async (req)=>{
|
|
14
|
+
if (inputSchema) {
|
|
15
|
+
if (config.method === 'get') {
|
|
16
|
+
const routeParams = req.routeParams ?? {};
|
|
17
|
+
const searchParams = req.searchParams ? Object.fromEntries(req.searchParams.entries()) : {};
|
|
18
|
+
const merged = {
|
|
19
|
+
...searchParams,
|
|
20
|
+
...routeParams
|
|
21
|
+
};
|
|
22
|
+
const result = inputSchema.safeParse(merged);
|
|
23
|
+
if (!result.success) {
|
|
24
|
+
return Response.json({
|
|
25
|
+
error: result.error
|
|
26
|
+
}, {
|
|
27
|
+
status: 400
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// biome-ignore lint/complexity/noBannedTypes: ugly type cast
|
|
31
|
+
const output = await handler(req, result.data);
|
|
32
|
+
return wrapOutput(output);
|
|
33
|
+
}
|
|
34
|
+
const { addDataAndFileToRequest } = await import(/* webpackIgnore: true */ 'payload');
|
|
35
|
+
await addDataAndFileToRequest(req);
|
|
36
|
+
const result = inputSchema.safeParse(req.data);
|
|
37
|
+
if (!result.success) {
|
|
38
|
+
return Response.json({
|
|
39
|
+
error: result.error
|
|
40
|
+
}, {
|
|
41
|
+
status: 400
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// biome-ignore lint/complexity/noBannedTypes: ugly type cast
|
|
45
|
+
const output = await handler(req, result.data);
|
|
46
|
+
return wrapOutput(output);
|
|
47
|
+
}
|
|
48
|
+
// biome-ignore lint/complexity/noBannedTypes: ugly type cast
|
|
49
|
+
const output = await handler(req);
|
|
50
|
+
return wrapOutput(output);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
call (apiUrl, ...args) {
|
|
55
|
+
const input = args[0];
|
|
56
|
+
if (config.method === 'get') {
|
|
57
|
+
let resolvedPath = config.path;
|
|
58
|
+
const queryParams = {};
|
|
59
|
+
if (input) {
|
|
60
|
+
for (const [key, value] of Object.entries(input)){
|
|
61
|
+
if (value == null) continue;
|
|
62
|
+
if (resolvedPath.includes(`:${key}`)) {
|
|
63
|
+
resolvedPath = resolvedPath.replace(`:${key}`, encodeURIComponent(String(value)));
|
|
64
|
+
} else {
|
|
65
|
+
queryParams[key] = String(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const queryString = new URLSearchParams(queryParams).toString();
|
|
70
|
+
const url = `${apiUrl}${resolvedPath}${queryString ? `?${queryString}` : ''}`;
|
|
71
|
+
return fetch(url, {
|
|
72
|
+
method: 'GET',
|
|
73
|
+
credentials: 'include'
|
|
74
|
+
}).then(async (response)=>{
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
|
77
|
+
}
|
|
78
|
+
return response.json();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const url = `${apiUrl}${config.path}`;
|
|
82
|
+
return fetch(url, {
|
|
83
|
+
method: config.method.toUpperCase(),
|
|
84
|
+
credentials: 'include',
|
|
85
|
+
headers: {
|
|
86
|
+
'Content-Type': 'application/json'
|
|
87
|
+
},
|
|
88
|
+
body: input ? JSON.stringify(input) : undefined
|
|
89
|
+
}).then(async (response)=>{
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
|
92
|
+
}
|
|
93
|
+
return response.json();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function defineProcedure(config) {
|
|
99
|
+
const proc = createProcedure(config, config.input);
|
|
100
|
+
return {
|
|
101
|
+
path: config.path,
|
|
102
|
+
method: config.method,
|
|
103
|
+
returns () {
|
|
104
|
+
return createProcedure(config, config.input);
|
|
105
|
+
},
|
|
106
|
+
endpoint: proc.endpoint,
|
|
107
|
+
call: proc.call
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=procedure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/procedure.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload';\n\ntype Method = 'get' | 'post' | 'put' | 'patch' | 'delete';\n\n/** Any Zod-like schema with safeParse and inferred output */\ninterface ZodLike<TOutput = unknown> {\n safeParse(\n data: unknown,\n ): { success: true; data: TOutput } | { success: false; error: unknown };\n}\n\ntype InferOutput<T> = T extends ZodLike<infer O> ? O : never;\n\ninterface ProcedureConfig<TSchema extends ZodLike | undefined = undefined> {\n path: `/${string}`;\n method: Method;\n input?: TSchema;\n}\n\nexport interface Procedure<TInput, TOutput> {\n path: `/${string}`;\n method: Method;\n endpoint(\n handler: (\n req: PayloadRequest,\n ...args: TInput extends void ? [] : [input: TInput]\n ) => Promise<unknown | Response>,\n ): Endpoint;\n call(\n apiUrl: string,\n ...args: TInput extends void ? [] : [input: TInput]\n ): Promise<TOutput>;\n}\n\nexport interface ProcedureBuilder<TInput> {\n path: string;\n method: Method;\n returns<TOutput>(): Procedure<TInput, TOutput>;\n endpoint(\n handler: (\n req: PayloadRequest,\n ...args: TInput extends void ? [] : [input: TInput]\n ) => Promise<unknown | Response>,\n ): Endpoint;\n call(\n apiUrl: string,\n ...args: TInput extends void ? [] : [input: TInput]\n ): Promise<unknown>;\n}\n\nfunction wrapOutput(output: unknown): Response {\n if (output instanceof Response) return output;\n return Response.json(output);\n}\n\nfunction createProcedure<TInput, TOutput>(\n config: ProcedureConfig<ZodLike | undefined>,\n inputSchema: ZodLike | undefined,\n): Procedure<TInput, TOutput> {\n return {\n path: config.path,\n method: config.method,\n endpoint(handler) {\n return {\n path: config.path,\n method: config.method,\n handler: async (req) => {\n if (inputSchema) {\n if (config.method === 'get') {\n const routeParams = req.routeParams ?? {};\n const searchParams = req.searchParams\n ? Object.fromEntries(req.searchParams.entries())\n : {};\n const merged = { ...searchParams, ...routeParams };\n const result = inputSchema.safeParse(merged);\n if (!result.success) {\n return Response.json({ error: result.error }, { status: 400 });\n }\n // biome-ignore lint/complexity/noBannedTypes: ugly type cast\n const output = await (handler as Function)(req, result.data);\n return wrapOutput(output);\n }\n\n const { addDataAndFileToRequest } = await import(\n /* webpackIgnore: true */ 'payload'\n );\n await addDataAndFileToRequest(req);\n const result = inputSchema.safeParse(req.data);\n if (!result.success) {\n return Response.json({ error: result.error }, { status: 400 });\n }\n // biome-ignore lint/complexity/noBannedTypes: ugly type cast\n const output = await (handler as Function)(req, result.data);\n return wrapOutput(output);\n }\n // biome-ignore lint/complexity/noBannedTypes: ugly type cast\n const output = await (handler as Function)(req);\n return wrapOutput(output);\n },\n };\n },\n call(apiUrl, ...args) {\n const input = args[0] as Record<string, unknown> | undefined;\n\n if (config.method === 'get') {\n let resolvedPath = config.path;\n const queryParams: Record<string, string> = {};\n\n if (input) {\n for (const [key, value] of Object.entries(input)) {\n if (value == null) continue;\n if (resolvedPath.includes(`:${key}`)) {\n resolvedPath = resolvedPath.replace(\n `:${key}`,\n encodeURIComponent(String(value)),\n ) as `/${string}`;\n } else {\n queryParams[key] = String(value);\n }\n }\n }\n\n const queryString = new URLSearchParams(queryParams).toString();\n const url = `${apiUrl}${resolvedPath}${queryString ? `?${queryString}` : ''}`;\n\n return fetch(url, {\n method: 'GET',\n credentials: 'include',\n }).then(async (response) => {\n if (!response.ok) {\n throw new Error(\n `Request failed: ${response.status} ${response.statusText}`,\n );\n }\n return response.json();\n }) as Promise<TOutput>;\n }\n\n const url = `${apiUrl}${config.path}`;\n return fetch(url, {\n method: config.method.toUpperCase(),\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: input ? JSON.stringify(input) : undefined,\n }).then(async (response) => {\n if (!response.ok) {\n throw new Error(\n `Request failed: ${response.status} ${response.statusText}`,\n );\n }\n return response.json();\n }) as Promise<TOutput>;\n },\n };\n}\n\nexport function defineProcedure<\n TSchema extends ZodLike | undefined = undefined,\n>(\n config: ProcedureConfig<TSchema>,\n): ProcedureBuilder<TSchema extends ZodLike ? InferOutput<TSchema> : void> {\n type TInput = TSchema extends ZodLike ? InferOutput<TSchema> : undefined;\n const proc = createProcedure<TInput, unknown>(config, config.input);\n\n return {\n path: config.path,\n method: config.method,\n returns<TOutput>(): Procedure<TInput, TOutput> {\n return createProcedure<TInput, TOutput>(config, config.input);\n },\n endpoint: proc.endpoint as unknown as ProcedureBuilder<TInput>['endpoint'],\n call: proc.call as ProcedureBuilder<TInput>['call'],\n };\n}\n"],"names":["wrapOutput","output","Response","json","createProcedure","config","inputSchema","path","method","endpoint","handler","req","routeParams","searchParams","Object","fromEntries","entries","merged","result","safeParse","success","error","status","data","addDataAndFileToRequest","call","apiUrl","args","input","resolvedPath","queryParams","key","value","includes","replace","encodeURIComponent","String","queryString","URLSearchParams","toString","url","fetch","credentials","then","response","ok","Error","statusText","toUpperCase","headers","body","JSON","stringify","undefined","defineProcedure","proc","returns"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@davincicoding/payload-plugin-kit",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Developer toolkit for building Payload CMS plugins — procedures, field utilities, build scripts, and base configuration.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/davincicoding-org/payload-plugins.git",
|
|
8
|
+
"directory": "packages/plugin-kit"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./base.tsconfig.json": "./tsconfig.base.json"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"bin": {
|
|
22
|
+
"generate-types": "./scripts/generate-types.js",
|
|
23
|
+
"plugin-build": "./scripts/build.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"scripts",
|
|
28
|
+
"tsconfig.base.json",
|
|
29
|
+
".swcrc"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "swc src -d dist --config-file .swcrc --strip-leading-paths --ignore '**/*.test.ts' && tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
33
|
+
"check:types": "tsc --noEmit",
|
|
34
|
+
"clean": "rm -rf dist && rm -rf node_modules",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
40
|
+
"@davincicoding/payload-utils": "workspace:*",
|
|
41
|
+
"@swc/cli": "^0.6.0",
|
|
42
|
+
"@swc/core": "^1.11.0",
|
|
43
|
+
"commander": "^14.0.2",
|
|
44
|
+
"tsc-alias": "^1.8.0",
|
|
45
|
+
"tsx": "^4.0.0",
|
|
46
|
+
"zod": "catalog:"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@payloadcms/ui": "catalog:payload-stack",
|
|
50
|
+
"@types/node": "^22.5.4",
|
|
51
|
+
"payload": "catalog:payload-stack",
|
|
52
|
+
"typescript": "catalog:payload-stack",
|
|
53
|
+
"vitest": "^3.1.2"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"payload": ">=3.72.0"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": "^18.20.2 || >=20.9.0"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
import { execSync, spawn } from 'node:child_process';
|
|
4
|
+
import {
|
|
5
|
+
copyFileSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
watch,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { Command } from '@commander-js/extra-typings';
|
|
17
|
+
|
|
18
|
+
const program = new Command()
|
|
19
|
+
.name('plugin-build')
|
|
20
|
+
.description('Build a Payload plugin using SWC + tsc')
|
|
21
|
+
.option('-w, --watch', 'run in watch mode')
|
|
22
|
+
.parse();
|
|
23
|
+
|
|
24
|
+
const opts = program.opts();
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const commonDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
27
|
+
const swcrc = path.join(commonDir, '.swcrc');
|
|
28
|
+
|
|
29
|
+
// Local bin first (package's own tsc), then common bin (swc, tsc-alias)
|
|
30
|
+
const localBin = path.join(cwd, 'node_modules', '.bin');
|
|
31
|
+
const commonBin = path.join(commonDir, 'node_modules', '.bin');
|
|
32
|
+
process.env.PATH = [localBin, commonBin, process.env.PATH].join(path.delimiter);
|
|
33
|
+
|
|
34
|
+
const ASSET_RE = /\.(css|scss|html|json|ttf|woff|woff2|eot|svg|jpg|png)$/;
|
|
35
|
+
const SWC_IGNORE =
|
|
36
|
+
'**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx,**/payload-types.ts';
|
|
37
|
+
const tsconfig = existsSync(path.join(cwd, 'tsconfig.build.json'))
|
|
38
|
+
? 'tsconfig.build.json'
|
|
39
|
+
: 'tsconfig.json';
|
|
40
|
+
|
|
41
|
+
// Note: execSync is used here with hardcoded build tool commands (swc, tsc, tsc-alias)
|
|
42
|
+
// and no user-controlled input, so shell injection is not a concern.
|
|
43
|
+
/** @param {string} cmd @param {import('node:child_process').ExecSyncOptions} [o] */
|
|
44
|
+
const exec = (cmd, o) => execSync(cmd, { cwd, stdio: 'inherit', ...o });
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Remove the `declare module 'payload'` augmentation from the built
|
|
48
|
+
* payload-types.d.ts so plugins don't pollute the consumer's type space.
|
|
49
|
+
* The interfaces themselves (User, Notification, etc.) are kept so that
|
|
50
|
+
* exported plugin types referencing them still resolve.
|
|
51
|
+
*/
|
|
52
|
+
function stripPayloadAugmentation() {
|
|
53
|
+
const dts = path.join(cwd, 'dist', 'payload-types.d.ts');
|
|
54
|
+
if (!existsSync(dts)) return;
|
|
55
|
+
const content = readFileSync(dts, 'utf-8');
|
|
56
|
+
const stripped = content.replace(
|
|
57
|
+
/\n*declare module ['"]payload['"] \{[\s\S]*?\n\}\n/,
|
|
58
|
+
'\n',
|
|
59
|
+
);
|
|
60
|
+
if (stripped !== content) writeFileSync(dts, stripped);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function copyAssets() {
|
|
64
|
+
const srcDir = path.join(cwd, 'src');
|
|
65
|
+
if (!existsSync(srcDir)) return;
|
|
66
|
+
for (const entry of readdirSync(srcDir, { recursive: true })) {
|
|
67
|
+
const file = String(entry);
|
|
68
|
+
if (!ASSET_RE.test(file)) continue;
|
|
69
|
+
const dest = path.join(cwd, 'dist', file);
|
|
70
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
71
|
+
copyFileSync(path.join(srcDir, file), dest);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (opts.watch) {
|
|
76
|
+
/** @type {import('node:child_process').ChildProcess[]} */
|
|
77
|
+
const children = [];
|
|
78
|
+
const cleanup = () => {
|
|
79
|
+
for (const c of children) c.kill();
|
|
80
|
+
};
|
|
81
|
+
process.on('SIGINT', cleanup);
|
|
82
|
+
process.on('SIGTERM', cleanup);
|
|
83
|
+
process.on('exit', cleanup);
|
|
84
|
+
|
|
85
|
+
/** @param {string} cmd @param {string[]} args */
|
|
86
|
+
const start = (cmd, args) => {
|
|
87
|
+
const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: true });
|
|
88
|
+
child.on('error', (err) => {
|
|
89
|
+
process.stderr.write(`[${cmd}] ${err.message}\n`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
children.push(child);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
copyAssets();
|
|
96
|
+
|
|
97
|
+
const srcDir = path.join(cwd, 'src');
|
|
98
|
+
watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
99
|
+
if (!filename || !ASSET_RE.test(filename)) return;
|
|
100
|
+
const src = path.join(srcDir, filename);
|
|
101
|
+
if (!existsSync(src)) return;
|
|
102
|
+
const dest = path.join(cwd, 'dist', filename);
|
|
103
|
+
mkdirSync(path.dirname(dest), { recursive: true });
|
|
104
|
+
copyFileSync(src, dest);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
start('swc', [
|
|
108
|
+
'src',
|
|
109
|
+
'-d',
|
|
110
|
+
'dist',
|
|
111
|
+
'--config-file',
|
|
112
|
+
swcrc,
|
|
113
|
+
'--strip-leading-paths',
|
|
114
|
+
'--ignore',
|
|
115
|
+
SWC_IGNORE,
|
|
116
|
+
'--watch',
|
|
117
|
+
]);
|
|
118
|
+
start('tsc', [
|
|
119
|
+
'-p',
|
|
120
|
+
tsconfig,
|
|
121
|
+
'--emitDeclarationOnly',
|
|
122
|
+
'--watch',
|
|
123
|
+
'--preserveWatchOutput',
|
|
124
|
+
]);
|
|
125
|
+
start('tsc-alias', ['-p', tsconfig, '--watch']);
|
|
126
|
+
} else {
|
|
127
|
+
if (existsSync(path.join(cwd, 'dist')))
|
|
128
|
+
rmSync(path.join(cwd, 'dist'), { recursive: true, force: true });
|
|
129
|
+
for (const f of ['tsconfig.tsbuildinfo', 'tsconfig.build.tsbuildinfo']) {
|
|
130
|
+
const p = path.join(cwd, f);
|
|
131
|
+
if (existsSync(p)) rmSync(p);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
copyAssets();
|
|
135
|
+
exec(
|
|
136
|
+
`swc src -d dist --config-file "${swcrc}" --strip-leading-paths --ignore "${SWC_IGNORE}"`,
|
|
137
|
+
);
|
|
138
|
+
exec(`tsc -p ${tsconfig} --emitDeclarationOnly`);
|
|
139
|
+
exec(`tsc-alias -p ${tsconfig}`);
|
|
140
|
+
stripPayloadAugmentation();
|
|
141
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
3
|
+
|
|
4
|
+
// tsx v4 requires --import flag. If not already loaded with tsx,
|
|
5
|
+
// respawn this script with the proper flag.
|
|
6
|
+
if (!process.env.__GEN_TYPES_TSX) {
|
|
7
|
+
const { execFileSync } = await import('node:child_process');
|
|
8
|
+
const { fileURLToPath, pathToFileURL } = await import('node:url');
|
|
9
|
+
const { createRequire } = await import('node:module');
|
|
10
|
+
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const tsxPath = pathToFileURL(require.resolve('tsx')).href;
|
|
13
|
+
|
|
14
|
+
process.env.__GEN_TYPES_TSX = '1';
|
|
15
|
+
try {
|
|
16
|
+
execFileSync(
|
|
17
|
+
process.execPath,
|
|
18
|
+
[
|
|
19
|
+
'--import',
|
|
20
|
+
tsxPath,
|
|
21
|
+
fileURLToPath(import.meta.url),
|
|
22
|
+
...process.argv.slice(2),
|
|
23
|
+
],
|
|
24
|
+
{ stdio: 'inherit' },
|
|
25
|
+
);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
process.exit(/** @type {any} */ (e).status ?? 1);
|
|
28
|
+
}
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Note: execSync is used here with a hardcoded npx payload command
|
|
33
|
+
// and no user-controlled input, so shell injection is not a concern.
|
|
34
|
+
import { execSync } from 'node:child_process';
|
|
35
|
+
import * as fs from 'node:fs';
|
|
36
|
+
import * as path from 'node:path';
|
|
37
|
+
import { Command, InvalidArgumentError } from '@commander-js/extra-typings';
|
|
38
|
+
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
|
|
41
|
+
const program = new Command()
|
|
42
|
+
.name('generate-types')
|
|
43
|
+
.description('Generate Payload types for a plugin')
|
|
44
|
+
.option(
|
|
45
|
+
'-e, --export <name>',
|
|
46
|
+
'plugin export name (default: derived from package name)',
|
|
47
|
+
)
|
|
48
|
+
.option(
|
|
49
|
+
'-o, --opt <key=value>',
|
|
50
|
+
'plugin option (repeatable)',
|
|
51
|
+
(val, acc) => {
|
|
52
|
+
const eq = val.indexOf('=');
|
|
53
|
+
if (eq === -1)
|
|
54
|
+
throw new InvalidArgumentError(`expected key=value, got "${val}"`);
|
|
55
|
+
acc[val.slice(0, eq)] = val.slice(eq + 1);
|
|
56
|
+
return acc;
|
|
57
|
+
},
|
|
58
|
+
/** @type {Record<string, string>} */ ({}),
|
|
59
|
+
)
|
|
60
|
+
.parse();
|
|
61
|
+
|
|
62
|
+
const plugin = await resolvePlugin();
|
|
63
|
+
const configPath = generateMinimalPayloadConfig(plugin);
|
|
64
|
+
try {
|
|
65
|
+
generateTypes(configPath);
|
|
66
|
+
} finally {
|
|
67
|
+
cleanup(configPath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @returns {Promise<{ exportName: string; call: string }>} */
|
|
71
|
+
async function resolvePlugin() {
|
|
72
|
+
const opts = program.opts();
|
|
73
|
+
const explicit = opts.export != null;
|
|
74
|
+
const exportName = opts.export ?? deriveExportName();
|
|
75
|
+
|
|
76
|
+
const mod = await import(path.join(cwd, 'src/index.ts'));
|
|
77
|
+
if (typeof mod[exportName] !== 'function') {
|
|
78
|
+
const fns = Object.entries(mod)
|
|
79
|
+
.filter(([, v]) => typeof v === 'function')
|
|
80
|
+
.map(([name]) => name);
|
|
81
|
+
|
|
82
|
+
if (explicit) {
|
|
83
|
+
console.error(
|
|
84
|
+
`"${exportName}" is not a function export in src/index.ts.`,
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
console.error(
|
|
88
|
+
`Could not find a plugin export matching the naming convention "${exportName}").`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (fns.length > 0) {
|
|
92
|
+
console.error(`Available function exports: ${fns.join(', ')}`);
|
|
93
|
+
console.error(`Fix: generate-types -e ${fns[0]}`);
|
|
94
|
+
}
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
exportName,
|
|
100
|
+
call: `${exportName}(${JSON.stringify(opts.opt)} as any)`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @returns {string} */
|
|
105
|
+
function deriveExportName() {
|
|
106
|
+
const pkg = JSON.parse(
|
|
107
|
+
fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'),
|
|
108
|
+
);
|
|
109
|
+
/** @type {string} */
|
|
110
|
+
const name = pkg.name.replace(/^payload-/, '');
|
|
111
|
+
return `${name}-plugin`.replace(/-([a-z])/g, (_, /** @type {string} */ c) =>
|
|
112
|
+
c.toUpperCase(),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {{ exportName: string; call: string }} plugin
|
|
118
|
+
* @returns {string}
|
|
119
|
+
*/
|
|
120
|
+
function generateMinimalPayloadConfig(plugin) {
|
|
121
|
+
const importPath = path.join(cwd, 'src/index').replaceAll('\\', '/');
|
|
122
|
+
const configPath = path.join(cwd, '.payload-gen.config.ts');
|
|
123
|
+
|
|
124
|
+
fs.writeFileSync(
|
|
125
|
+
configPath,
|
|
126
|
+
`
|
|
127
|
+
import { buildConfig } from 'payload';
|
|
128
|
+
import { ${plugin.exportName} } from '${importPath}';
|
|
129
|
+
|
|
130
|
+
export default buildConfig({
|
|
131
|
+
secret: '',
|
|
132
|
+
db: {
|
|
133
|
+
defaultIDType: 'text',
|
|
134
|
+
init: () => {
|
|
135
|
+
throw new Error('Not implemented');
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
email: ({ payload }) => ({
|
|
139
|
+
defaultFromAddress: '',
|
|
140
|
+
defaultFromName: '',
|
|
141
|
+
name: '',
|
|
142
|
+
sendEmail: async () => void 0,
|
|
143
|
+
}),
|
|
144
|
+
admin: {
|
|
145
|
+
user: 'users',
|
|
146
|
+
},
|
|
147
|
+
collections: [{ slug: 'users', auth: true, fields: [] }],
|
|
148
|
+
plugins: [${plugin.call}],
|
|
149
|
+
});
|
|
150
|
+
`,
|
|
151
|
+
);
|
|
152
|
+
return configPath;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** @param {string} configPath */
|
|
156
|
+
function generateTypes(configPath) {
|
|
157
|
+
execSync('npx payload generate:types', {
|
|
158
|
+
stdio: 'inherit',
|
|
159
|
+
cwd,
|
|
160
|
+
env: {
|
|
161
|
+
...process.env,
|
|
162
|
+
PAYLOAD_CONFIG_PATH: configPath,
|
|
163
|
+
PAYLOAD_TS_OUTPUT_PATH: path.join(cwd, 'src/payload-types.ts'),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** @param {string} configPath */
|
|
169
|
+
function cleanup(configPath) {
|
|
170
|
+
fs.rmSync(configPath);
|
|
171
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"jsxImportSource": "react",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"allowJs": true,
|
|
17
|
+
"checkJs": true,
|
|
18
|
+
"composite": true,
|
|
19
|
+
"declaration": true,
|
|
20
|
+
"declarationMap": true
|
|
21
|
+
}
|
|
22
|
+
}
|