Files

145 lines
4.7 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import ts from 'typescript'
const root = path.resolve(import.meta.dirname, '..')
const files = []
function walk(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (['.git', 'node_modules'].includes(entry.name)) continue
const file = path.join(dir, entry.name)
if (entry.isDirectory()) walk(file)
else if (/\.(ts|tsx|mjs)$/.test(file)) files.push(file)
}
}
walk(root)
const errors = [],
routes = [],
columns = new Map()
function value(node, env) {
if (!node) return undefined
if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node))
return value(node.expression, env)
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text
if (ts.isIdentifier(node)) return env[node.text]
if (ts.isArrayLiteralExpression(node)) return node.elements.map((n) => value(n, env))
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
const a = value(node.left, env),
b = value(node.right, env)
return typeof a === 'string' && typeof b === 'string' ? a + b : undefined
}
}
function normalize(route) {
return route.replace(/\{[^}]+\}|:[^/]+/g, ':param')
}
for (const file of files) {
const source = ts.createSourceFile(
file,
fs.readFileSync(file, 'utf8'),
ts.ScriptTarget.Latest,
true,
)
for (const d of source.parseDiagnostics)
errors.push(
path.relative(root, file) + ': ' + ts.flattenDiagnosticMessageText(d.messageText, ' '),
)
function visit(node, env = {}) {
if (ts.isForOfStatement(node) && ts.isVariableDeclarationList(node.initializer)) {
const name = node.initializer.declarations[0].name,
values = value(node.expression, env)
if (ts.isIdentifier(name) && Array.isArray(values)) {
for (const item of values) visit(node.statement, { ...env, [name.text]: item })
return
}
}
if (
ts.isCallExpression(node) &&
ts.isPropertyAccessExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) &&
node.expression.expression.text === 'app' &&
['get', 'post', 'put', 'patch', 'delete'].includes(node.expression.name.text)
) {
const route = value(node.arguments[0], env)
if (route)
routes.push({
method: node.expression.name.text.toUpperCase(),
path: route,
file: path.relative(root, file),
})
}
if (
file.endsWith('/packages/database/src/schema.ts') &&
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'pgTable' &&
ts.isStringLiteral(node.arguments[0]) &&
ts.isObjectLiteralExpression(node.arguments[1])
)
columns.set(
node.arguments[0].text,
node.arguments[1].properties
.filter(ts.isPropertyAssignment)
.map((p) => p.name.getText(source).replaceAll(/['"]/g, '')),
)
ts.forEachChild(node, (child) => visit(child, env))
}
visit(source)
}
const expected = JSON.parse(fs.readFileSync(path.join(root, 'migration/routes.json'), 'utf8'))
for (const route of expected)
if (!routes.some((r) => r.method === route.method && normalize(r.path) === normalize(route.path)))
errors.push('Missing API route ' + route.method + ' ' + route.path)
const legacyColumns = JSON.parse(
fs.readFileSync(path.join(root, 'migration/database-columns.json'), 'utf8'),
)
for (const [table, names] of Object.entries(legacyColumns))
for (const name of names)
if (!columns.get(table)?.includes(name))
errors.push('Missing database column ' + table + '.' + name)
const sourcePages = [
'/',
'/en',
'/en/trump',
'/en/macro',
'/en/kol',
'/en/trades',
'/en/analytics',
'/en/settings',
'/en/archive',
'/en/posts',
'/en/btc',
'/en/methodology',
'/en/glossary',
'/en/case-studies',
'/en/privacy',
'/en/terms',
'/en/contact',
]
for (const route of sourcePages) {
const relative =
route === '/'
? 'index'
: route === '/en'
? '[locale]/index'
: route.replace('/en/', '[locale]/')
if (!fs.existsSync(path.join(root, 'apps/web/pages', relative + '.tsx')))
errors.push('Missing page ' + route)
}
const report = {
sourceFilesParsed: files.length,
legacyApiHandlers: expected.length,
registeredHttpRoutes: routes.length,
pages: sourcePages.length,
legacyTables: Object.keys(legacyColumns).length,
legacyColumns: Object.values(legacyColumns).reduce((n, a) => n + a.length, 0),
errors,
applicationExecuted: false,
buildExecuted: false,
testsExecuted: false,
}
fs.writeFileSync(
path.join(root, 'migration/static-review.json'),
JSON.stringify(report, null, 2) + '\n',
)
console.log(JSON.stringify(report, null, 2))
if (errors.length) process.exitCode = 1