@contember/cli 1.1.0-alpha.8 → 1.1.0-beta.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/dist/src/commands/migrations/MigrationAmendCommand.js +2 -2
- package/dist/src/commands/migrations/MigrationAmendCommand.js.map +1 -1
- package/dist/src/commands/migrations/MigrationDiffCommand.js +2 -2
- package/dist/src/commands/migrations/MigrationDiffCommand.js.map +1 -1
- package/dist/src/commands/project/ProjectGenerateDocumentation.d.ts +13 -0
- package/dist/src/commands/project/ProjectGenerateDocumentation.d.ts.map +1 -0
- package/dist/src/commands/project/ProjectGenerateDocumentation.js +39 -0
- package/dist/src/commands/project/ProjectGenerateDocumentation.js.map +1 -0
- package/dist/src/commands/project/ProjectPrintSchemaCommand.d.ts +1 -1
- package/dist/src/commands/project/ProjectPrintSchemaCommand.d.ts.map +1 -1
- package/dist/src/commands/project/ProjectPrintSchemaCommand.js +13 -6
- package/dist/src/commands/project/ProjectPrintSchemaCommand.js.map +1 -1
- package/dist/src/commands/project/ProjectValidateCommand.js +2 -2
- package/dist/src/commands/project/ProjectValidateCommand.js.map +1 -1
- package/dist/src/commands/project/index.d.ts +1 -0
- package/dist/src/commands/project/index.d.ts.map +1 -1
- package/dist/src/commands/project/index.js +1 -0
- package/dist/src/commands/project/index.js.map +1 -1
- package/dist/src/run.js +1 -0
- package/dist/src/run.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/utils/project/components/EntityInfo.d.ts +11 -0
- package/dist/src/utils/project/components/EntityInfo.d.ts.map +1 -0
- package/dist/src/utils/project/components/EntityInfo.js +54 -0
- package/dist/src/utils/project/components/EntityInfo.js.map +1 -0
- package/dist/src/utils/project/components/EntityLink.d.ts +5 -0
- package/dist/src/utils/project/components/EntityLink.d.ts.map +1 -0
- package/dist/src/utils/project/components/EntityLink.js +8 -0
- package/dist/src/utils/project/components/EntityLink.js.map +1 -0
- package/dist/src/utils/project/components/FieldLink.d.ts +7 -0
- package/dist/src/utils/project/components/FieldLink.d.ts.map +1 -0
- package/dist/src/utils/project/components/FieldLink.js +13 -0
- package/dist/src/utils/project/components/FieldLink.js.map +1 -0
- package/dist/src/utils/project/components/FieldPermissions.d.ts +7 -0
- package/dist/src/utils/project/components/FieldPermissions.d.ts.map +1 -0
- package/dist/src/utils/project/components/FieldPermissions.js +17 -0
- package/dist/src/utils/project/components/FieldPermissions.js.map +1 -0
- package/dist/src/utils/project/components/FieldType.d.ts +6 -0
- package/dist/src/utils/project/components/FieldType.d.ts.map +1 -0
- package/dist/src/utils/project/components/FieldType.js +59 -0
- package/dist/src/utils/project/components/FieldType.js.map +1 -0
- package/dist/src/utils/project/components/OnDelete.d.ts +9 -0
- package/dist/src/utils/project/components/OnDelete.d.ts.map +1 -0
- package/dist/src/utils/project/components/OnDelete.js +70 -0
- package/dist/src/utils/project/components/OnDelete.js.map +1 -0
- package/dist/src/utils/project/components/ProjectInfo.d.ts +7 -0
- package/dist/src/utils/project/components/ProjectInfo.d.ts.map +1 -0
- package/dist/src/utils/project/components/ProjectInfo.js +28 -0
- package/dist/src/utils/project/components/ProjectInfo.js.map +1 -0
- package/dist/src/utils/project/components/SinglePermission.d.ts +7 -0
- package/dist/src/utils/project/components/SinglePermission.d.ts.map +1 -0
- package/dist/src/utils/project/components/SinglePermission.js +15 -0
- package/dist/src/utils/project/components/SinglePermission.js.map +1 -0
- package/dist/src/utils/project/components/utils.d.ts +3 -0
- package/dist/src/utils/project/components/utils.d.ts.map +1 -0
- package/dist/src/utils/project/components/utils.js +8 -0
- package/dist/src/utils/project/components/utils.js.map +1 -0
- package/dist/src/utils/{project.d.ts → project/loadSchema.d.ts} +1 -1
- package/dist/src/utils/project/loadSchema.d.ts.map +1 -0
- package/dist/src/utils/{project.js → project/loadSchema.js} +1 -1
- package/dist/src/utils/project/loadSchema.js.map +1 -0
- package/dist/src/utils/project/projectDescribe.d.ts +3 -0
- package/dist/src/utils/project/projectDescribe.d.ts.map +1 -0
- package/dist/src/utils/project/projectDescribe.js +29 -0
- package/dist/src/utils/project/projectDescribe.js.map +1 -0
- package/dist/src/utils/project/resources/out.css +759 -0
- package/dist/src/utils/schema/collectReferences.d.ts +10 -0
- package/dist/src/utils/schema/collectReferences.d.ts.map +1 -0
- package/dist/src/utils/schema/collectReferences.js +35 -0
- package/dist/src/utils/schema/collectReferences.js.map +1 -0
- package/package.json +17 -11
- package/postcss.config.js +6 -0
- package/src/commands/migrations/MigrationAmendCommand.ts +1 -1
- package/src/commands/migrations/MigrationDiffCommand.ts +1 -1
- package/src/commands/project/ProjectGenerateDocumentation.ts +44 -0
- package/src/commands/project/ProjectPrintSchemaCommand.ts +12 -6
- package/src/commands/project/ProjectValidateCommand.ts +1 -1
- package/src/commands/project/index.ts +1 -0
- package/src/run.ts +2 -0
- package/src/tsconfig.json +5 -1
- package/src/utils/project/components/EntityInfo.tsx +82 -0
- package/src/utils/project/components/EntityLink.tsx +6 -0
- package/src/utils/project/components/FieldLink.tsx +10 -0
- package/src/utils/project/components/FieldPermissions.tsx +14 -0
- package/src/utils/project/components/FieldType.tsx +54 -0
- package/src/utils/project/components/OnDelete.tsx +72 -0
- package/src/utils/project/components/ProjectInfo.tsx +48 -0
- package/src/utils/project/components/SinglePermission.tsx +13 -0
- package/src/utils/project/components/utils.ts +3 -0
- package/src/utils/{project.ts → project/loadSchema.ts} +0 -0
- package/src/utils/project/projectDescribe.tsx +26 -0
- package/src/utils/project/resources/style.css +3 -0
- package/src/utils/schema/collectReferences.ts +40 -0
- package/tailwind.config.js +7 -0
- package/dist/src/utils/project.d.ts.map +0 -1
- package/dist/src/utils/project.js.map +0 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Model } from '@contember/schema';
|
|
2
|
+
export declare type ReferenceMapEntry = {
|
|
3
|
+
owningEntity: Model.Entity;
|
|
4
|
+
owningRelation: Model.OneHasOneOwningRelation | Model.ManyHasOneRelation;
|
|
5
|
+
targetEntity: Model.Entity;
|
|
6
|
+
targetRelation: Model.OneHasOneInverseRelation | Model.OneHasManyRelation | null;
|
|
7
|
+
};
|
|
8
|
+
export declare type ReferencesMap = Record<string, ReferenceMapEntry[]>;
|
|
9
|
+
export declare const collectReferences: (model: Model.Schema) => ReferencesMap;
|
|
10
|
+
//# sourceMappingURL=collectReferences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectReferences.d.ts","sourceRoot":"","sources":["../../../../src/utils/schema/collectReferences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAEzC,oBAAY,iBAAiB,GAAG;IAC/B,YAAY,EAAE,KAAK,CAAC,MAAM,CAAA;IAC1B,cAAc,EAAE,KAAK,CAAC,uBAAuB,GAAG,KAAK,CAAC,kBAAkB,CAAA;IACxE,YAAY,EAAE,KAAK,CAAC,MAAM,CAAA;IAC1B,cAAc,EAAE,KAAK,CAAC,wBAAwB,GAAG,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAA;CAChF,CAAA;AACD,oBAAY,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAA;AAE/D,eAAO,MAAM,iBAAiB,UAAW,MAAM,MAAM,KAAG,aA6BvD,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectReferences = void 0;
|
|
4
|
+
const collectReferences = (model) => {
|
|
5
|
+
const entityReferences = Object.fromEntries(Object.keys(model.entities).map(it => [it, []]));
|
|
6
|
+
for (const entity of Object.values(model.entities)) {
|
|
7
|
+
for (const field of Object.values(entity.fields)) {
|
|
8
|
+
switch (field.type) {
|
|
9
|
+
case 'OneHasOne':
|
|
10
|
+
const targetEntity = model.entities[field.target];
|
|
11
|
+
if (!('ownedBy' in field)) {
|
|
12
|
+
entityReferences[field.target].push({
|
|
13
|
+
owningEntity: entity,
|
|
14
|
+
owningRelation: field,
|
|
15
|
+
targetEntity: targetEntity,
|
|
16
|
+
targetRelation: field.inversedBy ? targetEntity.fields[field.inversedBy] : null,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
break;
|
|
20
|
+
case 'ManyHasOne':
|
|
21
|
+
const targetEntity2 = model.entities[field.target];
|
|
22
|
+
entityReferences[field.target].push({
|
|
23
|
+
owningEntity: entity,
|
|
24
|
+
owningRelation: field,
|
|
25
|
+
targetEntity: targetEntity2,
|
|
26
|
+
targetRelation: field.inversedBy ? targetEntity2.fields[field.inversedBy] : null,
|
|
27
|
+
});
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return entityReferences;
|
|
33
|
+
};
|
|
34
|
+
exports.collectReferences = collectReferences;
|
|
35
|
+
//# sourceMappingURL=collectReferences.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collectReferences.js","sourceRoot":"","sources":["../../../../src/utils/schema/collectReferences.ts"],"names":[],"mappings":";;;AAUO,MAAM,iBAAiB,GAAG,CAAC,KAAmB,EAAiB,EAAE;IACvE,MAAM,gBAAgB,GAAkB,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3G,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;QACnD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE;YACjD,QAAQ,KAAK,CAAC,IAAI,EAAE;gBACnB,KAAK,WAAW;oBACf,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;oBACjD,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC,EAAE;wBAC1B,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;4BACnC,YAAY,EAAE,MAAM;4BACpB,cAAc,EAAE,KAAK;4BACrB,YAAY,EAAE,YAAY;4BAC1B,cAAc,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAmC,CAAC,CAAC,CAAC,IAAI;yBACjH,CAAC,CAAA;qBACF;oBACD,MAAK;gBACN,KAAK,YAAY;oBAChB,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;oBAClD,gBAAgB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;wBACnC,YAAY,EAAE,MAAM;wBACpB,cAAc,EAAE,KAAK;wBACrB,YAAY,EAAE,aAAa;wBAC3B,cAAc,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAA6B,CAAC,CAAC,CAAC,IAAI;qBAC5G,CAAC,CAAA;oBACF,MAAK;aACN;SACD;KACD;IACD,OAAO,gBAAgB,CAAA;AACxB,CAAC,CAAA;AA7BY,QAAA,iBAAiB,qBA6B7B"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contember/cli",
|
|
3
|
-
"version": "1.1.0-
|
|
3
|
+
"version": "1.1.0-beta.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"contember": "./dist/src/run.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
+
"build": "tailwindcss -i ./src/utils/project/resources/style.css -o ./dist/src/utils/project/resources/out.css",
|
|
9
10
|
"test": "echo 'No tests'"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
13
|
"@types/fs-extra": "^9.0.3",
|
|
13
14
|
"@types/js-yaml": "^4.0.1",
|
|
14
|
-
"@types/prompts": "^2.0.1"
|
|
15
|
+
"@types/prompts": "^2.0.1",
|
|
16
|
+
"autoprefixer": "^10.4.5",
|
|
17
|
+
"postcss": "^8.4.12",
|
|
18
|
+
"tailwindcss": "^3.0.24"
|
|
15
19
|
},
|
|
16
20
|
"dependencies": {
|
|
17
|
-
"@contember/cli-common": "^1.1.0-
|
|
18
|
-
"@contember/config-loader": "^1.1.0-
|
|
19
|
-
"@contember/database-migrations": "^1.1.0-
|
|
20
|
-
"@contember/dic": "^1.1.0-
|
|
21
|
-
"@contember/engine-content-api": "^1.1.0-
|
|
22
|
-
"@contember/schema": "^1.1.0-
|
|
23
|
-
"@contember/schema-definition": "^1.1.0-
|
|
24
|
-
"@contember/schema-migrations": "^1.1.0-
|
|
25
|
-
"@contember/schema-utils": "^1.1.0-
|
|
21
|
+
"@contember/cli-common": "^1.1.0-beta.2",
|
|
22
|
+
"@contember/config-loader": "^1.1.0-beta.2",
|
|
23
|
+
"@contember/database-migrations": "^1.1.0-beta.2",
|
|
24
|
+
"@contember/dic": "^1.1.0-beta.2",
|
|
25
|
+
"@contember/engine-content-api": "^1.1.0-beta.2",
|
|
26
|
+
"@contember/schema": "^1.1.0-beta.2",
|
|
27
|
+
"@contember/schema-definition": "^1.1.0-beta.2",
|
|
28
|
+
"@contember/schema-migrations": "^1.1.0-beta.2",
|
|
29
|
+
"@contember/schema-utils": "^1.1.0-beta.2",
|
|
26
30
|
"@graphql-tools/merge": "^6.2.14",
|
|
27
31
|
"chalk": "^4.1.0",
|
|
28
32
|
"chalk-table": "^1.0.2",
|
|
@@ -33,6 +37,8 @@
|
|
|
33
37
|
"js-yaml": "^4.1.0",
|
|
34
38
|
"node-fetch": "^2.6.1",
|
|
35
39
|
"pg": "^8.5.0",
|
|
40
|
+
"preact": "^10.7.1",
|
|
41
|
+
"preact-render-to-string": "^5.2.0",
|
|
36
42
|
"prompts": "^2.2.1",
|
|
37
43
|
"vm2": "^3.9.9",
|
|
38
44
|
"yawn-yaml": "^1.5.0"
|
|
@@ -13,7 +13,7 @@ import { resolveSystemApiClient } from './SystemApiClientResolver'
|
|
|
13
13
|
import prompts from 'prompts'
|
|
14
14
|
import { emptySchema } from '@contember/schema-utils'
|
|
15
15
|
import { validateMigrations } from './MigrationValidationHelper'
|
|
16
|
-
import { loadSchema } from '../../utils/project'
|
|
16
|
+
import { loadSchema } from '../../utils/project/loadSchema'
|
|
17
17
|
|
|
18
18
|
type Args = {
|
|
19
19
|
project: string
|
|
@@ -8,7 +8,7 @@ import prompts from 'prompts'
|
|
|
8
8
|
import { interactiveResolveApiToken, TenantClient } from '../../utils/tenant'
|
|
9
9
|
import { interactiveResolveInstanceEnvironmentFromInput } from '../../utils/instance'
|
|
10
10
|
import { SystemClient } from '../../utils/system'
|
|
11
|
-
import { loadSchema } from '../../utils/project'
|
|
11
|
+
import { loadSchema } from '../../utils/project/loadSchema'
|
|
12
12
|
|
|
13
13
|
type Args = {
|
|
14
14
|
project: string
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command, CommandConfiguration, Input, validateProjectName, Workspace } from '@contember/cli-common'
|
|
2
|
+
import { validateSchemaAndPrintErrors } from '../../utils/schema'
|
|
3
|
+
import { loadSchema } from '../../utils/project/loadSchema'
|
|
4
|
+
import { renderProjectInfoHtml } from '../../utils/project/projectDescribe'
|
|
5
|
+
import { writeFile } from 'fs/promises'
|
|
6
|
+
import { join } from 'path'
|
|
7
|
+
|
|
8
|
+
type Args = {
|
|
9
|
+
project: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type Options = {
|
|
13
|
+
output?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ProjectGenerateDocumentation extends Command<Args, Options> {
|
|
17
|
+
protected configure(configuration: CommandConfiguration<Args, Options>): void {
|
|
18
|
+
configuration.description('Generates HTML documentation from project schema')
|
|
19
|
+
configuration.argument('project')
|
|
20
|
+
configuration.option('output').valueRequired().description('file name or "stdout"')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
protected async execute(input: Input<Args, Options>): Promise<number> {
|
|
24
|
+
const projectName = input.getArgument('project')
|
|
25
|
+
const workspace = await Workspace.get(process.cwd())
|
|
26
|
+
|
|
27
|
+
validateProjectName(projectName)
|
|
28
|
+
const project = await workspace.projects.getProject(projectName, { fuzzy: true })
|
|
29
|
+
const schema = await loadSchema(project)
|
|
30
|
+
if (!validateSchemaAndPrintErrors(schema, 'Defined schema is invalid:')) {
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
const html = await renderProjectInfoHtml(schema, project.name)
|
|
34
|
+
const output = input.getOption('output') ?? `${project.name}.html`
|
|
35
|
+
if (output === 'stdout') {
|
|
36
|
+
console.log(html)
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`Documentation saved to ${output}`)
|
|
39
|
+
await writeFile(join(process.cwd(), output), html)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return 0
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@contember/engine-content-api'
|
|
12
12
|
import { DocumentNode, printSchema } from 'graphql'
|
|
13
13
|
import { mergeSchemas } from '@graphql-tools/merge'
|
|
14
|
-
import { loadSchema } from '../../utils/project'
|
|
14
|
+
import { loadSchema } from '../../utils/project/loadSchema'
|
|
15
15
|
|
|
16
16
|
type Args = {
|
|
17
17
|
project: string
|
|
@@ -19,14 +19,14 @@ type Args = {
|
|
|
19
19
|
|
|
20
20
|
type Options = {
|
|
21
21
|
role?: string[]
|
|
22
|
-
format?: 'graphql' | 'introspection'
|
|
22
|
+
format?: 'graphql' | 'introspection' | 'schema'
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export class ProjectPrintSchemaCommand extends Command<Args, Options> {
|
|
26
26
|
protected configure(configuration: CommandConfiguration<Args, Options>): void {
|
|
27
27
|
configuration.description('Prints project schema')
|
|
28
28
|
configuration.argument('project')
|
|
29
|
-
configuration.option('format').valueRequired().description('graphql|introspection')
|
|
29
|
+
configuration.option('format').valueRequired().description('graphql|introspection|schema')
|
|
30
30
|
configuration.option('role').valueArray()
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -42,7 +42,8 @@ export class ProjectPrintSchemaCommand extends Command<Args, Options> {
|
|
|
42
42
|
return 1
|
|
43
43
|
}
|
|
44
44
|
const permissionFactory = new PermissionFactory(schema.model)
|
|
45
|
-
const
|
|
45
|
+
const inputRoles = input.getOption('role')
|
|
46
|
+
const permissions = permissionFactory.create(schema.acl, inputRoles || ['admin'])
|
|
46
47
|
const schemaBuilderFactory = new GraphQlSchemaBuilderFactory()
|
|
47
48
|
const authorizator = new Authorizator(permissions, schema.acl.customPrimary ?? false)
|
|
48
49
|
const introspection = new IntrospectionSchemaFactory(
|
|
@@ -50,7 +51,12 @@ export class ProjectPrintSchemaCommand extends Command<Args, Options> {
|
|
|
50
51
|
new EntityRulesResolver(schema.validation, schema.model),
|
|
51
52
|
authorizator,
|
|
52
53
|
)
|
|
53
|
-
if (format === '
|
|
54
|
+
if (format === 'schema') {
|
|
55
|
+
if (inputRoles) {
|
|
56
|
+
throw `--roles option is not supported for "schema" format`
|
|
57
|
+
}
|
|
58
|
+
console.log(JSON.stringify(schema, null, '\t'))
|
|
59
|
+
} else if (format === 'introspection') {
|
|
54
60
|
console.log(JSON.stringify(introspection.create(), null, '\t'))
|
|
55
61
|
} else if (format === 'graphql') {
|
|
56
62
|
const contentSchema = schemaBuilderFactory.create(schema.model, authorizator).build()
|
|
@@ -64,7 +70,7 @@ export class ProjectPrintSchemaCommand extends Command<Args, Options> {
|
|
|
64
70
|
const printedSchema = printSchema(gqlSchema).replace('""""""\n', '') // remove empty comment
|
|
65
71
|
console.log(printedSchema)
|
|
66
72
|
} else {
|
|
67
|
-
throw
|
|
73
|
+
throw `Unknown format ${format}`
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
return 0
|
|
@@ -3,7 +3,7 @@ import { MigrationsContainerFactory } from '../../MigrationsContainer'
|
|
|
3
3
|
import { validateSchemaAndPrintErrors } from '../../utils/schema'
|
|
4
4
|
import { emptySchema } from '@contember/schema-utils'
|
|
5
5
|
import { validateMigrations } from '../migrations/MigrationValidationHelper'
|
|
6
|
-
import { loadSchema } from '../../utils/project'
|
|
6
|
+
import { loadSchema } from '../../utils/project/loadSchema'
|
|
7
7
|
|
|
8
8
|
type Args = {
|
|
9
9
|
project: string
|
package/src/run.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
MigrationRebaseCommand,
|
|
12
12
|
MigrationStatusCommand,
|
|
13
13
|
ProjectCreateCommand,
|
|
14
|
+
ProjectGenerateDocumentation,
|
|
14
15
|
ProjectPrintSchemaCommand,
|
|
15
16
|
ProjectValidateCommand,
|
|
16
17
|
ResetPasswordCommand,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
['project:create']: () => new ProjectCreateCommand(),
|
|
36
37
|
['project:validate']: () => new ProjectValidateCommand(),
|
|
37
38
|
['project:print-schema']: () => new ProjectPrintSchemaCommand(),
|
|
39
|
+
['project:generate-doc']: () => new ProjectGenerateDocumentation(),
|
|
38
40
|
['tenant:sign-in']: () => new SignInCommand(),
|
|
39
41
|
['tenant:create-api-key']: () => new CreateApiKeyCommand(),
|
|
40
42
|
['tenant:invite']: () => new InviteCommand(),
|
package/src/tsconfig.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Model, Schema } from '@contember/schema'
|
|
2
|
+
import { ReferencesMap } from '../../schema/collectReferences'
|
|
3
|
+
import { formatEntityAnchor, formatFieldAnchor } from './utils'
|
|
4
|
+
import { SinglePermission } from './SinglePermission'
|
|
5
|
+
import { FieldType } from './FieldType'
|
|
6
|
+
import { FieldPermissions } from './FieldPermissions'
|
|
7
|
+
import { OnDelete } from './OnDelete'
|
|
8
|
+
import { Fragment, h } from 'preact'
|
|
9
|
+
|
|
10
|
+
interface EntityInfoProps {
|
|
11
|
+
entity: Model.Entity
|
|
12
|
+
schema: Schema
|
|
13
|
+
references: ReferencesMap
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const EntityInfo = ({ entity, schema, references }: EntityInfoProps) => (
|
|
17
|
+
<div class={'m-4 border border-gray-400 rounded-lg group'}
|
|
18
|
+
id={formatEntityAnchor(entity.name)}>
|
|
19
|
+
<div class={'font-bold text-gray-900 py-2 px-4 border-b border-gray-400 group-target:bg-yellow-100 rounded-t-lg'}>
|
|
20
|
+
{entity.name}
|
|
21
|
+
</div>
|
|
22
|
+
<div class={'px-2 py-2 flex flex-wrap '}>
|
|
23
|
+
<div>
|
|
24
|
+
<table>
|
|
25
|
+
<thead>
|
|
26
|
+
<tr>
|
|
27
|
+
<th class={'text-left px-2 text-gray-500 font-normal w-[180px] max-w-[180px]'}>Name</th>
|
|
28
|
+
<th class={'text-left px-2 text-gray-500 font-normal w-[340px] max-w-[340px]'}>Type</th>
|
|
29
|
+
{Object.keys(schema.acl.roles).map(role => <th
|
|
30
|
+
class={'text-left px-2 text-gray-500 font-normal w-[120px] max-w-[120px] text-xs truncate text-sm'}>{role}</th>)}
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tr>
|
|
34
|
+
<td></td>
|
|
35
|
+
<td></td>
|
|
36
|
+
{Object.values(schema.acl.roles).map(role => <td class={'px-2'}>
|
|
37
|
+
<SinglePermission value={'D'}
|
|
38
|
+
predicate={role.entities[entity.name]?.operations.delete} />
|
|
39
|
+
</td>)}
|
|
40
|
+
</tr>
|
|
41
|
+
{Object.values(entity.fields).map(field => (
|
|
42
|
+
<tr class={'even:bg-gray-100 target:bg-yellow-100'}
|
|
43
|
+
id={formatFieldAnchor(entity.name, field.name)}>
|
|
44
|
+
<td class={'px-2 font-mono truncate w-[180px] max-w-[180px]'} title={field.name}>
|
|
45
|
+
{field.name}
|
|
46
|
+
</td>
|
|
47
|
+
<td class={'text-gray-800 px-2 font-mono w-[420px] max-w-[420px]'}>
|
|
48
|
+
<FieldType field={field} />
|
|
49
|
+
</td>
|
|
50
|
+
{Object.values(schema.acl.roles).map(role => <td class={'px-2'}>
|
|
51
|
+
<FieldPermissions field={field.name}
|
|
52
|
+
entityPermissions={role.entities[entity.name]} />
|
|
53
|
+
</td>)}
|
|
54
|
+
</tr>
|
|
55
|
+
))}
|
|
56
|
+
|
|
57
|
+
</table>
|
|
58
|
+
</div>
|
|
59
|
+
<div class={'px-2 py-2 w-1/2'}>
|
|
60
|
+
<h3 class={'text-lg text-gray-600'}>On delete behaviour:</h3>
|
|
61
|
+
<p class={'text-gray-500 text-sm'}>
|
|
62
|
+
(What happens when you attempt to delete entity {entity.name})</p>
|
|
63
|
+
<div class={'mt-2 text-sm '}>
|
|
64
|
+
<OnDelete references={references} entity={entity} />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
{false && <div class={'px-2 py-2 w-1/2'}>
|
|
68
|
+
<h3 class={'text-lg text-gray-600'}>ACL predicates:</h3>
|
|
69
|
+
<div class={'mt-2 text-sm '}>
|
|
70
|
+
{Object.entries(schema.acl.roles).map(([roleName, role]) =>
|
|
71
|
+
Object.entries(role.entities[entity.name]?.predicates ?? {}).map(([predicateName, predicate]) => (
|
|
72
|
+
<Fragment>
|
|
73
|
+
<h4>{roleName}: {predicateName}</h4>
|
|
74
|
+
<code>{JSON.stringify(predicate)}</code>
|
|
75
|
+
</Fragment>
|
|
76
|
+
)),
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</div>}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Fragment, h } from 'preact'
|
|
2
|
+
import { EntityLink } from './EntityLink'
|
|
3
|
+
import { formatFieldAnchor } from './utils'
|
|
4
|
+
|
|
5
|
+
export const FieldLink = ({ entity, field, noEntityLabel }: { entity: string; field: string; noEntityLabel?: boolean }) => (
|
|
6
|
+
<Fragment>
|
|
7
|
+
{noEntityLabel ? '' : <Fragment><EntityLink entity={entity} />.</Fragment>}
|
|
8
|
+
<a href={`#${formatFieldAnchor(entity, field)}`} class={'text-blue-500 hover:underline'}>{field}</a>
|
|
9
|
+
</Fragment>
|
|
10
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Fragment, h } from 'preact'
|
|
2
|
+
import { Acl } from '@contember/schema'
|
|
3
|
+
import { SinglePermission } from './SinglePermission'
|
|
4
|
+
|
|
5
|
+
export const FieldPermissions = ({ entityPermissions, field }: { entityPermissions?: Acl.EntityPermissions; field: string }) => {
|
|
6
|
+
const readPermissions = entityPermissions?.operations.read?.[field]
|
|
7
|
+
const updatePermissions = entityPermissions?.operations.update?.[field]
|
|
8
|
+
const createPermissions = entityPermissions?.operations.create?.[field]
|
|
9
|
+
return <Fragment>
|
|
10
|
+
<SinglePermission value={'C'} predicate={createPermissions} />
|
|
11
|
+
<SinglePermission value={'R'} predicate={readPermissions} />
|
|
12
|
+
<SinglePermission value={'U'} predicate={updatePermissions} />
|
|
13
|
+
</Fragment>
|
|
14
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Model } from '@contember/schema'
|
|
2
|
+
import { h, Fragment } from 'preact'
|
|
3
|
+
import { EntityLink } from './EntityLink'
|
|
4
|
+
import { FieldLink } from './FieldLink'
|
|
5
|
+
|
|
6
|
+
export const FieldType = ({ field }: { field: Model.AnyField }) => {
|
|
7
|
+
if (field.type === 'ManyHasMany') {
|
|
8
|
+
return <>
|
|
9
|
+
<EntityLink entity={field.target}/>[]{' '}
|
|
10
|
+
<TargetField field={field}/> (m:n)
|
|
11
|
+
</>
|
|
12
|
+
|
|
13
|
+
} else if (field.type === 'OneHasMany') {
|
|
14
|
+
return <>
|
|
15
|
+
<EntityLink entity={field.target}/>[]{' '}
|
|
16
|
+
<TargetField field={field}/> (1:n)
|
|
17
|
+
</>
|
|
18
|
+
|
|
19
|
+
} else if (field.type === 'OneHasOne') {
|
|
20
|
+
return <>
|
|
21
|
+
<NullableMark field={field} />
|
|
22
|
+
<EntityLink entity={field.target}/>{' '}
|
|
23
|
+
<TargetField field={field}/> (1:1)
|
|
24
|
+
</>
|
|
25
|
+
|
|
26
|
+
} else if (field.type === 'ManyHasOne') {
|
|
27
|
+
return <>
|
|
28
|
+
<NullableMark field={field} />
|
|
29
|
+
<EntityLink entity={field.target}/>{' '}
|
|
30
|
+
<TargetField field={field}/> (m:1)
|
|
31
|
+
</>
|
|
32
|
+
|
|
33
|
+
} else {
|
|
34
|
+
return <>
|
|
35
|
+
<NullableMark field={field} />
|
|
36
|
+
{field.type}
|
|
37
|
+
</>
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const TargetField = ({ field }: {field: Model.AnyRelation}) => {
|
|
43
|
+
if ('ownedBy' in field) {
|
|
44
|
+
return <FieldLink entity={field.target} field={field.ownedBy} noEntityLabel />
|
|
45
|
+
}
|
|
46
|
+
if (field.inversedBy) {
|
|
47
|
+
return <FieldLink entity={field.target} field={field.inversedBy} noEntityLabel />
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const NullableMark = (props: { field: Model.NullableRelation | Model.AnyColumn }) => {
|
|
53
|
+
return props.field.nullable ? <span class={'font-bold color-black'}>? </span> : null
|
|
54
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { h, Fragment } from 'preact'
|
|
2
|
+
import { Model } from '@contember/schema'
|
|
3
|
+
import { ReferenceMapEntry, ReferencesMap } from '../../schema/collectReferences'
|
|
4
|
+
import { FieldLink } from './FieldLink'
|
|
5
|
+
import { EntityLink } from './EntityLink'
|
|
6
|
+
|
|
7
|
+
const sortReferences = (a: ReferenceMapEntry, b: ReferenceMapEntry) =>
|
|
8
|
+
a.owningRelation.joiningColumn.onDelete.localeCompare(b.owningRelation.joiningColumn.onDelete)
|
|
9
|
+
|| a.owningEntity.name.localeCompare(b.owningEntity.name)
|
|
10
|
+
|| a.owningRelation.name.localeCompare(b.owningRelation.name)
|
|
11
|
+
|
|
12
|
+
export const OnDelete = ({ references, entity, visited = [] }: { references: ReferencesMap; entity: Model.Entity; visited?: Model.Entity[] }) => {
|
|
13
|
+
const referencedFrom = references[entity.name]
|
|
14
|
+
if (referencedFrom.length === 0) {
|
|
15
|
+
if (visited.length !== 0) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
<span class={'bg-green-200 text-gray-600 p-1 rounded'}>
|
|
20
|
+
This entity is not referenced from any other entity.
|
|
21
|
+
</span>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (visited.includes(entity)) {
|
|
26
|
+
return (
|
|
27
|
+
<ul class={'ml-4'}>
|
|
28
|
+
<li class={'italic'}>(recursion)</li>
|
|
29
|
+
</ul>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<ul class={visited.length > 0 ? 'ml-4' : ''}>
|
|
35
|
+
{referencedFrom
|
|
36
|
+
.sort(sortReferences)
|
|
37
|
+
.map(it => {
|
|
38
|
+
const inverseDescr = it.targetRelation
|
|
39
|
+
? <>see <FieldLink entity={entity.name} field={it.targetRelation.name} /></>
|
|
40
|
+
: 'no inverse side'
|
|
41
|
+
if (it.owningRelation.joiningColumn.onDelete === 'restrict') {
|
|
42
|
+
return (
|
|
43
|
+
<li>
|
|
44
|
+
<span class={'bg-black text-white px-1'}>Fails</span> when referenced from
|
|
45
|
+
{' '}<FieldLink entity={it.owningEntity.name} field={it.owningRelation.name} />
|
|
46
|
+
{' '}({inverseDescr})
|
|
47
|
+
</li>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
if (it.owningRelation.joiningColumn.onDelete === 'set null') {
|
|
51
|
+
return (
|
|
52
|
+
<li>
|
|
53
|
+
<span class={'bg-blue-400 text-white px-1'}>Sets null</span> at
|
|
54
|
+
{' '}<FieldLink entity={it.owningEntity.name} field={it.owningRelation.name} />
|
|
55
|
+
{' '}({inverseDescr})
|
|
56
|
+
</li>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
if (it.owningRelation.joiningColumn.onDelete === 'cascade') {
|
|
60
|
+
return (
|
|
61
|
+
<li>
|
|
62
|
+
<span class={'bg-red-400 text-white px-1'}>Deletes</span> <EntityLink entity={it.owningEntity.name} />
|
|
63
|
+
{' '}connected using
|
|
64
|
+
{' '}<FieldLink entity={it.owningEntity.name} field={it.owningRelation.name} noEntityLabel />
|
|
65
|
+
{' '}({inverseDescr})
|
|
66
|
+
<OnDelete entity={it.owningEntity} visited={[...visited, entity]} references={references} />
|
|
67
|
+
</li>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
})}
|
|
71
|
+
</ul>)
|
|
72
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Schema } from '@contember/schema'
|
|
2
|
+
import { h } from 'preact'
|
|
3
|
+
import { collectReferences } from '../../schema/collectReferences'
|
|
4
|
+
import { EntityLink } from './EntityLink'
|
|
5
|
+
import { EntityInfo } from './EntityInfo'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const ProjectInfo = (props: { schema: Schema; projectName: string }) => {
|
|
9
|
+
const references = collectReferences(props.schema.model)
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div class={'w-full'}>
|
|
13
|
+
<div class={'bg-gray-100 p-6 border-b border-gray-400'}>
|
|
14
|
+
<h1 class={'font-bold text-xl mb-1'}>{props.projectName}</h1>
|
|
15
|
+
<div class={'flex flex-column gap-4'}>
|
|
16
|
+
<div>
|
|
17
|
+
<h2 class={'font-semibold text-gray-500 mb-1'}>Entities</h2>
|
|
18
|
+
<ul class={'text-sm'}>
|
|
19
|
+
{Object.values(props.schema.model.entities).map(entity => (
|
|
20
|
+
<li>
|
|
21
|
+
<EntityLink entity={entity.name} />
|
|
22
|
+
</li>
|
|
23
|
+
))}
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
<h2 class={'font-semibold text-gray-500 mb-1'}>Enums</h2>
|
|
28
|
+
<ul class={'text-sm'}>
|
|
29
|
+
{Object.entries(props.schema.model.enums).map(([name, values]) => (
|
|
30
|
+
<li class={'mb-2'}>
|
|
31
|
+
{name} <br />
|
|
32
|
+
<span class={'text-gray-700 ml-4'}>
|
|
33
|
+
{values.join(', ')}
|
|
34
|
+
</span>
|
|
35
|
+
</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
</div>
|
|
43
|
+
{Object.values(props.schema.model.entities).map(entity => (
|
|
44
|
+
<EntityInfo entity={entity} schema={props.schema} references={references}/>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { h } from 'preact'
|
|
2
|
+
import { Acl } from '@contember/schema'
|
|
3
|
+
|
|
4
|
+
export const SinglePermission = ({ predicate, value }: { predicate?: Acl.Predicate; value: string }) => {
|
|
5
|
+
if (!predicate) {
|
|
6
|
+
return <span class={'bg-red-500 text-white font-semibold px-0.5'}>{value}</span>
|
|
7
|
+
}
|
|
8
|
+
if (predicate === true) {
|
|
9
|
+
return <span class={'bg-green-600 text-white font-semibold px-0.5'}>{value}</span>
|
|
10
|
+
}
|
|
11
|
+
return <span class={'bg-yellow-400 text-black font-semibold px-0.5'} title={predicate}>{value}</span>
|
|
12
|
+
}
|
|
13
|
+
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Schema } from '@contember/schema'
|
|
2
|
+
import { h } from 'preact'
|
|
3
|
+
import render from 'preact-render-to-string'
|
|
4
|
+
import { ProjectInfo } from './components/ProjectInfo'
|
|
5
|
+
import { readFile } from 'fs/promises'
|
|
6
|
+
|
|
7
|
+
export const renderProjectInfoHtml = async (schema: Schema, projectName: string) => {
|
|
8
|
+
const html = render(<ProjectInfo schema={schema} projectName={projectName} />)
|
|
9
|
+
|
|
10
|
+
const css = await readFile(__dirname + '/resources/out.css')
|
|
11
|
+
return `<!DOCTYPE html>
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="UTF-8">
|
|
15
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
16
|
+
<style>${css}</style>
|
|
17
|
+
<title>${projectName}</title>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
${html}
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|