@automateinc/fleet-types 1.0.110 → 1.0.111
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/README.md +4 -2
- package/bin/fleet-types.mjs +69 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,8 +26,10 @@ FLEET_API_PATH=../fleet-api
|
|
|
26
26
|
FLEET_API_TYPES_PATH=api/types.d.ts
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
Both paths are resolved relative to the directory where the command is run.
|
|
30
|
-
API's `
|
|
29
|
+
Both paths are resolved relative to the directory where the command is run. The generated declaration includes Fleet
|
|
30
|
+
API's global `PrismaJson` namespace from `prisma/types.d.ts`; backend-only dependencies referenced by that namespace
|
|
31
|
+
are emitted as `unknown` so the declaration remains portable. Generate the declaration from Fleet API's `AppRouter`
|
|
32
|
+
with:
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
35
|
npx @automateinc/fleet-types generate
|
package/bin/fleet-types.mjs
CHANGED
|
@@ -81,6 +81,7 @@ async function generateTypes() {
|
|
|
81
81
|
const apiRoot = path.resolve(callerRoot, fleetApiPath);
|
|
82
82
|
const outputPath = path.resolve(callerRoot, fleetApiTypesPath);
|
|
83
83
|
const routerSourcePath = path.join(apiRoot, "src/routers/trpc/index.ts");
|
|
84
|
+
const prismaJsonSourcePath = path.join(apiRoot, "prisma/types.d.ts");
|
|
84
85
|
const apiTsconfigPath = path.join(apiRoot, "tsconfig.json");
|
|
85
86
|
const executableExtension = process.platform === "win32" ? ".cmd" : "";
|
|
86
87
|
const callerBiomePath = path.join(callerRoot, `node_modules/.bin/biome${executableExtension}`);
|
|
@@ -118,10 +119,14 @@ async function generateTypes() {
|
|
|
118
119
|
rootNames: parsedConfig.fileNames,
|
|
119
120
|
});
|
|
120
121
|
const routerSource = program.getSourceFile(routerSourcePath);
|
|
122
|
+
const prismaJsonSource = program.getSourceFile(prismaJsonSourcePath);
|
|
121
123
|
|
|
122
124
|
if (!routerSource) {
|
|
123
125
|
throw new Error(`Unable to load AppRouter source at ${routerSourcePath}.`);
|
|
124
126
|
}
|
|
127
|
+
if (!prismaJsonSource) {
|
|
128
|
+
throw new Error(`Unable to load PrismaJson declarations at ${prismaJsonSourcePath}.`);
|
|
129
|
+
}
|
|
125
130
|
|
|
126
131
|
const sourceDiagnostics = [
|
|
127
132
|
...program.getSyntacticDiagnostics(routerSource),
|
|
@@ -156,6 +161,7 @@ async function generateTypes() {
|
|
|
156
161
|
const sourceFile = ts.createSourceFile(emittedPath, emittedDeclaration, ts.ScriptTarget.Latest, true);
|
|
157
162
|
const sanitizedSourceFile = sanitizeBackendTypes(ts, sourceFile);
|
|
158
163
|
const transformedSourceFile = transformProcedureOutputs(ts, sanitizedSourceFile);
|
|
164
|
+
const prismaJsonDeclaration = createPrismaJsonDeclaration(ts, prismaJsonSource);
|
|
159
165
|
const statements = transformedSourceFile.statements.filter(
|
|
160
166
|
statement =>
|
|
161
167
|
ts.isImportDeclaration(statement) ||
|
|
@@ -180,7 +186,7 @@ async function generateTypes() {
|
|
|
180
186
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
181
187
|
await writeFile(
|
|
182
188
|
outputPath,
|
|
183
|
-
`// Generated by \`npx @automateinc/fleet-types generate\`. Do not edit manually.\n${SERIALIZED_OUTPUT_TYPES}\n${appRouterDeclaration}\n`,
|
|
189
|
+
`// Generated by \`npx @automateinc/fleet-types generate\`. Do not edit manually.\n${prismaJsonDeclaration}\n${SERIALIZED_OUTPUT_TYPES}\n${appRouterDeclaration}\n`,
|
|
184
190
|
);
|
|
185
191
|
|
|
186
192
|
if (existsSync(biomePath)) {
|
|
@@ -199,6 +205,40 @@ async function generateTypes() {
|
|
|
199
205
|
console.log(`Generated ${path.relative(callerRoot, outputPath)} from ${routerSourcePath}`);
|
|
200
206
|
}
|
|
201
207
|
|
|
208
|
+
function createPrismaJsonDeclaration(ts, sourceFile) {
|
|
209
|
+
const backendTypeNames = new Set();
|
|
210
|
+
|
|
211
|
+
for (const statement of sourceFile.statements) {
|
|
212
|
+
if (!ts.isImportDeclaration(statement) || !statement.importClause) continue;
|
|
213
|
+
if (!ts.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith("@/")) continue;
|
|
214
|
+
|
|
215
|
+
if (statement.importClause.name) backendTypeNames.add(statement.importClause.name.text);
|
|
216
|
+
|
|
217
|
+
const bindings = statement.importClause.namedBindings;
|
|
218
|
+
if (bindings && ts.isNamespaceImport(bindings)) {
|
|
219
|
+
backendTypeNames.add(bindings.name.text);
|
|
220
|
+
} else if (bindings) {
|
|
221
|
+
for (const element of bindings.elements) backendTypeNames.add(element.name.text);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sanitizedSourceFile = sanitizeBackendTypes(ts, sourceFile, backendTypeNames);
|
|
226
|
+
const globalDeclarations = sanitizedSourceFile.statements.filter(
|
|
227
|
+
statement => ts.isModuleDeclaration(statement) && statement.name.text === "global",
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (globalDeclarations.length === 0) {
|
|
231
|
+
throw new Error(`PrismaJson global declarations were not found in ${sourceFile.fileName}.`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
235
|
+
|
|
236
|
+
return globalDeclarations
|
|
237
|
+
.map(statement => printer.printNode(ts.EmitHint.Unspecified, statement, sanitizedSourceFile))
|
|
238
|
+
.join("\n")
|
|
239
|
+
.replace(/^(?: {4})+/gm, indentation => "\t".repeat(indentation.length / 4));
|
|
240
|
+
}
|
|
241
|
+
|
|
202
242
|
function transformProcedureOutputs(ts, sourceFile) {
|
|
203
243
|
const procedureTypes = new Set(["TRPCMutationProcedure", "TRPCQueryProcedure", "TRPCSubscriptionProcedure"]);
|
|
204
244
|
const transformation = ts.transform(sourceFile, [
|
|
@@ -256,18 +296,15 @@ function transformProcedureOutputs(ts, sourceFile) {
|
|
|
256
296
|
return transformedSourceFile;
|
|
257
297
|
}
|
|
258
298
|
|
|
259
|
-
function sanitizeBackendTypes(ts, sourceFile) {
|
|
299
|
+
function sanitizeBackendTypes(ts, sourceFile, backendTypeNames = new Set()) {
|
|
260
300
|
const transformation = ts.transform(sourceFile, [
|
|
261
301
|
context => {
|
|
262
302
|
const visit = node => {
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (ts.isIdentifier(typeName) && typeName.text === "PrismaJson") {
|
|
269
|
-
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
270
|
-
}
|
|
303
|
+
if (
|
|
304
|
+
(ts.isTypeReferenceNode(node) || ts.isIndexedAccessTypeNode(node) || ts.isTypeQueryNode(node)) &&
|
|
305
|
+
referencesBackendType(ts, node, backendTypeNames)
|
|
306
|
+
) {
|
|
307
|
+
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
271
308
|
}
|
|
272
309
|
|
|
273
310
|
if (
|
|
@@ -292,6 +329,28 @@ function sanitizeBackendTypes(ts, sourceFile) {
|
|
|
292
329
|
return transformedSourceFile;
|
|
293
330
|
}
|
|
294
331
|
|
|
332
|
+
function referencesBackendType(ts, node, backendTypeNames) {
|
|
333
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
334
|
+
let typeName = node.typeName;
|
|
335
|
+
|
|
336
|
+
while (ts.isQualifiedName(typeName)) typeName = typeName.left;
|
|
337
|
+
|
|
338
|
+
return ts.isIdentifier(typeName) && backendTypeNames.has(typeName.text);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (ts.isIndexedAccessTypeNode(node)) return referencesBackendType(ts, node.objectType, backendTypeNames);
|
|
342
|
+
|
|
343
|
+
if (ts.isTypeQueryNode(node)) {
|
|
344
|
+
let expressionName = node.exprName;
|
|
345
|
+
|
|
346
|
+
while (ts.isQualifiedName(expressionName)) expressionName = expressionName.left;
|
|
347
|
+
|
|
348
|
+
return ts.isIdentifier(expressionName) && backendTypeNames.has(expressionName.text);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
295
354
|
function formatTypeScriptDiagnostics(ts, diagnostics, currentDirectory) {
|
|
296
355
|
return ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
297
356
|
getCanonicalFileName: fileName => fileName,
|
package/package.json
CHANGED