@rvoh/psychic 2.0.3 → 2.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.
- package/dist/cjs/src/bin/index.js +5 -9
- package/dist/cjs/src/cli/helpers/ASTBuilder.js +175 -0
- package/dist/cjs/src/cli/helpers/ASTPsychicTypesBuilder.js +59 -0
- package/dist/cjs/src/cli/index.js +9 -9
- package/dist/esm/src/bin/index.js +5 -9
- package/dist/esm/src/cli/helpers/ASTBuilder.js +175 -0
- package/dist/esm/src/cli/helpers/ASTPsychicTypesBuilder.js +59 -0
- package/dist/esm/src/cli/index.js +9 -9
- package/dist/types/src/bin/index.d.ts +1 -1
- package/dist/types/src/cli/helpers/ASTBuilder.d.ts +89 -0
- package/dist/types/src/cli/helpers/ASTPsychicTypesBuilder.d.ts +28 -0
- package/package.json +4 -4
- package/dist/cjs/src/cli/helpers/TypesBuilder.js +0 -23
- package/dist/esm/src/cli/helpers/TypesBuilder.js +0 -23
- package/dist/types/src/cli/helpers/TypesBuilder.d.ts +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliFileWriter, DreamBin, DreamCLI } from '@rvoh/dream/system';
|
|
2
2
|
import * as fs from 'node:fs/promises';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import
|
|
4
|
+
import ASTPsychicTypesBuilder from '../cli/helpers/ASTPsychicTypesBuilder.js';
|
|
5
5
|
import generateController from '../generate/controller.js';
|
|
6
6
|
import generateResource from '../generate/resource.js';
|
|
7
7
|
import isObject from '../helpers/isObject.js';
|
|
@@ -54,11 +54,10 @@ export default class PsychicBin {
|
|
|
54
54
|
await CliFileWriter.revert();
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
DreamCLI.logger.logEndProgress();
|
|
57
|
+
static async syncTypes() {
|
|
58
|
+
await DreamCLI.logger.logProgress(`syncing types/psychic.ts...`, async () => {
|
|
59
|
+
await new ASTPsychicTypesBuilder().build();
|
|
60
|
+
});
|
|
62
61
|
}
|
|
63
62
|
static async syncOpenapiTypescriptFiles() {
|
|
64
63
|
DreamCLI.logger.logStartProgress(`syncing openapi types...`);
|
|
@@ -117,8 +116,5 @@ export default class PsychicBin {
|
|
|
117
116
|
output = { ...output, ...res };
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
|
-
if (Object.keys(output).length) {
|
|
121
|
-
await PsychicBin.syncTypes(output);
|
|
122
|
-
}
|
|
123
119
|
}
|
|
124
120
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { DreamApp } from '@rvoh/dream';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
const f = ts.factory;
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*
|
|
8
|
+
* This is a base class, which is inherited by the ASTSchemaBuilder,
|
|
9
|
+
* the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
|
|
10
|
+
* each of which is responsible for building up the output of the various
|
|
11
|
+
* type files consumed by dream internally.
|
|
12
|
+
*
|
|
13
|
+
* This base class is just a container for common methods used by all
|
|
14
|
+
* classes.
|
|
15
|
+
*/
|
|
16
|
+
export default class ASTBuilder {
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*
|
|
20
|
+
* builds a new line, useful for injecting new lines into AST statements
|
|
21
|
+
*/
|
|
22
|
+
newLine() {
|
|
23
|
+
return f.createIdentifier('\n');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @internal
|
|
27
|
+
*
|
|
28
|
+
* given an interface declaration, it will extrace the relevant property statement
|
|
29
|
+
* by the given property name.
|
|
30
|
+
*/
|
|
31
|
+
getPropertyFromInterface(interfaceNode, propertyName) {
|
|
32
|
+
for (const member of interfaceNode.members) {
|
|
33
|
+
if (ts.isPropertySignature(member)) {
|
|
34
|
+
if (ts.isIdentifier(member.name) && member.name.text === propertyName) {
|
|
35
|
+
return member;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @internal
|
|
43
|
+
*
|
|
44
|
+
* returns an array of string type literals which were extracted from
|
|
45
|
+
* either a type or type union, depending on what is provided
|
|
46
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
47
|
+
* an array of types given an alias
|
|
48
|
+
*/
|
|
49
|
+
extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias) {
|
|
50
|
+
const literals = [];
|
|
51
|
+
if (ts.isUnionTypeNode(typeAlias.type)) {
|
|
52
|
+
typeAlias.type.types.forEach(typeNode => {
|
|
53
|
+
if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
|
|
54
|
+
literals.push(typeNode);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else if (ts.isLiteralTypeNode(typeAlias.type) && ts.isStringLiteral(typeAlias.type.literal)) {
|
|
59
|
+
literals.push(typeAlias.type);
|
|
60
|
+
}
|
|
61
|
+
return literals;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @internal
|
|
65
|
+
*
|
|
66
|
+
* returns an array of type literals which were extracted from
|
|
67
|
+
* either a type or type union, depending on what is provided
|
|
68
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
69
|
+
* an array of types given an alias
|
|
70
|
+
*/
|
|
71
|
+
extractTypeNodesFromTypeOrUnion(typeAlias) {
|
|
72
|
+
const literals = [];
|
|
73
|
+
if (typeAlias.type && ts.isUnionTypeNode(typeAlias.type)) {
|
|
74
|
+
typeAlias.type.types.forEach(typeNode => {
|
|
75
|
+
literals.push(typeNode);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (typeAlias.type) {
|
|
79
|
+
literals.push(typeAlias.type);
|
|
80
|
+
}
|
|
81
|
+
return literals;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @internal
|
|
85
|
+
*
|
|
86
|
+
* returns the provided node iff
|
|
87
|
+
* a.) the node is an exported type alias
|
|
88
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
89
|
+
*
|
|
90
|
+
* otherwise, returns null
|
|
91
|
+
*/
|
|
92
|
+
exportedTypeAliasOrNull(node, exportName) {
|
|
93
|
+
if (ts.isTypeAliasDeclaration(node) &&
|
|
94
|
+
node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
95
|
+
(!exportName ? true : node.name.text === exportName))
|
|
96
|
+
return node;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*
|
|
102
|
+
* returns the provided node iff
|
|
103
|
+
* a.) the node is an exported interface
|
|
104
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
105
|
+
*
|
|
106
|
+
* otherwise, returns null
|
|
107
|
+
*/
|
|
108
|
+
exportedInterfaceOrNull(node, exportName) {
|
|
109
|
+
if (ts.isInterfaceDeclaration(node) &&
|
|
110
|
+
node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
111
|
+
(!exportName ? true : node.name.text === exportName))
|
|
112
|
+
return node;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* @internal
|
|
117
|
+
*
|
|
118
|
+
* returns the path to the dream.globals.ts file
|
|
119
|
+
*/
|
|
120
|
+
psychicTypesPath() {
|
|
121
|
+
const dreamApp = DreamApp.getOrFail();
|
|
122
|
+
return path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* @internal
|
|
126
|
+
*
|
|
127
|
+
* safely runs prettier against the provided output. If prettier
|
|
128
|
+
* is not installed, then the original output is returned
|
|
129
|
+
*/
|
|
130
|
+
async prettier(output) {
|
|
131
|
+
try {
|
|
132
|
+
// dynamically, safely bring in prettier.
|
|
133
|
+
// ini the event that it fails, we will return the
|
|
134
|
+
// original output, unformatted, since prettier
|
|
135
|
+
// is technically not a real dependency of dream,
|
|
136
|
+
// though psychic and dream apps are provisioned
|
|
137
|
+
// with prettier by default, so this should usually work
|
|
138
|
+
const prettier = (await import('prettier')).default;
|
|
139
|
+
const results = await prettier.format(output, {
|
|
140
|
+
parser: 'typescript',
|
|
141
|
+
semi: false,
|
|
142
|
+
singleQuote: true,
|
|
143
|
+
tabWidth: 2,
|
|
144
|
+
lineWidth: 80,
|
|
145
|
+
});
|
|
146
|
+
return typeof results === 'string' ? results : output;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// intentional noop, we don't want to raise if prettier
|
|
150
|
+
// fails, since it is possible for the end user to not
|
|
151
|
+
// want to use prettier, and it is not a required peer
|
|
152
|
+
// dependency of dream
|
|
153
|
+
return output;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* @internal
|
|
158
|
+
*
|
|
159
|
+
* given a type node, it will send back the first found generic
|
|
160
|
+
* provided to that type.
|
|
161
|
+
*/
|
|
162
|
+
getFirstGenericType(node) {
|
|
163
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
164
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
165
|
+
return node.typeArguments[0];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (ts.isCallExpression(node)) {
|
|
169
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
170
|
+
return node.typeArguments[0];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { CliFileWriter, DreamCLI } from '@rvoh/dream/system';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import PsychicApp from '../../psychic-app/index.js';
|
|
4
|
+
import ASTBuilder from './ASTBuilder.js';
|
|
5
|
+
const f = ts.factory;
|
|
6
|
+
/**
|
|
7
|
+
* Responsible for building dream globals, which can be found at
|
|
8
|
+
* types/dream.globals.ts.
|
|
9
|
+
*
|
|
10
|
+
* This class leverages internal AST building mechanisms built into
|
|
11
|
+
* typescript to manually build up object literals and interfaces
|
|
12
|
+
* for our app to consume.
|
|
13
|
+
*/
|
|
14
|
+
export default class ASTPsychicTypesBuilder extends ASTBuilder {
|
|
15
|
+
async build() {
|
|
16
|
+
const logger = DreamCLI.logger;
|
|
17
|
+
const sourceFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
18
|
+
await logger.logProgress('[psychic] building psychic types', async () => {
|
|
19
|
+
const output = await this.prettier(this.printStatements(this.buildPsychicTypes(), sourceFile));
|
|
20
|
+
await CliFileWriter.write(this.psychicTypesPath(), output);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*
|
|
26
|
+
* builds up the `export const psychicTypes = ...` statement within the types/psychic.ts
|
|
27
|
+
* file. It does this by leveraging low-level AST utils built into typescript
|
|
28
|
+
* to manually build up an object literal, cast it as a const, and write it to
|
|
29
|
+
* an exported variable.
|
|
30
|
+
*/
|
|
31
|
+
buildPsychicTypes() {
|
|
32
|
+
const psychicApp = PsychicApp.getOrFail();
|
|
33
|
+
const psychicTypesObjectLiteral = f.createObjectLiteralExpression([
|
|
34
|
+
f.createPropertyAssignment(f.createIdentifier('openapiNames'), f.createArrayLiteralExpression(Object.keys(psychicApp.openapi).map(key => f.createStringLiteral(key)))),
|
|
35
|
+
], true);
|
|
36
|
+
// add "as const" to the end of the schema object we
|
|
37
|
+
// have built before returning it
|
|
38
|
+
const constAssertion = f.createAsExpression(psychicTypesObjectLiteral, f.createKeywordTypeNode(ts.SyntaxKind.ConstKeyword));
|
|
39
|
+
const psychicTypesObjectLiteralConst = f.createVariableStatement(undefined, f.createVariableDeclarationList([
|
|
40
|
+
f.createVariableDeclaration(f.createIdentifier('psychicTypes'), undefined, undefined, constAssertion),
|
|
41
|
+
], ts.NodeFlags.Const));
|
|
42
|
+
const defaultExportIdentifier = f.createIdentifier('psychicTypes');
|
|
43
|
+
const exportDefaultStatement = f.createExportDefault(defaultExportIdentifier);
|
|
44
|
+
return [psychicTypesObjectLiteralConst, this.newLine(), exportDefaultStatement];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @internal
|
|
48
|
+
*
|
|
49
|
+
* writes the compiled statements to string.
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
printStatements(statements, sourceFile) {
|
|
53
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, omitTrailingSemicolon: true });
|
|
54
|
+
const result = printer.printList(ts.ListFormat.SourceFileStatements, f.createNodeArray(statements), sourceFile);
|
|
55
|
+
// TODO: add autogenerate disclaimer
|
|
56
|
+
return `\
|
|
57
|
+
${result}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -66,7 +66,7 @@ export default class PsychicCLI {
|
|
|
66
66
|
program
|
|
67
67
|
.command('generate:resource')
|
|
68
68
|
.alias('g:resource')
|
|
69
|
-
.description('
|
|
69
|
+
.description('Generates a Dream model with corresponding spec factory, serializer, migration, and controller with the inheritance chain leading to that controller, with fleshed out specs for each resourceful action in the controller.')
|
|
70
70
|
.option('--singular', 'generates a "resource" route instead of "resources", along with the necessary controller and spec changes')
|
|
71
71
|
.option('--only <onlyActions>', `comma separated list of resourceful endpionts (e.g. "--only=create,show"); any of:
|
|
72
72
|
- index
|
|
@@ -91,7 +91,7 @@ export default class PsychicCLI {
|
|
|
91
91
|
program
|
|
92
92
|
.command('generate:controller')
|
|
93
93
|
.alias('g:controller')
|
|
94
|
-
.description('
|
|
94
|
+
.description('Generates a controller and the inheritance chain leading to that controller, and a spec skeleton for the controller.')
|
|
95
95
|
.argument('<controllerName>', 'the name of the controller to create, including namespaces, e.g. Posts or Api/V1/Posts')
|
|
96
96
|
.argument('[actions...]', 'the names of controller actions to create')
|
|
97
97
|
.action(async (controllerName, actions) => {
|
|
@@ -101,7 +101,7 @@ export default class PsychicCLI {
|
|
|
101
101
|
});
|
|
102
102
|
program
|
|
103
103
|
.command('setup:sync:enums')
|
|
104
|
-
.description('
|
|
104
|
+
.description('Generates an initializer in your app for syncing enums to a particular path.')
|
|
105
105
|
.argument('<outfile>', 'the path from your backend directory to the location which you want the enums copied. Should end with .ts, i.e. "../client/src/api/enums.ts"')
|
|
106
106
|
.option('--initializer-filename <initializerFilename>', 'the name you want the file to be in your initializers folder. defaults to `sync-enums.ts`')
|
|
107
107
|
.action(async (outfile, { initializerName, }) => {
|
|
@@ -114,7 +114,7 @@ export default class PsychicCLI {
|
|
|
114
114
|
});
|
|
115
115
|
program
|
|
116
116
|
.command('setup:sync:openapi-redux')
|
|
117
|
-
.description('
|
|
117
|
+
.description('Generates openapi redux bindings to connect one of your openapi files to one of your clients.')
|
|
118
118
|
.option('--schema-file <schemaFile>', 'the path from your api root to the openapi file you wish to use to generate your schema, i.e. ./src/openapi/openapi.json')
|
|
119
119
|
.option('--api-file <apiFile>', 'the path to the boilerplate api file that will be used to scaffold your backend endpoints together with, i.e. ../client/app/api.ts')
|
|
120
120
|
.option('--api-import <apiImport>', 'the camelCased name of the export from your api module, i.e. emptyBackendApi')
|
|
@@ -136,7 +136,7 @@ export default class PsychicCLI {
|
|
|
136
136
|
});
|
|
137
137
|
program
|
|
138
138
|
.command('setup:sync:openapi-typescript')
|
|
139
|
-
.description('
|
|
139
|
+
.description('Generates an initializer in your app for converting one of your openapi files to typescript.')
|
|
140
140
|
.argument('<openapiFilepath>', 'the path from your backend directory to the openapi file you wish to scan, i.e. "./src/openapi/openapi.json"')
|
|
141
141
|
.argument('<outfile>', 'the path from your backend directory to the location which you want the openapi types written to. Must end with .d.ts, i.e. "./src/conf/openapi/openapi.types.d.ts"')
|
|
142
142
|
.option('--initializer-filename <initializerFilename>', 'the name you want the file to be in your initializers folder. defaults to `sync-openapi-typescript.ts`')
|
|
@@ -150,7 +150,7 @@ export default class PsychicCLI {
|
|
|
150
150
|
});
|
|
151
151
|
program
|
|
152
152
|
.command('routes')
|
|
153
|
-
.description('
|
|
153
|
+
.description('Prints a list of routes defined by the application, including path arguments and the controller/action reached by the route.')
|
|
154
154
|
.action(async () => {
|
|
155
155
|
await initializePsychicApp();
|
|
156
156
|
PsychicBin.printRoutes();
|
|
@@ -158,7 +158,7 @@ export default class PsychicCLI {
|
|
|
158
158
|
});
|
|
159
159
|
program
|
|
160
160
|
.command('sync')
|
|
161
|
-
.description(
|
|
161
|
+
.description("Generates types from the current state of the database. Generates OpenAPI specs from @OpenAPI decorated controller actions. Additional sync actions may be customized with `on('cli:sync', async () => {})` in conf/app.ts or in an initializer in `conf/initializers/`.")
|
|
162
162
|
.option('--ignore-errors')
|
|
163
163
|
.option('--schema-only')
|
|
164
164
|
.action(async (options) => {
|
|
@@ -184,13 +184,13 @@ export default class PsychicCLI {
|
|
|
184
184
|
});
|
|
185
185
|
program
|
|
186
186
|
.command('sync:routes')
|
|
187
|
-
.description('
|
|
187
|
+
.description('Reads the routes generated by your app and generates a cache file, which is then used to give autocomplete support to the route helper and other things.')
|
|
188
188
|
.action(async () => {
|
|
189
189
|
await PsychicBin.syncRoutes();
|
|
190
190
|
});
|
|
191
191
|
program
|
|
192
192
|
.command('sync:openapi')
|
|
193
|
-
.description('
|
|
193
|
+
.description('Syncs openapi.json file to current state of all psychic controllers within the app')
|
|
194
194
|
.action(async () => {
|
|
195
195
|
await initializePsychicApp();
|
|
196
196
|
await PsychicBin.syncOpenapiJson();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliFileWriter, DreamBin, DreamCLI } from '@rvoh/dream/system';
|
|
2
2
|
import * as fs from 'node:fs/promises';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import
|
|
4
|
+
import ASTPsychicTypesBuilder from '../cli/helpers/ASTPsychicTypesBuilder.js';
|
|
5
5
|
import generateController from '../generate/controller.js';
|
|
6
6
|
import generateResource from '../generate/resource.js';
|
|
7
7
|
import isObject from '../helpers/isObject.js';
|
|
@@ -54,11 +54,10 @@ export default class PsychicBin {
|
|
|
54
54
|
await CliFileWriter.revert();
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
DreamCLI.logger.logEndProgress();
|
|
57
|
+
static async syncTypes() {
|
|
58
|
+
await DreamCLI.logger.logProgress(`syncing types/psychic.ts...`, async () => {
|
|
59
|
+
await new ASTPsychicTypesBuilder().build();
|
|
60
|
+
});
|
|
62
61
|
}
|
|
63
62
|
static async syncOpenapiTypescriptFiles() {
|
|
64
63
|
DreamCLI.logger.logStartProgress(`syncing openapi types...`);
|
|
@@ -117,8 +116,5 @@ export default class PsychicBin {
|
|
|
117
116
|
output = { ...output, ...res };
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
|
-
if (Object.keys(output).length) {
|
|
121
|
-
await PsychicBin.syncTypes(output);
|
|
122
|
-
}
|
|
123
119
|
}
|
|
124
120
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { DreamApp } from '@rvoh/dream';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
const f = ts.factory;
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*
|
|
8
|
+
* This is a base class, which is inherited by the ASTSchemaBuilder,
|
|
9
|
+
* the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
|
|
10
|
+
* each of which is responsible for building up the output of the various
|
|
11
|
+
* type files consumed by dream internally.
|
|
12
|
+
*
|
|
13
|
+
* This base class is just a container for common methods used by all
|
|
14
|
+
* classes.
|
|
15
|
+
*/
|
|
16
|
+
export default class ASTBuilder {
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*
|
|
20
|
+
* builds a new line, useful for injecting new lines into AST statements
|
|
21
|
+
*/
|
|
22
|
+
newLine() {
|
|
23
|
+
return f.createIdentifier('\n');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @internal
|
|
27
|
+
*
|
|
28
|
+
* given an interface declaration, it will extrace the relevant property statement
|
|
29
|
+
* by the given property name.
|
|
30
|
+
*/
|
|
31
|
+
getPropertyFromInterface(interfaceNode, propertyName) {
|
|
32
|
+
for (const member of interfaceNode.members) {
|
|
33
|
+
if (ts.isPropertySignature(member)) {
|
|
34
|
+
if (ts.isIdentifier(member.name) && member.name.text === propertyName) {
|
|
35
|
+
return member;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @internal
|
|
43
|
+
*
|
|
44
|
+
* returns an array of string type literals which were extracted from
|
|
45
|
+
* either a type or type union, depending on what is provided
|
|
46
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
47
|
+
* an array of types given an alias
|
|
48
|
+
*/
|
|
49
|
+
extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias) {
|
|
50
|
+
const literals = [];
|
|
51
|
+
if (ts.isUnionTypeNode(typeAlias.type)) {
|
|
52
|
+
typeAlias.type.types.forEach(typeNode => {
|
|
53
|
+
if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
|
|
54
|
+
literals.push(typeNode);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else if (ts.isLiteralTypeNode(typeAlias.type) && ts.isStringLiteral(typeAlias.type.literal)) {
|
|
59
|
+
literals.push(typeAlias.type);
|
|
60
|
+
}
|
|
61
|
+
return literals;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @internal
|
|
65
|
+
*
|
|
66
|
+
* returns an array of type literals which were extracted from
|
|
67
|
+
* either a type or type union, depending on what is provided
|
|
68
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
69
|
+
* an array of types given an alias
|
|
70
|
+
*/
|
|
71
|
+
extractTypeNodesFromTypeOrUnion(typeAlias) {
|
|
72
|
+
const literals = [];
|
|
73
|
+
if (typeAlias.type && ts.isUnionTypeNode(typeAlias.type)) {
|
|
74
|
+
typeAlias.type.types.forEach(typeNode => {
|
|
75
|
+
literals.push(typeNode);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (typeAlias.type) {
|
|
79
|
+
literals.push(typeAlias.type);
|
|
80
|
+
}
|
|
81
|
+
return literals;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @internal
|
|
85
|
+
*
|
|
86
|
+
* returns the provided node iff
|
|
87
|
+
* a.) the node is an exported type alias
|
|
88
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
89
|
+
*
|
|
90
|
+
* otherwise, returns null
|
|
91
|
+
*/
|
|
92
|
+
exportedTypeAliasOrNull(node, exportName) {
|
|
93
|
+
if (ts.isTypeAliasDeclaration(node) &&
|
|
94
|
+
node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
95
|
+
(!exportName ? true : node.name.text === exportName))
|
|
96
|
+
return node;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* @internal
|
|
101
|
+
*
|
|
102
|
+
* returns the provided node iff
|
|
103
|
+
* a.) the node is an exported interface
|
|
104
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
105
|
+
*
|
|
106
|
+
* otherwise, returns null
|
|
107
|
+
*/
|
|
108
|
+
exportedInterfaceOrNull(node, exportName) {
|
|
109
|
+
if (ts.isInterfaceDeclaration(node) &&
|
|
110
|
+
node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
|
|
111
|
+
(!exportName ? true : node.name.text === exportName))
|
|
112
|
+
return node;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* @internal
|
|
117
|
+
*
|
|
118
|
+
* returns the path to the dream.globals.ts file
|
|
119
|
+
*/
|
|
120
|
+
psychicTypesPath() {
|
|
121
|
+
const dreamApp = DreamApp.getOrFail();
|
|
122
|
+
return path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* @internal
|
|
126
|
+
*
|
|
127
|
+
* safely runs prettier against the provided output. If prettier
|
|
128
|
+
* is not installed, then the original output is returned
|
|
129
|
+
*/
|
|
130
|
+
async prettier(output) {
|
|
131
|
+
try {
|
|
132
|
+
// dynamically, safely bring in prettier.
|
|
133
|
+
// ini the event that it fails, we will return the
|
|
134
|
+
// original output, unformatted, since prettier
|
|
135
|
+
// is technically not a real dependency of dream,
|
|
136
|
+
// though psychic and dream apps are provisioned
|
|
137
|
+
// with prettier by default, so this should usually work
|
|
138
|
+
const prettier = (await import('prettier')).default;
|
|
139
|
+
const results = await prettier.format(output, {
|
|
140
|
+
parser: 'typescript',
|
|
141
|
+
semi: false,
|
|
142
|
+
singleQuote: true,
|
|
143
|
+
tabWidth: 2,
|
|
144
|
+
lineWidth: 80,
|
|
145
|
+
});
|
|
146
|
+
return typeof results === 'string' ? results : output;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// intentional noop, we don't want to raise if prettier
|
|
150
|
+
// fails, since it is possible for the end user to not
|
|
151
|
+
// want to use prettier, and it is not a required peer
|
|
152
|
+
// dependency of dream
|
|
153
|
+
return output;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* @internal
|
|
158
|
+
*
|
|
159
|
+
* given a type node, it will send back the first found generic
|
|
160
|
+
* provided to that type.
|
|
161
|
+
*/
|
|
162
|
+
getFirstGenericType(node) {
|
|
163
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
164
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
165
|
+
return node.typeArguments[0];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (ts.isCallExpression(node)) {
|
|
169
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
170
|
+
return node.typeArguments[0];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { CliFileWriter, DreamCLI } from '@rvoh/dream/system';
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import PsychicApp from '../../psychic-app/index.js';
|
|
4
|
+
import ASTBuilder from './ASTBuilder.js';
|
|
5
|
+
const f = ts.factory;
|
|
6
|
+
/**
|
|
7
|
+
* Responsible for building dream globals, which can be found at
|
|
8
|
+
* types/dream.globals.ts.
|
|
9
|
+
*
|
|
10
|
+
* This class leverages internal AST building mechanisms built into
|
|
11
|
+
* typescript to manually build up object literals and interfaces
|
|
12
|
+
* for our app to consume.
|
|
13
|
+
*/
|
|
14
|
+
export default class ASTPsychicTypesBuilder extends ASTBuilder {
|
|
15
|
+
async build() {
|
|
16
|
+
const logger = DreamCLI.logger;
|
|
17
|
+
const sourceFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
18
|
+
await logger.logProgress('[psychic] building psychic types', async () => {
|
|
19
|
+
const output = await this.prettier(this.printStatements(this.buildPsychicTypes(), sourceFile));
|
|
20
|
+
await CliFileWriter.write(this.psychicTypesPath(), output);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*
|
|
26
|
+
* builds up the `export const psychicTypes = ...` statement within the types/psychic.ts
|
|
27
|
+
* file. It does this by leveraging low-level AST utils built into typescript
|
|
28
|
+
* to manually build up an object literal, cast it as a const, and write it to
|
|
29
|
+
* an exported variable.
|
|
30
|
+
*/
|
|
31
|
+
buildPsychicTypes() {
|
|
32
|
+
const psychicApp = PsychicApp.getOrFail();
|
|
33
|
+
const psychicTypesObjectLiteral = f.createObjectLiteralExpression([
|
|
34
|
+
f.createPropertyAssignment(f.createIdentifier('openapiNames'), f.createArrayLiteralExpression(Object.keys(psychicApp.openapi).map(key => f.createStringLiteral(key)))),
|
|
35
|
+
], true);
|
|
36
|
+
// add "as const" to the end of the schema object we
|
|
37
|
+
// have built before returning it
|
|
38
|
+
const constAssertion = f.createAsExpression(psychicTypesObjectLiteral, f.createKeywordTypeNode(ts.SyntaxKind.ConstKeyword));
|
|
39
|
+
const psychicTypesObjectLiteralConst = f.createVariableStatement(undefined, f.createVariableDeclarationList([
|
|
40
|
+
f.createVariableDeclaration(f.createIdentifier('psychicTypes'), undefined, undefined, constAssertion),
|
|
41
|
+
], ts.NodeFlags.Const));
|
|
42
|
+
const defaultExportIdentifier = f.createIdentifier('psychicTypes');
|
|
43
|
+
const exportDefaultStatement = f.createExportDefault(defaultExportIdentifier);
|
|
44
|
+
return [psychicTypesObjectLiteralConst, this.newLine(), exportDefaultStatement];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @internal
|
|
48
|
+
*
|
|
49
|
+
* writes the compiled statements to string.
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
printStatements(statements, sourceFile) {
|
|
53
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, omitTrailingSemicolon: true });
|
|
54
|
+
const result = printer.printList(ts.ListFormat.SourceFileStatements, f.createNodeArray(statements), sourceFile);
|
|
55
|
+
// TODO: add autogenerate disclaimer
|
|
56
|
+
return `\
|
|
57
|
+
${result}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -66,7 +66,7 @@ export default class PsychicCLI {
|
|
|
66
66
|
program
|
|
67
67
|
.command('generate:resource')
|
|
68
68
|
.alias('g:resource')
|
|
69
|
-
.description('
|
|
69
|
+
.description('Generates a Dream model with corresponding spec factory, serializer, migration, and controller with the inheritance chain leading to that controller, with fleshed out specs for each resourceful action in the controller.')
|
|
70
70
|
.option('--singular', 'generates a "resource" route instead of "resources", along with the necessary controller and spec changes')
|
|
71
71
|
.option('--only <onlyActions>', `comma separated list of resourceful endpionts (e.g. "--only=create,show"); any of:
|
|
72
72
|
- index
|
|
@@ -91,7 +91,7 @@ export default class PsychicCLI {
|
|
|
91
91
|
program
|
|
92
92
|
.command('generate:controller')
|
|
93
93
|
.alias('g:controller')
|
|
94
|
-
.description('
|
|
94
|
+
.description('Generates a controller and the inheritance chain leading to that controller, and a spec skeleton for the controller.')
|
|
95
95
|
.argument('<controllerName>', 'the name of the controller to create, including namespaces, e.g. Posts or Api/V1/Posts')
|
|
96
96
|
.argument('[actions...]', 'the names of controller actions to create')
|
|
97
97
|
.action(async (controllerName, actions) => {
|
|
@@ -101,7 +101,7 @@ export default class PsychicCLI {
|
|
|
101
101
|
});
|
|
102
102
|
program
|
|
103
103
|
.command('setup:sync:enums')
|
|
104
|
-
.description('
|
|
104
|
+
.description('Generates an initializer in your app for syncing enums to a particular path.')
|
|
105
105
|
.argument('<outfile>', 'the path from your backend directory to the location which you want the enums copied. Should end with .ts, i.e. "../client/src/api/enums.ts"')
|
|
106
106
|
.option('--initializer-filename <initializerFilename>', 'the name you want the file to be in your initializers folder. defaults to `sync-enums.ts`')
|
|
107
107
|
.action(async (outfile, { initializerName, }) => {
|
|
@@ -114,7 +114,7 @@ export default class PsychicCLI {
|
|
|
114
114
|
});
|
|
115
115
|
program
|
|
116
116
|
.command('setup:sync:openapi-redux')
|
|
117
|
-
.description('
|
|
117
|
+
.description('Generates openapi redux bindings to connect one of your openapi files to one of your clients.')
|
|
118
118
|
.option('--schema-file <schemaFile>', 'the path from your api root to the openapi file you wish to use to generate your schema, i.e. ./src/openapi/openapi.json')
|
|
119
119
|
.option('--api-file <apiFile>', 'the path to the boilerplate api file that will be used to scaffold your backend endpoints together with, i.e. ../client/app/api.ts')
|
|
120
120
|
.option('--api-import <apiImport>', 'the camelCased name of the export from your api module, i.e. emptyBackendApi')
|
|
@@ -136,7 +136,7 @@ export default class PsychicCLI {
|
|
|
136
136
|
});
|
|
137
137
|
program
|
|
138
138
|
.command('setup:sync:openapi-typescript')
|
|
139
|
-
.description('
|
|
139
|
+
.description('Generates an initializer in your app for converting one of your openapi files to typescript.')
|
|
140
140
|
.argument('<openapiFilepath>', 'the path from your backend directory to the openapi file you wish to scan, i.e. "./src/openapi/openapi.json"')
|
|
141
141
|
.argument('<outfile>', 'the path from your backend directory to the location which you want the openapi types written to. Must end with .d.ts, i.e. "./src/conf/openapi/openapi.types.d.ts"')
|
|
142
142
|
.option('--initializer-filename <initializerFilename>', 'the name you want the file to be in your initializers folder. defaults to `sync-openapi-typescript.ts`')
|
|
@@ -150,7 +150,7 @@ export default class PsychicCLI {
|
|
|
150
150
|
});
|
|
151
151
|
program
|
|
152
152
|
.command('routes')
|
|
153
|
-
.description('
|
|
153
|
+
.description('Prints a list of routes defined by the application, including path arguments and the controller/action reached by the route.')
|
|
154
154
|
.action(async () => {
|
|
155
155
|
await initializePsychicApp();
|
|
156
156
|
PsychicBin.printRoutes();
|
|
@@ -158,7 +158,7 @@ export default class PsychicCLI {
|
|
|
158
158
|
});
|
|
159
159
|
program
|
|
160
160
|
.command('sync')
|
|
161
|
-
.description(
|
|
161
|
+
.description("Generates types from the current state of the database. Generates OpenAPI specs from @OpenAPI decorated controller actions. Additional sync actions may be customized with `on('cli:sync', async () => {})` in conf/app.ts or in an initializer in `conf/initializers/`.")
|
|
162
162
|
.option('--ignore-errors')
|
|
163
163
|
.option('--schema-only')
|
|
164
164
|
.action(async (options) => {
|
|
@@ -184,13 +184,13 @@ export default class PsychicCLI {
|
|
|
184
184
|
});
|
|
185
185
|
program
|
|
186
186
|
.command('sync:routes')
|
|
187
|
-
.description('
|
|
187
|
+
.description('Reads the routes generated by your app and generates a cache file, which is then used to give autocomplete support to the route helper and other things.')
|
|
188
188
|
.action(async () => {
|
|
189
189
|
await PsychicBin.syncRoutes();
|
|
190
190
|
});
|
|
191
191
|
program
|
|
192
192
|
.command('sync:openapi')
|
|
193
|
-
.description('
|
|
193
|
+
.description('Syncs openapi.json file to current state of all psychic controllers within the app')
|
|
194
194
|
.action(async () => {
|
|
195
195
|
await initializePsychicApp();
|
|
196
196
|
await PsychicBin.syncOpenapiJson();
|
|
@@ -13,7 +13,7 @@ export default class PsychicBin {
|
|
|
13
13
|
schemaOnly?: boolean;
|
|
14
14
|
}): Promise<void>;
|
|
15
15
|
static postSync(): Promise<void>;
|
|
16
|
-
static syncTypes(
|
|
16
|
+
static syncTypes(): Promise<void>;
|
|
17
17
|
static syncOpenapiTypescriptFiles(): Promise<void>;
|
|
18
18
|
static syncOpenapiJson(): Promise<void>;
|
|
19
19
|
static syncRoutes(): Promise<void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* This is a base class, which is inherited by the ASTSchemaBuilder,
|
|
6
|
+
* the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
|
|
7
|
+
* each of which is responsible for building up the output of the various
|
|
8
|
+
* type files consumed by dream internally.
|
|
9
|
+
*
|
|
10
|
+
* This base class is just a container for common methods used by all
|
|
11
|
+
* classes.
|
|
12
|
+
*/
|
|
13
|
+
export default class ASTBuilder {
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*
|
|
17
|
+
* builds a new line, useful for injecting new lines into AST statements
|
|
18
|
+
*/
|
|
19
|
+
protected newLine(): ts.Identifier;
|
|
20
|
+
/**
|
|
21
|
+
* @internal
|
|
22
|
+
*
|
|
23
|
+
* given an interface declaration, it will extrace the relevant property statement
|
|
24
|
+
* by the given property name.
|
|
25
|
+
*/
|
|
26
|
+
protected getPropertyFromInterface(interfaceNode: ts.InterfaceDeclaration, propertyName: string): ts.PropertySignature | null;
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*
|
|
30
|
+
* returns an array of string type literals which were extracted from
|
|
31
|
+
* either a type or type union, depending on what is provided
|
|
32
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
33
|
+
* an array of types given an alias
|
|
34
|
+
*/
|
|
35
|
+
protected extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias: ts.TypeAliasDeclaration): (ts.LiteralTypeNode & {
|
|
36
|
+
literal: {
|
|
37
|
+
text: string;
|
|
38
|
+
};
|
|
39
|
+
})[];
|
|
40
|
+
/**
|
|
41
|
+
* @internal
|
|
42
|
+
*
|
|
43
|
+
* returns an array of type literals which were extracted from
|
|
44
|
+
* either a type or type union, depending on what is provided
|
|
45
|
+
* for the typeAlias. this allows you to safely and easily collect
|
|
46
|
+
* an array of types given an alias
|
|
47
|
+
*/
|
|
48
|
+
protected extractTypeNodesFromTypeOrUnion(typeAlias: ts.TypeAliasDeclaration | ts.PropertySignature): ts.TypeNode[];
|
|
49
|
+
/**
|
|
50
|
+
* @internal
|
|
51
|
+
*
|
|
52
|
+
* returns the provided node iff
|
|
53
|
+
* a.) the node is an exported type alias
|
|
54
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
55
|
+
*
|
|
56
|
+
* otherwise, returns null
|
|
57
|
+
*/
|
|
58
|
+
protected exportedTypeAliasOrNull(node: ts.Node, exportName?: string): ts.TypeAliasDeclaration | null;
|
|
59
|
+
/**
|
|
60
|
+
* @internal
|
|
61
|
+
*
|
|
62
|
+
* returns the provided node iff
|
|
63
|
+
* a.) the node is an exported interface
|
|
64
|
+
* b.) the exported name matches the provided name (or else there was no name provided)
|
|
65
|
+
*
|
|
66
|
+
* otherwise, returns null
|
|
67
|
+
*/
|
|
68
|
+
protected exportedInterfaceOrNull(node: ts.Node, exportName?: string): ts.InterfaceDeclaration | null;
|
|
69
|
+
/**
|
|
70
|
+
* @internal
|
|
71
|
+
*
|
|
72
|
+
* returns the path to the dream.globals.ts file
|
|
73
|
+
*/
|
|
74
|
+
protected psychicTypesPath(): string;
|
|
75
|
+
/**
|
|
76
|
+
* @internal
|
|
77
|
+
*
|
|
78
|
+
* safely runs prettier against the provided output. If prettier
|
|
79
|
+
* is not installed, then the original output is returned
|
|
80
|
+
*/
|
|
81
|
+
protected prettier(output: string): Promise<string>;
|
|
82
|
+
/**
|
|
83
|
+
* @internal
|
|
84
|
+
*
|
|
85
|
+
* given a type node, it will send back the first found generic
|
|
86
|
+
* provided to that type.
|
|
87
|
+
*/
|
|
88
|
+
protected getFirstGenericType(node: ts.Node): ts.TypeNode | null;
|
|
89
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ASTBuilder from './ASTBuilder.js';
|
|
2
|
+
/**
|
|
3
|
+
* Responsible for building dream globals, which can be found at
|
|
4
|
+
* types/dream.globals.ts.
|
|
5
|
+
*
|
|
6
|
+
* This class leverages internal AST building mechanisms built into
|
|
7
|
+
* typescript to manually build up object literals and interfaces
|
|
8
|
+
* for our app to consume.
|
|
9
|
+
*/
|
|
10
|
+
export default class ASTPsychicTypesBuilder extends ASTBuilder {
|
|
11
|
+
build(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
*
|
|
15
|
+
* builds up the `export const psychicTypes = ...` statement within the types/psychic.ts
|
|
16
|
+
* file. It does this by leveraging low-level AST utils built into typescript
|
|
17
|
+
* to manually build up an object literal, cast it as a const, and write it to
|
|
18
|
+
* an exported variable.
|
|
19
|
+
*/
|
|
20
|
+
private buildPsychicTypes;
|
|
21
|
+
/**
|
|
22
|
+
* @internal
|
|
23
|
+
*
|
|
24
|
+
* writes the compiled statements to string.
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
private printStatements;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@rvoh/psychic",
|
|
4
4
|
"description": "Typescript web framework",
|
|
5
|
-
"version": "2.0
|
|
5
|
+
"version": "2.1.0",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@rvoh/dream": "^2.0.3",
|
|
82
82
|
"@types/express": "^5.0.1",
|
|
83
83
|
"commander": "^12.1.0",
|
|
84
|
-
"express": "^5.1
|
|
84
|
+
"express": "^5.2.1",
|
|
85
85
|
"openapi-typescript": "^7.8.0"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@rvoh/dream": "^2.0.3",
|
|
91
91
|
"@rvoh/dream-spec-helpers": "^2.0.0",
|
|
92
92
|
"@rvoh/psychic-spec-helpers": "^2.0.0",
|
|
93
|
-
"@types/express": "^5.0.
|
|
93
|
+
"@types/express": "^5.0.6",
|
|
94
94
|
"@types/express-session": "^1.18.2",
|
|
95
95
|
"@types/node": "^22.17.1",
|
|
96
96
|
"@types/passport": "^0",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"@types/supertest": "^6.0.3",
|
|
100
100
|
"@typescript/analyze-trace": "^0.10.1",
|
|
101
101
|
"eslint": "^9.19.0",
|
|
102
|
-
"express": "^5.1
|
|
102
|
+
"express": "^5.2.1",
|
|
103
103
|
"express-session": "^1.18.2",
|
|
104
104
|
"jsdom": "^26.1.0",
|
|
105
105
|
"kysely": "^0.28.5",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { DreamApp } from '@rvoh/dream';
|
|
2
|
-
import { CliFileWriter } from '@rvoh/dream/system';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import PsychicApp from '../../psychic-app/index.js';
|
|
5
|
-
export default class TypesBuilder {
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
-
static async sync(customTypes = undefined) {
|
|
8
|
-
const dreamApp = DreamApp.getOrFail();
|
|
9
|
-
const schemaPath = path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
|
|
10
|
-
await CliFileWriter.write(schemaPath, this.build(customTypes));
|
|
11
|
-
}
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
-
static build(customTypes = undefined) {
|
|
14
|
-
const psychicApp = PsychicApp.getOrFail();
|
|
15
|
-
const output = {
|
|
16
|
-
openapiNames: Object.keys(psychicApp.openapi),
|
|
17
|
-
...(customTypes || {}),
|
|
18
|
-
};
|
|
19
|
-
return `const psychicTypes = ${JSON.stringify(output, null, 2)} as const
|
|
20
|
-
|
|
21
|
-
export default psychicTypes`;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { DreamApp } from '@rvoh/dream';
|
|
2
|
-
import { CliFileWriter } from '@rvoh/dream/system';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import PsychicApp from '../../psychic-app/index.js';
|
|
5
|
-
export default class TypesBuilder {
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
-
static async sync(customTypes = undefined) {
|
|
8
|
-
const dreamApp = DreamApp.getOrFail();
|
|
9
|
-
const schemaPath = path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
|
|
10
|
-
await CliFileWriter.write(schemaPath, this.build(customTypes));
|
|
11
|
-
}
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
-
static build(customTypes = undefined) {
|
|
14
|
-
const psychicApp = PsychicApp.getOrFail();
|
|
15
|
-
const output = {
|
|
16
|
-
openapiNames: Object.keys(psychicApp.openapi),
|
|
17
|
-
...(customTypes || {}),
|
|
18
|
-
};
|
|
19
|
-
return `const psychicTypes = ${JSON.stringify(output, null, 2)} as const
|
|
20
|
-
|
|
21
|
-
export default psychicTypes`;
|
|
22
|
-
}
|
|
23
|
-
}
|