@airdraft/next 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/CHANGELOG.md +5 -0
- package/dist/client.d.ts +18 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +21 -0
- package/dist/client.js.map +1 -0
- package/dist/forwarder.d.ts +32 -0
- package/dist/forwarder.d.ts.map +1 -0
- package/dist/forwarder.js +44 -0
- package/dist/forwarder.js.map +1 -0
- package/dist/handler.d.ts +28 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +468 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +39 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +89 -0
- package/dist/middleware.js.map +1 -0
- package/dist/revalidation.d.ts +29 -0
- package/dist/revalidation.d.ts.map +1 -0
- package/dist/revalidation.js +69 -0
- package/dist/revalidation.js.map +1 -0
- package/dist/tokenProvider.d.ts +33 -0
- package/dist/tokenProvider.d.ts.map +1 -0
- package/dist/tokenProvider.js +68 -0
- package/dist/tokenProvider.js.map +1 -0
- package/package.json +50 -0
package/CHANGELOG.md
ADDED
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CmsConfig, type ListOptions, type Entry } from '@airdraft/core';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a server-side CMS client that reads content directly via the adapter,
|
|
4
|
+
* bypassing HTTP. Use in Server Components, generateStaticParams, and other
|
|
5
|
+
* server-side Next.js patterns.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const cms = createCmsClient(airdraft)
|
|
9
|
+
* const posts = await cms.listEntries('posts', { status: 'published' })
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function createCmsClient(config: CmsConfig): {
|
|
13
|
+
listEntries: (collection: string, options?: ListOptions) => Promise<Entry[]>;
|
|
14
|
+
getEntry: (collection: string, slug: string) => Promise<Entry | null>;
|
|
15
|
+
getSchema: () => import("@airdraft/core").CmsSchema;
|
|
16
|
+
getCollection: (name: string) => import("@airdraft/core").CollectionConfig | null;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAExF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS;8BAInB,MAAM,YAAY,WAAW,KAAG,OAAO,CAAC,KAAK,EAAE,CAAC;2BAGnD,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;;0BAK7C,MAAM;EAE/B"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CmsEngine } from '@airdraft/core';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a server-side CMS client that reads content directly via the adapter,
|
|
4
|
+
* bypassing HTTP. Use in Server Components, generateStaticParams, and other
|
|
5
|
+
* server-side Next.js patterns.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const cms = createCmsClient(airdraft)
|
|
9
|
+
* const posts = await cms.listEntries('posts', { status: 'published' })
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function createCmsClient(config) {
|
|
13
|
+
const engine = new CmsEngine(config);
|
|
14
|
+
return {
|
|
15
|
+
listEntries: (collection, options) => engine.listEntries(collection, options),
|
|
16
|
+
getEntry: (collection, slug) => engine.getEntry(collection, slug),
|
|
17
|
+
getSchema: () => engine.getSchema(),
|
|
18
|
+
getCollection: (name) => engine.getCollection(name),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgD,MAAM,gBAAgB,CAAA;AAExF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAA;IAEpC,OAAO;QACL,WAAW,EAAE,CAAC,UAAkB,EAAE,OAAqB,EAAoB,EAAE,CAC3E,MAAM,CAAC,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC;QAEzC,QAAQ,EAAE,CAAC,UAAkB,EAAE,IAAY,EAAyB,EAAE,CACpE,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;QAEnC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;QAEnC,aAAa,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;KAC5D,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud forwarder — re-exports all HTTP method handlers that forward requests to
|
|
3
|
+
* the Airdraft Cloud CMS runtime.
|
|
4
|
+
*
|
|
5
|
+
* Mount in `app/api/cms/[...route]/route.ts` of a Next.js project:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* export { GET, POST, PUT, PATCH, DELETE } from '@airdraft/next/forwarder'
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Reads `AIRDRAFT_CMS_URL` from environment variables to locate the cloud
|
|
12
|
+
* CMS runtime (e.g. `https://cms.airdraft.space/my-project`).
|
|
13
|
+
*
|
|
14
|
+
* The proxy transparently forwards headers (Authorization, Content-Type, etc.)
|
|
15
|
+
* and the request body, then streams the response back to the client.
|
|
16
|
+
*/
|
|
17
|
+
type NextRequest = Request & {
|
|
18
|
+
nextUrl?: URL;
|
|
19
|
+
};
|
|
20
|
+
interface RouteContext {
|
|
21
|
+
params: Promise<{
|
|
22
|
+
route?: string[];
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
declare function proxyRequest(req: NextRequest, ctx: RouteContext): Promise<Response>;
|
|
26
|
+
export declare const GET: typeof proxyRequest;
|
|
27
|
+
export declare const POST: typeof proxyRequest;
|
|
28
|
+
export declare const PUT: typeof proxyRequest;
|
|
29
|
+
export declare const PATCH: typeof proxyRequest;
|
|
30
|
+
export declare const DELETE: typeof proxyRequest;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=forwarder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forwarder.d.ts","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,KAAK,WAAW,GAAG,OAAO,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAA;AAC9C,UAAU,YAAY;IACpB,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CACtC;AAED,iBAAe,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2BlF;AAED,eAAO,MAAM,GAAG,qBAAe,CAAA;AAC/B,eAAO,MAAM,IAAI,qBAAe,CAAA;AAChC,eAAO,MAAM,GAAG,qBAAe,CAAA;AAC/B,eAAO,MAAM,KAAK,qBAAe,CAAA;AACjC,eAAO,MAAM,MAAM,qBAAe,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud forwarder — re-exports all HTTP method handlers that forward requests to
|
|
3
|
+
* the Airdraft Cloud CMS runtime.
|
|
4
|
+
*
|
|
5
|
+
* Mount in `app/api/cms/[...route]/route.ts` of a Next.js project:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* export { GET, POST, PUT, PATCH, DELETE } from '@airdraft/next/forwarder'
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* Reads `AIRDRAFT_CMS_URL` from environment variables to locate the cloud
|
|
12
|
+
* CMS runtime (e.g. `https://cms.airdraft.space/my-project`).
|
|
13
|
+
*
|
|
14
|
+
* The proxy transparently forwards headers (Authorization, Content-Type, etc.)
|
|
15
|
+
* and the request body, then streams the response back to the client.
|
|
16
|
+
*/
|
|
17
|
+
async function proxyRequest(req, ctx) {
|
|
18
|
+
const cmsBase = process.env.AIRDRAFT_CMS_URL;
|
|
19
|
+
if (!cmsBase) {
|
|
20
|
+
return new Response(JSON.stringify({ error: { code: 'MISSING_CMS_URL', message: 'AIRDRAFT_CMS_URL is not set' } }), { status: 500, headers: { 'Content-Type': 'application/json' } });
|
|
21
|
+
}
|
|
22
|
+
const { route } = await ctx.params;
|
|
23
|
+
const routePath = route ? `/${route.join('/')}` : '';
|
|
24
|
+
const url = new URL(req.nextUrl ?? req.url);
|
|
25
|
+
const targetUrl = `${cmsBase.replace(/\/$/, '')}/api/cms${routePath}${url.search}`;
|
|
26
|
+
// Forward request with original headers + body
|
|
27
|
+
const init = {
|
|
28
|
+
method: req.method,
|
|
29
|
+
headers: req.headers,
|
|
30
|
+
// Body only for non-GET/HEAD
|
|
31
|
+
...(req.method !== 'GET' && req.method !== 'HEAD' ? { body: req.body, duplex: 'half' } : {}),
|
|
32
|
+
};
|
|
33
|
+
const upstream = await fetch(targetUrl, init);
|
|
34
|
+
return new Response(upstream.body, {
|
|
35
|
+
status: upstream.status,
|
|
36
|
+
headers: upstream.headers,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export const GET = proxyRequest;
|
|
40
|
+
export const POST = proxyRequest;
|
|
41
|
+
export const PUT = proxyRequest;
|
|
42
|
+
export const PATCH = proxyRequest;
|
|
43
|
+
export const DELETE = proxyRequest;
|
|
44
|
+
//# sourceMappingURL=forwarder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forwarder.js","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,KAAK,UAAU,YAAY,CAAC,GAAgB,EAAE,GAAiB;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;IAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,6BAA6B,EAAE,EAAE,CAAC,EAC9F,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACjE,CAAA;IACH,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,GAAG,CAAC,MAAM,CAAA;IAClC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACpD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;IAElF,+CAA+C;IAC/C,MAAM,IAAI,GAAgB;QACxB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,6BAA6B;QAC7B,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7F,CAAA;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,IAAmB,CAAC,CAAA;IAC5D,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC1B,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAA;AAC/B,MAAM,CAAC,MAAM,IAAI,GAAG,YAAY,CAAA;AAChC,MAAM,CAAC,MAAM,GAAG,GAAG,YAAY,CAAA;AAC/B,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAA;AACjC,MAAM,CAAC,MAAM,MAAM,GAAG,YAAY,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CmsConfig } from '@airdraft/core';
|
|
2
|
+
type NextRequest = Request & {
|
|
3
|
+
nextUrl?: URL;
|
|
4
|
+
};
|
|
5
|
+
interface RouteContext {
|
|
6
|
+
params: Promise<{
|
|
7
|
+
route?: string[];
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
interface HandlerExports {
|
|
11
|
+
GET: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
|
|
12
|
+
POST: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
|
|
13
|
+
PATCH: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
|
|
14
|
+
PUT: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
|
|
15
|
+
DELETE: (req: NextRequest, ctx: RouteContext) => Promise<Response>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Produces App Router route handler exports from a CmsConfig.
|
|
19
|
+
*
|
|
20
|
+
* Place in `app/api/cms/[...route]/route.ts`:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* export const { GET, POST, PUT, DELETE } = createCmsHandler(airdraft)
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function createCmsHandler(config: CmsConfig): HandlerExports;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAQf,MAAM,gBAAgB,CAAA;AAMvB,KAAK,WAAW,GAAG,OAAO,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAA;AAE9C,UAAU,YAAY;IACpB,MAAM,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CACtC;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/D,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAChE,KAAK,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjE,GAAG,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC/D,MAAM,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACnE;AAmGD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,cAAc,CAuZlE"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { CmsEngine, AirdraftError, ValidationError, EntryNotFoundError, } from '@airdraft/core';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// JSON helpers
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
function json(body, status = 200) {
|
|
6
|
+
return new Response(JSON.stringify(body), {
|
|
7
|
+
status,
|
|
8
|
+
headers: { 'Content-Type': 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function errorResponse(err) {
|
|
12
|
+
if (err instanceof AirdraftError) {
|
|
13
|
+
const body = {
|
|
14
|
+
error: {
|
|
15
|
+
code: err.code,
|
|
16
|
+
message: err.message,
|
|
17
|
+
details: err instanceof ValidationError ? err.details : {},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
return json(body, err.statusHint);
|
|
21
|
+
}
|
|
22
|
+
// Unhandled — log server-side, return opaque 500
|
|
23
|
+
console.error('[airdraft]', err);
|
|
24
|
+
return json({ error: { code: 'INTERNAL_ERROR', message: 'Internal server error', details: {} } }, 500);
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Route parsing
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
function parseRoute(segments) {
|
|
30
|
+
if (!segments || segments.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
const [collection, slug] = segments;
|
|
33
|
+
return { collection, slug };
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// List query string parsing
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function parseListOptions(url) {
|
|
39
|
+
const u = typeof url === 'string' ? new URL(url) : url;
|
|
40
|
+
const opts = {};
|
|
41
|
+
const limit = u.searchParams.get('limit');
|
|
42
|
+
if (limit)
|
|
43
|
+
opts.limit = Math.min(parseInt(limit, 10), 100);
|
|
44
|
+
const offset = u.searchParams.get('offset');
|
|
45
|
+
if (offset)
|
|
46
|
+
opts.offset = parseInt(offset, 10);
|
|
47
|
+
const sort = u.searchParams.get('sort');
|
|
48
|
+
const order = u.searchParams.get('order') ?? 'desc';
|
|
49
|
+
if (sort)
|
|
50
|
+
opts.sort = { field: sort, order };
|
|
51
|
+
const status = u.searchParams.get('status');
|
|
52
|
+
if (status)
|
|
53
|
+
opts.status = status;
|
|
54
|
+
const filter = {};
|
|
55
|
+
for (const [key, value] of u.searchParams.entries()) {
|
|
56
|
+
const match = key.match(/^filter\[(.+)\]$/);
|
|
57
|
+
if (match)
|
|
58
|
+
filter[match[1]] = value;
|
|
59
|
+
}
|
|
60
|
+
if (Object.keys(filter).length > 0)
|
|
61
|
+
opts.filter = filter;
|
|
62
|
+
const expandParam = u.searchParams.get('expand');
|
|
63
|
+
if (expandParam)
|
|
64
|
+
opts.expand = expandParam.split(',').map((s) => s.trim()).filter(Boolean);
|
|
65
|
+
return opts;
|
|
66
|
+
}
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Production warning for missing auth
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
let authWarned = false;
|
|
71
|
+
function warnIfNoAuth(config) {
|
|
72
|
+
if (authWarned)
|
|
73
|
+
return;
|
|
74
|
+
if (process.env.NODE_ENV === 'production') {
|
|
75
|
+
const hasAuth = config.plugins.some((p) => p.name === 'auth');
|
|
76
|
+
if (!hasAuth) {
|
|
77
|
+
console.warn('[airdraft] WARNING: No auth plugin detected. The CMS API is publicly accessible. ' +
|
|
78
|
+
'Install @airdraft/plugin-auth to protect it.');
|
|
79
|
+
authWarned = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// createCmsHandler
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Produces App Router route handler exports from a CmsConfig.
|
|
88
|
+
*
|
|
89
|
+
* Place in `app/api/cms/[...route]/route.ts`:
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* export const { GET, POST, PUT, DELETE } = createCmsHandler(airdraft)
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function createCmsHandler(config) {
|
|
96
|
+
const engine = new CmsEngine(config);
|
|
97
|
+
warnIfNoAuth(config);
|
|
98
|
+
async function runMiddleware(req) {
|
|
99
|
+
try {
|
|
100
|
+
for (const plugin of config.plugins) {
|
|
101
|
+
await plugin.middleware?.(req);
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
return errorResponse(err);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Dispatch a request to a plugin-defined route. Returns null if no match. */
|
|
110
|
+
async function dispatchPluginRoute(req, route) {
|
|
111
|
+
const routeKey = '/' + route.join('/');
|
|
112
|
+
// Build a sorted list of all plugin routes (most literal segments first so
|
|
113
|
+
// `/media/:key/url` is tried before `/media/:key`).
|
|
114
|
+
const candidates = [];
|
|
115
|
+
for (const plugin of config.plugins) {
|
|
116
|
+
if (!plugin.routes)
|
|
117
|
+
continue;
|
|
118
|
+
for (const pattern of Object.keys(plugin.routes)) {
|
|
119
|
+
candidates.push({ plugin, pattern });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
candidates.sort((a, b) => {
|
|
123
|
+
const literalCount = (p) => p.split('/').filter((s) => !s.startsWith(':')).length;
|
|
124
|
+
return literalCount(b.pattern) - literalCount(a.pattern);
|
|
125
|
+
});
|
|
126
|
+
for (const { plugin, pattern } of candidates) {
|
|
127
|
+
// Exact match
|
|
128
|
+
if (pattern === routeKey) {
|
|
129
|
+
return plugin.routes[pattern](req, { adapter: config.adapter, config });
|
|
130
|
+
}
|
|
131
|
+
// Pattern match: :param segments match one or more path characters (greedy).
|
|
132
|
+
// This allows keys that contain slashes, e.g. /media/:key matching
|
|
133
|
+
// /media/uploads/2026/05/hero.jpg
|
|
134
|
+
if (pattern.includes(':')) {
|
|
135
|
+
const regexStr = pattern
|
|
136
|
+
.split('/')
|
|
137
|
+
.map((seg) => (seg.startsWith(':') ? '(.+?)' : seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
|
|
138
|
+
.join('\\/');
|
|
139
|
+
const match = new RegExp(`^${regexStr}$`).test(routeKey);
|
|
140
|
+
if (match) {
|
|
141
|
+
return plugin.routes[pattern](req, { adapter: config.adapter, config });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
/** Run all plugin transformEntry hooks in order on a single entry. */
|
|
148
|
+
async function runTransformEntry(entry, collection) {
|
|
149
|
+
const collectionConfig = config.collections[collection];
|
|
150
|
+
if (!collectionConfig)
|
|
151
|
+
return entry;
|
|
152
|
+
let result = entry;
|
|
153
|
+
for (const plugin of config.plugins) {
|
|
154
|
+
if (plugin.hooks?.transformEntry) {
|
|
155
|
+
result = await plugin.hooks.transformEntry(result, collectionConfig);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
// -------------------------------------------------------------------------
|
|
161
|
+
// GET
|
|
162
|
+
// -------------------------------------------------------------------------
|
|
163
|
+
async function GET(req, ctx) {
|
|
164
|
+
const authErr = await runMiddleware(req);
|
|
165
|
+
if (authErr)
|
|
166
|
+
return authErr;
|
|
167
|
+
try {
|
|
168
|
+
const { route = [] } = await ctx.params;
|
|
169
|
+
// Plugin-defined GET routes
|
|
170
|
+
const pluginRes = await dispatchPluginRoute(req, route);
|
|
171
|
+
if (pluginRes)
|
|
172
|
+
return pluginRes;
|
|
173
|
+
// GET /schema
|
|
174
|
+
if (route[0] === 'schema' && route.length === 1) {
|
|
175
|
+
return json({ data: engine.getSchema() });
|
|
176
|
+
}
|
|
177
|
+
// GET /docs → redirect to Scalar (placeholder until openapi is wired)
|
|
178
|
+
if (route[0] === 'docs' && route.length === 1) {
|
|
179
|
+
return json({ message: 'OpenAPI docs coming in Phase 3' });
|
|
180
|
+
}
|
|
181
|
+
// GET /openapi.json
|
|
182
|
+
if (route[0] === 'openapi.json' && route.length === 1) {
|
|
183
|
+
return json({ message: 'OpenAPI spec coming in Phase 3' });
|
|
184
|
+
}
|
|
185
|
+
const parsed = parseRoute(route);
|
|
186
|
+
if (!parsed)
|
|
187
|
+
return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
|
|
188
|
+
const { collection, slug } = parsed;
|
|
189
|
+
if (slug) {
|
|
190
|
+
// GET /{collection}/{slug}
|
|
191
|
+
const url = req.nextUrl ?? new URL(req.url);
|
|
192
|
+
const expandParam = url.searchParams.get('expand');
|
|
193
|
+
const getOptions = expandParam
|
|
194
|
+
? { expand: expandParam.split(',').map((s) => s.trim()).filter(Boolean) }
|
|
195
|
+
: {};
|
|
196
|
+
const raw = await engine.getEntry(collection, slug, getOptions);
|
|
197
|
+
if (!raw)
|
|
198
|
+
throw new EntryNotFoundError(collection, slug);
|
|
199
|
+
const entry = await runTransformEntry(raw, collection);
|
|
200
|
+
return json({ data: { slug: entry.slug, ...entry.data, ...(entry.published !== undefined ? { published: entry.published, publishedAt: entry.publishedAt } : {}) }, meta: { sha: entry.meta.sha } });
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
// GET /{collection}
|
|
204
|
+
const url = req.nextUrl ?? new URL(req.url);
|
|
205
|
+
const opts = parseListOptions(url);
|
|
206
|
+
const rawEntries = await engine.listEntries(collection, opts);
|
|
207
|
+
const entries = await Promise.all(rawEntries.map((e) => runTransformEntry(e, collection)));
|
|
208
|
+
const data = entries.map((e) => ({
|
|
209
|
+
slug: e.slug,
|
|
210
|
+
...e.data,
|
|
211
|
+
...(e.published !== undefined ? { published: e.published, publishedAt: e.publishedAt } : {}),
|
|
212
|
+
_sha: e.meta.sha,
|
|
213
|
+
}));
|
|
214
|
+
return json({
|
|
215
|
+
data,
|
|
216
|
+
meta: { total: data.length, offset: opts.offset ?? 0, limit: opts.limit ?? 20 },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
return errorResponse(err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
// POST
|
|
226
|
+
// -------------------------------------------------------------------------
|
|
227
|
+
async function POST(req, ctx) {
|
|
228
|
+
const authErr = await runMiddleware(req);
|
|
229
|
+
if (authErr)
|
|
230
|
+
return authErr;
|
|
231
|
+
try {
|
|
232
|
+
const { route = [] } = await ctx.params;
|
|
233
|
+
// Plugin-defined POST routes (e.g. /media/upload)
|
|
234
|
+
const pluginRes = await dispatchPluginRoute(req, route);
|
|
235
|
+
if (pluginRes)
|
|
236
|
+
return pluginRes;
|
|
237
|
+
const parsed = parseRoute(route);
|
|
238
|
+
if (!parsed || parsed.slug) {
|
|
239
|
+
return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
|
|
240
|
+
}
|
|
241
|
+
const body = await req.json();
|
|
242
|
+
const payload = body.data ?? {};
|
|
243
|
+
const slug = body.slug ?? payload['slug'];
|
|
244
|
+
const entry = await engine.createEntry(parsed.collection, slug, payload);
|
|
245
|
+
return json({
|
|
246
|
+
data: { slug: entry.slug, ...entry.data, ...(entry.published !== undefined ? { published: entry.published, publishedAt: entry.publishedAt } : {}) },
|
|
247
|
+
meta: { sha: entry.meta.sha },
|
|
248
|
+
}, 201);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
return errorResponse(err);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// -------------------------------------------------------------------------
|
|
255
|
+
// PATCH — plugin-defined routes only (e.g. PATCH /media/:key)
|
|
256
|
+
// -------------------------------------------------------------------------
|
|
257
|
+
async function PATCH(req, ctx) {
|
|
258
|
+
const authErr = await runMiddleware(req);
|
|
259
|
+
if (authErr)
|
|
260
|
+
return authErr;
|
|
261
|
+
try {
|
|
262
|
+
const { route = [] } = await ctx.params;
|
|
263
|
+
const pluginRes = await dispatchPluginRoute(req, route);
|
|
264
|
+
if (pluginRes)
|
|
265
|
+
return pluginRes;
|
|
266
|
+
return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
return errorResponse(err);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// -------------------------------------------------------------------------
|
|
273
|
+
// PUT
|
|
274
|
+
// -------------------------------------------------------------------------
|
|
275
|
+
async function PUT(req, ctx) {
|
|
276
|
+
const authErr = await runMiddleware(req);
|
|
277
|
+
if (authErr)
|
|
278
|
+
return authErr;
|
|
279
|
+
try {
|
|
280
|
+
const { route = [] } = await ctx.params;
|
|
281
|
+
// Plugin-defined PUT routes (e.g. PUT /schema/collections/:name)
|
|
282
|
+
const pluginRes = await dispatchPluginRoute(req, route);
|
|
283
|
+
if (pluginRes)
|
|
284
|
+
return pluginRes;
|
|
285
|
+
const parsed = parseRoute(route);
|
|
286
|
+
if (!parsed || !parsed.slug) {
|
|
287
|
+
return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
|
|
288
|
+
}
|
|
289
|
+
const body = await req.json();
|
|
290
|
+
const sha = body.meta?.sha;
|
|
291
|
+
if (!sha) {
|
|
292
|
+
return json({ error: { code: 'CONFLICT', message: 'meta.sha is required for update operations', details: {} } }, 409);
|
|
293
|
+
}
|
|
294
|
+
const payload = { ...(body.data ?? {}), 'meta.sha': sha };
|
|
295
|
+
const entry = await engine.updateEntry(parsed.collection, parsed.slug, payload);
|
|
296
|
+
return json({
|
|
297
|
+
data: { slug: entry.slug, ...entry.data, ...(entry.published !== undefined ? { published: entry.published, publishedAt: entry.publishedAt } : {}) },
|
|
298
|
+
meta: { sha: entry.meta.sha },
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
return errorResponse(err);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// -------------------------------------------------------------------------
|
|
306
|
+
// DELETE
|
|
307
|
+
// -------------------------------------------------------------------------
|
|
308
|
+
async function DELETE(req, ctx) {
|
|
309
|
+
const authErr = await runMiddleware(req);
|
|
310
|
+
if (authErr)
|
|
311
|
+
return authErr;
|
|
312
|
+
try {
|
|
313
|
+
const { route = [] } = await ctx.params;
|
|
314
|
+
// Plugin-defined DELETE routes (e.g. DELETE /schema/collections/:name)
|
|
315
|
+
const pluginRes = await dispatchPluginRoute(req, route);
|
|
316
|
+
if (pluginRes)
|
|
317
|
+
return pluginRes;
|
|
318
|
+
const parsed = parseRoute(route);
|
|
319
|
+
if (!parsed || !parsed.slug) {
|
|
320
|
+
return json({ error: { code: 'NOT_FOUND', message: 'Not found', details: {} } }, 404);
|
|
321
|
+
}
|
|
322
|
+
const body = await req.json().catch(() => ({}));
|
|
323
|
+
const sha = body.meta?.sha;
|
|
324
|
+
if (!sha) {
|
|
325
|
+
return json({ error: { code: 'CONFLICT', message: 'meta.sha is required for delete operations', details: {} } }, 409);
|
|
326
|
+
}
|
|
327
|
+
await engine.deleteEntry(parsed.collection, parsed.slug, sha);
|
|
328
|
+
return new Response(null, { status: 204 });
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
return errorResponse(err);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// -------------------------------------------------------------------------
|
|
335
|
+
// Audit helpers
|
|
336
|
+
// -------------------------------------------------------------------------
|
|
337
|
+
/** Test a route key against a parameterised pattern like /media/:key/url. */
|
|
338
|
+
function matchesPattern(pattern, routeKey) {
|
|
339
|
+
const regexStr = pattern
|
|
340
|
+
.split('/')
|
|
341
|
+
.map((seg) => seg.startsWith(':') ? '(.+?)' : seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
342
|
+
.join('\\/');
|
|
343
|
+
return new RegExp(`^${regexStr}$`).test(routeKey);
|
|
344
|
+
}
|
|
345
|
+
/** Classify method + route segments into a human-readable action string. */
|
|
346
|
+
function classifyAction(method, route) {
|
|
347
|
+
const routeKey = '/' + route.join('/');
|
|
348
|
+
// Build sorted plugin route candidates (same logic as dispatchPluginRoute)
|
|
349
|
+
const candidates = [];
|
|
350
|
+
for (const plugin of config.plugins) {
|
|
351
|
+
if (!plugin.routes)
|
|
352
|
+
continue;
|
|
353
|
+
for (const pattern of Object.keys(plugin.routes))
|
|
354
|
+
candidates.push(pattern);
|
|
355
|
+
}
|
|
356
|
+
candidates.sort((a, b) => {
|
|
357
|
+
const literalCount = (p) => p.split('/').filter((s) => !s.startsWith(':')).length;
|
|
358
|
+
return literalCount(b) - literalCount(a);
|
|
359
|
+
});
|
|
360
|
+
for (const pattern of candidates) {
|
|
361
|
+
if (pattern === routeKey || (pattern.includes(':') && matchesPattern(pattern, routeKey))) {
|
|
362
|
+
// e.g. /media/:key/url → plugin.media.*.url
|
|
363
|
+
const action = 'plugin' + pattern.replace(/:[^/]+/g, '*');
|
|
364
|
+
return { action };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (route.length === 0)
|
|
368
|
+
return { action: 'unknown' };
|
|
369
|
+
if (route[0] === 'schema')
|
|
370
|
+
return { action: 'schema.get' };
|
|
371
|
+
if (route[0] === 'docs')
|
|
372
|
+
return { action: 'docs.get' };
|
|
373
|
+
if (route[0] === 'openapi.json')
|
|
374
|
+
return { action: 'openapi.get' };
|
|
375
|
+
const collection = route[0];
|
|
376
|
+
const slug = route[1];
|
|
377
|
+
const hasSingle = !!slug;
|
|
378
|
+
const actionMap = {
|
|
379
|
+
GET: ['collection.list', 'collection.get'],
|
|
380
|
+
POST: ['collection.create', 'unknown'],
|
|
381
|
+
PATCH: ['unknown', 'unknown'],
|
|
382
|
+
PUT: ['unknown', 'collection.update'],
|
|
383
|
+
DELETE: ['unknown', 'collection.delete'],
|
|
384
|
+
};
|
|
385
|
+
const pair = actionMap[method] ?? ['unknown', 'unknown'];
|
|
386
|
+
return { action: pair[hasSingle ? 1 : 0], collection, slug };
|
|
387
|
+
}
|
|
388
|
+
/** Emit an audit event to all plugins that registered the onAuditEvent hook. */
|
|
389
|
+
async function emitAuditEvent(event) {
|
|
390
|
+
for (const plugin of config.plugins) {
|
|
391
|
+
if (plugin.hooks?.onAuditEvent) {
|
|
392
|
+
try {
|
|
393
|
+
await plugin.hooks.onAuditEvent(event);
|
|
394
|
+
}
|
|
395
|
+
catch (e) {
|
|
396
|
+
console.error('[airdraft] audit event emit error:', e);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/** Wrap a handler function so it automatically emits a wide audit event on completion. */
|
|
402
|
+
function auditWrap(method, fn) {
|
|
403
|
+
return async (req, ctx) => {
|
|
404
|
+
const startMs = Date.now();
|
|
405
|
+
const url = new URL(req.url);
|
|
406
|
+
const { route = [] } = await ctx.params;
|
|
407
|
+
const { action, collection, slug } = classifyAction(method, route);
|
|
408
|
+
const actor = req.headers.get('x-api-key') ??
|
|
409
|
+
req.headers.get('authorization')?.replace(/^Bearer\s+/i, '') ??
|
|
410
|
+
undefined;
|
|
411
|
+
const query = Object.fromEntries(url.searchParams);
|
|
412
|
+
const event = {
|
|
413
|
+
requestId: crypto.randomUUID(),
|
|
414
|
+
timestamp: new Date(startMs).toISOString(),
|
|
415
|
+
method,
|
|
416
|
+
path: url.pathname,
|
|
417
|
+
action,
|
|
418
|
+
...(collection !== undefined && { collection }),
|
|
419
|
+
...(slug !== undefined && { slug }),
|
|
420
|
+
statusCode: 0,
|
|
421
|
+
durationMs: 0,
|
|
422
|
+
...(actor !== undefined && { actor }),
|
|
423
|
+
...(Object.keys(query).length > 0 && { request: { query } }),
|
|
424
|
+
};
|
|
425
|
+
try {
|
|
426
|
+
const response = await fn(req, ctx);
|
|
427
|
+
event.statusCode = response.status;
|
|
428
|
+
// Extract error details from 4xx/5xx responses
|
|
429
|
+
if (response.status >= 400) {
|
|
430
|
+
try {
|
|
431
|
+
const body = (await response.clone().json());
|
|
432
|
+
if (body?.error) {
|
|
433
|
+
event.error = {
|
|
434
|
+
message: body.error.message ?? 'Unknown error',
|
|
435
|
+
...(body.error.code ? { code: body.error.code } : {}),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
/* body parse failed — not JSON */
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return response;
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
event.statusCode = 500;
|
|
447
|
+
event.error = { message: err instanceof Error ? err.message : String(err) };
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
event.durationMs = Date.now() - startMs;
|
|
452
|
+
await emitAuditEvent(event);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
// Only wrap when at least one plugin listens for audit events (zero overhead otherwise)
|
|
457
|
+
const hasAuditPlugins = config.plugins.some((p) => p.hooks?.onAuditEvent);
|
|
458
|
+
if (!hasAuditPlugins)
|
|
459
|
+
return { GET, POST, PATCH, PUT, DELETE };
|
|
460
|
+
return {
|
|
461
|
+
GET: auditWrap('GET', GET),
|
|
462
|
+
POST: auditWrap('POST', POST),
|
|
463
|
+
PATCH: auditWrap('PATCH', PATCH),
|
|
464
|
+
PUT: auditWrap('PUT', PUT),
|
|
465
|
+
DELETE: auditWrap('DELETE', DELETE),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface CmsProxyOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Base path of the CMS API. Requests to paths under this prefix will be
|
|
4
|
+
* gated by the middleware. Default: `/api/cms`.
|
|
5
|
+
*/
|
|
6
|
+
basePath?: string;
|
|
7
|
+
/**
|
|
8
|
+
* CORS origins to allow. Defaults to same-origin only (no CORS header added).
|
|
9
|
+
* Pass `'*'` to allow any origin (not recommended for write routes).
|
|
10
|
+
*/
|
|
11
|
+
corsOrigins?: string | string[];
|
|
12
|
+
/**
|
|
13
|
+
* When `true`, the middleware checks for the presence of any recognized
|
|
14
|
+
* credential (Bearer, X-API-Key, X-Embed-Token, cookie, or ?embedToken)
|
|
15
|
+
* and returns 401 if none are found. Full verification still happens in
|
|
16
|
+
* the route handler — this is a first-pass edge gate only.
|
|
17
|
+
* Default: `false`.
|
|
18
|
+
*/
|
|
19
|
+
requireCredentials?: boolean;
|
|
20
|
+
}
|
|
21
|
+
type NextMiddlewareRequest = Request & {
|
|
22
|
+
nextUrl?: URL;
|
|
23
|
+
};
|
|
24
|
+
type NextMiddlewareResponse = Response;
|
|
25
|
+
/**
|
|
26
|
+
* Returns a Next.js-compatible Proxy function that applies CORS headers
|
|
27
|
+
* and an optional credential presence check to all CMS routes.
|
|
28
|
+
*
|
|
29
|
+
* Place in your project's `proxy.ts` (Next.js 16+) or `middleware.ts`:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { withCmsProxy } from '@airdraft/next'
|
|
33
|
+
* export default withCmsProxy({ basePath: '/api/cms', requireCredentials: true })
|
|
34
|
+
* export const config = { matcher: ['/api/cms/:path*'] }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function withCmsProxy(options?: CmsProxyOptions): (request: NextMiddlewareRequest) => NextMiddlewareResponse;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAE/B;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAED,KAAK,qBAAqB,GAAG,OAAO,GAAG;IAAE,OAAO,CAAC,EAAE,GAAG,CAAA;CAAE,CAAA;AACxD,KAAK,sBAAsB,GAAG,QAAQ,CAAA;AAEtC;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,eAAoB,IAI/B,SAAS,qBAAqB,KAAG,sBAAsB,CAiDjF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// withCmsProxy — Next.js Proxy (edge) handler for CMS route protection
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/**
|
|
5
|
+
* Returns a Next.js-compatible Proxy function that applies CORS headers
|
|
6
|
+
* and an optional credential presence check to all CMS routes.
|
|
7
|
+
*
|
|
8
|
+
* Place in your project's `proxy.ts` (Next.js 16+) or `middleware.ts`:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { withCmsProxy } from '@airdraft/next'
|
|
12
|
+
* export default withCmsProxy({ basePath: '/api/cms', requireCredentials: true })
|
|
13
|
+
* export const config = { matcher: ['/api/cms/:path*'] }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function withCmsProxy(options = {}) {
|
|
17
|
+
const basePath = options.basePath ?? '/api/cms';
|
|
18
|
+
const { corsOrigins, requireCredentials = false } = options;
|
|
19
|
+
return function cmsProxy(request) {
|
|
20
|
+
const url = request.nextUrl ?? new URL(request.url);
|
|
21
|
+
const pathname = url.pathname;
|
|
22
|
+
// Only act on CMS routes
|
|
23
|
+
if (!pathname.startsWith(basePath)) {
|
|
24
|
+
return new Response(null, { status: 200 });
|
|
25
|
+
}
|
|
26
|
+
// OPTIONS preflight
|
|
27
|
+
if (request.method === 'OPTIONS') {
|
|
28
|
+
return buildCorsResponse(request, corsOrigins);
|
|
29
|
+
}
|
|
30
|
+
// Credential presence check (edge gate — not full verification)
|
|
31
|
+
if (requireCredentials) {
|
|
32
|
+
const hasCredential = request.headers.has('authorization') ||
|
|
33
|
+
request.headers.has('x-api-key') ||
|
|
34
|
+
request.headers.has('x-embed-token') ||
|
|
35
|
+
hasCmsSessionCookie(request.headers.get('cookie')) ||
|
|
36
|
+
url.searchParams.has('embedToken');
|
|
37
|
+
if (!hasCredential) {
|
|
38
|
+
return new Response(JSON.stringify({
|
|
39
|
+
error: { code: 'UNAUTHORIZED', message: 'Authentication required', details: {} },
|
|
40
|
+
}), {
|
|
41
|
+
status: 401,
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
...corsHeaders(request, corsOrigins),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Pass through — add CORS headers to response
|
|
50
|
+
const response = new Response(null, { status: 200 });
|
|
51
|
+
if (corsOrigins) {
|
|
52
|
+
const h = corsHeaders(request, corsOrigins);
|
|
53
|
+
for (const [k, v] of Object.entries(h)) {
|
|
54
|
+
response.headers.set(k, v);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return response;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Helpers
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
function hasCmsSessionCookie(cookieHeader) {
|
|
64
|
+
if (!cookieHeader)
|
|
65
|
+
return false;
|
|
66
|
+
return cookieHeader.split(';').some((c) => c.trim().startsWith('airdraft_session='));
|
|
67
|
+
}
|
|
68
|
+
function corsHeaders(request, origins) {
|
|
69
|
+
if (!origins)
|
|
70
|
+
return {};
|
|
71
|
+
const requestOrigin = request.headers.get('origin') ?? '';
|
|
72
|
+
const allowed = origins === '*' ||
|
|
73
|
+
(Array.isArray(origins) ? origins.includes(requestOrigin) : origins === requestOrigin);
|
|
74
|
+
if (!allowed)
|
|
75
|
+
return {};
|
|
76
|
+
return {
|
|
77
|
+
'Access-Control-Allow-Origin': requestOrigin || '*',
|
|
78
|
+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
79
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key, X-Embed-Token',
|
|
80
|
+
'Access-Control-Max-Age': '86400',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function buildCorsResponse(request, origins) {
|
|
84
|
+
return new Response(null, {
|
|
85
|
+
status: 204,
|
|
86
|
+
headers: corsHeaders(request, origins),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface RevalidationWebhookOptions {
|
|
2
|
+
secret: string;
|
|
3
|
+
onRevalidate?: (paths: string[], payload: WebhookPayload) => Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface WebhookPayload {
|
|
6
|
+
action: 'entry.created' | 'entry.updated' | 'entry.deleted';
|
|
7
|
+
collection: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
paths: string[];
|
|
10
|
+
timestamp: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Next.js App Router POST handler for the Airdraft revalidation webhook.
|
|
14
|
+
*
|
|
15
|
+
* Verifies `X-Webhook-Signature: sha256=<hex>` using HMAC-SHA256 over the raw body,
|
|
16
|
+
* then calls `revalidatePath` (or `onRevalidate`) for each path in the payload.
|
|
17
|
+
*
|
|
18
|
+
* Usage in `app/api/cms/revalidate/route.ts`:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* export const { POST } = createRevalidationHandler({
|
|
22
|
+
* secret: process.env.AIRDRAFT_REVALIDATE_SECRET!,
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function createRevalidationHandler(opts: RevalidationWebhookOptions): {
|
|
27
|
+
POST: (req: Request) => Promise<Response>;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=revalidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revalidation.d.ts","sourceRoot":"","sources":["../src/revalidation.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,eAAe,GAAG,eAAe,GAAG,eAAe,CAAA;IAC3D,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,0BAA0B;gBAC/C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAoDrD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a Next.js App Router POST handler for the Airdraft revalidation webhook.
|
|
4
|
+
*
|
|
5
|
+
* Verifies `X-Webhook-Signature: sha256=<hex>` using HMAC-SHA256 over the raw body,
|
|
6
|
+
* then calls `revalidatePath` (or `onRevalidate`) for each path in the payload.
|
|
7
|
+
*
|
|
8
|
+
* Usage in `app/api/cms/revalidate/route.ts`:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* export const { POST } = createRevalidationHandler({
|
|
12
|
+
* secret: process.env.AIRDRAFT_REVALIDATE_SECRET!,
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function createRevalidationHandler(opts) {
|
|
17
|
+
async function POST(req) {
|
|
18
|
+
const signature = req.headers.get('x-webhook-signature');
|
|
19
|
+
if (!signature) {
|
|
20
|
+
return errorJson(401, 'UNAUTHORIZED', 'Missing X-Webhook-Signature header');
|
|
21
|
+
}
|
|
22
|
+
const rawBody = await req.text();
|
|
23
|
+
// Verify HMAC-SHA256 signature
|
|
24
|
+
const expected = `sha256=${createHmac('sha256', opts.secret).update(rawBody).digest('hex')}`;
|
|
25
|
+
const expectedBuf = Buffer.from(expected);
|
|
26
|
+
const actualBuf = Buffer.from(signature);
|
|
27
|
+
if (expectedBuf.length !== actualBuf.length ||
|
|
28
|
+
!timingSafeEqual(expectedBuf, actualBuf)) {
|
|
29
|
+
return errorJson(401, 'UNAUTHORIZED', 'Invalid webhook signature');
|
|
30
|
+
}
|
|
31
|
+
let payload;
|
|
32
|
+
try {
|
|
33
|
+
payload = JSON.parse(rawBody);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return errorJson(400, 'VALIDATION_ERROR', 'Invalid JSON payload');
|
|
37
|
+
}
|
|
38
|
+
if (!Array.isArray(payload.paths)) {
|
|
39
|
+
return errorJson(400, 'VALIDATION_ERROR', '"paths" must be an array');
|
|
40
|
+
}
|
|
41
|
+
if (opts.onRevalidate) {
|
|
42
|
+
await opts.onRevalidate(payload.paths, payload);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Default: attempt Next.js revalidatePath if available (edge-safe dynamic import)
|
|
46
|
+
try {
|
|
47
|
+
const { revalidatePath } = await import('next/cache');
|
|
48
|
+
for (const p of payload.paths) {
|
|
49
|
+
revalidatePath(p);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Not running in a Next.js context — caller should use onRevalidate instead
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return new Response(JSON.stringify({ ok: true, revalidated: payload.paths }), {
|
|
57
|
+
status: 200,
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return { POST };
|
|
62
|
+
}
|
|
63
|
+
function errorJson(status, code, message) {
|
|
64
|
+
return new Response(JSON.stringify({ error: { code, message, details: {} } }), {
|
|
65
|
+
status,
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=revalidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revalidation.js","sourceRoot":"","sources":["../src/revalidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAezD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAgC;IACxE,KAAK,UAAU,IAAI,CAAC,GAAY;QAC9B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAA;QACxD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,oCAAoC,CAAC,CAAA;QAC7E,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAEhC,+BAA+B;QAC/B,MAAM,QAAQ,GAAG,UAAU,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;QAC5F,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACzC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAExC,IACE,WAAW,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;YACvC,CAAC,eAAe,CAAC,WAAW,EAAE,SAAS,CAAC,EACxC,CAAC;YACD,OAAO,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,2BAA2B,CAAC,CAAA;QACpE,CAAC;QAED,IAAI,OAAuB,CAAA;QAC3B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAA;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,sBAAsB,CAAC,CAAA;QACnE,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAClC,OAAO,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,0BAA0B,CAAC,CAAA;QACvE,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QACjD,CAAC;aAAM,CAAC;YACN,kFAAkF;YAClF,IAAI,CAAC;gBACH,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAA;gBACrD,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;oBAC9B,cAAc,CAAC,CAAC,CAAC,CAAA;gBACnB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4EAA4E;YAC9E,CAAC;QACH,CAAC;QAED,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE;YAC5E,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAA;AACjB,CAAC;AAED,SAAS,SAAS,CAAC,MAAc,EAAE,IAAY,EAAE,OAAe;IAC9D,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE;QAC7E,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface AirdraftTokenProviderOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Airdraft project API key (`ntk_*`). Typically from
|
|
4
|
+
* `process.env.AIRDRAFT_PROJECT_KEY`.
|
|
5
|
+
*/
|
|
6
|
+
projectKey: string;
|
|
7
|
+
/**
|
|
8
|
+
* Override the relay base URL. Reads `AIRDRAFT_API_URL` env var when omitted.
|
|
9
|
+
* Default: `https://api.airdraft.space`.
|
|
10
|
+
*/
|
|
11
|
+
relayUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns a `tokenProvider` function for use with `GitHubAdapter` in mode 2
|
|
15
|
+
* (self-hosted + Airdraft shared GitHub App).
|
|
16
|
+
*
|
|
17
|
+
* Exchanges the project API key for a GitHub installation access token via the
|
|
18
|
+
* Airdraft token relay API. Tokens are cached in-memory for the lifetime of
|
|
19
|
+
* the process and refreshed 5 minutes before expiry.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* import { createAirdraftTokenProvider } from '@airdraft/next'
|
|
23
|
+
*
|
|
24
|
+
* new GitHubAdapter({
|
|
25
|
+
* tokenProvider: createAirdraftTokenProvider({
|
|
26
|
+
* projectKey: process.env.AIRDRAFT_PROJECT_KEY!,
|
|
27
|
+
* }),
|
|
28
|
+
* repo: process.env.GITCMS_REPO!,
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function createAirdraftTokenProvider(options: AirdraftTokenProviderOptions): () => Promise<string>;
|
|
33
|
+
//# sourceMappingURL=tokenProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenProvider.d.ts","sourceRoot":"","sources":["../src/tokenProvider.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAeD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,MAAM,OAAO,CAAC,MAAM,CAAC,CAkDvB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AdapterError } from '@airdraft/core';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// createAirdraftTokenProvider
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const RELAY_PATH = '/v1/github/token';
|
|
6
|
+
const EXPIRY_BUFFER_MS = 5 * 60 * 1000; // refresh 5 minutes before expiry
|
|
7
|
+
/**
|
|
8
|
+
* Returns a `tokenProvider` function for use with `GitHubAdapter` in mode 2
|
|
9
|
+
* (self-hosted + Airdraft shared GitHub App).
|
|
10
|
+
*
|
|
11
|
+
* Exchanges the project API key for a GitHub installation access token via the
|
|
12
|
+
* Airdraft token relay API. Tokens are cached in-memory for the lifetime of
|
|
13
|
+
* the process and refreshed 5 minutes before expiry.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { createAirdraftTokenProvider } from '@airdraft/next'
|
|
17
|
+
*
|
|
18
|
+
* new GitHubAdapter({
|
|
19
|
+
* tokenProvider: createAirdraftTokenProvider({
|
|
20
|
+
* projectKey: process.env.AIRDRAFT_PROJECT_KEY!,
|
|
21
|
+
* }),
|
|
22
|
+
* repo: process.env.GITCMS_REPO!,
|
|
23
|
+
* })
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function createAirdraftTokenProvider(options) {
|
|
27
|
+
const { projectKey } = options;
|
|
28
|
+
const baseUrl = options.relayUrl ??
|
|
29
|
+
(typeof process !== 'undefined' ? process.env['AIRDRAFT_API_URL'] : undefined) ??
|
|
30
|
+
'https://api.airdraft.space';
|
|
31
|
+
if (!projectKey || !projectKey.startsWith('ntk_')) {
|
|
32
|
+
throw new AdapterError('createAirdraftTokenProvider: projectKey must be a valid ntk_ API key');
|
|
33
|
+
}
|
|
34
|
+
const relayUrl = `${baseUrl.replace(/\/$/, '')}${RELAY_PATH}`;
|
|
35
|
+
let cached = null;
|
|
36
|
+
return async function tokenProvider() {
|
|
37
|
+
// Return cached token if still fresh
|
|
38
|
+
if (cached && cached.expiresAt - Date.now() > EXPIRY_BUFFER_MS) {
|
|
39
|
+
return cached.token;
|
|
40
|
+
}
|
|
41
|
+
const res = await fetch(relayUrl, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
Authorization: `Bearer ${projectKey}`,
|
|
45
|
+
Accept: 'application/json',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (res.status === 401) {
|
|
49
|
+
throw new AdapterError('Airdraft token relay: invalid or revoked project API key (INVALID_KEY)');
|
|
50
|
+
}
|
|
51
|
+
if (res.status === 404) {
|
|
52
|
+
throw new AdapterError('Airdraft token relay: project has no GitHub connection (GITHUB_NOT_CONNECTED)');
|
|
53
|
+
}
|
|
54
|
+
if (res.status === 503) {
|
|
55
|
+
throw new AdapterError('Airdraft token relay: GitHub token API unavailable (GITHUB_UNAVAILABLE)');
|
|
56
|
+
}
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new AdapterError(`Airdraft token relay: unexpected status ${res.status}`);
|
|
59
|
+
}
|
|
60
|
+
const body = await res.json();
|
|
61
|
+
cached = {
|
|
62
|
+
token: body.token,
|
|
63
|
+
expiresAt: new Date(body.expiresAt).getTime(),
|
|
64
|
+
};
|
|
65
|
+
return cached.token;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=tokenProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenProvider.js","sourceRoot":"","sources":["../src/tokenProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAyB7C,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E,MAAM,UAAU,GAAG,kBAAkB,CAAA;AACrC,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,kCAAkC;AAEzE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,2BAA2B,CACzC,OAAqC;IAErC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAA;IAC9B,MAAM,OAAO,GACX,OAAO,CAAC,QAAQ;QAChB,CAAC,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9E,4BAA4B,CAAA;IAE9B,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,YAAY,CACpB,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,UAAU,EAAE,CAAA;IAC7D,IAAI,MAAM,GAAuB,IAAI,CAAA;IAErC,OAAO,KAAK,UAAU,aAAa;QACjC,qCAAqC;QACrC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,EAAE,CAAC;YAC/D,OAAO,MAAM,CAAC,KAAK,CAAA;QACrB,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,UAAU,EAAE;gBACrC,MAAM,EAAE,kBAAkB;aAC3B;SACF,CAAC,CAAA;QAEF,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,YAAY,CAAC,wEAAwE,CAAC,CAAA;QAClG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,YAAY,CAAC,+EAA+E,CAAC,CAAA;QACzG,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,YAAY,CAAC,yEAAyE,CAAC,CAAA;QACnG,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,YAAY,CAAC,2CAA2C,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;QACjF,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA0C,CAAA;QACrE,MAAM,GAAG;YACP,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;SAC9C,CAAA;QACD,OAAO,MAAM,CAAC,KAAK,CAAA;IACrB,CAAC,CAAA;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@airdraft/next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Airdraft Next.js App Router integration — route handlers, OpenAPI, revalidation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./forwarder": {
|
|
14
|
+
"import": "./dist/forwarder.js",
|
|
15
|
+
"types": "./dist/forwarder.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist", "README.md", "CHANGELOG.md"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"clean": "rm -rf dist",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"release": "standard-version",
|
|
28
|
+
"release:patch": "standard-version --release-as patch",
|
|
29
|
+
"release:minor": "standard-version --release-as minor",
|
|
30
|
+
"release:major": "standard-version --release-as major"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": { "access": "public" },
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"next": ">=14.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.0.0",
|
|
39
|
+
"next": "^14.0.0",
|
|
40
|
+
"standard-version": "^9.5.0",
|
|
41
|
+
"tsx": "^4.7.0",
|
|
42
|
+
"typescript": "^5.4.0",
|
|
43
|
+
"vitest": "^1.5.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@airdraft/core": "*",
|
|
47
|
+
"zod": "^3.23.0"
|
|
48
|
+
},
|
|
49
|
+
"engines": { "node": ">=18.0.0" }
|
|
50
|
+
}
|