Skip to content

Commit cd8a09f

Browse files
committed
progress on compilation
1 parent 14b07ac commit cd8a09f

File tree

8 files changed

+179
-111
lines changed

8 files changed

+179
-111
lines changed

packages/schema/src/parse.ts

+7-96
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,14 @@
1-
import chalk from 'chalk';
2-
import path from 'path';
3-
import { Glob } from 'bun';
4-
import babel from '@babel/parser';
5-
import _traverse from '@babel/traverse';
6-
import { buildTraverse } from './parser/build-traverse';
7-
import { parse } from 'url';
8-
9-
// bun compile has a bug where traverse gets unwrapped improperly
10-
// so we have to manually grab the default export
11-
const traverse = (_traverse as unknown as { default: typeof _traverse }).default;
12-
13-
function normalizeResourceType(fileName: string) {
14-
const dirname = path.dirname(fileName);
15-
const [resourceType] = path.basename(fileName).split('.');
16-
17-
const fullType = dirname === '.' ? resourceType : `${dirname}/${resourceType}`;
18-
const matchType = resourceType;
19-
const KlassType = matchType
20-
.split('-')
21-
.map((word) => {
22-
return word[0].toUpperCase() + word.slice(1);
23-
})
24-
.join('');
25-
26-
return {
27-
fullType,
28-
matchType,
29-
KlassType,
30-
};
31-
}
32-
33-
function parseContent($contents: string) {
34-
const ast = babel.parse($contents, {
35-
sourceType: 'module',
36-
plugins: ['classProperties', 'classPrivateProperties', 'classStaticBlock', ['typescript', {}], ['decorators', {}]],
37-
});
38-
39-
const context = {};
40-
traverse(ast, buildTraverse(context));
41-
42-
return context;
43-
}
44-
45-
async function parseSchemas(fileName: string, $contents: string) {
46-
const $potentialPrimaryResourceType = normalizeResourceType(fileName);
47-
console.log($potentialPrimaryResourceType);
48-
49-
const context = parseContent($contents);
50-
51-
// TODO - expand the schema into something useful
52-
53-
return context;
54-
}
55-
56-
function write($text: string) {
57-
process.stdout.write(chalk.gray($text));
58-
}
1+
import { getSchemaConfig } from './parser/steps/get-config';
2+
import { gatherSchemaFiles } from './parser/steps/gather-schema-files';
3+
import { compileJSONSchemas } from './parser/compile/json';
594

605
async function main() {
61-
const args = Bun.argv.slice(2);
62-
const [schemaPath] = args;
63-
64-
write(
65-
`\n\t ${chalk.yellow('$')} ${chalk.bold(chalk.greenBright('@warp-drive/') + chalk.magentaBright('schema'))} ${chalk.cyan(chalk.bold('parse'))} ${schemaPath ?? chalk.red('<missing path>')}`
66-
);
67-
68-
if (!schemaPath) {
69-
write(`\n\t${chalk.bold('💥 Error')} Please supply a path to the schema file to parse!\n`);
70-
process.exit(1);
71-
}
72-
73-
const schemaFile = Bun.file(schemaPath);
74-
const schemaFileExists = await schemaFile.exists();
75-
76-
if (!schemaFileExists) {
77-
write(`\n\t${chalk.bold('💥 Error')} ${chalk.white(schemaPath)} does not exist!`);
78-
process.exit(1);
79-
}
80-
81-
const schemaContent = await schemaFile.json();
82-
83-
const schemaDirectory = path.join(process.cwd(), path.dirname(schemaPath), schemaContent.schemas);
84-
const schemaDestination = path.join(process.cwd(), path.dirname(schemaPath), schemaContent.dest);
6+
const config = await getSchemaConfig();
857

86-
write(
87-
`\n\t\tParsing schemas from ${chalk.bold(
88-
chalk.cyan(path.relative(process.cwd(), schemaDirectory))
89-
)} into ${chalk.cyan(path.relative(process.cwd(), schemaDestination))}`
90-
);
8+
const modules = await gatherSchemaFiles(config);
9+
const compiledJson = await compileJSONSchemas(modules);
9110

92-
const glob = new Glob(`**/*.ts`);
93-
for await (const filePath of glob.scan(schemaDirectory)) {
94-
write(`\n\t\tParsing ${chalk.bold(chalk.cyan(filePath))}`);
95-
const fullPath = path.join(schemaDirectory, filePath);
96-
const file = Bun.file(fullPath);
97-
const contents = await file.text();
98-
const schemas = await parseSchemas(filePath, contents);
99-
console.log(schemas);
100-
}
11+
console.log(compiledJson);
10112
}
10213

10314
await main();

packages/schema/src/parser/compile/json-schema-spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ObjectValue, PrimitiveValue } from '../extract-json';
1+
import { ObjectValue, PrimitiveValue } from '../utils/extract-json';
22

33
/**
44
* A generic "field" that can be used to define
@@ -563,5 +563,6 @@ export type Schema = {
563563
* - for inline objects: The pattern `$${ResourceKlassName}.${fieldPath}:$field:anonymous` e.g. `$User.shippingAddress:$field:anonymous`
564564
*/
565565
'@type': string;
566+
traits: string[];
566567
fields: SchemaField[];
567568
};
+37-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
1-
import { SchemaModule } from '../build-traverse';
1+
import chalk from 'chalk';
2+
import { SchemaModule } from '../utils/process-file';
3+
import { write } from '../utils/utils';
24
import { Schema } from './json-schema-spec';
35

4-
export function compileJSON(module: SchemaModule) {
5-
const compiled: Schema = {};
6+
export async function compileJSONSchemas(modules: Map<string, SchemaModule>) {
7+
const compiled: Schema[] = [];
8+
9+
for (const [filePath, module] of modules) {
10+
if (module.exports.length === 0) {
11+
write(
12+
`\n\t\t${chalk.bold(chalk.yellow('⚠️ caution: '))} No exported schemas found in ${chalk.bold(chalk.yellow(filePath))}`
13+
);
14+
}
15+
16+
if (module.exports.length > 1) {
17+
write(
18+
`\n\t\t${chalk.bold(chalk.red('❌ error: '))} Multiple exported schemas found in ${chalk.bold(chalk.red(filePath))}`
19+
);
20+
process.exit(1);
21+
}
22+
23+
const klassSchema = module.exports[0];
24+
const { FullKlassType, KlassType, fullType } = module.$potentialPrimaryResourceType;
25+
26+
if (klassSchema.name !== FullKlassType && klassSchema.name !== KlassType) {
27+
write(
28+
`\n\t\t${chalk.bold(chalk.yellow('⚠️ caution: '))} Exported schema ${chalk.bold(klassSchema.name)} in ${fullType} does not seem to match the expected name of ${chalk.bold(FullKlassType)}`
29+
);
30+
}
31+
32+
const schema: Partial<Schema> = {
33+
'@type': fullType,
34+
};
35+
36+
// compile traits
37+
38+
// compile fields
39+
}
640

741
return compiled;
842
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import chalk from 'chalk';
2+
import path from 'path';
3+
import { Glob } from 'bun';
4+
import { SchemaModule, parseSchemaFile } from '../utils/process-file';
5+
import { write } from '../utils/utils';
6+
import { SchemaConfig } from './get-config';
7+
8+
export async function gatherSchemaFiles(config: SchemaConfig) {
9+
const { fullSchemaDirectory, relativeSchemaDirectory } = config;
10+
write(`\n\t\tParsing schema files from ${chalk.bold(chalk.cyan(relativeSchemaDirectory))}`);
11+
const modules = new Map<string, SchemaModule>();
12+
13+
const glob = new Glob(`**/*.ts`);
14+
for await (const filePath of glob.scan(fullSchemaDirectory)) {
15+
write(`\n\t\tParsing ${chalk.bold(chalk.cyan(filePath))}`);
16+
const fullPath = path.join(fullSchemaDirectory, filePath);
17+
const file = Bun.file(fullPath);
18+
const contents = await file.text();
19+
const schemaModule = await parseSchemaFile(filePath, contents);
20+
modules.set(filePath, schemaModule);
21+
}
22+
23+
return modules;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import chalk from 'chalk';
2+
import path from 'path';
3+
import { write } from '../utils/utils';
4+
5+
export type SchemaConfig = Awaited<ReturnType<typeof getSchemaConfig>>;
6+
7+
export async function getSchemaConfig() {
8+
const args = Bun.argv.slice(2);
9+
const [schemaPath] = args;
10+
11+
write(
12+
`\n\t ${chalk.yellow('$')} ${chalk.bold(chalk.greenBright('@warp-drive/') + chalk.magentaBright('schema'))} ${chalk.cyan(chalk.bold('parse'))} ${schemaPath ?? chalk.red('<missing path>')}`
13+
);
14+
15+
if (!schemaPath) {
16+
write(`\n\t${chalk.bold('💥 Error')} Please supply a path to the schema file to parse!\n`);
17+
process.exit(1);
18+
}
19+
20+
const schemaFile = Bun.file(schemaPath);
21+
const schemaFileExists = await schemaFile.exists();
22+
23+
if (!schemaFileExists) {
24+
write(`\n\t${chalk.bold('💥 Error')} ${chalk.white(schemaPath)} does not exist!`);
25+
process.exit(1);
26+
}
27+
28+
const config = await schemaFile.json();
29+
const schemaDirectory = path.join(process.cwd(), path.dirname(schemaPath), config.schemas);
30+
const schemaDestination = path.join(process.cwd(), path.dirname(schemaPath), config.dest);
31+
32+
return {
33+
_config: config,
34+
schemaPath,
35+
relativeSchemaDirectory: path.relative(process.cwd(), schemaDirectory),
36+
relativeSchemaDestination: path.relative(process.cwd(), schemaDestination),
37+
fullSchemaDirectory: schemaDirectory,
38+
fullSchemaDestination: schemaDestination,
39+
};
40+
}

packages/schema/src/parser/build-traverse.ts packages/schema/src/parser/utils/process-file.ts

+64-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
import { type TraverseOptions } from '@babel/traverse';
22
import { type ClassProperty, type Node } from '@babel/types';
3-
import { extractJSONObject } from './to-json';
3+
import { extractJSONObject } from './extract-json';
4+
import path from 'path';
5+
6+
import babel from '@babel/parser';
7+
import _traverse from '@babel/traverse';
8+
9+
// bun compile has a bug where traverse gets unwrapped improperly
10+
// so we have to manually grab the default export
11+
const traverse = (_traverse as unknown as { default: typeof _traverse }).default;
12+
13+
function normalizeResourceType(fileName: string) {
14+
const dirname = path.dirname(fileName);
15+
const [resourceType] = path.basename(fileName).split('.');
16+
17+
const fullType = dirname === '.' ? resourceType : `${dirname}/${resourceType}`;
18+
const matchType = resourceType;
19+
const FullKlassType = fullType
20+
.split('-')
21+
.map((word) => {
22+
return word[0].toUpperCase() + word.slice(1);
23+
})
24+
.join('')
25+
.split('/')
26+
.map((word) => {
27+
return word[0].toUpperCase() + word.slice(1);
28+
})
29+
.join('');
30+
const KlassType = matchType
31+
.split('-')
32+
.map((word) => {
33+
return word[0].toUpperCase() + word.slice(1);
34+
})
35+
.join('');
36+
37+
return {
38+
fullType,
39+
matchType,
40+
KlassType,
41+
FullKlassType,
42+
};
43+
}
444

545
// TODO do this via import matching
646
const TypeDecorators = new Set(['createonly', 'optional', 'readonly', 'nullable', 'readonly'] as const);
@@ -101,20 +141,16 @@ export type SchemaSource = {
101141
source: string;
102142
};
103143
export type SchemaModule = {
144+
$potentialPrimaryResourceType: ReturnType<typeof normalizeResourceType>;
104145
externals: SchemaSource[];
105146
internal: Schema[];
147+
inline: Schema[];
106148
exports: Schema[];
107149
};
108150

109-
export type Context = {
110-
klasses: Map<string, Schema>;
111-
};
112-
113-
export function buildTraverse(context: Partial<Context>): TraverseOptions {
114-
const klasses = new Map<string, Schema>();
115-
context.klasses = klasses;
151+
export function buildTraverse(schemModule: SchemaModule): TraverseOptions {
152+
let currentClass: Schema | null = null;
116153

117-
let currentClass = {} as Schema;
118154
return {
119155
FunctionDeclaration() {
120156
throw new Error('Functions are not allowed in schemas.');
@@ -125,7 +161,6 @@ export function buildTraverse(context: Partial<Context>): TraverseOptions {
125161

126162
ClassDeclaration: {
127163
enter(path) {
128-
console.log('entering');
129164
currentClass = {} as Schema;
130165

131166
// gather name
@@ -207,9 +242,27 @@ export function buildTraverse(context: Partial<Context>): TraverseOptions {
207242
});
208243
},
209244
exit(path) {
210-
console.log('exiting');
211245
console.dir(currentClass, { depth: 10 });
212246
},
213247
},
214248
};
215249
}
250+
251+
export async function parseSchemaFile(fileName: string, $contents: string): Promise<SchemaModule> {
252+
const $potentialPrimaryResourceType = normalizeResourceType(fileName);
253+
const ast = babel.parse($contents, {
254+
sourceType: 'module',
255+
plugins: ['classProperties', 'classPrivateProperties', 'classStaticBlock', ['typescript', {}], ['decorators', {}]],
256+
});
257+
258+
const context = {
259+
$potentialPrimaryResourceType,
260+
externals: [],
261+
internal: [],
262+
inline: [],
263+
exports: [],
264+
};
265+
traverse(ast, buildTraverse(context));
266+
267+
return context;
268+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import chalk from 'chalk';
2+
3+
export function write($text: string) {
4+
process.stdout.write(chalk.gray($text));
5+
}

0 commit comments

Comments
 (0)