@axinom/mosaic-cli 0.14.2-rc.8 → 0.15.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/package.json +6 -5
  2. package/src/cli/README.md +60 -0
  3. package/src/cli/index.ts +47 -0
  4. package/src/commands/apply-templates/apply-templates.spec.ts +623 -0
  5. package/src/commands/apply-templates/apply-templates.ts +494 -0
  6. package/src/commands/apply-templates/bitwarden-vault.ts +130 -0
  7. package/src/commands/apply-templates/index.ts +1 -0
  8. package/src/commands/create-extension-config/create-extension-config.ts +92 -0
  9. package/src/commands/create-extension-config/index.ts +23 -0
  10. package/src/commands/get-access-token/get-access-token-options.ts +9 -0
  11. package/src/commands/get-access-token/get-dev-access-token.ts +32 -0
  12. package/src/commands/get-access-token/index.ts +66 -0
  13. package/src/commands/graphql-diff.ts +143 -0
  14. package/src/commands/msg-codegen/codegen.ts +891 -0
  15. package/src/commands/msg-codegen/index.ts +48 -0
  16. package/src/commands/msg-codegen/lint.ts +84 -0
  17. package/src/commands/msg-codegen/message-codegen-options.ts +7 -0
  18. package/src/commands/msg-diff/asyncapi-override.ts +31 -0
  19. package/src/commands/msg-diff/git-checkout-tmp.ts +73 -0
  20. package/src/commands/msg-diff/index.ts +53 -0
  21. package/src/commands/msg-diff/message-diff-options.ts +7 -0
  22. package/src/commands/msg-diff/msg-diff.spec.ts +412 -0
  23. package/src/commands/msg-diff/msg-diff.ts +364 -0
  24. package/src/commands/msg-diff/test-resources/0/1-asyncapi.yml +38 -0
  25. package/src/commands/msg-diff/test-resources/0/2-asyncapi.yml +36 -0
  26. package/src/commands/msg-diff/test-resources/0/command.json +74 -0
  27. package/src/commands/msg-diff/test-resources/0/event.json +25 -0
  28. package/src/commands/msg-diff/test-resources/1/1-asyncapi.yml +25 -0
  29. package/src/commands/msg-diff/test-resources/1/moved-event.json +25 -0
  30. package/src/commands/msg-diff/test-resources/common.json +20 -0
  31. package/src/commands/pg-dump/README.md +21 -0
  32. package/src/commands/pg-dump/generate.ts +146 -0
  33. package/src/commands/pg-dump/index.ts +39 -0
  34. package/src/commands/pg-dump/pg-dump-options.ts +6 -0
  35. package/src/commands/publish-schema-to-db/README.md +130 -0
  36. package/src/commands/publish-schema-to-db/abstractions/base-smart-tags.ts +6 -0
  37. package/src/commands/publish-schema-to-db/abstractions/index.ts +5 -0
  38. package/src/commands/publish-schema-to-db/abstractions/pg-column.ts +31 -0
  39. package/src/commands/publish-schema-to-db/abstractions/pg-fk-column.ts +6 -0
  40. package/src/commands/publish-schema-to-db/abstractions/pg-table.ts +55 -0
  41. package/src/commands/publish-schema-to-db/abstractions/pg-type.ts +8 -0
  42. package/src/commands/publish-schema-to-db/content-entity-model.ts +93 -0
  43. package/src/commands/publish-schema-to-db/generate.ts +82 -0
  44. package/src/commands/publish-schema-to-db/index.ts +49 -0
  45. package/src/commands/publish-schema-to-db/jest.config.js +9 -0
  46. package/src/commands/publish-schema-to-db/pg-models/columns/fk-column.spec.ts +42 -0
  47. package/src/commands/publish-schema-to-db/pg-models/columns/fk-column.ts +41 -0
  48. package/src/commands/publish-schema-to-db/pg-models/columns/index.ts +4 -0
  49. package/src/commands/publish-schema-to-db/pg-models/columns/pk-column.spec.ts +47 -0
  50. package/src/commands/publish-schema-to-db/pg-models/columns/pk-column.ts +34 -0
  51. package/src/commands/publish-schema-to-db/pg-models/columns/primitive-column.spec.ts +65 -0
  52. package/src/commands/publish-schema-to-db/pg-models/columns/primitive-column.ts +62 -0
  53. package/src/commands/publish-schema-to-db/pg-models/columns/virtual-fk-column.spec.ts +24 -0
  54. package/src/commands/publish-schema-to-db/pg-models/columns/virtual-fk-column.ts +34 -0
  55. package/src/commands/publish-schema-to-db/pg-models/json-schema-parse-utils.spec.ts +182 -0
  56. package/src/commands/publish-schema-to-db/pg-models/json-schema-parse-utils.ts +166 -0
  57. package/src/commands/publish-schema-to-db/pg-models/pg-sql-gen-utils.spec.ts +19 -0
  58. package/src/commands/publish-schema-to-db/pg-models/pg-sql-gen-utils.ts +237 -0
  59. package/src/commands/publish-schema-to-db/pg-models/pgl-utils.spec.ts +19 -0
  60. package/src/commands/publish-schema-to-db/pg-models/pgl-utils.ts +115 -0
  61. package/src/commands/publish-schema-to-db/pg-models/tables/content-entity-table.ts +104 -0
  62. package/src/commands/publish-schema-to-db/pg-models/tables/index.ts +3 -0
  63. package/src/commands/publish-schema-to-db/pg-models/tables/object-property-table.ts +113 -0
  64. package/src/commands/publish-schema-to-db/pg-models/tables/relations-table.ts +115 -0
  65. package/src/commands/publish-schema-to-db/postprocessors/collection-postprocessor.ts +33 -0
  66. package/src/commands/publish-schema-to-db/postprocessors/content-entity-model-postprocessor.ts +13 -0
  67. package/src/commands/publish-schema-to-db/postprocessors/episode-postprocessor.ts +37 -0
  68. package/src/commands/publish-schema-to-db/postprocessors/index.ts +6 -0
  69. package/src/commands/publish-schema-to-db/postprocessors/movie-postprocessor.ts +30 -0
  70. package/src/commands/publish-schema-to-db/postprocessors/postprocessing-utils.ts +21 -0
  71. package/src/commands/publish-schema-to-db/postprocessors/season-postprocessor.ts +37 -0
  72. package/src/commands/publish-schema-to-db/postprocessors/tvshow-postprocessor.ts +30 -0
  73. package/src/commands/publish-schema-to-db/publish-schema-to-db-options.ts +15 -0
  74. package/src/commands/publish-schema-to-db/types/sql-formatter.d.ts +10 -0
  75. package/src/exports.ts +2 -0
  76. package/src/index.ts +1 -0
@@ -0,0 +1,92 @@
1
+ import { exec } from 'child_process';
2
+ import * as findNearestFile from 'find-nearest-file';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { promisify } from 'util';
6
+ const execAsync = promisify(exec);
7
+
8
+ /**
9
+ * Regex to determine version of yarn installed in project: Classic, or Modern.
10
+ * Required to correctly build yarn command to retrieve list of installed packages.
11
+ */
12
+ const yarnClassicVersionRegex = /^1\..*$/gim;
13
+
14
+ /**
15
+ * Regex for Mosaic libraries.
16
+ */
17
+ const mosaicLibraryRegex =
18
+ /(?<prefix>@axinom\/mosaic-)(?<library>[-A-Za-z0-9]+)/gim;
19
+
20
+ /**
21
+ * Find all Mosaic packages used in project.
22
+ * @returns list of installed packages.
23
+ */
24
+ const findInstalledPackages = async (): Promise<string[]> => {
25
+ let installedMosaicLibs: string[] = [];
26
+ const yarnVersionResult = await execAsync('yarn -v');
27
+
28
+ if (yarnVersionResult.stderr) {
29
+ console.log(yarnVersionResult.stderr);
30
+ }
31
+
32
+ if (yarnVersionResult.stdout) {
33
+ const packagesListCommand = yarnClassicVersionRegex.test(
34
+ yarnVersionResult.stdout,
35
+ )
36
+ ? 'yarn list --pattern @axinom/mosaic-* --depth=0'
37
+ : 'yarn info @axinom/mosaic-* --recursive --all --name-only';
38
+
39
+ const packagesListResult = await execAsync(packagesListCommand);
40
+ if (packagesListResult.stdout) {
41
+ installedMosaicLibs = [
42
+ ...new Set(packagesListResult.stdout.match(mosaicLibraryRegex) ?? []),
43
+ ];
44
+ }
45
+ if (packagesListResult.stderr) {
46
+ console.log(packagesListResult.stderr);
47
+ }
48
+ }
49
+ return installedMosaicLibs;
50
+ };
51
+
52
+ /**
53
+ * Provides path to '.mosaic' configuration file.
54
+ * Path to file provided based on the location of `yarn.lock`.
55
+ */
56
+ export const getExtensionConfigPath = (): string => {
57
+ const yarnLockFilePath = findNearestFile('yarn.lock');
58
+ if (yarnLockFilePath) {
59
+ return path.resolve(path.dirname(yarnLockFilePath), '.mosaic');
60
+ }
61
+ return path.resolve('.mosaic');
62
+ };
63
+
64
+ /**
65
+ * Creates `.mosaic` configuration file with list of libraries, that provide extension for Mosaic CLI.
66
+ */
67
+ export const createExtensionConfig = async (): Promise<void> => {
68
+ console.log(`Starting extension configuration generation!`);
69
+ const installedMosaicLibs = await findInstalledPackages();
70
+ const extensions: string[] = [];
71
+ await Promise.all(
72
+ installedMosaicLibs.map(async (lib: string) => {
73
+ //filter libraries, that have cli extension
74
+ try {
75
+ const result = await import(lib);
76
+ if (result.cliExtension) {
77
+ extensions.push(lib);
78
+ }
79
+ } catch (error) {
80
+ //mosaic libraries, that are always(?) failing to load:
81
+ //all `@axinom/mosaic-*-workflows`, @axinom/mosaic-portal, @axinom/mosaic-ui, @axinom/mosaic-home, @axinom/mosaic-id-link
82
+ //todo: extend regex to exclude libraries?
83
+ }
84
+ }),
85
+ );
86
+ console.log(`Modular CLI extensions: ${extensions}`);
87
+ //save library names, that can be used in command line
88
+ const configFilePath = getExtensionConfigPath();
89
+ const config = `CLI_EXTENSIONS=${extensions.join(',')}`;
90
+ fs.writeFileSync(configFilePath, config);
91
+ console.log(`Success! Extensions configuration file path: ${configFilePath}`);
92
+ };
@@ -0,0 +1,23 @@
1
+ import * as dotenv from 'dotenv';
2
+ import { CommandModule } from 'yargs';
3
+ import {
4
+ createExtensionConfig,
5
+ getExtensionConfigPath,
6
+ } from './create-extension-config';
7
+
8
+ export const createExtensionConfigCommand: CommandModule<unknown, undefined> = {
9
+ command: 'create-extension-config',
10
+ describe:
11
+ 'Creates Mosaic CLI configuration file, that allows to access commands from all installed Mosaic libraries through `mosaic` script.',
12
+ handler: createExtensionConfig,
13
+ };
14
+
15
+ export const getExtensions = (): string[] => {
16
+ dotenv.config({
17
+ path: getExtensionConfigPath(),
18
+ });
19
+ if (process.env.CLI_EXTENSIONS) {
20
+ return process.env.CLI_EXTENSIONS.split(',');
21
+ }
22
+ return [];
23
+ };
@@ -0,0 +1,9 @@
1
+ export interface GetAccessTokenOptions {
2
+ authEndpoint: string;
3
+ clientId: string;
4
+ clientSecret: string;
5
+ permissionsFile: string;
6
+ userEmail?: string;
7
+ expiration?: number;
8
+ failOnUnknownPermission?: boolean;
9
+ }
@@ -0,0 +1,32 @@
1
+ import {
2
+ devGenerateUserAccessTokenWithPermissions,
3
+ getServiceAccountToken,
4
+ TokenResult,
5
+ } from '@axinom/mosaic-id-link-be';
6
+ import { readFileSync } from 'fs';
7
+ import { GetAccessTokenOptions } from './get-access-token-options';
8
+
9
+ /**
10
+ * Returns a development ID JWT token based on passed options.
11
+ */
12
+ export const getDevAccessToken = async (
13
+ options: GetAccessTokenOptions,
14
+ ): Promise<TokenResult> => {
15
+ const permissions = JSON.parse(
16
+ readFileSync(options.permissionsFile, 'utf-8'),
17
+ );
18
+
19
+ const serviceAccountToken = await getServiceAccountToken(
20
+ options.authEndpoint,
21
+ options.clientId,
22
+ options.clientSecret,
23
+ );
24
+ return devGenerateUserAccessTokenWithPermissions(
25
+ options.authEndpoint,
26
+ serviceAccountToken.accessToken,
27
+ permissions,
28
+ options.userEmail,
29
+ options.expiration,
30
+ options.failOnUnknownPermission,
31
+ );
32
+ };
@@ -0,0 +1,66 @@
1
+ import { assertError } from '@axinom/mosaic-service-common';
2
+ import * as chalk from 'chalk';
3
+ import { CommandModule } from 'yargs';
4
+ import { GetAccessTokenOptions } from './get-access-token-options';
5
+ import { getDevAccessToken } from './get-dev-access-token';
6
+
7
+ export const getAccessToken: CommandModule<unknown, GetAccessTokenOptions> = {
8
+ command: 'get-access-token',
9
+ describe: 'Requests a long living access token for development purposes',
10
+ builder: (yargs) =>
11
+ yargs
12
+ .option('permissionsFile', {
13
+ alias: 'f',
14
+ describe:
15
+ 'A JSON file containing the wanted permission structure.\nJSON Format:\n{"serviceId" : string, "permissions": string[]}[]',
16
+ string: true,
17
+ demandOption: true,
18
+ })
19
+ .option('clientId', {
20
+ alias: ['i', 'devServiceAccountClientId'],
21
+ describe:
22
+ "The service account client id.\nRequired permission:\nID SERVICE -> 'Dev Generate User Access Token With Permissions'",
23
+ string: true,
24
+ demandOption: true,
25
+ })
26
+ .option('clientSecret', {
27
+ alias: ['s', 'devServiceAccountClientSecret'],
28
+ describe: 'The service account client secret.',
29
+ string: true,
30
+ demandOption: true,
31
+ })
32
+ .option('authEndpoint', {
33
+ alias: ['a', 'idServiceAuthBaseUrl'],
34
+ describe: 'The authentication service endpoint url.',
35
+ string: true,
36
+ demandOption: true,
37
+ })
38
+ .option('userEmail', {
39
+ alias: ['e'],
40
+ describe:
41
+ 'The email of a user that exists on the environment. If unspecified, a pseudo-user will be used to generate the token for.',
42
+ string: true,
43
+ })
44
+ .option('failOnUnknownPermission', {
45
+ alias: 'u',
46
+ describe: 'Fails if an unknown permission is requested.',
47
+ boolean: true,
48
+ default: false,
49
+ })
50
+ .option('expiration', {
51
+ alias: 'x',
52
+ describe: 'Token expiration time in seconds.',
53
+ number: true,
54
+ default: 2592000,
55
+ }),
56
+ handler: async (options: GetAccessTokenOptions): Promise<void> => {
57
+ try {
58
+ const result = await getDevAccessToken(options);
59
+
60
+ console.log(chalk.greenBright(result.accessToken));
61
+ } catch (err) {
62
+ assertError(err);
63
+ console.log(chalk.red('Command failed:'), err.message);
64
+ }
65
+ },
66
+ };
@@ -0,0 +1,143 @@
1
+ /** Script to check graphql schemas for breaking changes
2
+ * Uses: https://www.npmjs.com/package/@graphql-inspector/core
3
+ * The associated CLI tool (https://www.npmjs.com/package/@graphql-inspector/cli)
4
+ * is simple to use but inflexible for our use case.
5
+ */
6
+ /* eslint-disable no-console */
7
+ /* eslint-disable @typescript-eslint/no-explicit-any */
8
+ import { CriticalityLevel, diff } from '@graphql-inspector/core';
9
+ import { green, red } from 'chalk';
10
+ import { execSync } from 'child_process';
11
+ import * as fs from 'fs';
12
+ import { buildSchema, GraphQLSchema } from 'graphql';
13
+ import * as path from 'path';
14
+ import { CommandModule } from 'yargs';
15
+
16
+ interface GraphqlDiffOptions {
17
+ files: (string | number)[];
18
+ branch: string;
19
+ verbose: boolean;
20
+ }
21
+
22
+ export const graphqlDiff: CommandModule<unknown, GraphqlDiffOptions> = {
23
+ command: 'graphql-diff',
24
+ describe: 'Compares graphql schema(s) with git for breaking changes.',
25
+ builder: (yargs) =>
26
+ yargs
27
+ .options('files', {
28
+ alias: 'f',
29
+ describe:
30
+ 'The .graphql file(s) to check. Each will be checked against git.',
31
+ type: 'array',
32
+ demand: true,
33
+ })
34
+ .option('branch', {
35
+ alias: 'b',
36
+ description: 'Git branch or commit identifier to compare to.',
37
+ type: 'string',
38
+ default: 'origin/master',
39
+ })
40
+ .option('verbose', {
41
+ alias: 'v',
42
+ description: 'Verbose output including non-breaking changes',
43
+ type: 'boolean',
44
+ default: false,
45
+ }),
46
+ handler: async (argv) => {
47
+ let totalBreaking = 0;
48
+ try {
49
+ for (const file of argv.files) {
50
+ totalBreaking += await gqlDiff(
51
+ file.toString(),
52
+ argv.branch,
53
+ argv.verbose,
54
+ );
55
+ }
56
+ } catch (e) {
57
+ console.log(red(e));
58
+ process.exit(1);
59
+ }
60
+ if (totalBreaking > 0) {
61
+ process.exit(1);
62
+ }
63
+ },
64
+ };
65
+
66
+ async function gqlDiff(
67
+ file: string,
68
+ branch: string,
69
+ verbose: boolean,
70
+ ): Promise<number> {
71
+ const root = path.dirname(__dirname);
72
+ const relPath = path.relative(root, file);
73
+ console.log(`Comparing schema '${relPath}' with branch '${branch}':`);
74
+
75
+ let schema1: GraphQLSchema | undefined = undefined;
76
+ let schema2: GraphQLSchema | undefined = undefined;
77
+
78
+ // load schemas
79
+ try {
80
+ const buffer = await fs.promises.readFile(file);
81
+ schema2 = buildSchema(buffer.toString());
82
+ } catch (e: any) {
83
+ throw `Error reading ${file}: ${e.toString()}`;
84
+ }
85
+ try {
86
+ const text = readFileFromGit(branch, path.resolve(file));
87
+ schema1 = buildSchema(text);
88
+ } catch (e: any) {
89
+ if (e.toString().includes(`exists on disk, but not in '${branch}'`)) {
90
+ console.log(e.toString());
91
+ console.log(
92
+ `WARNING: Check will be skipped on the assumption that this is a new schema.`,
93
+ );
94
+ return 0;
95
+ }
96
+ throw `Error reading ${file} from branch ${branch}: ${e}`;
97
+ }
98
+
99
+ // generate diff
100
+ const diffResult = await diff(
101
+ schema1 as GraphQLSchema,
102
+ schema2 as GraphQLSchema,
103
+ );
104
+ const breaking = diffResult.filter(
105
+ (r) => r.criticality.level === CriticalityLevel.Breaking,
106
+ );
107
+
108
+ // log results
109
+ if (breaking.length > 0 || verbose) {
110
+ for (const item of diffResult) {
111
+ if (item.criticality.level === CriticalityLevel.Breaking) {
112
+ console.log(` ✖ ${item.message}`);
113
+ } else {
114
+ console.log(` ✔ ${item.message}`);
115
+ }
116
+ }
117
+ }
118
+ const color = breaking.length > 0 ? red : green;
119
+ console.log(
120
+ color(
121
+ `${breaking.length > 0 ? '❌ ' : ''}${breaking.length} breaking change${
122
+ breaking.length === 1 ? '' : 's'
123
+ }`,
124
+ ),
125
+ );
126
+
127
+ return breaking.length;
128
+ }
129
+
130
+ function readFileFromGit(branch: string, file: string): string {
131
+ const root = execSync(
132
+ `git -C ${path
133
+ .dirname(file)
134
+ .replace(/\\/g, '/')} rev-parse --show-toplevel`,
135
+ )
136
+ .toString()
137
+ .trim();
138
+ return execSync(
139
+ `git -C ${root} show ${branch}:${path
140
+ .relative(root, file)
141
+ .replace(/\\/g, '/')}`,
142
+ ).toString();
143
+ }