@smartive/graphql-magic 8.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/README.md +8 -15
  3. package/dist/bin/gqm.cjs +1812 -0
  4. package/dist/cjs/index.cjs +544 -6
  5. package/dist/esm/bin/gqm.d.ts +2 -0
  6. package/dist/esm/bin/gqm.js +121 -0
  7. package/dist/esm/bin/gqm.js.map +1 -0
  8. package/dist/esm/client/mutations.d.ts +2 -2
  9. package/dist/esm/client/mutations.js +2 -1
  10. package/dist/esm/client/mutations.js.map +1 -1
  11. package/dist/esm/gqm/codegen.d.ts +2 -0
  12. package/dist/esm/gqm/codegen.js +46 -0
  13. package/dist/esm/gqm/codegen.js.map +1 -0
  14. package/dist/esm/gqm/index.d.ts +9 -0
  15. package/dist/esm/gqm/index.js +11 -0
  16. package/dist/esm/gqm/index.js.map +1 -0
  17. package/dist/esm/gqm/parse-knexfile.d.ts +2 -0
  18. package/dist/esm/gqm/parse-knexfile.js +19 -0
  19. package/dist/esm/gqm/parse-knexfile.js.map +1 -0
  20. package/dist/esm/gqm/parse-models.d.ts +2 -0
  21. package/dist/esm/gqm/parse-models.js +19 -0
  22. package/dist/esm/gqm/parse-models.js.map +1 -0
  23. package/dist/esm/gqm/readline.d.ts +1 -0
  24. package/dist/esm/gqm/readline.js +14 -0
  25. package/dist/esm/gqm/readline.js.map +1 -0
  26. package/dist/esm/gqm/settings.d.ts +9 -0
  27. package/dist/esm/gqm/settings.js +98 -0
  28. package/dist/esm/gqm/settings.js.map +1 -0
  29. package/dist/esm/gqm/static-eval.d.ts +3 -0
  30. package/dist/esm/gqm/static-eval.js +188 -0
  31. package/dist/esm/gqm/static-eval.js.map +1 -0
  32. package/dist/esm/gqm/templates.d.ts +4 -0
  33. package/dist/esm/gqm/templates.js +62 -0
  34. package/dist/esm/gqm/templates.js.map +1 -0
  35. package/dist/esm/gqm/utils.d.ts +2 -0
  36. package/dist/esm/gqm/utils.js +22 -0
  37. package/dist/esm/gqm/utils.js.map +1 -0
  38. package/dist/esm/gqm/visitor.d.ts +8 -0
  39. package/dist/esm/gqm/visitor.js +15 -0
  40. package/dist/esm/gqm/visitor.js.map +1 -0
  41. package/dist/esm/index.d.ts +1 -0
  42. package/dist/esm/index.js +1 -0
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/migrations/generate.d.ts +1 -0
  45. package/dist/esm/migrations/generate.js +11 -0
  46. package/dist/esm/migrations/generate.js.map +1 -1
  47. package/dist/esm/models/models.d.ts +6 -0
  48. package/dist/esm/models/utils.d.ts +8 -0
  49. package/dist/esm/models/utils.js +1 -0
  50. package/dist/esm/models/utils.js.map +1 -1
  51. package/dist/esm/schema/generate.js +14 -2
  52. package/dist/esm/schema/generate.js.map +1 -1
  53. package/package.json +17 -4
  54. package/src/bin/gqm.ts +146 -0
  55. package/src/client/mutations.ts +4 -3
  56. package/src/gqm/codegen.ts +47 -0
  57. package/src/gqm/index.ts +11 -0
  58. package/src/gqm/parse-knexfile.ts +21 -0
  59. package/src/gqm/parse-models.ts +24 -0
  60. package/src/gqm/readline.ts +15 -0
  61. package/src/gqm/settings.ts +112 -0
  62. package/src/gqm/static-eval.ts +203 -0
  63. package/src/gqm/templates.ts +64 -0
  64. package/src/gqm/utils.ts +23 -0
  65. package/src/gqm/visitor.ts +29 -0
  66. package/src/index.ts +1 -0
  67. package/src/migrations/generate.ts +13 -0
  68. package/src/models/models.ts +5 -0
  69. package/src/models/utils.ts +3 -0
  70. package/src/schema/generate.ts +14 -1
  71. package/tests/utils/generate-migration.ts +2 -13
package/src/bin/gqm.ts ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import { config } from 'dotenv';
5
+ import knex from 'knex';
6
+ import { simpleGit } from 'simple-git';
7
+ import {
8
+ MigrationGenerator,
9
+ generateDBModels,
10
+ generateKnexTables,
11
+ generateMutations,
12
+ getMigrationDate,
13
+ printSchemaFromModels,
14
+ } from '..';
15
+ import { generateGraphqlApiTypes, generateGraphqlClientTypes } from '../gqm/codegen';
16
+ import { KNEXFILE_PATH, parseKnexfile } from '../gqm/parse-knexfile';
17
+ import { parseModels } from '../gqm/parse-models';
18
+ import { readLine } from '../gqm/readline';
19
+ import { ensureFileExists, getSetting, getSettings, writeToFile } from '../gqm/settings';
20
+ import { KNEXFILE } from '../gqm/templates';
21
+
22
+ config({
23
+ path: '.env',
24
+ });
25
+ config({
26
+ path: '.env.local',
27
+ });
28
+
29
+ program.description('The graphql-magic cli.');
30
+
31
+ program
32
+ .command('setup')
33
+ .description('Set up the project')
34
+ .action(async () => {
35
+ await getSettings();
36
+ ensureFileExists(KNEXFILE_PATH, KNEXFILE);
37
+ });
38
+
39
+ program
40
+ .command('generate')
41
+ .description('Generate all the things')
42
+ .action(async () => {
43
+ const rawModels = await parseModels();
44
+ const generatedFolderPath = await getSetting('generatedFolderPath');
45
+ writeToFile(`${generatedFolderPath}/schema.graphql`, printSchemaFromModels(rawModels));
46
+ writeToFile(`${generatedFolderPath}/client/mutations.ts`, generateMutations(rawModels));
47
+ writeToFile(`${generatedFolderPath}/db/index.ts`, generateDBModels(rawModels));
48
+ writeToFile(`${generatedFolderPath}/db/knex.ts`, generateKnexTables(rawModels));
49
+ await generateGraphqlApiTypes();
50
+ await generateGraphqlClientTypes();
51
+ });
52
+
53
+ program
54
+ .command('generate-models')
55
+ .description('Generate models.json')
56
+ .action(async () => {
57
+ const rawModels = await parseModels();
58
+ const generatedFolderPath = await getSetting('generatedFolderPath');
59
+ writeToFile(`${generatedFolderPath}/models.json`, JSON.stringify(rawModels, null, 2));
60
+ });
61
+
62
+ program
63
+ .command('generate-schema')
64
+ .description('Generate schema')
65
+ .action(async () => {
66
+ const rawModels = await parseModels();
67
+ const generatedFolderPath = await getSetting('generatedFolderPath');
68
+ writeToFile(`${generatedFolderPath}/schema.graphql`, printSchemaFromModels(rawModels));
69
+ });
70
+
71
+ program
72
+ .command('generate-mutation-queries')
73
+ .description('Generate mutation-queries')
74
+ .action(async () => {
75
+ const rawModels = await parseModels();
76
+ const generatedFolderPath = await getSetting('generatedFolderPath');
77
+ writeToFile(`${generatedFolderPath}/client/mutations.ts`, generateMutations(rawModels));
78
+ });
79
+
80
+ program
81
+ .command('generate-db-types')
82
+ .description('Generate DB types')
83
+ .action(async () => {
84
+ const rawModels = await parseModels();
85
+ const generatedFolderPath = await getSetting('generatedFolderPath');
86
+ writeToFile(`${generatedFolderPath}/db/index.ts`, generateMutations(rawModels));
87
+ });
88
+
89
+ program
90
+ .command('generate-knex-types')
91
+ .description('Generate Knex types')
92
+ .action(async () => {
93
+ const rawModels = await parseModels();
94
+ const generatedFolderPath = await getSetting('generatedFolderPath');
95
+ writeToFile(`${generatedFolderPath}/db/knex.ts`, generateKnexTables(rawModels));
96
+ });
97
+
98
+ program
99
+ .command('generate-graphql-api-types')
100
+ .description('Generate Graphql API types')
101
+ .action(async () => {
102
+ await generateGraphqlApiTypes();
103
+ });
104
+
105
+ program
106
+ .command('generate-graphql-client-types')
107
+ .description('Generate Graphql client types')
108
+ .action(async () => {
109
+ await generateGraphqlClientTypes();
110
+ });
111
+
112
+ program
113
+ .command('generate-migration')
114
+ .description('Generate Migration')
115
+ .action(async () => {
116
+ const git = simpleGit();
117
+
118
+ let name = process.argv[2] || (await git.branch()).current.split('/').pop();
119
+
120
+ if (name && ['staging', 'production'].includes(name)) {
121
+ name = await readLine('Migration name:');
122
+ }
123
+
124
+ const knexfile = await parseKnexfile();
125
+ const db = knex(knexfile);
126
+
127
+ try {
128
+ const rawModels = await parseModels();
129
+ const migrations = await new MigrationGenerator(db, rawModels).generate();
130
+
131
+ writeToFile(`migrations/${getMigrationDate()}_${name}.ts`, migrations);
132
+ } finally {
133
+ await db.destroy();
134
+ }
135
+ });
136
+
137
+ program
138
+ .command('*', { noHelp: true })
139
+ .description('Invalid command')
140
+ .action(() => {
141
+ console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
142
+ process.exit(1);
143
+ });
144
+
145
+ // Parse the arguments
146
+ program.parse(process.argv);
@@ -1,11 +1,12 @@
1
1
  import upperCase from 'lodash/upperCase';
2
- import { Models } from '../models/models';
2
+ import { isEntityModel } from '..';
3
+ import { RawModels } from '../models/models';
3
4
 
4
5
  const constantCase = (str: string) => upperCase(str).replace(/ /g, '_');
5
6
 
6
- export const generateMutations = (models: Models) => {
7
+ export const generateMutations = (models: RawModels) => {
7
8
  const parts: string[] = [];
8
- for (const { name, creatable, updatable, deletable } of models) {
9
+ for (const { name, creatable, updatable, deletable } of models.filter(isEntityModel)) {
9
10
  if (creatable) {
10
11
  parts.push(
11
12
  `export const CREATE_${constantCase(
@@ -0,0 +1,47 @@
1
+ import { generate } from '@graphql-codegen/cli';
2
+ import { getSetting } from './settings';
3
+
4
+ export const generateGraphqlApiTypes = async () => {
5
+ const generatedFolderPath = await getSetting('generatedFolderPath');
6
+ await generate({
7
+ overwrite: true,
8
+ schema: `${generatedFolderPath}/schema.graphql`,
9
+ documents: null,
10
+ generates: {
11
+ [`${generatedFolderPath}/api/index.ts`]: {
12
+ plugins: ['typescript', 'typescript-resolvers', { add: { content: `import { DateTime } from 'luxon';` } }],
13
+ },
14
+ },
15
+ config: {
16
+ scalars: {
17
+ DateTime: 'DateTime',
18
+ },
19
+ },
20
+ });
21
+ };
22
+
23
+ export const generateGraphqlClientTypes = async () => {
24
+ const generatedFolderPath = await getSetting('generatedFolderPath');
25
+ await generate({
26
+ schema: `${generatedFolderPath}/schema.graphql`,
27
+ documents: ['./src/**/*.ts', './src/**/*.tsx'],
28
+ generates: {
29
+ [`${generatedFolderPath}/client/index.ts`]: {
30
+ plugins: ['typescript', 'typescript-operations', 'typescript-compatibility'],
31
+ },
32
+ },
33
+ config: {
34
+ preResolveTypes: true, // Simplifies the generated types
35
+ namingConvention: 'keep', // Keeps naming as-is
36
+ nonOptionalTypename: true, // Forces `__typename` on all selection sets
37
+ skipTypeNameForRoot: true, // Don't generate __typename for root types
38
+ avoidOptionals: {
39
+ // Avoids optionals on the level of the field
40
+ field: true,
41
+ },
42
+ scalars: {
43
+ DateTime: 'string',
44
+ },
45
+ },
46
+ });
47
+ };
@@ -0,0 +1,11 @@
1
+ // created from 'create-ts-index'
2
+
3
+ export * from './codegen';
4
+ export * from './parse-knexfile';
5
+ export * from './parse-models';
6
+ export * from './readline';
7
+ export * from './settings';
8
+ export * from './static-eval';
9
+ export * from './templates';
10
+ export * from './utils';
11
+ export * from './visitor';
@@ -0,0 +1,21 @@
1
+ import { IndentationText, Project } from 'ts-morph';
2
+ import { ensureFileExists } from './settings';
3
+ import { staticEval } from './static-eval';
4
+ import { KNEXFILE } from './templates';
5
+ import { findDeclarationInFile } from './utils';
6
+
7
+ export const KNEXFILE_PATH = `knexfile.ts`;
8
+
9
+ export const parseKnexfile = async () => {
10
+ const project = new Project({
11
+ manipulationSettings: {
12
+ indentationText: IndentationText.TwoSpaces,
13
+ },
14
+ });
15
+ ensureFileExists(KNEXFILE_PATH, KNEXFILE);
16
+
17
+ const sourceFile = project.addSourceFileAtPath(KNEXFILE_PATH);
18
+ const configDeclaration = findDeclarationInFile(sourceFile, 'config');
19
+ const config = staticEval(configDeclaration, {});
20
+ return config;
21
+ };
@@ -0,0 +1,24 @@
1
+ import { IndentationText, Project } from 'ts-morph';
2
+ import { RawModels } from '..';
3
+ import { getSetting, writeToFile } from './settings';
4
+ import { staticEval } from './static-eval';
5
+ import { findDeclarationInFile } from './utils';
6
+
7
+ export const parseModels = async () => {
8
+ const project = new Project({
9
+ manipulationSettings: {
10
+ indentationText: IndentationText.TwoSpaces,
11
+ },
12
+ });
13
+ const modelsPath = await getSetting('modelsPath');
14
+ const sourceFile = project.addSourceFileAtPath(modelsPath);
15
+
16
+ const modelsDeclaration = findDeclarationInFile(sourceFile, 'rawModels');
17
+
18
+ const rawModels = staticEval(modelsDeclaration, {});
19
+
20
+ const generatedFolderPath = await getSetting('generatedFolderPath');
21
+ writeToFile(`${generatedFolderPath}/models.json`, JSON.stringify(rawModels, null, 2));
22
+
23
+ return rawModels as RawModels;
24
+ };
@@ -0,0 +1,15 @@
1
+ import readline from 'readline';
2
+
3
+ export const readLine = (prompt: string): Promise<string> => {
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout,
7
+ });
8
+
9
+ return new Promise<string>((resolve) => {
10
+ rl.question(prompt, (answer) => {
11
+ rl.close();
12
+ resolve(answer);
13
+ });
14
+ });
15
+ };
@@ -0,0 +1,112 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { dirname } from 'path';
3
+ import { readLine } from './readline';
4
+ import { CLIENT_CODEGEN, EMPTY_MODELS, GRAPHQL_CODEGEN } from './templates';
5
+
6
+ const SETTINGS_PATH = '.gqmrc.json';
7
+
8
+ const DEFAULTS = {
9
+ modelsPath: {
10
+ question: 'What is the models path?',
11
+ defaultValue: 'src/config/models.ts',
12
+ init: (path: string) => {
13
+ ensureFileExists(path, EMPTY_MODELS);
14
+ },
15
+ },
16
+ generatedFolderPath: {
17
+ question: 'What is the path for generated stuff?',
18
+ defaultValue: 'src/generated',
19
+ init: (path: string) => {
20
+ ensureFileExists(`${path}/.gitkeep`, '');
21
+ ensureFileExists(`${path}/db/.gitkeep`, '');
22
+ ensureFileExists(`${path}/api/.gitkeep`, '');
23
+ ensureFileExists(`${path}/client/.gitkeep`, '');
24
+ ensureFileExists(`graphql-codegen.yml`, GRAPHQL_CODEGEN(path));
25
+ ensureFileExists(`client-codegen.yml`, CLIENT_CODEGEN(path));
26
+ },
27
+ },
28
+ };
29
+
30
+ type Settings = {
31
+ modelsPath: string;
32
+ generatedFolderPath: string;
33
+ };
34
+
35
+ const initSetting = async (name: string) => {
36
+ const { question, defaultValue, init } = DEFAULTS[name];
37
+ const value = (await readLine(`${question} (${defaultValue})`)) || defaultValue;
38
+ init(value);
39
+ return value;
40
+ };
41
+
42
+ const initSettings = async () => {
43
+ const settings: Settings = {} as Settings;
44
+ for (const name of Object.keys(DEFAULTS)) {
45
+ settings[name] = await initSetting(name);
46
+ }
47
+ saveSettings(settings);
48
+ };
49
+
50
+ const saveSettings = (settings: Settings) => {
51
+ writeToFile(SETTINGS_PATH, JSON.stringify(settings, null, 2));
52
+ };
53
+
54
+ export const getSettings = async (): Promise<Settings> => {
55
+ if (!existsSync(SETTINGS_PATH)) {
56
+ await initSettings();
57
+ }
58
+ return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8'));
59
+ };
60
+
61
+ export const getSetting = async (name: keyof Settings): Promise<string> => {
62
+ const settings = await getSettings();
63
+ if (!(name in settings)) {
64
+ settings[name] = await initSetting(name);
65
+ saveSettings(settings);
66
+ }
67
+ return settings[name];
68
+ };
69
+
70
+ const ensureDirectoryExists = (filePath: string) => {
71
+ const dir = dirname(filePath);
72
+
73
+ if (existsSync(dir)) {
74
+ return true;
75
+ }
76
+
77
+ ensureDirectoryExists(dir);
78
+
79
+ try {
80
+ mkdirSync(dir);
81
+ return true;
82
+ } catch (err) {
83
+ if (err.code === 'EEXIST') {
84
+ return true;
85
+ }
86
+ throw err;
87
+ }
88
+ };
89
+
90
+ export const ensureFileExists = (filePath: string, content: string) => {
91
+ if (!existsSync(filePath)) {
92
+ console.info(`Creating ${filePath}`);
93
+ ensureDirectoryExists(filePath);
94
+ writeFileSync(filePath, content);
95
+ }
96
+ };
97
+
98
+ export const writeToFile = (filePath: string, content: string) => {
99
+ ensureDirectoryExists(filePath);
100
+ if (existsSync(filePath)) {
101
+ const currentContent = readFileSync(filePath, 'utf-8');
102
+ if (content === currentContent) {
103
+ // console.info(`${filePath} unchanged`);
104
+ } else {
105
+ writeFileSync(filePath, content);
106
+ console.info(`${filePath} updated`);
107
+ }
108
+ } else {
109
+ writeFileSync(filePath, content);
110
+ console.info(`Created ${filePath}`);
111
+ }
112
+ };
@@ -0,0 +1,203 @@
1
+ import { Dictionary } from 'lodash';
2
+ import {
3
+ CaseClause,
4
+ ElementAccessExpression,
5
+ Identifier,
6
+ Node,
7
+ ObjectLiteralExpression,
8
+ PrefixUnaryExpression,
9
+ ShorthandPropertyAssignment,
10
+ SyntaxKind,
11
+ TemplateExpression,
12
+ TemplateTail,
13
+ } from 'ts-morph';
14
+ import { Visitor, visit } from './visitor';
15
+
16
+ export const staticEval = (node: Node, context: Dictionary<unknown>) =>
17
+ visit<unknown, Dictionary<unknown>>(node, context, visitor);
18
+
19
+ const visitor: Visitor<unknown, Dictionary<unknown>> = {
20
+ undefined: () => undefined,
21
+ [SyntaxKind.VariableDeclaration]: (node, context) => staticEval(node.getInitializer(), context),
22
+ [SyntaxKind.ArrayLiteralExpression]: (node, context) => {
23
+ const values: unknown[] = [];
24
+ for (const value of node.getElements()) {
25
+ if (value.isKind(SyntaxKind.SpreadElement)) {
26
+ values.push(...(staticEval(value, context) as unknown[]));
27
+ } else {
28
+ values.push(staticEval(value, context));
29
+ }
30
+ }
31
+ return values;
32
+ },
33
+ [SyntaxKind.ObjectLiteralExpression]: (node: ObjectLiteralExpression, context) => {
34
+ const result: Dictionary<unknown> = {};
35
+ for (const property of node.getProperties()) {
36
+ Object.assign(result, staticEval(property, context));
37
+ }
38
+ return result;
39
+ },
40
+ [SyntaxKind.StringLiteral]: (node) => node.getLiteralValue(),
41
+ [SyntaxKind.PropertyAssignment]: (node, context) => ({
42
+ [node.getName()]: staticEval(node.getInitializer(), context),
43
+ }),
44
+ [SyntaxKind.ShorthandPropertyAssignment]: (node: ShorthandPropertyAssignment, context) => ({
45
+ [node.getName()]: staticEval(node.getNameNode(), context),
46
+ }),
47
+ [SyntaxKind.SpreadElement]: (node, context) => staticEval(node.getExpression(), context),
48
+ [SyntaxKind.SpreadAssignment]: (node, context) => staticEval(node.getExpression(), context),
49
+ [SyntaxKind.Identifier]: (node: Identifier, context) => {
50
+ switch (node.getText()) {
51
+ case 'undefined':
52
+ return undefined;
53
+ case 'process':
54
+ return process;
55
+ }
56
+ const definitionNodes = node.getDefinitionNodes();
57
+ if (!definitionNodes.length) {
58
+ throw new Error(`No definition node found for identifier ${node.getText()}.`);
59
+ }
60
+ return staticEval(definitionNodes[0], context);
61
+ },
62
+ [SyntaxKind.ParenthesizedExpression]: (node, context) => staticEval(node.getExpression(), context),
63
+ [SyntaxKind.AsExpression]: (node, context) => staticEval(node.getExpression(), context),
64
+ [SyntaxKind.ConditionalExpression]: (node, context) =>
65
+ staticEval(node.getCondition(), context)
66
+ ? staticEval(node.getWhenTrue(), context)
67
+ : staticEval(node.getWhenFalse(), context),
68
+ [SyntaxKind.TrueKeyword]: () => true,
69
+ [SyntaxKind.FalseKeyword]: () => false,
70
+ [SyntaxKind.NumericLiteral]: (node) => node.getLiteralValue(),
71
+ [SyntaxKind.CallExpression]: (node, context) => {
72
+ const method = staticEval(node.getExpression(), context) as (...args: unknown[]) => unknown;
73
+ const args = node.getArguments().map((arg) => staticEval(arg, context));
74
+ return method(...args);
75
+ },
76
+ [SyntaxKind.PropertyAccessExpression]: (node, context) => {
77
+ const target = staticEval(node.getExpression(), context);
78
+ const property = target[node.getName()];
79
+ if (typeof property === 'function') {
80
+ if (Array.isArray(target)) {
81
+ switch (node.getName()) {
82
+ case 'map':
83
+ case 'flatMap':
84
+ case 'includes':
85
+ case 'some':
86
+ case 'find':
87
+ case 'filter':
88
+ return target[node.getName()].bind(target);
89
+ }
90
+ } else if (typeof target === 'string') {
91
+ const name = node.getName() as keyof string;
92
+ switch (name) {
93
+ case 'slice':
94
+ case 'toUpperCase':
95
+ case 'toLowerCase':
96
+ return target[name].bind(target);
97
+ }
98
+ }
99
+ throw new Error(`Cannot handle method ${node.getName()} on type ${typeof target}`);
100
+ }
101
+
102
+ return property;
103
+ },
104
+ [SyntaxKind.ArrowFunction]: (node, context) => {
105
+ return (...args: unknown[]) => {
106
+ const parameters: Dictionary<unknown> = {};
107
+ let i = 0;
108
+ for (const parameter of node.getParameters()) {
109
+ parameters[parameter.getName()] = args[i];
110
+ i++;
111
+ }
112
+ return staticEval(node.getBody(), { ...context, ...parameters });
113
+ };
114
+ },
115
+ [SyntaxKind.Block]: (node, context) => {
116
+ for (const statement of node.getStatements()) {
117
+ return staticEval(statement, context);
118
+ }
119
+ },
120
+ [SyntaxKind.CaseClause]: (node, context) => {
121
+ const statements = node.getStatements();
122
+ if (statements.length !== 1) {
123
+ console.error(node.getText());
124
+ throw new Error(`Can only handle code blocks with 1 statement.`);
125
+ }
126
+ return staticEval(statements[0], context);
127
+ },
128
+ [SyntaxKind.DefaultClause]: (node, context) => {
129
+ const statements = node.getStatements();
130
+ if (statements.length !== 1) {
131
+ console.error(node.getText());
132
+ throw new Error(`Can only handle code blocks with exactly 1 statement.`);
133
+ }
134
+ return staticEval(statements[0], context);
135
+ },
136
+ [SyntaxKind.ReturnStatement]: (node, context) => {
137
+ return staticEval(node.getExpression(), context);
138
+ },
139
+ [SyntaxKind.SwitchStatement]: (node, context) => {
140
+ const value = staticEval(node.getExpression(), context);
141
+ let active = false;
142
+ for (const clause of node.getCaseBlock().getClauses()) {
143
+ switch (clause.getKind()) {
144
+ case SyntaxKind.DefaultClause:
145
+ return staticEval(clause, context);
146
+ case SyntaxKind.CaseClause: {
147
+ const caseClause: CaseClause = clause.asKindOrThrow(SyntaxKind.CaseClause);
148
+ if (caseClause.getStatements().length && active) {
149
+ return staticEval(clause, context);
150
+ }
151
+ const caseValue = staticEval(caseClause.getExpression(), context);
152
+ if (value === caseValue) {
153
+ active = true;
154
+ if (caseClause.getStatements().length) {
155
+ return staticEval(clause, context);
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ },
162
+ [SyntaxKind.Parameter]: (node, context) => context[node.getName()],
163
+ [SyntaxKind.BinaryExpression]: (node, context) => {
164
+ switch (node.getOperatorToken().getKind()) {
165
+ case SyntaxKind.EqualsEqualsEqualsToken:
166
+ return staticEval(node.getLeft(), context) === staticEval(node.getRight(), context);
167
+ case SyntaxKind.BarBarToken:
168
+ return staticEval(node.getLeft(), context) || staticEval(node.getRight(), context);
169
+ default:
170
+ throw new Error(`Cannot handle operator of kind ${node.getOperatorToken().getKindName()}`);
171
+ }
172
+ },
173
+ [SyntaxKind.SatisfiesExpression]: (node, context) => staticEval(node.getExpression(), context),
174
+ [SyntaxKind.TemplateExpression]: (node: TemplateExpression, context) =>
175
+ node.getHead().getLiteralText() +
176
+ node
177
+ .getTemplateSpans()
178
+ .map((span) => (staticEval(span.getExpression(), context) as string) + staticEval(span.getLiteral(), context))
179
+ .join(''),
180
+ [SyntaxKind.TemplateTail]: (node: TemplateTail) => node.getLiteralText(),
181
+ [SyntaxKind.TemplateMiddle]: (node) => node.getLiteralText(),
182
+ [SyntaxKind.PrefixUnaryExpression]: (node: PrefixUnaryExpression, context) => {
183
+ switch (node.getOperatorToken()) {
184
+ case SyntaxKind.PlusToken:
185
+ return +staticEval(node.getOperand(), context);
186
+ case SyntaxKind.MinusToken:
187
+ return -staticEval(node.getOperand(), context);
188
+ case SyntaxKind.TildeToken:
189
+ return ~staticEval(node.getOperand(), context);
190
+ case SyntaxKind.ExclamationToken:
191
+ return !staticEval(node.getOperand(), context);
192
+ case SyntaxKind.PlusPlusToken:
193
+ case SyntaxKind.MinusMinusToken:
194
+ throw new Error(`Cannot handle assignments.`);
195
+ }
196
+ },
197
+ [SyntaxKind.ElementAccessExpression]: (node: ElementAccessExpression, context) => {
198
+ const target = staticEval(node.getExpression(), context);
199
+ const argument = staticEval(node.getArgumentExpression(), context) as string;
200
+ return target[argument];
201
+ },
202
+ [SyntaxKind.NoSubstitutionTemplateLiteral]: (node) => node.getLiteralValue(),
203
+ };
@@ -0,0 +1,64 @@
1
+ export const EMPTY_MODELS = `
2
+ import { RawModels, getModels } from '@smartive/graphql-magic';
3
+
4
+ export const rawModels: RawModels = [
5
+ {
6
+ kind: 'entity',
7
+ name: 'User',
8
+ fields: []
9
+ },
10
+ ]
11
+
12
+ export const models = getModels(rawModels);
13
+ `;
14
+
15
+ export const GRAPHQL_CODEGEN = (path: string) => `
16
+ overwrite: true
17
+ schema: '${path}/schema.graphql'
18
+ documents: null
19
+ generates:
20
+ ${path}/api/index.ts:
21
+ plugins:
22
+ - 'typescript'
23
+ - 'typescript-resolvers'
24
+ - add:
25
+ content: "import { DateTime } from 'luxon'"
26
+ config:
27
+ scalars:
28
+ DateTime: DateTime
29
+ `;
30
+
31
+ export const CLIENT_CODEGEN = (path: string) => `
32
+ schema: ${path}/schema.graphql
33
+ documents: [ './src/**/*.ts', './src/**/*.tsx' ]
34
+ generates:
35
+ ${path}/client/index.ts:
36
+ plugins:
37
+ - typescript
38
+ - typescript-operations
39
+ - typescript-compatibility
40
+
41
+ config:
42
+ preResolveTypes: true # Simplifies the generated types
43
+ namingConvention: keep # Keeps naming as-is
44
+ nonOptionalTypename: true # Forces \`__typename\` on all selection sets
45
+ skipTypeNameForRoot: true # Don't generate __typename for root types
46
+ avoidOptionals: # Avoids optionals on the level of the field
47
+ field: true
48
+ scalars:
49
+ DateTime: string
50
+ `;
51
+
52
+ export const KNEXFILE = `
53
+ const config = {
54
+ client: 'postgresql',
55
+ connection: {
56
+ host: process.env.DATABASE_HOST,
57
+ database: process.env.DATABASE_NAME,
58
+ user: process.env.DATABASE_USER,
59
+ password: process.env.DATABASE_PASSWORD,
60
+ },
61
+ } as const;
62
+
63
+ export default config;
64
+ `;
@@ -0,0 +1,23 @@
1
+ import { SourceFile, SyntaxKind, SyntaxList } from 'ts-morph';
2
+
3
+ export const findDeclarationInFile = (sourceFile: SourceFile, name: string) => {
4
+ const syntaxList = sourceFile.getChildrenOfKind(SyntaxKind.SyntaxList)[0];
5
+ if (!syntaxList) {
6
+ throw new Error('No SyntaxList');
7
+ }
8
+ const declaration = findDeclaration(syntaxList, name);
9
+ if (!declaration) {
10
+ throw new Error(`No ${name} declaration`);
11
+ }
12
+ return declaration;
13
+ };
14
+
15
+ const findDeclaration = (syntaxList: SyntaxList, name: string) => {
16
+ for (const variableStatement of syntaxList.getChildrenOfKind(SyntaxKind.VariableStatement)) {
17
+ for (const declaration of variableStatement.getDeclarationList().getDeclarations()) {
18
+ if (declaration.getName() === name) {
19
+ return declaration;
20
+ }
21
+ }
22
+ }
23
+ };