@valkyrianlabs/payload-markdown-docs 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -110,12 +110,20 @@ const docsLinks = await getPayloadMarkdownDocsLinks({ payload })
110
110
 
111
111
  ## Validate Locally
112
112
 
113
+ In an app or docs repository that has installed this package:
114
+
113
115
  ```bash
114
116
  pnpm exec payload-markdown-docs validate ./docs --source payload-markdown-docs
115
117
  pnpm exec payload-markdown-docs manifest ./docs --source payload-markdown-docs --pretty
116
118
  pnpm exec payload-markdown-docs plan ./docs --source payload-markdown-docs
117
119
  ```
118
120
 
121
+ From this package source checkout, use the local source CLI instead:
122
+
123
+ ```bash
124
+ pnpm cli validate ./docs --source payload-markdown-docs
125
+ ```
126
+
119
127
  In GitHub Actions, `--source` can be omitted when the docs set slug matches the
120
128
  repository name. The CLI infers it from `GITHUB_REPOSITORY`.
121
129
 
@@ -1,2 +1,2 @@
1
1
  import type { UIFieldServerProps } from 'payload';
2
- export declare const DocsSetManager: ({ id, field, payload, req, }: UIFieldServerProps) => Promise<import("react/jsx-runtime").JSX.Element>;
2
+ export declare const DocsSetManager: ({ id, field, payload, req }: UIFieldServerProps) => Promise<import("react/jsx-runtime").JSX.Element>;
@@ -18,6 +18,15 @@ const formatDate = (value)=>{
18
18
  }
19
19
  return date.toISOString();
20
20
  };
21
+ const normalizeRoute = (route = '/')=>{
22
+ const normalized = `/${route.trim()}`.replace(/\/+/g, '/');
23
+ return normalized.length > 1 ? normalized.replace(/\/+$/g, '') : normalized;
24
+ };
25
+ const getPublishAction = ({ apiRoute, docsSetId, docsSetsCollectionSlug, redirect })=>{
26
+ const path = `${normalizeRoute(apiRoute ?? '/api')}/${docsSetsCollectionSlug}/${encodeURIComponent(String(docsSetId))}/publish-generated-docs`;
27
+ return `${path}?redirect=${encodeURIComponent(redirect)}`;
28
+ };
29
+ const getDocsSetAdminURL = ({ adminRoute, docsSetId, docsSetsCollectionSlug })=>`${normalizeRoute(adminRoute ?? '/admin')}/collections/${docsSetsCollectionSlug}/${encodeURIComponent(String(docsSetId))}`;
21
30
  const StatusLabel = ({ item })=>{
22
31
  if (item.archived) {
23
32
  return /*#__PURE__*/ _jsx("span", {
@@ -210,6 +219,7 @@ export const DocsSetManager = async ({ id, field, payload, req })=>{
210
219
  const docsCollectionSlug = custom.docsCollectionSlug ?? DEFAULT_DOCS_COLLECTION_SLUG;
211
220
  const docsGroupsCollectionSlug = custom.docsGroupsCollectionSlug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG;
212
221
  const docsSetsCollectionSlug = custom.docsSetsCollectionSlug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG;
222
+ const canPublishGeneratedDocs = custom.docsEnableDrafts === true && custom.allowPublish === true;
213
223
  if (!id) {
214
224
  return /*#__PURE__*/ _jsxs("section", {
215
225
  children: [
@@ -230,6 +240,17 @@ export const DocsSetManager = async ({ id, field, payload, req })=>{
230
240
  docsSetsCollectionSlug,
231
241
  payload: payload
232
242
  });
243
+ const docsSetAdminURL = getDocsSetAdminURL({
244
+ adminRoute: req.payload.config.routes.admin,
245
+ docsSetId: id,
246
+ docsSetsCollectionSlug
247
+ });
248
+ const publishAction = getPublishAction({
249
+ apiRoute: req.payload.config.routes.api,
250
+ docsSetId: id,
251
+ docsSetsCollectionSlug,
252
+ redirect: docsSetAdminURL
253
+ });
233
254
  return /*#__PURE__*/ _jsxs("section", {
234
255
  children: [
235
256
  /*#__PURE__*/ _jsxs("header", {
@@ -263,7 +284,27 @@ export const DocsSetManager = async ({ id, field, payload, req })=>{
263
284
  }),
264
285
  /*#__PURE__*/ _jsx(Summary, {
265
286
  data: data
266
- })
287
+ }),
288
+ data.summary.drafts > 0 ? /*#__PURE__*/ _jsxs("div", {
289
+ children: [
290
+ /*#__PURE__*/ _jsxs("p", {
291
+ children: [
292
+ data.summary.drafts,
293
+ " generated docs records are drafts and are not public."
294
+ ]
295
+ }),
296
+ canPublishGeneratedDocs ? /*#__PURE__*/ _jsx("form", {
297
+ action: publishAction,
298
+ method: "post",
299
+ children: /*#__PURE__*/ _jsx("button", {
300
+ type: "submit",
301
+ children: "Publish generated docs"
302
+ })
303
+ }) : /*#__PURE__*/ _jsx("p", {
304
+ children: "Publishing is disabled for this plugin config."
305
+ })
306
+ ]
307
+ }) : null
267
308
  ]
268
309
  }),
269
310
  data.warnings.length > 0 ? /*#__PURE__*/ _jsxs("section", {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/DocsSetManager.tsx"],"sourcesContent":["import type { UIFieldServerProps } from 'payload'\nimport type { ReactNode } from 'react'\n\nimport type {\n DocsSetManagerData,\n DocsSetManagerDocItem,\n DocsSetManagerPayloadOperations,\n} from './docsSetManagerTypes.js'\n\nimport {\n DEFAULT_DOCS_COLLECTION_SLUG,\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n} from '../constants.js'\nimport { getDocsSetManagerData } from './docsSetManagerData.js'\n\ntype DocsSetManagerFieldCustom = {\n docsCollectionSlug?: string\n docsGroupsCollectionSlug?: string\n docsSetsCollectionSlug?: string\n}\n\nconst getFieldCustom = (\n field: UIFieldServerProps['field'],\n): DocsSetManagerFieldCustom => {\n const custom = 'custom' in field ? field.custom : undefined\n\n if (!custom || typeof custom !== 'object') {\n return {}\n }\n\n return custom as DocsSetManagerFieldCustom\n}\n\nconst formatDate = (value?: string): string => {\n if (!value) {\n return 'Never'\n }\n\n const date = new Date(value)\n\n if (Number.isNaN(date.getTime())) {\n return value\n }\n\n return date.toISOString()\n}\n\nconst StatusLabel = ({ item }: { item: DocsSetManagerDocItem }) => {\n if (item.archived) {\n return <span>archived</span>\n }\n\n if (item.draft) {\n return <span>draft</span>\n }\n\n if (item.published) {\n return <span>published</span>\n }\n\n return <span>synced</span>\n}\n\nconst OverrideSummary = ({ item }: { item: DocsSetManagerDocItem }) => {\n if (item.overrideSummary.length === 0) {\n return <span>none</span>\n }\n\n return <span>{item.overrideSummary.join(', ')}</span>\n}\n\nconst renderDocItem = (item: DocsSetManagerDocItem): ReactNode => {\n if (item.kind === 'folder') {\n return (\n <details key={item.id}>\n <summary>{item.title}</summary>\n <div>\n {item.children?.map((child) => renderDocItem(child))}\n </div>\n </details>\n )\n }\n\n return (\n <details key={item.id}>\n <summary>{item.sourcePath}</summary>\n <dl>\n <div>\n <dt>Route</dt>\n <dd>{item.route || 'Missing route'}</dd>\n </div>\n <div>\n <dt>Title</dt>\n <dd>{item.title}</dd>\n </div>\n <div>\n <dt>Status</dt>\n <dd>\n <StatusLabel item={item} />\n </dd>\n </div>\n <div>\n <dt>Overrides</dt>\n <dd>\n <OverrideSummary item={item} />\n </dd>\n </div>\n </dl>\n {item.adminURL ? <a href={item.adminURL}>Open generated doc</a> : null}\n </details>\n )\n}\n\nconst Summary = ({ data }: { data: DocsSetManagerData }) => (\n <dl>\n <div>\n <dt>Docs</dt>\n <dd>{data.summary.total}</dd>\n </div>\n <div>\n <dt>Archived</dt>\n <dd>{data.summary.archived}</dd>\n </div>\n <div>\n <dt>Drafts</dt>\n <dd>{data.summary.drafts}</dd>\n </div>\n <div>\n <dt>Published</dt>\n <dd>{data.summary.published}</dd>\n </div>\n <div>\n <dt>Hidden from nav</dt>\n <dd>{data.summary.hiddenFromNav}</dd>\n </div>\n <div>\n <dt>With overrides</dt>\n <dd>{data.summary.withOverrides}</dd>\n </div>\n <div>\n <dt>Last sync</dt>\n <dd>{formatDate(data.sync?.lastSyncedAt)}</dd>\n </div>\n <div>\n <dt>Last status</dt>\n <dd>{data.sync?.lastStatus ?? 'unknown'}</dd>\n </div>\n </dl>\n)\n\nexport const DocsSetManager = async ({\n id,\n field,\n payload,\n req,\n}: UIFieldServerProps) => {\n const custom = getFieldCustom(field)\n const docsCollectionSlug = custom.docsCollectionSlug ?? DEFAULT_DOCS_COLLECTION_SLUG\n const docsGroupsCollectionSlug =\n custom.docsGroupsCollectionSlug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG\n const docsSetsCollectionSlug =\n custom.docsSetsCollectionSlug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG\n\n if (!id) {\n return (\n <section>\n <h2>Generated Docs</h2>\n <p>Save this docs set before reviewing generated docs records.</p>\n </section>\n )\n }\n\n const data = await getDocsSetManagerData({\n adminRoute: req.payload.config.routes.admin,\n docsCollectionSlug,\n docsGroupsCollectionSlug,\n docsSetId: String(id),\n docsSetsCollectionSlug,\n payload: payload as DocsSetManagerPayloadOperations,\n })\n\n return (\n <section>\n <header>\n <h2>Generated Docs</h2>\n <p>\n Review generated docs records for {data.docsSet.title}. Source docs remain\n Git-backed; per-doc overrides can be edited by opening a generated doc.\n </p>\n </header>\n\n <section>\n <h3>Effective Route</h3>\n <p>{data.docsSet.routeBase || 'No route available yet'}</p>\n </section>\n\n <section>\n <h3>Sync Summary</h3>\n <Summary data={data} />\n </section>\n\n {data.warnings.length > 0 ? (\n <section>\n <h3>Warnings</h3>\n <ul>\n {data.warnings.map((warning) => (\n <li key={`${warning.docId ?? 'docs-set'}:${warning.message}`}>\n {warning.sourcePath ? `${warning.sourcePath}: ` : null}\n {warning.message}\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n\n <section>\n <h3>Generated Docs</h3>\n {data.tree.length > 0 ? (\n <div>{data.tree.map((item) => renderDocItem(item))}</div>\n ) : (\n <p>No generated docs records are linked to this docs set yet.</p>\n )}\n </section>\n </section>\n )\n}\n"],"names":["DEFAULT_DOCS_COLLECTION_SLUG","DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","getDocsSetManagerData","getFieldCustom","field","custom","undefined","formatDate","value","date","Date","Number","isNaN","getTime","toISOString","StatusLabel","item","archived","span","draft","published","OverrideSummary","overrideSummary","length","join","renderDocItem","kind","details","summary","title","div","children","map","child","id","sourcePath","dl","dt","dd","route","adminURL","a","href","Summary","data","total","drafts","hiddenFromNav","withOverrides","sync","lastSyncedAt","lastStatus","DocsSetManager","payload","req","docsCollectionSlug","docsGroupsCollectionSlug","docsSetsCollectionSlug","section","h2","p","adminRoute","config","routes","admin","docsSetId","String","header","docsSet","h3","routeBase","warnings","ul","warning","li","message","docId","tree"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/admin/DocsSetManager.tsx"],"sourcesContent":["import type { UIFieldServerProps } from 'payload'\nimport type { ReactNode } from 'react'\n\nimport type {\n DocsSetManagerData,\n DocsSetManagerDocItem,\n DocsSetManagerPayloadOperations,\n} from './docsSetManagerTypes.js'\n\nimport {\n DEFAULT_DOCS_COLLECTION_SLUG,\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n} from '../constants.js'\nimport { getDocsSetManagerData } from './docsSetManagerData.js'\n\ntype DocsSetManagerFieldCustom = {\n allowPublish?: boolean\n docsCollectionSlug?: string\n docsEnableDrafts?: boolean\n docsGroupsCollectionSlug?: string\n docsSetsCollectionSlug?: string\n}\n\nconst getFieldCustom = (field: UIFieldServerProps['field']): DocsSetManagerFieldCustom => {\n const custom = 'custom' in field ? field.custom : undefined\n\n if (!custom || typeof custom !== 'object') {\n return {}\n }\n\n return custom as DocsSetManagerFieldCustom\n}\n\nconst formatDate = (value?: string): string => {\n if (!value) {\n return 'Never'\n }\n\n const date = new Date(value)\n\n if (Number.isNaN(date.getTime())) {\n return value\n }\n\n return date.toISOString()\n}\n\nconst normalizeRoute = (route = '/'): string => {\n const normalized = `/${route.trim()}`.replace(/\\/+/g, '/')\n\n return normalized.length > 1 ? normalized.replace(/\\/+$/g, '') : normalized\n}\n\nconst getPublishAction = ({\n apiRoute,\n docsSetId,\n docsSetsCollectionSlug,\n redirect,\n}: {\n apiRoute?: string\n docsSetId: number | string\n docsSetsCollectionSlug: string\n redirect: string\n}): string => {\n const path = `${normalizeRoute(apiRoute ?? '/api')}/${docsSetsCollectionSlug}/${encodeURIComponent(String(docsSetId))}/publish-generated-docs`\n\n return `${path}?redirect=${encodeURIComponent(redirect)}`\n}\n\nconst getDocsSetAdminURL = ({\n adminRoute,\n docsSetId,\n docsSetsCollectionSlug,\n}: {\n adminRoute?: string\n docsSetId: number | string\n docsSetsCollectionSlug: string\n}): string =>\n `${normalizeRoute(adminRoute ?? '/admin')}/collections/${docsSetsCollectionSlug}/${encodeURIComponent(String(docsSetId))}`\n\nconst StatusLabel = ({ item }: { item: DocsSetManagerDocItem }) => {\n if (item.archived) {\n return <span>archived</span>\n }\n\n if (item.draft) {\n return <span>draft</span>\n }\n\n if (item.published) {\n return <span>published</span>\n }\n\n return <span>synced</span>\n}\n\nconst OverrideSummary = ({ item }: { item: DocsSetManagerDocItem }) => {\n if (item.overrideSummary.length === 0) {\n return <span>none</span>\n }\n\n return <span>{item.overrideSummary.join(', ')}</span>\n}\n\nconst renderDocItem = (item: DocsSetManagerDocItem): ReactNode => {\n if (item.kind === 'folder') {\n return (\n <details key={item.id}>\n <summary>{item.title}</summary>\n <div>{item.children?.map((child) => renderDocItem(child))}</div>\n </details>\n )\n }\n\n return (\n <details key={item.id}>\n <summary>{item.sourcePath}</summary>\n <dl>\n <div>\n <dt>Route</dt>\n <dd>{item.route || 'Missing route'}</dd>\n </div>\n <div>\n <dt>Title</dt>\n <dd>{item.title}</dd>\n </div>\n <div>\n <dt>Status</dt>\n <dd>\n <StatusLabel item={item} />\n </dd>\n </div>\n <div>\n <dt>Overrides</dt>\n <dd>\n <OverrideSummary item={item} />\n </dd>\n </div>\n </dl>\n {item.adminURL ? <a href={item.adminURL}>Open generated doc</a> : null}\n </details>\n )\n}\n\nconst Summary = ({ data }: { data: DocsSetManagerData }) => (\n <dl>\n <div>\n <dt>Docs</dt>\n <dd>{data.summary.total}</dd>\n </div>\n <div>\n <dt>Archived</dt>\n <dd>{data.summary.archived}</dd>\n </div>\n <div>\n <dt>Drafts</dt>\n <dd>{data.summary.drafts}</dd>\n </div>\n <div>\n <dt>Published</dt>\n <dd>{data.summary.published}</dd>\n </div>\n <div>\n <dt>Hidden from nav</dt>\n <dd>{data.summary.hiddenFromNav}</dd>\n </div>\n <div>\n <dt>With overrides</dt>\n <dd>{data.summary.withOverrides}</dd>\n </div>\n <div>\n <dt>Last sync</dt>\n <dd>{formatDate(data.sync?.lastSyncedAt)}</dd>\n </div>\n <div>\n <dt>Last status</dt>\n <dd>{data.sync?.lastStatus ?? 'unknown'}</dd>\n </div>\n </dl>\n)\n\nexport const DocsSetManager = async ({ id, field, payload, req }: UIFieldServerProps) => {\n const custom = getFieldCustom(field)\n const docsCollectionSlug = custom.docsCollectionSlug ?? DEFAULT_DOCS_COLLECTION_SLUG\n const docsGroupsCollectionSlug =\n custom.docsGroupsCollectionSlug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG\n const docsSetsCollectionSlug = custom.docsSetsCollectionSlug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG\n const canPublishGeneratedDocs = custom.docsEnableDrafts === true && custom.allowPublish === true\n\n if (!id) {\n return (\n <section>\n <h2>Generated Docs</h2>\n <p>Save this docs set before reviewing generated docs records.</p>\n </section>\n )\n }\n\n const data = await getDocsSetManagerData({\n adminRoute: req.payload.config.routes.admin,\n docsCollectionSlug,\n docsGroupsCollectionSlug,\n docsSetId: String(id),\n docsSetsCollectionSlug,\n payload: payload as DocsSetManagerPayloadOperations,\n })\n const docsSetAdminURL = getDocsSetAdminURL({\n adminRoute: req.payload.config.routes.admin,\n docsSetId: id,\n docsSetsCollectionSlug,\n })\n const publishAction = getPublishAction({\n apiRoute: req.payload.config.routes.api,\n docsSetId: id,\n docsSetsCollectionSlug,\n redirect: docsSetAdminURL,\n })\n\n return (\n <section>\n <header>\n <h2>Generated Docs</h2>\n <p>\n Review generated docs records for {data.docsSet.title}. Source docs remain Git-backed;\n per-doc overrides can be edited by opening a generated doc.\n </p>\n </header>\n\n <section>\n <h3>Effective Route</h3>\n <p>{data.docsSet.routeBase || 'No route available yet'}</p>\n </section>\n\n <section>\n <h3>Sync Summary</h3>\n <Summary data={data} />\n {data.summary.drafts > 0 ? (\n <div>\n <p>{data.summary.drafts} generated docs records are drafts and are not public.</p>\n {canPublishGeneratedDocs ? (\n <form action={publishAction} method=\"post\">\n <button type=\"submit\">Publish generated docs</button>\n </form>\n ) : (\n <p>Publishing is disabled for this plugin config.</p>\n )}\n </div>\n ) : null}\n </section>\n\n {data.warnings.length > 0 ? (\n <section>\n <h3>Warnings</h3>\n <ul>\n {data.warnings.map((warning) => (\n <li key={`${warning.docId ?? 'docs-set'}:${warning.message}`}>\n {warning.sourcePath ? `${warning.sourcePath}: ` : null}\n {warning.message}\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n\n <section>\n <h3>Generated Docs</h3>\n {data.tree.length > 0 ? (\n <div>{data.tree.map((item) => renderDocItem(item))}</div>\n ) : (\n <p>No generated docs records are linked to this docs set yet.</p>\n )}\n </section>\n </section>\n )\n}\n"],"names":["DEFAULT_DOCS_COLLECTION_SLUG","DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","getDocsSetManagerData","getFieldCustom","field","custom","undefined","formatDate","value","date","Date","Number","isNaN","getTime","toISOString","normalizeRoute","route","normalized","trim","replace","length","getPublishAction","apiRoute","docsSetId","docsSetsCollectionSlug","redirect","path","encodeURIComponent","String","getDocsSetAdminURL","adminRoute","StatusLabel","item","archived","span","draft","published","OverrideSummary","overrideSummary","join","renderDocItem","kind","details","summary","title","div","children","map","child","id","sourcePath","dl","dt","dd","adminURL","a","href","Summary","data","total","drafts","hiddenFromNav","withOverrides","sync","lastSyncedAt","lastStatus","DocsSetManager","payload","req","docsCollectionSlug","docsGroupsCollectionSlug","canPublishGeneratedDocs","docsEnableDrafts","allowPublish","section","h2","p","config","routes","admin","docsSetAdminURL","publishAction","api","header","docsSet","h3","routeBase","form","action","method","button","type","warnings","ul","warning","li","message","docId","tree"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { signDocsSyncRequest } from '../../security/index.js';
2
+ import { DocsSyncKeyError, signDocsSyncRequest } from '../../security/index.js';
3
3
  import { buildDocsManifest, sha256Hex, validateDocsManifest } from '../../sync/index.js';
4
4
  import { readDocsAiExportManifest, walkDocsFiles } from '../filesystem.js';
5
5
  import { formatIssues, formatPushSummary, printJson } from '../format.js';
@@ -258,12 +258,22 @@ export const runPushCommand = async (args, httpPost = postJson, httpGet = getJso
258
258
  }
259
259
  };
260
260
  } else {
261
- signedRequest = signDocsSyncRequest({
262
- body,
263
- endpoint: options.endpoint,
264
- keyId: options.keyId,
265
- privateKey: options.privateKey
266
- });
261
+ try {
262
+ signedRequest = signDocsSyncRequest({
263
+ body,
264
+ endpoint: options.endpoint,
265
+ keyId: options.keyId,
266
+ privateKey: options.privateKey
267
+ });
268
+ } catch (error) {
269
+ if (error instanceof DocsSyncKeyError) {
270
+ return {
271
+ exitCode: 1,
272
+ stderr: `${error.message}\n`
273
+ };
274
+ }
275
+ throw error;
276
+ }
267
277
  }
268
278
  const response = await httpPost({
269
279
  body: signedRequest.body,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/push.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\n\nimport type { DocsDeleteBehavior } from '../../sync/index.js'\nimport type {\n HttpGetJson,\n HttpPostJson,\n} from '../http.js'\nimport type {\n CliResult,\n ParsedCliArgs,\n PushCommandOptions,\n} from '../types.js'\n\nimport { signDocsSyncRequest } from '../../security/index.js'\nimport {\n buildDocsManifest,\n sha256Hex,\n validateDocsManifest,\n} from '../../sync/index.js'\nimport {\n readDocsAiExportManifest,\n walkDocsFiles,\n} from '../filesystem.js'\nimport { formatIssues, formatPushSummary, printJson } from '../format.js'\nimport {\n getJson,\n postJson,\n} from '../http.js'\nimport { getFlagBoolean, getFlagString } from '../parseArgs.js'\nimport { getDocsCommandOptions } from './validate.js'\n\nconst supportedPushDeleteBehaviors = new Set<DocsDeleteBehavior>([\n 'archive',\n 'delete',\n 'draft',\n 'ignore',\n])\n\ntype ServerPushResponse = {\n deleteBehavior?: string\n effectivePublishMode?: string\n error?: {\n code?: string\n message?: string\n }\n ok?: boolean\n publishRequested?: boolean\n summary?: {\n archive?: number\n create?: number\n delete?: number\n draft?: number\n unchanged?: number\n update?: number\n warnings?: number\n }\n syncRunId?: string\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isServerPushResponse = (value: unknown): value is ServerPushResponse =>\n isRecord(value)\n\nconst validateEndpointUrl = (endpoint: string): CliResult | string => {\n try {\n const parsed = new URL(endpoint)\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return {\n exitCode: 1,\n stderr: '--endpoint must be a full http:// or https:// URL.\\n',\n }\n }\n\n return parsed.toString()\n } catch {\n return {\n exitCode: 1,\n stderr: '--endpoint must be a valid full http:// or https:// URL.\\n',\n }\n }\n}\n\nconst readPrivateKey = async (\n args: ParsedCliArgs,\n): Promise<CliResult | string> => {\n const privateKeyFile = getFlagString(args, 'private-key-file')\n const privateKeyEnv = getFlagString(args, 'private-key-env')\n\n if (privateKeyFile && privateKeyEnv) {\n return {\n exitCode: 1,\n stderr:\n 'Use either --private-key-file or --private-key-env, not both.\\n',\n }\n }\n\n if (!privateKeyFile && !privateKeyEnv) {\n return {\n exitCode: 1,\n stderr: 'Push requires --private-key-file or --private-key-env.\\n',\n }\n }\n\n if (privateKeyEnv) {\n const privateKey = process.env[privateKeyEnv]\n\n if (!privateKey) {\n return {\n exitCode: 1,\n stderr: `Environment variable \"${privateKeyEnv}\" is not set.\\n`,\n }\n }\n\n return privateKey\n }\n\n try {\n return await readFile(privateKeyFile ?? '', 'utf8')\n } catch (error) {\n return {\n exitCode: 1,\n stderr:\n error instanceof Error\n ? `Could not read private key file: ${error.message}\\n`\n : 'Could not read private key file.\\n',\n }\n }\n}\n\nconst getGithubOidcTokenRequestUrl = ({\n audience,\n requestUrl,\n}: {\n audience: string\n requestUrl: string\n}): CliResult | string => {\n try {\n const url = new URL(requestUrl)\n url.searchParams.set('audience', audience)\n\n return url.toString()\n } catch {\n return {\n exitCode: 1,\n stderr: 'ACTIONS_ID_TOKEN_REQUEST_URL is not a valid URL.\\n',\n }\n }\n}\n\nconst readGithubOidcToken = async ({\n args,\n audience,\n httpGet,\n}: {\n args: ParsedCliArgs\n audience: string\n httpGet: HttpGetJson\n}): Promise<CliResult | string> => {\n const tokenEnv = getFlagString(args, 'oidc-token-env')\n\n if (tokenEnv) {\n const token = process.env[tokenEnv]\n\n if (!token) {\n return {\n exitCode: 1,\n stderr: `Environment variable \"${tokenEnv}\" is not set.\\n`,\n }\n }\n\n return token\n }\n\n const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL\n const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN\n\n if (!requestUrl || !requestToken) {\n return {\n exitCode: 1,\n stderr:\n 'GitHub OIDC push requires ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN, or --oidc-token-env.\\n',\n }\n }\n\n const url = getGithubOidcTokenRequestUrl({\n audience,\n requestUrl,\n })\n\n if (typeof url !== 'string') {\n return url\n }\n\n const response = await httpGet({\n headers: {\n Authorization: `bearer ${requestToken}`,\n },\n url,\n })\n\n if (!response.ok || !isRecord(response.body) || typeof response.body.value !== 'string') {\n return {\n exitCode: 1,\n stderr: `Could not retrieve GitHub OIDC token. HTTP status ${response.status}.\\n`,\n }\n }\n\n return response.body.value\n}\n\nconst getPushCommandOptions = async (\n args: ParsedCliArgs,\n): Promise<CliResult | PushCommandOptions> => {\n const docsOptions = getDocsCommandOptions(args)\n\n if ('exitCode' in docsOptions) {\n return docsOptions\n }\n\n const endpointFlag = getFlagString(args, 'endpoint')\n\n if (!endpointFlag) {\n return {\n exitCode: 1,\n stderr: 'Push requires --endpoint <url>.\\n',\n }\n }\n\n const endpoint = validateEndpointUrl(endpointFlag)\n\n if (typeof endpoint !== 'string') {\n return endpoint\n }\n\n if (getFlagBoolean(args, 'dry-run') && getFlagBoolean(args, 'sync')) {\n return {\n exitCode: 1,\n stderr: 'Use either --dry-run or --sync, not both.\\n',\n }\n }\n\n const deleteBehaviorFlag = getFlagString(args, 'delete-behavior')\n\n if (\n deleteBehaviorFlag !== undefined &&\n !supportedPushDeleteBehaviors.has(deleteBehaviorFlag as DocsDeleteBehavior)\n ) {\n return {\n exitCode: 1,\n stderr: '--delete-behavior for push must be archive, delete, draft, or ignore.\\n',\n }\n }\n\n const mode: PushCommandOptions['mode'] = getFlagBoolean(args, 'sync')\n ? 'sync'\n : 'dry-run'\n const baseOptions = {\n ...docsOptions,\n deleteBehavior: deleteBehaviorFlag as DocsDeleteBehavior | undefined,\n endpoint,\n mode,\n publish: getFlagBoolean(args, 'publish'),\n }\n\n if (getFlagBoolean(args, 'github-oidc')) {\n if (getFlagString(args, 'key-id')) {\n return {\n exitCode: 1,\n stderr: 'Do not use --key-id with --github-oidc.\\n',\n }\n }\n\n if (getFlagString(args, 'private-key-file') || getFlagString(args, 'private-key-env')) {\n return {\n exitCode: 1,\n stderr: 'Do not use Ed25519 private key flags with --github-oidc.\\n',\n }\n }\n\n return {\n ...baseOptions,\n authMode: 'github-oidc',\n oidcTokenEnv: getFlagString(args, 'oidc-token-env'),\n }\n }\n\n const keyId = getFlagString(args, 'key-id')\n\n if (!keyId) {\n return {\n exitCode: 1,\n stderr: 'Push requires --key-id <id>.\\n',\n }\n }\n\n const privateKey = await readPrivateKey(args)\n\n if (typeof privateKey !== 'string') {\n return privateKey\n }\n\n return {\n ...baseOptions,\n authMode: 'ed25519',\n keyId,\n privateKey,\n }\n}\n\nconst formatServerFailure = ({\n body,\n status,\n}: {\n body: unknown\n status: number\n}): string => {\n if (isServerPushResponse(body) && body.error?.message) {\n return `${body.error.message}\\n`\n }\n\n return `Sync request failed with HTTP status ${status}.\\n`\n}\n\nexport const runPushCommand = async (\n args: ParsedCliArgs,\n httpPost: HttpPostJson = postJson,\n httpGet: HttpGetJson = getJson,\n): Promise<CliResult> => {\n const options = await getPushCommandOptions(args)\n\n if ('exitCode' in options) {\n return options\n }\n\n const files = await walkDocsFiles({\n root: options.docsRoot,\n })\n const aiExport = await readDocsAiExportManifest({\n root: options.docsRoot,\n })\n\n if (!aiExport.ok) {\n return {\n exitCode: 1,\n stderr: `AI export manifest is invalid.\\n\\nErrors:\\n${formatIssues(aiExport.issues)}\\n`,\n }\n }\n\n const manifest = buildDocsManifest({\n aiExport: aiExport.manifest,\n branch: options.branch,\n commit: options.commit,\n deleteBehavior: options.deleteBehavior ?? 'archive',\n files,\n mode: options.mode,\n publish: options.publish,\n repository: options.repository,\n sourceId: options.sourceId,\n })\n const validation = validateDocsManifest(manifest, {\n maxFileBytes: options.maxFileBytes,\n maxFiles: options.maxFiles,\n maxTotalBytes: options.maxTotalBytes,\n routeBase: `/${options.sourceId}`,\n })\n\n if (!validation.ok) {\n return {\n exitCode: 1,\n stderr: `Manifest is invalid.\\n\\nErrors:\\n${formatIssues(validation.issues)}\\n`,\n }\n }\n\n const body = JSON.stringify(manifest)\n let signedRequest:\n | {\n body: string\n headers: Record<string, string>\n }\n | ReturnType<typeof signDocsSyncRequest>\n\n if (options.authMode === 'github-oidc') {\n const oidcToken = await readGithubOidcToken({\n args,\n audience: options.sourceId,\n httpGet,\n })\n\n if (typeof oidcToken !== 'string') {\n return oidcToken\n }\n\n signedRequest = {\n body,\n headers: {\n Authorization: `Bearer ${oidcToken}`,\n 'Content-Type': 'application/json',\n 'X-VL-MD-DOCS-Body-SHA256': sha256Hex(body),\n },\n }\n } else {\n signedRequest = signDocsSyncRequest({\n body,\n endpoint: options.endpoint,\n keyId: options.keyId,\n privateKey: options.privateKey,\n })\n }\n\n const response = await httpPost({\n body: signedRequest.body,\n headers: signedRequest.headers,\n url: options.endpoint,\n })\n\n if (getFlagBoolean(args, 'json')) {\n return {\n exitCode:\n response.ok &&\n isServerPushResponse(response.body) &&\n response.body.ok === true\n ? 0\n : 1,\n stdout: printJson(\n {\n endpoint: options.endpoint,\n mode: options.mode,\n response: response.body,\n sourceId: options.sourceId,\n status: response.status,\n },\n getFlagBoolean(args, 'pretty'),\n ),\n }\n }\n\n if (\n !response.ok ||\n !isServerPushResponse(response.body) ||\n response.body.ok !== true\n ) {\n return {\n exitCode: 1,\n stderr: formatServerFailure({\n body: response.body,\n status: response.status,\n }),\n }\n }\n\n return {\n exitCode: 0,\n stdout: formatPushSummary({\n endpoint: options.endpoint,\n mode: options.mode,\n response: response.body,\n sourceId: options.sourceId,\n }),\n }\n}\n"],"names":["readFile","signDocsSyncRequest","buildDocsManifest","sha256Hex","validateDocsManifest","readDocsAiExportManifest","walkDocsFiles","formatIssues","formatPushSummary","printJson","getJson","postJson","getFlagBoolean","getFlagString","getDocsCommandOptions","supportedPushDeleteBehaviors","Set","isRecord","value","Array","isArray","isServerPushResponse","validateEndpointUrl","endpoint","parsed","URL","protocol","exitCode","stderr","toString","readPrivateKey","args","privateKeyFile","privateKeyEnv","privateKey","process","env","error","Error","message","getGithubOidcTokenRequestUrl","audience","requestUrl","url","searchParams","set","readGithubOidcToken","httpGet","tokenEnv","token","ACTIONS_ID_TOKEN_REQUEST_URL","requestToken","ACTIONS_ID_TOKEN_REQUEST_TOKEN","response","headers","Authorization","ok","body","status","getPushCommandOptions","docsOptions","endpointFlag","deleteBehaviorFlag","undefined","has","mode","baseOptions","deleteBehavior","publish","authMode","oidcTokenEnv","keyId","formatServerFailure","runPushCommand","httpPost","options","files","root","docsRoot","aiExport","issues","manifest","branch","commit","repository","sourceId","validation","maxFileBytes","maxFiles","maxTotalBytes","routeBase","JSON","stringify","signedRequest","oidcToken","stdout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/cli/commands/push.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\n\nimport type { DocsDeleteBehavior } from '../../sync/index.js'\nimport type {\n HttpGetJson,\n HttpPostJson,\n} from '../http.js'\nimport type {\n CliResult,\n ParsedCliArgs,\n PushCommandOptions,\n} from '../types.js'\n\nimport {\n DocsSyncKeyError,\n signDocsSyncRequest,\n} from '../../security/index.js'\nimport {\n buildDocsManifest,\n sha256Hex,\n validateDocsManifest,\n} from '../../sync/index.js'\nimport {\n readDocsAiExportManifest,\n walkDocsFiles,\n} from '../filesystem.js'\nimport { formatIssues, formatPushSummary, printJson } from '../format.js'\nimport {\n getJson,\n postJson,\n} from '../http.js'\nimport { getFlagBoolean, getFlagString } from '../parseArgs.js'\nimport { getDocsCommandOptions } from './validate.js'\n\nconst supportedPushDeleteBehaviors = new Set<DocsDeleteBehavior>([\n 'archive',\n 'delete',\n 'draft',\n 'ignore',\n])\n\ntype ServerPushResponse = {\n deleteBehavior?: string\n effectivePublishMode?: string\n error?: {\n code?: string\n message?: string\n }\n ok?: boolean\n publishRequested?: boolean\n summary?: {\n archive?: number\n create?: number\n delete?: number\n draft?: number\n unchanged?: number\n update?: number\n warnings?: number\n }\n syncRunId?: string\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isServerPushResponse = (value: unknown): value is ServerPushResponse =>\n isRecord(value)\n\nconst validateEndpointUrl = (endpoint: string): CliResult | string => {\n try {\n const parsed = new URL(endpoint)\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return {\n exitCode: 1,\n stderr: '--endpoint must be a full http:// or https:// URL.\\n',\n }\n }\n\n return parsed.toString()\n } catch {\n return {\n exitCode: 1,\n stderr: '--endpoint must be a valid full http:// or https:// URL.\\n',\n }\n }\n}\n\nconst readPrivateKey = async (\n args: ParsedCliArgs,\n): Promise<CliResult | string> => {\n const privateKeyFile = getFlagString(args, 'private-key-file')\n const privateKeyEnv = getFlagString(args, 'private-key-env')\n\n if (privateKeyFile && privateKeyEnv) {\n return {\n exitCode: 1,\n stderr:\n 'Use either --private-key-file or --private-key-env, not both.\\n',\n }\n }\n\n if (!privateKeyFile && !privateKeyEnv) {\n return {\n exitCode: 1,\n stderr: 'Push requires --private-key-file or --private-key-env.\\n',\n }\n }\n\n if (privateKeyEnv) {\n const privateKey = process.env[privateKeyEnv]\n\n if (!privateKey) {\n return {\n exitCode: 1,\n stderr: `Environment variable \"${privateKeyEnv}\" is not set.\\n`,\n }\n }\n\n return privateKey\n }\n\n try {\n return await readFile(privateKeyFile ?? '', 'utf8')\n } catch (error) {\n return {\n exitCode: 1,\n stderr:\n error instanceof Error\n ? `Could not read private key file: ${error.message}\\n`\n : 'Could not read private key file.\\n',\n }\n }\n}\n\nconst getGithubOidcTokenRequestUrl = ({\n audience,\n requestUrl,\n}: {\n audience: string\n requestUrl: string\n}): CliResult | string => {\n try {\n const url = new URL(requestUrl)\n url.searchParams.set('audience', audience)\n\n return url.toString()\n } catch {\n return {\n exitCode: 1,\n stderr: 'ACTIONS_ID_TOKEN_REQUEST_URL is not a valid URL.\\n',\n }\n }\n}\n\nconst readGithubOidcToken = async ({\n args,\n audience,\n httpGet,\n}: {\n args: ParsedCliArgs\n audience: string\n httpGet: HttpGetJson\n}): Promise<CliResult | string> => {\n const tokenEnv = getFlagString(args, 'oidc-token-env')\n\n if (tokenEnv) {\n const token = process.env[tokenEnv]\n\n if (!token) {\n return {\n exitCode: 1,\n stderr: `Environment variable \"${tokenEnv}\" is not set.\\n`,\n }\n }\n\n return token\n }\n\n const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL\n const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN\n\n if (!requestUrl || !requestToken) {\n return {\n exitCode: 1,\n stderr:\n 'GitHub OIDC push requires ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN, or --oidc-token-env.\\n',\n }\n }\n\n const url = getGithubOidcTokenRequestUrl({\n audience,\n requestUrl,\n })\n\n if (typeof url !== 'string') {\n return url\n }\n\n const response = await httpGet({\n headers: {\n Authorization: `bearer ${requestToken}`,\n },\n url,\n })\n\n if (!response.ok || !isRecord(response.body) || typeof response.body.value !== 'string') {\n return {\n exitCode: 1,\n stderr: `Could not retrieve GitHub OIDC token. HTTP status ${response.status}.\\n`,\n }\n }\n\n return response.body.value\n}\n\nconst getPushCommandOptions = async (\n args: ParsedCliArgs,\n): Promise<CliResult | PushCommandOptions> => {\n const docsOptions = getDocsCommandOptions(args)\n\n if ('exitCode' in docsOptions) {\n return docsOptions\n }\n\n const endpointFlag = getFlagString(args, 'endpoint')\n\n if (!endpointFlag) {\n return {\n exitCode: 1,\n stderr: 'Push requires --endpoint <url>.\\n',\n }\n }\n\n const endpoint = validateEndpointUrl(endpointFlag)\n\n if (typeof endpoint !== 'string') {\n return endpoint\n }\n\n if (getFlagBoolean(args, 'dry-run') && getFlagBoolean(args, 'sync')) {\n return {\n exitCode: 1,\n stderr: 'Use either --dry-run or --sync, not both.\\n',\n }\n }\n\n const deleteBehaviorFlag = getFlagString(args, 'delete-behavior')\n\n if (\n deleteBehaviorFlag !== undefined &&\n !supportedPushDeleteBehaviors.has(deleteBehaviorFlag as DocsDeleteBehavior)\n ) {\n return {\n exitCode: 1,\n stderr: '--delete-behavior for push must be archive, delete, draft, or ignore.\\n',\n }\n }\n\n const mode: PushCommandOptions['mode'] = getFlagBoolean(args, 'sync')\n ? 'sync'\n : 'dry-run'\n const baseOptions = {\n ...docsOptions,\n deleteBehavior: deleteBehaviorFlag as DocsDeleteBehavior | undefined,\n endpoint,\n mode,\n publish: getFlagBoolean(args, 'publish'),\n }\n\n if (getFlagBoolean(args, 'github-oidc')) {\n if (getFlagString(args, 'key-id')) {\n return {\n exitCode: 1,\n stderr: 'Do not use --key-id with --github-oidc.\\n',\n }\n }\n\n if (getFlagString(args, 'private-key-file') || getFlagString(args, 'private-key-env')) {\n return {\n exitCode: 1,\n stderr: 'Do not use Ed25519 private key flags with --github-oidc.\\n',\n }\n }\n\n return {\n ...baseOptions,\n authMode: 'github-oidc',\n oidcTokenEnv: getFlagString(args, 'oidc-token-env'),\n }\n }\n\n const keyId = getFlagString(args, 'key-id')\n\n if (!keyId) {\n return {\n exitCode: 1,\n stderr: 'Push requires --key-id <id>.\\n',\n }\n }\n\n const privateKey = await readPrivateKey(args)\n\n if (typeof privateKey !== 'string') {\n return privateKey\n }\n\n return {\n ...baseOptions,\n authMode: 'ed25519',\n keyId,\n privateKey,\n }\n}\n\nconst formatServerFailure = ({\n body,\n status,\n}: {\n body: unknown\n status: number\n}): string => {\n if (isServerPushResponse(body) && body.error?.message) {\n return `${body.error.message}\\n`\n }\n\n return `Sync request failed with HTTP status ${status}.\\n`\n}\n\nexport const runPushCommand = async (\n args: ParsedCliArgs,\n httpPost: HttpPostJson = postJson,\n httpGet: HttpGetJson = getJson,\n): Promise<CliResult> => {\n const options = await getPushCommandOptions(args)\n\n if ('exitCode' in options) {\n return options\n }\n\n const files = await walkDocsFiles({\n root: options.docsRoot,\n })\n const aiExport = await readDocsAiExportManifest({\n root: options.docsRoot,\n })\n\n if (!aiExport.ok) {\n return {\n exitCode: 1,\n stderr: `AI export manifest is invalid.\\n\\nErrors:\\n${formatIssues(aiExport.issues)}\\n`,\n }\n }\n\n const manifest = buildDocsManifest({\n aiExport: aiExport.manifest,\n branch: options.branch,\n commit: options.commit,\n deleteBehavior: options.deleteBehavior ?? 'archive',\n files,\n mode: options.mode,\n publish: options.publish,\n repository: options.repository,\n sourceId: options.sourceId,\n })\n const validation = validateDocsManifest(manifest, {\n maxFileBytes: options.maxFileBytes,\n maxFiles: options.maxFiles,\n maxTotalBytes: options.maxTotalBytes,\n routeBase: `/${options.sourceId}`,\n })\n\n if (!validation.ok) {\n return {\n exitCode: 1,\n stderr: `Manifest is invalid.\\n\\nErrors:\\n${formatIssues(validation.issues)}\\n`,\n }\n }\n\n const body = JSON.stringify(manifest)\n let signedRequest:\n | {\n body: string\n headers: Record<string, string>\n }\n | ReturnType<typeof signDocsSyncRequest>\n\n if (options.authMode === 'github-oidc') {\n const oidcToken = await readGithubOidcToken({\n args,\n audience: options.sourceId,\n httpGet,\n })\n\n if (typeof oidcToken !== 'string') {\n return oidcToken\n }\n\n signedRequest = {\n body,\n headers: {\n Authorization: `Bearer ${oidcToken}`,\n 'Content-Type': 'application/json',\n 'X-VL-MD-DOCS-Body-SHA256': sha256Hex(body),\n },\n }\n } else {\n try {\n signedRequest = signDocsSyncRequest({\n body,\n endpoint: options.endpoint,\n keyId: options.keyId,\n privateKey: options.privateKey,\n })\n } catch (error) {\n if (error instanceof DocsSyncKeyError) {\n return {\n exitCode: 1,\n stderr: `${error.message}\\n`,\n }\n }\n\n throw error\n }\n }\n\n const response = await httpPost({\n body: signedRequest.body,\n headers: signedRequest.headers,\n url: options.endpoint,\n })\n\n if (getFlagBoolean(args, 'json')) {\n return {\n exitCode:\n response.ok &&\n isServerPushResponse(response.body) &&\n response.body.ok === true\n ? 0\n : 1,\n stdout: printJson(\n {\n endpoint: options.endpoint,\n mode: options.mode,\n response: response.body,\n sourceId: options.sourceId,\n status: response.status,\n },\n getFlagBoolean(args, 'pretty'),\n ),\n }\n }\n\n if (\n !response.ok ||\n !isServerPushResponse(response.body) ||\n response.body.ok !== true\n ) {\n return {\n exitCode: 1,\n stderr: formatServerFailure({\n body: response.body,\n status: response.status,\n }),\n }\n }\n\n return {\n exitCode: 0,\n stdout: formatPushSummary({\n endpoint: options.endpoint,\n mode: options.mode,\n response: response.body,\n sourceId: options.sourceId,\n }),\n }\n}\n"],"names":["readFile","DocsSyncKeyError","signDocsSyncRequest","buildDocsManifest","sha256Hex","validateDocsManifest","readDocsAiExportManifest","walkDocsFiles","formatIssues","formatPushSummary","printJson","getJson","postJson","getFlagBoolean","getFlagString","getDocsCommandOptions","supportedPushDeleteBehaviors","Set","isRecord","value","Array","isArray","isServerPushResponse","validateEndpointUrl","endpoint","parsed","URL","protocol","exitCode","stderr","toString","readPrivateKey","args","privateKeyFile","privateKeyEnv","privateKey","process","env","error","Error","message","getGithubOidcTokenRequestUrl","audience","requestUrl","url","searchParams","set","readGithubOidcToken","httpGet","tokenEnv","token","ACTIONS_ID_TOKEN_REQUEST_URL","requestToken","ACTIONS_ID_TOKEN_REQUEST_TOKEN","response","headers","Authorization","ok","body","status","getPushCommandOptions","docsOptions","endpointFlag","deleteBehaviorFlag","undefined","has","mode","baseOptions","deleteBehavior","publish","authMode","oidcTokenEnv","keyId","formatServerFailure","runPushCommand","httpPost","options","files","root","docsRoot","aiExport","issues","manifest","branch","commit","repository","sourceId","validation","maxFileBytes","maxFiles","maxTotalBytes","routeBase","JSON","stringify","signedRequest","oidcToken","stdout"],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -87,7 +87,7 @@ Options:
87
87
  Options:
88
88
  --endpoint <url> Full Payload sync endpoint URL.
89
89
  --key-id <id> Server-configured Ed25519 key id.
90
- --private-key-file <path> PEM private key file from keygen.
90
+ --private-key-file <path> Private key file from keygen, or an unencrypted OpenSSH Ed25519 key.
91
91
  --private-key-env <name> Environment variable containing the private key.
92
92
  --github-oidc Use GitHub Actions OIDC bearer auth instead of Ed25519.
93
93
  --oidc-token-env <name> Environment variable containing an already-fetched OIDC token.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport type { CliCommandName, CliResult, ParsedCliArgs } from './types.js'\n\nimport { runInstallCommand } from './commands/install.js'\nimport { runKeygenCommand } from './commands/keygen.js'\nimport { runManifestCommand } from './commands/manifest.js'\nimport { runPlanCommand } from './commands/plan.js'\nimport { runPushCommand } from './commands/push.js'\nimport { runValidateCommand } from './commands/validate.js'\nimport { getFlagString, parseCliArgs } from './parseArgs.js'\n\nconst helpText = `payload-markdown-docs\n\nUsage:\n payload-markdown-docs validate <docs-root> [options]\n payload-markdown-docs manifest <docs-root> [options]\n payload-markdown-docs plan <docs-root> [options]\n payload-markdown-docs push <docs-root> [options]\n payload-markdown-docs keygen [options]\n payload-markdown-docs install skill --codex [options]\n\nCommands:\n validate Validate a local Markdown docs directory.\n manifest Print a JSON docs manifest for a local Markdown docs directory.\n plan Build a dry sync plan against optional existing docs records.\n push Sign and upload a docs manifest to a Payload sync endpoint.\n keygen Generate Ed25519 keys for signed sync.\n install Install local AI-agent guidance for docs maintenance.\n`\n\nconst commandHelp: Record<Exclude<CliCommandName, 'help'>, string> = {\n install: `payload-markdown-docs install skill --codex\n\nAliases:\n payload-markdown-docs install ai-skill --codex\n payload-markdown-docs install skill --agent codex\n\nOptions:\n --codex Install the Codex skill pack.\n --agent <codex> Agent target. Currently only codex.\n --out <path> Output directory. Defaults to .agents/skills/payload-markdown-docs.\n --docs-root <path> Docs root to mention in installed guidance. Defaults to ./docs.\n --package-manager <name> pnpm, npm, yarn, or bun. Auto-detected when omitted.\n --force Overwrite existing skill files.\n --dry-run Print planned files without writing.\n --help Show this help.\n\nInstalls local AI-agent guidance only. It does not sync docs, call Payload, or run package manager commands.\n`,\n keygen: `payload-markdown-docs keygen\n\nOptions:\n --format <pem|base64> Output key format. Defaults to pem.\n --out <dir> Write docs-sync-public.pem and docs-sync-private.pem.\n --force Overwrite existing key files when used with --out.\n --help Show this help.\n`,\n manifest: `payload-markdown-docs manifest <docs-root>\n\nOptions:\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --pretty Pretty-print JSON.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n`,\n plan: `payload-markdown-docs plan <docs-root>\n\nOptions:\n --existing <path> JSON array of existing docs records.\n --delete-behavior <value> archive, delete, draft, or ignore.\n --json Print full plan JSON.\n --pretty Pretty-print JSON output.\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n`,\n push: `payload-markdown-docs push <docs-root>\n\nOptions:\n --endpoint <url> Full Payload sync endpoint URL.\n --key-id <id> Server-configured Ed25519 key id.\n --private-key-file <path> PEM private key file from keygen.\n --private-key-env <name> Environment variable containing the private key.\n --github-oidc Use GitHub Actions OIDC bearer auth instead of Ed25519.\n --oidc-token-env <name> Environment variable containing an already-fetched OIDC token.\n --dry-run Upload as dry-run mode. This is the default.\n --sync Upload as sync mode. Requires server sync.allowWrites.\n --publish Request published output. Server must allow publishing.\n --delete-behavior <value> archive, delete, draft, or ignore. Defaults to archive.\n --json Print structured JSON output.\n --pretty Pretty-print JSON output with --json.\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n\nExamples:\n Ed25519:\n payload-markdown-docs push ./docs --endpoint \"$DOCS_SYNC_ENDPOINT\" --source main-docs --key-id github-actions-main --private-key-env DOCS_SYNC_PRIVATE_KEY --sync\n\n GitHub OIDC:\n payload-markdown-docs push ./docs --endpoint \"$DOCS_SYNC_ENDPOINT\" --github-oidc --sync\n\nGitHub OIDC requires workflow permissions: id-token: write and contents: read.\nHard delete requires explicit server sync.allowHardDelete. Existing collection and block targets are not supported yet.\n`,\n validate: `payload-markdown-docs validate <docs-root>\n\nOptions:\n --json Print validation JSON.\n --pretty Pretty-print JSON output.\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n`,\n}\n\nconst getHelpForArgs = (args: ParsedCliArgs): string => {\n if (args.command !== 'help') {\n return commandHelp[args.command]\n }\n\n const topic = getFlagString(args, 'topic') ?? args.positionals[0]\n\n if (\n topic === 'keygen' ||\n topic === 'install' ||\n topic === 'manifest' ||\n topic === 'plan' ||\n topic === 'push' ||\n topic === 'validate'\n ) {\n return commandHelp[topic]\n }\n\n return helpText\n}\n\nexport const runCli = async (argv: string[]): Promise<CliResult> => {\n try {\n const parsed = parseCliArgs(argv)\n\n if (!parsed.ok) {\n return {\n exitCode: 1,\n stderr: `${parsed.error}\\n`,\n }\n }\n\n if (parsed.args.command === 'help' || parsed.args.flags.help === true) {\n return {\n exitCode: 0,\n stdout: getHelpForArgs(parsed.args),\n }\n }\n\n if (parsed.args.command === 'keygen') {\n return runKeygenCommand(parsed.args)\n }\n\n if (parsed.args.command === 'install') {\n return runInstallCommand(parsed.args)\n }\n\n if (parsed.args.command === 'manifest') {\n return runManifestCommand(parsed.args)\n }\n\n if (parsed.args.command === 'plan') {\n return runPlanCommand(parsed.args)\n }\n\n if (parsed.args.command === 'push') {\n return runPushCommand(parsed.args)\n }\n\n if (parsed.args.command === 'validate') {\n return runValidateCommand(parsed.args)\n }\n\n return {\n exitCode: 1,\n stderr: 'Unknown command.\\n',\n }\n } catch (error) {\n return {\n exitCode: 2,\n stderr: error instanceof Error ? `${error.message}\\n` : 'Unexpected internal error.\\n',\n }\n }\n}\n\nconst isCliEntrypoint = (): boolean => {\n if (!process.argv[1]) {\n return false\n }\n\n return fileURLToPath(import.meta.url) === path.resolve(process.argv[1])\n}\n\nif (isCliEntrypoint()) {\n const result = await runCli(process.argv.slice(2))\n\n if (result.stdout) {\n process.stdout.write(result.stdout)\n }\n\n if (result.stderr) {\n process.stderr.write(result.stderr)\n }\n\n process.exitCode = result.exitCode\n}\n"],"names":["path","fileURLToPath","runInstallCommand","runKeygenCommand","runManifestCommand","runPlanCommand","runPushCommand","runValidateCommand","getFlagString","parseCliArgs","helpText","commandHelp","install","keygen","manifest","plan","push","validate","getHelpForArgs","args","command","topic","positionals","runCli","argv","parsed","ok","exitCode","stderr","error","flags","help","stdout","Error","message","isCliEntrypoint","process","url","resolve","result","slice","write"],"mappings":";AAEA,OAAOA,UAAU,YAAW;AAC5B,SAASC,aAAa,QAAQ,WAAU;AAIxC,SAASC,iBAAiB,QAAQ,wBAAuB;AACzD,SAASC,gBAAgB,QAAQ,uBAAsB;AACvD,SAASC,kBAAkB,QAAQ,yBAAwB;AAC3D,SAASC,cAAc,QAAQ,qBAAoB;AACnD,SAASC,cAAc,QAAQ,qBAAoB;AACnD,SAASC,kBAAkB,QAAQ,yBAAwB;AAC3D,SAASC,aAAa,EAAEC,YAAY,QAAQ,iBAAgB;AAE5D,MAAMC,WAAW,CAAC;;;;;;;;;;;;;;;;;AAiBlB,CAAC;AAED,MAAMC,cAA+D;IACnEC,SAAS,CAAC;;;;;;;;;;;;;;;;;AAiBZ,CAAC;IACCC,QAAQ,CAAC;;;;;;;AAOX,CAAC;IACCC,UAAU,CAAC;;;;;;;;;;;;AAYb,CAAC;IACCC,MAAM,CAAC;;;;;;;;;;;;;;;AAeT,CAAC;IACCC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCT,CAAC;IACCC,UAAU,CAAC;;;;;;;;;;;;;AAab,CAAC;AACD;AAEA,MAAMC,iBAAiB,CAACC;IACtB,IAAIA,KAAKC,OAAO,KAAK,QAAQ;QAC3B,OAAOT,WAAW,CAACQ,KAAKC,OAAO,CAAC;IAClC;IAEA,MAAMC,QAAQb,cAAcW,MAAM,YAAYA,KAAKG,WAAW,CAAC,EAAE;IAEjE,IACED,UAAU,YACVA,UAAU,aACVA,UAAU,cACVA,UAAU,UACVA,UAAU,UACVA,UAAU,YACV;QACA,OAAOV,WAAW,CAACU,MAAM;IAC3B;IAEA,OAAOX;AACT;AAEA,OAAO,MAAMa,SAAS,OAAOC;IAC3B,IAAI;QACF,MAAMC,SAAShB,aAAae;QAE5B,IAAI,CAACC,OAAOC,EAAE,EAAE;YACd,OAAO;gBACLC,UAAU;gBACVC,QAAQ,GAAGH,OAAOI,KAAK,CAAC,EAAE,CAAC;YAC7B;QACF;QAEA,IAAIJ,OAAON,IAAI,CAACC,OAAO,KAAK,UAAUK,OAAON,IAAI,CAACW,KAAK,CAACC,IAAI,KAAK,MAAM;YACrE,OAAO;gBACLJ,UAAU;gBACVK,QAAQd,eAAeO,OAAON,IAAI;YACpC;QACF;QAEA,IAAIM,OAAON,IAAI,CAACC,OAAO,KAAK,UAAU;YACpC,OAAOjB,iBAAiBsB,OAAON,IAAI;QACrC;QAEA,IAAIM,OAAON,IAAI,CAACC,OAAO,KAAK,WAAW;YACrC,OAAOlB,kBAAkBuB,OAAON,IAAI;QACtC;QAEA,IAAIM,OAAON,IAAI,CAACC,OAAO,KAAK,YAAY;YACtC,OAAOhB,mBAAmBqB,OAAON,IAAI;QACvC;QAEA,IAAIM,OAAON,IAAI,CAACC,OAAO,KAAK,QAAQ;YAClC,OAAOf,eAAeoB,OAAON,IAAI;QACnC;QAEA,IAAIM,OAAON,IAAI,CAACC,OAAO,KAAK,QAAQ;YAClC,OAAOd,eAAemB,OAAON,IAAI;QACnC;QAEA,IAAIM,OAAON,IAAI,CAACC,OAAO,KAAK,YAAY;YACtC,OAAOb,mBAAmBkB,OAAON,IAAI;QACvC;QAEA,OAAO;YACLQ,UAAU;YACVC,QAAQ;QACV;IACF,EAAE,OAAOC,OAAO;QACd,OAAO;YACLF,UAAU;YACVC,QAAQC,iBAAiBI,QAAQ,GAAGJ,MAAMK,OAAO,CAAC,EAAE,CAAC,GAAG;QAC1D;IACF;AACF,EAAC;AAED,MAAMC,kBAAkB;IACtB,IAAI,CAACC,QAAQZ,IAAI,CAAC,EAAE,EAAE;QACpB,OAAO;IACT;IAEA,OAAOvB,cAAc,YAAYoC,GAAG,MAAMrC,KAAKsC,OAAO,CAACF,QAAQZ,IAAI,CAAC,EAAE;AACxE;AAEA,IAAIW,mBAAmB;IACrB,MAAMI,SAAS,MAAMhB,OAAOa,QAAQZ,IAAI,CAACgB,KAAK,CAAC;IAE/C,IAAID,OAAOP,MAAM,EAAE;QACjBI,QAAQJ,MAAM,CAACS,KAAK,CAACF,OAAOP,MAAM;IACpC;IAEA,IAAIO,OAAOX,MAAM,EAAE;QACjBQ,QAAQR,MAAM,CAACa,KAAK,CAACF,OAAOX,MAAM;IACpC;IAEAQ,QAAQT,QAAQ,GAAGY,OAAOZ,QAAQ;AACpC"}
1
+ {"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport type { CliCommandName, CliResult, ParsedCliArgs } from './types.js'\n\nimport { runInstallCommand } from './commands/install.js'\nimport { runKeygenCommand } from './commands/keygen.js'\nimport { runManifestCommand } from './commands/manifest.js'\nimport { runPlanCommand } from './commands/plan.js'\nimport { runPushCommand } from './commands/push.js'\nimport { runValidateCommand } from './commands/validate.js'\nimport { getFlagString, parseCliArgs } from './parseArgs.js'\n\nconst helpText = `payload-markdown-docs\n\nUsage:\n payload-markdown-docs validate <docs-root> [options]\n payload-markdown-docs manifest <docs-root> [options]\n payload-markdown-docs plan <docs-root> [options]\n payload-markdown-docs push <docs-root> [options]\n payload-markdown-docs keygen [options]\n payload-markdown-docs install skill --codex [options]\n\nCommands:\n validate Validate a local Markdown docs directory.\n manifest Print a JSON docs manifest for a local Markdown docs directory.\n plan Build a dry sync plan against optional existing docs records.\n push Sign and upload a docs manifest to a Payload sync endpoint.\n keygen Generate Ed25519 keys for signed sync.\n install Install local AI-agent guidance for docs maintenance.\n`\n\nconst commandHelp: Record<Exclude<CliCommandName, 'help'>, string> = {\n install: `payload-markdown-docs install skill --codex\n\nAliases:\n payload-markdown-docs install ai-skill --codex\n payload-markdown-docs install skill --agent codex\n\nOptions:\n --codex Install the Codex skill pack.\n --agent <codex> Agent target. Currently only codex.\n --out <path> Output directory. Defaults to .agents/skills/payload-markdown-docs.\n --docs-root <path> Docs root to mention in installed guidance. Defaults to ./docs.\n --package-manager <name> pnpm, npm, yarn, or bun. Auto-detected when omitted.\n --force Overwrite existing skill files.\n --dry-run Print planned files without writing.\n --help Show this help.\n\nInstalls local AI-agent guidance only. It does not sync docs, call Payload, or run package manager commands.\n`,\n keygen: `payload-markdown-docs keygen\n\nOptions:\n --format <pem|base64> Output key format. Defaults to pem.\n --out <dir> Write docs-sync-public.pem and docs-sync-private.pem.\n --force Overwrite existing key files when used with --out.\n --help Show this help.\n`,\n manifest: `payload-markdown-docs manifest <docs-root>\n\nOptions:\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --pretty Pretty-print JSON.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n`,\n plan: `payload-markdown-docs plan <docs-root>\n\nOptions:\n --existing <path> JSON array of existing docs records.\n --delete-behavior <value> archive, delete, draft, or ignore.\n --json Print full plan JSON.\n --pretty Pretty-print JSON output.\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n`,\n push: `payload-markdown-docs push <docs-root>\n\nOptions:\n --endpoint <url> Full Payload sync endpoint URL.\n --key-id <id> Server-configured Ed25519 key id.\n --private-key-file <path> Private key file from keygen, or an unencrypted OpenSSH Ed25519 key.\n --private-key-env <name> Environment variable containing the private key.\n --github-oidc Use GitHub Actions OIDC bearer auth instead of Ed25519.\n --oidc-token-env <name> Environment variable containing an already-fetched OIDC token.\n --dry-run Upload as dry-run mode. This is the default.\n --sync Upload as sync mode. Requires server sync.allowWrites.\n --publish Request published output. Server must allow publishing.\n --delete-behavior <value> archive, delete, draft, or ignore. Defaults to archive.\n --json Print structured JSON output.\n --pretty Pretty-print JSON output with --json.\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n\nExamples:\n Ed25519:\n payload-markdown-docs push ./docs --endpoint \"$DOCS_SYNC_ENDPOINT\" --source main-docs --key-id github-actions-main --private-key-env DOCS_SYNC_PRIVATE_KEY --sync\n\n GitHub OIDC:\n payload-markdown-docs push ./docs --endpoint \"$DOCS_SYNC_ENDPOINT\" --github-oidc --sync\n\nGitHub OIDC requires workflow permissions: id-token: write and contents: read.\nHard delete requires explicit server sync.allowHardDelete. Existing collection and block targets are not supported yet.\n`,\n validate: `payload-markdown-docs validate <docs-root>\n\nOptions:\n --json Print validation JSON.\n --pretty Pretty-print JSON output.\n --source <id> Docs set slug. Defaults to the GitHub repository name in GitHub Actions, otherwise local-docs.\n --repository <repo> Source repository metadata.\n --branch <branch> Source branch metadata.\n --commit <sha> Source commit metadata.\n --max-files <number> Maximum file count.\n --max-file-bytes <number> Maximum single file size.\n --max-total-bytes <number> Maximum total Markdown bytes.\n --help Show this help.\n`,\n}\n\nconst getHelpForArgs = (args: ParsedCliArgs): string => {\n if (args.command !== 'help') {\n return commandHelp[args.command]\n }\n\n const topic = getFlagString(args, 'topic') ?? args.positionals[0]\n\n if (\n topic === 'keygen' ||\n topic === 'install' ||\n topic === 'manifest' ||\n topic === 'plan' ||\n topic === 'push' ||\n topic === 'validate'\n ) {\n return commandHelp[topic]\n }\n\n return helpText\n}\n\nexport const runCli = async (argv: string[]): Promise<CliResult> => {\n try {\n const parsed = parseCliArgs(argv)\n\n if (!parsed.ok) {\n return {\n exitCode: 1,\n stderr: `${parsed.error}\\n`,\n }\n }\n\n if (parsed.args.command === 'help' || parsed.args.flags.help === true) {\n return {\n exitCode: 0,\n stdout: getHelpForArgs(parsed.args),\n }\n }\n\n if (parsed.args.command === 'keygen') {\n return runKeygenCommand(parsed.args)\n }\n\n if (parsed.args.command === 'install') {\n return runInstallCommand(parsed.args)\n }\n\n if (parsed.args.command === 'manifest') {\n return runManifestCommand(parsed.args)\n }\n\n if (parsed.args.command === 'plan') {\n return runPlanCommand(parsed.args)\n }\n\n if (parsed.args.command === 'push') {\n return runPushCommand(parsed.args)\n }\n\n if (parsed.args.command === 'validate') {\n return runValidateCommand(parsed.args)\n }\n\n return {\n exitCode: 1,\n stderr: 'Unknown command.\\n',\n }\n } catch (error) {\n return {\n exitCode: 2,\n stderr: error instanceof Error ? `${error.message}\\n` : 'Unexpected internal error.\\n',\n }\n }\n}\n\nconst isCliEntrypoint = (): boolean => {\n if (!process.argv[1]) {\n return false\n }\n\n return fileURLToPath(import.meta.url) === path.resolve(process.argv[1])\n}\n\nif (isCliEntrypoint()) {\n const result = await runCli(process.argv.slice(2))\n\n if (result.stdout) {\n process.stdout.write(result.stdout)\n }\n\n if (result.stderr) {\n process.stderr.write(result.stderr)\n }\n\n process.exitCode = result.exitCode\n}\n"],"names":["path","fileURLToPath","runInstallCommand","runKeygenCommand","runManifestCommand","runPlanCommand","runPushCommand","runValidateCommand","getFlagString","parseCliArgs","helpText","commandHelp","install","keygen","manifest","plan","push","validate","getHelpForArgs","args","command","topic","positionals","runCli","argv","parsed","ok","exitCode","stderr","error","flags","help","stdout","Error","message","isCliEntrypoint","process","url","resolve","result","slice","write"],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import type { CollectionConfig } from 'payload';
2
2
  export type CreateDocsSetsCollectionOptions = {
3
+ allowPublish?: boolean;
3
4
  docsCollectionSlug?: string;
5
+ docsEnableDrafts?: boolean;
4
6
  docsGroupsCollectionSlug: string;
5
7
  slug: string;
6
8
  syncRunsCollectionSlug?: string;
7
9
  };
8
- export declare const createDocsSetsCollection: ({ slug, docsCollectionSlug, docsGroupsCollectionSlug, syncRunsCollectionSlug, }: CreateDocsSetsCollectionOptions) => CollectionConfig;
10
+ export declare const createDocsSetsCollection: ({ slug, allowPublish, docsCollectionSlug, docsEnableDrafts, docsGroupsCollectionSlug, syncRunsCollectionSlug, }: CreateDocsSetsCollectionOptions) => CollectionConfig;
@@ -1,5 +1,6 @@
1
1
  import { DOCS_GLOBALS_ADMIN_GROUP, DOCS_SET_MANAGER_COMPONENT } from '../constants.js';
2
- export const createDocsSetsCollection = ({ slug, docsCollectionSlug, docsGroupsCollectionSlug, syncRunsCollectionSlug })=>({
2
+ import { createPublishGeneratedDocsEndpoint } from '../endpoints/publishGeneratedDocs.js';
3
+ export const createDocsSetsCollection = ({ slug, allowPublish = false, docsCollectionSlug, docsEnableDrafts = false, docsGroupsCollectionSlug, syncRunsCollectionSlug })=>({
3
4
  slug,
4
5
  admin: {
5
6
  defaultColumns: [
@@ -11,6 +12,12 @@ export const createDocsSetsCollection = ({ slug, docsCollectionSlug, docsGroupsC
11
12
  group: DOCS_GLOBALS_ADMIN_GROUP,
12
13
  useAsTitle: 'title'
13
14
  },
15
+ endpoints: docsCollectionSlug && docsEnableDrafts && allowPublish ? [
16
+ createPublishGeneratedDocsEndpoint({
17
+ docsCollectionSlug,
18
+ docsSetsCollectionSlug: slug
19
+ })
20
+ ] : undefined,
14
21
  fields: [
15
22
  {
16
23
  name: 'title',
@@ -135,7 +142,9 @@ export const createDocsSetsCollection = ({ slug, docsCollectionSlug, docsGroupsC
135
142
  Field: DOCS_SET_MANAGER_COMPONENT
136
143
  },
137
144
  custom: {
145
+ allowPublish,
138
146
  docsCollectionSlug,
147
+ docsEnableDrafts,
139
148
  docsGroupsCollectionSlug,
140
149
  docsSetsCollectionSlug: slug
141
150
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collections/docsSets.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport {\n DOCS_GLOBALS_ADMIN_GROUP,\n DOCS_SET_MANAGER_COMPONENT,\n} from '../constants.js'\n\nexport type CreateDocsSetsCollectionOptions = {\n docsCollectionSlug?: string\n docsGroupsCollectionSlug: string\n slug: string\n syncRunsCollectionSlug?: string\n}\n\nexport const createDocsSetsCollection = ({\n slug,\n docsCollectionSlug,\n docsGroupsCollectionSlug,\n syncRunsCollectionSlug,\n}: CreateDocsSetsCollectionOptions): CollectionConfig => ({\n slug,\n admin: {\n defaultColumns: ['title', 'slug', 'branch', 'updatedAt'],\n group: DOCS_GLOBALS_ADMIN_GROUP,\n useAsTitle: 'title',\n },\n fields: [\n {\n name: 'title',\n type: 'text',\n required: true,\n },\n {\n name: 'slug',\n type: 'text',\n index: true,\n required: true,\n unique: true,\n },\n {\n name: 'group',\n type: 'relationship',\n relationTo: docsGroupsCollectionSlug,\n },\n {\n name: 'branch',\n type: 'text',\n admin: {\n description:\n 'Git branch allowed to publish this docs set. The full Git ref is handled internally.',\n },\n defaultValue: 'main',\n },\n {\n name: 'allowPullRequests',\n type: 'checkbox',\n admin: {\n description:\n 'Allow GitHub pull request events to dry-run or publish this docs set.',\n },\n defaultValue: false,\n },\n {\n name: 'description',\n type: 'textarea',\n },\n {\n name: 'advancedSecurity',\n type: 'group',\n admin: {\n description:\n 'Optional workflow lock-down. Leave disabled to allow any workflow from a trusted GitHub owner/repository and branch.',\n },\n fields: [\n {\n name: 'enabled',\n type: 'checkbox',\n admin: {\n description:\n 'When enabled, only the workflow refs listed below can publish this docs set.',\n },\n defaultValue: false,\n },\n {\n name: 'allowedWorkflowRefs',\n type: 'array',\n admin: {\n condition: (_data, siblingData) => siblingData?.enabled === true,\n description:\n 'Exact GitHub workflow refs, for example owner/repo/.github/workflows/publish-docs.yml@refs/heads/main.',\n },\n fields: [\n {\n name: 'value',\n type: 'text',\n required: true,\n },\n ],\n validate: (value, { siblingData }) => {\n const advancedSecurityData =\n typeof siblingData === 'object' && siblingData !== null\n ? (siblingData as { enabled?: unknown })\n : undefined\n\n if (\n advancedSecurityData?.enabled === true &&\n (!Array.isArray(value) || value.length === 0)\n ) {\n return 'Add at least one workflow ref or disable advanced security.'\n }\n\n return true\n },\n },\n ],\n },\n {\n name: 'aiExport',\n type: 'json',\n admin: {\n description:\n 'Parsed index.ai.yml control data for the raw Markdown AI export route.',\n },\n },\n {\n name: 'sync',\n type: 'group',\n fields: [\n {\n name: 'lastSyncedAt',\n type: 'date',\n },\n ...(syncRunsCollectionSlug\n ? [\n {\n name: 'lastSyncRunId',\n type: 'relationship' as const,\n relationTo: syncRunsCollectionSlug,\n },\n ]\n : []),\n {\n name: 'lastStatus',\n type: 'select',\n options: ['failed', 'pending', 'success'],\n },\n {\n name: 'docsCount',\n type: 'number',\n defaultValue: 0,\n },\n ],\n },\n ...(docsCollectionSlug\n ? [\n {\n name: 'docsSetManager',\n type: 'ui' as const,\n admin: {\n components: {\n Field: DOCS_SET_MANAGER_COMPONENT,\n },\n custom: {\n docsCollectionSlug,\n docsGroupsCollectionSlug,\n docsSetsCollectionSlug: slug,\n },\n },\n },\n ]\n : []),\n ],\n labels: {\n plural: 'Sets',\n singular: 'Set',\n },\n})\n"],"names":["DOCS_GLOBALS_ADMIN_GROUP","DOCS_SET_MANAGER_COMPONENT","createDocsSetsCollection","slug","docsCollectionSlug","docsGroupsCollectionSlug","syncRunsCollectionSlug","admin","defaultColumns","group","useAsTitle","fields","name","type","required","index","unique","relationTo","description","defaultValue","condition","_data","siblingData","enabled","validate","value","advancedSecurityData","undefined","Array","isArray","length","options","components","Field","custom","docsSetsCollectionSlug","labels","plural","singular"],"mappings":"AAEA,SACEA,wBAAwB,EACxBC,0BAA0B,QACrB,kBAAiB;AASxB,OAAO,MAAMC,2BAA2B,CAAC,EACvCC,IAAI,EACJC,kBAAkB,EAClBC,wBAAwB,EACxBC,sBAAsB,EACU,GAAwB,CAAA;QACxDH;QACAI,OAAO;YACLC,gBAAgB;gBAAC;gBAAS;gBAAQ;gBAAU;aAAY;YACxDC,OAAOT;YACPU,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,UAAU;YACZ;YACA;gBACEF,MAAM;gBACNC,MAAM;gBACNE,OAAO;gBACPD,UAAU;gBACVE,QAAQ;YACV;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNI,YAAYZ;YACd;YACA;gBACEO,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLW,aACE;gBACJ;gBACAC,cAAc;YAChB;YACA;gBACEP,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLW,aACE;gBACJ;gBACAC,cAAc;YAChB;YACA;gBACEP,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLW,aACE;gBACJ;gBACAP,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLW,aACE;wBACJ;wBACAC,cAAc;oBAChB;oBACA;wBACEP,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLa,WAAW,CAACC,OAAOC,cAAgBA,aAAaC,YAAY;4BAC5DL,aACE;wBACJ;wBACAP,QAAQ;4BACN;gCACEC,MAAM;gCACNC,MAAM;gCACNC,UAAU;4BACZ;yBACD;wBACDU,UAAU,CAACC,OAAO,EAAEH,WAAW,EAAE;4BAC/B,MAAMI,uBACJ,OAAOJ,gBAAgB,YAAYA,gBAAgB,OAC9CA,cACDK;4BAEN,IACED,sBAAsBH,YAAY,QACjC,CAAA,CAACK,MAAMC,OAAO,CAACJ,UAAUA,MAAMK,MAAM,KAAK,CAAA,GAC3C;gCACA,OAAO;4BACT;4BAEA,OAAO;wBACT;oBACF;iBACD;YACH;YACA;gBACElB,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLW,aACE;gBACJ;YACF;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNF,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;oBACR;uBACIP,yBACA;wBACE;4BACEM,MAAM;4BACNC,MAAM;4BACNI,YAAYX;wBACd;qBACD,GACD,EAAE;oBACN;wBACEM,MAAM;wBACNC,MAAM;wBACNkB,SAAS;4BAAC;4BAAU;4BAAW;yBAAU;oBAC3C;oBACA;wBACEnB,MAAM;wBACNC,MAAM;wBACNM,cAAc;oBAChB;iBACD;YACH;eACIf,qBACA;gBACE;oBACEQ,MAAM;oBACNC,MAAM;oBACNN,OAAO;wBACLyB,YAAY;4BACVC,OAAOhC;wBACT;wBACAiC,QAAQ;4BACN9B;4BACAC;4BACA8B,wBAAwBhC;wBAC1B;oBACF;gBACF;aACD,GACD,EAAE;SACP;QACDiC,QAAQ;YACNC,QAAQ;YACRC,UAAU;QACZ;IACF,CAAA,EAAE"}
1
+ {"version":3,"sources":["../../src/collections/docsSets.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport {\n DOCS_GLOBALS_ADMIN_GROUP,\n DOCS_SET_MANAGER_COMPONENT,\n} from '../constants.js'\nimport { createPublishGeneratedDocsEndpoint } from '../endpoints/publishGeneratedDocs.js'\n\nexport type CreateDocsSetsCollectionOptions = {\n allowPublish?: boolean\n docsCollectionSlug?: string\n docsEnableDrafts?: boolean\n docsGroupsCollectionSlug: string\n slug: string\n syncRunsCollectionSlug?: string\n}\n\nexport const createDocsSetsCollection = ({\n slug,\n allowPublish = false,\n docsCollectionSlug,\n docsEnableDrafts = false,\n docsGroupsCollectionSlug,\n syncRunsCollectionSlug,\n}: CreateDocsSetsCollectionOptions): CollectionConfig => ({\n slug,\n admin: {\n defaultColumns: ['title', 'slug', 'branch', 'updatedAt'],\n group: DOCS_GLOBALS_ADMIN_GROUP,\n useAsTitle: 'title',\n },\n endpoints:\n docsCollectionSlug && docsEnableDrafts && allowPublish\n ? [\n createPublishGeneratedDocsEndpoint({\n docsCollectionSlug,\n docsSetsCollectionSlug: slug,\n }),\n ]\n : undefined,\n fields: [\n {\n name: 'title',\n type: 'text',\n required: true,\n },\n {\n name: 'slug',\n type: 'text',\n index: true,\n required: true,\n unique: true,\n },\n {\n name: 'group',\n type: 'relationship',\n relationTo: docsGroupsCollectionSlug,\n },\n {\n name: 'branch',\n type: 'text',\n admin: {\n description:\n 'Git branch allowed to publish this docs set. The full Git ref is handled internally.',\n },\n defaultValue: 'main',\n },\n {\n name: 'allowPullRequests',\n type: 'checkbox',\n admin: {\n description:\n 'Allow GitHub pull request events to dry-run or publish this docs set.',\n },\n defaultValue: false,\n },\n {\n name: 'description',\n type: 'textarea',\n },\n {\n name: 'advancedSecurity',\n type: 'group',\n admin: {\n description:\n 'Optional workflow lock-down. Leave disabled to allow any workflow from a trusted GitHub owner/repository and branch.',\n },\n fields: [\n {\n name: 'enabled',\n type: 'checkbox',\n admin: {\n description:\n 'When enabled, only the workflow refs listed below can publish this docs set.',\n },\n defaultValue: false,\n },\n {\n name: 'allowedWorkflowRefs',\n type: 'array',\n admin: {\n condition: (_data, siblingData) => siblingData?.enabled === true,\n description:\n 'Exact GitHub workflow refs, for example owner/repo/.github/workflows/publish-docs.yml@refs/heads/main.',\n },\n fields: [\n {\n name: 'value',\n type: 'text',\n required: true,\n },\n ],\n validate: (value, { siblingData }) => {\n const advancedSecurityData =\n typeof siblingData === 'object' && siblingData !== null\n ? (siblingData as { enabled?: unknown })\n : undefined\n\n if (\n advancedSecurityData?.enabled === true &&\n (!Array.isArray(value) || value.length === 0)\n ) {\n return 'Add at least one workflow ref or disable advanced security.'\n }\n\n return true\n },\n },\n ],\n },\n {\n name: 'aiExport',\n type: 'json',\n admin: {\n description:\n 'Parsed index.ai.yml control data for the raw Markdown AI export route.',\n },\n },\n {\n name: 'sync',\n type: 'group',\n fields: [\n {\n name: 'lastSyncedAt',\n type: 'date',\n },\n ...(syncRunsCollectionSlug\n ? [\n {\n name: 'lastSyncRunId',\n type: 'relationship' as const,\n relationTo: syncRunsCollectionSlug,\n },\n ]\n : []),\n {\n name: 'lastStatus',\n type: 'select',\n options: ['failed', 'pending', 'success'],\n },\n {\n name: 'docsCount',\n type: 'number',\n defaultValue: 0,\n },\n ],\n },\n ...(docsCollectionSlug\n ? [\n {\n name: 'docsSetManager',\n type: 'ui' as const,\n admin: {\n components: {\n Field: DOCS_SET_MANAGER_COMPONENT,\n },\n custom: {\n allowPublish,\n docsCollectionSlug,\n docsEnableDrafts,\n docsGroupsCollectionSlug,\n docsSetsCollectionSlug: slug,\n },\n },\n },\n ]\n : []),\n ],\n labels: {\n plural: 'Sets',\n singular: 'Set',\n },\n})\n"],"names":["DOCS_GLOBALS_ADMIN_GROUP","DOCS_SET_MANAGER_COMPONENT","createPublishGeneratedDocsEndpoint","createDocsSetsCollection","slug","allowPublish","docsCollectionSlug","docsEnableDrafts","docsGroupsCollectionSlug","syncRunsCollectionSlug","admin","defaultColumns","group","useAsTitle","endpoints","docsSetsCollectionSlug","undefined","fields","name","type","required","index","unique","relationTo","description","defaultValue","condition","_data","siblingData","enabled","validate","value","advancedSecurityData","Array","isArray","length","options","components","Field","custom","labels","plural","singular"],"mappings":"AAEA,SACEA,wBAAwB,EACxBC,0BAA0B,QACrB,kBAAiB;AACxB,SAASC,kCAAkC,QAAQ,uCAAsC;AAWzF,OAAO,MAAMC,2BAA2B,CAAC,EACvCC,IAAI,EACJC,eAAe,KAAK,EACpBC,kBAAkB,EAClBC,mBAAmB,KAAK,EACxBC,wBAAwB,EACxBC,sBAAsB,EACU,GAAwB,CAAA;QACxDL;QACAM,OAAO;YACLC,gBAAgB;gBAAC;gBAAS;gBAAQ;gBAAU;aAAY;YACxDC,OAAOZ;YACPa,YAAY;QACd;QACAC,WACER,sBAAsBC,oBAAoBF,eACtC;YACEH,mCAAmC;gBACjCI;gBACAS,wBAAwBX;YAC1B;SACD,GACDY;QACNC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,UAAU;YACZ;YACA;gBACEF,MAAM;gBACNC,MAAM;gBACNE,OAAO;gBACPD,UAAU;gBACVE,QAAQ;YACV;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNI,YAAYf;YACd;YACA;gBACEU,MAAM;gBACNC,MAAM;gBACNT,OAAO;oBACLc,aACE;gBACJ;gBACAC,cAAc;YAChB;YACA;gBACEP,MAAM;gBACNC,MAAM;gBACNT,OAAO;oBACLc,aACE;gBACJ;gBACAC,cAAc;YAChB;YACA;gBACEP,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;gBACNT,OAAO;oBACLc,aACE;gBACJ;gBACAP,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNT,OAAO;4BACLc,aACE;wBACJ;wBACAC,cAAc;oBAChB;oBACA;wBACEP,MAAM;wBACNC,MAAM;wBACNT,OAAO;4BACLgB,WAAW,CAACC,OAAOC,cAAgBA,aAAaC,YAAY;4BAC5DL,aACE;wBACJ;wBACAP,QAAQ;4BACN;gCACEC,MAAM;gCACNC,MAAM;gCACNC,UAAU;4BACZ;yBACD;wBACDU,UAAU,CAACC,OAAO,EAAEH,WAAW,EAAE;4BAC/B,MAAMI,uBACJ,OAAOJ,gBAAgB,YAAYA,gBAAgB,OAC9CA,cACDZ;4BAEN,IACEgB,sBAAsBH,YAAY,QACjC,CAAA,CAACI,MAAMC,OAAO,CAACH,UAAUA,MAAMI,MAAM,KAAK,CAAA,GAC3C;gCACA,OAAO;4BACT;4BAEA,OAAO;wBACT;oBACF;iBACD;YACH;YACA;gBACEjB,MAAM;gBACNC,MAAM;gBACNT,OAAO;oBACLc,aACE;gBACJ;YACF;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNF,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;oBACR;uBACIV,yBACA;wBACE;4BACES,MAAM;4BACNC,MAAM;4BACNI,YAAYd;wBACd;qBACD,GACD,EAAE;oBACN;wBACES,MAAM;wBACNC,MAAM;wBACNiB,SAAS;4BAAC;4BAAU;4BAAW;yBAAU;oBAC3C;oBACA;wBACElB,MAAM;wBACNC,MAAM;wBACNM,cAAc;oBAChB;iBACD;YACH;eACInB,qBACA;gBACE;oBACEY,MAAM;oBACNC,MAAM;oBACNT,OAAO;wBACL2B,YAAY;4BACVC,OAAOrC;wBACT;wBACAsC,QAAQ;4BACNlC;4BACAC;4BACAC;4BACAC;4BACAO,wBAAwBX;wBAC1B;oBACF;gBACF;aACD,GACD,EAAE;SACP;QACDoC,QAAQ;YACNC,QAAQ;YACRC,UAAU;QACZ;IACF,CAAA,EAAE"}
@@ -1,2 +1,4 @@
1
+ export { createPublishGeneratedDocsEndpoint } from './publishGeneratedDocs.js';
2
+ export type { CreatePublishGeneratedDocsEndpointOptions } from './publishGeneratedDocs.js';
1
3
  export { createSyncEndpoint } from './sync.js';
2
- export type { CreateSyncEndpointOptions, DocsSyncEndpointErrorCode, } from './sync.js';
4
+ export type { CreateSyncEndpointOptions, DocsSyncEndpointErrorCode } from './sync.js';
@@ -1,3 +1,4 @@
1
+ export { createPublishGeneratedDocsEndpoint } from './publishGeneratedDocs.js';
1
2
  export { createSyncEndpoint } from './sync.js';
2
3
 
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/index.ts"],"sourcesContent":["export { createSyncEndpoint } from './sync.js'\nexport type {\n CreateSyncEndpointOptions,\n DocsSyncEndpointErrorCode,\n} from './sync.js'\n"],"names":["createSyncEndpoint"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,YAAW"}
1
+ {"version":3,"sources":["../../src/endpoints/index.ts"],"sourcesContent":["export { createPublishGeneratedDocsEndpoint } from './publishGeneratedDocs.js'\nexport type { CreatePublishGeneratedDocsEndpointOptions } from './publishGeneratedDocs.js'\nexport { createSyncEndpoint } from './sync.js'\nexport type { CreateSyncEndpointOptions, DocsSyncEndpointErrorCode } from './sync.js'\n"],"names":["createPublishGeneratedDocsEndpoint","createSyncEndpoint"],"mappings":"AAAA,SAASA,kCAAkC,QAAQ,4BAA2B;AAE9E,SAASC,kBAAkB,QAAQ,YAAW"}
@@ -0,0 +1,6 @@
1
+ import type { Endpoint } from 'payload';
2
+ export type CreatePublishGeneratedDocsEndpointOptions = {
3
+ docsCollectionSlug: string;
4
+ docsSetsCollectionSlug: string;
5
+ };
6
+ export declare const createPublishGeneratedDocsEndpoint: ({ docsCollectionSlug, docsSetsCollectionSlug, }: CreatePublishGeneratedDocsEndpointOptions) => Endpoint;
@@ -0,0 +1,76 @@
1
+ import { publishGeneratedDocsForSet } from '../payload/publishGeneratedDocs.js';
2
+ const jsonResponse = (body, status = 200)=>Response.json(body, {
3
+ status
4
+ });
5
+ const getRouteParam = (req, key)=>{
6
+ const value = req.routeParams?.[key];
7
+ if (typeof value === 'string' || typeof value === 'number') {
8
+ return value;
9
+ }
10
+ return undefined;
11
+ };
12
+ const getRedirectTarget = (req)=>{
13
+ if (!req.url) {
14
+ return undefined;
15
+ }
16
+ const url = new URL(req.url);
17
+ const redirect = url.searchParams.get('redirect');
18
+ if (!redirect || !redirect.startsWith('/') || redirect.startsWith('//')) {
19
+ return undefined;
20
+ }
21
+ return redirect;
22
+ };
23
+ const redirectResponse = (location)=>new Response(null, {
24
+ headers: {
25
+ Location: location
26
+ },
27
+ status: 303
28
+ });
29
+ export const createPublishGeneratedDocsEndpoint = ({ docsCollectionSlug, docsSetsCollectionSlug })=>({
30
+ handler: async (req)=>{
31
+ if (!req.user) {
32
+ return jsonResponse({
33
+ error: 'Unauthorized',
34
+ ok: false
35
+ }, 401);
36
+ }
37
+ const docsSetId = getRouteParam(req, 'id');
38
+ if (docsSetId === undefined) {
39
+ return jsonResponse({
40
+ error: 'Missing docs set id.',
41
+ ok: false
42
+ }, 400);
43
+ }
44
+ try {
45
+ await req.payload.findByID({
46
+ id: docsSetId,
47
+ collection: docsSetsCollectionSlug,
48
+ depth: 0,
49
+ overrideAccess: false,
50
+ user: req.user
51
+ });
52
+ } catch {
53
+ return jsonResponse({
54
+ error: 'Forbidden',
55
+ ok: false
56
+ }, 403);
57
+ }
58
+ const summary = await publishGeneratedDocsForSet({
59
+ docsCollectionSlug,
60
+ docsSetId,
61
+ payload: req.payload
62
+ });
63
+ const redirect = getRedirectTarget(req);
64
+ if (redirect) {
65
+ return redirectResponse(redirect);
66
+ }
67
+ return jsonResponse({
68
+ ok: true,
69
+ summary
70
+ });
71
+ },
72
+ method: 'post',
73
+ path: '/:id/publish-generated-docs'
74
+ });
75
+
76
+ //# sourceMappingURL=publishGeneratedDocs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/publishGeneratedDocs.ts"],"sourcesContent":["import type { Endpoint, PayloadRequest } from 'payload'\n\nimport type { PublishGeneratedDocsPayloadOperations } from '../payload/publishGeneratedDocs.js'\n\nimport { publishGeneratedDocsForSet } from '../payload/publishGeneratedDocs.js'\n\nexport type CreatePublishGeneratedDocsEndpointOptions = {\n docsCollectionSlug: string\n docsSetsCollectionSlug: string\n}\n\ntype AccessCheckedPayload = {\n findByID: (args: Record<string, unknown>) => Promise<unknown>\n}\n\nconst jsonResponse = (body: Record<string, unknown>, status = 200): Response =>\n Response.json(body, {\n status,\n })\n\nconst getRouteParam = (req: PayloadRequest, key: string): number | string | undefined => {\n const value = req.routeParams?.[key]\n\n if (typeof value === 'string' || typeof value === 'number') {\n return value\n }\n\n return undefined\n}\n\nconst getRedirectTarget = (req: PayloadRequest): string | undefined => {\n if (!req.url) {\n return undefined\n }\n\n const url = new URL(req.url)\n const redirect = url.searchParams.get('redirect')\n\n if (!redirect || !redirect.startsWith('/') || redirect.startsWith('//')) {\n return undefined\n }\n\n return redirect\n}\n\nconst redirectResponse = (location: string): Response =>\n new Response(null, {\n headers: {\n Location: location,\n },\n status: 303,\n })\n\nexport const createPublishGeneratedDocsEndpoint = ({\n docsCollectionSlug,\n docsSetsCollectionSlug,\n}: CreatePublishGeneratedDocsEndpointOptions): Endpoint => ({\n handler: async (req) => {\n if (!req.user) {\n return jsonResponse(\n {\n error: 'Unauthorized',\n ok: false,\n },\n 401,\n )\n }\n\n const docsSetId = getRouteParam(req, 'id')\n\n if (docsSetId === undefined) {\n return jsonResponse(\n {\n error: 'Missing docs set id.',\n ok: false,\n },\n 400,\n )\n }\n\n try {\n await (req.payload as unknown as AccessCheckedPayload).findByID({\n id: docsSetId,\n collection: docsSetsCollectionSlug,\n depth: 0,\n overrideAccess: false,\n user: req.user,\n })\n } catch {\n return jsonResponse(\n {\n error: 'Forbidden',\n ok: false,\n },\n 403,\n )\n }\n\n const summary = await publishGeneratedDocsForSet({\n docsCollectionSlug,\n docsSetId,\n payload: req.payload as unknown as PublishGeneratedDocsPayloadOperations,\n })\n const redirect = getRedirectTarget(req)\n\n if (redirect) {\n return redirectResponse(redirect)\n }\n\n return jsonResponse({\n ok: true,\n summary,\n })\n },\n method: 'post',\n path: '/:id/publish-generated-docs',\n})\n"],"names":["publishGeneratedDocsForSet","jsonResponse","body","status","Response","json","getRouteParam","req","key","value","routeParams","undefined","getRedirectTarget","url","URL","redirect","searchParams","get","startsWith","redirectResponse","location","headers","Location","createPublishGeneratedDocsEndpoint","docsCollectionSlug","docsSetsCollectionSlug","handler","user","error","ok","docsSetId","payload","findByID","id","collection","depth","overrideAccess","summary","method","path"],"mappings":"AAIA,SAASA,0BAA0B,QAAQ,qCAAoC;AAW/E,MAAMC,eAAe,CAACC,MAA+BC,SAAS,GAAG,GAC/DC,SAASC,IAAI,CAACH,MAAM;QAClBC;IACF;AAEF,MAAMG,gBAAgB,CAACC,KAAqBC;IAC1C,MAAMC,QAAQF,IAAIG,WAAW,EAAE,CAACF,IAAI;IAEpC,IAAI,OAAOC,UAAU,YAAY,OAAOA,UAAU,UAAU;QAC1D,OAAOA;IACT;IAEA,OAAOE;AACT;AAEA,MAAMC,oBAAoB,CAACL;IACzB,IAAI,CAACA,IAAIM,GAAG,EAAE;QACZ,OAAOF;IACT;IAEA,MAAME,MAAM,IAAIC,IAAIP,IAAIM,GAAG;IAC3B,MAAME,WAAWF,IAAIG,YAAY,CAACC,GAAG,CAAC;IAEtC,IAAI,CAACF,YAAY,CAACA,SAASG,UAAU,CAAC,QAAQH,SAASG,UAAU,CAAC,OAAO;QACvE,OAAOP;IACT;IAEA,OAAOI;AACT;AAEA,MAAMI,mBAAmB,CAACC,WACxB,IAAIhB,SAAS,MAAM;QACjBiB,SAAS;YACPC,UAAUF;QACZ;QACAjB,QAAQ;IACV;AAEF,OAAO,MAAMoB,qCAAqC,CAAC,EACjDC,kBAAkB,EAClBC,sBAAsB,EACoB,GAAgB,CAAA;QAC1DC,SAAS,OAAOnB;YACd,IAAI,CAACA,IAAIoB,IAAI,EAAE;gBACb,OAAO1B,aACL;oBACE2B,OAAO;oBACPC,IAAI;gBACN,GACA;YAEJ;YAEA,MAAMC,YAAYxB,cAAcC,KAAK;YAErC,IAAIuB,cAAcnB,WAAW;gBAC3B,OAAOV,aACL;oBACE2B,OAAO;oBACPC,IAAI;gBACN,GACA;YAEJ;YAEA,IAAI;gBACF,MAAM,AAACtB,IAAIwB,OAAO,CAAqCC,QAAQ,CAAC;oBAC9DC,IAAIH;oBACJI,YAAYT;oBACZU,OAAO;oBACPC,gBAAgB;oBAChBT,MAAMpB,IAAIoB,IAAI;gBAChB;YACF,EAAE,OAAM;gBACN,OAAO1B,aACL;oBACE2B,OAAO;oBACPC,IAAI;gBACN,GACA;YAEJ;YAEA,MAAMQ,UAAU,MAAMrC,2BAA2B;gBAC/CwB;gBACAM;gBACAC,SAASxB,IAAIwB,OAAO;YACtB;YACA,MAAMhB,WAAWH,kBAAkBL;YAEnC,IAAIQ,UAAU;gBACZ,OAAOI,iBAAiBJ;YAC1B;YAEA,OAAOd,aAAa;gBAClB4B,IAAI;gBACJQ;YACF;QACF;QACAC,QAAQ;QACRC,MAAM;IACR,CAAA,EAAE"}
@@ -0,0 +1,29 @@
1
+ export type PublishGeneratedDocsPayloadOperations = {
2
+ find: (args: {
3
+ collection: string;
4
+ depth?: number;
5
+ limit?: number;
6
+ overrideAccess?: boolean;
7
+ where?: unknown;
8
+ }) => Promise<{
9
+ docs: unknown[];
10
+ }>;
11
+ update: (args: {
12
+ collection: string;
13
+ data: Record<string, unknown>;
14
+ id: number | string;
15
+ overrideAccess?: boolean;
16
+ }) => Promise<unknown>;
17
+ };
18
+ export type PublishGeneratedDocsResult = {
19
+ archived: number;
20
+ drafts: number;
21
+ published: number;
22
+ total: number;
23
+ updated: number;
24
+ };
25
+ export declare const publishGeneratedDocsForSet: ({ docsCollectionSlug, docsSetId, payload, }: {
26
+ docsCollectionSlug: string;
27
+ docsSetId: number | string;
28
+ payload: PublishGeneratedDocsPayloadOperations;
29
+ }) => Promise<PublishGeneratedDocsResult>;
@@ -0,0 +1,78 @@
1
+ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ const getRecordId = (doc)=>{
3
+ if (typeof doc.id === 'string' || typeof doc.id === 'number') {
4
+ return doc.id;
5
+ }
6
+ return undefined;
7
+ };
8
+ const getRelationshipId = (value)=>{
9
+ if (typeof value === 'string' || typeof value === 'number') {
10
+ return String(value);
11
+ }
12
+ if (isRecord(value)) {
13
+ const id = getRecordId(value);
14
+ return id === undefined ? undefined : String(id);
15
+ }
16
+ return undefined;
17
+ };
18
+ const isArchived = (doc)=>{
19
+ const sync = isRecord(doc.sync) ? doc.sync : undefined;
20
+ return sync?.archived === true;
21
+ };
22
+ export const publishGeneratedDocsForSet = async ({ docsCollectionSlug, docsSetId, payload })=>{
23
+ const result = await payload.find({
24
+ collection: docsCollectionSlug,
25
+ depth: 0,
26
+ limit: 1000,
27
+ overrideAccess: true,
28
+ where: {
29
+ docsSet: {
30
+ equals: docsSetId
31
+ }
32
+ }
33
+ });
34
+ const summary = {
35
+ archived: 0,
36
+ drafts: 0,
37
+ published: 0,
38
+ total: 0,
39
+ updated: 0
40
+ };
41
+ for (const rawDoc of result.docs){
42
+ if (!isRecord(rawDoc)) {
43
+ continue;
44
+ }
45
+ if (getRelationshipId(rawDoc.docsSet) !== String(docsSetId)) {
46
+ continue;
47
+ }
48
+ summary.total += 1;
49
+ if (isArchived(rawDoc)) {
50
+ summary.archived += 1;
51
+ continue;
52
+ }
53
+ if (rawDoc._status === 'published') {
54
+ summary.published += 1;
55
+ continue;
56
+ }
57
+ if (rawDoc._status === 'draft') {
58
+ summary.drafts += 1;
59
+ }
60
+ const id = getRecordId(rawDoc);
61
+ if (id === undefined) {
62
+ continue;
63
+ }
64
+ await payload.update({
65
+ id,
66
+ collection: docsCollectionSlug,
67
+ data: {
68
+ _status: 'published'
69
+ },
70
+ overrideAccess: true
71
+ });
72
+ summary.updated += 1;
73
+ summary.published += 1;
74
+ }
75
+ return summary;
76
+ };
77
+
78
+ //# sourceMappingURL=publishGeneratedDocs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/payload/publishGeneratedDocs.ts"],"sourcesContent":["export type PublishGeneratedDocsPayloadOperations = {\n find: (args: {\n collection: string\n depth?: number\n limit?: number\n overrideAccess?: boolean\n where?: unknown\n }) => Promise<{\n docs: unknown[]\n }>\n update: (args: {\n collection: string\n data: Record<string, unknown>\n id: number | string\n overrideAccess?: boolean\n }) => Promise<unknown>\n}\n\nexport type PublishGeneratedDocsResult = {\n archived: number\n drafts: number\n published: number\n total: number\n updated: number\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst getRecordId = (doc: Record<string, unknown>): number | string | undefined => {\n if (typeof doc.id === 'string' || typeof doc.id === 'number') {\n return doc.id\n }\n\n return undefined\n}\n\nconst getRelationshipId = (value: unknown): string | undefined => {\n if (typeof value === 'string' || typeof value === 'number') {\n return String(value)\n }\n\n if (isRecord(value)) {\n const id = getRecordId(value)\n\n return id === undefined ? undefined : String(id)\n }\n\n return undefined\n}\n\nconst isArchived = (doc: Record<string, unknown>): boolean => {\n const sync = isRecord(doc.sync) ? doc.sync : undefined\n\n return sync?.archived === true\n}\n\nexport const publishGeneratedDocsForSet = async ({\n docsCollectionSlug,\n docsSetId,\n payload,\n}: {\n docsCollectionSlug: string\n docsSetId: number | string\n payload: PublishGeneratedDocsPayloadOperations\n}): Promise<PublishGeneratedDocsResult> => {\n const result = await payload.find({\n collection: docsCollectionSlug,\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n where: {\n docsSet: {\n equals: docsSetId,\n },\n },\n })\n\n const summary: PublishGeneratedDocsResult = {\n archived: 0,\n drafts: 0,\n published: 0,\n total: 0,\n updated: 0,\n }\n\n for (const rawDoc of result.docs) {\n if (!isRecord(rawDoc)) {\n continue\n }\n\n if (getRelationshipId(rawDoc.docsSet) !== String(docsSetId)) {\n continue\n }\n\n summary.total += 1\n\n if (isArchived(rawDoc)) {\n summary.archived += 1\n continue\n }\n\n if (rawDoc._status === 'published') {\n summary.published += 1\n continue\n }\n\n if (rawDoc._status === 'draft') {\n summary.drafts += 1\n }\n\n const id = getRecordId(rawDoc)\n\n if (id === undefined) {\n continue\n }\n\n await payload.update({\n id,\n collection: docsCollectionSlug,\n data: {\n _status: 'published',\n },\n overrideAccess: true,\n })\n summary.updated += 1\n summary.published += 1\n }\n\n return summary\n}\n"],"names":["isRecord","value","Array","isArray","getRecordId","doc","id","undefined","getRelationshipId","String","isArchived","sync","archived","publishGeneratedDocsForSet","docsCollectionSlug","docsSetId","payload","result","find","collection","depth","limit","overrideAccess","where","docsSet","equals","summary","drafts","published","total","updated","rawDoc","docs","_status","update","data"],"mappings":"AA0BA,MAAMA,WAAW,CAACC,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF;AAEhE,MAAMG,cAAc,CAACC;IACnB,IAAI,OAAOA,IAAIC,EAAE,KAAK,YAAY,OAAOD,IAAIC,EAAE,KAAK,UAAU;QAC5D,OAAOD,IAAIC,EAAE;IACf;IAEA,OAAOC;AACT;AAEA,MAAMC,oBAAoB,CAACP;IACzB,IAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAAU;QAC1D,OAAOQ,OAAOR;IAChB;IAEA,IAAID,SAASC,QAAQ;QACnB,MAAMK,KAAKF,YAAYH;QAEvB,OAAOK,OAAOC,YAAYA,YAAYE,OAAOH;IAC/C;IAEA,OAAOC;AACT;AAEA,MAAMG,aAAa,CAACL;IAClB,MAAMM,OAAOX,SAASK,IAAIM,IAAI,IAAIN,IAAIM,IAAI,GAAGJ;IAE7C,OAAOI,MAAMC,aAAa;AAC5B;AAEA,OAAO,MAAMC,6BAA6B,OAAO,EAC/CC,kBAAkB,EAClBC,SAAS,EACTC,OAAO,EAKR;IACC,MAAMC,SAAS,MAAMD,QAAQE,IAAI,CAAC;QAChCC,YAAYL;QACZM,OAAO;QACPC,OAAO;QACPC,gBAAgB;QAChBC,OAAO;YACLC,SAAS;gBACPC,QAAQV;YACV;QACF;IACF;IAEA,MAAMW,UAAsC;QAC1Cd,UAAU;QACVe,QAAQ;QACRC,WAAW;QACXC,OAAO;QACPC,SAAS;IACX;IAEA,KAAK,MAAMC,UAAUd,OAAOe,IAAI,CAAE;QAChC,IAAI,CAAChC,SAAS+B,SAAS;YACrB;QACF;QAEA,IAAIvB,kBAAkBuB,OAAOP,OAAO,MAAMf,OAAOM,YAAY;YAC3D;QACF;QAEAW,QAAQG,KAAK,IAAI;QAEjB,IAAInB,WAAWqB,SAAS;YACtBL,QAAQd,QAAQ,IAAI;YACpB;QACF;QAEA,IAAImB,OAAOE,OAAO,KAAK,aAAa;YAClCP,QAAQE,SAAS,IAAI;YACrB;QACF;QAEA,IAAIG,OAAOE,OAAO,KAAK,SAAS;YAC9BP,QAAQC,MAAM,IAAI;QACpB;QAEA,MAAMrB,KAAKF,YAAY2B;QAEvB,IAAIzB,OAAOC,WAAW;YACpB;QACF;QAEA,MAAMS,QAAQkB,MAAM,CAAC;YACnB5B;YACAa,YAAYL;YACZqB,MAAM;gBACJF,SAAS;YACX;YACAX,gBAAgB;QAClB;QACAI,QAAQI,OAAO,IAAI;QACnBJ,QAAQE,SAAS,IAAI;IACvB;IAEA,OAAOF;AACT,EAAC"}
package/dist/plugin.js CHANGED
@@ -106,7 +106,9 @@ export const payloadMarkdownDocs = (pluginOptions = {})=>(incomingConfig)=>{
106
106
  ...docsSetsEnabled ? [
107
107
  createDocsSetsCollection({
108
108
  slug: docsSetsCollectionSlug,
109
+ allowPublish: pluginOptions.sync?.allowPublish === true,
109
110
  docsCollectionSlug: docsEnabled ? docsCollectionSlug : undefined,
111
+ docsEnableDrafts: enableDrafts,
110
112
  docsGroupsCollectionSlug,
111
113
  syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined
112
114
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { PayloadMarkdownDocsConfig } from './types.js'\n\nimport {\n createDocsCollection,\n createDocsGroupsCollection,\n createDocsKeysCollection,\n createDocsSetsCollection,\n createDocsTrustedCollection,\n createNoncesCollection,\n createSyncRunsCollection,\n} from './collections/index.js'\nimport {\n DEFAULT_DOCS_COLLECTION_SLUG,\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_KEYS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG,\n DEFAULT_MARKDOWN_FIELD_NAME,\n DEFAULT_MAX_BODY_BYTES,\n DEFAULT_PAGES_BRIDGE_FIELD,\n DEFAULT_PAGES_COLLECTION_SLUG,\n DEFAULT_PAGES_ROUTE_FIELD,\n} from './constants.js'\nimport { createSyncEndpoint } from './endpoints/index.js'\n\ntype ResolvedCollectionOptions = {\n docsCollectionSlug: string\n docsEnabled: boolean\n docsGroupsCollectionSlug: string\n docsGroupsEnabled: boolean\n docsKeysCollectionSlug: string\n docsKeysEnabled: boolean\n docsSetsCollectionSlug: string\n docsSetsEnabled: boolean\n docsTrustedCollectionSlug: string\n docsTrustedEnabled: boolean\n enableDrafts: boolean\n markdownFieldName: string\n noncesCollectionSlug: string\n noncesEnabled: boolean\n syncRunsCollectionSlug: string\n syncRunsEnabled: boolean\n}\n\nconst normalizeEndpointPath = (path: string): string => {\n const normalized = `/${path.trim()}`.replace(/\\/+/g, '/')\n\n return normalized.length > 1 ? normalized.replace(/\\/+$/g, '') : normalized\n}\n\nconst resolveCollectionOptions = (\n pluginOptions: PayloadMarkdownDocsConfig,\n): ResolvedCollectionOptions => {\n if (\n pluginOptions.target?.type !== undefined &&\n pluginOptions.target.type !== 'docsCollection'\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.type only supports \"docsCollection\". existingCollection is not supported.',\n )\n }\n\n const docsSlugFromTarget = pluginOptions.target?.slug\n const docsSlugFromCollections = pluginOptions.collections?.docs?.slug\n\n if (\n docsSlugFromTarget &&\n docsSlugFromCollections &&\n docsSlugFromTarget !== docsSlugFromCollections\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.slug and collections.docs.slug must match when both are provided.',\n )\n }\n\n return {\n docsCollectionSlug:\n docsSlugFromTarget ?? docsSlugFromCollections ?? DEFAULT_DOCS_COLLECTION_SLUG,\n docsEnabled: pluginOptions.collections?.docs?.enabled !== false,\n docsGroupsCollectionSlug:\n pluginOptions.collections?.docsGroups?.slug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n docsGroupsEnabled: pluginOptions.collections?.docsGroups?.enabled !== false,\n docsKeysCollectionSlug:\n pluginOptions.collections?.docsKeys?.slug ?? DEFAULT_DOCS_KEYS_COLLECTION_SLUG,\n docsKeysEnabled: pluginOptions.collections?.docsKeys?.enabled !== false,\n docsSetsCollectionSlug:\n pluginOptions.collections?.docsSets?.slug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n docsSetsEnabled: pluginOptions.collections?.docsSets?.enabled !== false,\n docsTrustedCollectionSlug:\n pluginOptions.collections?.docsTrusted?.slug ?? DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG,\n docsTrustedEnabled: pluginOptions.collections?.docsTrusted?.enabled !== false,\n enableDrafts:\n pluginOptions.target?.enableDrafts === true,\n markdownFieldName:\n pluginOptions.target?.markdownField ?? DEFAULT_MARKDOWN_FIELD_NAME,\n noncesCollectionSlug:\n pluginOptions.collections?.nonces?.slug ?? DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n noncesEnabled: pluginOptions.collections?.nonces?.enabled !== false,\n syncRunsCollectionSlug:\n pluginOptions.collections?.syncRuns?.slug ?? DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n syncRunsEnabled: pluginOptions.collections?.syncRuns?.enabled !== false,\n }\n}\n\nconst assertCollectionOptionCompatibility = ({\n docsGroupsEnabled,\n docsSetsEnabled,\n}: ResolvedCollectionOptions) => {\n if (docsSetsEnabled && !docsGroupsEnabled) {\n throw new Error(\n 'payloadMarkdownDocs: collections.docsSets requires collections.docsGroups to be enabled.',\n )\n }\n}\n\nconst assertNoCollectionSlugConflicts = (\n incomingConfig: Config,\n collectionSlugsToAdd: string[],\n) => {\n const duplicateRequestedSlug = collectionSlugsToAdd.find(\n (slug, index) => collectionSlugsToAdd.indexOf(slug) !== index,\n )\n\n if (duplicateRequestedSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${duplicateRequestedSlug}\" is configured more than once.`,\n )\n }\n\n const existingCollectionSlugs = new Set(\n incomingConfig.collections?.map((collection) => collection.slug) ?? [],\n )\n\n const conflictingSlug = collectionSlugsToAdd.find((slug) =>\n existingCollectionSlugs.has(slug),\n )\n\n if (conflictingSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${conflictingSlug}\" already exists in the Payload config.`,\n )\n }\n}\n\nexport const payloadMarkdownDocs =\n (pluginOptions: PayloadMarkdownDocsConfig = {}): Plugin =>\n (incomingConfig: Config): Config => {\n if (pluginOptions.enabled === false) {\n return incomingConfig\n }\n\n const {\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n } = resolveCollectionOptions(pluginOptions)\n assertCollectionOptionCompatibility({\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n })\n const endpointPath = normalizeEndpointPath(\n pluginOptions.endpoint?.path ?? DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n )\n\n const collectionSlugsToAdd = [\n ...(docsGroupsEnabled ? [docsGroupsCollectionSlug] : []),\n ...(docsSetsEnabled ? [docsSetsCollectionSlug] : []),\n ...(docsKeysEnabled ? [docsKeysCollectionSlug] : []),\n ...(docsTrustedEnabled ? [docsTrustedCollectionSlug] : []),\n ...(docsEnabled ? [docsCollectionSlug] : []),\n ...(syncRunsEnabled ? [syncRunsCollectionSlug] : []),\n ...(noncesEnabled ? [noncesCollectionSlug] : []),\n ]\n\n assertNoCollectionSlugConflicts(incomingConfig, collectionSlugsToAdd)\n\n const addedCollections = [\n ...(docsGroupsEnabled\n ? [\n createDocsGroupsCollection({\n slug: docsGroupsCollectionSlug,\n }),\n ]\n : []),\n ...(docsSetsEnabled\n ? [\n createDocsSetsCollection({\n slug: docsSetsCollectionSlug,\n docsCollectionSlug: docsEnabled ? docsCollectionSlug : undefined,\n docsGroupsCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(docsKeysEnabled\n ? [\n createDocsKeysCollection({\n slug: docsKeysCollectionSlug,\n }),\n ]\n : []),\n ...(docsTrustedEnabled\n ? [\n createDocsTrustedCollection({\n slug: docsTrustedCollectionSlug,\n }),\n ]\n : []),\n ...(docsEnabled\n ? [\n createDocsCollection({\n slug: docsCollectionSlug,\n docsSetsCollectionSlug: docsSetsEnabled\n ? docsSetsCollectionSlug\n : undefined,\n enableDrafts,\n markdownFieldName,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(syncRunsEnabled\n ? [\n createSyncRunsCollection({\n slug: syncRunsCollectionSlug,\n }),\n ]\n : []),\n ...(noncesEnabled\n ? [\n createNoncesCollection({\n slug: noncesCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ]\n\n return {\n ...incomingConfig,\n collections: [...(incomingConfig.collections ?? []), ...addedCollections],\n endpoints: [\n ...(incomingConfig.endpoints ?? []),\n createSyncEndpoint({\n allowHardDelete: pluginOptions.sync?.allowHardDelete,\n allowPublish: pluginOptions.sync?.allowPublish,\n allowWrites: pluginOptions.sync?.allowWrites,\n auth: pluginOptions.auth,\n defaultPublishMode: pluginOptions.sync?.defaultPublishMode,\n deleteBehavior: pluginOptions.sync?.deleteBehavior,\n docsCollectionSlug,\n docsEnabled,\n docsEnableDrafts: enableDrafts,\n docsGroupsCollectionSlug,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n endpointPath,\n markdownFieldName,\n maxBodyBytes: pluginOptions.endpoint?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n noncesCollectionSlug,\n noncesEnabled,\n requireDryRunBeforeApply: pluginOptions.sync?.requireDryRunBeforeApply,\n routing: {\n pages: {\n allowBridgePages:\n pluginOptions.routing?.pages?.allowBridgePages ?? true,\n bridgeField:\n pluginOptions.routing?.pages?.bridgeField ?? DEFAULT_PAGES_BRIDGE_FIELD,\n collection:\n pluginOptions.routing?.pages?.collection ?? DEFAULT_PAGES_COLLECTION_SLUG,\n enabled: pluginOptions.routing?.pages?.enabled === true,\n routeField:\n pluginOptions.routing?.pages?.routeField ?? DEFAULT_PAGES_ROUTE_FIELD,\n },\n },\n syncRunsCollectionSlug,\n syncRunsEnabled,\n }),\n ],\n }\n }\n"],"names":["createDocsCollection","createDocsGroupsCollection","createDocsKeysCollection","createDocsSetsCollection","createDocsTrustedCollection","createNoncesCollection","createSyncRunsCollection","DEFAULT_DOCS_COLLECTION_SLUG","DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_KEYS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_ENDPOINT_PATH","DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG","DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG","DEFAULT_MARKDOWN_FIELD_NAME","DEFAULT_MAX_BODY_BYTES","DEFAULT_PAGES_BRIDGE_FIELD","DEFAULT_PAGES_COLLECTION_SLUG","DEFAULT_PAGES_ROUTE_FIELD","createSyncEndpoint","normalizeEndpointPath","path","normalized","trim","replace","length","resolveCollectionOptions","pluginOptions","target","type","undefined","Error","docsSlugFromTarget","slug","docsSlugFromCollections","collections","docs","docsCollectionSlug","docsEnabled","enabled","docsGroupsCollectionSlug","docsGroups","docsGroupsEnabled","docsKeysCollectionSlug","docsKeys","docsKeysEnabled","docsSetsCollectionSlug","docsSets","docsSetsEnabled","docsTrustedCollectionSlug","docsTrusted","docsTrustedEnabled","enableDrafts","markdownFieldName","markdownField","noncesCollectionSlug","nonces","noncesEnabled","syncRunsCollectionSlug","syncRuns","syncRunsEnabled","assertCollectionOptionCompatibility","assertNoCollectionSlugConflicts","incomingConfig","collectionSlugsToAdd","duplicateRequestedSlug","find","index","indexOf","existingCollectionSlugs","Set","map","collection","conflictingSlug","has","payloadMarkdownDocs","endpointPath","endpoint","addedCollections","endpoints","allowHardDelete","sync","allowPublish","allowWrites","auth","defaultPublishMode","deleteBehavior","docsEnableDrafts","maxBodyBytes","requireDryRunBeforeApply","routing","pages","allowBridgePages","bridgeField","routeField"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Plugin } from 'payload'\n\nimport type { PayloadMarkdownDocsConfig } from './types.js'\n\nimport {\n createDocsCollection,\n createDocsGroupsCollection,\n createDocsKeysCollection,\n createDocsSetsCollection,\n createDocsTrustedCollection,\n createNoncesCollection,\n createSyncRunsCollection,\n} from './collections/index.js'\nimport {\n DEFAULT_DOCS_COLLECTION_SLUG,\n DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n DEFAULT_DOCS_KEYS_COLLECTION_SLUG,\n DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG,\n DEFAULT_MARKDOWN_FIELD_NAME,\n DEFAULT_MAX_BODY_BYTES,\n DEFAULT_PAGES_BRIDGE_FIELD,\n DEFAULT_PAGES_COLLECTION_SLUG,\n DEFAULT_PAGES_ROUTE_FIELD,\n} from './constants.js'\nimport { createSyncEndpoint } from './endpoints/index.js'\n\ntype ResolvedCollectionOptions = {\n docsCollectionSlug: string\n docsEnabled: boolean\n docsGroupsCollectionSlug: string\n docsGroupsEnabled: boolean\n docsKeysCollectionSlug: string\n docsKeysEnabled: boolean\n docsSetsCollectionSlug: string\n docsSetsEnabled: boolean\n docsTrustedCollectionSlug: string\n docsTrustedEnabled: boolean\n enableDrafts: boolean\n markdownFieldName: string\n noncesCollectionSlug: string\n noncesEnabled: boolean\n syncRunsCollectionSlug: string\n syncRunsEnabled: boolean\n}\n\nconst normalizeEndpointPath = (path: string): string => {\n const normalized = `/${path.trim()}`.replace(/\\/+/g, '/')\n\n return normalized.length > 1 ? normalized.replace(/\\/+$/g, '') : normalized\n}\n\nconst resolveCollectionOptions = (\n pluginOptions: PayloadMarkdownDocsConfig,\n): ResolvedCollectionOptions => {\n if (\n pluginOptions.target?.type !== undefined &&\n pluginOptions.target.type !== 'docsCollection'\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.type only supports \"docsCollection\". existingCollection is not supported.',\n )\n }\n\n const docsSlugFromTarget = pluginOptions.target?.slug\n const docsSlugFromCollections = pluginOptions.collections?.docs?.slug\n\n if (\n docsSlugFromTarget &&\n docsSlugFromCollections &&\n docsSlugFromTarget !== docsSlugFromCollections\n ) {\n throw new Error(\n 'payloadMarkdownDocs: target.slug and collections.docs.slug must match when both are provided.',\n )\n }\n\n return {\n docsCollectionSlug:\n docsSlugFromTarget ?? docsSlugFromCollections ?? DEFAULT_DOCS_COLLECTION_SLUG,\n docsEnabled: pluginOptions.collections?.docs?.enabled !== false,\n docsGroupsCollectionSlug:\n pluginOptions.collections?.docsGroups?.slug ?? DEFAULT_DOCS_GROUPS_COLLECTION_SLUG,\n docsGroupsEnabled: pluginOptions.collections?.docsGroups?.enabled !== false,\n docsKeysCollectionSlug:\n pluginOptions.collections?.docsKeys?.slug ?? DEFAULT_DOCS_KEYS_COLLECTION_SLUG,\n docsKeysEnabled: pluginOptions.collections?.docsKeys?.enabled !== false,\n docsSetsCollectionSlug:\n pluginOptions.collections?.docsSets?.slug ?? DEFAULT_DOCS_SETS_COLLECTION_SLUG,\n docsSetsEnabled: pluginOptions.collections?.docsSets?.enabled !== false,\n docsTrustedCollectionSlug:\n pluginOptions.collections?.docsTrusted?.slug ?? DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG,\n docsTrustedEnabled: pluginOptions.collections?.docsTrusted?.enabled !== false,\n enableDrafts:\n pluginOptions.target?.enableDrafts === true,\n markdownFieldName:\n pluginOptions.target?.markdownField ?? DEFAULT_MARKDOWN_FIELD_NAME,\n noncesCollectionSlug:\n pluginOptions.collections?.nonces?.slug ?? DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG,\n noncesEnabled: pluginOptions.collections?.nonces?.enabled !== false,\n syncRunsCollectionSlug:\n pluginOptions.collections?.syncRuns?.slug ?? DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG,\n syncRunsEnabled: pluginOptions.collections?.syncRuns?.enabled !== false,\n }\n}\n\nconst assertCollectionOptionCompatibility = ({\n docsGroupsEnabled,\n docsSetsEnabled,\n}: ResolvedCollectionOptions) => {\n if (docsSetsEnabled && !docsGroupsEnabled) {\n throw new Error(\n 'payloadMarkdownDocs: collections.docsSets requires collections.docsGroups to be enabled.',\n )\n }\n}\n\nconst assertNoCollectionSlugConflicts = (\n incomingConfig: Config,\n collectionSlugsToAdd: string[],\n) => {\n const duplicateRequestedSlug = collectionSlugsToAdd.find(\n (slug, index) => collectionSlugsToAdd.indexOf(slug) !== index,\n )\n\n if (duplicateRequestedSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${duplicateRequestedSlug}\" is configured more than once.`,\n )\n }\n\n const existingCollectionSlugs = new Set(\n incomingConfig.collections?.map((collection) => collection.slug) ?? [],\n )\n\n const conflictingSlug = collectionSlugsToAdd.find((slug) =>\n existingCollectionSlugs.has(slug),\n )\n\n if (conflictingSlug) {\n throw new Error(\n `payloadMarkdownDocs: collection slug \"${conflictingSlug}\" already exists in the Payload config.`,\n )\n }\n}\n\nexport const payloadMarkdownDocs =\n (pluginOptions: PayloadMarkdownDocsConfig = {}): Plugin =>\n (incomingConfig: Config): Config => {\n if (pluginOptions.enabled === false) {\n return incomingConfig\n }\n\n const {\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n } = resolveCollectionOptions(pluginOptions)\n assertCollectionOptionCompatibility({\n docsCollectionSlug,\n docsEnabled,\n docsGroupsCollectionSlug,\n docsGroupsEnabled,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n enableDrafts,\n markdownFieldName,\n noncesCollectionSlug,\n noncesEnabled,\n syncRunsCollectionSlug,\n syncRunsEnabled,\n })\n const endpointPath = normalizeEndpointPath(\n pluginOptions.endpoint?.path ?? DEFAULT_DOCS_SYNC_ENDPOINT_PATH,\n )\n\n const collectionSlugsToAdd = [\n ...(docsGroupsEnabled ? [docsGroupsCollectionSlug] : []),\n ...(docsSetsEnabled ? [docsSetsCollectionSlug] : []),\n ...(docsKeysEnabled ? [docsKeysCollectionSlug] : []),\n ...(docsTrustedEnabled ? [docsTrustedCollectionSlug] : []),\n ...(docsEnabled ? [docsCollectionSlug] : []),\n ...(syncRunsEnabled ? [syncRunsCollectionSlug] : []),\n ...(noncesEnabled ? [noncesCollectionSlug] : []),\n ]\n\n assertNoCollectionSlugConflicts(incomingConfig, collectionSlugsToAdd)\n\n const addedCollections = [\n ...(docsGroupsEnabled\n ? [\n createDocsGroupsCollection({\n slug: docsGroupsCollectionSlug,\n }),\n ]\n : []),\n ...(docsSetsEnabled\n ? [\n createDocsSetsCollection({\n slug: docsSetsCollectionSlug,\n allowPublish: pluginOptions.sync?.allowPublish === true,\n docsCollectionSlug: docsEnabled ? docsCollectionSlug : undefined,\n docsEnableDrafts: enableDrafts,\n docsGroupsCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(docsKeysEnabled\n ? [\n createDocsKeysCollection({\n slug: docsKeysCollectionSlug,\n }),\n ]\n : []),\n ...(docsTrustedEnabled\n ? [\n createDocsTrustedCollection({\n slug: docsTrustedCollectionSlug,\n }),\n ]\n : []),\n ...(docsEnabled\n ? [\n createDocsCollection({\n slug: docsCollectionSlug,\n docsSetsCollectionSlug: docsSetsEnabled\n ? docsSetsCollectionSlug\n : undefined,\n enableDrafts,\n markdownFieldName,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ...(syncRunsEnabled\n ? [\n createSyncRunsCollection({\n slug: syncRunsCollectionSlug,\n }),\n ]\n : []),\n ...(noncesEnabled\n ? [\n createNoncesCollection({\n slug: noncesCollectionSlug,\n syncRunsCollectionSlug: syncRunsEnabled ? syncRunsCollectionSlug : undefined,\n }),\n ]\n : []),\n ]\n\n return {\n ...incomingConfig,\n collections: [...(incomingConfig.collections ?? []), ...addedCollections],\n endpoints: [\n ...(incomingConfig.endpoints ?? []),\n createSyncEndpoint({\n allowHardDelete: pluginOptions.sync?.allowHardDelete,\n allowPublish: pluginOptions.sync?.allowPublish,\n allowWrites: pluginOptions.sync?.allowWrites,\n auth: pluginOptions.auth,\n defaultPublishMode: pluginOptions.sync?.defaultPublishMode,\n deleteBehavior: pluginOptions.sync?.deleteBehavior,\n docsCollectionSlug,\n docsEnabled,\n docsEnableDrafts: enableDrafts,\n docsGroupsCollectionSlug,\n docsKeysCollectionSlug,\n docsKeysEnabled,\n docsSetsCollectionSlug,\n docsSetsEnabled,\n docsTrustedCollectionSlug,\n docsTrustedEnabled,\n endpointPath,\n markdownFieldName,\n maxBodyBytes: pluginOptions.endpoint?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n noncesCollectionSlug,\n noncesEnabled,\n requireDryRunBeforeApply: pluginOptions.sync?.requireDryRunBeforeApply,\n routing: {\n pages: {\n allowBridgePages:\n pluginOptions.routing?.pages?.allowBridgePages ?? true,\n bridgeField:\n pluginOptions.routing?.pages?.bridgeField ?? DEFAULT_PAGES_BRIDGE_FIELD,\n collection:\n pluginOptions.routing?.pages?.collection ?? DEFAULT_PAGES_COLLECTION_SLUG,\n enabled: pluginOptions.routing?.pages?.enabled === true,\n routeField:\n pluginOptions.routing?.pages?.routeField ?? DEFAULT_PAGES_ROUTE_FIELD,\n },\n },\n syncRunsCollectionSlug,\n syncRunsEnabled,\n }),\n ],\n }\n }\n"],"names":["createDocsCollection","createDocsGroupsCollection","createDocsKeysCollection","createDocsSetsCollection","createDocsTrustedCollection","createNoncesCollection","createSyncRunsCollection","DEFAULT_DOCS_COLLECTION_SLUG","DEFAULT_DOCS_GROUPS_COLLECTION_SLUG","DEFAULT_DOCS_KEYS_COLLECTION_SLUG","DEFAULT_DOCS_SETS_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_ENDPOINT_PATH","DEFAULT_DOCS_SYNC_NONCES_COLLECTION_SLUG","DEFAULT_DOCS_SYNC_RUNS_COLLECTION_SLUG","DEFAULT_DOCS_TRUSTED_COLLECTION_SLUG","DEFAULT_MARKDOWN_FIELD_NAME","DEFAULT_MAX_BODY_BYTES","DEFAULT_PAGES_BRIDGE_FIELD","DEFAULT_PAGES_COLLECTION_SLUG","DEFAULT_PAGES_ROUTE_FIELD","createSyncEndpoint","normalizeEndpointPath","path","normalized","trim","replace","length","resolveCollectionOptions","pluginOptions","target","type","undefined","Error","docsSlugFromTarget","slug","docsSlugFromCollections","collections","docs","docsCollectionSlug","docsEnabled","enabled","docsGroupsCollectionSlug","docsGroups","docsGroupsEnabled","docsKeysCollectionSlug","docsKeys","docsKeysEnabled","docsSetsCollectionSlug","docsSets","docsSetsEnabled","docsTrustedCollectionSlug","docsTrusted","docsTrustedEnabled","enableDrafts","markdownFieldName","markdownField","noncesCollectionSlug","nonces","noncesEnabled","syncRunsCollectionSlug","syncRuns","syncRunsEnabled","assertCollectionOptionCompatibility","assertNoCollectionSlugConflicts","incomingConfig","collectionSlugsToAdd","duplicateRequestedSlug","find","index","indexOf","existingCollectionSlugs","Set","map","collection","conflictingSlug","has","payloadMarkdownDocs","endpointPath","endpoint","addedCollections","allowPublish","sync","docsEnableDrafts","endpoints","allowHardDelete","allowWrites","auth","defaultPublishMode","deleteBehavior","maxBodyBytes","requireDryRunBeforeApply","routing","pages","allowBridgePages","bridgeField","routeField"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare class DocsSyncKeyError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare const getEd25519PrivateKeyInput: (privateKey: string) => import("crypto").KeyObject;
5
+ export declare const getEd25519PublicKeyInput: (publicKey: string) => string | import("crypto").KeyObject;
6
+ export declare const buildOpenSshEd25519PublicKey: ({ comment, publicKey, }: {
7
+ comment?: string;
8
+ publicKey: Buffer;
9
+ }) => string;
@@ -0,0 +1,183 @@
1
+ import { createPrivateKey, createPublicKey } from 'node:crypto';
2
+ const opensshPrivateKeyBegin = '-----BEGIN OPENSSH PRIVATE KEY-----';
3
+ const opensshPrivateKeyEnd = '-----END OPENSSH PRIVATE KEY-----';
4
+ const pkcs8Ed25519PrivateKeyDerPrefix = Buffer.from('302e020100300506032b657004220420', 'hex');
5
+ const spkiEd25519PublicKeyDerPrefix = Buffer.from('302a300506032b6570032100', 'hex');
6
+ class BufferReader {
7
+ buffer;
8
+ offset = 0;
9
+ constructor(buffer){
10
+ this.buffer = buffer;
11
+ }
12
+ readBytes(length) {
13
+ if (length < 0 || this.remaining < length) {
14
+ throw new DocsSyncKeyError('OpenSSH key data is truncated.');
15
+ }
16
+ const value = this.buffer.subarray(this.offset, this.offset + length);
17
+ this.offset += length;
18
+ return value;
19
+ }
20
+ readString() {
21
+ const length = this.readUInt32();
22
+ return this.readBytes(length);
23
+ }
24
+ readUInt32() {
25
+ if (this.remaining < 4) {
26
+ throw new DocsSyncKeyError('OpenSSH key data is truncated.');
27
+ }
28
+ const value = this.buffer.readUInt32BE(this.offset);
29
+ this.offset += 4;
30
+ return value;
31
+ }
32
+ get remaining() {
33
+ return this.buffer.length - this.offset;
34
+ }
35
+ }
36
+ export class DocsSyncKeyError extends Error {
37
+ constructor(message){
38
+ super(message);
39
+ this.name = 'DocsSyncKeyError';
40
+ }
41
+ }
42
+ const packOpenSshString = (value)=>{
43
+ const buffer = typeof value === 'string' ? Buffer.from(value, 'utf8') : value;
44
+ const length = Buffer.alloc(4);
45
+ length.writeUInt32BE(buffer.length, 0);
46
+ return Buffer.concat([
47
+ length,
48
+ buffer
49
+ ]);
50
+ };
51
+ const normalizeBase64 = (value)=>value.replace(/\s+/g, '');
52
+ const createEd25519PrivateKeyFromSeed = (seed)=>{
53
+ if (seed.length !== 32) {
54
+ throw new DocsSyncKeyError('OpenSSH Ed25519 private key seed is invalid.');
55
+ }
56
+ return createPrivateKey({
57
+ type: 'pkcs8',
58
+ format: 'der',
59
+ key: Buffer.concat([
60
+ pkcs8Ed25519PrivateKeyDerPrefix,
61
+ seed
62
+ ])
63
+ });
64
+ };
65
+ const createEd25519PublicKeyFromRaw = (publicKey)=>{
66
+ if (publicKey.length !== 32) {
67
+ throw new DocsSyncKeyError('OpenSSH Ed25519 public key is invalid.');
68
+ }
69
+ return createPublicKey({
70
+ type: 'spki',
71
+ format: 'der',
72
+ key: Buffer.concat([
73
+ spkiEd25519PublicKeyDerPrefix,
74
+ publicKey
75
+ ])
76
+ });
77
+ };
78
+ const parseOpenSshPrivateKey = (privateKey)=>{
79
+ const begin = privateKey.indexOf(opensshPrivateKeyBegin);
80
+ const end = privateKey.indexOf(opensshPrivateKeyEnd);
81
+ if (begin < 0 || end < 0 || end <= begin) {
82
+ throw new DocsSyncKeyError('OpenSSH private key PEM is invalid.');
83
+ }
84
+ const base64Body = privateKey.slice(begin + opensshPrivateKeyBegin.length, end);
85
+ const data = Buffer.from(normalizeBase64(base64Body), 'base64');
86
+ const authMagic = Buffer.from('openssh-key-v1\0', 'utf8');
87
+ if (!data.subarray(0, authMagic.length).equals(authMagic)) {
88
+ throw new DocsSyncKeyError('OpenSSH private key magic header is invalid.');
89
+ }
90
+ const reader = new BufferReader(data.subarray(authMagic.length));
91
+ const cipherName = reader.readString().toString('utf8');
92
+ const kdfName = reader.readString().toString('utf8');
93
+ reader.readString();
94
+ if (cipherName !== 'none' || kdfName !== 'none') {
95
+ throw new DocsSyncKeyError('Encrypted OpenSSH private keys are not supported. Use `payload-markdown-docs keygen --out .docs-sync` or provide an unencrypted PKCS#8 PEM Ed25519 private key.');
96
+ }
97
+ const keyCount = reader.readUInt32();
98
+ if (keyCount !== 1) {
99
+ throw new DocsSyncKeyError('OpenSSH private key must contain exactly one key.');
100
+ }
101
+ reader.readString();
102
+ const privateBlob = reader.readString();
103
+ const privateReader = new BufferReader(privateBlob);
104
+ const checkInt = privateReader.readUInt32();
105
+ const repeatedCheckInt = privateReader.readUInt32();
106
+ if (checkInt !== repeatedCheckInt) {
107
+ throw new DocsSyncKeyError('OpenSSH private key check values do not match.');
108
+ }
109
+ const keyType = privateReader.readString().toString('utf8');
110
+ if (keyType !== 'ssh-ed25519') {
111
+ throw new DocsSyncKeyError('Only Ed25519 private keys are supported for docs sync signing.');
112
+ }
113
+ const publicKey = privateReader.readString();
114
+ const privateKeyBytes = privateReader.readString();
115
+ if (privateKeyBytes.length !== 64) {
116
+ throw new DocsSyncKeyError('OpenSSH Ed25519 private key payload is invalid.');
117
+ }
118
+ if (!privateKeyBytes.subarray(32).equals(publicKey)) {
119
+ throw new DocsSyncKeyError('OpenSSH Ed25519 private/public key data does not match.');
120
+ }
121
+ return createEd25519PrivateKeyFromSeed(privateKeyBytes.subarray(0, 32));
122
+ };
123
+ const parseOpenSshPublicKey = (publicKey)=>{
124
+ const [keyType, base64Key] = publicKey.trim().split(/\s+/, 3);
125
+ if (keyType !== 'ssh-ed25519' || !base64Key) {
126
+ throw new DocsSyncKeyError('Only Ed25519 public keys are supported for docs sync verification.');
127
+ }
128
+ const reader = new BufferReader(Buffer.from(base64Key, 'base64'));
129
+ const parsedKeyType = reader.readString().toString('utf8');
130
+ if (parsedKeyType !== 'ssh-ed25519') {
131
+ throw new DocsSyncKeyError('OpenSSH public key type does not match ssh-ed25519.');
132
+ }
133
+ return createEd25519PublicKeyFromRaw(reader.readString());
134
+ };
135
+ export const getEd25519PrivateKeyInput = (privateKey)=>{
136
+ const trimmed = privateKey.trim();
137
+ if (trimmed.includes('BEGIN OPENSSH PRIVATE KEY')) {
138
+ return parseOpenSshPrivateKey(trimmed);
139
+ }
140
+ if (trimmed.includes('BEGIN')) {
141
+ try {
142
+ return createPrivateKey(trimmed);
143
+ } catch {
144
+ throw new DocsSyncKeyError('Private key must be an Ed25519 PKCS#8 PEM key, base64 PKCS#8 DER key, or unencrypted OpenSSH Ed25519 private key.');
145
+ }
146
+ }
147
+ try {
148
+ return createPrivateKey({
149
+ type: 'pkcs8',
150
+ format: 'der',
151
+ key: Buffer.from(normalizeBase64(trimmed), 'base64')
152
+ });
153
+ } catch {
154
+ throw new DocsSyncKeyError('Private key must be an Ed25519 PKCS#8 PEM key, base64 PKCS#8 DER key, or unencrypted OpenSSH Ed25519 private key.');
155
+ }
156
+ };
157
+ export const getEd25519PublicKeyInput = (publicKey)=>{
158
+ const trimmed = publicKey.trim();
159
+ if (trimmed.startsWith('ssh-ed25519 ')) {
160
+ return parseOpenSshPublicKey(trimmed);
161
+ }
162
+ if (trimmed.includes('BEGIN PUBLIC KEY')) {
163
+ return trimmed;
164
+ }
165
+ return createPublicKey({
166
+ type: 'spki',
167
+ format: 'der',
168
+ key: Buffer.from(normalizeBase64(trimmed), 'base64')
169
+ });
170
+ };
171
+ export const buildOpenSshEd25519PublicKey = ({ comment, publicKey })=>{
172
+ const blob = Buffer.concat([
173
+ packOpenSshString('ssh-ed25519'),
174
+ packOpenSshString(publicKey)
175
+ ]);
176
+ return [
177
+ 'ssh-ed25519',
178
+ blob.toString('base64'),
179
+ comment
180
+ ].filter(Boolean).join(' ');
181
+ };
182
+
183
+ //# sourceMappingURL=ed25519Keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/security/ed25519Keys.ts"],"sourcesContent":["import {\n createPrivateKey,\n createPublicKey,\n} from 'node:crypto'\n\nconst opensshPrivateKeyBegin = '-----BEGIN OPENSSH PRIVATE KEY-----'\nconst opensshPrivateKeyEnd = '-----END OPENSSH PRIVATE KEY-----'\nconst pkcs8Ed25519PrivateKeyDerPrefix = Buffer.from(\n '302e020100300506032b657004220420',\n 'hex',\n)\nconst spkiEd25519PublicKeyDerPrefix = Buffer.from(\n '302a300506032b6570032100',\n 'hex',\n)\n\nclass BufferReader {\n private offset = 0\n\n constructor(private readonly buffer: Buffer) {}\n\n readBytes(length: number): Buffer {\n if (length < 0 || this.remaining < length) {\n throw new DocsSyncKeyError('OpenSSH key data is truncated.')\n }\n\n const value = this.buffer.subarray(this.offset, this.offset + length)\n this.offset += length\n\n return value\n }\n\n readString(): Buffer {\n const length = this.readUInt32()\n\n return this.readBytes(length)\n }\n\n readUInt32(): number {\n if (this.remaining < 4) {\n throw new DocsSyncKeyError('OpenSSH key data is truncated.')\n }\n\n const value = this.buffer.readUInt32BE(this.offset)\n this.offset += 4\n\n return value\n }\n\n get remaining(): number {\n return this.buffer.length - this.offset\n }\n}\n\nexport class DocsSyncKeyError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'DocsSyncKeyError'\n }\n}\n\nconst packOpenSshString = (value: Buffer | string): Buffer => {\n const buffer = typeof value === 'string' ? Buffer.from(value, 'utf8') : value\n const length = Buffer.alloc(4)\n length.writeUInt32BE(buffer.length, 0)\n\n return Buffer.concat([length, buffer])\n}\n\nconst normalizeBase64 = (value: string): string => value.replace(/\\s+/g, '')\n\nconst createEd25519PrivateKeyFromSeed = (seed: Buffer) => {\n if (seed.length !== 32) {\n throw new DocsSyncKeyError('OpenSSH Ed25519 private key seed is invalid.')\n }\n\n return createPrivateKey({\n type: 'pkcs8',\n format: 'der',\n key: Buffer.concat([pkcs8Ed25519PrivateKeyDerPrefix, seed]),\n })\n}\n\nconst createEd25519PublicKeyFromRaw = (publicKey: Buffer) => {\n if (publicKey.length !== 32) {\n throw new DocsSyncKeyError('OpenSSH Ed25519 public key is invalid.')\n }\n\n return createPublicKey({\n type: 'spki',\n format: 'der',\n key: Buffer.concat([spkiEd25519PublicKeyDerPrefix, publicKey]),\n })\n}\n\nconst parseOpenSshPrivateKey = (privateKey: string) => {\n const begin = privateKey.indexOf(opensshPrivateKeyBegin)\n const end = privateKey.indexOf(opensshPrivateKeyEnd)\n\n if (begin < 0 || end < 0 || end <= begin) {\n throw new DocsSyncKeyError('OpenSSH private key PEM is invalid.')\n }\n\n const base64Body = privateKey.slice(begin + opensshPrivateKeyBegin.length, end)\n const data = Buffer.from(normalizeBase64(base64Body), 'base64')\n const authMagic = Buffer.from('openssh-key-v1\\0', 'utf8')\n\n if (!data.subarray(0, authMagic.length).equals(authMagic)) {\n throw new DocsSyncKeyError('OpenSSH private key magic header is invalid.')\n }\n\n const reader = new BufferReader(data.subarray(authMagic.length))\n const cipherName = reader.readString().toString('utf8')\n const kdfName = reader.readString().toString('utf8')\n reader.readString()\n\n if (cipherName !== 'none' || kdfName !== 'none') {\n throw new DocsSyncKeyError(\n 'Encrypted OpenSSH private keys are not supported. Use `payload-markdown-docs keygen --out .docs-sync` or provide an unencrypted PKCS#8 PEM Ed25519 private key.',\n )\n }\n\n const keyCount = reader.readUInt32()\n\n if (keyCount !== 1) {\n throw new DocsSyncKeyError('OpenSSH private key must contain exactly one key.')\n }\n\n reader.readString()\n const privateBlob = reader.readString()\n const privateReader = new BufferReader(privateBlob)\n const checkInt = privateReader.readUInt32()\n const repeatedCheckInt = privateReader.readUInt32()\n\n if (checkInt !== repeatedCheckInt) {\n throw new DocsSyncKeyError('OpenSSH private key check values do not match.')\n }\n\n const keyType = privateReader.readString().toString('utf8')\n\n if (keyType !== 'ssh-ed25519') {\n throw new DocsSyncKeyError(\n 'Only Ed25519 private keys are supported for docs sync signing.',\n )\n }\n\n const publicKey = privateReader.readString()\n const privateKeyBytes = privateReader.readString()\n\n if (privateKeyBytes.length !== 64) {\n throw new DocsSyncKeyError('OpenSSH Ed25519 private key payload is invalid.')\n }\n\n if (!privateKeyBytes.subarray(32).equals(publicKey)) {\n throw new DocsSyncKeyError('OpenSSH Ed25519 private/public key data does not match.')\n }\n\n return createEd25519PrivateKeyFromSeed(privateKeyBytes.subarray(0, 32))\n}\n\nconst parseOpenSshPublicKey = (publicKey: string) => {\n const [keyType, base64Key] = publicKey.trim().split(/\\s+/, 3)\n\n if (keyType !== 'ssh-ed25519' || !base64Key) {\n throw new DocsSyncKeyError(\n 'Only Ed25519 public keys are supported for docs sync verification.',\n )\n }\n\n const reader = new BufferReader(Buffer.from(base64Key, 'base64'))\n const parsedKeyType = reader.readString().toString('utf8')\n\n if (parsedKeyType !== 'ssh-ed25519') {\n throw new DocsSyncKeyError('OpenSSH public key type does not match ssh-ed25519.')\n }\n\n return createEd25519PublicKeyFromRaw(reader.readString())\n}\n\nexport const getEd25519PrivateKeyInput = (privateKey: string) => {\n const trimmed = privateKey.trim()\n\n if (trimmed.includes('BEGIN OPENSSH PRIVATE KEY')) {\n return parseOpenSshPrivateKey(trimmed)\n }\n\n if (trimmed.includes('BEGIN')) {\n try {\n return createPrivateKey(trimmed)\n } catch {\n throw new DocsSyncKeyError(\n 'Private key must be an Ed25519 PKCS#8 PEM key, base64 PKCS#8 DER key, or unencrypted OpenSSH Ed25519 private key.',\n )\n }\n }\n\n try {\n return createPrivateKey({\n type: 'pkcs8',\n format: 'der',\n key: Buffer.from(normalizeBase64(trimmed), 'base64'),\n })\n } catch {\n throw new DocsSyncKeyError(\n 'Private key must be an Ed25519 PKCS#8 PEM key, base64 PKCS#8 DER key, or unencrypted OpenSSH Ed25519 private key.',\n )\n }\n}\n\nexport const getEd25519PublicKeyInput = (publicKey: string) => {\n const trimmed = publicKey.trim()\n\n if (trimmed.startsWith('ssh-ed25519 ')) {\n return parseOpenSshPublicKey(trimmed)\n }\n\n if (trimmed.includes('BEGIN PUBLIC KEY')) {\n return trimmed\n }\n\n return createPublicKey({\n type: 'spki',\n format: 'der',\n key: Buffer.from(normalizeBase64(trimmed), 'base64'),\n })\n}\n\nexport const buildOpenSshEd25519PublicKey = ({\n comment,\n publicKey,\n}: {\n comment?: string\n publicKey: Buffer\n}): string => {\n const blob = Buffer.concat([\n packOpenSshString('ssh-ed25519'),\n packOpenSshString(publicKey),\n ])\n\n return ['ssh-ed25519', blob.toString('base64'), comment].filter(Boolean).join(' ')\n}\n"],"names":["createPrivateKey","createPublicKey","opensshPrivateKeyBegin","opensshPrivateKeyEnd","pkcs8Ed25519PrivateKeyDerPrefix","Buffer","from","spkiEd25519PublicKeyDerPrefix","BufferReader","offset","buffer","readBytes","length","remaining","DocsSyncKeyError","value","subarray","readString","readUInt32","readUInt32BE","Error","message","name","packOpenSshString","alloc","writeUInt32BE","concat","normalizeBase64","replace","createEd25519PrivateKeyFromSeed","seed","type","format","key","createEd25519PublicKeyFromRaw","publicKey","parseOpenSshPrivateKey","privateKey","begin","indexOf","end","base64Body","slice","data","authMagic","equals","reader","cipherName","toString","kdfName","keyCount","privateBlob","privateReader","checkInt","repeatedCheckInt","keyType","privateKeyBytes","parseOpenSshPublicKey","base64Key","trim","split","parsedKeyType","getEd25519PrivateKeyInput","trimmed","includes","getEd25519PublicKeyInput","startsWith","buildOpenSshEd25519PublicKey","comment","blob","filter","Boolean","join"],"mappings":"AAAA,SACEA,gBAAgB,EAChBC,eAAe,QACV,cAAa;AAEpB,MAAMC,yBAAyB;AAC/B,MAAMC,uBAAuB;AAC7B,MAAMC,kCAAkCC,OAAOC,IAAI,CACjD,oCACA;AAEF,MAAMC,gCAAgCF,OAAOC,IAAI,CAC/C,4BACA;AAGF,MAAME;;IACIC,SAAS,EAAC;IAElB,YAAY,AAAiBC,MAAc,CAAE;aAAhBA,SAAAA;IAAiB;IAE9CC,UAAUC,MAAc,EAAU;QAChC,IAAIA,SAAS,KAAK,IAAI,CAACC,SAAS,GAAGD,QAAQ;YACzC,MAAM,IAAIE,iBAAiB;QAC7B;QAEA,MAAMC,QAAQ,IAAI,CAACL,MAAM,CAACM,QAAQ,CAAC,IAAI,CAACP,MAAM,EAAE,IAAI,CAACA,MAAM,GAAGG;QAC9D,IAAI,CAACH,MAAM,IAAIG;QAEf,OAAOG;IACT;IAEAE,aAAqB;QACnB,MAAML,SAAS,IAAI,CAACM,UAAU;QAE9B,OAAO,IAAI,CAACP,SAAS,CAACC;IACxB;IAEAM,aAAqB;QACnB,IAAI,IAAI,CAACL,SAAS,GAAG,GAAG;YACtB,MAAM,IAAIC,iBAAiB;QAC7B;QAEA,MAAMC,QAAQ,IAAI,CAACL,MAAM,CAACS,YAAY,CAAC,IAAI,CAACV,MAAM;QAClD,IAAI,CAACA,MAAM,IAAI;QAEf,OAAOM;IACT;IAEA,IAAIF,YAAoB;QACtB,OAAO,IAAI,CAACH,MAAM,CAACE,MAAM,GAAG,IAAI,CAACH,MAAM;IACzC;AACF;AAEA,OAAO,MAAMK,yBAAyBM;IACpC,YAAYC,OAAe,CAAE;QAC3B,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,MAAMC,oBAAoB,CAACR;IACzB,MAAML,SAAS,OAAOK,UAAU,WAAWV,OAAOC,IAAI,CAACS,OAAO,UAAUA;IACxE,MAAMH,SAASP,OAAOmB,KAAK,CAAC;IAC5BZ,OAAOa,aAAa,CAACf,OAAOE,MAAM,EAAE;IAEpC,OAAOP,OAAOqB,MAAM,CAAC;QAACd;QAAQF;KAAO;AACvC;AAEA,MAAMiB,kBAAkB,CAACZ,QAA0BA,MAAMa,OAAO,CAAC,QAAQ;AAEzE,MAAMC,kCAAkC,CAACC;IACvC,IAAIA,KAAKlB,MAAM,KAAK,IAAI;QACtB,MAAM,IAAIE,iBAAiB;IAC7B;IAEA,OAAOd,iBAAiB;QACtB+B,MAAM;QACNC,QAAQ;QACRC,KAAK5B,OAAOqB,MAAM,CAAC;YAACtB;YAAiC0B;SAAK;IAC5D;AACF;AAEA,MAAMI,gCAAgC,CAACC;IACrC,IAAIA,UAAUvB,MAAM,KAAK,IAAI;QAC3B,MAAM,IAAIE,iBAAiB;IAC7B;IAEA,OAAOb,gBAAgB;QACrB8B,MAAM;QACNC,QAAQ;QACRC,KAAK5B,OAAOqB,MAAM,CAAC;YAACnB;YAA+B4B;SAAU;IAC/D;AACF;AAEA,MAAMC,yBAAyB,CAACC;IAC9B,MAAMC,QAAQD,WAAWE,OAAO,CAACrC;IACjC,MAAMsC,MAAMH,WAAWE,OAAO,CAACpC;IAE/B,IAAImC,QAAQ,KAAKE,MAAM,KAAKA,OAAOF,OAAO;QACxC,MAAM,IAAIxB,iBAAiB;IAC7B;IAEA,MAAM2B,aAAaJ,WAAWK,KAAK,CAACJ,QAAQpC,uBAAuBU,MAAM,EAAE4B;IAC3E,MAAMG,OAAOtC,OAAOC,IAAI,CAACqB,gBAAgBc,aAAa;IACtD,MAAMG,YAAYvC,OAAOC,IAAI,CAAC,oBAAoB;IAElD,IAAI,CAACqC,KAAK3B,QAAQ,CAAC,GAAG4B,UAAUhC,MAAM,EAAEiC,MAAM,CAACD,YAAY;QACzD,MAAM,IAAI9B,iBAAiB;IAC7B;IAEA,MAAMgC,SAAS,IAAItC,aAAamC,KAAK3B,QAAQ,CAAC4B,UAAUhC,MAAM;IAC9D,MAAMmC,aAAaD,OAAO7B,UAAU,GAAG+B,QAAQ,CAAC;IAChD,MAAMC,UAAUH,OAAO7B,UAAU,GAAG+B,QAAQ,CAAC;IAC7CF,OAAO7B,UAAU;IAEjB,IAAI8B,eAAe,UAAUE,YAAY,QAAQ;QAC/C,MAAM,IAAInC,iBACR;IAEJ;IAEA,MAAMoC,WAAWJ,OAAO5B,UAAU;IAElC,IAAIgC,aAAa,GAAG;QAClB,MAAM,IAAIpC,iBAAiB;IAC7B;IAEAgC,OAAO7B,UAAU;IACjB,MAAMkC,cAAcL,OAAO7B,UAAU;IACrC,MAAMmC,gBAAgB,IAAI5C,aAAa2C;IACvC,MAAME,WAAWD,cAAclC,UAAU;IACzC,MAAMoC,mBAAmBF,cAAclC,UAAU;IAEjD,IAAImC,aAAaC,kBAAkB;QACjC,MAAM,IAAIxC,iBAAiB;IAC7B;IAEA,MAAMyC,UAAUH,cAAcnC,UAAU,GAAG+B,QAAQ,CAAC;IAEpD,IAAIO,YAAY,eAAe;QAC7B,MAAM,IAAIzC,iBACR;IAEJ;IAEA,MAAMqB,YAAYiB,cAAcnC,UAAU;IAC1C,MAAMuC,kBAAkBJ,cAAcnC,UAAU;IAEhD,IAAIuC,gBAAgB5C,MAAM,KAAK,IAAI;QACjC,MAAM,IAAIE,iBAAiB;IAC7B;IAEA,IAAI,CAAC0C,gBAAgBxC,QAAQ,CAAC,IAAI6B,MAAM,CAACV,YAAY;QACnD,MAAM,IAAIrB,iBAAiB;IAC7B;IAEA,OAAOe,gCAAgC2B,gBAAgBxC,QAAQ,CAAC,GAAG;AACrE;AAEA,MAAMyC,wBAAwB,CAACtB;IAC7B,MAAM,CAACoB,SAASG,UAAU,GAAGvB,UAAUwB,IAAI,GAAGC,KAAK,CAAC,OAAO;IAE3D,IAAIL,YAAY,iBAAiB,CAACG,WAAW;QAC3C,MAAM,IAAI5C,iBACR;IAEJ;IAEA,MAAMgC,SAAS,IAAItC,aAAaH,OAAOC,IAAI,CAACoD,WAAW;IACvD,MAAMG,gBAAgBf,OAAO7B,UAAU,GAAG+B,QAAQ,CAAC;IAEnD,IAAIa,kBAAkB,eAAe;QACnC,MAAM,IAAI/C,iBAAiB;IAC7B;IAEA,OAAOoB,8BAA8BY,OAAO7B,UAAU;AACxD;AAEA,OAAO,MAAM6C,4BAA4B,CAACzB;IACxC,MAAM0B,UAAU1B,WAAWsB,IAAI;IAE/B,IAAII,QAAQC,QAAQ,CAAC,8BAA8B;QACjD,OAAO5B,uBAAuB2B;IAChC;IAEA,IAAIA,QAAQC,QAAQ,CAAC,UAAU;QAC7B,IAAI;YACF,OAAOhE,iBAAiB+D;QAC1B,EAAE,OAAM;YACN,MAAM,IAAIjD,iBACR;QAEJ;IACF;IAEA,IAAI;QACF,OAAOd,iBAAiB;YACtB+B,MAAM;YACNC,QAAQ;YACRC,KAAK5B,OAAOC,IAAI,CAACqB,gBAAgBoC,UAAU;QAC7C;IACF,EAAE,OAAM;QACN,MAAM,IAAIjD,iBACR;IAEJ;AACF,EAAC;AAED,OAAO,MAAMmD,2BAA2B,CAAC9B;IACvC,MAAM4B,UAAU5B,UAAUwB,IAAI;IAE9B,IAAII,QAAQG,UAAU,CAAC,iBAAiB;QACtC,OAAOT,sBAAsBM;IAC/B;IAEA,IAAIA,QAAQC,QAAQ,CAAC,qBAAqB;QACxC,OAAOD;IACT;IAEA,OAAO9D,gBAAgB;QACrB8B,MAAM;QACNC,QAAQ;QACRC,KAAK5B,OAAOC,IAAI,CAACqB,gBAAgBoC,UAAU;IAC7C;AACF,EAAC;AAED,OAAO,MAAMI,+BAA+B,CAAC,EAC3CC,OAAO,EACPjC,SAAS,EAIV;IACC,MAAMkC,OAAOhE,OAAOqB,MAAM,CAAC;QACzBH,kBAAkB;QAClBA,kBAAkBY;KACnB;IAED,OAAO;QAAC;QAAekC,KAAKrB,QAAQ,CAAC;QAAWoB;KAAQ,CAACE,MAAM,CAACC,SAASC,IAAI,CAAC;AAChF,EAAC"}
@@ -1,5 +1,6 @@
1
1
  export { buildCanonicalSigningString, getCanonicalPathFromRequestUrl, } from './canonical.js';
2
2
  export type { CanonicalSigningStringInput } from './canonical.js';
3
+ export { buildOpenSshEd25519PublicKey, DocsSyncKeyError, getEd25519PrivateKeyInput, getEd25519PublicKeyInput, } from './ed25519Keys.js';
3
4
  export { verifyGitHubOidcToken } from './githubOidc.js';
4
5
  export type { GitHubOidcClaims, GitHubOidcErrorCode, GitHubOidcTrustedSource, GitHubOidcVerifyConfig, VerifiedGitHubOidcToken, VerifyGitHubOidcTokenResult, } from './githubOidc.js';
5
6
  export { extractSyncRequestHeaders, syncHeaderNames } from './headers.js';
@@ -1,4 +1,5 @@
1
1
  export { buildCanonicalSigningString, getCanonicalPathFromRequestUrl } from './canonical.js';
2
+ export { buildOpenSshEd25519PublicKey, DocsSyncKeyError, getEd25519PrivateKeyInput, getEd25519PublicKeyInput } from './ed25519Keys.js';
2
3
  export { verifyGitHubOidcToken } from './githubOidc.js';
3
4
  export { extractSyncRequestHeaders, syncHeaderNames } from './headers.js';
4
5
  export { decodeJwt, toBase64Url } from './jwt.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/security/index.ts"],"sourcesContent":["export {\n buildCanonicalSigningString,\n getCanonicalPathFromRequestUrl,\n} from './canonical.js'\nexport type { CanonicalSigningStringInput } from './canonical.js'\nexport { verifyGitHubOidcToken } from './githubOidc.js'\nexport type {\n GitHubOidcClaims,\n GitHubOidcErrorCode,\n GitHubOidcTrustedSource,\n GitHubOidcVerifyConfig,\n VerifiedGitHubOidcToken,\n VerifyGitHubOidcTokenResult,\n} from './githubOidc.js'\nexport { extractSyncRequestHeaders, syncHeaderNames } from './headers.js'\nexport type {\n ExtractSyncHeadersResult,\n SyncRequestHeaders,\n} from './headers.js'\nexport type { FetchJson } from './jwks.js'\nexport {\n decodeJwt,\n toBase64Url,\n} from './jwt.js'\nexport type { DecodedJwt } from './jwt.js'\nexport {\n assertNonceNotReplayed,\n storeAcceptedNonce,\n} from './nonce.js'\nexport type { NoncePayloadOperations } from './nonce.js'\nexport { signDocsSyncRequest } from './sign.js'\nexport type {\n SignDocsSyncRequestOptions,\n SignedDocsSyncRequest,\n} from './sign.js'\nexport {\n validateTimestampSkew,\n verifyBodySha256,\n verifyEd25519Signature,\n} from './verify.js'\nexport type {\n ValidateTimestampResult,\n VerifyBodyHashResult,\n} from './verify.js'\n"],"names":["buildCanonicalSigningString","getCanonicalPathFromRequestUrl","verifyGitHubOidcToken","extractSyncRequestHeaders","syncHeaderNames","decodeJwt","toBase64Url","assertNonceNotReplayed","storeAcceptedNonce","signDocsSyncRequest","validateTimestampSkew","verifyBodySha256","verifyEd25519Signature"],"mappings":"AAAA,SACEA,2BAA2B,EAC3BC,8BAA8B,QACzB,iBAAgB;AAEvB,SAASC,qBAAqB,QAAQ,kBAAiB;AASvD,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,eAAc;AAMzE,SACEC,SAAS,EACTC,WAAW,QACN,WAAU;AAEjB,SACEC,sBAAsB,EACtBC,kBAAkB,QACb,aAAY;AAEnB,SAASC,mBAAmB,QAAQ,YAAW;AAK/C,SACEC,qBAAqB,EACrBC,gBAAgB,EAChBC,sBAAsB,QACjB,cAAa"}
1
+ {"version":3,"sources":["../../src/security/index.ts"],"sourcesContent":["export {\n buildCanonicalSigningString,\n getCanonicalPathFromRequestUrl,\n} from './canonical.js'\nexport type { CanonicalSigningStringInput } from './canonical.js'\nexport {\n buildOpenSshEd25519PublicKey,\n DocsSyncKeyError,\n getEd25519PrivateKeyInput,\n getEd25519PublicKeyInput,\n} from './ed25519Keys.js'\nexport { verifyGitHubOidcToken } from './githubOidc.js'\nexport type {\n GitHubOidcClaims,\n GitHubOidcErrorCode,\n GitHubOidcTrustedSource,\n GitHubOidcVerifyConfig,\n VerifiedGitHubOidcToken,\n VerifyGitHubOidcTokenResult,\n} from './githubOidc.js'\nexport { extractSyncRequestHeaders, syncHeaderNames } from './headers.js'\nexport type {\n ExtractSyncHeadersResult,\n SyncRequestHeaders,\n} from './headers.js'\nexport type { FetchJson } from './jwks.js'\nexport {\n decodeJwt,\n toBase64Url,\n} from './jwt.js'\nexport type { DecodedJwt } from './jwt.js'\nexport {\n assertNonceNotReplayed,\n storeAcceptedNonce,\n} from './nonce.js'\nexport type { NoncePayloadOperations } from './nonce.js'\nexport { signDocsSyncRequest } from './sign.js'\nexport type {\n SignDocsSyncRequestOptions,\n SignedDocsSyncRequest,\n} from './sign.js'\nexport {\n validateTimestampSkew,\n verifyBodySha256,\n verifyEd25519Signature,\n} from './verify.js'\nexport type {\n ValidateTimestampResult,\n VerifyBodyHashResult,\n} from './verify.js'\n"],"names":["buildCanonicalSigningString","getCanonicalPathFromRequestUrl","buildOpenSshEd25519PublicKey","DocsSyncKeyError","getEd25519PrivateKeyInput","getEd25519PublicKeyInput","verifyGitHubOidcToken","extractSyncRequestHeaders","syncHeaderNames","decodeJwt","toBase64Url","assertNonceNotReplayed","storeAcceptedNonce","signDocsSyncRequest","validateTimestampSkew","verifyBodySha256","verifyEd25519Signature"],"mappings":"AAAA,SACEA,2BAA2B,EAC3BC,8BAA8B,QACzB,iBAAgB;AAEvB,SACEC,4BAA4B,EAC5BC,gBAAgB,EAChBC,yBAAyB,EACzBC,wBAAwB,QACnB,mBAAkB;AACzB,SAASC,qBAAqB,QAAQ,kBAAiB;AASvD,SAASC,yBAAyB,EAAEC,eAAe,QAAQ,eAAc;AAMzE,SACEC,SAAS,EACTC,WAAW,QACN,WAAU;AAEjB,SACEC,sBAAsB,EACtBC,kBAAkB,QACb,aAAY;AAEnB,SAASC,mBAAmB,QAAQ,YAAW;AAK/C,SACEC,qBAAqB,EACrBC,gBAAgB,EAChBC,sBAAsB,QACjB,cAAa"}
@@ -1,16 +1,7 @@
1
- import { createPrivateKey, randomUUID, sign } from 'node:crypto';
1
+ import { randomUUID, sign } from 'node:crypto';
2
2
  import { sha256Hex } from '../sync/index.js';
3
3
  import { buildCanonicalSigningString } from './canonical.js';
4
- const getPrivateKeyInput = (privateKey)=>{
5
- if (privateKey.includes('BEGIN PRIVATE KEY')) {
6
- return privateKey;
7
- }
8
- return createPrivateKey({
9
- type: 'pkcs8',
10
- format: 'der',
11
- key: Buffer.from(privateKey, 'base64')
12
- });
13
- };
4
+ import { getEd25519PrivateKeyInput } from './ed25519Keys.js';
14
5
  const getEndpointPathname = (endpoint)=>new URL(endpoint).pathname;
15
6
  export const signDocsSyncRequest = ({ body, endpoint, keyId, nonce = randomUUID(), now = new Date(), privateKey })=>{
16
7
  const bodySha256 = sha256Hex(body);
@@ -22,7 +13,7 @@ export const signDocsSyncRequest = ({ body, endpoint, keyId, nonce = randomUUID(
22
13
  path: getEndpointPathname(endpoint),
23
14
  timestamp
24
15
  });
25
- const signature = sign(null, Buffer.from(canonicalString, 'utf8'), getPrivateKeyInput(privateKey)).toString('base64');
16
+ const signature = sign(null, Buffer.from(canonicalString, 'utf8'), getEd25519PrivateKeyInput(privateKey)).toString('base64');
26
17
  return {
27
18
  body,
28
19
  headers: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/security/sign.ts"],"sourcesContent":["import {\n createPrivateKey,\n randomUUID,\n sign,\n} from 'node:crypto'\n\nimport { sha256Hex } from '../sync/index.js'\nimport { buildCanonicalSigningString } from './canonical.js'\n\nexport type SignDocsSyncRequestOptions = {\n body: string\n endpoint: string\n keyId: string\n nonce?: string\n now?: Date\n privateKey: string\n}\n\nexport type SignedDocsSyncRequest = {\n body: string\n headers: Record<string, string>\n}\n\nconst getPrivateKeyInput = (privateKey: string) => {\n if (privateKey.includes('BEGIN PRIVATE KEY')) {\n return privateKey\n }\n\n return createPrivateKey({\n type: 'pkcs8',\n format: 'der',\n key: Buffer.from(privateKey, 'base64'),\n })\n}\n\nconst getEndpointPathname = (endpoint: string): string => new URL(endpoint).pathname\n\nexport const signDocsSyncRequest = ({\n body,\n endpoint,\n keyId,\n nonce = randomUUID(),\n now = new Date(),\n privateKey,\n}: SignDocsSyncRequestOptions): SignedDocsSyncRequest => {\n const bodySha256 = sha256Hex(body)\n const timestamp = now.toISOString()\n const canonicalString = buildCanonicalSigningString({\n bodySha256,\n method: 'POST',\n nonce,\n path: getEndpointPathname(endpoint),\n timestamp,\n })\n const signature = sign(\n null,\n Buffer.from(canonicalString, 'utf8'),\n getPrivateKeyInput(privateKey),\n ).toString('base64')\n\n return {\n body,\n headers: {\n 'Content-Type': 'application/json',\n 'X-VL-MD-DOCS-Body-SHA256': bodySha256,\n 'X-VL-MD-DOCS-Key-Id': keyId,\n 'X-VL-MD-DOCS-Nonce': nonce,\n 'X-VL-MD-DOCS-Signature': signature,\n 'X-VL-MD-DOCS-Timestamp': timestamp,\n },\n }\n}\n"],"names":["createPrivateKey","randomUUID","sign","sha256Hex","buildCanonicalSigningString","getPrivateKeyInput","privateKey","includes","type","format","key","Buffer","from","getEndpointPathname","endpoint","URL","pathname","signDocsSyncRequest","body","keyId","nonce","now","Date","bodySha256","timestamp","toISOString","canonicalString","method","path","signature","toString","headers"],"mappings":"AAAA,SACEA,gBAAgB,EAChBC,UAAU,EACVC,IAAI,QACC,cAAa;AAEpB,SAASC,SAAS,QAAQ,mBAAkB;AAC5C,SAASC,2BAA2B,QAAQ,iBAAgB;AAgB5D,MAAMC,qBAAqB,CAACC;IAC1B,IAAIA,WAAWC,QAAQ,CAAC,sBAAsB;QAC5C,OAAOD;IACT;IAEA,OAAON,iBAAiB;QACtBQ,MAAM;QACNC,QAAQ;QACRC,KAAKC,OAAOC,IAAI,CAACN,YAAY;IAC/B;AACF;AAEA,MAAMO,sBAAsB,CAACC,WAA6B,IAAIC,IAAID,UAAUE,QAAQ;AAEpF,OAAO,MAAMC,sBAAsB,CAAC,EAClCC,IAAI,EACJJ,QAAQ,EACRK,KAAK,EACLC,QAAQnB,YAAY,EACpBoB,MAAM,IAAIC,MAAM,EAChBhB,UAAU,EACiB;IAC3B,MAAMiB,aAAapB,UAAUe;IAC7B,MAAMM,YAAYH,IAAII,WAAW;IACjC,MAAMC,kBAAkBtB,4BAA4B;QAClDmB;QACAI,QAAQ;QACRP;QACAQ,MAAMf,oBAAoBC;QAC1BU;IACF;IACA,MAAMK,YAAY3B,KAChB,MACAS,OAAOC,IAAI,CAACc,iBAAiB,SAC7BrB,mBAAmBC,aACnBwB,QAAQ,CAAC;IAEX,OAAO;QACLZ;QACAa,SAAS;YACP,gBAAgB;YAChB,4BAA4BR;YAC5B,uBAAuBJ;YACvB,sBAAsBC;YACtB,0BAA0BS;YAC1B,0BAA0BL;QAC5B;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/security/sign.ts"],"sourcesContent":["import {\n randomUUID,\n sign,\n} from 'node:crypto'\n\nimport { sha256Hex } from '../sync/index.js'\nimport { buildCanonicalSigningString } from './canonical.js'\nimport { getEd25519PrivateKeyInput } from './ed25519Keys.js'\n\nexport type SignDocsSyncRequestOptions = {\n body: string\n endpoint: string\n keyId: string\n nonce?: string\n now?: Date\n privateKey: string\n}\n\nexport type SignedDocsSyncRequest = {\n body: string\n headers: Record<string, string>\n}\n\nconst getEndpointPathname = (endpoint: string): string => new URL(endpoint).pathname\n\nexport const signDocsSyncRequest = ({\n body,\n endpoint,\n keyId,\n nonce = randomUUID(),\n now = new Date(),\n privateKey,\n}: SignDocsSyncRequestOptions): SignedDocsSyncRequest => {\n const bodySha256 = sha256Hex(body)\n const timestamp = now.toISOString()\n const canonicalString = buildCanonicalSigningString({\n bodySha256,\n method: 'POST',\n nonce,\n path: getEndpointPathname(endpoint),\n timestamp,\n })\n const signature = sign(\n null,\n Buffer.from(canonicalString, 'utf8'),\n getEd25519PrivateKeyInput(privateKey),\n ).toString('base64')\n\n return {\n body,\n headers: {\n 'Content-Type': 'application/json',\n 'X-VL-MD-DOCS-Body-SHA256': bodySha256,\n 'X-VL-MD-DOCS-Key-Id': keyId,\n 'X-VL-MD-DOCS-Nonce': nonce,\n 'X-VL-MD-DOCS-Signature': signature,\n 'X-VL-MD-DOCS-Timestamp': timestamp,\n },\n }\n}\n"],"names":["randomUUID","sign","sha256Hex","buildCanonicalSigningString","getEd25519PrivateKeyInput","getEndpointPathname","endpoint","URL","pathname","signDocsSyncRequest","body","keyId","nonce","now","Date","privateKey","bodySha256","timestamp","toISOString","canonicalString","method","path","signature","Buffer","from","toString","headers"],"mappings":"AAAA,SACEA,UAAU,EACVC,IAAI,QACC,cAAa;AAEpB,SAASC,SAAS,QAAQ,mBAAkB;AAC5C,SAASC,2BAA2B,QAAQ,iBAAgB;AAC5D,SAASC,yBAAyB,QAAQ,mBAAkB;AAgB5D,MAAMC,sBAAsB,CAACC,WAA6B,IAAIC,IAAID,UAAUE,QAAQ;AAEpF,OAAO,MAAMC,sBAAsB,CAAC,EAClCC,IAAI,EACJJ,QAAQ,EACRK,KAAK,EACLC,QAAQZ,YAAY,EACpBa,MAAM,IAAIC,MAAM,EAChBC,UAAU,EACiB;IAC3B,MAAMC,aAAad,UAAUQ;IAC7B,MAAMO,YAAYJ,IAAIK,WAAW;IACjC,MAAMC,kBAAkBhB,4BAA4B;QAClDa;QACAI,QAAQ;QACRR;QACAS,MAAMhB,oBAAoBC;QAC1BW;IACF;IACA,MAAMK,YAAYrB,KAChB,MACAsB,OAAOC,IAAI,CAACL,iBAAiB,SAC7Bf,0BAA0BW,aAC1BU,QAAQ,CAAC;IAEX,OAAO;QACLf;QACAgB,SAAS;YACP,gBAAgB;YAChB,4BAA4BV;YAC5B,uBAAuBL;YACvB,sBAAsBC;YACtB,0BAA0BU;YAC1B,0BAA0BL;QAC5B;IACF;AACF,EAAC"}
@@ -1,5 +1,6 @@
1
- import { createPublicKey, verify } from 'node:crypto';
1
+ import { verify } from 'node:crypto';
2
2
  import { sha256Hex } from '../sync/index.js';
3
+ import { getEd25519PublicKeyInput } from './ed25519Keys.js';
3
4
  export const verifyBodySha256 = ({ body, expectedHash })=>{
4
5
  const computedHash = sha256Hex(body);
5
6
  if (!/^[a-f0-9]{64}$/i.test(expectedHash)) {
@@ -33,19 +34,9 @@ export const validateTimestampSkew = ({ maxSkewSeconds, now = new Date(), timest
33
34
  ok: true
34
35
  };
35
36
  };
36
- const getPublicKeyInput = (publicKey)=>{
37
- if (publicKey.includes('BEGIN PUBLIC KEY')) {
38
- return publicKey;
39
- }
40
- return createPublicKey({
41
- type: 'spki',
42
- format: 'der',
43
- key: Buffer.from(publicKey, 'base64')
44
- });
45
- };
46
37
  export const verifyEd25519Signature = ({ canonicalString, publicKey, signature })=>{
47
38
  try {
48
- return verify(null, Buffer.from(canonicalString, 'utf8'), getPublicKeyInput(publicKey), Buffer.from(signature, 'base64'));
39
+ return verify(null, Buffer.from(canonicalString, 'utf8'), getEd25519PublicKeyInput(publicKey), Buffer.from(signature, 'base64'));
49
40
  } catch {
50
41
  return false;
51
42
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/security/verify.ts"],"sourcesContent":["import { createPublicKey, verify } from 'node:crypto'\n\nimport { sha256Hex } from '../sync/index.js'\n\nexport type VerifyBodyHashResult =\n | {\n computedHash: string\n ok: false\n }\n | {\n computedHash: string\n ok: true\n }\n\nexport type ValidateTimestampResult =\n | {\n date: Date\n ok: true\n }\n | {\n message: string\n ok: false\n }\n\nexport const verifyBodySha256 = ({\n body,\n expectedHash,\n}: {\n body: string\n expectedHash: string\n}): VerifyBodyHashResult => {\n const computedHash = sha256Hex(body)\n\n if (!/^[a-f0-9]{64}$/i.test(expectedHash)) {\n return {\n computedHash,\n ok: false,\n }\n }\n\n return {\n computedHash,\n ok: computedHash === expectedHash.toLowerCase(),\n }\n}\n\nexport const validateTimestampSkew = ({\n maxSkewSeconds,\n now = new Date(),\n timestamp,\n}: {\n maxSkewSeconds: number\n now?: Date\n timestamp: string\n}): ValidateTimestampResult => {\n const date = new Date(timestamp)\n\n if (Number.isNaN(date.getTime())) {\n return {\n message: 'Sync request timestamp is invalid.',\n ok: false,\n }\n }\n\n const skewMs = Math.abs(now.getTime() - date.getTime())\n\n if (skewMs > maxSkewSeconds * 1000) {\n return {\n message: 'Sync request timestamp is outside the allowed skew.',\n ok: false,\n }\n }\n\n return {\n date,\n ok: true,\n }\n}\n\nconst getPublicKeyInput = (publicKey: string) => {\n if (publicKey.includes('BEGIN PUBLIC KEY')) {\n return publicKey\n }\n\n return createPublicKey({\n type: 'spki',\n format: 'der',\n key: Buffer.from(publicKey, 'base64'),\n })\n}\n\nexport const verifyEd25519Signature = ({\n canonicalString,\n publicKey,\n signature,\n}: {\n canonicalString: string\n publicKey: string\n signature: string\n}): boolean => {\n try {\n return verify(\n null,\n Buffer.from(canonicalString, 'utf8'),\n getPublicKeyInput(publicKey),\n Buffer.from(signature, 'base64'),\n )\n } catch {\n return false\n }\n}\n\n"],"names":["createPublicKey","verify","sha256Hex","verifyBodySha256","body","expectedHash","computedHash","test","ok","toLowerCase","validateTimestampSkew","maxSkewSeconds","now","Date","timestamp","date","Number","isNaN","getTime","message","skewMs","Math","abs","getPublicKeyInput","publicKey","includes","type","format","key","Buffer","from","verifyEd25519Signature","canonicalString","signature"],"mappings":"AAAA,SAASA,eAAe,EAAEC,MAAM,QAAQ,cAAa;AAErD,SAASC,SAAS,QAAQ,mBAAkB;AAsB5C,OAAO,MAAMC,mBAAmB,CAAC,EAC/BC,IAAI,EACJC,YAAY,EAIb;IACC,MAAMC,eAAeJ,UAAUE;IAE/B,IAAI,CAAC,kBAAkBG,IAAI,CAACF,eAAe;QACzC,OAAO;YACLC;YACAE,IAAI;QACN;IACF;IAEA,OAAO;QACLF;QACAE,IAAIF,iBAAiBD,aAAaI,WAAW;IAC/C;AACF,EAAC;AAED,OAAO,MAAMC,wBAAwB,CAAC,EACpCC,cAAc,EACdC,MAAM,IAAIC,MAAM,EAChBC,SAAS,EAKV;IACC,MAAMC,OAAO,IAAIF,KAAKC;IAEtB,IAAIE,OAAOC,KAAK,CAACF,KAAKG,OAAO,KAAK;QAChC,OAAO;YACLC,SAAS;YACTX,IAAI;QACN;IACF;IAEA,MAAMY,SAASC,KAAKC,GAAG,CAACV,IAAIM,OAAO,KAAKH,KAAKG,OAAO;IAEpD,IAAIE,SAAST,iBAAiB,MAAM;QAClC,OAAO;YACLQ,SAAS;YACTX,IAAI;QACN;IACF;IAEA,OAAO;QACLO;QACAP,IAAI;IACN;AACF,EAAC;AAED,MAAMe,oBAAoB,CAACC;IACzB,IAAIA,UAAUC,QAAQ,CAAC,qBAAqB;QAC1C,OAAOD;IACT;IAEA,OAAOxB,gBAAgB;QACrB0B,MAAM;QACNC,QAAQ;QACRC,KAAKC,OAAOC,IAAI,CAACN,WAAW;IAC9B;AACF;AAEA,OAAO,MAAMO,yBAAyB,CAAC,EACrCC,eAAe,EACfR,SAAS,EACTS,SAAS,EAKV;IACC,IAAI;QACF,OAAOhC,OACL,MACA4B,OAAOC,IAAI,CAACE,iBAAiB,SAC7BT,kBAAkBC,YAClBK,OAAOC,IAAI,CAACG,WAAW;IAE3B,EAAE,OAAM;QACN,OAAO;IACT;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/security/verify.ts"],"sourcesContent":["import { verify } from 'node:crypto'\n\nimport { sha256Hex } from '../sync/index.js'\nimport { getEd25519PublicKeyInput } from './ed25519Keys.js'\n\nexport type VerifyBodyHashResult =\n | {\n computedHash: string\n ok: false\n }\n | {\n computedHash: string\n ok: true\n }\n\nexport type ValidateTimestampResult =\n | {\n date: Date\n ok: true\n }\n | {\n message: string\n ok: false\n }\n\nexport const verifyBodySha256 = ({\n body,\n expectedHash,\n}: {\n body: string\n expectedHash: string\n}): VerifyBodyHashResult => {\n const computedHash = sha256Hex(body)\n\n if (!/^[a-f0-9]{64}$/i.test(expectedHash)) {\n return {\n computedHash,\n ok: false,\n }\n }\n\n return {\n computedHash,\n ok: computedHash === expectedHash.toLowerCase(),\n }\n}\n\nexport const validateTimestampSkew = ({\n maxSkewSeconds,\n now = new Date(),\n timestamp,\n}: {\n maxSkewSeconds: number\n now?: Date\n timestamp: string\n}): ValidateTimestampResult => {\n const date = new Date(timestamp)\n\n if (Number.isNaN(date.getTime())) {\n return {\n message: 'Sync request timestamp is invalid.',\n ok: false,\n }\n }\n\n const skewMs = Math.abs(now.getTime() - date.getTime())\n\n if (skewMs > maxSkewSeconds * 1000) {\n return {\n message: 'Sync request timestamp is outside the allowed skew.',\n ok: false,\n }\n }\n\n return {\n date,\n ok: true,\n }\n}\n\nexport const verifyEd25519Signature = ({\n canonicalString,\n publicKey,\n signature,\n}: {\n canonicalString: string\n publicKey: string\n signature: string\n}): boolean => {\n try {\n return verify(\n null,\n Buffer.from(canonicalString, 'utf8'),\n getEd25519PublicKeyInput(publicKey),\n Buffer.from(signature, 'base64'),\n )\n } catch {\n return false\n }\n}\n"],"names":["verify","sha256Hex","getEd25519PublicKeyInput","verifyBodySha256","body","expectedHash","computedHash","test","ok","toLowerCase","validateTimestampSkew","maxSkewSeconds","now","Date","timestamp","date","Number","isNaN","getTime","message","skewMs","Math","abs","verifyEd25519Signature","canonicalString","publicKey","signature","Buffer","from"],"mappings":"AAAA,SAASA,MAAM,QAAQ,cAAa;AAEpC,SAASC,SAAS,QAAQ,mBAAkB;AAC5C,SAASC,wBAAwB,QAAQ,mBAAkB;AAsB3D,OAAO,MAAMC,mBAAmB,CAAC,EAC/BC,IAAI,EACJC,YAAY,EAIb;IACC,MAAMC,eAAeL,UAAUG;IAE/B,IAAI,CAAC,kBAAkBG,IAAI,CAACF,eAAe;QACzC,OAAO;YACLC;YACAE,IAAI;QACN;IACF;IAEA,OAAO;QACLF;QACAE,IAAIF,iBAAiBD,aAAaI,WAAW;IAC/C;AACF,EAAC;AAED,OAAO,MAAMC,wBAAwB,CAAC,EACpCC,cAAc,EACdC,MAAM,IAAIC,MAAM,EAChBC,SAAS,EAKV;IACC,MAAMC,OAAO,IAAIF,KAAKC;IAEtB,IAAIE,OAAOC,KAAK,CAACF,KAAKG,OAAO,KAAK;QAChC,OAAO;YACLC,SAAS;YACTX,IAAI;QACN;IACF;IAEA,MAAMY,SAASC,KAAKC,GAAG,CAACV,IAAIM,OAAO,KAAKH,KAAKG,OAAO;IAEpD,IAAIE,SAAST,iBAAiB,MAAM;QAClC,OAAO;YACLQ,SAAS;YACTX,IAAI;QACN;IACF;IAEA,OAAO;QACLO;QACAP,IAAI;IACN;AACF,EAAC;AAED,OAAO,MAAMe,yBAAyB,CAAC,EACrCC,eAAe,EACfC,SAAS,EACTC,SAAS,EAKV;IACC,IAAI;QACF,OAAO1B,OACL,MACA2B,OAAOC,IAAI,CAACJ,iBAAiB,SAC7BtB,yBAAyBuB,YACzBE,OAAOC,IAAI,CAACF,WAAW;IAE3B,EAAE,OAAM;QACN,OAAO;IACT;AACF,EAAC"}
@@ -1,6 +1,6 @@
1
1
  # Admin Manager
2
2
 
3
- The Docs Set Admin Manager is a read-only overview on the docs set edit view.
3
+ The Docs Set Admin Manager is an overview on the docs set edit view.
4
4
 
5
5
  It shows:
6
6
 
@@ -13,6 +13,7 @@ It shows:
13
13
  - docs with overrides
14
14
  - generated docs grouped by source path
15
15
  - links to generated docs records
16
+ - a publish action for draft generated docs when draft publishing is enabled
16
17
 
17
18
  Per-doc overrides live on generated docs records:
18
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valkyrianlabs/payload-markdown-docs",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Git-backed Markdown documentation sync for Payload CMS, powered by payload-markdown.",
5
5
  "bin": {
6
6
  "payload-markdown-docs": "./dist/cli/index.js"
@@ -45,6 +45,8 @@
45
45
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths --ignore \"**/*.spec.ts\"",
46
46
  "build:types": "tsc -p tsconfig.build.json --outDir dist --rootDir ./src",
47
47
  "clean": "rimraf {dist,*.tsbuildinfo}",
48
+ "cli": "node --import @swc-node/register/esm-register ./src/cli/index.ts",
49
+ "cli:dist": "node ./dist/cli/index.js",
48
50
  "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json,md}\" dist/",
49
51
  "dev": "next dev dev --turbo",
50
52
  "dev:docs:keygen": "pnpm dev:payload run ./dev/scripts/create-docs-keypair.ts",