@qkix/strapi-plugin-blueprint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/_chunks/DiagramPage-F0OgJkQF.mjs +275 -0
- package/dist/_chunks/DiagramPage-LORj1QlF.js +1 -0
- package/dist/admin/index.js +1 -0
- package/dist/admin/index.mjs +43 -0
- package/dist/admin/src/api.d.ts +50 -0
- package/dist/admin/src/components/Canvas.d.ts +46 -0
- package/dist/admin/src/index.d.ts +11 -0
- package/dist/admin/src/pages/DiagramPage.d.ts +3 -0
- package/dist/admin/src/pluginId.d.ts +6 -0
- package/dist/server/index.js +35 -0
- package/dist/server/index.mjs +438 -0
- package/dist/server/src/config/index.d.ts +21 -0
- package/dist/server/src/controllers/diagram.d.ts +27 -0
- package/dist/server/src/controllers/index.d.ts +11 -0
- package/dist/server/src/index.d.ts +54 -0
- package/dist/server/src/policies/index.d.ts +4 -0
- package/dist/server/src/register.d.ts +12 -0
- package/dist/server/src/routes/admin.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +14 -0
- package/dist/server/src/services/graph.d.ts +69 -0
- package/dist/server/src/services/index.d.ts +15 -0
- package/dist/server/src/services/layout.d.ts +42 -0
- package/dist/server/src/services/svg.d.ts +23 -0
- package/dist/server/src/types.d.ts +83 -0
- package/dist/server/src/uids.d.ts +7 -0
- package/package.json +121 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Core } from '@strapi/strapi';
|
|
2
|
+
import type { Edge, Field, Graph, GraphOptions } from '../types';
|
|
3
|
+
export declare const DEFAULTS: GraphOptions;
|
|
4
|
+
export interface RawAttribute {
|
|
5
|
+
type: string;
|
|
6
|
+
relation?: string;
|
|
7
|
+
target?: string;
|
|
8
|
+
component?: string;
|
|
9
|
+
components?: string[];
|
|
10
|
+
repeatable?: boolean;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** Present on the *non-owning* side of a two-sided relation. */
|
|
13
|
+
mappedBy?: string;
|
|
14
|
+
inversedBy?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RawModel {
|
|
17
|
+
uid: string;
|
|
18
|
+
kind?: string;
|
|
19
|
+
modelType?: string;
|
|
20
|
+
category?: string;
|
|
21
|
+
info?: {
|
|
22
|
+
displayName?: string;
|
|
23
|
+
singularName?: string;
|
|
24
|
+
};
|
|
25
|
+
attributes?: Record<string, RawAttribute>;
|
|
26
|
+
pluginOptions?: {
|
|
27
|
+
'content-type-builder'?: {
|
|
28
|
+
visible?: boolean;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
declare const graph: ({ strapi }: {
|
|
33
|
+
strapi: Core.Strapi;
|
|
34
|
+
}) => {
|
|
35
|
+
/**
|
|
36
|
+
* The whole schema as a graph.
|
|
37
|
+
*
|
|
38
|
+
* Reads Strapi's own loaded models rather than the files on disk, so a
|
|
39
|
+
* content type a plugin adds at runtime is on the diagram too, and nothing
|
|
40
|
+
* has to be parsed.
|
|
41
|
+
*/
|
|
42
|
+
build(options?: Partial<GraphOptions>): Graph;
|
|
43
|
+
fieldsOf(model: RawModel, opts: GraphOptions): Field[];
|
|
44
|
+
/**
|
|
45
|
+
* Edges, one per reason two boxes are joined.
|
|
46
|
+
*
|
|
47
|
+
* Both ends of a two-sided relation describe the same line. Strapi marks
|
|
48
|
+
* the non-owning end with `mappedBy`, so skipping those leaves exactly one
|
|
49
|
+
* edge — deduplicating by node pair instead would silently merge two
|
|
50
|
+
* genuinely different relations between the same two types.
|
|
51
|
+
*
|
|
52
|
+
* Edges to something not on the diagram are dropped rather than drawn into
|
|
53
|
+
* nothing: with `includeComponents` off, every component attribute would
|
|
54
|
+
* otherwise become a line to a box that is not there.
|
|
55
|
+
*
|
|
56
|
+
* A field that is not drawn does not get a line either, and that is not a
|
|
57
|
+
* detail. i18n adds a `localizations` relation **to every content type,
|
|
58
|
+
* pointing at itself**, so skipping it in the field list but not here put a
|
|
59
|
+
* self-loop on every single box on the diagram.
|
|
60
|
+
*/
|
|
61
|
+
edgesOf(present: Map<string, RawModel>, opts: GraphOptions): Edge[];
|
|
62
|
+
/** Everything that could be excluded, for the settings UI. */
|
|
63
|
+
catalogue(): Array<{
|
|
64
|
+
uid: string;
|
|
65
|
+
label: string;
|
|
66
|
+
kind: string;
|
|
67
|
+
}>;
|
|
68
|
+
};
|
|
69
|
+
export default graph;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
graph: ({ strapi }: {
|
|
3
|
+
strapi: import("@strapi/types/dist/core").Strapi;
|
|
4
|
+
}) => {
|
|
5
|
+
build(options?: Partial<import("../types").GraphOptions>): import("../types").Graph;
|
|
6
|
+
fieldsOf(model: import("./graph").RawModel, opts: import("../types").GraphOptions): import("../types").Field[];
|
|
7
|
+
edgesOf(present: Map<string, import("./graph").RawModel>, opts: import("../types").GraphOptions): import("../types").Edge[];
|
|
8
|
+
catalogue(): {
|
|
9
|
+
uid: string;
|
|
10
|
+
label: string;
|
|
11
|
+
kind: string;
|
|
12
|
+
}[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Graph, Layout } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Box metrics. Kept here rather than in the renderer because the layout has to
|
|
4
|
+
* agree with the drawing about how big a box is, and two copies of that drift.
|
|
5
|
+
*/
|
|
6
|
+
export declare const METRICS: {
|
|
7
|
+
headerHeight: number;
|
|
8
|
+
rowHeight: number;
|
|
9
|
+
paddingX: number;
|
|
10
|
+
/** Rough width of one character at the font size the renderer uses. */
|
|
11
|
+
charWidth: number;
|
|
12
|
+
minWidth: number;
|
|
13
|
+
maxWidth: number;
|
|
14
|
+
/** Room for the type, drawn right-aligned on the same row as the name. */
|
|
15
|
+
typeGutter: number;
|
|
16
|
+
};
|
|
17
|
+
export type Direction = 'TB' | 'LR';
|
|
18
|
+
/**
|
|
19
|
+
* How many fields a box shows before it gives up and counts the rest.
|
|
20
|
+
*
|
|
21
|
+
* Strapi's own `File` has fifteen, and a diagram where one box is four times
|
|
22
|
+
* the height of its neighbours reads as a wall rather than a shape. The whole
|
|
23
|
+
* list is still a click away in the Content-Type Builder; the picture is for
|
|
24
|
+
* seeing how things connect.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_ROWS = 12;
|
|
27
|
+
/** What a box actually draws, and how much it is leaving out. */
|
|
28
|
+
export declare const rowsOf: (node: Graph['nodes'][number]) => {
|
|
29
|
+
fields: import("../types").Field[];
|
|
30
|
+
hidden: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Positions every box and routes every line.
|
|
34
|
+
*
|
|
35
|
+
* dagre does the hard part. It runs on the server, which is the whole point:
|
|
36
|
+
* the incumbents in this category all layout in the browser with a React graph
|
|
37
|
+
* library, which is what tied them to React and styled-components versions and
|
|
38
|
+
* is why both of them broke. A layered-graph algorithm has no opinion about the
|
|
39
|
+
* UI framework, and the SVG it feeds can be produced with no browser at all.
|
|
40
|
+
*/
|
|
41
|
+
export declare const layout: (graph: Graph, direction?: Direction) => Layout;
|
|
42
|
+
export default layout;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Layout } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Every string that reaches the output goes through here.
|
|
4
|
+
*
|
|
5
|
+
* Content-type names come from developers rather than from the public, so this
|
|
6
|
+
* is not the front line of anything — but the SVG is injected into the admin
|
|
7
|
+
* panel's DOM and also handed to people to commit into a repository, and a
|
|
8
|
+
* display name containing `</text><script>` must not become script in either
|
|
9
|
+
* place. Escaping at the boundary is the only version of this that stays true
|
|
10
|
+
* as the renderer grows.
|
|
11
|
+
*/
|
|
12
|
+
export declare const escapeXml: (value: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* The diagram, as a standalone SVG document.
|
|
15
|
+
*
|
|
16
|
+
* Standalone on purpose — styles and marker inline, no external references — so
|
|
17
|
+
* the same bytes work in the admin panel, in a README, and opened straight from
|
|
18
|
+
* a downloads folder. An `<img>` tag will not fetch anything an SVG refers to,
|
|
19
|
+
* which is what makes the usual "export" of this category a raster screenshot
|
|
20
|
+
* rather than a drawing you can scale.
|
|
21
|
+
*/
|
|
22
|
+
export declare const render: (placed: Layout) => string;
|
|
23
|
+
export default render;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** What a node on the diagram is. The three kinds are drawn differently. */
|
|
2
|
+
export type NodeKind = 'collectionType' | 'singleType' | 'component';
|
|
3
|
+
export interface Field {
|
|
4
|
+
name: string;
|
|
5
|
+
/** The Strapi attribute type, shown verbatim: `string`, `media`, `relation`… */
|
|
6
|
+
type: string;
|
|
7
|
+
/** `oneToMany`, `manyToOne`… only on relations, and only when Strapi says so. */
|
|
8
|
+
relation?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface Node {
|
|
12
|
+
/** The content-type or component uid — unique, and what edges point at. */
|
|
13
|
+
uid: string;
|
|
14
|
+
kind: NodeKind;
|
|
15
|
+
/** `Article`, `Shared / Seo` — what the admin panel calls it. */
|
|
16
|
+
label: string;
|
|
17
|
+
/** `api`, `plugin::users-permissions`, or the component category. */
|
|
18
|
+
group: string;
|
|
19
|
+
fields: Field[];
|
|
20
|
+
/** True for anything Strapi or a plugin owns, rather than the user's own API. */
|
|
21
|
+
foreign: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether this box has a page in the Content-Type Builder to open.
|
|
24
|
+
*
|
|
25
|
+
* A plugin's internal tables — Greenlight's transition log, upload's folders,
|
|
26
|
+
* every `admin::` type — are real content types and belong on the diagram, but
|
|
27
|
+
* there is nowhere to send someone who clicks one.
|
|
28
|
+
*/
|
|
29
|
+
navigable: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Why two nodes are joined.
|
|
33
|
+
*
|
|
34
|
+
* `relation` is the classic ERD edge. The other two are the reason this plugin
|
|
35
|
+
* exists: every diagram in this category draws relations only, so a schema built
|
|
36
|
+
* out of components — which is most of them — shows up as a handful of
|
|
37
|
+
* disconnected boxes and tells you nothing.
|
|
38
|
+
*/
|
|
39
|
+
export type EdgeKind = 'relation' | 'component' | 'dynamiczone';
|
|
40
|
+
export interface Edge {
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
kind: EdgeKind;
|
|
44
|
+
/** The attribute on `from` that causes the edge. */
|
|
45
|
+
field: string;
|
|
46
|
+
/** `oneToMany` and friends, for relations. */
|
|
47
|
+
cardinality?: string;
|
|
48
|
+
/** A repeatable component, or a dynamic zone: many of the target, not one. */
|
|
49
|
+
many?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface Graph {
|
|
52
|
+
nodes: Node[];
|
|
53
|
+
edges: Edge[];
|
|
54
|
+
}
|
|
55
|
+
/** What the caller asked to see. Everything defaults to the useful answer. */
|
|
56
|
+
export interface GraphOptions {
|
|
57
|
+
/** Content types owned by Strapi or by plugins — noise for most people. */
|
|
58
|
+
includeForeign: boolean;
|
|
59
|
+
/** Components and dynamic zones, and the edges into them. */
|
|
60
|
+
includeComponents: boolean;
|
|
61
|
+
/** `createdAt`, `updatedAt`, `publishedAt`, `locale` and friends. */
|
|
62
|
+
includeDefaultFields: boolean;
|
|
63
|
+
/** Uids to leave out entirely, whatever else is on. */
|
|
64
|
+
exclude: string[];
|
|
65
|
+
}
|
|
66
|
+
export interface Placed extends Node {
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
width: number;
|
|
70
|
+
height: number;
|
|
71
|
+
}
|
|
72
|
+
export interface RoutedEdge extends Edge {
|
|
73
|
+
points: Array<{
|
|
74
|
+
x: number;
|
|
75
|
+
y: number;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
export interface Layout {
|
|
79
|
+
nodes: Placed[];
|
|
80
|
+
edges: RoutedEdge[];
|
|
81
|
+
width: number;
|
|
82
|
+
height: number;
|
|
83
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qkix/strapi-plugin-blueprint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A diagram of your Strapi v5 content types, components and dynamic zones — rendered as real SVG",
|
|
5
|
+
"strapi": {
|
|
6
|
+
"kind": "plugin",
|
|
7
|
+
"name": "blueprint",
|
|
8
|
+
"displayName": "Blueprint",
|
|
9
|
+
"description": "A diagram of your Strapi v5 content types, components and dynamic zones — rendered as real SVG"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"strapi",
|
|
13
|
+
"strapi-plugin",
|
|
14
|
+
"strapi-v5",
|
|
15
|
+
"erd",
|
|
16
|
+
"diagram",
|
|
17
|
+
"schema",
|
|
18
|
+
"content-types",
|
|
19
|
+
"visualisation",
|
|
20
|
+
"documentation",
|
|
21
|
+
"svg",
|
|
22
|
+
"cms",
|
|
23
|
+
"headless-cms"
|
|
24
|
+
],
|
|
25
|
+
"type": "commonjs",
|
|
26
|
+
"exports": {
|
|
27
|
+
"./package.json": "./package.json",
|
|
28
|
+
"./strapi-admin": {
|
|
29
|
+
"types": "./dist/admin/src/index.d.ts",
|
|
30
|
+
"source": "./admin/src/index.ts",
|
|
31
|
+
"import": "./dist/admin/index.mjs",
|
|
32
|
+
"require": "./dist/admin/index.js",
|
|
33
|
+
"default": "./dist/admin/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./strapi-server": {
|
|
36
|
+
"types": "./dist/server/src/index.d.ts",
|
|
37
|
+
"source": "./server/src/index.ts",
|
|
38
|
+
"import": "./dist/server/index.mjs",
|
|
39
|
+
"require": "./dist/server/index.js",
|
|
40
|
+
"default": "./dist/server/index.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@dagrejs/dagre": "^1.1.4"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@strapi/sdk-plugin": "^5.3.2",
|
|
51
|
+
"@strapi/strapi": "^5.12.5",
|
|
52
|
+
"@strapi/typescript-utils": "^5.12.5",
|
|
53
|
+
"@types/react": "^18.3.8",
|
|
54
|
+
"@types/react-dom": "^18.3.0",
|
|
55
|
+
"eslint": "^10.8.1",
|
|
56
|
+
"prettier": "^3.5.3",
|
|
57
|
+
"react": "^18.3.1",
|
|
58
|
+
"react-dom": "^18.3.1",
|
|
59
|
+
"react-router-dom": "^6.30.0",
|
|
60
|
+
"styled-components": "^6.1.17",
|
|
61
|
+
"typescript": "^5.8.3",
|
|
62
|
+
"vitest": "^3.2.4",
|
|
63
|
+
"@qkix/eslint-config": "0.0.0",
|
|
64
|
+
"@qkix/strapi-test-harness": "0.0.0",
|
|
65
|
+
"@qkix/tsconfig": "0.0.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@strapi/admin": "^5.0.0",
|
|
69
|
+
"@strapi/design-system": "^2.0.0",
|
|
70
|
+
"@strapi/icons": "^2.0.0",
|
|
71
|
+
"@strapi/strapi": "^5.0.0",
|
|
72
|
+
"react": "^17.0.0 || ^18.0.0",
|
|
73
|
+
"react-dom": "^17.0.0 || ^18.0.0",
|
|
74
|
+
"react-intl": "^6.0.0 || ^7.0.0",
|
|
75
|
+
"react-router-dom": "^6.0.0",
|
|
76
|
+
"styled-components": "^6.0.0"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=20.0.0 <=24.x.x",
|
|
80
|
+
"npm": ">=6.0.0"
|
|
81
|
+
},
|
|
82
|
+
"license": "MIT",
|
|
83
|
+
"author": "qkix",
|
|
84
|
+
"repository": {
|
|
85
|
+
"type": "git",
|
|
86
|
+
"url": "git+https://github.com/qkix/strapi-plugins.git",
|
|
87
|
+
"directory": "packages/strapi-plugin-blueprint"
|
|
88
|
+
},
|
|
89
|
+
"bugs": {
|
|
90
|
+
"url": "https://github.com/qkix/strapi-plugins/issues"
|
|
91
|
+
},
|
|
92
|
+
"homepage": "https://github.com/qkix/strapi-plugins#readme",
|
|
93
|
+
"nx": {
|
|
94
|
+
"tags": [
|
|
95
|
+
"scope:plugin"
|
|
96
|
+
],
|
|
97
|
+
"targets": {
|
|
98
|
+
"test": {
|
|
99
|
+
"dependsOn": [
|
|
100
|
+
"build",
|
|
101
|
+
"^build"
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"publishConfig": {
|
|
107
|
+
"access": "public"
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"build": "rm -rf dist && strapi-plugin build --minify",
|
|
111
|
+
"clean": "rm -rf dist",
|
|
112
|
+
"lint": "eslint . && prettier --check .",
|
|
113
|
+
"format": "prettier --write .",
|
|
114
|
+
"test": "vitest run",
|
|
115
|
+
"test:watch": "vitest",
|
|
116
|
+
"verify": "strapi-plugin verify",
|
|
117
|
+
"watch": "strapi-plugin watch",
|
|
118
|
+
"watch:link": "strapi-plugin watch:link",
|
|
119
|
+
"typecheck": "tsc -p admin/tsconfig.json --noEmit && tsc -p server/tsconfig.json --noEmit"
|
|
120
|
+
}
|
|
121
|
+
}
|