Files
winos-config/scripts/gen-dsc-types.ts
2025-12-01 00:11:55 -05:00

132 lines
3.9 KiB
TypeScript

import { Command, FileSystem, Path } from '@effect/platform';
import { BunContext, BunRuntime } from '@effect/platform-bun';
import { Effect, Logger, LogLevel, pipe, Stream, String } from 'effect';
import { convertFromString } from './json-schema-to-effect';
// Helper function to collect stream output as a string
const runString = <E, R>(
stream: Stream.Stream<Uint8Array, E, R>,
): Effect.Effect<string, E, R> =>
stream.pipe(Stream.decodeText(), Stream.runFold(String.empty, String.concat));
const runCommand = (command: Command.Command) =>
pipe(
Command.start(command),
Effect.flatMap((process) =>
Effect.all(
[
process.exitCode,
runString(process.stdout),
runString(process.stderr),
],
{ concurrency: 3 },
),
),
Effect.map(([exitCode, stdout, stderr]) => ({ exitCode, stdout, stderr })),
);
const getDscJsonSchema = (schemaType: string) =>
Effect.gen(function* () {
const commandParts = ['dsc', 'schema', '--type', schemaType] as const;
yield* Effect.logDebug(`Running command: ${commandParts.join(' ')}`);
const schemaString = yield* Command.make(...commandParts).pipe(
Command.string,
);
return schemaString.trim();
});
const getPossibleDscSchemaTypes = () =>
Effect.gen(function* () {
yield* Effect.logDebug('Getting possible DSC schema types...');
const brokenCommand = Command.make(
'dsc',
'schema',
'--type',
'something-that-hopefully-never-exists',
);
const { stderr: brokenCommandOutput } = yield* runCommand(brokenCommand);
const possibleTypes = brokenCommandOutput
.split('\n')
.filter((line) => line.includes('possible values:'))
.map((line) => {
const values = line.match(/\[possible values: (.+)\]/)?.[1];
return values?.split(', ') ?? [];
})[0];
if (!possibleTypes) return yield* Effect.die('No possible types found');
yield* Effect.logDebug(`Found ${possibleTypes.length} schema types`);
return possibleTypes;
});
const jsonSchemaToEffectSchema = (params: {
jsonSchema: string;
name: string;
}) =>
Effect.gen(function* () {
yield* Effect.logDebug(`Converting schema: ${params.name}`);
const result = convertFromString(params.jsonSchema, params.name);
yield* Effect.logDebug(
`Converted "${params.name}" - types: ${result.typeNames.join(', ')}`,
);
if (result.recursiveTypes.length > 0) {
yield* Effect.logInfo(
`Recursive types in ${params.name}: ${result.recursiveTypes.join(', ')}`,
);
}
return result.code;
});
const genDscTypes = Effect.gen(function* () {
yield* Effect.log('Starting DSC types generation...');
const possibleTypes = yield* getPossibleDscSchemaTypes();
yield* Effect.log(`Found schema types: ${possibleTypes.join(', ')}`);
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const outputDir = path.join(process.cwd(), 'src', 'dsc-schema-types');
yield* Effect.logDebug(`Output directory: ${outputDir}`);
if (yield* fs.exists(outputDir)) {
yield* Effect.log('Removing existing output directory...');
yield* fs.remove(outputDir, { recursive: true, force: true });
}
yield* fs.makeDirectory(outputDir, { recursive: true });
for (const schemaType of possibleTypes) {
yield* Effect.logDebug(`Processing: ${schemaType}`);
const jsonSchema = yield* getDscJsonSchema(schemaType);
const effectSchema = yield* jsonSchemaToEffectSchema({
jsonSchema,
name: schemaType,
});
const outputPath = path.join(outputDir, `${schemaType}.gen.ts`);
yield* fs.writeFileString(outputPath, effectSchema);
yield* Effect.log(`Generated: ${schemaType}.gen.ts`);
}
yield* Effect.log('DSC types generation complete!');
});
BunRuntime.runMain(
genDscTypes.pipe(
Effect.provide(BunContext.layer),
Logger.withMinimumLogLevel(LogLevel.Debug),
Effect.scoped,
),
);