@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.
Files changed (96) hide show
  1. package/dist/src/commands/migrations/MigrationAmendCommand.js +2 -2
  2. package/dist/src/commands/migrations/MigrationAmendCommand.js.map +1 -1
  3. package/dist/src/commands/migrations/MigrationDiffCommand.js +2 -2
  4. package/dist/src/commands/migrations/MigrationDiffCommand.js.map +1 -1
  5. package/dist/src/commands/project/ProjectGenerateDocumentation.d.ts +13 -0
  6. package/dist/src/commands/project/ProjectGenerateDocumentation.d.ts.map +1 -0
  7. package/dist/src/commands/project/ProjectGenerateDocumentation.js +39 -0
  8. package/dist/src/commands/project/ProjectGenerateDocumentation.js.map +1 -0
  9. package/dist/src/commands/project/ProjectPrintSchemaCommand.d.ts +1 -1
  10. package/dist/src/commands/project/ProjectPrintSchemaCommand.d.ts.map +1 -1
  11. package/dist/src/commands/project/ProjectPrintSchemaCommand.js +13 -6
  12. package/dist/src/commands/project/ProjectPrintSchemaCommand.js.map +1 -1
  13. package/dist/src/commands/project/ProjectValidateCommand.js +2 -2
  14. package/dist/src/commands/project/ProjectValidateCommand.js.map +1 -1
  15. package/dist/src/commands/project/index.d.ts +1 -0
  16. package/dist/src/commands/project/index.d.ts.map +1 -1
  17. package/dist/src/commands/project/index.js +1 -0
  18. package/dist/src/commands/project/index.js.map +1 -1
  19. package/dist/src/run.js +1 -0
  20. package/dist/src/run.js.map +1 -1
  21. package/dist/src/tsconfig.tsbuildinfo +1 -1
  22. package/dist/src/utils/project/components/EntityInfo.d.ts +11 -0
  23. package/dist/src/utils/project/components/EntityInfo.d.ts.map +1 -0
  24. package/dist/src/utils/project/components/EntityInfo.js +54 -0
  25. package/dist/src/utils/project/components/EntityInfo.js.map +1 -0
  26. package/dist/src/utils/project/components/EntityLink.d.ts +5 -0
  27. package/dist/src/utils/project/components/EntityLink.d.ts.map +1 -0
  28. package/dist/src/utils/project/components/EntityLink.js +8 -0
  29. package/dist/src/utils/project/components/EntityLink.js.map +1 -0
  30. package/dist/src/utils/project/components/FieldLink.d.ts +7 -0
  31. package/dist/src/utils/project/components/FieldLink.d.ts.map +1 -0
  32. package/dist/src/utils/project/components/FieldLink.js +13 -0
  33. package/dist/src/utils/project/components/FieldLink.js.map +1 -0
  34. package/dist/src/utils/project/components/FieldPermissions.d.ts +7 -0
  35. package/dist/src/utils/project/components/FieldPermissions.d.ts.map +1 -0
  36. package/dist/src/utils/project/components/FieldPermissions.js +17 -0
  37. package/dist/src/utils/project/components/FieldPermissions.js.map +1 -0
  38. package/dist/src/utils/project/components/FieldType.d.ts +6 -0
  39. package/dist/src/utils/project/components/FieldType.d.ts.map +1 -0
  40. package/dist/src/utils/project/components/FieldType.js +59 -0
  41. package/dist/src/utils/project/components/FieldType.js.map +1 -0
  42. package/dist/src/utils/project/components/OnDelete.d.ts +9 -0
  43. package/dist/src/utils/project/components/OnDelete.d.ts.map +1 -0
  44. package/dist/src/utils/project/components/OnDelete.js +70 -0
  45. package/dist/src/utils/project/components/OnDelete.js.map +1 -0
  46. package/dist/src/utils/project/components/ProjectInfo.d.ts +7 -0
  47. package/dist/src/utils/project/components/ProjectInfo.d.ts.map +1 -0
  48. package/dist/src/utils/project/components/ProjectInfo.js +28 -0
  49. package/dist/src/utils/project/components/ProjectInfo.js.map +1 -0
  50. package/dist/src/utils/project/components/SinglePermission.d.ts +7 -0
  51. package/dist/src/utils/project/components/SinglePermission.d.ts.map +1 -0
  52. package/dist/src/utils/project/components/SinglePermission.js +15 -0
  53. package/dist/src/utils/project/components/SinglePermission.js.map +1 -0
  54. package/dist/src/utils/project/components/utils.d.ts +3 -0
  55. package/dist/src/utils/project/components/utils.d.ts.map +1 -0
  56. package/dist/src/utils/project/components/utils.js +8 -0
  57. package/dist/src/utils/project/components/utils.js.map +1 -0
  58. package/dist/src/utils/{project.d.ts → project/loadSchema.d.ts} +1 -1
  59. package/dist/src/utils/project/loadSchema.d.ts.map +1 -0
  60. package/dist/src/utils/{project.js → project/loadSchema.js} +1 -1
  61. package/dist/src/utils/project/loadSchema.js.map +1 -0
  62. package/dist/src/utils/project/projectDescribe.d.ts +3 -0
  63. package/dist/src/utils/project/projectDescribe.d.ts.map +1 -0
  64. package/dist/src/utils/project/projectDescribe.js +29 -0
  65. package/dist/src/utils/project/projectDescribe.js.map +1 -0
  66. package/dist/src/utils/project/resources/out.css +759 -0
  67. package/dist/src/utils/schema/collectReferences.d.ts +10 -0
  68. package/dist/src/utils/schema/collectReferences.d.ts.map +1 -0
  69. package/dist/src/utils/schema/collectReferences.js +35 -0
  70. package/dist/src/utils/schema/collectReferences.js.map +1 -0
  71. package/package.json +17 -11
  72. package/postcss.config.js +6 -0
  73. package/src/commands/migrations/MigrationAmendCommand.ts +1 -1
  74. package/src/commands/migrations/MigrationDiffCommand.ts +1 -1
  75. package/src/commands/project/ProjectGenerateDocumentation.ts +44 -0
  76. package/src/commands/project/ProjectPrintSchemaCommand.ts +12 -6
  77. package/src/commands/project/ProjectValidateCommand.ts +1 -1
  78. package/src/commands/project/index.ts +1 -0
  79. package/src/run.ts +2 -0
  80. package/src/tsconfig.json +5 -1
  81. package/src/utils/project/components/EntityInfo.tsx +82 -0
  82. package/src/utils/project/components/EntityLink.tsx +6 -0
  83. package/src/utils/project/components/FieldLink.tsx +10 -0
  84. package/src/utils/project/components/FieldPermissions.tsx +14 -0
  85. package/src/utils/project/components/FieldType.tsx +54 -0
  86. package/src/utils/project/components/OnDelete.tsx +72 -0
  87. package/src/utils/project/components/ProjectInfo.tsx +48 -0
  88. package/src/utils/project/components/SinglePermission.tsx +13 -0
  89. package/src/utils/project/components/utils.ts +3 -0
  90. package/src/utils/{project.ts → project/loadSchema.ts} +0 -0
  91. package/src/utils/project/projectDescribe.tsx +26 -0
  92. package/src/utils/project/resources/style.css +3 -0
  93. package/src/utils/schema/collectReferences.ts +40 -0
  94. package/tailwind.config.js +7 -0
  95. package/dist/src/utils/project.d.ts.map +0 -1
  96. 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-alpha.8",
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-alpha.8",
18
- "@contember/config-loader": "^1.1.0-alpha.8",
19
- "@contember/database-migrations": "^1.1.0-alpha.8",
20
- "@contember/dic": "^1.1.0-alpha.8",
21
- "@contember/engine-content-api": "^1.1.0-alpha.8",
22
- "@contember/schema": "^1.1.0-alpha.8",
23
- "@contember/schema-definition": "^1.1.0-alpha.8",
24
- "@contember/schema-migrations": "^1.1.0-alpha.8",
25
- "@contember/schema-utils": "^1.1.0-alpha.8",
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"
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -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 permissions = permissionFactory.create(schema.acl, input.getOption('role') || ['admin'])
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 === 'introspection') {
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 new Error(`Unknown format ${format}`)
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
@@ -1,3 +1,4 @@
1
1
  export * from './ProjectCreateCommand'
2
+ export * from './ProjectGenerateDocumentation'
2
3
  export * from './ProjectPrintSchemaCommand'
3
4
  export * from './ProjectValidateCommand'
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
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "extends": "../tsconfig.settings.json",
3
3
  "compilerOptions": {
4
- "outDir": "../dist/src"
4
+ "outDir": "../dist/src",
5
+ "jsx": "react",
6
+ "jsxFactory": "h",
7
+ "lib": ["dom"],
8
+ "jsxFragmentFactory": "Fragment"
5
9
  },
6
10
  "references": [
7
11
  {
@@ -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,6 @@
1
+ import { h } from 'preact'
2
+ import { formatEntityAnchor } from './utils'
3
+
4
+ export const EntityLink = ({ entity }: { entity: string }) => (
5
+ <a href={`#${formatEntityAnchor(entity)}`} class={'text-blue-500 hover:underline'}>{entity}</a>
6
+ )
@@ -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
+
@@ -0,0 +1,3 @@
1
+ export const formatEntityAnchor = (name: string) => `entity-${name}`
2
+ export const formatFieldAnchor = (entity: string, field: string) => `field-${entity}--${field}`
3
+
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
+
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;