@micelio/strapi-plugin-core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +5 -3
  2. package/dist/admin/MicelioLayout-Bmu5xCw9.js +1023 -0
  3. package/dist/admin/MicelioLayout-DMMsKnMo.mjs +1021 -0
  4. package/dist/admin/PreviewHeaderAction-BaduMvYy.mjs +61 -0
  5. package/dist/admin/PreviewHeaderAction-CGHycv4I.js +61 -0
  6. package/dist/admin/{en-D-T8z3qh.js → en-BDVuq0fN.js} +37 -1
  7. package/dist/admin/{en-C2O-S2ym.mjs → en-Bahc6y6N.mjs} +37 -1
  8. package/dist/admin/{es-RV0vkOHh.js → es-DStBGkm7.js} +37 -1
  9. package/dist/admin/{es-CQ2tv9N8.mjs → es-nUHBIoZG.mjs} +37 -1
  10. package/dist/admin/{index-D3TGbaQ7.js → index-D_Dbvo2F.js} +8 -2
  11. package/dist/admin/{index-CiSujTES.mjs → index-Dwr15ZsB.mjs} +8 -2
  12. package/dist/admin/index.js +1 -1
  13. package/dist/admin/index.mjs +1 -1
  14. package/dist/admin/permissions-BAI0qDr9.js +24 -0
  15. package/dist/admin/permissions-CYirM30e.mjs +25 -0
  16. package/dist/admin/src/components/EyeIcon.d.ts +8 -0
  17. package/dist/admin/src/components/PreviewFrame.d.ts +47 -0
  18. package/dist/admin/src/components/PreviewHeaderAction.d.ts +28 -0
  19. package/dist/admin/src/hooks/useEntry.d.ts +31 -0
  20. package/dist/admin/src/hooks/usePreviewSource.d.ts +31 -0
  21. package/dist/admin/src/pages/MicelioLayout.d.ts +2 -1
  22. package/dist/admin/src/pages/PreviewPage.d.ts +2 -0
  23. package/dist/admin/src/permissions.d.ts +5 -0
  24. package/dist/admin/src/translations/en.json.d.ts +37 -1
  25. package/dist/admin/src/utils/fetchError.d.ts +2 -0
  26. package/dist/admin/src/utils/previewAction.d.ts +22 -0
  27. package/dist/admin/src/utils/previewPane.d.ts +59 -0
  28. package/dist/server/index.js +796 -105
  29. package/dist/server/index.mjs +796 -105
  30. package/dist/server/src/constants/preview.d.ts +2 -0
  31. package/dist/server/src/constants/taxonomy.d.ts +13 -0
  32. package/dist/server/src/content-types/category/schema.json.d.ts +78 -0
  33. package/dist/server/src/content-types/index.d.ts +125 -2
  34. package/dist/server/src/content-types/preview-credential/schema.json.d.ts +7 -6
  35. package/dist/server/src/content-types/tag/schema.json.d.ts +46 -0
  36. package/dist/server/src/controllers/index.d.ts +6 -0
  37. package/dist/server/src/editorial/transitions.d.ts +5 -2
  38. package/dist/server/src/index.d.ts +140 -2
  39. package/dist/server/src/migrations/rename-legacy-uids.d.ts +26 -0
  40. package/dist/server/src/migrations/site-backfill.d.ts +13 -0
  41. package/dist/server/src/preview/credentials.d.ts +7 -5
  42. package/dist/server/src/preview/draft-query.d.ts +62 -0
  43. package/dist/server/src/preview/revocation.d.ts +9 -1
  44. package/dist/server/src/preview/shortcut.d.ts +24 -0
  45. package/dist/server/src/routes/index.d.ts +2 -0
  46. package/dist/server/src/routes/taxonomy.d.ts +12 -0
  47. package/dist/server/src/services/index.d.ts +6 -0
  48. package/dist/server/src/services/preview.d.ts +25 -6
  49. package/dist/server/src/site-scope/documents.d.ts +13 -3
  50. package/dist/server/src/types/hooks.d.ts +5 -4
  51. package/dist/server/src/types/preview.d.ts +35 -10
  52. package/package.json +4 -3
  53. package/dist/admin/MicelioLayout-3sKGPS_0.mjs +0 -443
  54. package/dist/admin/MicelioLayout-DXzRS4l3.js +0 -443
@@ -1,8 +1,16 @@
1
1
  import { Core } from '@strapi/strapi';
2
2
  /**
3
- * Revokes every preview token and grant of an admin user when they log out of the admin.
3
+ * Revokes every preview token and grant of an admin user before their logout answers.
4
4
  *
5
5
  * @remarks
6
+ * Strapi emits `admin.logout` without waiting for its listeners, so a listener can still be
7
+ * revoking when the logout response is sent and a draft request that follows finds the grant. An
8
+ * application middleware, which wraps every request and acts only on the admin's logout route
9
+ * (the route Strapi resolved, not a path, so every spelling the router accepts revokes), awaits
10
+ * `revoke` after the controller has run and before the response leaves, so no grant of the user
11
+ * is served once the logout has answered (ADR-0019, section 3). A failure is logged and does not
12
+ * fail the logout.
13
+ *
6
14
  * Losing the right to read a draft revokes its grants when the next exchange or draft request
7
15
  * checks the user's abilities again (`stillReadable` in the `preview` service).
8
16
  */
@@ -0,0 +1,24 @@
1
+ import { Core } from '@strapi/strapi';
2
+ /** What Strapi hands the `admin.preview` handler: the entry its "Open preview" button was pressed on. */
3
+ interface PreviewHandlerParams {
4
+ documentId?: string;
5
+ locale?: string;
6
+ }
7
+ /**
8
+ * The admin URL of the preview page for an entry, or `undefined` for a type that has no preview.
9
+ *
10
+ * @remarks
11
+ * A collection type has one when it belongs to a site and has a dynamic zone, the same rule
12
+ * the edit view's action follows. `undefined` makes Strapi answer `204`, and it shows no "Open preview" button.
13
+ */
14
+ export declare function previewPageUrl(strapi: Core.Strapi, uid: string, { documentId, locale }: PreviewHandlerParams): string | undefined;
15
+ /**
16
+ * Points Strapi's own "Open preview" button at the plugin's preview page, unless the host has
17
+ * configured `admin.preview` itself.
18
+ *
19
+ * @remarks
20
+ * The host's `config/admin` wins, `enabled: false` included. Strapi reads the handler on each
21
+ * request, so setting it while the plugin registers is early enough.
22
+ */
23
+ export declare function registerPreviewShortcut(strapi: Core.Strapi): void;
24
+ export {};
@@ -2,5 +2,7 @@ declare const _default: {
2
2
  'content-api': () => import('@strapi/types/dist/core').RouterInput;
3
3
  admin: () => import('@strapi/types/dist/core').RouterInput;
4
4
  site: import('@strapi/types/dist/core/core-api/router').Router;
5
+ category: import('@strapi/types/dist/core/core-api/router').Router;
6
+ tag: import('@strapi/types/dist/core/core-api/router').Router;
5
7
  };
6
8
  export default _default;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The content API routes of the category type: `/api/categories` and `/api/categories/:id`.
3
+ *
4
+ * @remarks
5
+ * `prefix: ''` drops the `/micelio` segment a plugin's content-api routes get, so the paths are
6
+ * those `micelio-cms` 0.5 and the frontend have (ADR-0017, amendments of 2026-10-21 and
7
+ * 2026-10-26). A host that has routes of its own on these paths keeps them: host routes register
8
+ * first.
9
+ */
10
+ export declare const categoryRoutes: import('@strapi/types/dist/core/core-api/router').Router;
11
+ /** The content API routes of the tag type: `/api/tags` and `/api/tags/:id`, as {@link categoryRoutes}. */
12
+ export declare const tagRoutes: import('@strapi/types/dist/core/core-api/router').Router;
@@ -2,6 +2,12 @@ declare const _default: {
2
2
  site: ({ strapi, }: {
3
3
  strapi: import('@strapi/types/dist/core').Strapi;
4
4
  }) => Partial<import('@strapi/types/dist/core/core-api/service').Base> & import('@strapi/types/dist/core/core-api/service').Generic & import('@strapi/types/dist/core/core-api/service').Base;
5
+ category: ({ strapi, }: {
6
+ strapi: import('@strapi/types/dist/core').Strapi;
7
+ }) => Partial<import('@strapi/types/dist/core/core-api/service').Base> & import('@strapi/types/dist/core/core-api/service').Generic & import('@strapi/types/dist/core/core-api/service').Base;
8
+ tag: ({ strapi, }: {
9
+ strapi: import('@strapi/types/dist/core').Strapi;
10
+ }) => Partial<import('@strapi/types/dist/core/core-api/service').Base> & import('@strapi/types/dist/core/core-api/service').Generic & import('@strapi/types/dist/core/core-api/service').Base;
5
11
  transitions: ({ strapi }: {
6
12
  strapi: import('@strapi/types/dist/core').Strapi;
7
13
  }) => import('./transitions').TransitionsService;
@@ -1,5 +1,5 @@
1
1
  import { Core } from '@strapi/strapi';
2
- import { PreviewBinding, PreviewExpectation, PreviewGrantAnswer, PreviewTokenAnswer } from '../types/preview';
2
+ import { PreviewBinding, PreviewExpectation, PreviewGrantAnswer, PreviewResolvedGrant, PreviewSystemBinding, PreviewTokenAnswer } from '../types/preview';
3
3
  /** The `preview` service: `strapi.plugin('micelio').service('preview')`, behind the preview routes. */
4
4
  export interface PreviewService {
5
5
  /**
@@ -20,14 +20,33 @@ export interface PreviewService {
20
20
  */
21
21
  exchange(token: string, siteDocumentId: string): Promise<PreviewGrantAnswer | null>;
22
22
  /**
23
- * The binding of a usable grant for a draft of a site, or `null` for every refusal.
23
+ * Makes a system grant for one version of a draft, and returns its value once.
24
24
  *
25
25
  * @remarks
26
- * Checks again that the admin user may read the draft. When they may not, every grant of that
27
- * user and draft is revoked.
26
+ * For plugin code that measures a draft at publish (ADR-0019, amendment of 2026-10-25); no
27
+ * route mints one. The grant lives 60 s, is used once, is bound to the site, type, document,
28
+ * locale and `binding.entryVersion`, and has no admin user. It is stored hashed; expired
29
+ * credentials past their retention are removed on the way. The caller sends it to the
30
+ * frontend's weight route only, never to a browser, and never logs it.
31
+ */
32
+ issueSystemGrant(binding: PreviewSystemBinding): Promise<string>;
33
+ /**
34
+ * What a usable grant names and who it acts for, or `null` for every refusal.
35
+ *
36
+ * @remarks
37
+ * An `admin` grant checks again that the admin user may read the draft; when they may not,
38
+ * every grant of that user and draft is revoked. A `system` grant is claimed with one
39
+ * conditional update, so of two checks at once one finds it spent, and it has no user to ask.
40
+ * Whether the draft is still the version the system grant was minted for is for the caller to
41
+ * check (`entryVersion`).
42
+ */
43
+ resolveGrant(grant: string, expected: PreviewExpectation): Promise<PreviewResolvedGrant | null>;
44
+ /**
45
+ * Revokes every unexpired token and grant of an admin user, or only those of one draft.
46
+ *
47
+ * @remarks
48
+ * System grants have no admin user, so this never touches one: 60 s and a single use bound them.
28
49
  */
29
- resolveGrant(grant: string, expected: PreviewExpectation): Promise<PreviewBinding | null>;
30
- /** Revokes every unexpired token and grant of an admin user, or only those of one draft. */
31
50
  revoke(adminUser: number, entryDocumentId?: string): Promise<void>;
32
51
  }
33
52
  /** The site of a preview has no frontend origin: the token route answers `409` `previewUnavailable`. */
@@ -4,8 +4,18 @@ import { Core, Modules } from '@strapi/strapi';
4
4
  *
5
5
  * @remarks
6
6
  * Outside a request scoped by the `site-scope` route middleware (the admin, boot steps, other
7
- * plugins) it only refuses to create an entry without a site or to remove its site. Inside one,
8
- * it also filters every read by the request's site after Strapi has sanitised the query, gives a
9
- * new entry the request's site, and answers "not found" for a write to another site's entry.
7
+ * plugins) it only refuses to create an entry without a site or to remove its site. An update
8
+ * whose `site` leaves the relation as it is (the Content Manager sends empty `connect` and
9
+ * `disconnect`) does not touch it. An update that adds a locale to an entry gives it the site of
10
+ * the entry's other locales, because Strapi's i18n does not copy a relation, and is refused with
11
+ * "site is required" when those rows name different sites or none and the update names none, or with
12
+ * "site must be the site of the other locales" when it names another one. An update may change the
13
+ * site of an existing locale only when no other locale of the entry stays on another site, and is
14
+ * refused otherwise ("site cannot change while other locales of the document stay on another
15
+ * site"); an entry of a single locale moves freely. Inside a scoped request, it also filters every read by the
16
+ * request's site after Strapi has sanitised the query, gives a new entry the request's site and
17
+ * keeps an updated one there whatever `site` the body names, and answers "not found" for a write
18
+ * to another site's entry, before anything else is read. Publishing or discarding the draft of a
19
+ * locale keeps its site on the row Strapi rebuilds.
10
20
  */
11
21
  export declare function siteScopeDocuments(strapi: Core.Strapi): Modules.Documents.Middleware.Middleware;
@@ -85,12 +85,13 @@ export interface HookPayloads {
85
85
  */
86
86
  afterTransition: TransitionPayload;
87
87
  /**
88
- * Runs after an entry's version goes live: today, after `in_review → published` and
89
- * `scheduled → published`, following `afterTransition`.
88
+ * Runs after an entry's version goes live: today, after `in_review → published`,
89
+ * `scheduled → published` and `published → published` (republish), following `afterTransition`.
90
90
  *
91
91
  * @remarks
92
- * Treat it as "this version is now live", not "first publication": bogd3v/micelio#699 adds a
93
- * transition that puts the edits of a published entry live, and this hook fires for it too. A
92
+ * Treat it as "this version is now live", not "first publication": `published → published`
93
+ * puts the edits of a published entry live (ADR-0017, amendment of 2026-10-25), and this hook
94
+ * fires for it too, with `afterTransition` carrying `published` as both `from` and `to`. A
94
95
  * handler that publishes elsewhere (the fediverse, a static rebuild) must be idempotent per
95
96
  * `documentId` and `locale`.
96
97
  */
@@ -1,9 +1,5 @@
1
- /** What a preview token or grant is bound to (ADR-0019, section 3). */
2
- export interface PreviewBinding {
3
- /** The `id` of the admin user who asked for the token. */
4
- adminUser: number;
5
- /** The origin of the admin page that asked, which the frontend frames itself for. */
6
- adminOrigin: string;
1
+ /** What every preview credential is bound to: one document of one site. */
2
+ export interface PreviewTarget {
7
3
  /** The `documentId` of the site. */
8
4
  siteDocumentId: string;
9
5
  /** The content type UID. */
@@ -12,19 +8,48 @@ export interface PreviewBinding {
12
8
  /** The locale of the draft; `null` for a type that is not localised. */
13
9
  entryLocale: string | null;
14
10
  }
15
- /** Which of the two credentials a record is. */
16
- export type PreviewCredentialKind = 'token' | 'grant';
11
+ /** What a preview token or grant is bound to (ADR-0019, section 3). */
12
+ export interface PreviewBinding extends PreviewTarget {
13
+ /** The `id` of the admin user who asked for the token. */
14
+ adminUser: number;
15
+ /** The origin of the admin page that asked, which the frontend frames itself for. */
16
+ adminOrigin: string;
17
+ }
18
+ /**
19
+ * What a system grant is bound to (ADR-0019, amendment of 2026-10-25): a document and the version
20
+ * of its draft, and no admin user.
21
+ */
22
+ export interface PreviewSystemBinding extends PreviewTarget {
23
+ /** The `updatedAt` of the draft at the moment of minting, as an ISO 8601 date. */
24
+ entryVersion: string;
25
+ }
26
+ /** Which of the credentials a record is: a token, a grant, or the plugin's own system grant. */
27
+ export type PreviewCredentialKind = 'token' | 'grant' | 'system';
28
+ /** Who a grant acts for: an admin user, or the plugin itself. */
29
+ export type PreviewActor = 'admin' | 'system';
17
30
  /** A credential as the store keeps it: never its value, only the hash. */
18
- export interface PreviewCredentialRecord extends PreviewBinding {
31
+ export interface PreviewCredentialRecord extends PreviewTarget {
19
32
  kind: PreviewCredentialKind;
20
33
  /** SHA-256 of the value, in hex. */
21
34
  hash: string;
35
+ /** The admin user a token or grant is bound to; `null` for a system grant. */
36
+ adminUser: number | null;
37
+ /** The admin origin a token or grant is bound to; `null` for a system grant. */
38
+ adminOrigin: string | null;
39
+ /** The `updatedAt` of the draft a system grant was minted for; `null` for the other kinds. */
40
+ entryVersion: string | null;
22
41
  expiresAt: Date;
23
- /** When a token was exchanged; `null` while unused, and always `null` for a grant. */
42
+ /** When a token was exchanged or a system grant was used; `null` while unused, and always `null` for a grant. */
24
43
  usedAt: Date | null;
25
44
  /** When logging out or losing the right to read the document ended it. */
26
45
  revokedAt: Date | null;
27
46
  }
47
+ /** The grant a request presented, once it checked out: what it names and who it acts for. */
48
+ export interface PreviewResolvedGrant extends PreviewTarget {
49
+ actor: PreviewActor;
50
+ /** The version a `system` grant was minted for; `null` for an `admin` grant. */
51
+ entryVersion: string | null;
52
+ }
28
53
  /** What a request presents a credential for. Absent fields are not checked. */
29
54
  export interface PreviewExpectation {
30
55
  siteDocumentId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micelio/strapi-plugin-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Micelio's core as a Strapi 5 plugin: sites, blocks, the editorial workflow, hooks and the host check.",
5
5
  "type": "commonjs",
6
6
  "license": "AGPL-3.0-only",
@@ -14,7 +14,7 @@
14
14
  "micelio"
15
15
  ],
16
16
  "engines": {
17
- "node": ">=22.12"
17
+ "node": ">=22.19.0"
18
18
  },
19
19
  "micelio": "0.6",
20
20
  "strapi": {
@@ -100,6 +100,7 @@
100
100
  "@micelio/tsconfig": "^0.1.0",
101
101
  "@strapi/design-system": "^2.2.4",
102
102
  "@strapi/icons": "^2.2.4",
103
+ "@strapi/plugin-users-permissions": "^5.59.0",
103
104
  "@strapi/sdk-plugin": "^6.1.1",
104
105
  "@strapi/strapi": "^5.59.0",
105
106
  "@strapi/typescript-utils": "^5.59.0",
@@ -110,7 +111,7 @@
110
111
  "better-sqlite3": "12.11.1",
111
112
  "eslint": "^10.12.0",
112
113
  "jiti": "^2.7.0",
113
- "playwright": "^1.63.0",
114
+ "playwright": "1.64.1",
114
115
  "postcss": "^8.5.31",
115
116
  "prettier": "^3.9.10",
116
117
  "react": "^18.3.1",