@contember/cli 1.4.0-alpha.1 → 1.4.0-alpha.3

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 (143) hide show
  1. package/Dockerfile +2 -2
  2. package/dist/src/commands/deploy/DeployCommand.d.ts +1 -1
  3. package/dist/src/commands/deploy/DeployCommand.d.ts.map +1 -1
  4. package/dist/src/commands/deploy/DeployCommand.js +8 -3
  5. package/dist/src/commands/deploy/DeployCommand.js.map +1 -1
  6. package/dist/src/commands/migrations/MigrationAmendCommand.d.ts.map +1 -1
  7. package/dist/src/commands/migrations/MigrationAmendCommand.js +6 -6
  8. package/dist/src/commands/migrations/MigrationAmendCommand.js.map +1 -1
  9. package/dist/src/commands/migrations/MigrationBlankCommand.d.ts +15 -0
  10. package/dist/src/commands/migrations/MigrationBlankCommand.d.ts.map +1 -0
  11. package/dist/src/commands/migrations/MigrationBlankCommand.js +29 -0
  12. package/dist/src/commands/migrations/MigrationBlankCommand.js.map +1 -0
  13. package/dist/src/commands/migrations/MigrationDescribeCommand.d.ts.map +1 -1
  14. package/dist/src/commands/migrations/MigrationDescribeCommand.js +5 -5
  15. package/dist/src/commands/migrations/MigrationDescribeCommand.js.map +1 -1
  16. package/dist/src/commands/migrations/MigrationDiffCommand.d.ts.map +1 -1
  17. package/dist/src/commands/migrations/MigrationDiffCommand.js +8 -3
  18. package/dist/src/commands/migrations/MigrationDiffCommand.js.map +1 -1
  19. package/dist/src/commands/migrations/MigrationExecuteCommand.d.ts +1 -1
  20. package/dist/src/commands/migrations/MigrationExecuteCommand.d.ts.map +1 -1
  21. package/dist/src/commands/migrations/MigrationExecuteCommand.js +8 -3
  22. package/dist/src/commands/migrations/MigrationExecuteCommand.js.map +1 -1
  23. package/dist/src/commands/migrations/MigrationRebaseCommand.d.ts.map +1 -1
  24. package/dist/src/commands/migrations/MigrationRebaseCommand.js +2 -3
  25. package/dist/src/commands/migrations/MigrationRebaseCommand.js.map +1 -1
  26. package/dist/src/commands/migrations/MigrationStatusCommand.d.ts +1 -2
  27. package/dist/src/commands/migrations/MigrationStatusCommand.d.ts.map +1 -1
  28. package/dist/src/commands/migrations/MigrationStatusCommand.js +5 -7
  29. package/dist/src/commands/migrations/MigrationStatusCommand.js.map +1 -1
  30. package/dist/src/commands/migrations/index.d.ts +1 -0
  31. package/dist/src/commands/migrations/index.d.ts.map +1 -1
  32. package/dist/src/commands/migrations/index.js +1 -0
  33. package/dist/src/commands/migrations/index.js.map +1 -1
  34. package/dist/src/commands/project/ProjectValidateCommand.js +2 -2
  35. package/dist/src/commands/project/ProjectValidateCommand.js.map +1 -1
  36. package/dist/src/run.js +1 -0
  37. package/dist/src/run.js.map +1 -1
  38. package/dist/src/tsconfig.tsbuildinfo +1 -1
  39. package/dist/src/utils/admin.d.ts.map +1 -1
  40. package/dist/src/utils/admin.js +1 -5
  41. package/dist/src/utils/admin.js.map +1 -1
  42. package/dist/src/utils/esbuild.d.ts +2 -0
  43. package/dist/src/utils/esbuild.d.ts.map +1 -0
  44. package/dist/src/utils/esbuild.js +41 -0
  45. package/dist/src/utils/esbuild.js.map +1 -0
  46. package/dist/src/utils/migrations/JsLoader.d.ts +9 -0
  47. package/dist/src/utils/migrations/JsLoader.d.ts.map +1 -0
  48. package/dist/src/utils/migrations/JsLoader.js +33 -0
  49. package/dist/src/utils/migrations/JsLoader.js.map +1 -0
  50. package/dist/src/utils/migrations/JsonLoader.d.ts +9 -0
  51. package/dist/src/utils/migrations/JsonLoader.d.ts.map +1 -0
  52. package/dist/src/utils/migrations/JsonLoader.js +17 -0
  53. package/dist/src/utils/migrations/JsonLoader.js.map +1 -0
  54. package/dist/src/{commands → utils}/migrations/MigrationCreateHelper.d.ts +6 -3
  55. package/dist/src/utils/migrations/MigrationCreateHelper.d.ts.map +1 -0
  56. package/dist/src/{commands → utils}/migrations/MigrationCreateHelper.js +1 -1
  57. package/dist/src/utils/migrations/MigrationCreateHelper.js.map +1 -0
  58. package/dist/src/utils/migrations/MigrationCreator.d.ts +21 -0
  59. package/dist/src/utils/migrations/MigrationCreator.d.ts.map +1 -0
  60. package/dist/src/utils/migrations/MigrationCreator.js +47 -0
  61. package/dist/src/utils/migrations/MigrationCreator.js.map +1 -0
  62. package/dist/src/{commands → utils}/migrations/MigrationExecuteHelper.d.ts +8 -4
  63. package/dist/src/utils/migrations/MigrationExecuteHelper.d.ts.map +1 -0
  64. package/dist/src/{commands → utils}/migrations/MigrationExecuteHelper.js +57 -5
  65. package/dist/src/utils/migrations/MigrationExecuteHelper.js.map +1 -0
  66. package/dist/src/utils/migrations/MigrationFile.d.ts +36 -0
  67. package/dist/src/utils/migrations/MigrationFile.d.ts.map +1 -0
  68. package/dist/src/utils/migrations/MigrationFile.js +6 -0
  69. package/dist/src/utils/migrations/MigrationFile.js.map +1 -0
  70. package/dist/src/utils/migrations/MigrationFileLoader.d.ts +5 -0
  71. package/dist/src/utils/migrations/MigrationFileLoader.d.ts.map +1 -0
  72. package/dist/src/utils/migrations/MigrationFileLoader.js +3 -0
  73. package/dist/src/utils/migrations/MigrationFileLoader.js.map +1 -0
  74. package/dist/src/utils/migrations/MigrationFilesManager.d.ts +18 -0
  75. package/dist/src/utils/migrations/MigrationFilesManager.d.ts.map +1 -0
  76. package/dist/src/utils/migrations/MigrationFilesManager.js +114 -0
  77. package/dist/src/utils/migrations/MigrationFilesManager.js.map +1 -0
  78. package/dist/src/utils/migrations/MigrationParser.d.ts +6 -0
  79. package/dist/src/utils/migrations/MigrationParser.d.ts.map +1 -0
  80. package/dist/src/utils/migrations/MigrationParser.js +33 -0
  81. package/dist/src/utils/migrations/MigrationParser.js.map +1 -0
  82. package/dist/src/{MigrationsContainer.d.ts → utils/migrations/MigrationsContainer.d.ts} +5 -1
  83. package/dist/src/utils/migrations/MigrationsContainer.d.ts.map +1 -0
  84. package/dist/src/utils/migrations/MigrationsContainer.js +50 -0
  85. package/dist/src/utils/migrations/MigrationsContainer.js.map +1 -0
  86. package/dist/src/utils/migrations/MigrationsResolver.d.ts +14 -0
  87. package/dist/src/utils/migrations/MigrationsResolver.d.ts.map +1 -0
  88. package/dist/src/utils/migrations/MigrationsResolver.js +37 -0
  89. package/dist/src/utils/migrations/MigrationsResolver.js.map +1 -0
  90. package/dist/src/utils/migrations/SchemaVersionBuilder.d.ts +13 -0
  91. package/dist/src/utils/migrations/SchemaVersionBuilder.d.ts.map +1 -0
  92. package/dist/src/utils/migrations/SchemaVersionBuilder.js +26 -0
  93. package/dist/src/utils/migrations/SchemaVersionBuilder.js.map +1 -0
  94. package/dist/src/utils/{migrations.d.ts → migrations/migrations.d.ts} +18 -22
  95. package/dist/src/utils/migrations/migrations.d.ts.map +1 -0
  96. package/dist/src/utils/{migrations.js → migrations/migrations.js} +13 -28
  97. package/dist/src/utils/migrations/migrations.js.map +1 -0
  98. package/dist/src/utils/project/loadSchema.d.ts.map +1 -1
  99. package/dist/src/utils/project/loadSchema.js +5 -44
  100. package/dist/src/utils/project/loadSchema.js.map +1 -1
  101. package/dist/src/utils/project/resources/out.css +106 -124
  102. package/dist/src/utils/system/index.d.ts +3 -4
  103. package/dist/src/utils/system/index.d.ts.map +1 -1
  104. package/dist/src/utils/system/index.js.map +1 -1
  105. package/package.json +26 -27
  106. package/src/commands/deploy/DeployCommand.ts +9 -4
  107. package/src/commands/migrations/MigrationAmendCommand.ts +7 -11
  108. package/src/commands/migrations/MigrationBlankCommand.ts +46 -0
  109. package/src/commands/migrations/MigrationDescribeCommand.ts +7 -5
  110. package/src/commands/migrations/MigrationDiffCommand.ts +8 -3
  111. package/src/commands/migrations/MigrationExecuteCommand.ts +8 -3
  112. package/src/commands/migrations/MigrationRebaseCommand.ts +2 -3
  113. package/src/commands/migrations/MigrationStatusCommand.ts +6 -9
  114. package/src/commands/migrations/index.ts +1 -0
  115. package/src/commands/project/ProjectValidateCommand.ts +2 -2
  116. package/src/run.ts +2 -0
  117. package/src/tsconfig.json +1 -0
  118. package/src/utils/admin.ts +0 -1
  119. package/src/utils/esbuild.ts +14 -0
  120. package/src/utils/migrations/JsLoader.ts +37 -0
  121. package/src/utils/migrations/JsonLoader.ts +18 -0
  122. package/src/{commands → utils}/migrations/MigrationCreateHelper.ts +7 -11
  123. package/src/utils/migrations/MigrationCreator.ts +60 -0
  124. package/src/{commands → utils}/migrations/MigrationExecuteHelper.ts +68 -9
  125. package/src/utils/migrations/MigrationFile.ts +52 -0
  126. package/src/utils/migrations/MigrationFileLoader.ts +5 -0
  127. package/src/utils/migrations/MigrationFilesManager.ts +102 -0
  128. package/src/utils/migrations/MigrationParser.ts +32 -0
  129. package/src/{MigrationsContainer.ts → utils/migrations/MigrationsContainer.ts} +29 -12
  130. package/src/utils/migrations/MigrationsResolver.ts +44 -0
  131. package/src/utils/migrations/SchemaVersionBuilder.ts +33 -0
  132. package/src/utils/{migrations.ts → migrations/migrations.ts} +33 -57
  133. package/src/utils/project/loadSchema.ts +7 -21
  134. package/src/utils/system/index.ts +3 -4
  135. package/dist/src/MigrationsContainer.d.ts.map +0 -1
  136. package/dist/src/MigrationsContainer.js +0 -25
  137. package/dist/src/MigrationsContainer.js.map +0 -1
  138. package/dist/src/commands/migrations/MigrationCreateHelper.d.ts.map +0 -1
  139. package/dist/src/commands/migrations/MigrationCreateHelper.js.map +0 -1
  140. package/dist/src/commands/migrations/MigrationExecuteHelper.d.ts.map +0 -1
  141. package/dist/src/commands/migrations/MigrationExecuteHelper.js.map +0 -1
  142. package/dist/src/utils/migrations.d.ts.map +0 -1
  143. package/dist/src/utils/migrations.js.map +0 -1
@@ -1,9 +1,9 @@
1
1
  import { Command, CommandConfiguration, Input, Workspace } from '@contember/cli-common'
2
2
  import { printValidationErrors } from '../../utils/schema'
3
3
  import { InvalidSchemaException } from '@contember/schema-migrations'
4
- import { executeCreateMigrationCommand } from './MigrationCreateHelper'
5
- import { createMigrationStatusTable, printMigrationDescription } from '../../utils/migrations'
6
- import { executeMigrations, resolveMigrationStatus } from './MigrationExecuteHelper'
4
+ import { executeCreateMigrationCommand } from '../../utils/migrations/MigrationCreateHelper'
5
+ import { createMigrationStatusTable, printMigrationDescription } from '../../utils/migrations/migrations'
6
+ import { executeMigrations, resolveMigrationStatus } from '../../utils/migrations/MigrationExecuteHelper'
7
7
  import prompts from 'prompts'
8
8
  import { interactiveResolveApiToken, TenantClient } from '../../utils/tenant'
9
9
  import { interactiveResolveInstanceEnvironmentFromInput } from '../../utils/instance'
@@ -110,6 +110,11 @@ export class MigrationDiffCommand extends Command<Args, Options> {
110
110
  requireConfirmation: status.migrationsToExecute.length > 1 && !yes,
111
111
  schemaVersionBuilder,
112
112
  migrationDescriber,
113
+ contentMigrationFactoryArgs: {
114
+ apiToken,
115
+ apiBaseUrl: instance.baseUrl.replace(/\/$/, ''),
116
+ projectName: project.name,
117
+ },
113
118
  })
114
119
  }
115
120
  return 0
@@ -1,12 +1,12 @@
1
1
  import { Command, CommandConfiguration, Input, Workspace } from '@contember/cli-common'
2
- import { MigrationsContainerFactory } from '../../MigrationsContainer'
2
+ import { MigrationsContainerFactory } from '../../utils/migrations/MigrationsContainer'
3
3
  import {
4
4
  configureExecuteMigrationCommand,
5
5
  ExecuteMigrationOptions,
6
6
  executeMigrations,
7
7
  resolveMigrationStatus,
8
- } from './MigrationExecuteHelper'
9
- import { createMigrationStatusTable } from '../../utils/migrations'
8
+ } from '../../utils/migrations/MigrationExecuteHelper'
9
+ import { createMigrationStatusTable } from '../../utils/migrations/migrations'
10
10
  import { interactiveResolveInstanceEnvironmentFromInput } from '../../utils/instance'
11
11
  import { interactiveResolveApiToken, TenantClient } from '../../utils/tenant'
12
12
  import { SystemClient } from '../../utils/system'
@@ -90,6 +90,11 @@ export class MigrationExecuteCommand extends Command<Args, Options> {
90
90
  migrations,
91
91
  requireConfirmation: !input.getOption('yes'),
92
92
  force: force,
93
+ contentMigrationFactoryArgs: {
94
+ apiToken,
95
+ apiBaseUrl: instance.baseUrl.replace(/\/$/, ''),
96
+ projectName: remoteProject,
97
+ },
93
98
  })
94
99
  code ||= singleCode
95
100
  }
@@ -1,7 +1,6 @@
1
1
  import { Command, CommandConfiguration, Input, Workspace } from '@contember/cli-common'
2
2
  import { MigrationVersionHelper } from '@contember/schema-migrations'
3
- import { executeCreateMigrationCommand } from './MigrationCreateHelper'
4
- import { getMigrationByName } from '../../utils/migrations'
3
+ import { executeCreateMigrationCommand } from '../../utils/migrations/MigrationCreateHelper'
5
4
  import { resolveSystemApiClient } from './SystemApiClientResolver'
6
5
  import { emptySchema } from '@contember/schema-utils'
7
6
  import { validateMigrations } from './MigrationValidationHelper'
@@ -52,7 +51,7 @@ export class MigrationRebaseCommand extends Command<Args, Options> {
52
51
  const migrationNames = input.getArgument('migration')
53
52
  const migrations = []
54
53
  for (const migrationName of migrationNames) {
55
- const migration = await getMigrationByName(migrationsResolver, migrationName)
54
+ const migration = await migrationsResolver.findSchemaMigrationByVersion(migrationName)
56
55
  if (!migration) {
57
56
  throw `Migration ${migrationName} not found`
58
57
  }
@@ -1,11 +1,11 @@
1
- import { Command, CommandConfiguration, Input } from '@contember/cli-common'
2
- import { MigrationsContainerFactory } from '../../MigrationsContainer'
1
+ import { Command, CommandConfiguration, Input, Workspace } from '@contember/cli-common'
2
+ import { MigrationsContainerFactory } from '../../utils/migrations/MigrationsContainer'
3
3
  import { interactiveResolveInstanceEnvironmentFromInput } from '../../utils/instance'
4
4
  import { interactiveResolveApiToken } from '../../utils/tenant'
5
5
  import { SystemClient } from '../../utils/system'
6
- import { createMigrationStatusTable, getMigrationsStatus, MigrationState, sortMigrations } from '../../utils/migrations'
6
+ import { createMigrationStatusTable, MigrationState, sortMigrations } from '../../utils/migrations/migrations'
7
7
  import chalk from 'chalk'
8
- import { Workspace } from '@contember/cli-common'
8
+ import { resolveMigrationStatus } from '../../utils/migrations/MigrationExecuteHelper'
9
9
 
10
10
  type Args = {
11
11
  project?: string
@@ -66,9 +66,7 @@ export class MigrationStatusCommand extends Command<Args, Options> {
66
66
  const remoteProject = input.getOption('remote-project') || project.name
67
67
  const client = SystemClient.create(instance.baseUrl, remoteProject, apiToken)
68
68
 
69
- const executedMigrations = await client.listExecutedMigrations()
70
- let localMigrations = await container.migrationsResolver.getMigrations()
71
- let status = getMigrationsStatus(executedMigrations, localMigrations)
69
+ let status = await resolveMigrationStatus(client, container.migrationsResolver)
72
70
  const restoreMissing = input.getOption('restore-missing')
73
71
  if (restoreMissing) {
74
72
  const missing = status.errorMigrations.filter(it => it.state === MigrationState.EXECUTED_MISSING)
@@ -87,8 +85,7 @@ export class MigrationStatusCommand extends Command<Args, Options> {
87
85
  fullMigration.name,
88
86
  )
89
87
  }
90
- localMigrations = await container.migrationsResolver.getMigrations()
91
- status = getMigrationsStatus(executedMigrations, localMigrations)
88
+ status = await resolveMigrationStatus(client, container.migrationsResolver)
92
89
  }
93
90
 
94
91
  const onlyErrors = input.getOption('only-errors')
@@ -1,4 +1,5 @@
1
1
  export * from './MigrationAmendCommand'
2
+ export * from './MigrationBlankCommand'
2
3
  export * from './MigrationDescribeCommand'
3
4
  export * from './MigrationDiffCommand'
4
5
  export * from './MigrationExecuteCommand'
@@ -1,5 +1,5 @@
1
1
  import { Command, CommandConfiguration, Input, validateProjectName, Workspace } from '@contember/cli-common'
2
- import { MigrationsContainerFactory } from '../../MigrationsContainer'
2
+ import { MigrationsContainerFactory } from '../../utils/migrations/MigrationsContainer'
3
3
  import { validateSchemaAndPrintErrors } from '../../utils/schema'
4
4
  import { emptySchema } from '@contember/schema-utils'
5
5
  import { validateMigrations } from '../migrations/MigrationValidationHelper'
@@ -39,7 +39,7 @@ export class ProjectValidateCommand extends Command<Args, Options> {
39
39
  console.group(`Project ${project.name}:`)
40
40
  const migrationsDir = await project.migrationsDir
41
41
  const container = new MigrationsContainerFactory(migrationsDir).create()
42
- const migrations = await container.migrationsResolver.getMigrations()
42
+ const migrations = await container.migrationsResolver.getSchemaMigrations()
43
43
  let projectValid = await validateMigrations(
44
44
  emptySchema,
45
45
  migrations,
package/src/run.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  DeployCommand,
6
6
  InviteCommand,
7
7
  MigrationAmendCommand,
8
+ MigrationBlankCommand,
8
9
  MigrationDescribeCommand,
9
10
  MigrationDiffCommand,
10
11
  MigrationExecuteCommand,
@@ -35,6 +36,7 @@ import { checkVersions } from './utils/checkVersions';
35
36
  ['data:transfer']: () => new TransferCommand(workspace),
36
37
  ['migrations:diff']: () => new MigrationDiffCommand(workspace),
37
38
  ['migrations:amend']: () => new MigrationAmendCommand(workspace),
39
+ ['migrations:blank']: () => new MigrationBlankCommand(workspace),
38
40
  ['migrations:describe']: () => new MigrationDescribeCommand(workspace),
39
41
  ['migrations:execute']: () => new MigrationExecuteCommand(workspace),
40
42
  ['migrations:rebase']: () => new MigrationRebaseCommand(workspace),
package/src/tsconfig.json CHANGED
@@ -12,6 +12,7 @@
12
12
  { "path": "../../config-loader/src" },
13
13
  { "path": "../../database/src" },
14
14
  { "path": "../../dic/src" },
15
+ { "path": "../../engine-common/src" },
15
16
  { "path": "../../engine-content-api/src" },
16
17
  { "path": "../../schema/src" },
17
18
  { "path": "../../schema-migrations/src" },
@@ -1,4 +1,3 @@
1
- import fetch, { Response } from 'node-fetch'
2
1
  import { readdir, readFile } from 'node:fs/promises'
3
2
  import { maskToken } from './token'
4
3
 
@@ -0,0 +1,14 @@
1
+ import * as esbuild from 'esbuild'
2
+
3
+ export const buildJs = async (path: string): Promise<string> => {
4
+ const response = await esbuild.build({
5
+ entryPoints: [path],
6
+ platform: 'node',
7
+ bundle: true,
8
+ treeShaking: true,
9
+ write: false,
10
+ loader: { '.sql': 'text' },
11
+ external: ['@contember/admin', 'react', 'react/jsx-runtime'],
12
+ })
13
+ return response.outputFiles[0].text
14
+ }
@@ -0,0 +1,37 @@
1
+ import { MigrationFileLoader } from './MigrationFileLoader'
2
+ import { buildJs } from '../esbuild'
3
+ import { MigrationParser } from './MigrationParser'
4
+ import { ContentMigration, MigrationContent, MigrationFile } from './MigrationFile'
5
+
6
+ export class JsLoader implements MigrationFileLoader {
7
+ constructor(
8
+ private readonly migrationParser: MigrationParser,
9
+ ) {
10
+ }
11
+
12
+
13
+ public async load(file: MigrationFile): Promise<MigrationContent> {
14
+ const code = await buildJs(file.path)
15
+ const fn = new Function(`var module = {}; ((module) => { ${code} })(module); return module`)
16
+ const exports = fn().exports
17
+ if (!('default' in exports) && !('query' in exports) && !('queries' in exports)) {
18
+ throw `export "default" or "query" is required in ${file.path}`
19
+ }
20
+ if (typeof exports.default !== 'function') {
21
+ return this.migrationParser.parse(file, exports.default || exports)
22
+ }
23
+
24
+ return {
25
+ type: 'factory',
26
+ factory: async (): Promise<ContentMigration> => {
27
+ const result = await exports.default()
28
+ const migration = this.migrationParser.parse(file, result ?? { queries: [] })
29
+ if (migration.type !== 'content') {
30
+ throw new Error()
31
+ }
32
+ return migration
33
+ },
34
+
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,18 @@
1
+ import { MigrationFileLoader } from './MigrationFileLoader'
2
+ import fs from 'node:fs/promises'
3
+ import { MigrationParser } from './MigrationParser'
4
+ import { MigrationFile } from './MigrationFile'
5
+
6
+ export class JsonLoader implements MigrationFileLoader {
7
+ constructor(
8
+ private readonly migrationParser: MigrationParser,
9
+ ) {
10
+ }
11
+
12
+ public async load(file: MigrationFile) {
13
+ return this.migrationParser.parse(
14
+ file,
15
+ JSON.parse(await fs.readFile(file.path, { encoding: 'utf8' })),
16
+ )
17
+ }
18
+ }
@@ -1,14 +1,10 @@
1
- import { CommandConfiguration, Input } from '@contember/cli-common'
2
- import { MigrationsContainerFactory } from '../../MigrationsContainer'
3
- import {
4
- MigrationCreator,
5
- MigrationDescriber,
6
- MigrationFilesManager,
7
- MigrationsResolver,
8
- SchemaMigrator,
9
- SchemaVersionBuilder,
10
- } from '@contember/schema-migrations'
11
- import { Workspace, Project } from '@contember/cli-common'
1
+ import { Input, Project, Workspace } from '@contember/cli-common'
2
+ import { MigrationsContainerFactory } from './MigrationsContainer'
3
+ import { MigrationDescriber, SchemaMigrator } from '@contember/schema-migrations'
4
+ import { MigrationCreator } from './MigrationCreator'
5
+ import { MigrationsResolver } from './MigrationsResolver'
6
+ import { SchemaVersionBuilder } from './SchemaVersionBuilder'
7
+ import { MigrationFilesManager } from './MigrationFilesManager'
12
8
 
13
9
  type Args = {
14
10
  project?: string
@@ -0,0 +1,60 @@
1
+ import { MigrationFilesManager } from './MigrationFilesManager'
2
+ import { Schema } from '@contember/schema'
3
+ import { Migration, SchemaDiffer, VERSION_LATEST } from '@contember/schema-migrations'
4
+ import { MigrationVersionHelper } from '@contember/engine-common'
5
+
6
+ export class MigrationCreator {
7
+ constructor(
8
+ private readonly migrationFilesManager: MigrationFilesManager,
9
+ private readonly schemaDiffer: SchemaDiffer,
10
+ private readonly emptyTemplates: Record<string, string> = {},
11
+ ) {}
12
+
13
+ async createEmptyMigrationFile(migrationName: string, format: string): Promise<string> {
14
+ if (!this.emptyTemplates.hasOwnProperty(format)) {
15
+ throw new Error(`Unknown format ${format}`)
16
+ }
17
+ await this.migrationFilesManager.createDirIfNotExist()
18
+ const [, fullName] = MigrationVersionHelper.createVersion(migrationName)
19
+ const filename = await this.migrationFilesManager.createFile(this.emptyTemplates[format], fullName, format)
20
+
21
+ return filename
22
+ }
23
+
24
+ async prepareMigration(
25
+ initialSchema: Schema,
26
+ newSchema: Schema,
27
+ migrationName: string,
28
+ { skipInitialSchemaValidation = false }: { skipInitialSchemaValidation?: boolean } = {},
29
+ ): Promise<{ migration: Migration; initialSchema: Schema } | null> {
30
+ await this.migrationFilesManager.createDirIfNotExist()
31
+
32
+ const modifications = this.schemaDiffer.diffSchemas(initialSchema, newSchema, { skipInitialSchemaValidation })
33
+ if (modifications.length === 0) {
34
+ return null
35
+ }
36
+
37
+ const migration = this.createMigration(modifications, migrationName)
38
+
39
+ return { initialSchema, migration }
40
+ }
41
+
42
+ async saveMigration(migration: Migration): Promise<string> {
43
+ const jsonDiff = MigrationCreator.createContent(migration)
44
+ const filename = await this.migrationFilesManager.createFile(jsonDiff, migration.name)
45
+ return filename
46
+ }
47
+
48
+ async removeMigration(name: string): Promise<void> {
49
+ await this.migrationFilesManager.removeFile(name)
50
+ }
51
+
52
+ private createMigration(modifications: Migration.Modification[], name: string): Migration {
53
+ const [version, fullName] = MigrationVersionHelper.createVersion(name)
54
+ return { formatVersion: VERSION_LATEST, modifications, version, name: fullName }
55
+ }
56
+
57
+ public static createContent({ modifications, formatVersion }: Migration): string {
58
+ return JSON.stringify({ formatVersion, modifications }, undefined, '\t') + '\n'
59
+ }
60
+ }
@@ -1,8 +1,12 @@
1
1
  import { CommandConfiguration } from '@contember/cli-common'
2
- import { SystemClient } from '../../utils/system'
3
- import { getMigrationsStatus, MigrationToExecuteOkStatus, printMigrationDescription } from '../../utils/migrations'
4
- import { MigrationDescriber, MigrationsResolver, SchemaVersionBuilder } from '@contember/schema-migrations'
2
+ import { SystemClient } from '../system'
3
+ import { getMigrationsStatus, MigrationToExecuteOkStatus, printMigrationDescription } from './migrations'
4
+ import { MigrationDescriber } from '@contember/schema-migrations'
5
5
  import prompts from 'prompts'
6
+ import { MigrationsResolver } from './MigrationsResolver'
7
+ import { SchemaVersionBuilder } from './SchemaVersionBuilder'
8
+ import { ContentMigrationFactoryArgs, isSchemaMigration, ResolvedMigrationContent } from './MigrationFile'
9
+ import { assertNever } from '../assertNever'
6
10
 
7
11
  export type ExecuteMigrationOptions = {
8
12
  instance?: string
@@ -31,7 +35,7 @@ export const resolveMigrationStatus = async (
31
35
  force: boolean = false,
32
36
  ): Promise<ReturnType<typeof getMigrationsStatus>> => {
33
37
  const executedMigrations = await client.listExecutedMigrations()
34
- const localMigrations = await migrationsResolver.getMigrations()
38
+ const localMigrations = await migrationsResolver.getMigrationFiles()
35
39
  return getMigrationsStatus(executedMigrations, localMigrations, force)
36
40
  }
37
41
 
@@ -41,6 +45,7 @@ export const executeMigrations = async ({
41
45
  requireConfirmation,
42
46
  schemaVersionBuilder,
43
47
  migrationDescriber,
48
+ contentMigrationFactoryArgs,
44
49
  force,
45
50
  }: {
46
51
  client: SystemClient
@@ -48,6 +53,7 @@ export const executeMigrations = async ({
48
53
  requireConfirmation: boolean
49
54
  schemaVersionBuilder: SchemaVersionBuilder
50
55
  migrationDescriber: MigrationDescriber
56
+ contentMigrationFactoryArgs: ContentMigrationFactoryArgs
51
57
  force?: boolean
52
58
  }): Promise<number> => {
53
59
  if (migrations.length === 0) {
@@ -73,7 +79,12 @@ export const executeMigrations = async ({
73
79
  if (action === 'describe') {
74
80
  const schema = await schemaVersionBuilder.buildSchemaUntil(migrations[0].version)
75
81
  for (const migration of migrations) {
76
- await printMigrationDescription(migrationDescriber, schema, migration.localMigration, {
82
+ const migrationContent = await migration.localMigration.getContent()
83
+ if (!isSchemaMigration(migrationContent)) {
84
+ continue
85
+ }
86
+
87
+ await printMigrationDescription(migrationDescriber, schema, migrationContent, {
77
88
  noSql: true,
78
89
  })
79
90
  }
@@ -85,10 +96,58 @@ export const executeMigrations = async ({
85
96
  } while (true)
86
97
  }
87
98
 
88
- await client.migrate(
89
- migrations.map(it => it.localMigration),
90
- force,
91
- )
99
+ let migrationsToRun: ResolvedMigrationContent[] = []
100
+
101
+ const executeMigrations = async () => {
102
+ if (migrationsToRun.length === 0) {
103
+ return
104
+ }
105
+ await client.migrate(
106
+ migrationsToRun.map(it => {
107
+ if (it.type === 'schema') {
108
+ return {
109
+ version: it.version,
110
+ name: it.name,
111
+ type: 'SCHEMA',
112
+ schemaMigration: {
113
+ formatVersion: it.formatVersion,
114
+ modifications: it.modifications,
115
+ skippedErrors: it.skippedErrors,
116
+ },
117
+ }
118
+ }
119
+ if (it.type === 'content') {
120
+ return {
121
+ version: it.version,
122
+ name: it.name,
123
+ type: 'CONTENT',
124
+ contentMigration: it.queries,
125
+ }
126
+ }
127
+ return assertNever(it)
128
+ }),
129
+ force,
130
+ )
131
+ migrationsToRun.forEach(it => {
132
+ console.log(it.name)
133
+ })
134
+ migrationsToRun = []
135
+ }
136
+
137
+ console.log('Executing...')
138
+
139
+ for (const migration of migrations) {
140
+ const migrationContent = await migration.localMigration.getContent()
141
+ if (migrationContent.type === 'factory') {
142
+ await executeMigrations()
143
+ const result = await migrationContent.factory(contentMigrationFactoryArgs)
144
+ migrationsToRun.push(result)
145
+ await executeMigrations()
146
+ } else {
147
+ migrationsToRun.push(migrationContent)
148
+ }
149
+ }
150
+ await executeMigrations()
92
151
  console.log('Migration executed')
93
152
  return 0
94
153
  }
@@ -0,0 +1,52 @@
1
+ import { Migration } from '@contember/schema-migrations'
2
+ import { JSONObject } from '@contember/cli-common'
3
+
4
+ export interface MigrationFile {
5
+ path: string
6
+ version: string
7
+ name: string
8
+
9
+ getContent: () => Promise<MigrationContent>
10
+ }
11
+
12
+ export const isSchemaMigration = (content: MigrationContent): content is SchemaMigration => typeof content !== 'function' && 'modifications' in content
13
+
14
+ export type ResolvedMigrationContent =
15
+ | SchemaMigration
16
+ | ContentMigration
17
+
18
+ export type MigrationContent =
19
+ | ResolvedMigrationContent
20
+ | ContentMigrationFactory
21
+
22
+ export type SchemaMigration =
23
+ & {
24
+ type: 'schema'
25
+ }
26
+ & Migration
27
+
28
+ export type ContentMigration = {
29
+ type: 'content'
30
+ version: string
31
+ name: string
32
+ queries: ContentMigrationQuery[]
33
+ }
34
+
35
+ export type ContentMigrationFactoryArgs = {
36
+ apiBaseUrl: string
37
+ projectName: string
38
+ apiToken: string
39
+ }
40
+
41
+ export type ContentMigrationFactory = {
42
+ type: 'factory'
43
+ factory: (args: ContentMigrationFactoryArgs) => Promise<ContentMigration>
44
+ }
45
+
46
+ export type ContentMigrationQuery = {
47
+ query: string
48
+ variables?: JSONObject
49
+ stage?: string
50
+ checkMutationResult?: boolean
51
+ }
52
+
@@ -0,0 +1,5 @@
1
+ import { MigrationContent, MigrationFile } from './MigrationFile'
2
+
3
+ export interface MigrationFileLoader {
4
+ load(file: MigrationFile): Promise<MigrationContent>
5
+ }
@@ -0,0 +1,102 @@
1
+ import { MigrationVersionHelper } from '@contember/engine-common'
2
+ import * as fs from 'node:fs/promises'
3
+ import { MigrationFileLoader } from './MigrationFileLoader'
4
+ import { MigrationContent, MigrationFile } from './MigrationFile'
5
+ import { join } from 'node:path'
6
+ import { Dirent } from 'node:fs'
7
+
8
+ export class MigrationFilesManager {
9
+ private migrations: null | Promise<MigrationFile[]> = null
10
+
11
+ constructor(
12
+ public readonly directory: string,
13
+ private readonly loaders: Record<string, MigrationFileLoader>,
14
+ ) {}
15
+
16
+ public async createFile(content: string, name: string, extension = 'json'): Promise<string> {
17
+ this.migrations = null
18
+ const path = this.formatPath(name, extension)
19
+ await fs.writeFile(path, content, { encoding: 'utf8' })
20
+ return await fs.realpath(path)
21
+ }
22
+
23
+ public async removeFile(name: string) {
24
+ this.migrations = null
25
+ const path = this.formatPath(name)
26
+ await fs.unlink(path)
27
+ }
28
+
29
+ public async moveFile(oldName: string, newName: string) {
30
+ this.migrations = null
31
+ await fs.rename(this.formatPath(oldName), this.formatPath(newName))
32
+ }
33
+
34
+ public async createDirIfNotExist(): Promise<void> {
35
+ try {
36
+ await fs.mkdir(this.directory)
37
+ } catch (e) {
38
+ if (!(e instanceof Error) || !('code' in e) || (e as any).code !== 'EEXIST') {
39
+ throw e
40
+ }
41
+ }
42
+ }
43
+
44
+ public async readFiles(): Promise<MigrationFile[]> {
45
+ return this.migrations ??= this.forceReadFiles()
46
+ }
47
+
48
+ public async forceReadFiles(): Promise<MigrationFile[]> {
49
+ const files = (await this.listFiles()).map(it => ({
50
+ filename: it,
51
+ version: MigrationVersionHelper.extractVersion(it),
52
+ }))
53
+
54
+ return files.map(({ version, filename }): MigrationFile => {
55
+ const ext = filename.substring(filename.lastIndexOf('.') + 1)
56
+ const path = join(this.directory, filename)
57
+ let content: Promise<MigrationContent> | null = null
58
+ const file: MigrationFile = {
59
+ path: path,
60
+ version: version,
61
+ name: MigrationVersionHelper.extractName(path.substring(path.lastIndexOf('/') + 1)),
62
+ getContent: () => {
63
+ content ??= this.loaders[ext].load(file)
64
+ return content
65
+ },
66
+ }
67
+ return file
68
+ })
69
+ }
70
+
71
+ private async tryReadDir(): Promise<Dirent[]> {
72
+ try {
73
+ return await fs.readdir(this.directory, { withFileTypes: true })
74
+ } catch (e) {
75
+ if (e instanceof Error && 'code' in e && (e as any).code === 'ENOENT') {
76
+ return []
77
+ }
78
+ throw e
79
+ }
80
+ }
81
+
82
+ private async listFiles(): Promise<string[]> {
83
+ const files = await this.tryReadDir()
84
+
85
+ const extensions = Object.keys(this.loaders).join('|')
86
+ const regex = new RegExp('^\\d{4}-\\d{2}-\\d{2}-\\d{6}-[\\w-]+\\.(' + extensions + ')$')
87
+ const filteredFiles = await Promise.all(
88
+ files
89
+ .filter(it => it.isFile())
90
+ .map(it => it.name)
91
+ .filter(it => it.match(regex)),
92
+ )
93
+
94
+ return filteredFiles.sort()
95
+ }
96
+
97
+ private formatPath(version: string, extension = 'json'): string {
98
+ const filename = `${version}.${extension}`
99
+ const path = `${this.directory}/${filename}`
100
+ return path
101
+ }
102
+ }
@@ -0,0 +1,32 @@
1
+ import { JSONValue } from '@contember/cli-common'
2
+ import { Migration, VERSION_INITIAL } from '@contember/schema-migrations'
3
+ import { ContentMigrationQuery, MigrationFile, ResolvedMigrationContent } from './MigrationFile'
4
+
5
+ export class MigrationParser {
6
+ public parse(migrationFile: MigrationFile, content: JSONValue): ResolvedMigrationContent {
7
+ if (typeof content !== 'object' || content === null) {
8
+ throw new Error('Invalid migration')
9
+ }
10
+
11
+ if ('modifications' in content) {
12
+ const validated = content as Migration // todo validate
13
+
14
+ return {
15
+ type: 'schema',
16
+ version: migrationFile.version,
17
+ name: migrationFile.name,
18
+ formatVersion: validated.formatVersion || VERSION_INITIAL,
19
+ modifications: validated.modifications,
20
+ ...(validated.skippedErrors ? { skippedErrors: validated.skippedErrors } : {}),
21
+ }
22
+ } else if ('queries' in content || 'query' in content) {
23
+ return {
24
+ type: 'content',
25
+ version: migrationFile.version,
26
+ name: migrationFile.name,
27
+ queries: ('queries' in content ? content.queries : [content]) as ContentMigrationQuery[],
28
+ }
29
+ }
30
+ throw new Error('Invalid migration content')
31
+ }
32
+ }